From e0c05d59e3022fdaf6821bf695301f7010c0064e Mon Sep 17 00:00:00 2001 From: Raven Black Date: Mon, 13 Apr 2026 15:58:48 -0400 Subject: [PATCH 001/750] StreamRateLimiter change relnote (#44195) Relnote to accompany the change made in #44145, in case of external filters using the API. Signed-off-by: Raven Black --- changelogs/current.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8fce0410e1fa0..9cb796b1d4a79 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -135,6 +135,18 @@ minor_behavior_changes: and the caller does not want to manage shared pointer ownership. The new shared pointer methods are intended for use cases where the caller needs to keep the ownerships to the route, cluster, or virtual host. +- area: http + change: | + ``StreamRateLimiter`` constructor was updated to always take a pre-constructed and initialized + ``TokenBucket``, and remove the first argument ``max_buffered_data`` and the ``TimeSource&`` + argument. + The ``write_stats_cb`` callback signature was changed from taking a ``bool`` argument + "something is buffered" to a ``uint64_t`` "how many bytes are buffered". + If already initializing with a ``TokenBucket``, you may need to call ``maybeReset`` on that + bucket before passing it to ``StreamRateLimiter`` to ensure equivalent initialization. + If previously constructing a ``StreamRateLimiter`` using ``max_buffered_data``, use + ``StreamRateLimiter::simpleTokenBucket(limit_kbps, time_source)`` to initialize an equivalent + ``TokenBucket``. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* From 4fa57a689f5f5a1cc9c77f5329fc95ac41686a63 Mon Sep 17 00:00:00 2001 From: tyxia <72890320+tyxia@users.noreply.github.com> Date: Mon, 13 Apr 2026 21:23:20 -0400 Subject: [PATCH 002/750] perf: Improve memory footprint on router, part 2 (#44409) Credit: Optimization from @penguingao make_shared does one single heap allocation of the shared_ptr and the object, resulting in more compact memory usage compared to two separate allocations when reset(new) is used. Save ~6% of memory (0.1G out of 1.6G) on router creation Before: image After image --------- Signed-off-by: tyxia --- source/common/router/config_impl.cc | 24 ++++++++++++------------ source/common/router/config_impl.h | 16 ++++++++++------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 38b86fe87b818..d5a8fc5cf0e42 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -73,28 +73,28 @@ class RouteCreator { RouteEntryImplBaseConstSharedPtr route; switch (route_config.match().path_specifier_case()) { case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPrefix: - route.reset(new PrefixRouteEntryImpl(vhost, route_config, factory_context, validator, - creation_status)); + route = std::make_shared(vhost, route_config, factory_context, + validator, creation_status); break; case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPath: - route.reset( - new PathRouteEntryImpl(vhost, route_config, factory_context, validator, creation_status)); + route = std::make_shared(vhost, route_config, factory_context, validator, + creation_status); break; case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kSafeRegex: - route.reset(new RegexRouteEntryImpl(vhost, route_config, factory_context, validator, - creation_status)); + route = std::make_shared(vhost, route_config, factory_context, validator, + creation_status); break; case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kConnectMatcher: - route.reset(new ConnectRouteEntryImpl(vhost, route_config, factory_context, validator, - creation_status)); + route = std::make_shared(vhost, route_config, factory_context, + validator, creation_status); break; case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPathSeparatedPrefix: - route.reset(new PathSeparatedPrefixRouteEntryImpl(vhost, route_config, factory_context, - validator, creation_status)); + route = std::make_shared( + vhost, route_config, factory_context, validator, creation_status); break; case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPathMatchPolicy: - route.reset(new UriTemplateMatcherRouteEntryImpl(vhost, route_config, factory_context, - validator, creation_status)); + route = std::make_shared( + vhost, route_config, factory_context, validator, creation_status); break; case envoy::config::route::v3::RouteMatch::PathSpecifierCase::PATH_SPECIFIER_NOT_SET: break; // return the error below. diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index f8d8bcdbc21f0..bd06a71ec7266 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -951,7 +951,6 @@ class UriTemplateMatcherRouteEntryImpl : public RouteEntryImplBase { const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) const override; -private: friend class RouteCreator; UriTemplateMatcherRouteEntryImpl(const CommonVirtualHostSharedPtr& vhost, @@ -960,6 +959,7 @@ class UriTemplateMatcherRouteEntryImpl : public RouteEntryImplBase { ProtobufMessage::ValidationVisitor& validator, absl::Status& creation_status); +private: const std::string uri_template_; }; @@ -986,14 +986,15 @@ class PrefixRouteEntryImpl : public RouteEntryImplBase { const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) const override; -private: friend class RouteCreator; + PrefixRouteEntryImpl(const CommonVirtualHostSharedPtr& vhost, const envoy::config::route::v3::Route& route, Server::Configuration::ServerFactoryContext& factory_context, ProtobufMessage::ValidationVisitor& validator, absl::Status& creation_status); +private: const Matchers::PathMatcherConstSharedPtr path_matcher_; }; @@ -1020,13 +1021,14 @@ class PathRouteEntryImpl : public RouteEntryImplBase { const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) const override; -private: friend class RouteCreator; + PathRouteEntryImpl(const CommonVirtualHostSharedPtr& vhost, const envoy::config::route::v3::Route& route, Server::Configuration::ServerFactoryContext& factory_context, ProtobufMessage::ValidationVisitor& validator, absl::Status& creation_status); +private: const Matchers::PathMatcherConstSharedPtr path_matcher_; }; @@ -1053,13 +1055,14 @@ class RegexRouteEntryImpl : public RouteEntryImplBase { const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) const override; -private: friend class RouteCreator; + RegexRouteEntryImpl(const CommonVirtualHostSharedPtr& vhost, const envoy::config::route::v3::Route& route, Server::Configuration::ServerFactoryContext& factory_context, ProtobufMessage::ValidationVisitor& validator, absl::Status& creation_status); +private: const Matchers::PathMatcherConstSharedPtr path_matcher_; }; @@ -1087,8 +1090,8 @@ class ConnectRouteEntryImpl : public RouteEntryImplBase { bool supportsPathlessHeaders() const override { return true; } -private: friend class RouteCreator; + ConnectRouteEntryImpl(const CommonVirtualHostSharedPtr& vhost, const envoy::config::route::v3::Route& route, Server::Configuration::ServerFactoryContext& factory_context, @@ -1119,14 +1122,15 @@ class PathSeparatedPrefixRouteEntryImpl : public RouteEntryImplBase { const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) const override; -private: friend class RouteCreator; + PathSeparatedPrefixRouteEntryImpl(const CommonVirtualHostSharedPtr& vhost, const envoy::config::route::v3::Route& route, Server::Configuration::ServerFactoryContext& factory_context, ProtobufMessage::ValidationVisitor& validator, absl::Status& creation_status); +private: const Matchers::PathMatcherConstSharedPtr path_matcher_; }; From dd5dbe5cdef0d1ded599cc766d3e1e207f657450 Mon Sep 17 00:00:00 2001 From: code Date: Tue, 14 Apr 2026 09:46:57 +0800 Subject: [PATCH 003/750] fix: stats: make the HDS's cluster to honor alt_stat_name (#44407) In the previous implementation, the cluster of HDS update will not use the alt_stat_name even it's set. This PR fixed related behavior. Risk Level: low Testing: unit. Docs Changes: n/a. Release Notes: added. Signed-off-by: wbpcode/wangbaiping --- changelogs/current.yaml | 10 +++++++ .../upstream/prod_cluster_info_factory.cc | 3 +- source/common/upstream/upstream_impl.cc | 12 ++++---- source/common/upstream/upstream_impl.h | 3 +- .../prod_cluster_info_factory_test.cc | 30 +++++++++++++++++++ 5 files changed, 47 insertions(+), 11 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9cb796b1d4a79..4e378383645cc 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -77,6 +77,11 @@ minor_behavior_changes: making per-host comparison O(1). In rare cases where two semantically equivalent metadata messages have different serializations, this may cause a spurious metadata update (false positive) but will never miss an actual change. +- area: upstream + change: | + ``ProdClusterInfoFactory`` now honors ``alt_stat_name`` when creating cluster stats scopes, so + stats emitted through that path use the alternate cluster name consistently with other upstream + cluster implementations. - area: histograms change: | Updated libcircllhist to 0.3.2, which changes how bucket bounds are interpreted. This should not impact @@ -312,6 +317,11 @@ bug_fixes: change: | Fixed an out-of-bounds issue in ThreadAwareLoadBalancerBase that could occur during mid-batch EDS host updates due to eagerly calling refresh() before the deferred priority state resize. +- area: upstream + change: | + Fixed ``ProdClusterInfoFactory`` of HDS to honor ``alt_stat_name`` when creating cluster stats scopes, so + stats emitted through that path now use the alternate cluster name consistently with other upstream + cluster implementations. - area: access_log change: | Fixed a crash on listener removal with a process-level access log rate limiter diff --git a/source/common/upstream/prod_cluster_info_factory.cc b/source/common/upstream/prod_cluster_info_factory.cc index da1cb9f7d9b7a..b3ef462879e09 100644 --- a/source/common/upstream/prod_cluster_info_factory.cc +++ b/source/common/upstream/prod_cluster_info_factory.cc @@ -12,8 +12,7 @@ namespace Upstream { ClusterInfoConstSharedPtr ProdClusterInfoFactory::createClusterInfo(const CreateClusterInfoParams& params) { - Envoy::Stats::ScopeSharedPtr scope = - generateStatsScope(params.cluster_, params.server_context_, false); + Envoy::Stats::ScopeSharedPtr scope = generateStatsScope(params.cluster_, params.server_context_); Envoy::Server::Configuration::TransportSocketFactoryContextImpl factory_context( params.server_context_, *scope, params.server_context_.messageValidationVisitor()); diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 764b2488e08dd..3c532c8b5e249 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -420,8 +420,7 @@ constexpr absl::string_view StatsMatcherMetadataKey = "envoy.stats_matcher"; Stats::ScopeSharedPtr generateStatsScope(const envoy::config::cluster::v3::Cluster& config, - Server::Configuration::ServerFactoryContext& server_context, - bool use_alt_stat_name) { + Server::Configuration::ServerFactoryContext& server_context) { auto& stats = server_context.serverScope().store(); Stats::StatsMatcherSharedPtr scope_matcher; @@ -442,11 +441,10 @@ generateStatsScope(const envoy::config::cluster::v3::Cluster& config, } } - return stats.createScope( - fmt::format("cluster.{}.", (!config.alt_stat_name().empty() && use_alt_stat_name) - ? config.alt_stat_name() - : config.name()), - false, {}, std::move(scope_matcher)); + return stats.createScope(fmt::format("cluster.{}.", (!config.alt_stat_name().empty()) + ? config.alt_stat_name() + : config.name()), + false, {}, std::move(scope_matcher)); } // TODO(pianiststickman): this implementation takes a lock on the hot path and puts a copy of the diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index daafc93b90ec9..129b879bcfde9 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -85,8 +85,7 @@ using UpstreamNetworkFilterConfigProviderManager = Stats::ScopeSharedPtr generateStatsScope(const envoy::config::cluster::v3::Cluster& config, - Server::Configuration::ServerFactoryContext& server_context, - bool use_alt_stat_name = true); + Server::Configuration::ServerFactoryContext& server_context); class LegacyLbPolicyConfigHelper { public: diff --git a/test/common/upstream/prod_cluster_info_factory_test.cc b/test/common/upstream/prod_cluster_info_factory_test.cc index a856c66ddb6db..9fc1dc5bb68bf 100644 --- a/test/common/upstream/prod_cluster_info_factory_test.cc +++ b/test/common/upstream/prod_cluster_info_factory_test.cc @@ -36,6 +36,33 @@ class ProdClusterInfoFactoryTest : public testing::Test { ProdClusterInfoFactory factory_; }; +// alt_stat_name is preferred over name for stats scope generation. +TEST_F(ProdClusterInfoFactoryTest, AltStatNamePreferred) { + const std::string yaml = R"EOF( + name: my_cluster + alt_stat_name: my_alt_cluster + connect_timeout: 0.25s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11001 + )EOF"; + + auto info = createClusterInfo(parseClusterFromV3Yaml(yaml)); + ASSERT_NE(nullptr, info); + info->trafficStats(); + + // Stats scope should be generated with alt_stat_name, not name. + EXPECT_EQ(info->statsScope().symbolTable().toString(info->statsScope().prefix()), + "cluster.my_alt_cluster"); +} + // Verify that a cluster without a stats matcher in metadata creates all stats normally. TEST_F(ProdClusterInfoFactoryTest, NoMetadataStatsMatcher) { const std::string yaml = R"EOF( @@ -57,6 +84,9 @@ TEST_F(ProdClusterInfoFactoryTest, NoMetadataStatsMatcher) { ASSERT_NE(nullptr, info); info->trafficStats(); + EXPECT_EQ(info->statsScope().symbolTable().toString(info->statsScope().prefix()), + "cluster.my_cluster"); + // Without a scope matcher, stats of any name are accepted. EXPECT_NE("", info->statsScope().counterFromString("upstream_cx_total").name()); EXPECT_NE("", info->statsScope().counterFromString("upstream_rq_total").name()); From 5aeaf3762c1a1010b094049821932aa899415131 Mon Sep 17 00:00:00 2001 From: Anton Kanugalawattage Date: Mon, 13 Apr 2026 21:55:29 -0400 Subject: [PATCH 004/750] network filters: add tcp bandwidth limit (#42996) Fixes https://github.com/envoyproxy/envoy/issues/26754 Risk Level: low Testing: test Docs Changes: yes Release Notes: yes Platform Specific Features: no --------- Signed-off-by: Anton Kanugalawattage --- CODEOWNERS | 1 + api/BUILD | 1 + .../network/tcp_bandwidth_limit/v3/BUILD | 12 + .../v3/tcp_bandwidth_limit.proto | 48 + api/versioning/BUILD | 1 + .../network_filters/network_filters.rst | 1 + .../tcp_bandwidth_limit_filter.rst | 66 ++ envoy/network/filter.h | 12 + source/common/network/connection_impl.cc | 23 +- source/common/network/connection_impl.h | 3 +- source/common/network/connection_impl_base.cc | 23 + source/common/network/connection_impl_base.h | 8 + source/common/network/filter_manager_impl.h | 19 + .../quic_filter_manager_connection_impl.cc | 8 +- source/extensions/extensions_build_config.bzl | 1 + source/extensions/extensions_metadata.yaml | 7 + .../filters/network/tcp_bandwidth_limit/BUILD | 48 + .../network/tcp_bandwidth_limit/config.cc | 33 + .../network/tcp_bandwidth_limit/config.h | 29 + .../tcp_bandwidth_limit.cc | 265 +++++ .../tcp_bandwidth_limit/tcp_bandwidth_limit.h | 153 +++ .../filters/network/tcp_bandwidth_limit/BUILD | 52 + .../tcp_bandwidth_limit/config_test.cc | 194 ++++ .../tcp_bandwidth_limit/filter_test.cc | 989 ++++++++++++++++++ .../tcp_bandwidth_limit_integration_test.cc | 122 +++ test/mocks/network/connection.h | 2 + test/mocks/network/mocks.h | 2 + 27 files changed, 2097 insertions(+), 26 deletions(-) create mode 100644 api/envoy/extensions/filters/network/tcp_bandwidth_limit/v3/BUILD create mode 100644 api/envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.proto create mode 100644 docs/root/configuration/listeners/network_filters/tcp_bandwidth_limit_filter.rst create mode 100644 source/extensions/filters/network/tcp_bandwidth_limit/BUILD create mode 100644 source/extensions/filters/network/tcp_bandwidth_limit/config.cc create mode 100644 source/extensions/filters/network/tcp_bandwidth_limit/config.h create mode 100644 source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.cc create mode 100644 source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.h create mode 100644 test/extensions/filters/network/tcp_bandwidth_limit/BUILD create mode 100644 test/extensions/filters/network/tcp_bandwidth_limit/config_test.cc create mode 100644 test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc create mode 100644 test/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit_integration_test.cc diff --git a/CODEOWNERS b/CODEOWNERS index e12d493291cfc..ddf611b5dff9a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -172,6 +172,7 @@ extensions/filters/common/original_src @klarose @mattklein123 /*/extensions/retry/priority/previous_priorities @ravenblackx @mattklein123 /*/extensions/retry/host @ravenblackx @mattklein123 /*/extensions/filters/network/http_connection_manager @yanavlasov @mattklein123 +/*/extensions/filters/network/tcp_bandwidth_limit @yanavlasov @nezdolik @antonkanug /*/extensions/filters/network/tcp_proxy @zuercher @ggreenway /*/extensions/filters/network/echo @yanavlasov @adisuissa /*/extensions/filters/udp/dns_filter @mattklein123 @yanjunxiang-google diff --git a/api/BUILD b/api/BUILD index 42ed9cb587f35..dc1eeafb03b36 100644 --- a/api/BUILD +++ b/api/BUILD @@ -283,6 +283,7 @@ proto_library( "//envoy/extensions/filters/network/set_filter_state/v3:pkg", "//envoy/extensions/filters/network/sni_cluster/v3:pkg", "//envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3:pkg", + "//envoy/extensions/filters/network/tcp_bandwidth_limit/v3:pkg", "//envoy/extensions/filters/network/tcp_proxy/v3:pkg", "//envoy/extensions/filters/network/thrift_proxy/filters/header_to_metadata/v3:pkg", "//envoy/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/v3:pkg", diff --git a/api/envoy/extensions/filters/network/tcp_bandwidth_limit/v3/BUILD b/api/envoy/extensions/filters/network/tcp_bandwidth_limit/v3/BUILD new file mode 100644 index 0000000000000..504c6c70514ac --- /dev/null +++ b/api/envoy/extensions/filters/network/tcp_bandwidth_limit/v3/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/config/core/v3:pkg", + "@xds//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.proto b/api/envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.proto new file mode 100644 index 0000000000000..cc76ce0cc9d42 --- /dev/null +++ b/api/envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package envoy.extensions.filters.network.tcp_bandwidth_limit.v3; + +import "envoy/config/core/v3/base.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.network.tcp_bandwidth_limit.v3"; +option java_outer_classname = "TcpBandwidthLimitProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_bandwidth_limit/v3;tcp_bandwidth_limitv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: TCP Bandwidth Limit] +// TCP Bandwidth Limit :ref:`configuration overview `. +// [#extension: envoy.filters.network.tcp_bandwidth_limit] + +// [#next-free-field: 6] +message TcpBandwidthLimit { + // The human readable prefix to use when emitting stats. + string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; + + // The limit for read (onData) bandwidth in KiB/s. + // If not set, no limit is applied (unlimited bandwidth). + // If set to 0, all reads are blocked. + google.protobuf.UInt64Value read_limit_kbps = 2; + + // The limit for write (onWrite) bandwidth in KiB/s. + // If not set, no limit is applied (unlimited bandwidth). + // If set to 0, all writes are is blocked. + google.protobuf.UInt64Value write_limit_kbps = 3; + + // The interval at which to process buffered data and check for available bandwidth. + // Defaults to 50ms. It must be at least 20ms to avoid too frequent processing. + google.protobuf.Duration fill_interval = 4 [(validate.rules).duration = { + lte {seconds: 1} + gte {nanos: 20000000} + }]; + + // Runtime flag that controls whether the filter is enabled or not. If not specified, defaults + // to enabled. + config.core.v3.RuntimeFeatureFlag runtime_enabled = 5; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index c2d128b35aca7..48e88efea02f2 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -222,6 +222,7 @@ proto_library( "//envoy/extensions/filters/network/set_filter_state/v3:pkg", "//envoy/extensions/filters/network/sni_cluster/v3:pkg", "//envoy/extensions/filters/network/sni_dynamic_forward_proxy/v3:pkg", + "//envoy/extensions/filters/network/tcp_bandwidth_limit/v3:pkg", "//envoy/extensions/filters/network/tcp_proxy/v3:pkg", "//envoy/extensions/filters/network/thrift_proxy/filters/header_to_metadata/v3:pkg", "//envoy/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/v3:pkg", diff --git a/docs/root/configuration/listeners/network_filters/network_filters.rst b/docs/root/configuration/listeners/network_filters/network_filters.rst index c299b6ae9c449..1dace6c5215c1 100644 --- a/docs/root/configuration/listeners/network_filters/network_filters.rst +++ b/docs/root/configuration/listeners/network_filters/network_filters.rst @@ -34,6 +34,7 @@ filters. set_filter_state sni_cluster_filter sni_dynamic_forward_proxy_filter + tcp_bandwidth_limit_filter tcp_proxy_filter thrift_proxy_filter wasm_filter diff --git a/docs/root/configuration/listeners/network_filters/tcp_bandwidth_limit_filter.rst b/docs/root/configuration/listeners/network_filters/tcp_bandwidth_limit_filter.rst new file mode 100644 index 0000000000000..7008f26b1e8fa --- /dev/null +++ b/docs/root/configuration/listeners/network_filters/tcp_bandwidth_limit_filter.rst @@ -0,0 +1,66 @@ +.. _config_network_filters_tcp_bandwidth_limit: + +TCP bandwidth limit +=================== + +* This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.network.tcp_bandwidth_limit.v3.TcpBandwidthLimit``. +* :ref:`v3 API reference ` + +Overview +-------- + +The TCP bandwidth limit filter is a network filter that limits the bandwidth on the downstream +connection. It can be configured to limit read and write bandwidth independently, using a token +bucket algorithm similar to the HTTP bandwidth limit filter. + +- ``read_limit_kbps`` limits data read from the downstream connection. +- ``write_limit_kbps`` limits data written to the downstream connection. + +The filter works by: + +* Consuming tokens from a token bucket when data passes through +* Buffering data when insufficient tokens are available +* Using timers to refill tokens and resume data flow when bandwidth becomes available + +Example configuration +--------------------- + +The following example configuration limits read bandwidth to 1 MiB/s and write bandwidth to 512 KiB/s: + +.. code-block:: yaml + + name: envoy.filters.network.tcp_bandwidth_limit + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_bandwidth_limit.v3.TcpBandwidthLimit + stat_prefix: bandwidth_limiter + read_limit_kbps: 1024 # 1 MiB/s + write_limit_kbps: 512 # 512 KiB/s + fill_interval: + nanos: 50000000 # 50ms + +Statistics +---------- + +The TCP bandwidth limit filter outputs statistics in the ``.`` namespace. + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + read_enabled, Counter, Total number of times the read limit was applied to incoming data + write_enabled, Counter, Total number of times the write limit was applied to outgoing data + read_throttled, Counter, Total number of times read data was throttled + write_throttled, Counter, Total number of times write data was throttled + read_total_bytes, Counter, Total bytes read + write_total_bytes, Counter, Total bytes written + read_bytes_buffered, Gauge, Current number of bytes buffered for read + write_bytes_buffered, Gauge, Current number of bytes buffered for write + read_rate_bps, Gauge, Current read rate in bytes per second + write_rate_bps, Gauge, Current write rate in bytes per second + +Runtime +------- + +The TCP bandwidth limit filter can be runtime feature flagged via the :ref:`runtime_enabled +` +configuration field. diff --git a/envoy/network/filter.h b/envoy/network/filter.h index 6a96af5fed27b..a863eae82e54c 100644 --- a/envoy/network/filter.h +++ b/envoy/network/filter.h @@ -116,6 +116,18 @@ class WriteFilterCallbacks : public virtual NetworkFilterCallbacks { * marking the filter as close ready. */ virtual void disableClose(bool disabled) PURE; + + /** + * Called by a write filter to signal that its internal buffer is above its high watermark. + * This propagates backpressure to the connection, which may notify ConnectionCallbacks. + */ + virtual void onAboveWriteBufferHighWatermark() PURE; + + /** + * Called by a write filter to signal that its internal buffer has drained below its low + * watermark. Must be paired with a prior onAboveWriteBufferHighWatermark() call. + */ + virtual void onBelowWriteBufferLowWatermark() PURE; }; /** diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index 7afe5e577696a..820b8ebe3fd03 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -80,10 +80,9 @@ ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPt [this]() -> void { this->onReadBufferLowWatermark(); }, [this]() -> void { this->onReadBufferHighWatermark(); }, []() -> void { /* TODO(adisuissa): Handle overflow watermark */ })), - write_buffer_above_high_watermark_(false), detect_early_close_(true), - enable_half_close_(false), read_end_stream_raised_(false), read_end_stream_(false), - write_end_stream_(false), current_write_end_stream_(false), dispatch_buffered_data_(false), - transport_wants_read_(false), + detect_early_close_(true), enable_half_close_(false), read_end_stream_raised_(false), + read_end_stream_(false), write_end_stream_(false), current_write_end_stream_(false), + dispatch_buffered_data_(false), transport_wants_read_(false), enable_close_through_filter_manager_(Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.connection_close_through_filter_manager")) { @@ -697,30 +696,18 @@ void ConnectionImpl::onReadBufferHighWatermark() { void ConnectionImpl::onWriteBufferLowWatermark() { ENVOY_CONN_LOG(debug, "onBelowWriteBufferLowWatermark", *this); - ASSERT(write_buffer_above_high_watermark_); - write_buffer_above_high_watermark_ = false; if (state() == State::Open) { maybeCancelBufferHighWatermarkTimeout(); } - for (ConnectionCallbacks* callback : callbacks_) { - if (callback) { - callback->onBelowWriteBufferLowWatermark(); - } - } + onFilterBelowLowWatermark(); } void ConnectionImpl::onWriteBufferHighWatermark() { ENVOY_CONN_LOG(debug, "onAboveWriteBufferHighWatermark", *this); - ASSERT(!write_buffer_above_high_watermark_); - write_buffer_above_high_watermark_ = true; if (state() == State::Open) { scheduleBufferHighWatermarkTimeout(); } - for (ConnectionCallbacks* callback : callbacks_) { - if (callback) { - callback->onAboveWriteBufferHighWatermark(); - } - } + onFilterAboveHighWatermark(); } void ConnectionImpl::setFailureReason(absl::string_view failure_reason) { diff --git a/source/common/network/connection_impl.h b/source/common/network/connection_impl.h index fbff2aca723c1..b27bf7945f7d2 100644 --- a/source/common/network/connection_impl.h +++ b/source/common/network/connection_impl.h @@ -106,7 +106,7 @@ class ConnectionImpl : public ConnectionImplBase, public TransportSocketCallback void setBufferLimits(uint32_t limit) override; void setBufferHighWatermarkTimeout(std::chrono::milliseconds timeout) override; uint32_t bufferLimit() const override { return read_buffer_limit_; } - bool aboveHighWatermark() const override { return write_buffer_above_high_watermark_; } + bool aboveHighWatermark() const override { return above_high_watermark_count_ > 0; } const ConnectionSocket::OptionsSharedPtr& socketOptions() const override { return socket_->options(); } @@ -264,7 +264,6 @@ class ConnectionImpl : public ConnectionImplBase, public TransportSocketCallback StreamInfo::DetectedCloseType detected_close_type_{StreamInfo::DetectedCloseType::Normal}; std::chrono::milliseconds buffer_high_watermark_timeout_{}; Event::TimerPtr buffer_high_watermark_timer_{nullptr}; - bool write_buffer_above_high_watermark_ : 1; bool detect_early_close_ : 1; bool enable_half_close_ : 1; bool read_end_stream_raised_ : 1; diff --git a/source/common/network/connection_impl_base.cc b/source/common/network/connection_impl_base.cc index e047afd0382ef..0535c8e09f285 100644 --- a/source/common/network/connection_impl_base.cc +++ b/source/common/network/connection_impl_base.cc @@ -78,5 +78,28 @@ void ConnectionImplBase::onDelayedCloseTimeout() { StreamInfo::LocalCloseReasons::get().TriggeredDelayedCloseTimeout); } +void ConnectionImplBase::onFilterAboveHighWatermark() { + ++above_high_watermark_count_; + if (above_high_watermark_count_ == 1) { + for (ConnectionCallbacks* callback : callbacks_) { + if (callback) { + callback->onAboveWriteBufferHighWatermark(); + } + } + } +} + +void ConnectionImplBase::onFilterBelowLowWatermark() { + ASSERT(above_high_watermark_count_ > 0); + --above_high_watermark_count_; + if (above_high_watermark_count_ == 0) { + for (ConnectionCallbacks* callback : callbacks_) { + if (callback) { + callback->onBelowWriteBufferLowWatermark(); + } + } + } +} + } // namespace Network } // namespace Envoy diff --git a/source/common/network/connection_impl_base.h b/source/common/network/connection_impl_base.h index 284cd38e96b47..f23f9ab1b40bc 100644 --- a/source/common/network/connection_impl_base.h +++ b/source/common/network/connection_impl_base.h @@ -23,6 +23,11 @@ class ConnectionImplBase : public FilterManagerConnection, // Network::Connection void addConnectionCallbacks(ConnectionCallbacks& cb) override; void removeConnectionCallbacks(ConnectionCallbacks& cb) override; + + // Network::FilterManagerConnection + void onFilterAboveHighWatermark() override; + void onFilterBelowLowWatermark() override; + Event::Dispatcher& dispatcher() const override { return dispatcher_; } uint64_t id() const override { return id_; } void hashKey(std::vector& hash) const override; @@ -74,6 +79,9 @@ class ConnectionImplBase : public FilterManagerConnection, const uint64_t id_; std::list callbacks_; std::unique_ptr connection_stats_; + // Number of sources above their high watermark. ConnectionCallbacks are notified on + // transitions (0→1, 1→0). + uint32_t above_high_watermark_count_{0}; private: // Callback issued when a delayed close timeout triggers. diff --git a/source/common/network/filter_manager_impl.h b/source/common/network/filter_manager_impl.h index 99afc09bff880..fff3f2eedf2bf 100644 --- a/source/common/network/filter_manager_impl.h +++ b/source/common/network/filter_manager_impl.h @@ -101,6 +101,18 @@ class FilterManagerConnection : public virtual Connection, * @param action for how the connection will be closed. */ virtual void closeConnection(ConnectionCloseAction action) PURE; + + /** + * Called when a filter's internal write buffer has gone above its high watermark. + * This propagates backpressure to the connection, which may notify ConnectionCallbacks. + */ + virtual void onFilterAboveHighWatermark() PURE; + + /** + * Called when a filter's internal write buffer has drained below its low watermark. + * Must be paired with a prior onFilterAboveHighWatermark() call. + */ + virtual void onFilterBelowLowWatermark() PURE; }; /** @@ -182,6 +194,13 @@ class FilterManagerImpl : protected Logger::Loggable { void disableClose(bool disable) override; + void onAboveWriteBufferHighWatermark() override { + parent_.connection_.onFilterAboveHighWatermark(); + } + void onBelowWriteBufferLowWatermark() override { + parent_.connection_.onFilterBelowLowWatermark(); + } + FilterManagerImpl& parent_; WriteFilterSharedPtr filter_; bool pending_close_{false}; diff --git a/source/common/quic/quic_filter_manager_connection_impl.cc b/source/common/quic/quic_filter_manager_connection_impl.cc index df45d5332d816..ea1fcfe3fed1a 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.cc +++ b/source/common/quic/quic_filter_manager_connection_impl.cc @@ -235,16 +235,12 @@ void QuicFilterManagerConnectionImpl::closeConnectionImmediately() { void QuicFilterManagerConnectionImpl::onSendBufferHighWatermark() { ENVOY_CONN_LOG(trace, "onSendBufferHighWatermark", *this); - for (auto callback : callbacks_) { - callback->onAboveWriteBufferHighWatermark(); - } + onFilterAboveHighWatermark(); } void QuicFilterManagerConnectionImpl::onSendBufferLowWatermark() { ENVOY_CONN_LOG(trace, "onSendBufferLowWatermark", *this); - for (auto callback : callbacks_) { - callback->onBelowWriteBufferLowWatermark(); - } + onFilterBelowLowWatermark(); } absl::optional diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 0156ec4abbec7..efdb8e357721c 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -260,6 +260,7 @@ EXTENSIONS = { "envoy.filters.network.http_connection_manager": "//source/extensions/filters/network/http_connection_manager:config", "envoy.filters.network.local_ratelimit": "//source/extensions/filters/network/local_ratelimit:config", "envoy.filters.network.mongo_proxy": "//source/extensions/filters/network/mongo_proxy:config", + "envoy.filters.network.tcp_bandwidth_limit": "//source/extensions/filters/network/tcp_bandwidth_limit:config", "envoy.filters.network.ratelimit": "//source/extensions/filters/network/ratelimit:config", "envoy.filters.network.rbac": "//source/extensions/filters/network/rbac:config", "envoy.filters.network.redis_proxy": "//source/extensions/filters/network/redis_proxy:config", diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index fd31272f8d06f..12d0121bae3be 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -960,6 +960,13 @@ envoy.filters.network.sni_dynamic_forward_proxy: status: alpha type_urls: - envoy.extensions.filters.network.sni_dynamic_forward_proxy.v3.FilterConfig +envoy.filters.network.tcp_bandwidth_limit: + categories: + - envoy.filters.network + security_posture: unknown + status: alpha + type_urls: + - envoy.extensions.filters.network.tcp_bandwidth_limit.v3.TcpBandwidthLimit envoy.filters.network.tcp_proxy: categories: - envoy.filters.network diff --git a/source/extensions/filters/network/tcp_bandwidth_limit/BUILD b/source/extensions/filters/network/tcp_bandwidth_limit/BUILD new file mode 100644 index 0000000000000..5b10b29129f40 --- /dev/null +++ b/source/extensions/filters/network/tcp_bandwidth_limit/BUILD @@ -0,0 +1,48 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "tcp_bandwidth_limit", + srcs = ["tcp_bandwidth_limit.cc"], + hdrs = ["tcp_bandwidth_limit.h"], + deps = [ + "//envoy/buffer:buffer_interface", + "//envoy/common:time_interface", + "//envoy/event:dispatcher_interface", + "//envoy/event:timer_interface", + "//envoy/network:connection_interface", + "//envoy/network:filter_interface", + "//envoy/runtime:runtime_interface", + "//envoy/stats:stats_interface", + "//envoy/stats:stats_macros", + "//source/common/buffer:buffer_lib", + "//source/common/buffer:watermark_buffer_lib", + "//source/common/common:assert_lib", + "//source/common/common:logger_lib", + "//source/common/common:shared_token_bucket_impl_lib", + "//source/common/runtime:runtime_lib", + "@envoy_api//envoy/extensions/filters/network/tcp_bandwidth_limit/v3:pkg_cc_proto", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":tcp_bandwidth_limit", + "//envoy/registry", + "//envoy/server:filter_config_interface", + "//source/extensions/filters/network:well_known_names", + "//source/extensions/filters/network/common:factory_base_lib", + "@envoy_api//envoy/extensions/filters/network/tcp_bandwidth_limit/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/filters/network/tcp_bandwidth_limit/config.cc b/source/extensions/filters/network/tcp_bandwidth_limit/config.cc new file mode 100644 index 0000000000000..7d00523ccb3f9 --- /dev/null +++ b/source/extensions/filters/network/tcp_bandwidth_limit/config.cc @@ -0,0 +1,33 @@ +#include "source/extensions/filters/network/tcp_bandwidth_limit/config.h" + +#include "envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.pb.h" +#include "envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.pb.validate.h" + +#include "source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace TcpBandwidthLimit { + +Network::FilterFactoryCb TcpBandwidthLimitConfigFactory::createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit& + proto_config, + Server::Configuration::FactoryContext& context) { + + auto config = std::make_shared(proto_config, context.scope(), + context.serverFactoryContext().runtime(), + context.serverFactoryContext().timeSource()); + + return [config](Network::FilterManager& filter_manager) -> void { + filter_manager.addFilter(std::make_shared(config)); + }; +} + +REGISTER_FACTORY(TcpBandwidthLimitConfigFactory, + Server::Configuration::NamedNetworkFilterConfigFactory); + +} // namespace TcpBandwidthLimit +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/tcp_bandwidth_limit/config.h b/source/extensions/filters/network/tcp_bandwidth_limit/config.h new file mode 100644 index 0000000000000..c477ac163f77b --- /dev/null +++ b/source/extensions/filters/network/tcp_bandwidth_limit/config.h @@ -0,0 +1,29 @@ +#pragma once + +#include "envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.pb.h" +#include "envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.pb.validate.h" + +#include "source/extensions/filters/network/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace TcpBandwidthLimit { + +class TcpBandwidthLimitConfigFactory + : public Common::FactoryBase< + envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit> { +public: + TcpBandwidthLimitConfigFactory() : FactoryBase("envoy.filters.network.tcp_bandwidth_limit") {} + +private: + Network::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit& + proto_config, + Server::Configuration::FactoryContext& context) override; +}; + +} // namespace TcpBandwidthLimit +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.cc b/source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.cc new file mode 100644 index 0000000000000..a8f996bd8c1a8 --- /dev/null +++ b/source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.cc @@ -0,0 +1,265 @@ +#include "source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.h" + +#include "envoy/buffer/buffer.h" +#include "envoy/event/dispatcher.h" +#include "envoy/network/connection.h" + +#include "source/common/common/assert.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace TcpBandwidthLimit { +namespace { + +constexpr uint64_t kiloBytesToBytes(uint64_t val) { return val * 1024; } +constexpr int64_t RateUpdateIntervalMs = 1000; +constexpr uint64_t MillisecondsPerSecond = 1000; + +} // namespace + +FilterConfig::FilterConfig( + const envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit& config, + Stats::Scope& scope, Runtime::Loader& runtime, TimeSource& time_source) + : runtime_(runtime), time_source_(time_source), + read_limit_kbps_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, read_limit_kbps, 0)), + write_limit_kbps_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, write_limit_kbps, 0)), + fill_interval_(std::chrono::milliseconds( + PROTOBUF_GET_MS_OR_DEFAULT(config, fill_interval, 50))), // Default 50ms + enabled_(config.runtime_enabled(), runtime), + stats_(generateStats(config.stat_prefix(), scope)), + // The token bucket is configured with a max token count of the number of + // bytes per second, and refills at the same rate, so that we have a per + // second limit which refills gradually over the fill interval. + read_token_bucket_(config.has_read_limit_kbps() + ? std::make_shared( + kiloBytesToBytes(read_limit_kbps_), time_source_, + static_cast(kiloBytesToBytes(read_limit_kbps_))) + : nullptr), + write_token_bucket_(config.has_write_limit_kbps() + ? std::make_shared( + kiloBytesToBytes(write_limit_kbps_), time_source_, + static_cast(kiloBytesToBytes(write_limit_kbps_))) + : nullptr) {} + +TcpBandwidthLimitStats FilterConfig::generateStats(const std::string& prefix, Stats::Scope& scope) { + const std::string final_prefix = prefix + ".tcp_bandwidth_limit"; + return {ALL_TCP_BANDWIDTH_LIMIT_STATS(POOL_COUNTER_PREFIX(scope, final_prefix), + POOL_GAUGE_PREFIX(scope, final_prefix))}; +} + +TcpBandwidthLimitFilter::TcpBandwidthLimitFilter(FilterConfigSharedPtr config) + : config_(config), read_buffer_([this]() { onReadBufferLowWatermark(); }, + [this]() { onReadBufferHighWatermark(); }, []() -> void {}), + write_buffer_([this]() { onWriteBufferLowWatermark(); }, + [this]() { onWriteBufferHighWatermark(); }, []() -> void {}), + last_read_rate_update_(config->timeSource().monotonicTime()), + last_write_rate_update_(config->timeSource().monotonicTime()) {} + +TcpBandwidthLimitFilter::~TcpBandwidthLimitFilter() { + if (read_timer_) { + read_timer_->disableTimer(); + read_timer_.reset(); + } + if (write_timer_) { + write_timer_->disableTimer(); + write_timer_.reset(); + } +} + +Network::FilterStatus TcpBandwidthLimitFilter::onData(Buffer::Instance& data, bool end_stream) { + if (!config_->enabled() || !config_->hasReadLimit()) { + return Network::FilterStatus::Continue; + } + + config_->stats().read_enabled_.inc(); + + // If there's already buffered data, we must buffer new data too to preserve byte ordering. + if (read_buffer_.length() > 0) { + config_->stats().read_throttled_.inc(); + read_end_stream_ = end_stream; + read_buffer_.move(data); + config_->stats().read_bytes_buffered_.set(read_buffer_.length()); + return Network::FilterStatus::StopIteration; + } + + uint64_t data_size = data.length(); + uint64_t consumed = config_->readTokenBucket()->consume(data_size, true); + + if (consumed < data_size) { + config_->stats().read_throttled_.inc(); + + if (consumed > 0) { + Buffer::OwnedImpl passthrough; + passthrough.move(data, consumed); + read_callbacks_->injectReadDataToFilterChain(passthrough, false); + updateReadRate(consumed); + } + + read_end_stream_ = end_stream; + read_buffer_.move(data); + config_->stats().read_bytes_buffered_.set(read_buffer_.length()); + + if (!read_timer_) { + read_timer_ = + read_callbacks_->connection().dispatcher().createTimer([this]() { onReadTokenTimer(); }); + read_timer_->enableTimer(config_->fillInterval()); + } + + return Network::FilterStatus::StopIteration; + } + + updateReadRate(data_size); + return Network::FilterStatus::Continue; +} + +Network::FilterStatus TcpBandwidthLimitFilter::onWrite(Buffer::Instance& data, bool end_stream) { + if (!config_->enabled() || !config_->hasWriteLimit()) { + return Network::FilterStatus::Continue; + } + + config_->stats().write_enabled_.inc(); + + // If there's already buffered data, we must buffer new data too to preserve byte ordering. + if (write_buffer_.length() > 0) { + config_->stats().write_throttled_.inc(); + write_end_stream_ = end_stream; + write_buffer_.move(data); + config_->stats().write_bytes_buffered_.set(write_buffer_.length()); + return Network::FilterStatus::StopIteration; + } + + uint64_t data_size = data.length(); + uint64_t consumed = config_->writeTokenBucket()->consume(data_size, true); + + if (consumed < data_size) { + config_->stats().write_throttled_.inc(); + + if (consumed > 0) { + Buffer::OwnedImpl to_send; + to_send.move(data, consumed); + write_callbacks_->injectWriteDataToFilterChain(to_send, false); + updateWriteRate(consumed); + } + + write_end_stream_ = end_stream; + write_buffer_.move(data); + config_->stats().write_bytes_buffered_.set(write_buffer_.length()); + + if (!write_timer_) { + write_timer_ = + read_callbacks_->connection().dispatcher().createTimer([this]() { onWriteTokenTimer(); }); + write_timer_->enableTimer(config_->fillInterval()); + } + + return Network::FilterStatus::StopIteration; + } + + updateWriteRate(data_size); + return Network::FilterStatus::Continue; +} + +void TcpBandwidthLimitFilter::onReadTokenTimer() { + processBufferedReadData(); + + if (read_buffer_.length() > 0) { + read_timer_->enableTimer(config_->fillInterval()); + } else { + read_timer_.reset(); + } +} + +void TcpBandwidthLimitFilter::onWriteTokenTimer() { + processBufferedWriteData(); + + if (write_buffer_.length() > 0) { + write_timer_->enableTimer(config_->fillInterval()); + } else { + write_timer_.reset(); + } +} + +void TcpBandwidthLimitFilter::onReadBufferHighWatermark() { + read_callbacks_->connection().readDisable(true); +} + +void TcpBandwidthLimitFilter::onReadBufferLowWatermark() { + read_callbacks_->connection().readDisable(false); +} + +void TcpBandwidthLimitFilter::onWriteBufferHighWatermark() { + write_callbacks_->onAboveWriteBufferHighWatermark(); +} + +void TcpBandwidthLimitFilter::onWriteBufferLowWatermark() { + write_callbacks_->onBelowWriteBufferLowWatermark(); +} + +void TcpBandwidthLimitFilter::processBufferedReadData() { + if (read_buffer_.length() == 0 || !config_->readTokenBucket()) { + return; + } + + uint64_t buffer_size = read_buffer_.length(); + uint64_t consumed = config_->readTokenBucket()->consume(buffer_size, true); + + if (consumed > 0) { + Buffer::OwnedImpl data_to_send; + data_to_send.move(read_buffer_, consumed); + const bool end_stream = read_end_stream_ && read_buffer_.length() == 0; + read_callbacks_->injectReadDataToFilterChain(data_to_send, end_stream); + updateReadRate(consumed); + config_->stats().read_bytes_buffered_.set(read_buffer_.length()); + } +} + +void TcpBandwidthLimitFilter::processBufferedWriteData() { + if (write_buffer_.length() == 0 || !config_->writeTokenBucket()) { + return; + } + + uint64_t buffer_size = write_buffer_.length(); + uint64_t consumed = config_->writeTokenBucket()->consume(buffer_size, true); + + if (consumed > 0) { + Buffer::OwnedImpl data_to_send; + data_to_send.move(write_buffer_, consumed); + const bool end_stream = write_end_stream_ && write_buffer_.length() == 0; + write_callbacks_->injectWriteDataToFilterChain(data_to_send, end_stream); + updateWriteRate(consumed); + config_->stats().write_bytes_buffered_.set(write_buffer_.length()); + } +} + +void TcpBandwidthLimitFilter::updateReadRate(uint64_t bytes) { + config_->stats().read_total_bytes_.add(bytes); + read_bytes_since_last_rate_ += bytes; + const auto now = config_->timeSource().monotonicTime(); + const auto elapsed = + std::chrono::duration_cast(now - last_read_rate_update_); + if (elapsed.count() >= RateUpdateIntervalMs) { + const uint64_t rate = (read_bytes_since_last_rate_ * MillisecondsPerSecond) / elapsed.count(); + config_->stats().read_rate_bps_.set(rate); + read_bytes_since_last_rate_ = 0; + last_read_rate_update_ = now; + } +} + +void TcpBandwidthLimitFilter::updateWriteRate(uint64_t bytes) { + config_->stats().write_total_bytes_.add(bytes); + write_bytes_since_last_rate_ += bytes; + const auto now = config_->timeSource().monotonicTime(); + const auto elapsed = + std::chrono::duration_cast(now - last_write_rate_update_); + if (elapsed.count() >= RateUpdateIntervalMs) { + const uint64_t rate = (write_bytes_since_last_rate_ * MillisecondsPerSecond) / elapsed.count(); + config_->stats().write_rate_bps_.set(rate); + write_bytes_since_last_rate_ = 0; + last_write_rate_update_ = now; + } +} + +} // namespace TcpBandwidthLimit +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.h b/source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.h new file mode 100644 index 0000000000000..60344c5b323c7 --- /dev/null +++ b/source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.h @@ -0,0 +1,153 @@ +#pragma once + +#include +#include + +#include "envoy/common/time.h" +#include "envoy/event/timer.h" +#include "envoy/extensions/filters/network/tcp_bandwidth_limit/v3/tcp_bandwidth_limit.pb.h" +#include "envoy/network/filter.h" +#include "envoy/runtime/runtime.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +#include "source/common/buffer/watermark_buffer.h" +#include "source/common/common/logger.h" +#include "source/common/common/shared_token_bucket_impl.h" +#include "source/common/runtime/runtime_protos.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace TcpBandwidthLimit { + +/** + * All TCP bandwidth limit stats. @see stats_macros.h + */ +#define ALL_TCP_BANDWIDTH_LIMIT_STATS(COUNTER, GAUGE) \ + COUNTER(read_enabled) \ + COUNTER(write_enabled) \ + COUNTER(read_throttled) \ + COUNTER(write_throttled) \ + COUNTER(read_total_bytes) \ + COUNTER(write_total_bytes) \ + GAUGE(read_bytes_buffered, Accumulate) \ + GAUGE(write_bytes_buffered, Accumulate) \ + GAUGE(read_rate_bps, NeverImport) \ + GAUGE(write_rate_bps, NeverImport) + +/** + * Struct definition for all TCP bandwidth limit stats. @see stats_macros.h + */ +struct TcpBandwidthLimitStats { + ALL_TCP_BANDWIDTH_LIMIT_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) +}; + +/** + * Configuration for the TCP bandwidth limit filter. + */ +class FilterConfig { +public: + FilterConfig( + const envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit& config, + Stats::Scope& scope, Runtime::Loader& runtime, TimeSource& time_source); + + Runtime::Loader& runtime() { return runtime_; } + TcpBandwidthLimitStats& stats() { return stats_; } + TimeSource& timeSource() { return time_source_; } + + bool hasReadLimit() const { return read_token_bucket_ != nullptr; } + bool hasWriteLimit() const { return write_token_bucket_ != nullptr; } + uint64_t readLimit() const { return read_limit_kbps_; } + uint64_t writeLimit() const { return write_limit_kbps_; } + bool enabled() const { return enabled_.enabled(); } + const std::shared_ptr& readTokenBucket() const { + return read_token_bucket_; + } + const std::shared_ptr& writeTokenBucket() const { + return write_token_bucket_; + } + std::chrono::milliseconds fillInterval() const { return fill_interval_; } + +private: + static TcpBandwidthLimitStats generateStats(const std::string& prefix, Stats::Scope& scope); + + Runtime::Loader& runtime_; + TimeSource& time_source_; + const uint64_t read_limit_kbps_; + const uint64_t write_limit_kbps_; + const std::chrono::milliseconds fill_interval_; + const Runtime::FeatureFlag enabled_; + TcpBandwidthLimitStats stats_; + std::shared_ptr read_token_bucket_; + std::shared_ptr write_token_bucket_; +}; + +using FilterConfigSharedPtr = std::shared_ptr; + +/** + * TCP bandwidth limit filter. Implements rate limiting for TCP connections. + */ +class TcpBandwidthLimitFilter : public Network::Filter, Logger::Loggable { +public: + explicit TcpBandwidthLimitFilter(FilterConfigSharedPtr config); + ~TcpBandwidthLimitFilter() override; + + // Network::ReadFilter + Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; + Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + write_buffer_.setWatermarks(callbacks.connection().bufferLimit()); + } + + // Network::WriteFilter + Network::FilterStatus onWrite(Buffer::Instance& data, bool end_stream) override; + void initializeWriteFilterCallbacks(Network::WriteFilterCallbacks& callbacks) override { + write_callbacks_ = &callbacks; + read_buffer_.setWatermarks(callbacks.connection().bufferLimit()); + } + + void onReadTokenTimer(); + void onWriteTokenTimer(); + +private: + friend class TcpBandwidthLimitFilterTest; + + void processBufferedReadData(); + void processBufferedWriteData(); + void onReadBufferLowWatermark(); + void onReadBufferHighWatermark(); + void onWriteBufferLowWatermark(); + void onWriteBufferHighWatermark(); + void updateReadRate(uint64_t bytes); + void updateWriteRate(uint64_t bytes); + + FilterConfigSharedPtr config_; + + Network::ReadFilterCallbacks* read_callbacks_{}; + Network::WriteFilterCallbacks* write_callbacks_{}; + + // Buffered data waiting for tokens + Buffer::WatermarkBuffer read_buffer_; + Buffer::WatermarkBuffer write_buffer_; + + // Whether end_stream was observed while buffering data + bool read_end_stream_{false}; + bool write_end_stream_{false}; + + // Bytes sent since last rate update + uint64_t read_bytes_since_last_rate_{0}; + uint64_t write_bytes_since_last_rate_{0}; + MonotonicTime last_read_rate_update_; + MonotonicTime last_write_rate_update_; + + // Timers for processing buffered data + Event::TimerPtr read_timer_; + Event::TimerPtr write_timer_; +}; + +} // namespace TcpBandwidthLimit +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/tcp_bandwidth_limit/BUILD b/test/extensions/filters/network/tcp_bandwidth_limit/BUILD new file mode 100644 index 0000000000000..3ea1651971c22 --- /dev/null +++ b/test/extensions/filters/network/tcp_bandwidth_limit/BUILD @@ -0,0 +1,52 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "filter_test", + srcs = ["filter_test.cc"], + deps = [ + "//source/extensions/filters/network/tcp_bandwidth_limit", + "//test/mocks/event:event_mocks", + "//test/mocks/network:network_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + deps = [ + "//source/extensions/filters/network/tcp_bandwidth_limit:config", + "//test/mocks/event:event_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "tcp_bandwidth_limit_integration_test", + size = "large", + srcs = ["tcp_bandwidth_limit_integration_test.cc"], + extension_names = ["envoy.filters.network.tcp_bandwidth_limit"], + rbe_pool = "2core", + deps = [ + "//source/extensions/filters/network/tcp_bandwidth_limit:config", + "//source/extensions/filters/network/tcp_proxy:config", + "//test/integration:integration_lib", + ], +) diff --git a/test/extensions/filters/network/tcp_bandwidth_limit/config_test.cc b/test/extensions/filters/network/tcp_bandwidth_limit/config_test.cc new file mode 100644 index 0000000000000..a92325447e2d4 --- /dev/null +++ b/test/extensions/filters/network/tcp_bandwidth_limit/config_test.cc @@ -0,0 +1,194 @@ +#include "source/extensions/filters/network/tcp_bandwidth_limit/config.h" + +#include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::NiceMock; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace TcpBandwidthLimit { +namespace { + +class TcpBandwidthLimitConfigTest : public ::testing::Test { +public: + void setup(const std::string& yaml) { + envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + factory_ = std::make_unique(); + + ON_CALL(context_, scope()).WillByDefault(ReturnRef(*stats_store_.rootScope())); + ON_CALL(context_, serverFactoryContext()).WillByDefault(ReturnRef(server_context_)); + ON_CALL(server_context_, runtime()).WillByDefault(ReturnRef(runtime_)); + ON_CALL(server_context_, mainThreadDispatcher()).WillByDefault(ReturnRef(dispatcher_)); + ON_CALL(server_context_, timeSource()).WillByDefault(ReturnRef(time_source_)); + + auto result = factory_->createFilterFactoryFromProto(proto_config, context_); + EXPECT_TRUE(result.ok()); + cb_ = result.value(); + } + + Stats::IsolatedStoreImpl stats_store_; + NiceMock context_; + NiceMock server_context_; + NiceMock runtime_; + NiceMock dispatcher_; + Event::SimulatedTimeSystem time_source_; + std::unique_ptr factory_; + Network::FilterFactoryCb cb_; +}; + +TEST_F(TcpBandwidthLimitConfigTest, BasicConfig) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1024 + write_limit_kbps: 512 + )EOF"; + + setup(yaml); + EXPECT_TRUE(cb_ != nullptr); + + // Test filter creation + Network::MockFilterManager filter_manager; + EXPECT_CALL(filter_manager, addFilter(_)); + cb_(filter_manager); +} + +TEST_F(TcpBandwidthLimitConfigTest, MinimalConfig) { + const std::string yaml = R"EOF( + stat_prefix: test + )EOF"; + + setup(yaml); + EXPECT_TRUE(cb_ != nullptr); + + // Test filter creation + Network::MockFilterManager filter_manager; + EXPECT_CALL(filter_manager, addFilter(_)); + cb_(filter_manager); +} + +TEST_F(TcpBandwidthLimitConfigTest, ConfigWithFillInterval) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 100 + fill_interval: + seconds: 0 + nanos: 100000000 + )EOF"; + + setup(yaml); + EXPECT_TRUE(cb_ != nullptr); + + // Test filter creation + Network::MockFilterManager filter_manager; + EXPECT_CALL(filter_manager, addFilter(_)); + cb_(filter_manager); +} + +TEST_F(TcpBandwidthLimitConfigTest, ConfigWithRuntimeFlag) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 256 + runtime_enabled: + default_value: false + runtime_key: bandwidth_limit_enabled + )EOF"; + + setup(yaml); + EXPECT_TRUE(cb_ != nullptr); + + // Test filter creation + Network::MockFilterManager filter_manager; + EXPECT_CALL(filter_manager, addFilter(_)); + cb_(filter_manager); +} + +TEST_F(TcpBandwidthLimitConfigTest, ConfigWithZeroLimits) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 0 + write_limit_kbps: 0 + )EOF"; + + setup(yaml); + EXPECT_TRUE(cb_ != nullptr); + + // Test filter creation + Network::MockFilterManager filter_manager; + EXPECT_CALL(filter_manager, addFilter(_)); + cb_(filter_manager); +} + +TEST_F(TcpBandwidthLimitConfigTest, FactoryName) { + auto factory = TcpBandwidthLimitConfigFactory(); + EXPECT_EQ("envoy.filters.network.tcp_bandwidth_limit", factory.name()); +} + +TEST_F(TcpBandwidthLimitConfigTest, EmptyProto) { + envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit proto_config; + TcpBandwidthLimitConfigFactory factory; + + // Should throw with empty stat_prefix + EXPECT_THROW( + { + auto result = factory.createFilterFactoryFromProto(proto_config, context_); + (void)result; // Suppress unused result warning + }, + EnvoyException); +} + +TEST_F(TcpBandwidthLimitConfigTest, InvalidFillInterval) { + // Test fill interval too small (< 20ms) + const std::string yaml1 = R"EOF( + stat_prefix: test + read_limit_kbps: 100 + fill_interval: + seconds: 0 + nanos: 10000000 + )EOF"; + + envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit proto_config1; + TestUtility::loadFromYaml(yaml1, proto_config1); + + TcpBandwidthLimitConfigFactory factory; + // Should throw when creating config with invalid fill interval + EXPECT_THROW( + { + auto result = factory.createFilterFactoryFromProto(proto_config1, context_); + (void)result; // Suppress unused result warning + }, + EnvoyException); + + // Test fill interval too large (> 1s) + const std::string yaml2 = R"EOF( + stat_prefix: test + read_limit_kbps: 100 + fill_interval: + seconds: 2 + nanos: 0 + )EOF"; + + envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit proto_config2; + TestUtility::loadFromYaml(yaml2, proto_config2); + + // Should throw when creating config with invalid fill interval + EXPECT_THROW( + { + auto result = factory.createFilterFactoryFromProto(proto_config2, context_); + (void)result; // Suppress unused result warning + }, + EnvoyException); +} + +} // namespace +} // namespace TcpBandwidthLimit +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc b/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc new file mode 100644 index 0000000000000..7037445a519e9 --- /dev/null +++ b/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc @@ -0,0 +1,989 @@ +#include "source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.h" + +#include "test/mocks/event/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::InSequence; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace TcpBandwidthLimit { + +class TcpBandwidthLimitFilterTest : public ::testing::Test { +public: + Buffer::WatermarkBuffer& getReadBuffer() { return filter_->read_buffer_; } + Buffer::WatermarkBuffer& getWriteBuffer() { return filter_->write_buffer_; } + Event::TimerPtr& getReadTimer() { return filter_->read_timer_; } + Event::TimerPtr& getWriteTimer() { return filter_->write_timer_; } + + void setup(const std::string& yaml) { + envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + + config_ = std::make_shared(proto_config, *stats_store_.rootScope(), runtime_, + time_source_); + filter_ = std::make_unique(config_); + + // Set a buffer limit so the WatermarkBuffer watermarks are active. + ON_CALL(read_filter_callbacks_.connection_, bufferLimit()).WillByDefault(Return(1)); + ON_CALL(write_filter_callbacks_.connection_, bufferLimit()).WillByDefault(Return(1)); + filter_->initializeReadFilterCallbacks(read_filter_callbacks_); + filter_->initializeWriteFilterCallbacks(write_filter_callbacks_); + } + + NiceMock runtime_; + Stats::IsolatedStoreImpl stats_store_; + Event::SimulatedTimeSystem time_source_; + NiceMock read_filter_callbacks_; + NiceMock write_filter_callbacks_; + FilterConfigSharedPtr config_; + std::unique_ptr filter_; +}; + +TEST_F(TcpBandwidthLimitFilterTest, ReadLimit) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data1(std::string(1024, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data1, false)); + + Buffer::OwnedImpl data2(std::string(512, 'b')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data2, false)); + + // Verify upload passes through (no upload limit configured) + Buffer::OwnedImpl upload_data(std::string(10000, 'c')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(upload_data, false)); +} + +TEST_F(TcpBandwidthLimitFilterTest, WriteLimit) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data1(std::string(1024, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(data1, false)); + + Buffer::OwnedImpl data2(std::string(512, 'b')); + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(data2, false)); + + // Verify download passes through (no download limit configured) + Buffer::OwnedImpl download_data(std::string(10000, 'c')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(download_data, false)); +} + +TEST_F(TcpBandwidthLimitFilterTest, RuntimeDisabled) { + EXPECT_CALL(runtime_.snapshot_, featureEnabled("test_key", false)).WillRepeatedly(Return(false)); + + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + runtime_enabled: + default_value: false + runtime_key: test_key + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data(std::string(200, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data, false)); +} + +TEST_F(TcpBandwidthLimitFilterTest, BothLimitsConfigured) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + write_limit_kbps: 2 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl download_data(std::string(1024, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(download_data, false)); + + Buffer::OwnedImpl upload_data(std::string(2048, 'b')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(upload_data, false)); + + Buffer::OwnedImpl download_data2(std::string(512, 'c')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(download_data2, false)); + + Buffer::OwnedImpl upload_data2(std::string(512, 'd')); + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(upload_data2, false)); +} + +TEST_F(TcpBandwidthLimitFilterTest, PartialConsumption) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl large_data(std::string(2048, 'a')); + + Buffer::OwnedImpl injected_data; + EXPECT_CALL(read_filter_callbacks_, injectReadDataToFilterChain(_, false)) + .WillOnce( + Invoke([&injected_data](Buffer::Instance& data, bool) { injected_data.move(data); })); + + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(large_data, false)); + + EXPECT_EQ(1024, injected_data.length()); + EXPECT_EQ(std::string(1024, 'a'), injected_data.toString()); + + EXPECT_EQ(0, large_data.length()); +} + +TEST_F(TcpBandwidthLimitFilterTest, PartialConsumptionWrite) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 1 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl large_data(std::string(2048, 'b')); + + Buffer::OwnedImpl written_data; + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, false)) + .WillOnce(Invoke([&written_data](Buffer::Instance& data, bool) { written_data.move(data); })); + + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(large_data, false)); + + EXPECT_EQ(1024, written_data.length()); + EXPECT_EQ(std::string(1024, 'b'), written_data.toString()); + + EXPECT_EQ(0, large_data.length()); +} + +TEST_F(TcpBandwidthLimitFilterTest, NoLimitPassThrough) { + const std::string yaml = R"EOF( + stat_prefix: test + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl large_download(std::string(10000, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(large_download, false)); + EXPECT_EQ(10000, large_download.length()); + + Buffer::OwnedImpl large_upload(std::string(10000, 'b')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(large_upload, false)); + EXPECT_EQ(10000, large_upload.length()); +} + +TEST_F(TcpBandwidthLimitFilterTest, ZeroLimitBlocksAllRead) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 0 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data(std::string(1024, 'a')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data, false)); + + EXPECT_EQ(0, data.length()); + EXPECT_CALL(read_filter_callbacks_, injectReadDataToFilterChain(_, _)).Times(0); +} + +TEST_F(TcpBandwidthLimitFilterTest, ZeroLimitBlocksAllWrite) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 0 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data(std::string(1024, 'b')); + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(data, false)); + + EXPECT_EQ(0, data.length()); + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, _)).Times(0); +} + +TEST_F(TcpBandwidthLimitFilterTest, ConnectionClosed) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + write_limit_kbps: 1 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl empty_data1; + Buffer::OwnedImpl empty_data2; + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(empty_data1, true)); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(empty_data2, true)); +} + +TEST_F(TcpBandwidthLimitFilterTest, FillIntervalValidation) { + // Test minimum fill interval (20ms) + const std::string yaml1 = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 20000000 + )EOF"; + + setup(yaml1); + EXPECT_EQ(std::chrono::milliseconds(20), config_->fillInterval()); + + // Test maximum fill interval (1s) + const std::string yaml2 = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + fill_interval: + seconds: 1 + nanos: 0 + )EOF"; + + FilterConfigSharedPtr config2 = std::make_shared( + TestUtility::parseYaml< + envoy::extensions::filters::network::tcp_bandwidth_limit::v3::TcpBandwidthLimit>(yaml2), + *stats_store_.rootScope(), runtime_, time_source_); + EXPECT_EQ(std::chrono::milliseconds(1000), config2->fillInterval()); +} + +TEST_F(TcpBandwidthLimitFilterTest, DefaultFillInterval) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 100 + )EOF"; + + setup(yaml); + EXPECT_EQ(std::chrono::milliseconds(50), config_->fillInterval()); +} + +TEST_F(TcpBandwidthLimitFilterTest, EmptyDataWithLimits) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 100 + write_limit_kbps: 100 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl empty_data; + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(empty_data, false)); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(empty_data, false)); +} + +TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedDataScenarios) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + write_limit_kbps: 1 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl download_data(std::string(2048, 'a')); + Buffer::OwnedImpl injected_data; + + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + EXPECT_CALL(read_filter_callbacks_, injectReadDataToFilterChain(_, false)) + .WillOnce( + Invoke([&injected_data](Buffer::Instance& data, bool) { injected_data.move(data); })); + + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(download_data, false)); + EXPECT_EQ(1024, injected_data.length()); + + Buffer::OwnedImpl upload_data(std::string(2048, 'b')); + Buffer::OwnedImpl written_data; + + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, false)) + .WillOnce(Invoke([&written_data](Buffer::Instance& data, bool) { written_data.move(data); })); + + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(upload_data, false)); + EXPECT_EQ(1024, written_data.length()); +} + +TEST_F(TcpBandwidthLimitFilterTest, DestructorCleanup) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + write_limit_kbps: 1 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data(std::string(2048, 'a')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data, false)); + + filter_.reset(); +} + +TEST_F(TcpBandwidthLimitFilterTest, ConfigAccessors) { + const std::string yaml_with_limits = R"EOF( + stat_prefix: test + read_limit_kbps: 100 + write_limit_kbps: 50 + fill_interval: + seconds: 0 + nanos: 100000000 + )EOF"; + + setup(yaml_with_limits); + + EXPECT_TRUE(config_->hasReadLimit()); + EXPECT_TRUE(config_->hasWriteLimit()); + EXPECT_EQ(100, config_->readLimit()); + EXPECT_EQ(50, config_->writeLimit()); + EXPECT_EQ(std::chrono::milliseconds(100), config_->fillInterval()); + EXPECT_TRUE(config_->enabled()); + + EXPECT_EQ(&runtime_, &config_->runtime()); + EXPECT_EQ(&time_source_, &config_->timeSource()); + + const std::string yaml_no_limits = R"EOF( + stat_prefix: test + )EOF"; + + setup(yaml_no_limits); + + EXPECT_FALSE(config_->hasReadLimit()); + EXPECT_FALSE(config_->hasWriteLimit()); + EXPECT_EQ(std::chrono::milliseconds(50), config_->fillInterval()); // Default 50ms +} + +TEST_F(TcpBandwidthLimitFilterTest, OnNewConnectionTest) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 100 + )EOF"; + + setup(yaml); + + auto new_filter = std::make_unique(config_); + + EXPECT_EQ(Network::FilterStatus::Continue, new_filter->onNewConnection()); + + new_filter->initializeReadFilterCallbacks(read_filter_callbacks_); + new_filter->initializeWriteFilterCallbacks(write_filter_callbacks_); + + Buffer::OwnedImpl data(std::string(100, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, new_filter->onData(data, false)); +} + +TEST_F(TcpBandwidthLimitFilterTest, ReadTimerBufferDraining) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Event::MockTimer* timer = + new NiceMock(&read_filter_callbacks_.connection_.dispatcher_); + ON_CALL(read_filter_callbacks_.connection_.dispatcher_, createTimer_(_)) + .WillByDefault(testing::Invoke([timer](Event::TimerCb) { return timer; })); + + Buffer::OwnedImpl data1(std::string(1024, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data1, false)); + + Buffer::OwnedImpl data2(std::string(100, 'b')); + ON_CALL(read_filter_callbacks_.connection_, readDisable(true)) + .WillByDefault( + testing::Return(Network::Connection::ReadDisableStatus::TransitionedToReadDisabled)); + ON_CALL(*timer, enableTimer(std::chrono::milliseconds(50), _)).WillByDefault(testing::Return()); + + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data2, false)); + + ON_CALL(read_filter_callbacks_, injectReadDataToFilterChain(_, false)) + .WillByDefault(testing::Return()); + ON_CALL(read_filter_callbacks_.connection_, readDisable(false)) + .WillByDefault(testing::Return(Network::Connection::ReadDisableStatus::NoTransition)); + + filter_->onReadTokenTimer(); + filter_->onReadTokenTimer(); + filter_->onReadTokenTimer(); +} + +TEST_F(TcpBandwidthLimitFilterTest, WriteTimerBufferDraining) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Event::MockTimer* timer = + new NiceMock(&read_filter_callbacks_.connection_.dispatcher_); + ON_CALL(read_filter_callbacks_.connection_.dispatcher_, createTimer_(_)) + .WillByDefault(testing::Invoke([timer](Event::TimerCb) { return timer; })); + + Buffer::OwnedImpl data1(std::string(1024, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(data1, false)); + + Buffer::OwnedImpl data2(std::string(100, 'b')); + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + ON_CALL(*timer, enableTimer(std::chrono::milliseconds(50), _)).WillByDefault(testing::Return()); + + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(data2, false)); + + ON_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, _)) + .WillByDefault(testing::Return()); + ON_CALL(write_filter_callbacks_, onBelowWriteBufferLowWatermark()) + .WillByDefault(testing::Return()); + + filter_->onWriteTokenTimer(); + filter_->onWriteTokenTimer(); + filter_->onWriteTokenTimer(); +} + +TEST_F(TcpBandwidthLimitFilterTest, SimultaneousTimers) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + write_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Event::MockTimer* download_timer = + new NiceMock(&read_filter_callbacks_.connection_.dispatcher_); + Event::MockTimer* upload_timer = + new NiceMock(&read_filter_callbacks_.connection_.dispatcher_); + int timer_count = 0; + ON_CALL(read_filter_callbacks_.connection_.dispatcher_, createTimer_(_)) + .WillByDefault(testing::Invoke([&timer_count, download_timer, upload_timer](Event::TimerCb) { + return (timer_count++ == 0) ? download_timer : upload_timer; + })); + + Buffer::OwnedImpl data1(std::string(1024, 'x')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data1, false)); + Buffer::OwnedImpl data2(std::string(1024, 'y')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(data2, false)); + + Buffer::OwnedImpl download_data(std::string(512, 'a')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + ON_CALL(*download_timer, enableTimer(std::chrono::milliseconds(50), _)) + .WillByDefault(testing::Return()); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(download_data, false)); + + Buffer::OwnedImpl upload_data(std::string(512, 'b')); + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + ON_CALL(*upload_timer, enableTimer(std::chrono::milliseconds(50), _)) + .WillByDefault(testing::Return()); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(upload_data, false)); + + ON_CALL(read_filter_callbacks_, injectReadDataToFilterChain(_, false)) + .WillByDefault(testing::Return()); + ON_CALL(read_filter_callbacks_.connection_, readDisable(false)) + .WillByDefault(testing::Return(Network::Connection::ReadDisableStatus::NoTransition)); + ON_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, _)) + .WillByDefault(testing::Return()); + + filter_->onReadTokenTimer(); + + filter_->onWriteTokenTimer(); + + // Verify both can be called again without issues + filter_->onReadTokenTimer(); + filter_->onWriteTokenTimer(); +} + +// Test edge case: throttling with small amounts of data +TEST_F(TcpBandwidthLimitFilterTest, TimerWithEmptyBuffer) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + )EOF"; + + setup(yaml); + + Event::MockTimer* timer = + new NiceMock(&read_filter_callbacks_.connection_.dispatcher_); + ON_CALL(read_filter_callbacks_.connection_.dispatcher_, createTimer_(_)) + .WillByDefault(testing::Invoke([timer](Event::TimerCb) { return timer; })); + + Buffer::OwnedImpl data1(std::string(1024, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data1, false)); + + Buffer::OwnedImpl data2(std::string(100, 'b')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + ON_CALL(*timer, enableTimer(std::chrono::milliseconds(50), _)).WillByDefault(testing::Return()); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data2, false)); + + ON_CALL(read_filter_callbacks_, injectReadDataToFilterChain(_, false)) + .WillByDefault(testing::Return()); + ON_CALL(read_filter_callbacks_.connection_, readDisable(false)) + .WillByDefault(testing::Return(Network::Connection::ReadDisableStatus::NoTransition)); + + // First timer fire: processes the 100 bytes of buffered data + // This should inject the buffered data and may re-enable the timer + filter_->onReadTokenTimer(); + + // Subsequent fires: handle the empty buffer case + // When buffer is empty, timer should be reset and read re-enabled + filter_->onReadTokenTimer(); + + // One more call to ensure empty buffer handling is robust + filter_->onReadTokenTimer(); +} + +// Test stats increment for throttling +TEST_F(TcpBandwidthLimitFilterTest, StatsIncrement) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + write_limit_kbps: 1 + )EOF"; + + setup(yaml); + + EXPECT_EQ(0, stats_store_.counterFromString("test.tcp_bandwidth_limit.read_enabled").value()); + EXPECT_EQ(0, stats_store_.counterFromString("test.tcp_bandwidth_limit.write_enabled").value()); + EXPECT_EQ(0, stats_store_.counterFromString("test.tcp_bandwidth_limit.read_throttled").value()); + EXPECT_EQ(0, stats_store_.counterFromString("test.tcp_bandwidth_limit.write_throttled").value()); + + Buffer::OwnedImpl data1(std::string(512, 'a')); + filter_->onData(data1, false); + EXPECT_EQ(1, stats_store_.counterFromString("test.tcp_bandwidth_limit.read_enabled").value()); + + Buffer::OwnedImpl data2(std::string(512, 'b')); + filter_->onWrite(data2, false); + EXPECT_EQ(1, stats_store_.counterFromString("test.tcp_bandwidth_limit.write_enabled").value()); + + Buffer::OwnedImpl large_data(std::string(2048, 'c')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + filter_->onData(large_data, false); + EXPECT_EQ(2, stats_store_.counterFromString("test.tcp_bandwidth_limit.read_enabled").value()); + EXPECT_EQ(1, stats_store_.counterFromString("test.tcp_bandwidth_limit.read_throttled").value()); + EXPECT_EQ(1536, stats_store_ + .gaugeFromString("test.tcp_bandwidth_limit.read_bytes_buffered", + Stats::Gauge::ImportMode::Accumulate) + .value()); + + Buffer::OwnedImpl large_upload(std::string(2048, 'd')); + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + filter_->onWrite(large_upload, false); + EXPECT_EQ(2, stats_store_.counterFromString("test.tcp_bandwidth_limit.write_enabled").value()); + EXPECT_EQ(1, stats_store_.counterFromString("test.tcp_bandwidth_limit.write_throttled").value()); + EXPECT_EQ(1536, stats_store_ + .gaugeFromString("test.tcp_bandwidth_limit.write_bytes_buffered", + Stats::Gauge::ImportMode::Accumulate) + .value()); +} + +TEST_F(TcpBandwidthLimitFilterTest, TimerReEnableReadAlreadyEnabled) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl initial_data(std::string(1024, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(initial_data, false)); + + Buffer::OwnedImpl more_data(std::string(512, 'b')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(more_data, false)); + + ON_CALL(read_filter_callbacks_.connection_, readDisable(false)) + .WillByDefault(Return(Network::Connection::ReadDisableStatus::NoTransition)); + + Buffer::OwnedImpl final_data(std::string(256, 'c')); + filter_->onData(final_data, false); +} + +// Test timer callback when buffer becomes empty +TEST_F(TcpBandwidthLimitFilterTest, ReadTimerEmptyBufferPath) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 10 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data(std::string(100, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data, false)); + + filter_->onReadTokenTimer(); +} + +// Test upload timer callback when buffer becomes empty +TEST_F(TcpBandwidthLimitFilterTest, WriteTimerEmptyBufferPath) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 10 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data(std::string(100, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(data, false)); + + filter_->onWriteTokenTimer(); +} + +TEST_F(TcpBandwidthLimitFilterTest, PartialTokenConsumptionRead) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data1(std::string(900, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data1, false)); + + Buffer::OwnedImpl data2(std::string(500, 'b')); + + Buffer::OwnedImpl injected_data; + EXPECT_CALL(read_filter_callbacks_, injectReadDataToFilterChain(_, false)) + .WillOnce( + Invoke([&injected_data](Buffer::Instance& data, bool) { injected_data.move(data); })); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data2, false)); + EXPECT_GT(injected_data.length(), 0); + EXPECT_LT(injected_data.length(), 500); +} + +TEST_F(TcpBandwidthLimitFilterTest, PartialTokenConsumptionWrite) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data1(std::string(900, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(data1, false)); + + Buffer::OwnedImpl data2(std::string(500, 'b')); + + Buffer::OwnedImpl written_data; + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, false)) + .WillOnce(Invoke([&written_data](Buffer::Instance& data, bool) { written_data.move(data); })); + + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(data2, false)); + EXPECT_GT(written_data.length(), 0); + EXPECT_LT(written_data.length(), 500); +} + +// Edge case: Connection closing while data is buffered +TEST_F(TcpBandwidthLimitFilterTest, ConnectionClosingWithBufferedData) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data1(std::string(1024, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data1, false)); + + Buffer::OwnedImpl data2(std::string(512, 'b')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data2, false)); +} + +TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedReadDataEmptyBuffer) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + )EOF"; + + setup(yaml); + + // Directly call with empty buffer should return early + filter_->onReadTokenTimer(); +} + +TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedReadDataNoTokenBucket) { + const std::string yaml = R"EOF( + stat_prefix: test + )EOF"; + + setup(yaml); + + // Directly call with empty buffer should return early + filter_->onReadTokenTimer(); +} + +TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedWriteDataEmptyBuffer) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 1 + )EOF"; + + setup(yaml); + + // Directly call with empty buffer should return early + filter_->onWriteTokenTimer(); +} + +TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedWriteDataNoTokenBucket) { + const std::string yaml = R"EOF( + stat_prefix: test + )EOF"; + + setup(yaml); + + // Directly call with empty buffer should return early + filter_->onWriteTokenTimer(); +} + +TEST_F(TcpBandwidthLimitFilterTest, ReadTimerDrainsBuffer) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data(std::string(2048, 'x')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + EXPECT_CALL(read_filter_callbacks_, injectReadDataToFilterChain(_, false)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data, true)); + + // Advance time so the token bucket refills + time_source_.advanceTimeWait(std::chrono::milliseconds(1100)); + + EXPECT_CALL(read_filter_callbacks_, injectReadDataToFilterChain(_, true)); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(false)); + filter_->onReadTokenTimer(); + + EXPECT_EQ(0, getReadBuffer().length()); + EXPECT_EQ(nullptr, getReadTimer()); +} + +TEST_F(TcpBandwidthLimitFilterTest, WriteTimerDrainsBuffer) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + Buffer::OwnedImpl data(std::string(2048, 'x')); + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, false)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(data, true)); + + // Advance time so the token bucket refills + time_source_.advanceTimeWait(std::chrono::milliseconds(1100)); + + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, true)); + EXPECT_CALL(write_filter_callbacks_, onBelowWriteBufferLowWatermark()); + filter_->onWriteTokenTimer(); + + EXPECT_EQ(0, getWriteBuffer().length()); + EXPECT_EQ(nullptr, getWriteTimer()); +} + +TEST_F(TcpBandwidthLimitFilterTest, WriteTimerResetPath) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + // First trigger buffering + Buffer::OwnedImpl data(std::string(2048, 'x')); + auto* timer = new Event::MockTimer(&read_filter_callbacks_.connection_.dispatcher_); + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + EXPECT_CALL(*timer, enableTimer(std::chrono::milliseconds(50), nullptr)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(data, false)); + + // Clear the buffer completely to test the empty buffer path + getWriteBuffer().drain(getWriteBuffer().length()); + filter_->onWriteTokenTimer(); + + // Verify timer was reset + EXPECT_EQ(nullptr, getWriteTimer()); +} + +TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedReadWithTokens) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + getReadBuffer().add("test data"); + getReadTimer().reset(new Event::MockTimer()); + + time_source_.advanceTimeWait(std::chrono::milliseconds(100)); + EXPECT_CALL(read_filter_callbacks_, + injectReadDataToFilterChain(BufferStringEqual("test data"), false)); + + filter_->onReadTokenTimer(); + + // Verify buffer is now empty and timer is reset + EXPECT_EQ(0, getReadBuffer().length()); + EXPECT_EQ(nullptr, getReadTimer()); +} + +TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedWriteWithTokens) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + getWriteBuffer().add("test data"); + getWriteTimer().reset(new Event::MockTimer()); + + time_source_.advanceTimeWait(std::chrono::milliseconds(100)); + + EXPECT_CALL(write_filter_callbacks_, + injectWriteDataToFilterChain(BufferStringEqual("test data"), false)); + EXPECT_CALL(write_filter_callbacks_, onBelowWriteBufferLowWatermark()); + + filter_->onWriteTokenTimer(); + + // Verify buffer is now empty and timer is reset + EXPECT_EQ(0, getWriteBuffer().length()); + EXPECT_EQ(nullptr, getWriteTimer()); +} + +// Test that new data is buffered when there's already pending data, to preserve byte ordering. +TEST_F(TcpBandwidthLimitFilterTest, ReadOrderingWithPendingBuffer) { + const std::string yaml = R"EOF( + stat_prefix: test + read_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + // First call: consume all tokens + Buffer::OwnedImpl data1(std::string(1024, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data1, false)); + + // Second call: partial consumption, buffer remainder + Buffer::OwnedImpl data2(std::string(512, 'b')); + EXPECT_CALL(read_filter_callbacks_.connection_, readDisable(true)); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data2, false)); + EXPECT_EQ(512, getReadBuffer().length()); + + // Third call: buffer has pending data. New data must be appended without consuming tokens. + EXPECT_CALL(read_filter_callbacks_, injectReadDataToFilterChain(_, _)).Times(0); + Buffer::OwnedImpl data3(std::string(256, 'c')); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data3, false)); + EXPECT_EQ(512 + 256, getReadBuffer().length()); +} + +TEST_F(TcpBandwidthLimitFilterTest, WriteOrderingWithPendingBuffer) { + const std::string yaml = R"EOF( + stat_prefix: test + write_limit_kbps: 1 + fill_interval: + seconds: 0 + nanos: 50000000 + )EOF"; + + setup(yaml); + + // First call: consume all tokens + Buffer::OwnedImpl data1(std::string(1024, 'a')); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onWrite(data1, false)); + + // Second call: partial consumption, buffer remainder + Buffer::OwnedImpl data2(std::string(512, 'b')); + EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(data2, false)); + EXPECT_EQ(512, getWriteBuffer().length()); + + // Third call: buffer has pending data. New data must be appended without consuming tokens. + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, _)).Times(0); + Buffer::OwnedImpl data3(std::string(256, 'c')); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onWrite(data3, false)); + EXPECT_EQ(512 + 256, getWriteBuffer().length()); +} + +} // namespace TcpBandwidthLimit +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit_integration_test.cc b/test/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit_integration_test.cc new file mode 100644 index 0000000000000..9a24b641f559a --- /dev/null +++ b/test/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit_integration_test.cc @@ -0,0 +1,122 @@ +#include "test/integration/integration.h" + +namespace Envoy { +namespace { + +class TcpBandwidthLimitIntegrationTest : public Event::TestUsingSimulatedTime, + public testing::TestWithParam, + public BaseIntegrationTest { +public: + TcpBandwidthLimitIntegrationTest() + : BaseIntegrationTest(GetParam(), ConfigHelper::tcpProxyConfig()) {} + + void setup(const std::string& filter_yaml) { + config_helper_.addNetworkFilter(filter_yaml); + BaseIntegrationTest::initialize(); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, TcpBandwidthLimitIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(TcpBandwidthLimitIntegrationTest, ReadLimiting) { + setup(R"EOF( +name: envoy.filters.network.tcp_bandwidth_limit +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_bandwidth_limit.v3.TcpBandwidthLimit + stat_prefix: tcp_bw + read_limit_kbps: 1 +)EOF"); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + + ASSERT_TRUE(tcp_client->write("hello")); + ASSERT_TRUE(fake_upstream_connection->waitForData(5)); + + ASSERT_TRUE(fake_upstream_connection->write("world")); + tcp_client->waitForData("world"); + + EXPECT_EQ(test_server_->counter("tcp_bw.tcp_bandwidth_limit.read_enabled")->value(), 1); + EXPECT_EQ(test_server_->counter("tcp_bw.tcp_bandwidth_limit.read_throttled")->value(), 0); + + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); +} + +TEST_P(TcpBandwidthLimitIntegrationTest, WriteLimiting) { + setup(R"EOF( +name: envoy.filters.network.tcp_bandwidth_limit +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_bandwidth_limit.v3.TcpBandwidthLimit + stat_prefix: tcp_bw + write_limit_kbps: 1 +)EOF"); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + + ASSERT_TRUE(fake_upstream_connection->write("hello")); + tcp_client->waitForData("hello"); + + EXPECT_EQ(test_server_->counter("tcp_bw.tcp_bandwidth_limit.write_enabled")->value(), 1); + EXPECT_EQ(test_server_->counter("tcp_bw.tcp_bandwidth_limit.write_throttled")->value(), 0); + + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); +} + +TEST_P(TcpBandwidthLimitIntegrationTest, ReadThrottled) { + setup(R"EOF( +name: envoy.filters.network.tcp_bandwidth_limit +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_bandwidth_limit.v3.TcpBandwidthLimit + stat_prefix: tcp_bw + read_limit_kbps: 1 +)EOF"); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + + const std::string large_data(2048, 'a'); + ASSERT_TRUE(tcp_client->write(large_data)); + + test_server_->waitForCounterGe("tcp_bw.tcp_bandwidth_limit.read_throttled", 1); + test_server_->waitForGaugeEq("tcp_bw.tcp_bandwidth_limit.read_bytes_buffered", 1024); + + timeSystem().advanceTimeWait(std::chrono::milliseconds(100)); + test_server_->waitForGaugeEq("tcp_bw.tcp_bandwidth_limit.read_bytes_buffered", 0); + + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); +} + +TEST_P(TcpBandwidthLimitIntegrationTest, WriteThrottled) { + setup(R"EOF( +name: envoy.filters.network.tcp_bandwidth_limit +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_bandwidth_limit.v3.TcpBandwidthLimit + stat_prefix: tcp_bw + write_limit_kbps: 1 +)EOF"); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + + const std::string large_data(2048, 'b'); + ASSERT_TRUE(fake_upstream_connection->write(large_data)); + + test_server_->waitForCounterGe("tcp_bw.tcp_bandwidth_limit.write_throttled", 1); + test_server_->waitForGaugeEq("tcp_bw.tcp_bandwidth_limit.write_bytes_buffered", 1024); + + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); +} + +} // namespace +} // namespace Envoy diff --git a/test/mocks/network/connection.h b/test/mocks/network/connection.h index 4a096c9311a78..b089520cbcb4b 100644 --- a/test/mocks/network/connection.h +++ b/test/mocks/network/connection.h @@ -153,6 +153,8 @@ class MockFilterManagerConnection : public FilterManagerConnection, public MockC MOCK_METHOD(StreamBuffer, getWriteBuffer, ()); MOCK_METHOD(void, rawWrite, (Buffer::Instance & data, bool end_stream)); MOCK_METHOD(void, closeConnection, (ConnectionCloseAction close_action)); + MOCK_METHOD(void, onFilterAboveHighWatermark, ()); + MOCK_METHOD(void, onFilterBelowLowWatermark, ()); }; } // namespace Network diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index 08d7dbf69ee74..6243d8d4aa3cb 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -193,6 +193,8 @@ class MockWriteFilterCallbacks : public WriteFilterCallbacks { MOCK_METHOD(const ConnectionSocket&, socket, ()); MOCK_METHOD(void, injectWriteDataToFilterChain, (Buffer::Instance & data, bool end_stream)); MOCK_METHOD(void, disableClose, (bool disable)); + MOCK_METHOD(void, onAboveWriteBufferHighWatermark, ()); + MOCK_METHOD(void, onBelowWriteBufferLowWatermark, ()); testing::NiceMock connection_; testing::NiceMock socket_; From 398ebc7971ea8262da4ef9a89af52cfd4d66039e Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 14 Apr 2026 12:24:18 +0100 Subject: [PATCH 005/750] deps/py: Bump pygments -> 0.2.20 (#44441) Fix cve Signed-off-by: Ryan Northey --- docs/tools/python/requirements.in | 1 + docs/tools/python/requirements.txt | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/tools/python/requirements.in b/docs/tools/python/requirements.in index a2f904350b43b..72afa3cd25d18 100644 --- a/docs/tools/python/requirements.in +++ b/docs/tools/python/requirements.in @@ -16,5 +16,6 @@ envoy.code.check>=0.5.12 frozendict>=2.3.7 jinja2 packaging +pygments>=2.20.0 pyyaml uvloop==0.20.0 diff --git a/docs/tools/python/requirements.txt b/docs/tools/python/requirements.txt index 0b39836ffaa30..773816391980a 100644 --- a/docs/tools/python/requirements.txt +++ b/docs/tools/python/requirements.txt @@ -1216,10 +1216,11 @@ pyflakes==3.4.0 \ --hash=sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58 \ --hash=sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f # via flake8 -pygments==2.19.2 \ - --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ - --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b +pygments==2.20.0 \ + --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ + --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 # via + # -r requirements.in # envoy-docs-sphinx-runner # sphinx pyjwt[crypto]==2.12.1 \ From 544b80ad4dd869a30b9e26f48ea150f1329d70b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 12:31:55 +0100 Subject: [PATCH 006/750] build(deps): bump rand from 0.9.2 to 0.9.4 in the cargo group across 1 directory (#44432) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 755a11ae9dc24..52c487bfe3d85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -881,9 +881,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", "rand_core", From 86102d737568d25458d6fdabf2c2e965afb6a871 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 12:32:32 +0100 Subject: [PATCH 007/750] build(deps): bump actions/cache from 5.0.4 to 5.0.5 (#44434) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_request.yml | 4 ++-- .github/workflows/_run.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index f3f2e998bc543..4798ca2889e4c 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -176,14 +176,14 @@ jobs: # TODO(phlax): shift this to ci/request action above - name: Check Docker cache (x64) id: cache-exists-docker-x64 - uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: lookup-only: true path: /tmp/cache key: ${{ fromJSON(steps.data.outputs.value).request.build-image.default }} - name: Check Docker cache (arm64) id: cache-exists-docker-arm64 - uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: lookup-only: true path: /tmp/cache diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 289ba04a59af0..0d401d0ea24ed 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -318,7 +318,7 @@ jobs: # HACK/WORKAROUND for cache scope issue (https://github.com/envoyproxy/envoy/issues/37603) - if: ${{ inputs.cache-build-image }} id: cache-lookup - uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: lookup-only: true path: /tmp/cache From ab38c4ddbcf24867c94932be9746597b780f57b6 Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Tue, 14 Apr 2026 15:23:21 +0200 Subject: [PATCH 008/750] [hcm] Add ability to set Keep-alive as response header (#44161) Added ability to set Keep-Alive timeout response header which previously was sanitized by HCM. Keep-alive response header will only be appended when downstream connection is H1.x and when runtime flag is enabled. --> Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: Runtime guard "envoy.reloadable_features.preserve_downstream_keepalive" Fixes #17613 [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Kateryna Nezdolii --- changelogs/current.yaml | 6 +++ source/common/http/conn_manager_utility.cc | 9 +++- source/common/router/router.cc | 11 ++++ source/common/runtime/runtime_features.cc | 1 + test/common/http/conn_manager_utility_test.cc | 53 +++++++++++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 4e378383645cc..343195d02280c 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -189,6 +189,12 @@ bug_fixes: - area: http change: | Fixed crash if a downstream watermark is hit by network filter writes before the HTTP codec is created. +- area: http + change: | + Allow ``Keep-Alive`` response headers added via ``response_headers_to_add`` to be preserved instead + of being stripped by HCM sanitization. Upstream ``Keep-Alive`` headers are now stripped in the router + before user-configured response headers are applied, so user-added ``Keep-Alive`` headers survive. + Protected by runtime guard ``envoy.reloadable_features.preserve_downstream_keepalive``. - area: http change: | Fixed a bug where the internal redirect logic may hang up a request when the request buffer is diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index fc9f40b840a75..c99ac3b57d6e9 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -717,7 +717,14 @@ void ConnectionManagerUtility::mutateResponseHeaders(ResponseHeaderMap& response } if (clear_hop_by_hop) { response_headers.removeTransferEncoding(); - response_headers.removeKeepAlive(); + // Only preserve Keep-Alive for HTTP/1.x downstream connections when the feature is enabled. + // Keep-Alive is an HTTP/1.1 hop-by-hop header with no meaning in HTTP/2 or HTTP/3. + const auto protocol = stream_info.protocol(); + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.preserve_downstream_keepalive") || + !protocol.has_value() || *protocol >= Protocol::Http2) { + response_headers.removeKeepAlive(); + } response_headers.removeProxyConnection(); } diff --git a/source/common/router/router.cc b/source/common/router/router.cc index f7c4022b019ed..8b9b76d2f9090 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -1872,6 +1872,17 @@ void Filter::onUpstreamHeaders(uint64_t response_code, Http::ResponseHeaderMapPt // Modify response headers after we have set the final upstream info because we may need to // modify the headers based on the upstream host. + // + // Strip upstream hop-by-hop Keep-Alive header before applying user-configured response headers. + // This ensures that Keep-Alive headers added via response_headers_to_add are preserved, since + // HCM sanitization (mutateResponseHeaders) will skip removing Keep-Alive when this flag is on. + // Only relevant for HTTP/1.x downstream connections. + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.preserve_downstream_keepalive")) { + const auto protocol = callbacks_->streamInfo().protocol(); + if (protocol.has_value() && *protocol <= Http::Protocol::Http11) { + headers->removeKeepAlive(); + } + } const Formatter::Context formatter_context(downstream_headers_, headers.get(), {}, {}, {}, &callbacks_->activeSpan()); route_entry_->finalizeResponseHeaders(*headers, formatter_context, callbacks_->streamInfo()); diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 48ada70e481c4..73797d65d37a6 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -77,6 +77,7 @@ RUNTIME_GUARD(envoy_reloadable_features_on_demand_track_end_stream); RUNTIME_GUARD(envoy_reloadable_features_orca_weight_manager_use_named_metrics_first); RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout); RUNTIME_GUARD(envoy_reloadable_features_prefix_map_matcher_resume_after_subtree_miss); +RUNTIME_GUARD(envoy_reloadable_features_preserve_downstream_keepalive); RUNTIME_GUARD(envoy_reloadable_features_propagate_upstream_rst_through_tunneled_tcp_proxy); RUNTIME_GUARD(envoy_reloadable_features_proxy_protocol_allow_duplicate_tlvs); RUNTIME_GUARD(envoy_reloadable_features_quic_defer_logging_to_ack_listener); diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index 6a3feae6eaee6..ed66b55451dd1 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -2504,6 +2504,59 @@ TEST_F(ConnectionManagerUtilityTest, RemovesProxyResponseHeaders) { EXPECT_FALSE(response_headers.has("proxy-connection")); } +// Verify that when preserve_downstream_keepalive is enabled, Keep-Alive headers +// are preserved by mutateResponseHeaders for HTTP/1.1 downstream connections. +TEST_F(ConnectionManagerUtilityTest, PreserveUserAddedKeepAlive) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.preserve_downstream_keepalive", "true"}}); + + ON_CALL(connection_.stream_info_, protocol()).WillByDefault(Return(Http::Protocol::Http11)); + + Http::TestRequestHeaderMapImpl request_headers{{}}; + Http::TestResponseHeaderMapImpl response_headers{{"keep-alive", "timeout=70"}}; + ConnectionManagerUtility::mutateResponseHeaders(response_headers, &request_headers, config_, "", + connection_.stream_info_, node_id_); + + // Keep-Alive should be preserved when the runtime flag is enabled and downstream is HTTP/1.1. + EXPECT_TRUE(response_headers.has("keep-alive")); + EXPECT_EQ("timeout=70", response_headers.get_("keep-alive")); +} + +// Verify that when preserve_downstream_keepalive is disabled, Keep-Alive is still removed. +TEST_F(ConnectionManagerUtilityTest, RemoveKeepAliveWhenFlagDisabled) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.preserve_downstream_keepalive", "false"}}); + + ON_CALL(connection_.stream_info_, protocol()).WillByDefault(Return(Http::Protocol::Http11)); + + Http::TestRequestHeaderMapImpl request_headers{{}}; + Http::TestResponseHeaderMapImpl response_headers{{"keep-alive", "timeout=70"}, + {"proxy-connection", "proxy-header"}}; + ConnectionManagerUtility::mutateResponseHeaders(response_headers, &request_headers, config_, "", + connection_.stream_info_, node_id_); + + EXPECT_FALSE(response_headers.has("keep-alive")); + EXPECT_FALSE(response_headers.has("proxy-connection")); +} + +// Verify that Keep-Alive is always stripped for HTTP/2 downstream, even with +// preserve_downstream_keepalive enabled (Keep-Alive is an HTTP/1.1 concept). +TEST_F(ConnectionManagerUtilityTest, RemoveKeepAliveForHttp2Downstream) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.preserve_downstream_keepalive", "true"}}); + + ON_CALL(connection_.stream_info_, protocol()).WillByDefault(Return(Http::Protocol::Http2)); + + Http::TestRequestHeaderMapImpl request_headers{{}}; + Http::TestResponseHeaderMapImpl response_headers{{"keep-alive", "timeout=70"}}; + ConnectionManagerUtility::mutateResponseHeaders(response_headers, &request_headers, config_, "", + connection_.stream_info_, node_id_); + + // Keep-Alive must be stripped for HTTP/2 downstream regardless of the runtime flag. + EXPECT_FALSE(response_headers.has("keep-alive")); +} + // maybeNormalizePath() returns true with an empty path. TEST_F(ConnectionManagerUtilityTest, SanitizeEmptyPath) { ON_CALL(config_, shouldNormalizePath()).WillByDefault(Return(false)); From 15d9bdec54d391415f09648c2aac7ea951d84e9e Mon Sep 17 00:00:00 2001 From: Rudrakh Panigrahi Date: Tue, 14 Apr 2026 20:12:16 +0530 Subject: [PATCH 009/750] feat: ability to set status for destination not found in stateful session (#44210) Signed-off-by: Rudrakh Panigrahi --- .../v3/stateful_session.proto | 14 ++- changelogs/current.yaml | 9 ++ envoy/upstream/load_balancer.h | 18 +++- source/common/router/router.cc | 24 +++-- source/common/router/router.h | 9 +- .../common/upstream/cluster_manager_impl.cc | 30 ++++-- source/common/upstream/host_utility.cc | 18 ++-- source/common/upstream/host_utility.h | 26 +++++- .../http/stateful_session/stateful_session.cc | 9 +- .../http/stateful_session/stateful_session.h | 5 + .../upstream/cluster_manager_impl_test.cc | 17 ++++ test/common/upstream/host_utility_test.cc | 87 +++++++++--------- .../stateful_session/stateful_session_test.cc | 91 ++++++++++++++++++- 13 files changed, 269 insertions(+), 88 deletions(-) diff --git a/api/envoy/extensions/filters/http/stateful_session/v3/stateful_session.proto b/api/envoy/extensions/filters/http/stateful_session/v3/stateful_session.proto index b3e5e53af852f..a61d9124000cf 100644 --- a/api/envoy/extensions/filters/http/stateful_session/v3/stateful_session.proto +++ b/api/envoy/extensions/filters/http/stateful_session/v3/stateful_session.proto @@ -25,9 +25,11 @@ message StatefulSession { config.core.v3.TypedExtensionConfig session_state = 1; // Determines whether the HTTP request must be strictly routed to the requested destination. When set to ``true``, - // if the requested destination is unavailable, Envoy will return a 503 status code. The default value is ``false``, - // which allows Envoy to fall back to its load balancing mechanism. In this case, if the requested destination is not - // found, the request will be routed according to the load balancing algorithm. + // if the requested destination is not found in the set of available endpoints, Envoy will return a status code + // determined by ``status_on_strict_destination_not_found``. If the destination exists but is unhealthy, Envoy will + // always return ``503`` regardless of ``status_on_strict_destination_not_found``. The default value is ``false``, + // which allows Envoy to fall back to its load balancing mechanism and route the request according to the load + // balancing algorithm. bool strict = 2; // Optional stat prefix. If specified, the filter will emit statistics in the @@ -38,6 +40,12 @@ message StatefulSession { // Per-route configuration overrides do not support statistics and will not emit stats even if this field is set // in the per-route config. string stat_prefix = 3; + + // The HTTP status code to return when ``strict`` mode is enabled and the requested destination + // is not found in the set of available endpoints. This does not apply when the destination exists + // but is unhealthy. This field has no effect when ``strict`` is set to ``false`` and will be + // ignored. Defaults to ``503`` (Service Unavailable) if not specified or set to ``0``. + uint32 status_on_strict_destination_not_found = 4; } message StatefulSessionPerRoute { diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 343195d02280c..01634ee2d747a 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -982,6 +982,15 @@ new_features: Added support for clear route cache in the :ref:`set_filter_state http filter `. When ``clear_route_cache`` is set, the filter will clear the route cache for the current request after applying filter state updates. This is necessary if the route configuration may depend on the filter state values set. +- area: stateful_session + change: | + Added :ref:`status_on_strict_destination_not_found + ` + to the :ref:`stateful session filter `. When + :ref:`strict ` + mode is enabled and the requested destination is not found in the set of available endpoints, Envoy + will return the configured HTTP status code instead of the default ``503``. This does not apply when + the destination exists but is unhealthy. - area: tcp_proxy change: | Propagate upstream TCP RST to downstream when detected close type is RemoteReset. diff --git a/envoy/upstream/load_balancer.h b/envoy/upstream/load_balancer.h index 3ef70a0c09fbb..32f7f2737b7ba 100644 --- a/envoy/upstream/load_balancer.h +++ b/envoy/upstream/load_balancer.h @@ -6,6 +6,7 @@ #include "envoy/common/optref.h" #include "envoy/common/pure.h" #include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/http/codes.h" #include "envoy/network/transport_socket.h" #include "envoy/router/router.h" #include "envoy/stream_info/stream_info.h" @@ -30,14 +31,14 @@ namespace Upstream { using ClusterProto = envoy::config::cluster::v3::Cluster; /* - * A handle to allow cancelation of asynchronous host selection. + * A handle to allow cancellation of asynchronous host selection. * If chooseHost returns a HostSelectionResponse with an AsyncHostSelectionHandle - * handle, and the endpoint does not wish to receive onAsyncHostSelction call, + * handle, and the endpoint does not wish to receive onAsyncHostSelection call, * it must call cancel() on the provided handle. * * Please note that the AsyncHostSelectionHandle may be deleted after the - * cancel() call. It is up to the implemention of the asynchronous load balancer - * to ensure the cancelation state persists until the load balancer checks it. + * cancel() call. It is up to the implementation of the asynchronous load balancer + * to ensure the cancellation state persists until the load balancer checks it. */ class AsyncHostSelectionHandle { public: @@ -53,7 +54,7 @@ class AsyncHostSelectionHandle { * load balancing, returns an AsyncHostSelectionHandle handle. * * If it returns a AsyncHostSelectionHandle handle, the load balancer guarantees an - * eventual call to LoadBalancerContext::onAsyncHostSelction unless + * eventual call to LoadBalancerContext::onAsyncHostSelection unless * AsyncHostSelectionHandle::cancel is called. */ struct HostSelectionResponse { @@ -66,6 +67,9 @@ struct HostSelectionResponse { // Optional details if host selection fails (empty string implies no details). std::string details; std::unique_ptr cancelable; + // Optional HTTP status code to use when host selection fails because the strict override + // destination is missing from available endpoints. If not set, defaults to 503. + absl::optional failure_status; }; /** @@ -156,6 +160,10 @@ class LoadBalancerContext { // If strict and no valid host is found, the load balancer should return nullptr. // If not strict, the load balancer will select another host if the target host is not valid. bool strict{false}; + // The HTTP status code to return when strict mode is enabled and the target host + // is not found in the set of available endpoints. Does not apply when the host + // exists but is unhealthy. Defaults to 503 (ServiceUnavailable). + Http::Code status_on_strict_destination_not_found{Http::Code::ServiceUnavailable}; }; /** * Returns the host the load balancer should select directly. If the expected host exists and diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 8b9b76d2f9090..a2a6c58db9131 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -714,7 +714,7 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, // well as handling unsupported asynchronous host selection by treating it // as host selection failure and calling sendNoHealthyUpstreamResponse. continueDecodeHeaders(cluster, headers, end_stream, std::move(host_selection_response.host), - host_selection_response.details); + host_selection_response.details, host_selection_response.failure_status); return Http::FilterHeadersStatus::StopIteration; } @@ -756,13 +756,14 @@ void Filter::onAsyncHostSelection(Upstream::HostConstSharedPtr&& host, std::stri bool Filter::continueDecodeHeaders(Upstream::ThreadLocalCluster* cluster, Http::RequestHeaderMap& headers, bool end_stream, Upstream::HostConstSharedPtr&& selected_host, - absl::string_view host_selection_details) { + absl::string_view host_selection_details, + absl::optional failure_status) { callbacks_->streamInfo().downstreamTiming().setValue( "envoy.router.host_selection_end_ms", callbacks_->dispatcher().timeSource().monotonicTime()); std::unique_ptr generic_conn_pool = createConnPool(*cluster, selected_host); if (!generic_conn_pool) { - sendNoHealthyUpstreamResponse(host_selection_details); + sendNoHealthyUpstreamResponse(host_selection_details, failure_status); return false; } Upstream::HostDescriptionConstSharedPtr host = generic_conn_pool->host(); @@ -952,14 +953,16 @@ std::unique_ptr Filter::createConnPool(Upstream::ThreadLocalClu callbacks_->streamInfo().protocol(), this, *message); } -void Filter::sendNoHealthyUpstreamResponse(absl::string_view optional_details) { +void Filter::sendNoHealthyUpstreamResponse(absl::string_view optional_details, + absl::optional failure_status) { + const Http::Code status_code = failure_status.value_or(Http::Code::ServiceUnavailable); callbacks_->streamInfo().setResponseFlag(StreamInfo::CoreResponseFlag::NoHealthyUpstream); - chargeUpstreamCode(Http::Code::ServiceUnavailable, {}, false); + chargeUpstreamCode(status_code, {}, false); absl::string_view details = optional_details.empty() ? StreamInfo::ResponseCodeDetails::get().NoHealthyUpstream : optional_details; - callbacks_->sendLocalReply(Http::Code::ServiceUnavailable, "no healthy upstream", modify_headers_, - absl::nullopt, details); + callbacks_->sendLocalReply(status_code, "no healthy upstream", modify_headers_, absl::nullopt, + details); } uint64_t Filter::calculateEffectiveBufferLimit() const { @@ -2256,7 +2259,7 @@ void Filter::doRetry(bool can_send_early_data, bool can_use_http3, TimeoutRetry // as host selection failure). continueDoRetry(can_send_early_data, can_use_http3, is_timeout_retry, std::move(host_selection_response.host), *cluster, - host_selection_response.details); + host_selection_response.details, host_selection_response.failure_status); } ENVOY_STREAM_LOG(debug, "Handling asynchronous host selection for retry\n", *callbacks_); @@ -2274,12 +2277,13 @@ void Filter::doRetry(bool can_send_early_data, bool can_use_http3, TimeoutRetry void Filter::continueDoRetry(bool can_send_early_data, bool can_use_http3, TimeoutRetry is_timeout_retry, Upstream::HostConstSharedPtr&& host, Upstream::ThreadLocalCluster& cluster, - absl::string_view host_selection_details) { + absl::string_view host_selection_details, + absl::optional failure_status) { callbacks_->streamInfo().downstreamTiming().setValue( "envoy.router.host_selection_end_ms", callbacks_->dispatcher().timeSource().monotonicTime()); std::unique_ptr generic_conn_pool = createConnPool(cluster, host); if (!generic_conn_pool) { - sendNoHealthyUpstreamResponse(host_selection_details); + sendNoHealthyUpstreamResponse(host_selection_details, failure_status); cleanup(); return; } diff --git a/source/common/router/router.h b/source/common/router/router.h index c6c3dd77e9041..86b09203fa21d 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -333,7 +333,8 @@ class Filter : Logger::Loggable, bool continueDecodeHeaders(Upstream::ThreadLocalCluster* cluster, Http::RequestHeaderMap& headers, bool end_stream, Upstream::HostConstSharedPtr&& host, - absl::string_view host_selection_details = {}); + absl::string_view host_selection_details = {}, + absl::optional failure_status = absl::nullopt); Http::FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override; Http::FilterTrailersStatus decodeTrailers(Http::RequestTrailerMap& trailers) override; @@ -603,7 +604,8 @@ class Filter : Logger::Loggable, // if a "good" response comes back and we return downstream, so there is no point in waiting // for the remaining upstream requests to return. void resetOtherUpstreams(UpstreamRequest& upstream_request); - void sendNoHealthyUpstreamResponse(absl::string_view details); + void sendNoHealthyUpstreamResponse(absl::string_view details, + absl::optional failure_status = absl::nullopt); bool setupRedirect(const Http::ResponseHeaderMap& headers); bool convertRequestHeadersForInternalRedirect(Http::RequestHeaderMap& downstream_headers, const Http::ResponseHeaderMap& upstream_headers, @@ -614,7 +616,8 @@ class Filter : Logger::Loggable, void doRetry(bool can_send_early_data, bool can_use_http3, TimeoutRetry is_timeout_retry); void continueDoRetry(bool can_send_early_data, bool can_use_http3, TimeoutRetry is_timeout_retry, Upstream::HostConstSharedPtr&& host, Upstream::ThreadLocalCluster& cluster, - absl::string_view host_selection_details); + absl::string_view host_selection_details, + absl::optional failure_status = absl::nullopt); void updateStatsOnNoRetry(RetryStatus retry_status); void updateStatsOnDoRetry(RetryState::DoRetryType do_retry_type); diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index bbabc3587d9cb..23676d4eb8630 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -2066,13 +2066,13 @@ void ClusterManagerImpl::ThreadLocalClusterManagerImpl::httpConnPoolIsIdle( HostSelectionResponse ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::chooseHost( LoadBalancerContext* context) { auto cross_priority_host_map = priority_set_.crossPriorityHostMap(); - auto host_and_strict_mode = HostUtility::selectOverrideHost(cross_priority_host_map.get(), - override_host_statuses_, context); - if (host_and_strict_mode.first != nullptr) { - return {std::move(host_and_strict_mode.first)}; + auto override_result = HostUtility::selectOverrideHost(cross_priority_host_map.get(), + override_host_statuses_, context); + if (override_result.host != nullptr) { + return {std::move(override_result.host)}; } - if (!host_and_strict_mode.second) { + if (!override_result.strict) { Upstream::HostSelectionResponse host_selection = lb_->chooseHost(context); if (host_selection.host || host_selection.cancelable) { return host_selection; @@ -2084,16 +2084,26 @@ HostSelectionResponse ClusterManagerImpl::ThreadLocalClusterManagerImpl::Cluster cluster_info_->trafficStats()->upstream_cx_none_healthy_.inc(); ENVOY_LOG(debug, "no healthy host"); - return {nullptr}; + HostSelectionResponse response{nullptr}; + // Only apply the custom failure status when the destination is missing from + // available endpoints, not when it exists but is unhealthy. + if (override_result.status == HostUtility::OverrideHostSelectionStatus::NotFound) { + if (context != nullptr) { + if (auto override_host = context->overrideHostToSelect(); override_host.has_value()) { + response.failure_status = override_host->status_on_strict_destination_not_found; + } + } + } + return response; } HostConstSharedPtr ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::peekAnotherHost( LoadBalancerContext* context) { auto cross_priority_host_map = priority_set_.crossPriorityHostMap(); - auto host_and_strict_mode = HostUtility::selectOverrideHost(cross_priority_host_map.get(), - override_host_statuses_, context); - if (host_and_strict_mode.first != nullptr) { - return std::move(host_and_strict_mode.first); + auto override_result = HostUtility::selectOverrideHost(cross_priority_host_map.get(), + override_host_statuses_, context); + if (override_result.host != nullptr) { + return std::move(override_result.host); } // TODO(wbpcode): should we do strict mode check of override host here? return lb_->peekAnotherHost(context); diff --git a/source/common/upstream/host_utility.cc b/source/common/upstream/host_utility.cc index d2d0335689cd8..2d5005bd29d41 100644 --- a/source/common/upstream/host_utility.cc +++ b/source/common/upstream/host_utility.cc @@ -137,39 +137,39 @@ HostUtility::HostStatusSet HostUtility::createOverrideHostStatus( return override_host_status; } -std::pair HostUtility::selectOverrideHost(const HostMap* host_map, - HostStatusSet status, - LoadBalancerContext* context) { +HostUtility::OverrideHostSelectionResult +HostUtility::selectOverrideHost(const HostMap* host_map, HostStatusSet status, + LoadBalancerContext* context) { if (context == nullptr) { - return {nullptr, false}; + return {}; } OptRef override_host = context->overrideHostToSelect(); if (!override_host.has_value()) { - return {nullptr, false}; + return {}; } const bool strict_mode = override_host->strict; if (host_map == nullptr) { - return {nullptr, strict_mode}; + return {nullptr, strict_mode, OverrideHostSelectionStatus::NotFound}; } auto host_iter = host_map->find(override_host->host); // The override host cannot be found in the host map. if (host_iter == host_map->end()) { - return {nullptr, strict_mode}; + return {nullptr, strict_mode, OverrideHostSelectionStatus::NotFound}; } HostConstSharedPtr host = host_iter->second; ASSERT(host != nullptr); if (status[static_cast(host->healthStatus())]) { - return {host, strict_mode}; + return {host, strict_mode, OverrideHostSelectionStatus::Success}; } - return {nullptr, strict_mode}; + return {nullptr, strict_mode, OverrideHostSelectionStatus::Unhealthy}; } void HostUtility::forEachHostMetric( diff --git a/source/common/upstream/host_utility.h b/source/common/upstream/host_utility.h index 0d9324da02fcc..f2c38b103d3c5 100644 --- a/source/common/upstream/host_utility.h +++ b/source/common/upstream/host_utility.h @@ -26,13 +26,33 @@ class HostUtility { // A utility function to create override host status from lb config. static HostStatusSet createOverrideHostStatus(const CommonLbConfigProto& common_config); + // Status of override host selection. + enum class OverrideHostSelectionStatus { + // Host was successfully selected. + Success, + // Host was not found in the host map. + NotFound, + // Host was found but is not healthy. + Unhealthy, + }; + + // Result of attempting to select an override host from the host map. + struct OverrideHostSelectionResult { + // The selected host, or nullptr if selection failed. + HostConstSharedPtr host; + // Whether strict mode was requested for the override. + bool strict{false}; + // The status of the override host selection. + OverrideHostSelectionStatus status{OverrideHostSelectionStatus::Success}; + }; + /** * A utility function to select override host from host map according to load balancer context. * - * @return pair the first element is the selected host and the second - * element is a boolean indicating whether the host should be selected strictly or not. + * @return OverrideHostSelectionResult containing the selected host, whether strict mode was + * requested, and the reason for the selection outcome. */ - static std::pair + static OverrideHostSelectionResult selectOverrideHost(const HostMap* host_map, HostStatusSet status, LoadBalancerContext* context); // Iterate over all per-endpoint metrics, for clusters with `per_endpoint_stats` enabled. diff --git a/source/extensions/filters/http/stateful_session/stateful_session.cc b/source/extensions/filters/http/stateful_session/stateful_session.cc index bccf25ab044a1..f9d0d83a9652b 100644 --- a/source/extensions/filters/http/stateful_session/stateful_session.cc +++ b/source/extensions/filters/http/stateful_session/stateful_session.cc @@ -26,7 +26,11 @@ class EmptySessionStateFactory : public Envoy::Http::SessionStateFactory { StatefulSessionConfig::StatefulSessionConfig(const ProtoConfig& config, Server::Configuration::GenericFactoryContext& context, const std::string& stats_prefix, Stats::Scope& scope) - : strict_(config.strict()) { + : strict_(config.strict()), + status_on_strict_destination_not_found_( + config.strict() && config.status_on_strict_destination_not_found() != 0 + ? static_cast(config.status_on_strict_destination_not_found()) + : Http::Code::ServiceUnavailable) { // Only construct stats if stat_prefix is explicitly set. if (!config.stat_prefix().empty()) { const std::string final_prefix = @@ -83,7 +87,8 @@ Http::FilterHeadersStatus StatefulSession::decodeHeaders(Http::RequestHeaderMap& if (auto upstream_address = session_state_->upstreamAddress(); upstream_address.has_value()) { decoder_callbacks_->setUpstreamOverrideHost(Upstream::LoadBalancerContext::OverrideHost{ - std::string(upstream_address.value()), effective_config_->isStrict()}); + std::string(upstream_address.value()), effective_config_->isStrict(), + effective_config_->statusOnMissingStrictDestination()}); } return Http::FilterHeadersStatus::Continue; } diff --git a/source/extensions/filters/http/stateful_session/stateful_session.h b/source/extensions/filters/http/stateful_session/stateful_session.h index cdf578feb195c..9f3c6b38e7cc4 100644 --- a/source/extensions/filters/http/stateful_session/stateful_session.h +++ b/source/extensions/filters/http/stateful_session/stateful_session.h @@ -54,11 +54,16 @@ class StatefulSessionConfig { bool isStrict() const { return strict_; } + Http::Code statusOnMissingStrictDestination() const { + return status_on_strict_destination_not_found_; + } + OptRef stats() { return makeOptRefFromPtr(stats_.get()); } private: Http::SessionStateFactorySharedPtr factory_; bool strict_{false}; + Http::Code status_on_strict_destination_not_found_{Http::Code::ServiceUnavailable}; std::shared_ptr stats_; }; using StatefulSessionConfigSharedPtr = std::shared_ptr; diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 467c6591838dc..4c95c72b21c97 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -1700,6 +1700,23 @@ TEST_F(ClusterManagerImplTest, SelectOverrideHostTestWithNonExistingHostStrict) EXPECT_FALSE(opt_cp.has_value()); } +TEST_F(ClusterManagerImplTest, StrictOverrideHostNotFoundReturnsCustomFailureStatus) { + createWithBasicStaticCluster(); + NiceMock context; + + // Non-existing host with strict mode and custom failure status (421). + Upstream::LoadBalancerContext::OverrideHost override_host{"127.0.0.2:12345", true, + Http::Code::MisdirectedRequest}; + EXPECT_CALL(context, overrideHostToSelect()) + .WillRepeatedly( + Return(OptRef(override_host))); + + auto result = cluster_manager_->getThreadLocalCluster("cluster_1")->chooseHost(&context); + EXPECT_EQ(nullptr, result.host); + ASSERT_TRUE(result.failure_status.has_value()); + EXPECT_EQ(Http::Code::MisdirectedRequest, result.failure_status.value()); +} + TEST_F(ClusterManagerImplTest, UpstreamSocketOptionsPassedToConnPool) { createWithBasicStaticCluster(); NiceMock context; diff --git a/test/common/upstream/host_utility_test.cc b/test/common/upstream/host_utility_test.cc index 107eb5a50f943..60dcd29b46572 100644 --- a/test/common/upstream/host_utility_test.cc +++ b/test/common/upstream/host_utility_test.cc @@ -148,10 +148,13 @@ TEST(HostUtilityTest, CreateOverrideHostStatus) { } TEST(HostUtilityTest, SelectOverrideHostTest) { - constexpr auto expect_helper = [](std::pair expected_result, - HostConstSharedPtr host, bool strict_mode) { - EXPECT_EQ(expected_result.first, host); - EXPECT_EQ(expected_result.second, strict_mode); + using Status = HostUtility::OverrideHostSelectionStatus; + + constexpr auto expect_result = [](HostUtility::OverrideHostSelectionResult result, + HostConstSharedPtr host, bool strict_mode, Status status) { + EXPECT_EQ(result.host, host); + EXPECT_EQ(result.strict, strict_mode); + EXPECT_EQ(result.status, status); }; NiceMock context; @@ -163,8 +166,8 @@ TEST(HostUtilityTest, SelectOverrideHostTest) { { // No valid load balancer context. auto host_map = std::make_shared(); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), AllStatuses, nullptr), nullptr, - false); + expect_result(HostUtility::selectOverrideHost(host_map.get(), AllStatuses, nullptr), nullptr, + false, Status::Success); } { @@ -172,8 +175,8 @@ TEST(HostUtilityTest, SelectOverrideHostTest) { EXPECT_CALL(context, overrideHostToSelect()) .WillOnce(Return(OptRef())); auto host_map = std::make_shared(); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), AllStatuses, &context), nullptr, - false); + expect_result(HostUtility::selectOverrideHost(host_map.get(), AllStatuses, &context), nullptr, + false, Status::Success); } // Test overriding host in strict and non-strict mode. @@ -183,8 +186,8 @@ TEST(HostUtilityTest, SelectOverrideHostTest) { LoadBalancerContext::OverrideHost override_host{"1.2.3.4", strict_mode}; EXPECT_CALL(context, overrideHostToSelect()) .WillOnce(Return(OptRef(override_host))); - expect_helper(HostUtility::selectOverrideHost(nullptr, AllStatuses, &context), nullptr, - strict_mode); + expect_result(HostUtility::selectOverrideHost(nullptr, AllStatuses, &context), nullptr, + strict_mode, Status::NotFound); } { @@ -193,8 +196,8 @@ TEST(HostUtilityTest, SelectOverrideHostTest) { EXPECT_CALL(context, overrideHostToSelect()) .WillOnce(Return(OptRef(override_host))); auto host_map = std::make_shared(); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), AllStatuses, &context), nullptr, - strict_mode); + expect_result(HostUtility::selectOverrideHost(host_map.get(), AllStatuses, &context), nullptr, + strict_mode, Status::NotFound); } { auto mock_host = std::make_shared>(); @@ -208,21 +211,21 @@ TEST(HostUtilityTest, SelectOverrideHostTest) { auto host_map = std::make_shared(); host_map->insert({"1.2.3.4", mock_host}); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), UnhealthyStatus, &context), - mock_host, strict_mode); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), AllStatuses, &context), - mock_host, strict_mode); - - expect_helper(HostUtility::selectOverrideHost(host_map.get(), HealthyStatus, &context), - nullptr, strict_mode); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), DegradedStatus, &context), - nullptr, strict_mode); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), TimeoutStatus, &context), - nullptr, strict_mode); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), DrainingStatus, &context), - nullptr, strict_mode); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), UnknownStatus, &context), - nullptr, strict_mode); + expect_result(HostUtility::selectOverrideHost(host_map.get(), UnhealthyStatus, &context), + mock_host, strict_mode, Status::Success); + expect_result(HostUtility::selectOverrideHost(host_map.get(), AllStatuses, &context), + mock_host, strict_mode, Status::Success); + + expect_result(HostUtility::selectOverrideHost(host_map.get(), HealthyStatus, &context), + nullptr, strict_mode, Status::Unhealthy); + expect_result(HostUtility::selectOverrideHost(host_map.get(), DegradedStatus, &context), + nullptr, strict_mode, Status::Unhealthy); + expect_result(HostUtility::selectOverrideHost(host_map.get(), TimeoutStatus, &context), + nullptr, strict_mode, Status::Unhealthy); + expect_result(HostUtility::selectOverrideHost(host_map.get(), DrainingStatus, &context), + nullptr, strict_mode, Status::Unhealthy); + expect_result(HostUtility::selectOverrideHost(host_map.get(), UnknownStatus, &context), + nullptr, strict_mode, Status::Unhealthy); } { auto mock_host = std::make_shared>(); @@ -236,21 +239,21 @@ TEST(HostUtilityTest, SelectOverrideHostTest) { auto host_map = std::make_shared(); host_map->insert({"1.2.3.4", mock_host}); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), DegradedStatus, &context), - mock_host, strict_mode); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), AllStatuses, &context), - mock_host, strict_mode); - - expect_helper(HostUtility::selectOverrideHost(host_map.get(), HealthyStatus, &context), - nullptr, strict_mode); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), UnhealthyStatus, &context), - nullptr, strict_mode); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), TimeoutStatus, &context), - nullptr, strict_mode); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), DrainingStatus, &context), - nullptr, strict_mode); - expect_helper(HostUtility::selectOverrideHost(host_map.get(), UnknownStatus, &context), - nullptr, strict_mode); + expect_result(HostUtility::selectOverrideHost(host_map.get(), DegradedStatus, &context), + mock_host, strict_mode, Status::Success); + expect_result(HostUtility::selectOverrideHost(host_map.get(), AllStatuses, &context), + mock_host, strict_mode, Status::Success); + + expect_result(HostUtility::selectOverrideHost(host_map.get(), HealthyStatus, &context), + nullptr, strict_mode, Status::Unhealthy); + expect_result(HostUtility::selectOverrideHost(host_map.get(), UnhealthyStatus, &context), + nullptr, strict_mode, Status::Unhealthy); + expect_result(HostUtility::selectOverrideHost(host_map.get(), TimeoutStatus, &context), + nullptr, strict_mode, Status::Unhealthy); + expect_result(HostUtility::selectOverrideHost(host_map.get(), DrainingStatus, &context), + nullptr, strict_mode, Status::Unhealthy); + expect_result(HostUtility::selectOverrideHost(host_map.get(), UnknownStatus, &context), + nullptr, strict_mode, Status::Unhealthy); } } } diff --git a/test/extensions/filters/http/stateful_session/stateful_session_test.cc b/test/extensions/filters/http/stateful_session/stateful_session_test.cc index 981087b9a2baa..91900af5dab83 100644 --- a/test/extensions/filters/http/stateful_session/stateful_session_test.cc +++ b/test/extensions/filters/http/stateful_session/stateful_session_test.cc @@ -1,4 +1,3 @@ -#include #include "source/extensions/filters/http/stateful_session/stateful_session.h" #include "source/server/generic_factory_context.h" @@ -409,6 +408,96 @@ stat_prefix: "test" EXPECT_EQ(0, context_.scope().counterFromString("stateful_session.test.no_session").value()); } +TEST_F(StatefulSessionTest, OverrideHostWithCustomStatus) { + const std::string strict_config = R"EOF( +session_state: + name: envoy.http.stateful_session.mock + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct +strict: true +stat_prefix: "test" +status_on_strict_destination_not_found: 421 +)EOF"; + + initialize(strict_config); + Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {":method", "GET"}, {":authority", "test.com"}}; + + auto raw_session_state = new testing::NiceMock(); + EXPECT_CALL(*factory_, create(_)) + .WillOnce(Return(testing::ByMove(std::unique_ptr(raw_session_state)))); + EXPECT_CALL(*raw_session_state, upstreamAddress()) + .WillOnce(Return(absl::make_optional("127.0.0.1:8080"))); + + Upstream::LoadBalancerContext::OverrideHost captured_host; + EXPECT_CALL(decoder_callbacks_, setUpstreamOverrideHost(_)) + .WillOnce(testing::SaveArg<0>(&captured_host)); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + // Verify the override host is set with the custom status code. + EXPECT_EQ("127.0.0.1:8080", captured_host.host); + EXPECT_TRUE(captured_host.strict); + EXPECT_EQ(Http::Code::MisdirectedRequest, captured_host.status_on_strict_destination_not_found); +} + +TEST_F(StatefulSessionTest, OverrideHostWithDefaultStatus) { + const std::string strict_config = R"EOF( +session_state: + name: envoy.http.stateful_session.mock + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct +strict: true +stat_prefix: "test" +)EOF"; + + initialize(strict_config); + Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {":method", "GET"}, {":authority", "test.com"}}; + + auto raw_session_state = new testing::NiceMock(); + EXPECT_CALL(*factory_, create(_)) + .WillOnce(Return(testing::ByMove(std::unique_ptr(raw_session_state)))); + EXPECT_CALL(*raw_session_state, upstreamAddress()) + .WillOnce(Return(absl::make_optional("127.0.0.1:8080"))); + + Upstream::LoadBalancerContext::OverrideHost captured_host; + EXPECT_CALL(decoder_callbacks_, setUpstreamOverrideHost(_)) + .WillOnce(testing::SaveArg<0>(&captured_host)); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + // Verify the override host is set with the default status code (503). + EXPECT_EQ("127.0.0.1:8080", captured_host.host); + EXPECT_TRUE(captured_host.strict); + EXPECT_EQ(Http::Code::ServiceUnavailable, captured_host.status_on_strict_destination_not_found); +} + +TEST_F(StatefulSessionTest, StatusOnStrictDestinationNotFoundIgnoredWithoutStrict) { + const std::string config_yaml = R"EOF( +session_state: + name: envoy.http.stateful_session.mock + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct +strict: false +status_on_strict_destination_not_found: 421 +)EOF"; + + Http::MockSessionStateFactoryConfig config_factory; + Registry::InjectFactory registration(config_factory); + auto mock_factory = std::make_shared>(); + EXPECT_CALL(config_factory, createSessionStateFactory(_, _)).WillOnce(Return(mock_factory)); + + ProtoConfig proto_config; + TestUtility::loadFromYaml(config_yaml, proto_config); + Envoy::Server::GenericFactoryContextImpl generic_context(context_); + + // When strict is false, status_on_strict_destination_not_found is ignored and defaults to 503. + StatefulSessionConfig config(proto_config, generic_context, "", context_.scope()); + EXPECT_FALSE(config.isStrict()); + EXPECT_EQ(Http::Code::ServiceUnavailable, config.statusOnMissingStrictDestination()); +} + } // namespace } // namespace StatefulSession } // namespace HttpFilters From 3fd93eab0cae37b676195c8212a645fa871b2e69 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Tue, 14 Apr 2026 11:15:06 -0400 Subject: [PATCH 010/750] Add McpRouterConfig interface to allow route or cluster overrides (#44430) Add interface for MCP router config to allow per route or cluster overrides. This is needed to integrate with the MCP Cluster. Risk Level: low Testing: unit tests Docs Changes: no Release Notes: no Platform Specific Features: no Signed-off-by: Yan Avlasov --- .../filters/http/mcp_router/config.cc | 2 +- .../filters/http/mcp_router/filter_config.cc | 36 +++++++++++-------- .../filters/http/mcp_router/filter_config.h | 26 ++++++++++++-- .../http/mcp_router/mcp_router_test.cc | 22 ++++++------ 4 files changed, 56 insertions(+), 30 deletions(-) diff --git a/source/extensions/filters/http/mcp_router/config.cc b/source/extensions/filters/http/mcp_router/config.cc index 5cb10ea7eb922..b87f7ea81c84d 100644 --- a/source/extensions/filters/http/mcp_router/config.cc +++ b/source/extensions/filters/http/mcp_router/config.cc @@ -15,7 +15,7 @@ Http::FilterFactoryCb McpRouterFilterConfigFactory::createFilterFactoryFromProto const std::string& stats_prefix, Server::Configuration::FactoryContext& context) { auto config = - std::make_shared(proto_config, stats_prefix, context.scope(), context); + std::make_shared(proto_config, stats_prefix, context.scope(), context); return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { callbacks.addStreamDecoderFilter(std::make_shared(config)); diff --git a/source/extensions/filters/http/mcp_router/filter_config.cc b/source/extensions/filters/http/mcp_router/filter_config.cc index f5524ed129a80..d3bc713d9ef24 100644 --- a/source/extensions/filters/http/mcp_router/filter_config.cc +++ b/source/extensions/filters/http/mcp_router/filter_config.cc @@ -1,5 +1,8 @@ #include "source/extensions/filters/http/mcp_router/filter_config.h" +#include +#include + #include "source/extensions/filters/common/mcp/filter_state.h" #include "absl/strings/str_cat.h" @@ -52,16 +55,11 @@ McpRouterStats generateStats(const std::string& prefix, Stats::Scope& scope) { const std::string final_prefix = absl::StrCat(prefix, "mcp_router."); return McpRouterStats{MCP_ROUTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))}; } -} // namespace -McpRouterConfig::McpRouterConfig( - const envoy::extensions::filters::http::mcp_router::v3::McpRouter& proto_config, - const std::string& stats_prefix, Stats::Scope& scope, - Server::Configuration::FactoryContext& context) - : factory_context_(context), session_identity_(parseSessionIdentity(proto_config)), - metadata_namespace_(Filters::Common::Mcp::metadataNamespace()), - stats_(generateStats(stats_prefix, scope)) { - for (const auto& server : proto_config.servers()) { +std::vector +parseBackends(const envoy::extensions::filters::http::mcp_router::v3::McpRouter& config) { + std::vector result; + for (const auto& server : config.servers()) { McpBackendConfig backend; const auto& mcp_cluster = server.mcp_cluster(); backend.name = server.name().empty() ? mcp_cluster.cluster() : server.name(); @@ -70,15 +68,23 @@ McpRouterConfig::McpRouterConfig( backend.timeout = std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(mcp_cluster, timeout, 5000)); backend.host_rewrite_literal = mcp_cluster.host_rewrite_literal(); - backends_.push_back(std::move(backend)); - } - - if (backends_.size() == 1) { - default_backend_name_ = backends_[0].name; + result.push_back(std::move(backend)); } + return result; } +} // namespace + +McpRouterConfigImpl::McpRouterConfigImpl( + const envoy::extensions::filters::http::mcp_router::v3::McpRouter& proto_config, + const std::string& stats_prefix, Stats::Scope& scope, + Server::Configuration::FactoryContext& context) + : backends_(parseBackends(proto_config)), + default_backend_name_(backends_.size() == 1 ? backends_[0].name : ""), + factory_context_(context), session_identity_(parseSessionIdentity(proto_config)), + metadata_namespace_(Filters::Common::Mcp::metadataNamespace()), + stats_(generateStats(stats_prefix, scope)) {} -const McpBackendConfig* McpRouterConfig::findBackend(const std::string& name) const { +const McpBackendConfig* McpRouterConfigImpl::findBackend(const std::string& name) const { for (const auto& backend : backends_) { if (backend.name == name) { return &backend; diff --git a/source/extensions/filters/http/mcp_router/filter_config.h b/source/extensions/filters/http/mcp_router/filter_config.h index e5aeaa2189773..3af125777fe4f 100644 --- a/source/extensions/filters/http/mcp_router/filter_config.h +++ b/source/extensions/filters/http/mcp_router/filter_config.h @@ -82,9 +82,29 @@ struct McpRouterStats { */ class McpRouterConfig { public: - McpRouterConfig(const envoy::extensions::filters::http::mcp_router::v3::McpRouter& proto_config, - const std::string& stats_prefix, Stats::Scope& scope, - Server::Configuration::FactoryContext& context); + virtual ~McpRouterConfig() = default; + + virtual const std::vector& backends() const = 0; + virtual bool isMultiplexing() const = 0; + virtual const std::string& defaultBackendName() const = 0; + virtual Server::Configuration::FactoryContext& factoryContext() const = 0; + virtual const McpBackendConfig* findBackend(const std::string& name) const = 0; + + virtual bool hasSessionIdentity() const = 0; + virtual const SubjectSource& subjectSource() const = 0; + virtual ValidationMode validationMode() const = 0; + virtual bool shouldEnforceValidation() const = 0; + virtual const std::string& metadataNamespace() const = 0; + + virtual McpRouterStats& stats() = 0; +}; + +class McpRouterConfigImpl : public McpRouterConfig { +public: + McpRouterConfigImpl( + const envoy::extensions::filters::http::mcp_router::v3::McpRouter& proto_config, + const std::string& stats_prefix, Stats::Scope& scope, + Server::Configuration::FactoryContext& context); const std::vector& backends() const { return backends_; } bool isMultiplexing() const { return backends_.size() > 1; } diff --git a/test/extensions/filters/http/mcp_router/mcp_router_test.cc b/test/extensions/filters/http/mcp_router/mcp_router_test.cc index 972886271c583..78a6dac2e8529 100644 --- a/test/extensions/filters/http/mcp_router/mcp_router_test.cc +++ b/test/extensions/filters/http/mcp_router/mcp_router_test.cc @@ -63,7 +63,7 @@ TEST_F(McpRouterConfigTest, MultipleBackendsEnablesMultiplexing) { server2->mutable_mcp_cluster()->set_cluster("calc_cluster"); server2->mutable_mcp_cluster()->set_path("/mcp/calc"); - McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_); + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); EXPECT_EQ(config.backends().size(), 2); EXPECT_TRUE(config.isMultiplexing()); @@ -90,7 +90,7 @@ TEST_F(McpRouterConfigTest, SingleBackendSetsDefaultName) { server->set_name("tools"); server->mutable_mcp_cluster()->set_cluster("tools_cluster"); - McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_); + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); EXPECT_EQ(config.backends().size(), 1); EXPECT_FALSE(config.isMultiplexing()); @@ -105,7 +105,7 @@ TEST_F(McpRouterConfigTest, DefaultPathWhenNotSpecified) { server->set_name("test"); server->mutable_mcp_cluster()->set_cluster("test_cluster"); - McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_); + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); const McpBackendConfig* backend = config.findBackend("test"); ASSERT_NE(backend, nullptr); @@ -120,7 +120,7 @@ TEST_F(McpRouterConfigTest, DefaultMetadataNamespace) { server->set_name("test"); server->mutable_mcp_cluster()->set_cluster("test_cluster"); - McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_); + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); EXPECT_EQ(config.metadataNamespace(), "envoy.filters.http.mcp"); } @@ -299,7 +299,7 @@ TEST_F(McpRouterConfigTest, SessionIdentityDisabledByDefault) { server->set_name("test"); server->mutable_mcp_cluster()->set_cluster("test_cluster"); - McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_); + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); EXPECT_FALSE(config.hasSessionIdentity()); EXPECT_FALSE(config.shouldEnforceValidation()); } @@ -314,7 +314,7 @@ TEST_F(McpRouterConfigTest, SessionIdentityWithHeaderSource) { auto* identity = proto_config.mutable_session_identity(); identity->mutable_identity()->mutable_header()->set_name("x-user-id"); - McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_); + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); EXPECT_TRUE(config.hasSessionIdentity()); EXPECT_TRUE(absl::holds_alternative(config.subjectSource())); EXPECT_FALSE(config.shouldEnforceValidation()); // DISABLED by default @@ -333,7 +333,7 @@ TEST_F(McpRouterConfigTest, SessionIdentityWithMetadataSource) { metadata_key->add_path()->set_key("payload"); metadata_key->add_path()->set_key("sub"); - McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_); + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); EXPECT_TRUE(config.hasSessionIdentity()); EXPECT_TRUE(absl::holds_alternative(config.subjectSource())); } @@ -351,7 +351,7 @@ TEST_F(McpRouterConfigTest, MetadataKeyPathParsed) { metadata_key->add_path()->set_key("payload"); metadata_key->add_path()->set_key("sub"); - McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_); + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); const auto& source = absl::get(config.subjectSource()); EXPECT_EQ(source.filter, "jwt"); ASSERT_EQ(source.path_keys.size(), 2); @@ -371,7 +371,7 @@ TEST_F(McpRouterConfigTest, ValidationModeEnforce) { identity->mutable_validation()->set_mode( envoy::extensions::filters::http::mcp_router::v3::ValidationPolicy::ENFORCE); - McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_); + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); EXPECT_TRUE(config.hasSessionIdentity()); EXPECT_TRUE(config.shouldEnforceValidation()); EXPECT_EQ(config.validationMode(), ValidationMode::Enforce); @@ -388,8 +388,8 @@ class McpRouterFilterTest : public testing::Test { McpRouterConfigSharedPtr createConfig(const envoy::extensions::filters::http::mcp_router::v3::McpRouter& proto_config) { - return std::make_shared(proto_config, std::string("test."), - *store_.rootScope(), factory_context_); + return std::make_shared(proto_config, std::string("test."), + *store_.rootScope(), factory_context_); } void setDynamicMetadata(const std::string& filter_name, const std::string& key, From 7b7610a667c63e89c9722d7186c7ca232fa4227b Mon Sep 17 00:00:00 2001 From: Jonathan Wu Date: Tue, 14 Apr 2026 12:05:38 -0400 Subject: [PATCH 011/750] Add a new workflow for building the mobile/library/python whl and uploading to pypi (#44193) Create a new workflow that runs on manual dispatch that builds and repairs the mobile/library/python whl and uploads to pypi Some things that still need work: The workflow currently only runs on manual dispatch. We could change it to run when changes are made to the //library/python:envoy_mobile_wheel target, but the upload to pypi will fail unless the version is bumped in the BUILD configuration ~~The workflow is currently still configured for test PyPi. We need to set up an account and credentials for an official PyPi repo and add them to the repository as a secret~~ --------- Signed-off-by: Jonathan Wu Signed-off-by: Jonathan Wu --- .github/workflows/_load_env.yml | 2 +- .github/workflows/mobile-release.yml | 112 +++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 9 deletions(-) diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index 426fda17765b9..71c28027ac140 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -110,5 +110,5 @@ jobs: needs: request if: ${{ inputs.cache-docker }} with: - request: ${{ toJSON(needs.request.outputs) }} + caches: ${{ needs.request.outputs.caches }} image-tag: ${{ fromJSON(needs.request.outputs.build-image).default }} diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index 46e7248d36797..709643a8fa069 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -5,6 +5,16 @@ permissions: on: workflow_dispatch: + inputs: + job_to_run: + description: 'Select the job to run' + required: true + default: 'all' + type: choice + options: + - all + - android + - python schedule: # Mondays at 1pm UTC (8am EST) - cron: "0 13 * * 1" @@ -18,16 +28,18 @@ jobs: contents: read uses: ./.github/workflows/_load_env.yml - release: + android-release: permissions: + actions: read contents: read packages: read if: >- - ${{ - github.repository == 'envoyproxy/envoy' - && (github.event.schedule - || !contains(github.actor, '[bot]')) - }} + github.repository == 'envoyproxy/envoy' + && (github.event.schedule + || !contains(github.actor, '[bot]')) + && (github.event.inputs.job_to_run == 'all' + || github.event.inputs.job_to_run == 'android' + || github.event_name == 'schedule') needs: env uses: ./.github/workflows/_mobile_container_ci.yml with: @@ -76,8 +88,14 @@ jobs: //:android_dist output: envoy - deploy: - needs: release + android-deploy: + needs: android-release + if: >- + always() + && (github.event.inputs.job_to_run == 'all' + || github.event.inputs.job_to_run == 'android' + || github.event_name == 'schedule') + && (needs.android-release.result == 'success') permissions: contents: read packages: read @@ -141,3 +159,81 @@ jobs: ${{ matrix.output }}-pom.xml.asc \ ${{ matrix.output }}-sources.jar.asc \ ${{ matrix.output }}-javadoc.jar.asc + + python-release: + permissions: + actions: read + contents: read + packages: read + if: >- + github.repository == 'envoyproxy/envoy' + && (github.event_name == 'workflow_dispatch') + && (github.event.inputs.job_to_run == 'all' + || github.event.inputs.job_to_run == 'python') + needs: env + name: Build and Publish Python Wheel + uses: ./.github/workflows/_mobile_container_ci.yml + with: + args: ${{ matrix.args }} + container: ${{ fromJSON(needs.env.outputs.build-image).default }} + container-output: | + "bazel-bin/library/python/": build/ + request: ${{ needs.env.outputs.request }} + steps-post: | + - name: Repair wheel + run: | + mkdir -p /tmp/dist + WHEEL_PATH=$(find /tmp/container-output/build -name "*.whl" | head -n 1) + + if [[ -z "$WHEEL_PATH" ]]; then + echo "Error: No wheel file found in /tmp/container-output/build" + ls -R /tmp/container-output/ + exit 1 + fi + python3 -m venv envoy_wheel + source envoy_wheel/bin/activate + pip install patchelf auditwheel + + auditwheel repair "$WHEEL_PATH" --wheel-dir /tmp/dist + shell: bash + target: ${{ matrix.target }} + upload-name: python-wheel + upload-path: /tmp/dist/*.whl + strategy: + fail-fast: false + matrix: + include: + - target: envoy_mobile_wheel + args: >- + build + --config=ci + --config=mobile-rbe + --config=mobile-release + -c opt --strip=always + //library/python:envoy_mobile_wheel + --//library/python:python_platform="manylinux2014_x86_64" + + python-publish: + needs: python-release + if: >- + always() + && (github.event.inputs.job_to_run == 'all' + || github.event.inputs.job_to_run == 'python') + && (needs.python-release.result == 'success') + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + environment: + name: pypi + url: https://pypi.org/p/envoy-mobile + steps: + - uses: actions/download-artifact@v4 + with: + name: python-wheel + path: dist/ + - name: Publish to PyPi + uses: pypa/gh-action-pypi-publish@release/v1 From de123b5095d970de78aac16de12207b7fb291253 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:23:34 +0100 Subject: [PATCH 012/750] deps: Bump `aws_lc` -> 1.72.0 (#44445) Fix #44435 Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index af45c850fc35a..fde7f0fb2dedb 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -13,7 +13,7 @@ aws_lc: project_name: "AWS libcrypto (AWS-LC)" project_desc: "OpenSSL compatible general-purpose crypto library" project_url: "https://github.com/aws/aws-lc" - release_date: "2026-03-19" + release_date: "2026-04-13" use_category: - controlplane - dataplane_core diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 20e60c2e56ce2..c9898ea62f790 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -67,8 +67,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/google/boringssl/archive/{version}.tar.gz"], ), aws_lc = dict( - version = "1.71.0", - sha256 = "31b1eed775294825f084c0d4e09df53e1cf036fb98a202a8c2c342543828a985", + version = "1.72.0", + sha256 = "f214c0e06e043c4f18b836059ccb5ecbed781173e8eed106839ee2dd4f4cc157", strip_prefix = "aws-lc-{version}", urls = ["https://github.com/aws/aws-lc/archive/v{version}.tar.gz"], ), From f1708ab7faa6944ca75033d412911b8756800dd7 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:10:01 -0400 Subject: [PATCH 013/750] deps: Bump `boringssl` -> 0.20260413.0 (#44446) Created by Envoy dependency bot for @phlax Fix #44436 Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --------- Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Signed-off-by: Jonh Wendell Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: Jonh Wendell --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 +- compat/openssl/BUILD | 2 + .../patch/crypto/x509/x509_test.cc.patch | 89 +++++++++---------- compat/openssl/patch/include/openssl/evp.h.sh | 1 + .../openssl/patch/include/openssl/x509.h.sh | 1 + 6 files changed, 50 insertions(+), 49 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index fde7f0fb2dedb..83de49863c570 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -53,7 +53,7 @@ boringssl: project_name: "BoringSSL" project_desc: "Minimal OpenSSL fork" project_url: "https://github.com/google/boringssl" - release_date: "2026-03-30" + release_date: "2026-04-14" use_category: - controlplane - dataplane_core diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index c9898ea62f790..69a632a2ef69d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -61,8 +61,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( boringssl = dict( # To update BoringSSL, which tracks BCR tags, open https://registry.bazel.build/modules/boringssl # and select an appropriate tag for the new version. - version = "0.20260327.0", - sha256 = "6af7037a8891d0e3d097dd61de6d195f6c2b532ed69d138a16da47c06a6cd022", + version = "0.20260413.0", + sha256 = "3560f7dd3f08e16b9f84d877a5be21ec62071564783009571af5fcc6fad734d2", strip_prefix = "boringssl-{version}", urls = ["https://github.com/google/boringssl/archive/{version}.tar.gz"], ), diff --git a/compat/openssl/BUILD b/compat/openssl/BUILD index dcdd36800113d..6e68f8833a197 100644 --- a/compat/openssl/BUILD +++ b/compat/openssl/BUILD @@ -502,6 +502,7 @@ mapping_func_filegroup( "EVP_PKEY_assign_RSA", "EVP_PKEY_bits", "EVP_PKEY_cmp", + "EVP_PKEY_eq", "EVP_PKEY_CTX_set_rsa_mgf1_md", "EVP_PKEY_CTX_set_rsa_padding", "EVP_PKEY_free", @@ -812,6 +813,7 @@ mapping_func_filegroup( "X509_getm_notAfter", "X509_getm_notBefore", "X509_INFO_free", + "X509_NAME_add_entry_by_NID", "X509_NAME_add_entry_by_txt", "X509_NAME_cmp", "X509_NAME_digest", diff --git a/compat/openssl/patch/crypto/x509/x509_test.cc.patch b/compat/openssl/patch/crypto/x509/x509_test.cc.patch index 209be7b8521f3..4c03574a98c37 100644 --- a/compat/openssl/patch/crypto/x509/x509_test.cc.patch +++ b/compat/openssl/patch/crypto/x509/x509_test.cc.patch @@ -3,64 +3,61 @@ @@ -1535,6 +1535,9 @@ Verify(root_cross_signed.get(), {cross_signing_root.get()}, {}, /*crls=*/{}, /*flags=*/0, configure_callback)); - + +#ifdef BSSL_COMPAT // This next check fails for negative depths on OpenSSL + if (depth < 0) continue; +#endif // An explicitly trusted self-signed certificate is unaffected by depth // checks. EXPECT_EQ(X509_V_OK, -@@ -2290,9 +2293,20 @@ +@@ -2310,7 +2313,18 @@ {GEN_URI, "foo:///not-a-url", "not-a-url", -- X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, +- X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, +#ifdef BSSL_COMPAT + // OpenSSL considers "foo:///not-a-url" to be a valid URI -+ X509_V_ERR_PERMITTED_VIOLATION, X509_V_ERR_EXCLUDED_VIOLATION}, ++ X509_V_ERR_PERMITTED_VIOLATION, SpecialCase::kExcludedViolation}, +#else // BSSL_COMPAT -+ X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, ++ X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, +#endif // BSSL_COMPAT {GEN_URI, "foo://:not-a-url", "not-a-url", - X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, -- {GEN_URI, "foo://", "not-a-url", X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, -- X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, + X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, +- {GEN_URI, "foo://", "not-a-url", X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, +#ifdef BSSL_COMPAT + // OpenSSL considers "foo://" to be a valid URI -+ {GEN_URI, "foo://", "not-a-url", X509_V_ERR_PERMITTED_VIOLATION, -+ X509_V_ERR_EXCLUDED_VIOLATION}, ++ {GEN_URI, "foo://", "not-a-url", ++ X509_V_ERR_PERMITTED_VIOLATION, SpecialCase::kExcludedViolation}, +#else // BSSL_COMPAT -+ {GEN_URI, "foo://", "not-a-url", X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, -+ X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, ++ {GEN_URI, "foo://", "not-a-url", X509_V_ERR_UNSUPPORTED_NAME_SYNTAX}, +#endif // BSSL_COMPAT // Hosts are an exact match. - {GEN_URI, "foo://example.com", "example.com", X509_V_OK, - X509_V_ERR_EXCLUDED_VIOLATION}, -@@ -2363,6 +2374,12 @@ + {GEN_URI, "foo://example.com", "example.com", X509_V_OK}, +@@ -2384,6 +2398,12 @@ +#ifdef BSSL_COMPAT // FIXME: See StackTest.test4 -+ auto rule = -+ exclude ? &nc->excludedSubtrees : &nc->permittedSubtrees; -+ *rule = reinterpret_cast>(sk_GENERAL_SUBTREE_new_null()); ++ auto rule = ++ exclude ? &nc->excludedSubtrees : &nc->permittedSubtrees; ++ *rule = reinterpret_cast>(sk_GENERAL_SUBTREE_new_null()); +#else - STACK_OF(GENERAL_SUBTREE) **rule = - exclude ? &nc->excludedSubtrees : &nc->permittedSubtrees; -- *rule = sk_GENERAL_SUBTREE_new_null(); -+ *rule = sk_GENERAL_SUBTREE_new_null(); + STACK_OF(GENERAL_SUBTREE) **rule = + exclude ? &nc->excludedSubtrees : &nc->permittedSubtrees; +- *rule = sk_GENERAL_SUBTREE_new_null(); ++ *rule = sk_GENERAL_SUBTREE_new_null(); +#endif - ASSERT_TRUE(*rule); - UniquePtr subtree(GENERAL_SUBTREE_new()); - ASSERT_TRUE(subtree); -@@ -2371,6 +2393,10 @@ - subtree->base = MakeGeneralName(t.type, t.constraint).release(); - ASSERT_TRUE(subtree->base); -- ASSERT_TRUE(PushToStack(*rule, std::move(subtree))); + ASSERT_TRUE(*rule); + UniquePtr subtree(GENERAL_SUBTREE_new()); + ASSERT_TRUE(subtree); +@@ -2392,6 +2412,10 @@ + subtree->base = MakeGeneralName(t.type, t.constraint).release(); + ASSERT_TRUE(subtree->base); +- ASSERT_TRUE(PushToStack(*rule, std::move(subtree))); +#ifdef BSSL_COMPAT // FIXME: -+ ASSERT_TRUE(PushToStack(reinterpret_cast(*rule), std::move(subtree))); ++ ASSERT_TRUE(PushToStack(reinterpret_cast(*rule), std::move(subtree))); +#else -+ ASSERT_TRUE(PushToStack(*rule, std::move(subtree))); ++ ASSERT_TRUE(PushToStack(*rule, std::move(subtree))); +#endif - - UniquePtr root = - MakeTestCert("Root", "Root", key.get(), /*is_ca=*/true); -@@ -3476,11 +3497,13 @@ + + UniquePtr root = + MakeTestCert("Root", "Root", key.get(), /*is_ca=*/true); +@@ -3476,11 +3500,13 @@ // |X509_check_purpose| with |X509_PURPOSE_ANY| and purpose -1 do not check // basicConstraints, but other purpose types do. (This is redundant with the // actual basicConstraints check, but |X509_check_purpose| is public API.) @@ -72,9 +69,9 @@ /*ca=*/1)); +#endif // BSSL_COMPAT } - + TEST(X509Test, NoBasicConstraintsNetscapeCA) { -@@ -4374,6 +4397,7 @@ +@@ -4374,6 +4400,7 @@ t6uPxHrmpUY= -----END CERTIFICATE----- )"; @@ -82,7 +79,7 @@ static const char kP256InvalidParam[] = R"( -----BEGIN CERTIFICATE----- MIIBMTCBz6ADAgECAgIE0jATBggqhkjOPQQDAgQHZ2FyYmFnZTAPMQ0wCwYDVQQD -@@ -4385,6 +4409,7 @@ +@@ -4385,6 +4412,7 @@ fLULTZnynuQUULQkRcF7S7T2WpIL -----END CERTIFICATE----- )"; @@ -90,7 +87,7 @@ static const char kRSANoParam[] = R"( -----BEGIN CERTIFICATE----- MIIBWzCBx6ADAgECAgIE0jALBgkqhkiG9w0BAQswDzENMAsGA1UEAxMEVGVzdDAg -@@ -4409,6 +4434,7 @@ +@@ -4409,6 +4437,7 @@ SwmQUz4bRpckRBj+sIyp1We+pg== -----END CERTIFICATE----- )"; @@ -98,7 +95,7 @@ static const char kRSAInvalidParam[] = R"( -----BEGIN CERTIFICATE----- MIIBbTCB0KADAgECAgIE0jAUBgkqhkiG9w0BAQsEB2dhcmJhZ2UwDzENMAsGA1UE -@@ -4421,7 +4447,7 @@ +@@ -4421,7 +4450,7 @@ 5OMNZ/ajVwOssw61GcAlScRqEHkZFBoGp7e+QpgB2tf9 -----END CERTIFICATE----- )"; @@ -107,10 +104,10 @@ TEST(X509Test, AlgorithmParameters) { // P-256 parameters should be omitted, but we accept NULL ones. UniquePtr key = PrivateKeyFromPEM(kP256Key); -@@ -4435,11 +4461,13 @@ +@@ -4435,11 +4464,13 @@ ASSERT_TRUE(cert); EXPECT_TRUE(X509_verify(cert.get(), key.get())); - + +#ifndef BSSL_COMPAT cert = CertFromPEM(kP256InvalidParam); ASSERT_TRUE(cert); @@ -118,13 +115,13 @@ EXPECT_TRUE( ErrorEquals(ERR_get_error(), ERR_LIB_X509, X509_R_INVALID_PARAMETER)); +#endif - + // RSA parameters should be NULL, but we accept omitted ones. key = PrivateKeyFromPEM(kRSAKey); -@@ -4453,11 +4481,13 @@ +@@ -4453,11 +4484,13 @@ ASSERT_TRUE(cert); EXPECT_TRUE(X509_verify(cert.get(), key.get())); - + +#ifndef BSSL_COMPAT cert = CertFromPEM(kRSAInvalidParam); ASSERT_TRUE(cert); @@ -133,5 +130,5 @@ ErrorEquals(ERR_get_error(), ERR_LIB_X509, X509_R_INVALID_PARAMETER)); +#endif } - + TEST(X509Test, GeneralName) { diff --git a/compat/openssl/patch/include/openssl/evp.h.sh b/compat/openssl/patch/include/openssl/evp.h.sh index 0d87a225c63bf..786d0ac19618f 100755 --- a/compat/openssl/patch/include/openssl/evp.h.sh +++ b/compat/openssl/patch/include/openssl/evp.h.sh @@ -7,6 +7,7 @@ uncomment.sh "$1" --comment -h \ --uncomment-func-decl EVP_PKEY_free \ --uncomment-func-decl EVP_PKEY_up_ref \ --uncomment-func-decl EVP_PKEY_cmp \ + --uncomment-func-decl EVP_PKEY_eq \ --uncomment-func-decl EVP_PKEY_id \ --uncomment-func-decl EVP_PKEY_assign_RSA \ --uncomment-func-decl EVP_PKEY_get0_RSA \ diff --git a/compat/openssl/patch/include/openssl/x509.h.sh b/compat/openssl/patch/include/openssl/x509.h.sh index 171c4f532a4a6..9d7f7f163b3ac 100755 --- a/compat/openssl/patch/include/openssl/x509.h.sh +++ b/compat/openssl/patch/include/openssl/x509.h.sh @@ -37,6 +37,7 @@ uncomment.sh "$1" --comment -h \ --uncomment-func-decl X509_NAME_entry_count \ --uncomment-func-decl X509_NAME_get_index_by_NID \ --uncomment-func-decl X509_NAME_get_entry \ + --uncomment-func-decl X509_NAME_add_entry_by_NID \ --uncomment-func-decl X509_NAME_add_entry_by_txt \ --uncomment-func-decl X509_NAME_ENTRY_set \ --uncomment-func-decl X509_EXTENSION_get_data \ From 80bf54aa0558dac3e1648fae1ae693d1fc0ffcfa Mon Sep 17 00:00:00 2001 From: Jonathan Wu Date: Tue, 14 Apr 2026 15:25:37 -0400 Subject: [PATCH 014/750] Fix pypi target project mobile-release.yml (#44449) Fixing a mismatch between the pypi target project and the project specified in mobile/pyproject.toml Signed-off-by: Jonathan Wu --- .github/workflows/mobile-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index 709643a8fa069..1cfa6f27a81d3 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -229,7 +229,7 @@ jobs: fail-fast: false environment: name: pypi - url: https://pypi.org/p/envoy-mobile + url: https://pypi.org/p/envoy-mobile-client steps: - uses: actions/download-artifact@v4 with: From 1af547ed15f0a10010dcb2dfe5c1bc3ab7e942a0 Mon Sep 17 00:00:00 2001 From: Adam Anderson <6754028+AdamEAnderson@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:17:36 -0700 Subject: [PATCH 015/750] dynamic modules: add abi.h to go mod vendor tree (#44395) Commit Message: add abi.h to go mod vendor tree Additional Description: go mod vendor is a common way in Golang to import modules/packages. It works by downloading and storing the locked version of a dependency in a vendor directory within the module. Vendoring skips downloading and storing all directories that do not have Go source files. This is a problem because the Go dynamic modules SDK uses a CGO preamble with an import of this header file, but this header is missing from the vendor download. This change adds a dummy Go file that will make Go treat this directory as a valid Go package and thus let it be imported so the header will exist properly. Risk Level: none Testing: n/a Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a --------- Signed-off-by: Adam Anderson <6754028+AdamEAnderson@users.noreply.github.com> --- source/extensions/dynamic_modules/abi/doc.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 source/extensions/dynamic_modules/abi/doc.go diff --git a/source/extensions/dynamic_modules/abi/doc.go b/source/extensions/dynamic_modules/abi/doc.go new file mode 100644 index 0000000000000..46a8eb3d00059 --- /dev/null +++ b/source/extensions/dynamic_modules/abi/doc.go @@ -0,0 +1,7 @@ +// Package abi contains the C ABI header for Envoy dynamic modules. +// +// This file exists so that `go mod vendor` includes this directory. +// Without a .go file, the Go toolchain skips this directory during +// vendoring, which causes the abi.h header (needed by the Go SDK's +// cgo include) to be missing from the vendor tree. +package abi From 6c67c709880060b0e3e2275e0cc15657b24bfa69 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Tue, 14 Apr 2026 14:47:47 -0700 Subject: [PATCH 016/750] Update QUICHE from bbe3dde94 to f4cb73412 (#44442) https://github.com/google/quiche/compare/bbe3dde94..f4cb73412 ``` $ git log bbe3dde94..f4cb73412 --date=short --no-merges --format="%ad %al %s" 2026-04-14 martinduke Limit Joining FETCH to largest_object at time of SUBSCRIBE. 2026-04-13 dmcardle Deprecate MASQUE PerRequestConfig::SetExpectedEncapsulatedResponseBody() 2026-04-10 dmcardle Add encapsulated response body callback to MASQUE client's PerRequestConfig 2026-04-10 ripere Add `num_ohttp_chunks` flag and remove `chunked` flag. 2026-04-10 ripere Add `num_bhttp_chunks` flag and remove `indeterminate_length` flag. 2026-04-10 ianswett Reject NEW_CONNECTION_ID frames with empty connection IDs and close the connection, per RFC9000. Based on cr/896368856 2026-04-10 wub No public description 2026-04-09 reubent Enforce a limit on the amount of total, uncompressed header bytes 2026-04-09 dschinazi Improve connection logging in OHTTP toy client 2026-04-09 dmcardle No public description 2026-04-09 martinduke Joining FETCH is allowed for any subscribe with forward = 1, without regard for filter type. 2026-04-08 asedeno Fis OSS QUICHE build. ``` Signed-off-by: Ryan Hamilton --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 83de49863c570..abdfe24b6adf3 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -296,7 +296,7 @@ quiche: project_name: "QUICHE" project_desc: "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols" project_url: "https://github.com/google/quiche" - release_date: "2026-04-09" + release_date: "2026-04-14" use_category: - controlplane - dataplane_core diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 69a632a2ef69d..e603f2c71222c 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -553,8 +553,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/simdutf/simdutf/releases/download/v{version}/singleheader.zip"], ), quiche = dict( - version = "bbe3dde941efd8cc9297face96b6305bbfd880e1", - sha256 = "4e1c184c1f4c9e350c718127754c7d369f9fcc11dc7557cce48a9fe47970a0ed", + version = "f4cb73412317e1ec397f37ec8e218396dad18186", + sha256 = "5798004d460932ef454f38a341773eba614097e581cbaa50513a8a0a446921a8", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", ), From eef5546da6589bf40e6be89fd7b3eb39bb89ba4d Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Wed, 15 Apr 2026 01:18:50 +0200 Subject: [PATCH 017/750] dynamic_modules: add http_per_route, load_balancer, and cluster arms to declare_all_init_functions! (#44452) ## Description The `declare_all_init_functions!` macro supports registering multiple filter types in a single module, but was missing arms for a couple of extension types. With this change, they can use the macro directly: ``` declare_all_init_functions!(init, bootstrap: new_bootstrap_config, http: new_http_filter_config, http_per_route: new_per_route_config, cluster: new_cluster_config, ); ``` --- **Commit Message:** dynamic_modules: add http_per_route, load_balancer, and cluster arms to declare_all_init_functions! **Additional Description:** **Risk Level:** Low **Testing:** CI **Docs Changes:** N/A **Release Notes:** N/A Signed-off-by: Rohit Agrawal --- .../dynamic_modules/sdk/rust/src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs index af787beaf1295..47afe7228f80b 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs @@ -555,6 +555,9 @@ macro_rules! declare_network_filter_init_functions { /// - `cert_validator:` — [`NewCertValidatorConfigFunction`] for TLS certificate validators /// - `upstream_http_tcp_bridge:` — [`NewUpstreamHttpTcpBridgeConfigFunction`] for upstream HTTP TCP /// bridges +/// - `http_per_route:` — [`NewHttpFilterPerRouteConfigFunction`] for HTTP per-route configs +/// - `load_balancer:` — [`NewLoadBalancerConfigFunction`] for load balancer policies +/// - `cluster:` — [`NewClusterConfigFunction`] for custom clusters /// - `tracer:` — [`NewTracerConfigFunction`] for tracers /// - `dns_resolver:` — [`NewDnsResolverConfigFunction`] for DNS resolvers /// - `transport_socket:` — [`NewTransportSocketFactoryConfigFunction`] for transport sockets @@ -661,6 +664,10 @@ macro_rules! declare_all_init_functions { envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION .get_or_init(|| $fn); }; + (@register http_per_route : $fn:expr) => { + envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION + .get_or_init(|| $fn); + }; (@register network : $fn:expr) => { envoy_proxy_dynamic_modules_rust_sdk::NEW_NETWORK_FILTER_CONFIG_FUNCTION .get_or_init(|| $fn); @@ -677,6 +684,14 @@ macro_rules! declare_all_init_functions { envoy_proxy_dynamic_modules_rust_sdk::NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION .get_or_init(|| $fn); }; + (@register load_balancer : $fn:expr) => { + envoy_proxy_dynamic_modules_rust_sdk::NEW_LOAD_BALANCER_CONFIG_FUNCTION + .get_or_init(|| $fn); + }; + (@register cluster : $fn:expr) => { + envoy_proxy_dynamic_modules_rust_sdk::NEW_CLUSTER_CONFIG_FUNCTION + .get_or_init(|| $fn); + }; (@register cert_validator : $fn:expr) => { envoy_proxy_dynamic_modules_rust_sdk::NEW_CERT_VALIDATOR_CONFIG_FUNCTION .get_or_init(|| $fn); From 186057efb00de0610fe46c24567f257e851f7997 Mon Sep 17 00:00:00 2001 From: Ethan Truong Date: Tue, 14 Apr 2026 19:47:58 -0400 Subject: [PATCH 018/750] Avoid the use of modulo in building maglev table (#44168) Local benchmark run shows possible performance improvement of ~15% by avoiding the use of modulo. A table build entry iterates through its permutations via `MaglevTable::permutation` which is implemented as follows: `(entry.offset_ + (entry.skip_ * entry.next_)) % table_size_` `offset_` and `skip_` are static, and `next_` is only incremented by 1 within `constructImplementationInternals`. Thus, we can avoid the expensive modulo `% table_size_` by keeping track of the `current_permutation` value and incrementing by `skip_` each iteration until `current_permutation` exceeds the `table_size_`, at which point we set `current_permutation = current_permutation - table_size_`, which is equivalent to the modulo operation, maintaining mathematical equivalence to the existing implementation while improving performance. `bazel run -c opt //test/extensions/load_balancing_policies/maglev:maglev_lb_benchmark -- --benchmark_filter=benchmarkMaglevLoadBalancerBuildTable` ## Baseline Benchmark | Time | CPU | Iterations | Memory | Memory per Host -- | -- | -- | -- | -- | -- BuildTable/100 | 2.68 ms | 2.67 ms | 266 | 67.8k | 678 BuildTable/200 | 2.42 ms | 2.42 ms | 284 | 77.656k | 388 BuildTable/500 | 2.60 ms | 2.60 ms | 269 | 90.584k | 181 ## Avoid use of modulo Benchmark | Time | CPU | Iterations | Memory | Memory per Host -- | -- | -- | -- | -- | -- BuildTable/100 | 2.22 ms | 2.22 ms | 322 | 67.8k | 678 BuildTable/200 | 2.05 ms | 2.05 ms | 342 | 77.656k | 388 BuildTable/500 | 2.22 ms | 2.21 ms | 314 | 90.584k | 181 Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] Fixes #44167 [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Ethan Truong --- .../maglev/maglev_lb.cc | 30 ++++++++++++++----- .../maglev/maglev_lb.h | 6 ++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/source/extensions/load_balancing_policies/maglev/maglev_lb.cc b/source/extensions/load_balancing_policies/maglev/maglev_lb.cc index eebfe93ff05a6..eeb6d2b2d39b6 100644 --- a/source/extensions/load_balancing_policies/maglev/maglev_lb.cc +++ b/source/extensions/load_balancing_policies/maglev/maglev_lb.cc @@ -159,6 +159,8 @@ void OriginalMaglevTable::constructImplementationInternals( for (uint32_t iteration = 1; table_index < table_size_; ++iteration) { for (uint64_t i = 0; i < table_build_entries.size() && table_index < table_size_; i++) { TableBuildEntry& entry = table_build_entries[i]; + ASSERT(entry.skip_ < table_size_, "skip must be less than table size"); + // To understand how target_weight_ and weight_ are used below, consider a host with weight // equal to max_normalized_weight. This would be picked on every single iteration. If it had // weight equal to max_normalized_weight / 3, then it would only be picked every 3 iterations, @@ -167,14 +169,21 @@ void OriginalMaglevTable::constructImplementationInternals( continue; } entry.target_weight_ += max_normalized_weight; - uint64_t c = permutation(entry); + uint64_t c = entry.current_permutation_; while (table_[c] != nullptr) { entry.next_++; - c = permutation(entry); + c += entry.skip_; + if (c >= table_size_) { + c -= table_size_; + } } table_[c] = entry.host_; entry.next_++; + entry.current_permutation_ = c + entry.skip_; + if (entry.current_permutation_ >= table_size_) { + entry.current_permutation_ -= table_size_; + } entry.count_++; table_index++; } @@ -209,6 +218,8 @@ void CompactMaglevTable::constructImplementationInternals( for (uint32_t iteration = 1; table_index < table_size_; ++iteration) { for (uint32_t i = 0; i < table_build_entries.size() && table_index < table_size_; i++) { TableBuildEntry& entry = table_build_entries[i]; + ASSERT(entry.skip_ < table_size_, "skip must be less than table size"); + // To understand how target_weight_ and weight_ are used below, consider a host with weight // equal to max_normalized_weight. This would be picked on every single iteration. If it had // weight equal to max_normalized_weight / 3, then it would only be picked every 3 iterations, @@ -219,10 +230,13 @@ void CompactMaglevTable::constructImplementationInternals( entry.target_weight_ += max_normalized_weight; // As we're using the compact implementation, our table size is limited to // 32-bit, hence static_cast here should be safe. - uint32_t c = static_cast(permutation(entry)); + uint32_t c = static_cast(entry.current_permutation_); while (occupied[c]) { entry.next_++; - c = static_cast(permutation(entry)); + c += entry.skip_; + if (c >= table_size_) { + c -= table_size_; + } } // Record the index of the given host. @@ -230,6 +244,10 @@ void CompactMaglevTable::constructImplementationInternals( occupied[c] = true; entry.next_++; + entry.current_permutation_ = c + entry.skip_; + if (entry.current_permutation_ >= table_size_) { + entry.current_permutation_ -= table_size_; + } entry.count_++; table_index++; } @@ -292,10 +310,6 @@ HostSelectionResponse CompactMaglevTable::chooseHost(uint64_t hash, uint32_t att return {host_table_[index]}; } -uint64_t MaglevTable::permutation(const TableBuildEntry& entry) { - return (entry.offset_ + (entry.skip_ * entry.next_)) % table_size_; -} - MaglevLoadBalancer::MaglevLoadBalancer(const PrioritySet& priority_set, ClusterLbStats& stats, Stats::Scope& scope, Runtime::Loader& runtime, Random::RandomGenerator& random, diff --git a/source/extensions/load_balancing_policies/maglev/maglev_lb.h b/source/extensions/load_balancing_policies/maglev/maglev_lb.h index 410e1ad6e39f7..51dae2eef361b 100644 --- a/source/extensions/load_balancing_policies/maglev/maglev_lb.h +++ b/source/extensions/load_balancing_policies/maglev/maglev_lb.h @@ -75,7 +75,8 @@ class MaglevTable : public ThreadAwareLoadBalancerBase::HashingLoadBalancer, protected: struct TableBuildEntry { TableBuildEntry(const HostConstSharedPtr& host, uint64_t offset, uint64_t skip, double weight) - : host_(host), offset_(offset), skip_(skip), weight_(weight) {} + : host_(host), offset_(offset), skip_(skip), weight_(weight), current_permutation_(offset) { + } HostConstSharedPtr host_; const uint64_t offset_; @@ -84,10 +85,9 @@ class MaglevTable : public ThreadAwareLoadBalancerBase::HashingLoadBalancer, double target_weight_{}; uint64_t next_{}; uint64_t count_{}; + uint64_t current_permutation_{}; }; - uint64_t permutation(const TableBuildEntry& entry); - /** * Template method for constructing the Maglev table. */ From 2a50e539cc2184d9732d7fc0144c11d45984847d Mon Sep 17 00:00:00 2001 From: Mukunda Rao Katta Date: Wed, 15 Apr 2026 01:45:24 -0700 Subject: [PATCH 019/750] contrib/kafka: fix 'succesfully' typo in log message (#44459) Trivial typo fix in a debug log message: `succesfully` -> `successfully`. Signed-off-by: MukundaKatta Co-authored-by: MukundaKatta --- .../filters/network/source/mesh/upstream_kafka_consumer_impl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/kafka/filters/network/source/mesh/upstream_kafka_consumer_impl.cc b/contrib/kafka/filters/network/source/mesh/upstream_kafka_consumer_impl.cc index 88db6e7500432..8ea651c6ce32b 100644 --- a/contrib/kafka/filters/network/source/mesh/upstream_kafka_consumer_impl.cc +++ b/contrib/kafka/filters/network/source/mesh/upstream_kafka_consumer_impl.cc @@ -65,7 +65,7 @@ RichKafkaConsumer::~RichKafkaConsumer() { consumer_->unassign(); consumer_->close(); - ENVOY_LOG(debug, "Kafka consumer [{}] closed succesfully", topic_); + ENVOY_LOG(debug, "Kafka consumer [{}] closed successfully", topic_); } // Read timeout constants. From ad40ae6ae0a06c359b27b6724f8ce4d0894fed99 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 15 Apr 2026 12:14:47 +0100 Subject: [PATCH 020/750] deps/rust: Fix for RUSTSEC-2026-0098 (#44465) Signed-off-by: Ryan Northey Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- Cargo.Bazel.lock | 28 +++++++++++++-------------- Cargo.lock | 4 ++-- bazel/tests/external/Cargo.Bazel.lock | 28 +++++++++++++-------------- docs/Cargo.Bazel.lock | 28 +++++++++++++-------------- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Cargo.Bazel.lock b/Cargo.Bazel.lock index 905305b931c87..f1fddba3d8089 100644 --- a/Cargo.Bazel.lock +++ b/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "8a020cdfb525b78c43df9dea09dc5c99c34712aa21a6a985feb2ec220306924a", + "checksum": "f853e38d6b1ff233bd49d987189c7baf375aecdb13730d651420d43946303a37", "crates": { "aho-corasick 1.1.4": { "name": "aho-corasick", @@ -2696,7 +2696,7 @@ "target": "once_cell" }, { - "id": "rand 0.9.2", + "id": "rand 0.9.4", "target": "rand" }, { @@ -2840,7 +2840,7 @@ "target": "parking_lot" }, { - "id": "rand 0.9.2", + "id": "rand 0.9.4", "target": "rand" }, { @@ -5857,14 +5857,14 @@ ], "license_file": null }, - "rand 0.9.2": { + "rand 0.9.4": { "name": "rand", - "version": "0.9.2", + "version": "0.9.4", "package_url": "https://github.com/rust-random/rand", "repository": { "Http": { - "url": "https://static.crates.io/crates/rand/0.9.2/download", - "sha256": "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" + "url": "https://static.crates.io/crates/rand/0.9.4/download", + "sha256": "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" } }, "targets": [ @@ -5910,7 +5910,7 @@ "selects": {} }, "edition": "2021", - "version": "0.9.2" + "version": "0.9.4" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -6532,7 +6532,7 @@ "alias": "pki_types" }, { - "id": "rustls-webpki 0.103.10", + "id": "rustls-webpki 0.103.12", "target": "webpki" }, { @@ -6633,14 +6633,14 @@ ], "license_file": "LICENSE-APACHE" }, - "rustls-webpki 0.103.10": { + "rustls-webpki 0.103.12": { "name": "rustls-webpki", - "version": "0.103.10", + "version": "0.103.12", "package_url": "https://github.com/rustls/webpki", "repository": { "Http": { - "url": "https://static.crates.io/crates/rustls-webpki/0.103.10/download", - "sha256": "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" + "url": "https://static.crates.io/crates/rustls-webpki/0.103.12/download", + "sha256": "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" } }, "targets": [ @@ -6689,7 +6689,7 @@ "selects": {} }, "edition": "2021", - "version": "0.103.10" + "version": "0.103.12" }, "license": "ISC", "license_ids": [ diff --git a/Cargo.lock b/Cargo.lock index 52c487bfe3d85..9f195d662c32f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -998,9 +998,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ "ring", "rustls-pki-types", diff --git a/bazel/tests/external/Cargo.Bazel.lock b/bazel/tests/external/Cargo.Bazel.lock index 5e3db0befa05c..7369c489c847e 100644 --- a/bazel/tests/external/Cargo.Bazel.lock +++ b/bazel/tests/external/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "5b28fd0dce18a76ab2908a4493989dbf92fe211266706e299c5a49874f95892d", + "checksum": "6c229299d001117048a532a667e66e1943ca53f008d36013a5fd3f15ebd71189", "crates": { "aho-corasick 1.1.4": { "name": "aho-corasick", @@ -2696,7 +2696,7 @@ "target": "once_cell" }, { - "id": "rand 0.9.2", + "id": "rand 0.9.4", "target": "rand" }, { @@ -2840,7 +2840,7 @@ "target": "parking_lot" }, { - "id": "rand 0.9.2", + "id": "rand 0.9.4", "target": "rand" }, { @@ -5857,14 +5857,14 @@ ], "license_file": null }, - "rand 0.9.2": { + "rand 0.9.4": { "name": "rand", - "version": "0.9.2", + "version": "0.9.4", "package_url": "https://github.com/rust-random/rand", "repository": { "Http": { - "url": "https://static.crates.io/crates/rand/0.9.2/download", - "sha256": "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" + "url": "https://static.crates.io/crates/rand/0.9.4/download", + "sha256": "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" } }, "targets": [ @@ -5910,7 +5910,7 @@ "selects": {} }, "edition": "2021", - "version": "0.9.2" + "version": "0.9.4" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -6532,7 +6532,7 @@ "alias": "pki_types" }, { - "id": "rustls-webpki 0.103.10", + "id": "rustls-webpki 0.103.12", "target": "webpki" }, { @@ -6633,14 +6633,14 @@ ], "license_file": "LICENSE-APACHE" }, - "rustls-webpki 0.103.10": { + "rustls-webpki 0.103.12": { "name": "rustls-webpki", - "version": "0.103.10", + "version": "0.103.12", "package_url": "https://github.com/rustls/webpki", "repository": { "Http": { - "url": "https://static.crates.io/crates/rustls-webpki/0.103.10/download", - "sha256": "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" + "url": "https://static.crates.io/crates/rustls-webpki/0.103.12/download", + "sha256": "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" } }, "targets": [ @@ -6689,7 +6689,7 @@ "selects": {} }, "edition": "2021", - "version": "0.103.10" + "version": "0.103.12" }, "license": "ISC", "license_ids": [ diff --git a/docs/Cargo.Bazel.lock b/docs/Cargo.Bazel.lock index 5e3db0befa05c..7369c489c847e 100644 --- a/docs/Cargo.Bazel.lock +++ b/docs/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "5b28fd0dce18a76ab2908a4493989dbf92fe211266706e299c5a49874f95892d", + "checksum": "6c229299d001117048a532a667e66e1943ca53f008d36013a5fd3f15ebd71189", "crates": { "aho-corasick 1.1.4": { "name": "aho-corasick", @@ -2696,7 +2696,7 @@ "target": "once_cell" }, { - "id": "rand 0.9.2", + "id": "rand 0.9.4", "target": "rand" }, { @@ -2840,7 +2840,7 @@ "target": "parking_lot" }, { - "id": "rand 0.9.2", + "id": "rand 0.9.4", "target": "rand" }, { @@ -5857,14 +5857,14 @@ ], "license_file": null }, - "rand 0.9.2": { + "rand 0.9.4": { "name": "rand", - "version": "0.9.2", + "version": "0.9.4", "package_url": "https://github.com/rust-random/rand", "repository": { "Http": { - "url": "https://static.crates.io/crates/rand/0.9.2/download", - "sha256": "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" + "url": "https://static.crates.io/crates/rand/0.9.4/download", + "sha256": "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" } }, "targets": [ @@ -5910,7 +5910,7 @@ "selects": {} }, "edition": "2021", - "version": "0.9.2" + "version": "0.9.4" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -6532,7 +6532,7 @@ "alias": "pki_types" }, { - "id": "rustls-webpki 0.103.10", + "id": "rustls-webpki 0.103.12", "target": "webpki" }, { @@ -6633,14 +6633,14 @@ ], "license_file": "LICENSE-APACHE" }, - "rustls-webpki 0.103.10": { + "rustls-webpki 0.103.12": { "name": "rustls-webpki", - "version": "0.103.10", + "version": "0.103.12", "package_url": "https://github.com/rustls/webpki", "repository": { "Http": { - "url": "https://static.crates.io/crates/rustls-webpki/0.103.10/download", - "sha256": "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" + "url": "https://static.crates.io/crates/rustls-webpki/0.103.12/download", + "sha256": "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" } }, "targets": [ @@ -6689,7 +6689,7 @@ "selects": {} }, "edition": "2021", - "version": "0.103.10" + "version": "0.103.12" }, "license": "ISC", "license_ids": [ From 5ce7109b31a47318ba30c8b16d3abfef9245071b Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Wed, 15 Apr 2026 20:12:52 +0800 Subject: [PATCH 021/750] oauth2: remove encrypt tokens runtime guard (#43016) Commit Message: oauth2: remove encrypt tokens runtime guard Additional Description: - Remove the `envoy.reloadable_features.oauth2_encrypt_tokens` runtime guard so token encryption is always used unless explicitly disabled via config. - Drop guard-specific tests and document the removal under removed_config_or_runtime. Risk Level: Low (removes legacy fallback; behavior has been default for months) Testing: Removed the related tests for this runtime guard. Docs Changes: N/A Release Notes: Added removed_config_or_runtime entry for OAuth2 encrypt-tokens runtime guard removal. Platform Specific Features: N/A Fixes #42978 --------- Signed-off-by: Huabing (Robin) Zhao --- changelogs/current.yaml | 5 + source/common/runtime/runtime_features.cc | 1 - .../extensions/filters/http/oauth2/filter.cc | 9 +- .../filters/http/oauth2/filter_test.cc | 112 ------------------ 4 files changed, 6 insertions(+), 121 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 01634ee2d747a..cef2b0a2ae31f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -372,6 +372,11 @@ bug_fixes: removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` +- area: oauth2 + change: | + Removed the runtime guard ``envoy.reloadable_features.oauth2_encrypt_tokens``. Token cookies (access, + ID, refresh) remain encrypted by default; use the ``disable_token_encryption`` filter configuration + to turn encryption off if needed. - area: tcp_proxy change: | Removed runtime guard ``envoy.reloadable_features.tcp_proxy_set_idle_timer_immediately_on_new_connection`` diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 73797d65d37a6..45b9fa4ddab72 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -70,7 +70,6 @@ RUNTIME_GUARD(envoy_reloadable_features_mcp_filter_use_new_metadata_namespace); RUNTIME_GUARD(envoy_reloadable_features_mobile_use_network_observer_registry); RUNTIME_GUARD(envoy_reloadable_features_no_extension_lookup_by_name); RUNTIME_GUARD(envoy_reloadable_features_oauth2_cleanup_cookies); -RUNTIME_GUARD(envoy_reloadable_features_oauth2_encrypt_tokens); RUNTIME_GUARD(envoy_reloadable_features_odcds_over_ads_fix); RUNTIME_GUARD(envoy_reloadable_features_on_demand_cluster_no_recreate_stream); RUNTIME_GUARD(envoy_reloadable_features_on_demand_track_end_stream); diff --git a/source/extensions/filters/http/oauth2/filter.cc b/source/extensions/filters/http/oauth2/filter.cc index 484965d0c2544..d7c659129b385 100644 --- a/source/extensions/filters/http/oauth2/filter.cc +++ b/source/extensions/filters/http/oauth2/filter.cc @@ -881,10 +881,6 @@ void OAuth2Filter::decryptAndUpdateOAuthTokenCookies(Http::RequestHeaderMap& hea return; } - if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.oauth2_encrypt_tokens")) { - return; - } - absl::flat_hash_map cookies = Http::Utility::parseCookies(headers); if (cookies.empty()) { return; @@ -920,10 +916,7 @@ std::string OAuth2Filter::encryptToken(const std::string& token) const { return token; } - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.oauth2_encrypt_tokens")) { - return encrypt(token, config_->hmacSecret(), random_); - } - return token; + return encrypt(token, config_->hmacSecret(), random_); } std::string OAuth2Filter::decryptToken(const std::string& encrypted_token) const { diff --git a/test/extensions/filters/http/oauth2/filter_test.cc b/test/extensions/filters/http/oauth2/filter_test.cc index 42842d7a5cafa..651023750f422 100644 --- a/test/extensions/filters/http/oauth2/filter_test.cc +++ b/test/extensions/filters/http/oauth2/filter_test.cc @@ -1238,74 +1238,8 @@ TEST_F(OAuth2Test, SetBearerTokenWithTlsClientAuth) { filter_->decodeHeaders(request_headers, false)); } -TEST_F(OAuth2Test, SetBearerTokenWithEncryptionDisabled) { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"envoy.reloadable_features.oauth2_encrypt_tokens", "false"}}); - - init(getConfig(false /* forward_bearer_token */, true /* use_refresh_token */)); - - // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. - test_time_.setSystemTime(SystemTime(std::chrono::seconds(1000))); - - Http::TestRequestHeaderMapImpl request_headers{ - {Http::Headers::get().Path.get(), "/_oauth?code=123&state=" + TEST_ENCODED_STATE}, - {Http::Headers::get().Cookie.get(), "OauthNonce.00000000075bcd15=" + TEST_CSRF_TOKEN}, - {Http::Headers::get().Cookie.get(), - "CodeVerifier.00000000075bcd15=" + TEST_ENCRYPTED_CODE_VERIFIER}, - {Http::Headers::get().Host.get(), "traffic.example.com"}, - {Http::Headers::get().Scheme.get(), "https"}, - {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, - }; - - EXPECT_CALL(*validator_, setParams(_, _)); - EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); - - EXPECT_CALL(*oauth_client_, asyncGetAccessToken("123", TEST_CLIENT_ID, "asdf_client_secret_fdsa", - "https://traffic.example.com" + TEST_CALLBACK, - TEST_CODE_VERIFIER, AuthType::UrlEncodedBody)); - - EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndBuffer, - filter_->decodeHeaders(request_headers, false)); - - // Expected response after the callback & validation is complete - verifying we kept the - // state and method of the original request, including the query string parameters. - Http::TestRequestHeaderMapImpl response_headers{ - {Http::Headers::get().Status.get(), "302"}, - {Http::Headers::get().SetCookie.get(), "OauthHMAC=" - "4TKyxPV/F7yyvr0XgJ2bkWFOc8t4IOFen1k29b84MAQ=;" - "path=/;Max-Age=600;secure;HttpOnly"}, - {Http::Headers::get().SetCookie.get(), - "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, - {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly"}, - {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly"}, - {Http::Headers::get().SetCookie.get(), - "RefreshToken=some-refresh-token;path=/;Max-Age=604800;secure;HttpOnly"}, - {Http::Headers::get().SetCookie.get(), - "OauthNonce.00000000075bcd15=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"}, - {Http::Headers::get().SetCookie.get(), - "OauthNonce=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"}, - {Http::Headers::get().SetCookie.get(), - "CodeVerifier.00000000075bcd15=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"}, - {Http::Headers::get().SetCookie.get(), - "CodeVerifier=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"}, - {Http::Headers::get().Location.get(), - "https://traffic.example.com/original_path?var1=1&var2=2"}, - }; - - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), true)); - - filter_->onGetAccessTokenSuccess("access_code", "some-id-token", "some-refresh-token", - std::chrono::seconds(600)); - - EXPECT_EQ(scope_.counterFromString("test.my_prefix.oauth_failure").value(), 0); - EXPECT_EQ(scope_.counterFromString("test.my_prefix.oauth_success").value(), 1); -} - TEST_F(OAuth2Test, SetBearerTokenWithDisableTokenEncryptionConfig) { TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"envoy.reloadable_features.oauth2_encrypt_tokens", "true"}}); constexpr auto DisabledSameSite = ::envoy::extensions::filters::http::oauth2::v3:: CookieConfig_SameSite::CookieConfig_SameSite_DISABLED; @@ -4527,52 +4461,6 @@ TEST_F(OAuth2Test, CookiesDecryptedBeforeForwarding) { EXPECT_EQ(cookies.contains("CodeVerifier"), false); } -// Ensure that the token cookies are decrypted before forwarding the request -TEST_F(OAuth2Test, CookiesDecryptedBeforeForwardingWithEncryptionDisabled) { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"envoy.reloadable_features.oauth2_encrypt_tokens", "false"}}); - - // Initialize with use_refresh_token set to false - init(getConfig(true /* forward_bearer_token */)); - - // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. - test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); - - Http::TestRequestHeaderMapImpl request_headers{ - {Http::Headers::get().Host.get(), "traffic.example.com"}, - {Http::Headers::get().Path.get(), "/original_path?var1=1&var2=2"}, - {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, - {Http::Headers::get().Cookie.get(), "OauthHMAC=4TKyxPV/F7yyvr0XgJ2bkWFOc8t4IOFen1k29b84MAQ="}, - {Http::Headers::get().Cookie.get(), "OauthExpires=1600"}, - {Http::Headers::get().Cookie.get(), "BearerToken=access_code"}, - {Http::Headers::get().Cookie.get(), "IdToken=some-id-token"}, - {Http::Headers::get().Cookie.get(), "RefreshToken=some-refresh-token"}, - {Http::Headers::get().Cookie.get(), "OauthNonce.00000000075bcd15=" + TEST_CSRF_TOKEN}, - }; - - // cookie-validation mocking - EXPECT_CALL(*validator_, setParams(_, _)); - EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); - - // return reference mocking - std::string access_token{"access_code"}; - EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(access_token)); - - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); - - // Expect the request headers to be updated with the decrypted tokens - auto cookies = Http::Utility::parseCookies(request_headers); - EXPECT_EQ(cookies.at("BearerToken"), "access_code"); - EXPECT_EQ(cookies.at("IdToken"), "some-id-token"); - - // OAuth flow cookies should be removed before forwarding the request - EXPECT_EQ(cookies.contains("OauthHMAC"), false); - EXPECT_EQ(cookies.contains("OauthExpires"), false); - EXPECT_EQ(cookies.contains("RefreshToken"), false); - EXPECT_EQ(cookies.contains("OauthNonce"), false); - EXPECT_EQ(cookies.contains("CodeVerifier"), false); -} - // Ensure that the token cookies are decrypted before forwarding the request TEST_F(OAuth2Test, CookiesDecryptedBeforeForwardingWithCleanupOAuthCookiesDisabled) { TestScopedRuntime scoped_runtime; From e3115f6c7411e5a439afb27d5fc766dda3f5eafe Mon Sep 17 00:00:00 2001 From: Connor Rogers <23215294+coro@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:16:08 +0100 Subject: [PATCH 022/750] oauth2: Unset `OauthExpires` cookie on signout (#44104) Commit Message: OAuth filter now removes the `OauthExpires` cookie from the user's browser upon logout. Additional Description: Previously the filter unset other OAuth session related cookies such as `OauthHMAC` but did not clear this one remaining cookie. This cookie refers to an already invalidated session state so therefore should be safely removed. Risk Level: Medium Testing: Ran `bazel test -c fastbuild //test/extensions/filters/http/oauth2:filter_test` with new tests Docs Changes: None Release Notes: Added Platform Specific Features: None [Optional Runtime guard:] N/A [Optional Fixes #Issue] N/A [Optional Fixes commit #PR or SHA] N/A [Optional Deprecated:] N/A [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] N/A cc @zhaohuabing as this is somewhat related to your OAuth cookie work in #42079 --------- Signed-off-by: Connor Rogers <23215294+coro@users.noreply.github.com> --- changelogs/current.yaml | 4 ++++ source/extensions/filters/http/oauth2/filter.cc | 2 +- .../filters/http/oauth2/filter_test.cc | 17 ++++++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index cef2b0a2ae31f..2658b1bee44b5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -118,6 +118,10 @@ minor_behavior_changes: change: | Added :ref:`per-route configuration support ` to the OAuth2 HTTP filter. +- area: oauth2 + change: | + The oauth2 filter now removes the OauthExpires cookie from a user's browser upon logging out. + Previously this cookie persisted beyond the logout despite the session no longer existing. - area: proto_api_scrubber change: | If :ref:`scrub_unknown_fields diff --git a/source/extensions/filters/http/oauth2/filter.cc b/source/extensions/filters/http/oauth2/filter.cc index d7c659129b385..9b62fdf25382c 100644 --- a/source/extensions/filters/http/oauth2/filter.cc +++ b/source/extensions/filters/http/oauth2/filter.cc @@ -1049,7 +1049,7 @@ Http::FilterHeadersStatus OAuth2Filter::signOutUser(const Http::RequestHeaderMap {cookie_names.bearer_token_, config_->bearerTokenCookieSettings().path_}, {cookie_names.id_token_, config_->idTokenCookieSettings().path_}, {cookie_names.refresh_token_, config_->refreshTokenCookieSettings().path_}, - + {cookie_names.oauth_expires_, config_->expiresCookieSettings().path_}, }; absl::flat_hash_map request_cookies = diff --git a/test/extensions/filters/http/oauth2/filter_test.cc b/test/extensions/filters/http/oauth2/filter_test.cc index 651023750f422..2f1d7e7d37cfb 100644 --- a/test/extensions/filters/http/oauth2/filter_test.cc +++ b/test/extensions/filters/http/oauth2/filter_test.cc @@ -922,13 +922,14 @@ TEST_F(OAuth2Test, RequestSignout) { EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, true)) .WillOnce(Invoke([&](Http::ResponseHeaderMap& headers, bool) { EXPECT_EQ(headers.Status()->value(), "302"); - EXPECT_EQ(headers.get(Http::Headers::get().SetCookie).size(), 10); + EXPECT_EQ(headers.get(Http::Headers::get().SetCookie).size(), 11); EXPECT_EQ(headers.get(Http::Headers::get().Location)[0]->value().getStringView(), "https://traffic.example.com/"); EXPECT_EQ(Http::Utility::parseSetCookieValue(headers, "OauthHMAC"), "deleted"); EXPECT_EQ(Http::Utility::parseSetCookieValue(headers, "BearerToken"), "deleted"); EXPECT_EQ(Http::Utility::parseSetCookieValue(headers, "IdToken"), "deleted"); EXPECT_EQ(Http::Utility::parseSetCookieValue(headers, "RefreshToken"), "deleted"); + EXPECT_EQ(Http::Utility::parseSetCookieValue(headers, "OauthExpires"), "deleted"); EXPECT_EQ(Http::Utility::parseSetCookieValue(headers, "OauthNonce"), "deleted"); EXPECT_EQ(Http::Utility::parseSetCookieValue(headers, "CodeVerifier"), "deleted"); EXPECT_EQ(Http::Utility::parseSetCookieValue(headers, "OauthNonce.1"), "deleted"); @@ -989,6 +990,8 @@ TEST_F(OAuth2Test, RequestSignoutWhenEndSessionEndpointIsConfigured) { "IdToken=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"}, {Http::Headers::get().SetCookie.get(), "RefreshToken=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"}, + {Http::Headers::get().SetCookie.get(), + "OauthExpires=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"}, {Http::Headers::get().Location.get(), "https://auth.example.com/oauth/" "logout?id_token_hint=xyztoken&client_id=1&post_logout_" "redirect_uri=https%3A%2F%2Ftraffic.example.com%2F"}, @@ -4641,6 +4644,7 @@ TEST_F(OAuth2Test, SecureAttributeAddedForSecureCookiePrefixesOnSignout) { auto* cookie_names = credentials->mutable_cookie_names(); cookie_names->set_oauth_hmac(absl::StrCat(prefix, "OauthHMAC")); cookie_names->set_bearer_token(absl::StrCat(prefix, "BearerToken")); + cookie_names->set_oauth_expires(absl::StrCat(prefix, "OauthExpires")); cookie_names->set_id_token(absl::StrCat(prefix, "IdToken")); cookie_names->set_refresh_token(absl::StrCat(prefix, "RefreshToken")); cookie_names->set_oauth_nonce(absl::StrCat(prefix, "OauthNonce")); @@ -4661,7 +4665,7 @@ TEST_F(OAuth2Test, SecureAttributeAddedForSecureCookiePrefixesOnSignout) { EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, true)) .WillOnce(Invoke([&](Http::ResponseHeaderMap& passed_headers, bool) { - EXPECT_EQ(passed_headers.get(Http::Headers::get().SetCookie).size(), 4); + EXPECT_EQ(passed_headers.get(Http::Headers::get().SetCookie).size(), 5); const auto& cookie_str = passed_headers.get(Http::Headers::get().SetCookie)[0]->value().getStringView(); if (expect_secure) { @@ -4885,13 +4889,19 @@ TEST_F(OAuth2Test, OAuthTestCustomCookiePaths) { filter_->decodeHeaders(signout_headers, false)); auto cookies = extract_cookies(response_headers); - bool found_hmac_delete = false, found_nonce_delete = false, found_code_verifier_delete = false; + bool found_hmac_delete = false, found_nonce_delete = false, found_code_verifier_delete = false, + found_expires_delete = false; for (const auto& cookie : cookies) { if (cookie.find("OauthHMAC=deleted") != std::string::npos) { EXPECT_NE(cookie.find("path=/app"), std::string::npos) << "OauthHMAC deletion should have path=/app, got: " << cookie; found_hmac_delete = true; } + if (cookie.find("OauthExpires=deleted") != std::string::npos) { + EXPECT_NE(cookie.find("path=/app"), std::string::npos) + << "OauthExpires deletion should have path=/app, got: " << cookie; + found_expires_delete = true; + } if (cookie.find("OauthNonce.00000000075bcd15=deleted") != std::string::npos) { EXPECT_NE(cookie.find("path=/auth/callback"), std::string::npos) << "OauthNonce deletion should have path=/auth/callback, got: " << cookie; @@ -4904,6 +4914,7 @@ TEST_F(OAuth2Test, OAuthTestCustomCookiePaths) { } } EXPECT_TRUE(found_hmac_delete) << "OauthHMAC deletion cookie not found."; + EXPECT_TRUE(found_expires_delete) << "OauthExpires deletion cookie not found."; EXPECT_TRUE(found_nonce_delete) << "OauthNonce deletion cookie not found."; EXPECT_TRUE(found_code_verifier_delete) << "CodeVerifier deletion cookie not found."; } From 8128c1cf48be3abbfe926c9c4cd547722fa6ff88 Mon Sep 17 00:00:00 2001 From: tyxia <72890320+tyxia@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:16:35 -0400 Subject: [PATCH 023/750] perf: Improve memory footprint on router (#44402) - replace vector with struct of bitfields bools - use flat_hash_map with shared_ptr value, for better cache locality. Pointer/iterator stability is not required for shared ptr - use inline vector for shadow policy Save ~6% of memory (0.1G out of 1.6G) on router creation Before: image After Opt: image --------- Signed-off-by: tyxia --- source/common/router/config_impl.h | 6 +- source/common/router/router.cc | 93 ++++++++++++++++++----------- source/common/router/router.h | 51 ++++++++-------- test/common/router/router_2_test.cc | 39 ++++++++---- 4 files changed, 113 insertions(+), 76 deletions(-) diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index bd06a71ec7266..1518dc2e77342 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -38,9 +38,9 @@ #include "source/common/router/tls_context_match_criteria_impl.h" #include "source/common/stats/symbol_table.h" +#include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/container/inlined_vector.h" -#include "absl/container/node_hash_map.h" #include "absl/types/optional.h" namespace Envoy { @@ -1216,7 +1216,7 @@ class RouteMatcher { absl::Status& creation_status); using WildcardVirtualHosts = - std::map, std::greater<>>; + std::map, std::greater<>>; using SubstringFunction = std::function; const VirtualHostImpl* findWildcardVirtualHost(absl::string_view host, const WildcardVirtualHosts& wildcard_virtual_hosts, @@ -1224,7 +1224,7 @@ class RouteMatcher { bool ignorePortInHostMatching() const { return ignore_port_in_host_matching_; } Stats::ScopeSharedPtr vhost_scope_; - absl::node_hash_map virtual_hosts_; + absl::flat_hash_map virtual_hosts_; // std::greater as a minor optimization to iterate from more to less specific // // A note on using an unordered_map versus a vector of (string, VirtualHostImplSharedPtr) pairs: diff --git a/source/common/router/router.cc b/source/common/router/router.cc index a2a6c58db9131..0e4d06b10ae9e 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -354,25 +354,6 @@ Filter::~Filter() { ASSERT(!retry_state_); } -const FilterUtility::StrictHeaderChecker::HeaderCheckResult -FilterUtility::StrictHeaderChecker::checkHeader(Http::RequestHeaderMap& headers, - const Http::LowerCaseString& target_header) { - if (target_header == Http::Headers::get().EnvoyUpstreamRequestTimeoutMs) { - return isInteger(headers.EnvoyUpstreamRequestTimeoutMs()); - } else if (target_header == Http::Headers::get().EnvoyUpstreamRequestPerTryTimeoutMs) { - return isInteger(headers.EnvoyUpstreamRequestPerTryTimeoutMs()); - } else if (target_header == Http::Headers::get().EnvoyMaxRetries) { - return isInteger(headers.EnvoyMaxRetries()); - } else if (target_header == Http::Headers::get().EnvoyRetryOn) { - return hasValidRetryFields(headers.EnvoyRetryOn(), &Router::RetryStateImpl::parseRetryOn); - } else if (target_header == Http::Headers::get().EnvoyRetryGrpcOn) { - return hasValidRetryFields(headers.EnvoyRetryGrpcOn(), - &Router::RetryStateImpl::parseRetryGrpcOn); - } - // Should only validate headers for which we have implemented a validator. - PANIC("unexpectedly reached"); -} - Stats::StatName Filter::upstreamZone(Upstream::HostDescriptionOptConstRef upstream_host) { return upstream_host ? upstream_host->localityZoneStatName() : config_->empty_stat_name_; } @@ -439,6 +420,60 @@ void Filter::chargeUpstreamCode(Http::Code code, Upstream::HostDescriptionOptCon chargeUpstreamCode(response_status_code, *fake_response_headers, upstream_host, dropped); } +Http::FilterHeadersStatus Filter::checkStrictHeaders(const Http::RequestHeaderMap& headers) { + auto handle_invalid_header = + [this](const FilterUtility::StrictHeaderChecker::HeaderCheckResult& res) { + callbacks_->streamInfo().setResponseFlag( + StreamInfo::CoreResponseFlag::InvalidEnvoyRequestHeaders); + const std::string body = fmt::format("invalid header '{}' with value '{}'", + std::string(res.entry_->key().getStringView()), + std::string(res.entry_->value().getStringView())); + const std::string details = + absl::StrCat(StreamInfo::ResponseCodeDetails::get().InvalidEnvoyRequestHeaders, "{", + StringUtil::replaceAllEmptySpace(res.entry_->key().getStringView()), "}"); + callbacks_->sendLocalReply(Http::Code::BadRequest, body, modify_headers_, absl::nullopt, + details); + return Http::FilterHeadersStatus::StopIteration; + }; + + if (config_->envoy_upstream_rq_timeout_ms_) { + const auto res = + FilterUtility::StrictHeaderChecker::isInteger(headers.EnvoyUpstreamRequestTimeoutMs()); + if (!res.valid_) { + return handle_invalid_header(res); + } + } + if (config_->envoy_upstream_rq_per_try_timeout_ms_) { + const auto res = FilterUtility::StrictHeaderChecker::isInteger( + headers.EnvoyUpstreamRequestPerTryTimeoutMs()); + if (!res.valid_) { + return handle_invalid_header(res); + } + } + if (config_->envoy_max_retries_) { + const auto res = FilterUtility::StrictHeaderChecker::isInteger(headers.EnvoyMaxRetries()); + if (!res.valid_) { + return handle_invalid_header(res); + } + } + if (config_->envoy_retry_on_) { + const auto res = FilterUtility::StrictHeaderChecker::hasValidRetryFields( + headers.EnvoyRetryOn(), &Router::RetryStateImpl::parseRetryOn); + if (!res.valid_) { + return handle_invalid_header(res); + } + } + if (config_->envoy_retry_grpc_on_) { + const auto res = FilterUtility::StrictHeaderChecker::hasValidRetryFields( + headers.EnvoyRetryGrpcOn(), &Router::RetryStateImpl::parseRetryGrpcOn); + if (!res.valid_) { + return handle_invalid_header(res); + } + } + + return Http::FilterHeadersStatus::Continue; +} + Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { downstream_headers_ = &headers; @@ -550,23 +585,9 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, ENVOY_STREAM_LOG(debug, "cluster '{}' match for URL '{}'", *callbacks_, route_entry_->clusterName(), headers.getPathValue()); - if (config_->strict_check_headers_ != nullptr) { - for (const auto& header : *config_->strict_check_headers_) { - const auto res = FilterUtility::StrictHeaderChecker::checkHeader(headers, header); - if (!res.valid_) { - callbacks_->streamInfo().setResponseFlag( - StreamInfo::CoreResponseFlag::InvalidEnvoyRequestHeaders); - const std::string body = fmt::format("invalid header '{}' with value '{}'", - std::string(res.entry_->key().getStringView()), - std::string(res.entry_->value().getStringView())); - const std::string details = - absl::StrCat(StreamInfo::ResponseCodeDetails::get().InvalidEnvoyRequestHeaders, "{", - StringUtil::replaceAllEmptySpace(res.entry_->key().getStringView()), "}"); - callbacks_->sendLocalReply(Http::Code::BadRequest, body, modify_headers_, absl::nullopt, - details); - return Http::FilterHeadersStatus::StopIteration; - } - } + const Http::FilterHeadersStatus status = checkStrictHeaders(headers); + if (status != Http::FilterHeadersStatus::Continue) { + return status; } const Http::HeaderEntry* request_alt_name = headers.EnvoyUpstreamAltStatName(); diff --git a/source/common/router/router.h b/source/common/router/router.h index 86b09203fa21d..857abde5b554b 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -27,6 +27,7 @@ #include "source/common/common/logger.h" #include "source/common/config/well_known_names.h" #include "source/common/http/filter_chain_helper.h" +#include "source/common/http/headers.h" #include "source/common/http/sidestream_watermark.h" #include "source/common/http/utility.h" #include "source/common/router/context_impl.h" @@ -36,6 +37,7 @@ #include "source/common/upstream/load_balancer_context_base.h" #include "source/common/upstream/upstream_factory_context_impl.h" +#include "absl/strings/match.h" #include "absl/types/optional.h" namespace Envoy { @@ -62,22 +64,8 @@ class FilterUtility { const Http::HeaderEntry* entry_; }; - /** - * Determine whether a given header's value passes the strict validation - * defined for that header. - * @param headers supplies the headers from which to get the target header. - * @param target_header is the header to be validated. - * @return HeaderCheckResult containing the entry for @param target_header - * and valid_ set to FALSE if @param target_header is set to an - * invalid value. If @param target_header doesn't appear in - * @param headers, return a result with valid_ set to TRUE. - */ - static const HeaderCheckResult checkHeader(Http::RequestHeaderMap& headers, - const Http::LowerCaseString& target_header); - using ParseRetryFlagsFunc = std::function(absl::string_view)>; - private: static HeaderCheckResult hasValidRetryFields(const Http::HeaderEntry* header_entry, const ParseRetryFlagsFunc& parse_fn) { HeaderCheckResult r; @@ -214,10 +202,19 @@ class FilterConfig : public Http::FilterChainFactory { reject_connect_request_early_data_(reject_connect_request_early_data), http_context_(http_context), zone_name_(factory_context.localInfo().zoneStatName()), shadow_writer_(std::move(shadow_writer)), time_source_(time_source) { - if (!strict_check_headers.empty()) { - strict_check_headers_ = std::make_unique(); - for (const auto& header : strict_check_headers) { - strict_check_headers_->emplace_back(Http::LowerCaseString(header)); + for (const auto& header : strict_check_headers) { + if (absl::EqualsIgnoreCase(header, + Http::Headers::get().EnvoyUpstreamRequestTimeoutMs.get())) { + envoy_upstream_rq_timeout_ms_ = true; + } else if (absl::EqualsIgnoreCase( + header, Http::Headers::get().EnvoyUpstreamRequestPerTryTimeoutMs.get())) { + envoy_upstream_rq_per_try_timeout_ms_ = true; + } else if (absl::EqualsIgnoreCase(header, Http::Headers::get().EnvoyMaxRetries.get())) { + envoy_max_retries_ = true; + } else if (absl::EqualsIgnoreCase(header, Http::Headers::get().EnvoyRetryOn.get())) { + envoy_retry_on_ = true; + } else if (absl::EqualsIgnoreCase(header, Http::Headers::get().EnvoyRetryGrpcOn.get())) { + envoy_retry_grpc_on_ = true; } } } @@ -265,13 +262,16 @@ class FilterConfig : public Http::FilterChainFactory { FilterStats default_stats_; FilterStats async_stats_; Random::RandomGenerator& random_; - const bool emit_dynamic_stats_; - const bool start_child_span_; - const bool suppress_envoy_headers_; - const bool respect_expected_rq_timeout_; - const bool suppress_grpc_request_failure_code_stats_; - // TODO(xyu-stripe): Make this a bitset to keep cluster memory footprint down. - HeaderVectorPtr strict_check_headers_; + const bool emit_dynamic_stats_ : 1; + const bool start_child_span_ : 1; + const bool suppress_envoy_headers_ : 1; + const bool respect_expected_rq_timeout_ : 1; + const bool suppress_grpc_request_failure_code_stats_ : 1; + bool envoy_upstream_rq_timeout_ms_ : 1 = false; + bool envoy_upstream_rq_per_try_timeout_ms_ : 1 = false; + bool envoy_max_retries_ : 1 = false; + bool envoy_retry_on_ : 1 = false; + bool envoy_retry_grpc_on_ : 1 = false; const bool flush_upstream_log_on_upstream_stream_; const bool reject_connect_request_early_data_; absl::optional upstream_log_flush_interval_; @@ -564,6 +564,7 @@ class Filter : Logger::Loggable, Upstream::HostDescriptionOptConstRef upstream_host, bool dropped); void chargeUpstreamCode(Http::Code code, Upstream::HostDescriptionOptConstRef upstream_host, bool dropped); + Http::FilterHeadersStatus checkStrictHeaders(const Http::RequestHeaderMap& headers); void chargeUpstreamAbort(Http::Code code, bool dropped, UpstreamRequest& upstream_request); void cleanup(); virtual RetryStatePtr createRetryState(const RetryPolicy& policy, diff --git a/test/common/router/router_2_test.cc b/test/common/router/router_2_test.cc index a6fa8617997fd..fff94e4c4d584 100644 --- a/test/common/router/router_2_test.cc +++ b/test/common/router/router_2_test.cc @@ -1,3 +1,4 @@ +#include "source/common/router/retry_state_impl.h" #include "source/common/tracing/http_tracer_impl.h" #include "test/common/router/router_test_base.h" @@ -955,12 +956,18 @@ TEST(RouterFilterUtilityTest, StrictCheckValidHeaders) { "cancelled,internal,deadline-exceeded,resource-exhausted , unavailable"}, // space is allowed }; - for (const auto& target : SUPPORTED_STRICT_CHECKED_HEADERS) { - EXPECT_TRUE( - FilterUtility::StrictHeaderChecker::checkHeader(headers, Http::LowerCaseString(target)) - .valid_) - << fmt::format("'{}' should have passed strict validation", target); - } + EXPECT_TRUE(FilterUtility::StrictHeaderChecker::isInteger(headers.EnvoyUpstreamRequestTimeoutMs()) + .valid_); + EXPECT_TRUE( + FilterUtility::StrictHeaderChecker::isInteger(headers.EnvoyUpstreamRequestPerTryTimeoutMs()) + .valid_); + EXPECT_TRUE(FilterUtility::StrictHeaderChecker::isInteger(headers.EnvoyMaxRetries()).valid_); + EXPECT_TRUE(FilterUtility::StrictHeaderChecker::hasValidRetryFields( + headers.EnvoyRetryOn(), &Router::RetryStateImpl::parseRetryOn) + .valid_); + EXPECT_TRUE(FilterUtility::StrictHeaderChecker::hasValidRetryFields( + headers.EnvoyRetryGrpcOn(), &Router::RetryStateImpl::parseRetryGrpcOn) + .valid_); Http::TestRequestHeaderMapImpl failing_headers{ {"X-envoy-Upstream-rq-timeout-ms", "10.0"}, @@ -970,12 +977,20 @@ TEST(RouterFilterUtilityTest, StrictCheckValidHeaders) { {"x-envoy-retry-grpc-on", "5xx,cancelled, internal"}, // '5xx' is an invalid entry }; - for (const auto& target : SUPPORTED_STRICT_CHECKED_HEADERS) { - EXPECT_FALSE(FilterUtility::StrictHeaderChecker::checkHeader(failing_headers, - Http::LowerCaseString(target)) - .valid_) - << fmt::format("'{}' should have failed strict validation", target); - } + EXPECT_FALSE( + FilterUtility::StrictHeaderChecker::isInteger(failing_headers.EnvoyUpstreamRequestTimeoutMs()) + .valid_); + EXPECT_FALSE(FilterUtility::StrictHeaderChecker::isInteger( + failing_headers.EnvoyUpstreamRequestPerTryTimeoutMs()) + .valid_); + EXPECT_FALSE( + FilterUtility::StrictHeaderChecker::isInteger(failing_headers.EnvoyMaxRetries()).valid_); + EXPECT_FALSE(FilterUtility::StrictHeaderChecker::hasValidRetryFields( + failing_headers.EnvoyRetryOn(), &Router::RetryStateImpl::parseRetryOn) + .valid_); + EXPECT_FALSE(FilterUtility::StrictHeaderChecker::hasValidRetryFields( + failing_headers.EnvoyRetryGrpcOn(), &Router::RetryStateImpl::parseRetryGrpcOn) + .valid_); } class RouterTestSupressGRPCStatsEnabled : public RouterTestBase { From 3b32fa610102fad1085b9ba57d7b5ee45d27fffb Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:25:31 -0400 Subject: [PATCH 024/750] test: fix load_stats_integration_test TSAN SIGSEGV flake (#44464) Signed-off-by: Ryan Northey Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- test/integration/load_stats_integration_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/load_stats_integration_test.cc b/test/integration/load_stats_integration_test.cc index 9850dfab3b986..4e01daad97a61 100644 --- a/test/integration/load_stats_integration_test.cc +++ b/test/integration/load_stats_integration_test.cc @@ -427,6 +427,7 @@ class LoadStatsIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, initiateClientConnection(); waitForUpstreamResponse(endpoint_index, response_code, send_orca_load_report); cleanupUpstreamAndDownstream(); + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); } void updateDropOverloadConfig() { From cccad3c93203b082d7a32fbd75852d09c045a4a9 Mon Sep 17 00:00:00 2001 From: Anton Kanugalawattage Date: Wed, 15 Apr 2026 10:32:33 -0400 Subject: [PATCH 025/750] reverse tunnels: allow additional headers on initiation requests (#44336) Fixes https://github.com/envoyproxy/envoy/issues/44328 For an explanation of how to fill out the fields, please see the relevant section in [PULL_REQUESTS.md](https://github.com/envoyproxy/envoy/blob/main/PULL_REQUESTS.md) !!!ATTENTION!!! Please check the [use of generative AI policy](https://github.com/envoyproxy/envoy/blob/main/CONTRIBUTING.md?plain=1#L41). You may use generative AI only if you fully understand the code. You need to disclose this usage in the PR description to ensure transparency. --> Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Anton Kanugalawattage --- .../downstream_socket_interface/v3/BUILD | 5 +- ..._reverse_connection_socket_interface.proto | 5 + .../downstream_socket_interface/BUILD | 1 + .../rc_connection_wrapper.cc | 24 ++ .../reverse_connection_io_handle.h | 10 + .../reverse_tunnel_initiator.cc | 1 + .../reverse_tunnel_initiator_extension.h | 13 ++ .../rc_connection_wrapper_test.cc | 215 ++++++++++++++++++ ...reverse_tunnel_initiator_extension_test.cc | 28 +++ 9 files changed, 301 insertions(+), 1 deletion(-) diff --git a/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/BUILD b/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/BUILD index 5f552f08145ca..504c6c70514ac 100644 --- a/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/BUILD +++ b/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/BUILD @@ -5,5 +5,8 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") licenses(["notice"]) # Apache 2 api_proto_package( - deps = ["@xds//udpa/annotations:pkg"], + deps = [ + "//envoy/config/core/v3:pkg", + "@xds//udpa/annotations:pkg", + ], ) diff --git a/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/downstream_reverse_connection_socket_interface.proto b/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/downstream_reverse_connection_socket_interface.proto index 72994c07973c8..cb3ccb4c1d9c1 100644 --- a/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/downstream_reverse_connection_socket_interface.proto +++ b/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/downstream_reverse_connection_socket_interface.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package envoy.extensions.bootstrap.reverse_tunnel.downstream_socket_interface.v3; +import "envoy/config/core/v3/base.proto"; + import "udpa/annotations/status.proto"; option java_package = "io.envoyproxy.envoy.extensions.bootstrap.reverse_tunnel.downstream_socket_interface.v3"; @@ -22,6 +24,9 @@ message DownstreamReverseConnectionSocketInterface { // Request path used when issuing the HTTP reverse-connection handshake. Defaults to // "/reverse_connections/request". string request_path = 1; + + // Additional headers to include in the HTTP handshake request. + repeated config.core.v3.HeaderValueOption additional_headers = 2; } // Stat prefix to be used for downstream reverse connection socket interface stats. diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD index 5f7b16eed3cea..428cb5333787e 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD @@ -90,6 +90,7 @@ envoy_cc_library( "//source/common/upstream:load_balancer_context_base_lib", "//source/extensions/bootstrap/reverse_tunnel/common:reverse_connection_utility_lib", "//source/extensions/bootstrap/reverse_tunnel/common:rping_interceptor_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc index e35ffcc870f4e..985c5737027d7 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc @@ -139,6 +139,30 @@ std::string RCConnectionWrapper::connect(const std::string& src_tenant_id, headers->addCopy(cluster_hdr, std::string(cluster_id)); headers->addCopy(tenant_hdr, std::string(tenant_id)); headers->addCopy(upstream_cluster_hdr, cluster_name_); + using HeaderValueOption = envoy::config::core::v3::HeaderValueOption; + for (const auto& h : parent_.additionalHeaders()) { + const Http::LowerCaseString key(h.header().key()); + const auto& value = h.header().value(); + switch (h.append_action()) { + PANIC_ON_PROTO_ENUM_SENTINEL_VALUES; + case HeaderValueOption::APPEND_IF_EXISTS_OR_ADD: + headers->addCopy(key, value); + break; + case HeaderValueOption::ADD_IF_ABSENT: + if (headers->get(key).empty()) { + headers->addCopy(key, value); + } + break; + case HeaderValueOption::OVERWRITE_IF_EXISTS: + if (!headers->get(key).empty()) { + headers->setCopy(key, value); + } + break; + case HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD: + headers->setCopy(key, value); + break; + } + } headers->setContentLength(0); // Encode via HTTP/1 codec. diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h index e63ae65b1c6c4..49b628620e1be 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h @@ -5,6 +5,7 @@ #include #include +#include "envoy/config/core/v3/base.pb.h" #include "envoy/network/io_handle.h" #include "envoy/network/socket.h" #include "envoy/stats/scope.h" @@ -83,6 +84,8 @@ struct ReverseConnectionSocketConfig { std::string src_tenant_id; // Tenant identifier of local envoy instance. std::string request_path{ std::string(ReverseConnectionUtility::DEFAULT_REVERSE_TUNNEL_REQUEST_PATH)}; + std::vector + additional_headers; // Additional headers for the handshake request. // TODO(basundhara-c): Add support for multiple remote clusters using the same // ReverseConnectionIOHandle. Currently, each ReverseConnectionIOHandle handles // reverse connections for a single upstream cluster since a different ReverseConnectionAddress @@ -305,6 +308,13 @@ class ReverseConnectionIOHandle : public Network::IoSocketHandleImpl, */ const std::string& requestPath() const { return config_.request_path; } + /** + * @return reference to the additional headers for the handshake request. + */ + const std::vector& additionalHeaders() const { + return config_.additional_headers; + } + private: /** * Get time source for consistent time operations. diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator.cc b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator.cc index 7917b57516ebf..1f73dd4844fa1 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator.cc +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator.cc @@ -127,6 +127,7 @@ ReverseTunnelInitiator::socket(Envoy::Network::Socket::Type socket_type, socket_config.remote_clusters.push_back(cluster_config); if (extension_ != nullptr) { socket_config.request_path = extension_->handshakeRequestPath(); + socket_config.additional_headers = extension_->handshakeAdditionalHeaders(); } // Pass config directly to helper method. diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension.h b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension.h index ab322edd10bcf..ebf1734995de5 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension.h +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension.h @@ -44,6 +44,10 @@ class ReverseTunnelInitiatorExtension : public Server::BootstrapExtension, handshake_request_path_ = std::string(ReverseConnectionUtility::DEFAULT_REVERSE_TUNNEL_REQUEST_PATH); } + if (config.has_http_handshake()) { + additional_headers_ = {config.http_handshake().additional_headers().begin(), + config.http_handshake().additional_headers().end()}; + } ENVOY_LOG(debug, "ReverseTunnelInitiatorExtension: creating downstream reverse connection " "socket interface with stat_prefix: {}", @@ -105,6 +109,14 @@ class ReverseTunnelInitiatorExtension : public Server::BootstrapExtension, */ const std::string& handshakeRequestPath() const { return handshake_request_path_; } + /** + * @return reference to the additional headers to include in the handshake request. + */ + const std::vector& + handshakeAdditionalHeaders() const { + return additional_headers_; + } + /** * Increment handshake stats for reverse tunnel connections (per-worker only). * Only tracks stats if enable_detailed_stats flag is true. @@ -134,6 +146,7 @@ class ReverseTunnelInitiatorExtension : public Server::BootstrapExtension, std::string stat_prefix_; // Reverse connection stats prefix bool enable_detailed_stats_{false}; std::string handshake_request_path_; + std::vector additional_headers_; /** * Update per-worker connection stats for debugging purposes. diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc index e808003ff887b..4ed8ab1ef12c2 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc @@ -305,6 +305,221 @@ TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeWithCustomRequestPath) { EXPECT_NE(encoded_request.find("GET /custom/handshake HTTP/1.1"), std::string::npos); } +// Test RCConnectionWrapper::connect() includes additional headers in the handshake request. +TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeWithAdditionalHeaders) { + auto mock_connection = std::make_unique>(); + + EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); + EXPECT_CALL(*mock_connection, addReadFilter(_)); + EXPECT_CALL(*mock_connection, connect()); + EXPECT_CALL(*mock_connection, id()).WillRepeatedly(Return(12345)); + EXPECT_CALL(*mock_connection, state()).WillRepeatedly(Return(Network::Connection::State::Open)); + + auto mock_address = std::make_shared("192.168.1.1", 8080); + auto mock_local_address = std::make_shared("127.0.0.1", 12345); + + EXPECT_CALL(*mock_connection, connectionInfoProvider()) + .WillRepeatedly(Invoke([mock_address, + mock_local_address]() -> const Network::ConnectionInfoProvider& { + static auto mock_provider = + std::make_unique(mock_local_address, mock_address); + return *mock_provider; + })); + + Buffer::OwnedImpl captured_buffer; + EXPECT_CALL(*mock_connection, write(_, _)) + .WillOnce(Invoke([&captured_buffer](Buffer::Instance& buffer, bool) { + captured_buffer.add(buffer); + buffer.drain(buffer.length()); + })); + + auto mock_host = std::make_shared>(); + + ReverseConnectionSocketConfig custom_config = createDefaultTestConfig(); + envoy::config::core::v3::HeaderValueOption hdr1; + hdr1.mutable_header()->set_key("x-custom-auth"); + hdr1.mutable_header()->set_value("token123"); + custom_config.additional_headers.push_back(hdr1); + envoy::config::core::v3::HeaderValueOption hdr2; + hdr2.mutable_header()->set_key("x-request-id"); + hdr2.mutable_header()->set_value("abc-def"); + custom_config.additional_headers.push_back(hdr2); + auto local_io_handle = createTestIOHandle(custom_config); + + RCConnectionWrapper wrapper(*local_io_handle, std::move(mock_connection), mock_host, + "test-cluster"); + + wrapper.connect("test-tenant", "test-cluster", "test-node"); + + const std::string encoded_request = captured_buffer.toString(); + EXPECT_NE(encoded_request.find("x-custom-auth: token123"), std::string::npos); + EXPECT_NE(encoded_request.find("x-request-id: abc-def"), std::string::npos); +} + +// Test that additional headers with OVERWRITE_IF_EXISTS_OR_ADD replace existing headers. +TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeAdditionalHeadersOverwrite) { + auto mock_connection = std::make_unique>(); + + EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); + EXPECT_CALL(*mock_connection, addReadFilter(_)); + EXPECT_CALL(*mock_connection, connect()); + EXPECT_CALL(*mock_connection, id()).WillRepeatedly(Return(12345)); + EXPECT_CALL(*mock_connection, state()).WillRepeatedly(Return(Network::Connection::State::Open)); + + auto mock_address = std::make_shared("192.168.1.1", 8080); + auto mock_local_address = std::make_shared("127.0.0.1", 12345); + + EXPECT_CALL(*mock_connection, connectionInfoProvider()) + .WillRepeatedly(Invoke([mock_address, + mock_local_address]() -> const Network::ConnectionInfoProvider& { + static auto mock_provider = + std::make_unique(mock_local_address, mock_address); + return *mock_provider; + })); + + Buffer::OwnedImpl captured_buffer; + EXPECT_CALL(*mock_connection, write(_, _)) + .WillOnce(Invoke([&captured_buffer](Buffer::Instance& buffer, bool) { + captured_buffer.add(buffer); + buffer.drain(buffer.length()); + })); + + auto mock_host = std::make_shared>(); + + ReverseConnectionSocketConfig custom_config = createDefaultTestConfig(); + envoy::config::core::v3::HeaderValueOption hdr; + hdr.mutable_header()->set_key("host"); + hdr.mutable_header()->set_value("custom-host:9090"); + hdr.set_append_action(envoy::config::core::v3::HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD); + custom_config.additional_headers.push_back(hdr); + auto local_io_handle = createTestIOHandle(custom_config); + + RCConnectionWrapper wrapper(*local_io_handle, std::move(mock_connection), mock_host, + "test-cluster"); + + wrapper.connect("test-tenant", "test-cluster", "test-node"); + + const std::string encoded_request = captured_buffer.toString(); + // Verify the host was overwritten (not duplicated). + EXPECT_NE(encoded_request.find("host: custom-host:9090"), std::string::npos); + EXPECT_EQ(encoded_request.find("192.168.1.1:8080"), std::string::npos); +} + +// Test ADD_IF_ABSENT: header is added when absent, skipped when present. +TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeAdditionalHeadersAddIfAbsent) { + auto mock_connection = std::make_unique>(); + + EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); + EXPECT_CALL(*mock_connection, addReadFilter(_)); + EXPECT_CALL(*mock_connection, connect()); + EXPECT_CALL(*mock_connection, id()).WillRepeatedly(Return(12345)); + EXPECT_CALL(*mock_connection, state()).WillRepeatedly(Return(Network::Connection::State::Open)); + + auto mock_address = std::make_shared("192.168.1.1", 8080); + auto mock_local_address = std::make_shared("127.0.0.1", 12345); + + EXPECT_CALL(*mock_connection, connectionInfoProvider()) + .WillRepeatedly(Invoke([mock_address, + mock_local_address]() -> const Network::ConnectionInfoProvider& { + static auto mock_provider = + std::make_unique(mock_local_address, mock_address); + return *mock_provider; + })); + + Buffer::OwnedImpl captured_buffer; + EXPECT_CALL(*mock_connection, write(_, _)) + .WillOnce(Invoke([&captured_buffer](Buffer::Instance& buffer, bool) { + captured_buffer.add(buffer); + buffer.drain(buffer.length()); + })); + + auto mock_host = std::make_shared>(); + + ReverseConnectionSocketConfig custom_config = createDefaultTestConfig(); + // Try to add "host" with ADD_IF_ABSENT — should be skipped since host already exists. + envoy::config::core::v3::HeaderValueOption hdr1; + hdr1.mutable_header()->set_key("host"); + hdr1.mutable_header()->set_value("should-not-appear"); + hdr1.set_append_action(envoy::config::core::v3::HeaderValueOption::ADD_IF_ABSENT); + custom_config.additional_headers.push_back(hdr1); + // Add a new header with ADD_IF_ABSENT — should be added since it doesn't exist. + envoy::config::core::v3::HeaderValueOption hdr2; + hdr2.mutable_header()->set_key("x-new-header"); + hdr2.mutable_header()->set_value("new-value"); + hdr2.set_append_action(envoy::config::core::v3::HeaderValueOption::ADD_IF_ABSENT); + custom_config.additional_headers.push_back(hdr2); + auto local_io_handle = createTestIOHandle(custom_config); + + RCConnectionWrapper wrapper(*local_io_handle, std::move(mock_connection), mock_host, + "test-cluster"); + + wrapper.connect("test-tenant", "test-cluster", "test-node"); + + const std::string encoded_request = captured_buffer.toString(); + // "host" was already set, so ADD_IF_ABSENT should not add "should-not-appear". + EXPECT_EQ(encoded_request.find("should-not-appear"), std::string::npos); + // "x-new-header" was absent, so ADD_IF_ABSENT should add it. + EXPECT_NE(encoded_request.find("x-new-header: new-value"), std::string::npos); +} + +// Test OVERWRITE_IF_EXISTS: overwrites existing header, does nothing for absent header. +TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeAdditionalHeadersOverwriteIfExists) { + auto mock_connection = std::make_unique>(); + + EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); + EXPECT_CALL(*mock_connection, addReadFilter(_)); + EXPECT_CALL(*mock_connection, connect()); + EXPECT_CALL(*mock_connection, id()).WillRepeatedly(Return(12345)); + EXPECT_CALL(*mock_connection, state()).WillRepeatedly(Return(Network::Connection::State::Open)); + + auto mock_address = std::make_shared("192.168.1.1", 8080); + auto mock_local_address = std::make_shared("127.0.0.1", 12345); + + EXPECT_CALL(*mock_connection, connectionInfoProvider()) + .WillRepeatedly(Invoke([mock_address, + mock_local_address]() -> const Network::ConnectionInfoProvider& { + static auto mock_provider = + std::make_unique(mock_local_address, mock_address); + return *mock_provider; + })); + + Buffer::OwnedImpl captured_buffer; + EXPECT_CALL(*mock_connection, write(_, _)) + .WillOnce(Invoke([&captured_buffer](Buffer::Instance& buffer, bool) { + captured_buffer.add(buffer); + buffer.drain(buffer.length()); + })); + + auto mock_host = std::make_shared>(); + + ReverseConnectionSocketConfig custom_config = createDefaultTestConfig(); + // Overwrite "host" which exists — should replace the value. + envoy::config::core::v3::HeaderValueOption hdr1; + hdr1.mutable_header()->set_key("host"); + hdr1.mutable_header()->set_value("overwritten-host"); + hdr1.set_append_action(envoy::config::core::v3::HeaderValueOption::OVERWRITE_IF_EXISTS); + custom_config.additional_headers.push_back(hdr1); + // Try to overwrite "x-nonexistent" which doesn't exist — should be a no-op. + envoy::config::core::v3::HeaderValueOption hdr2; + hdr2.mutable_header()->set_key("x-nonexistent"); + hdr2.mutable_header()->set_value("should-not-appear"); + hdr2.set_append_action(envoy::config::core::v3::HeaderValueOption::OVERWRITE_IF_EXISTS); + custom_config.additional_headers.push_back(hdr2); + auto local_io_handle = createTestIOHandle(custom_config); + + RCConnectionWrapper wrapper(*local_io_handle, std::move(mock_connection), mock_host, + "test-cluster"); + + wrapper.connect("test-tenant", "test-cluster", "test-node"); + + const std::string encoded_request = captured_buffer.toString(); + // "host" existed, so OVERWRITE_IF_EXISTS should replace it. + EXPECT_NE(encoded_request.find("host: overwritten-host"), std::string::npos); + EXPECT_EQ(encoded_request.find("192.168.1.1:8080"), std::string::npos); + // "x-nonexistent" didn't exist, so OVERWRITE_IF_EXISTS should not add it. + EXPECT_EQ(encoded_request.find("should-not-appear"), std::string::npos); +} + // Test RCConnectionWrapper::connect() method with connection write failure. TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeWriteFailure) { // Create a mock connection that fails to write. diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc index ad7fc6b72d074..3f111d79c9c8f 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc @@ -143,6 +143,34 @@ TEST_F(ReverseTunnelInitiatorExtensionTest, HandshakeRequestPathOverride) { EXPECT_EQ(custom_extension->handshakeRequestPath(), "/custom/handshake"); } +TEST_F(ReverseTunnelInitiatorExtensionTest, AdditionalHeadersDefaults) { + EXPECT_TRUE(extension_->handshakeAdditionalHeaders().empty()); +} + +TEST_F(ReverseTunnelInitiatorExtensionTest, AdditionalHeadersOverride) { + auto custom_config = config_; + auto* hdr1 = custom_config.mutable_http_handshake()->add_additional_headers(); + hdr1->mutable_header()->set_key("x-custom-auth"); + hdr1->mutable_header()->set_value("token123"); + hdr1->set_append_action(envoy::config::core::v3::HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD); + auto* hdr2 = custom_config.mutable_http_handshake()->add_additional_headers(); + hdr2->mutable_header()->set_key("x-request-id"); + hdr2->mutable_header()->set_value("abc-def"); + // hdr2 defaults to APPEND_IF_EXISTS_OR_ADD (proto default = 0). + auto custom_extension = + std::make_unique(context_, custom_config); + const auto& headers = custom_extension->handshakeAdditionalHeaders(); + ASSERT_EQ(headers.size(), 2); + EXPECT_EQ(headers[0].header().key(), "x-custom-auth"); + EXPECT_EQ(headers[0].header().value(), "token123"); + EXPECT_EQ(headers[0].append_action(), + envoy::config::core::v3::HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD); + EXPECT_EQ(headers[1].header().key(), "x-request-id"); + EXPECT_EQ(headers[1].header().value(), "abc-def"); + EXPECT_EQ(headers[1].append_action(), + envoy::config::core::v3::HeaderValueOption::APPEND_IF_EXISTS_OR_ADD); +} + TEST_F(ReverseTunnelInitiatorExtensionTest, OnServerInitialized) { // This should be a no-op. extension_->onServerInitialized(server_); From eb1e7ebc8f27fb3aaca68b64584b214312cc25cd Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Wed, 15 Apr 2026 10:43:41 -0400 Subject: [PATCH 026/750] ci: harden CI workflows against shell injection (#44470) Replace all `${{ }}` expression interpolation inside `run:` blocks with environment variable references passed through `env:` blocks. This prevents attacker-controlled values (e.g. from .github/config.yml) from being interpreted as shell syntax. Changes: - _publish_release.yml: move ENVOY_COMMIT/ENVOY_REPO/ENVOY_PUBLISH_DRY_RUN from inline source shell code to _run.yml env block - _run.yml: replace ${{ runner.temp }} and ${{ vars.ENVOY_CI_BAZELRC }} with env var references - _request_cache_bazel.yml: replace ${{ inputs.* }} in command block with env var references - _load.yml: replace ${{ inputs.run-id }} and ${{ github.repository }} with env var references - ci/envoy_build_sha.sh: add allowlist validation for repo, sha, and tag values extracted from config.yml Signed-off-by: Jonh Wendell Co-authored-by: Claude Opus 4.6 --- .github/workflows/_load.yml | 6 ++++-- .github/workflows/_publish_release.yml | 4 ---- .github/workflows/_request_cache_bazel.yml | 9 ++++++--- .github/workflows/_run.yml | 9 +++++++-- ci/envoy_build_sha.sh | 15 ++++++++++++++- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index dc8c9a811832c..efab1c91ed79e 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -87,14 +87,16 @@ jobs: gh api \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - /repos/${{ github.repository }}/actions/runs/${{ inputs.run-id }} \ + "/repos/${GH_REPO}/actions/runs/${RUN_ID}" \ | jq '.' - RUNID=$(gh run view ${{ inputs.run-id }} --repo ${{ github.repository }} --json databaseId | jq -r '.databaseId') + RUNID=$(gh run view "${RUN_ID}" --repo "${GH_REPO}" --json databaseId | jq -r '.databaseId') echo "value=${RUNID}" >> "$GITHUB_OUTPUT" id: run-id if: ${{ inputs.runs-after == true }} env: GH_TOKEN: ${{ github.token }} + RUN_ID: ${{ inputs.run-id }} + GH_REPO: ${{ github.repository }} # Load env data # Handle any failure in triggering job diff --git a/.github/workflows/_publish_release.yml b/.github/workflows/_publish_release.yml index 6a82a20ed3e0d..97dbc628bac77 100644 --- a/.github/workflows/_publish_release.yml +++ b/.github/workflows/_publish_release.yml @@ -121,10 +121,6 @@ jobs: include: - target: publish name: github - source: | - export ENVOY_COMMIT=${{ fromJSON(inputs.request).request.sha }} - export ENVOY_REPO=${{ github.repository }} - export ENVOY_PUBLISH_DRY_RUN=${{ (fromJSON(inputs.request).request.version.dev || ! inputs.trusted) && 1 || '' }} docs: # For normal commits to Envoy main this will trigger an update in the website repo, diff --git a/.github/workflows/_request_cache_bazel.yml b/.github/workflows/_request_cache_bazel.yml index 47cdf21971f36..01f3e503f872d 100644 --- a/.github/workflows/_request_cache_bazel.yml +++ b/.github/workflows/_request_cache_bazel.yml @@ -109,9 +109,9 @@ jobs: cd /source export BAZEL_BUILD_EXTRA_OPTIONS="--config=ci --config=rbe" export ENVOY_CACHE_ROOT=/build/bazel_root - export ENVOY_CACHE_OUTPUT_BASE="${{ inputs.output-base }}" - export ENVOY_CACHE_TARGETS=$(echo "${{ inputs.targets }}" | sed 's/ / + /g') - export ENVOY_CACHE_WORKING_DIR="${{ inputs.working-dir }}" + export ENVOY_CACHE_OUTPUT_BASE="${INPUT_OUTPUT_BASE}" + export ENVOY_CACHE_TARGETS=$(echo "${INPUT_TARGETS}" | sed 's/ / + /g') + export ENVOY_CACHE_WORKING_DIR="${INPUT_WORKING_DIR}" # ironically the repository_cache is just about the only thing you dont want to cache export ENVOY_REPOSITORY_CACHE=/tmp/cache ./ci/do_ci.sh cache-create @@ -128,3 +128,6 @@ jobs: run-as-sudo: false env: GITHUB_TOKEN: ${{ github.token }} + INPUT_OUTPUT_BASE: ${{ inputs.output-base }} + INPUT_TARGETS: ${{ inputs.targets }} + INPUT_WORKING_DIR: ${{ inputs.working-dir }} diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 0d401d0ea24ed..ffe7433a1c296 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -241,7 +241,7 @@ jobs: | . * {$config, $check} - run: | - mkdir ${{ runner.temp }}/container + mkdir "${RUNNER_TEMP}/container" MNT_AVAILABLE=false if mountpoint -q /mnt; then MNT_AVAILABLE=true @@ -380,9 +380,11 @@ jobs: if: >- ${{ fromJSON(inputs.request).request.pr != '' }} - run: | - echo "${{ vars.ENVOY_CI_BAZELRC }}" > repo.bazelrc + echo "${BAZELRC_CONTENT}" > repo.bazelrc if: ${{ vars.ENVOY_CI_BAZELRC }} name: Configure repo Bazel settings + env: + BAZELRC_CONTENT: ${{ vars.ENVOY_CI_BAZELRC }} # NOTE: This is where untrusted code can be run!!! # It MUST be the last step in the workflow @@ -438,3 +440,6 @@ jobs: MOUNT_GPG_HOME: ${{ inputs.import-gpg && 1 || '' }} ENVOY_DOCKER_CPUS: ${{ inputs.docker-cpus }} ENVOY_DOCKER_CI: ${{ inputs.docker-ci && 'true' || '' }} + ENVOY_COMMIT: ${{ fromJSON(inputs.request).request.sha }} + ENVOY_REPO: ${{ github.repository }} + ENVOY_PUBLISH_DRY_RUN: ${{ (fromJSON(inputs.request).request.version.dev || ! inputs.trusted) && 1 || '' }} diff --git a/ci/envoy_build_sha.sh b/ci/envoy_build_sha.sh index 5335338b7db99..d507c88e1e030 100644 --- a/ci/envoy_build_sha.sh +++ b/ci/envoy_build_sha.sh @@ -36,10 +36,23 @@ case $ENVOY_BUILD_VARIANT in ;; esac +# Validate extracted values contain only expected characters +if [[ -n "$BUILD_REPO" && ! "$BUILD_REPO" =~ ^[a-zA-Z0-9._:/-]+$ ]]; then + echo "Error: build-image.repo contains invalid characters" + exit 1 +fi +if [[ -n "$BUILD_SHA" && ! "$BUILD_SHA" =~ ^[a-fA-F0-9]+$ ]]; then + echo "Error: build-image.sha contains invalid characters" + exit 1 +fi +if [[ -n "$BUILD_TAG" && ! "$BUILD_TAG" =~ ^[a-fA-F0-9]+$ ]]; then + echo "Error: build-image.tag contains invalid characters" + exit 1 +fi + # shellcheck disable=SC2034 BUILD_CONTAINER="${BUILD_REPO}@sha256:${BUILD_SHA}" - if [[ -z "$BUILD_REPO" || -z "$BUILD_SHA" || -z "$BUILD_TAG" ]]; then echo "Error: Missing repo, sha, or tag values in .github/config.yml" exit 1 From 9d294057a5b7cb3af7eeb7bbfd794c50ff562a3a Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 15 Apr 2026 17:08:25 +0100 Subject: [PATCH 027/750] deps/ci: Bump toolshed actions -> 0.4.6 (#44473) Signed-off-by: Ryan Northey --- .github/workflows/_check_coverage.yml | 4 +-- .github/workflows/_cve_fetch.yml | 2 +- .github/workflows/_cve_scan.yml | 2 +- .github/workflows/_finish.yml | 8 ++--- .github/workflows/_load.yml | 10 +++--- .github/workflows/_load_env.yml | 8 ++--- .github/workflows/_precheck_publish.yml | 2 +- .github/workflows/_publish_release.yml | 4 +-- .../workflows/_publish_release_container.yml | 6 ++-- .github/workflows/_publish_verify.yml | 2 +- .github/workflows/_request.yml | 24 ++++++------- .github/workflows/_request_cache_bazel.yml | 10 +++--- .github/workflows/_request_cache_docker.yml | 10 +++--- .github/workflows/_request_checks.yml | 10 +++--- .github/workflows/_run.yml | 28 +++++++-------- .github/workflows/_upload_gcs.yml | 6 ++-- .github/workflows/codeql-daily.yml | 4 +-- .github/workflows/codeql-push.yml | 4 +-- .github/workflows/command.yml | 6 ++-- .github/workflows/envoy-dependency.yml | 18 +++++----- .github/workflows/envoy-release.yml | 34 +++++++++---------- .github/workflows/envoy-sync.yml | 4 +-- .github/workflows/mobile-android_build.yml | 12 +++---- .github/workflows/mobile-ios_build.yml | 4 +-- 24 files changed, 111 insertions(+), 111 deletions(-) diff --git a/.github/workflows/_check_coverage.yml b/.github/workflows/_check_coverage.yml index 310395dfc9ee3..090ff4a8b9264 100644 --- a/.github/workflows/_check_coverage.yml +++ b/.github/workflows/_check_coverage.yml @@ -58,7 +58,7 @@ jobs: upload-name: coverage upload-path: generated/coverage/html steps-post: | - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: output-path: generated/coverage/html/gcs-metadata.json input-format: yaml @@ -79,7 +79,7 @@ jobs: - target: fuzz_coverage name: Fuzz coverage steps-post: | - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: output-path: generated/fuzz_coverage/html/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_cve_fetch.yml b/.github/workflows/_cve_fetch.yml index c8e63432f1752..016136242ff8b 100644 --- a/.github/workflows/_cve_fetch.yml +++ b/.github/workflows/_cve_fetch.yml @@ -35,7 +35,7 @@ jobs: else echo "weekly_run=false" >> $GITHUB_OUTPUT fi - - uses: envoyproxy/toolshed/actions/gcp/setup@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/gcp/setup@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_cve_scan.yml b/.github/workflows/_cve_scan.yml index 58237c3d511a0..c59b8beb5dad5 100644 --- a/.github/workflows/_cve_scan.yml +++ b/.github/workflows/_cve_scan.yml @@ -28,7 +28,7 @@ jobs: id: vars run: | echo "cve-data-path=${{ inputs.cve-data-path }}" > $GITHUB_OUTPUT - - uses: envoyproxy/toolshed/actions/gcp/setup@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/gcp/setup@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_finish.yml b/.github/workflows/_finish.yml index 99d3b5da5e7f8..1efa76fafe928 100644 --- a/.github/workflows/_finish.yml +++ b/.github/workflows/_finish.yml @@ -36,7 +36,7 @@ jobs: actions: read contents: read steps: - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Incoming data id: needs with: @@ -87,7 +87,7 @@ jobs: summary: "Check has finished", text: $text}}}} - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Print summary with: input: ${{ toJSON(steps.needs.outputs.value).summary-title }} @@ -95,13 +95,13 @@ jobs: "## \(.)" options: -Rr output-path: GITHUB_STEP_SUMMARY - - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/checks@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Update check with: action: update diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index efab1c91ed79e..989bde97cd3db 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -102,7 +102,7 @@ jobs: # Handle any failure in triggering job # Remove any `checks` we dont care about # Prepare a check request - - uses: envoyproxy/toolshed/actions/github/env/load@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/env/load@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Load env id: data with: @@ -113,13 +113,13 @@ jobs: GH_TOKEN: ${{ github.token }} # Update the check - - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/checks@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Update check if: ${{ fromJSON(steps.data.outputs.data).data.check.action == 'RUN' }} with: @@ -127,7 +127,7 @@ jobs: checks: ${{ toJSON(fromJSON(steps.data.outputs.data).checks) }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Print request summary with: input: | @@ -147,7 +147,7 @@ jobs: | $summary.summary as $summary | "${{ inputs.template-request-summary }}" - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: request-output name: Load request with: diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index 71c28027ac140..8422dddd14532 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -63,18 +63,18 @@ jobs: request: ${{ steps.env.outputs.data }} trusted: true steps: - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: checkout name: Checkout Envoy repository - name: Generate environment variables - uses: envoyproxy/toolshed/actions/envoy/ci/env@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/envoy/ci/env@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: env with: branch-name: ${{ inputs.branch-name }} @@ -86,7 +86,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/env/summary@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} diff --git a/.github/workflows/_precheck_publish.yml b/.github/workflows/_precheck_publish.yml index b750b5bc493a9..fb346c37b7793 100644 --- a/.github/workflows/_precheck_publish.yml +++ b/.github/workflows/_precheck_publish.yml @@ -85,7 +85,7 @@ jobs: upload-name: docs upload-path: generated/docs steps-post: | - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: output-path: generated/docs/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_publish_release.yml b/.github/workflows/_publish_release.yml index 97dbc628bac77..37a8507adf064 100644 --- a/.github/workflows/_publish_release.yml +++ b/.github/workflows/_publish_release.yml @@ -135,12 +135,12 @@ jobs: needs: - release steps: - - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/actions/dispatch@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/dispatch@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: ref: main repository: ${{ fromJSON(inputs.request).request.version.dev && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} diff --git a/.github/workflows/_publish_release_container.yml b/.github/workflows/_publish_release_container.yml index 1c35d097fc9bd..0979fb032b5e4 100644 --- a/.github/workflows/_publish_release_container.yml +++ b/.github/workflows/_publish_release_container.yml @@ -56,7 +56,7 @@ jobs: - name: Generate manifest configuration (dev) id: dev-config if: ${{ inputs.dev && inputs.target-branch == 'main' }} - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: input-format: yaml filter: >- @@ -135,7 +135,7 @@ jobs: - tools-dev-${{ github.sha }} - name: Generate manifest configuration (release) - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: release-config if: ${{ ! inputs.dev || ! inputs.target-branch != 'main' }} with: @@ -225,7 +225,7 @@ jobs: - tools-v${{ inputs.version-major }}.${{ inputs.version-minor }}-latest - name: Collect and push OCI artifacts - uses: envoyproxy/toolshed/actions/oci/collector@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/oci/collector@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: artifacts-pattern: oci.* manifest-config: ${{ steps.dev-config.outputs.value || steps.release-config.outputs.value }} diff --git a/.github/workflows/_publish_verify.yml b/.github/workflows/_publish_verify.yml index 283e1e1d60435..61dd96ca92846 100644 --- a/.github/workflows/_publish_verify.yml +++ b/.github/workflows/_publish_verify.yml @@ -108,7 +108,7 @@ jobs: export NO_BUILD_SETUP=1 steps-pre: | - id: version-support - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: input: | version_major: ${{ fromJSON(inputs.request).request.version.major }} diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index 4798ca2889e4c..b2272cb63abd5 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -55,14 +55,14 @@ jobs: caches: ${{ steps.caches.outputs.value }} config: ${{ steps.config.outputs.config }} steps: - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: checkout name: Checkout Envoy repository (requested) with: @@ -76,7 +76,7 @@ jobs: # *ALL* variables collected should be treated as untrusted and should be sanitized before # use - name: Generate environment variables from commit - uses: envoyproxy/toolshed/actions/envoy/ci/request@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/envoy/ci/request@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: env with: branch-name: ${{ steps.checkout.outputs.branch-name }} @@ -87,7 +87,7 @@ jobs: vars: ${{ toJSON(vars) }} working-directory: requested - - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -95,7 +95,7 @@ jobs: config: | fetch-depth: 1 path: target - - uses: envoyproxy/toolshed/actions/hashfiles@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/hashfiles@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: bazel-cache-hash name: Bazel cache hash with: @@ -104,7 +104,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/env/summary@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} @@ -120,28 +120,28 @@ jobs: target-branch: ${{ fromJSON(steps.env.outputs.data).request.target-branch }} - id: cache-id-bazel-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-arm64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-arm64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-docs-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-docs-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-external-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-external-x64 wf-path: .github/workflows/request.yml - name: Environment data - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: data with: input: | @@ -189,7 +189,7 @@ jobs: path: /tmp/cache key: ${{ fromJSON(steps.data.outputs.value).request.build-image.default }}-arm64 - name: Caches - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: caches with: input-format: yaml diff --git a/.github/workflows/_request_cache_bazel.yml b/.github/workflows/_request_cache_bazel.yml index 01f3e503f872d..bf7e10462243e 100644 --- a/.github/workflows/_request_cache_bazel.yml +++ b/.github/workflows/_request_cache_bazel.yml @@ -59,7 +59,7 @@ jobs: && ! fromJSON(inputs.caches).bazel[format('{0}-{1}', inputs.output-base, inputs.arch)]) }} steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/bind-mounts@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: mounts: | - src: /mnt/workspace @@ -73,9 +73,9 @@ jobs: target: /build chown: "runner:docker" - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/diskspace@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 if: inputs.arch == 'x64' && github.event.repository.private - - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -83,14 +83,14 @@ jobs: config: | fetch-depth: 1 - - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/cache/prime@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/cache/prime@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: bazel-cache name: Prime Bazel cache with: diff --git a/.github/workflows/_request_cache_docker.yml b/.github/workflows/_request_cache_docker.yml index 751dacba5d6fe..1e3664f255b0b 100644 --- a/.github/workflows/_request_cache_docker.yml +++ b/.github/workflows/_request_cache_docker.yml @@ -39,7 +39,7 @@ on: # For a job that does, you can restore with something like: # # steps: -# - uses: envoyproxy/toolshed/actions/docker/cache/restore@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 +# - uses: envoyproxy/toolshed/actions/docker/cache/restore@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 # with: # key: "${{ needs.env.outputs.build-image }}" # @@ -51,13 +51,13 @@ jobs: name: "[${{ inputs.arch }}] Prime Docker cache" if: ${{ ! fromJSON(inputs.caches).docker[inputs.arch] }} steps: - - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/docker/cache/prime@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/docker/cache/prime@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: docker name: Prime Docker cache (${{ inputs.image-tag }}${{ inputs.cache-suffix }}) with: @@ -65,7 +65,7 @@ jobs: key-suffix: ${{ inputs.cache-suffix }} lock-token: ${{ steps.appauth.outputs.token }} lock-repository: ${{ inputs.lock-repository }} - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: data name: Cache data with: @@ -73,7 +73,7 @@ jobs: input: | cached: ${{ steps.docker.outputs.cached }} key: ${{ inputs.image-tag }}${{ inputs.cache-suffix }} - - uses: envoyproxy/toolshed/actions/json/table@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/json/table@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Summary with: json: ${{ steps.data.outputs.value }} diff --git a/.github/workflows/_request_checks.yml b/.github/workflows/_request_checks.yml index c663eeb4b3ead..0a3a8c87c8740 100644 --- a/.github/workflows/_request_checks.yml +++ b/.github/workflows/_request_checks.yml @@ -55,7 +55,7 @@ jobs: runs-on: ${{ fromJSON(inputs.env).config.ci.agent-ubuntu }} name: Start checks steps: - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: check-config name: Prepare check data with: @@ -78,13 +78,13 @@ jobs: | .skipped.output.summary = "${{ inputs.skipped-summary }}" | .skipped.output.text = "" - - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/checks@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Start checks id: checks with: @@ -95,7 +95,7 @@ jobs: ${{ fromJSON(inputs.env).summary.summary }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/json/table@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/json/table@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Summary with: collapse-open: true @@ -119,7 +119,7 @@ jobs: output-path: GITHUB_STEP_SUMMARY title: Checks started/skipped - - uses: envoyproxy/toolshed/actions/github/env/save@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/env/save@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Save env id: data with: diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index ffe7433a1c296..a09c8da32a9c0 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -143,7 +143,7 @@ on: summary-post: type: string default: | - - uses: envoyproxy/toolshed/actions/envoy/run/summary@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/envoy/run/summary@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: context: %{{ inputs.context }} steps-pre: @@ -212,7 +212,7 @@ jobs: name: ${{ inputs.target-suffix && format('[{0}] ', inputs.target-suffix) || '' }}${{ inputs.command }} ${{ inputs.target }} timeout-minutes: ${{ inputs.timeout-minutes }} steps: - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: started name: Create timestamp with: @@ -220,7 +220,7 @@ jobs: filter: | now # This controls which input vars are exposed to the run action (and related steps) - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Context id: context with: @@ -253,14 +253,14 @@ jobs: fi echo "mnt-available=$MNT_AVAILABLE" >> "$GITHUB_OUTPUT" id: disk - - uses: envoyproxy/toolshed/actions/github/remnt@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/remnt@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 if: steps.disk.outputs.should-remnt == 'true' - - uses: envoyproxy/toolshed/actions/bind-mounts@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/bind-mounts@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 if: inputs.bind-mount && steps.disk.outputs.mnt-available == 'true' with: mounts: ${{ inputs.bind-mounts }} - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/diskspace@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 if: inputs.diskspace-hack || steps.disk.outputs.mnt-available != 'true' with: to_remove: ${{ inputs.diskspace-hack-paths }} @@ -268,7 +268,7 @@ jobs: mount df -h - - uses: envoyproxy/toolshed/actions/bson@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/bson@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Configure Docker if: runner.os == 'Linux' with: @@ -284,7 +284,7 @@ jobs: | "${{ inputs.template-docker-configure }}" # Caches - - uses: envoyproxy/toolshed/actions/cache/restore@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/cache/restore@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 if: >- fromJSON(inputs.bazel-cache) name: >- @@ -325,12 +325,12 @@ jobs: key: ${{ inputs.cache-build-image }}${{ inputs.cache-build-image-key-suffix }} - if: ${{ inputs.cache-build-image && steps.cache-lookup.outputs.cache-hit == 'true' }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/actions/docker/cache/restore@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/docker/cache/restore@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: image-tag: ${{ inputs.cache-build-image }} key-suffix: ${{ inputs.cache-build-image-key-suffix }} - - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: appauth name: Appauth if: ${{ inputs.trusted }} @@ -341,7 +341,7 @@ jobs: # - the workaround is to allow the token to be passed through. token: ${{ github.token }} token-ok: true - - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: checkout name: Checkout Envoy repository with: @@ -357,7 +357,7 @@ jobs: token: ${{ inputs.trusted && steps.appauth.outputs.token || github.token }} # This is currently only use by mobile-docs and can be removed once they are updated to the newer website - - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: checkout-extra name: Checkout extra repository (for publishing) if: ${{ inputs.checkout-extra }} @@ -366,7 +366,7 @@ jobs: ssh-key: ${{ inputs.trusted && inputs.ssh-key-extra || '' }} - name: Import GPG key - uses: envoyproxy/toolshed/actions/gpg/import@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/gpg/import@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 if: ${{ inputs.import-gpg }} with: key: ${{ secrets.gpg-key }} @@ -388,7 +388,7 @@ jobs: # NOTE: This is where untrusted code can be run!!! # It MUST be the last step in the workflow - - uses: envoyproxy/toolshed/actions/github/run@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/run@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: args: ${{ inputs.args != '--' && inputs.args || inputs.target }} diff --git a/.github/workflows/_upload_gcs.yml b/.github/workflows/_upload_gcs.yml index 9662dd8dd9566..5535bebda962d 100644 --- a/.github/workflows/_upload_gcs.yml +++ b/.github/workflows/_upload_gcs.yml @@ -29,17 +29,17 @@ jobs: with: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} - - uses: envoyproxy/toolshed/actions/jq@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: metadata with: input: ${{ matrix.artifact }}/gcs-metadata.json input-format: json-path - run: | rm -rf ${{ matrix.artifact }}/gcs-metadata.json - - uses: envoyproxy/toolshed/actions/gcp/setup@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/gcp/setup@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: key: ${{ secrets.gcp-key }} - - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: bucket: ${{ fromJSON(steps.metadata.outputs.value).bucket }} path: ${{ matrix.artifact }} diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index a802944b3ba6b..982d1d79f123a 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -27,7 +27,7 @@ jobs: steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/bind-mounts@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 if: | ! github.event.repository.private with: @@ -42,7 +42,7 @@ jobs: if: | env.BUILD_TARGETS != '' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/diskspace@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 396806a486505..0155f90ec99c8 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-22.04 if: github.repository == 'envoyproxy/envoy' steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/bind-mounts@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 if: | ! github.event.repository.private with: @@ -48,7 +48,7 @@ jobs: if: | env.BUILD_TARGETS != '' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/diskspace@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/command.yml b/.github/workflows/command.yml index c42f30a20d442..30466cae04104 100644 --- a/.github/workflows/command.yml +++ b/.github/workflows/command.yml @@ -28,7 +28,7 @@ jobs: && github.actor != 'dependabot[bot]' }} steps: - - uses: envoyproxy/toolshed/actions/github/command@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/command@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Parse command from comment id: command with: @@ -37,14 +37,14 @@ jobs: ^/(retest) # /retest - - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 if: ${{ steps.command.outputs.command == 'retest' }} id: appauth-retest name: Appauth (retest) with: key: ${{ secrets.ENVOY_CI_APP_KEY }} app_id: ${{ secrets.ENVOY_CI_APP_ID }} - - uses: envoyproxy/toolshed/actions/retest@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/retest@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 if: ${{ steps.command.outputs.command == 'retest' }} name: Retest with: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 691ba7f760891..4f66e79aa33ee 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -53,16 +53,16 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/bson@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/bson@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: update name: Update dependency (${{ inputs.dependency }}) with: @@ -97,13 +97,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/actions/upload/diff@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/upload/diff@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.update.outputs.output }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/actions/github/pr@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/pr@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: base: main body: | @@ -134,11 +134,11 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: checkout name: Checkout Envoy repository with: @@ -184,7 +184,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/actions/docker/shas@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/docker/shas@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: images: | sha-ci: docker.io/envoyproxy/envoy-build:ci-${{ steps.build-tools.outputs.tag }} @@ -222,7 +222,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/pr@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index fd6be0dc4530e..f4008c077c650 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -60,14 +60,14 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} @@ -88,10 +88,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/actions/email/validate@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/email/validate@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/actions/github/run@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/run@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Create release with: source: | @@ -116,7 +116,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/pr@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: base: ${{ github.ref_name }} commit: false @@ -142,19 +142,19 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/run@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Re-open branch with: command: >- @@ -169,7 +169,7 @@ jobs: name: Dev version id: dev - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/pr@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: base: ${{ github.ref_name }} commit: false @@ -193,20 +193,20 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/github/run@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 name: Sync version histories with: command: >- @@ -216,7 +216,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/pr@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: append-commit-message: true base: ${{ github.ref_name }} @@ -243,13 +243,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: config: | fetch-depth: 0 @@ -279,13 +279,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - name: Checkout repository - uses: envoyproxy/toolshed/actions/github/checkout@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index c780f59df734d..a45681a85a30a 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -32,12 +32,12 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/actions/appauth@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/actions/dispatch@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/dispatch@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index 55696ada7559f..3e6752ee89e7e 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -103,9 +103,9 @@ jobs: target: kotlin-hello-world runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/envoy/android/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: apk: bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity @@ -146,7 +146,7 @@ jobs: target: ${{ matrix.target }} runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 steps-post: ${{ matrix.steps-post }} timeout-minutes: 50 trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} @@ -157,7 +157,7 @@ jobs: include: - name: java-hello-world steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/envoy/android/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: apk: bazel-bin/examples/java/hello_world/hello_envoy.apk app: io.envoyproxy.envoymobile.helloenvoy/.MainActivity @@ -180,7 +180,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/baseline:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/envoy/android/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: apk: bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity @@ -197,7 +197,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/experimental:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + - uses: envoyproxy/toolshed/actions/envoy/android/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 with: apk: bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index d717cda047cc5..4efe861bea185 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -101,7 +101,7 @@ jobs: # source ./ci/mac_ci_setup.sh # bazel shutdown # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} @@ -146,7 +146,7 @@ jobs: # source: | # source ./ci/mac_ci_setup.sh # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@8d5d8d4b9eeb5e4e76b92341b0b1b1f6438af231 # v0.4.5 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} From 4bc5745fef856660a68f45f38f3b419471f3acfa Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 15 Apr 2026 12:59:03 -0400 Subject: [PATCH 028/750] Build rate limit descriptors from cluster locality metadata (#44359) Add option for creating rate limit descriptors from cluster locality metadata. Risk Level: low (new config option) Testing: unit tests Docs Changes: yes Release Notes: yes Platform Specific Features: no Signed-off-by: Yan Avlasov --- .../config/route/v3/route_components.proto | 7 ++ changelogs/current.yaml | 4 + source/common/router/router_ratelimit.cc | 13 +++ test/common/router/router_ratelimit_test.cc | 92 ++++++++++++++++++ .../ratelimit/ratelimit_integration_test.cc | 94 +++++++++++++++++-- 5 files changed, 202 insertions(+), 8 deletions(-) diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index a5d6687f96248..f58349caa3c74 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -2395,6 +2395,13 @@ message RateLimit { // Query :ref:`cluster metadata ` CLUSTER_ENTRY = 2; + + // Query :ref:`cluster locality metadata ` + // Cluster locality metadata is available after upstream host selection only. To populate descriptors + // with cluster locality metadata it needs to be have the + // :ref:`apply_on_stream_done field ` + // set to ``true`` or host selection completed before the rate limit filter is executed. + CLUSTER_LOCALITY_ENTRY = 3; } // The key to use in the descriptor entry. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2658b1bee44b5..4321951cd4e1e 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1015,6 +1015,10 @@ new_features: - area: ratelimit change: | Support populating rate limit descriptors from cluster metadata. +- area: ratelimit + change: | + Support populating rate limit descriptors from cluster locality metadata. + - area: contrib change: | Added a new :ref:`Kafka stats sink ` contrib extension diff --git a/source/common/router/router_ratelimit.cc b/source/common/router/router_ratelimit.cc index 2065cc5ad7605..27229806ee14c 100644 --- a/source/common/router/router_ratelimit.cc +++ b/source/common/router/router_ratelimit.cc @@ -217,6 +217,19 @@ bool MetaDataAction::populateDescriptor(RateLimit::DescriptorEntry& descriptor_e metadata_source = cluster_info.has_value() ? &cluster_info.ref().metadata() : nullptr; break; } + case envoy::config::route::v3::RateLimit::Action::MetaData::CLUSTER_LOCALITY_ENTRY: { + const auto upstream_info = info.upstreamInfo(); + // Upstream host is only available after upstream host selection. + // This means that the cluster locality metadata can only be used on the + // response path (apply_on_stream_done is set to true) or in a scenario where host selection is + // done before the rate limit action is executed. + if (upstream_info.has_value() && upstream_info->upstreamHost()) { + metadata_source = upstream_info->upstreamHost()->localityMetadata().get(); + } else { + metadata_source = nullptr; + } + break; + } } const std::string metadata_string_value = diff --git a/test/common/router/router_ratelimit_test.cc b/test/common/router/router_ratelimit_test.cc index 5ecd87f80f688..cf936248c432b 100644 --- a/test/common/router/router_ratelimit_test.cc +++ b/test/common/router/router_ratelimit_test.cc @@ -16,6 +16,7 @@ #include "test/mocks/ratelimit/mocks.h" #include "test/mocks/router/mocks.h" #include "test/mocks/server/instance.h" +#include "test/mocks/stream_info/mocks.h" #include "test/test_common/printers.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" @@ -809,6 +810,97 @@ TEST_F(RateLimitPolicyEntryTest, MetaDataMatchClusterEntrySourceMissingMetadata) EXPECT_TRUE(descriptors_[0].entries_.empty()); } +TEST_F(RateLimitPolicyEntryTest, MetaDataMatchClusterLocalitySource) { + const std::string yaml = R"EOF( +actions: +- metadata: + descriptor_key: fake_key + default_value: fake_value + metadata_key: + key: 'envoy.xxx' + path: + - key: test + - key: prop + source: CLUSTER_LOCALITY_ENTRY + )EOF"; + + setupTest(yaml); + + std::string metadata_yaml = R"EOF( +filter_metadata: + envoy.xxx: + test: + prop: foo + )EOF"; + + auto locality_metadata = std::make_shared(); + TestUtility::loadFromYaml(metadata_yaml, *locality_metadata); + + auto upstream_host = std::make_shared>(); + + EXPECT_CALL(*upstream_host, localityMetadata()) + .WillRepeatedly(testing::Return(locality_metadata)); + stream_info_.upstreamInfo()->setUpstreamHost(upstream_host); + + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); + + ASSERT_EQ(1, descriptors_.size()); + EXPECT_EQ("fake_key", descriptors_[0].entries_[0].key_); + EXPECT_EQ("foo", descriptors_[0].entries_[0].value_); +} + +TEST_F(RateLimitPolicyEntryTest, MetaDataMatchClusterLocalitySourceNoHost) { + const std::string yaml = R"EOF( +actions: +- metadata: + descriptor_key: fake_key + default_value: fake_value + metadata_key: + key: 'envoy.xxx' + path: + - key: test + - key: prop + source: CLUSTER_LOCALITY_ENTRY + )EOF"; + + setupTest(yaml); + + // Clear upstream host + stream_info_.upstreamInfo()->setUpstreamHost(nullptr); + + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); + + ASSERT_EQ(1, descriptors_.size()); + EXPECT_EQ("fake_key", descriptors_[0].entries_[0].key_); + EXPECT_EQ("fake_value", descriptors_[0].entries_[0].value_); +} + +TEST_F(RateLimitPolicyEntryTest, MetaDataMatchClusterLocalitySourceNoUpstreamInfo) { + const std::string yaml = R"EOF( +actions: +- metadata: + descriptor_key: fake_key + default_value: fake_value + metadata_key: + key: 'envoy.xxx' + path: + - key: test + - key: prop + source: CLUSTER_LOCALITY_ENTRY + )EOF"; + + setupTest(yaml); + + // Clear upstream info + stream_info_.setUpstreamInfo(nullptr); + + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); + + ASSERT_EQ(1, descriptors_.size()); + EXPECT_EQ("fake_key", descriptors_[0].entries_[0].key_); + EXPECT_EQ("fake_value", descriptors_[0].entries_[0].value_); +} + // Tests that the default_value is used in the descriptor when the metadata_key is empty. TEST_F(RateLimitPolicyEntryTest, MetaDataNoMatchWithDefaultValue) { const std::string yaml = R"EOF( diff --git a/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc index dcc9af5d780ee..ac3b72ff86b49 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc @@ -1,3 +1,6 @@ +#include +#include + #include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/config/listener/v3/listener_components.pb.h" #include "envoy/config/route/v3/route_components.pb.h" @@ -96,10 +99,21 @@ class RatelimitIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, } void waitForRatelimitRequest() { + envoy::service::ratelimit::v3::RateLimitRequest expected_request_msg; + expected_request_msg.set_domain("some_domain"); + auto* entry = expected_request_msg.add_descriptors()->add_entries(); + entry->set_key("destination_cluster"); + entry->set_value("cluster_0"); + waitForRatelimitRequest(expected_request_msg); + } - AssertionResult result = - fake_upstreams_[1]->waitForHttpConnection(*dispatcher_, fake_ratelimit_connection_); - RELEASE_ASSERT(result, result.message()); + void waitForRatelimitRequest( + const envoy::service::ratelimit::v3::RateLimitRequest& expected_request_msg) { + if (fake_ratelimit_connection_ == nullptr) { + AssertionResult result = + fake_upstreams_[1]->waitForHttpConnection(*dispatcher_, fake_ratelimit_connection_); + RELEASE_ASSERT(result, result.message()); + } for (int i = 0; i < num_requests_; i++) { AssertionResult result = fake_ratelimit_connection_->waitForNewStream(*dispatcher_, ratelimit_requests_[i]); @@ -114,11 +128,6 @@ class RatelimitIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, ratelimit_requests_[i]->headers().getPathValue()); EXPECT_EQ("application/grpc", ratelimit_requests_[i]->headers().getContentTypeValue()); - envoy::service::ratelimit::v3::RateLimitRequest expected_request_msg; - expected_request_msg.set_domain("some_domain"); - auto* entry = expected_request_msg.add_descriptors()->add_entries(); - entry->set_key("destination_cluster"); - entry->set_value("cluster_0"); EXPECT_EQ(expected_request_msg.DebugString(), request_msg.DebugString()); } } @@ -727,5 +736,74 @@ envoy.xxx: EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.ratelimit.ok")->value()); } +TEST_P(RatelimitIntegrationTest, ClusterLocalityEntry) { + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* locality = bootstrap.mutable_static_resources() + ->mutable_clusters(0) + ->mutable_load_assignment() + ->mutable_endpoints(0); + + const std::string metadata_yaml = R"EOF( + filter_metadata: + envoy.xxx: + test: foo + )EOF"; + TestUtility::loadFromYaml(metadata_yaml, *locality->mutable_metadata()); + }); + + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + auto* rate_limit = hcm.mutable_route_config() + ->mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->add_rate_limits(); + // Apply rate limit when stream is complete and the host is available to + // get locality metadata. + rate_limit->set_apply_on_stream_done(true); + auto* action = rate_limit->add_actions()->mutable_metadata(); + action->set_source( + envoy::config::route::v3::RateLimit::Action::MetaData::CLUSTER_LOCALITY_ENTRY); + action->set_descriptor_key("cluster_locality_metadata"); + auto* metadata_key = action->mutable_metadata_key(); + metadata_key->set_key("envoy.xxx"); + metadata_key->add_path()->set_key("test"); + }); + + initialize(); + initiateClientConnection(); + + envoy::service::ratelimit::v3::RateLimitRequest expected_request_msg; + expected_request_msg.set_domain("some_domain"); + auto* descriptor = expected_request_msg.add_descriptors(); + auto* entry = descriptor->add_entries(); + entry->set_key("destination_cluster"); + entry->set_value("cluster_0"); + waitForRatelimitRequest(expected_request_msg); + sendRateLimitResponse(envoy::service::ratelimit::v3::RateLimitResponse::OK, {}, + Http::TestResponseHeaderMapImpl{}, Http::TestRequestHeaderMapImpl{}, 0); + + // Safe stream for the first rate limit request to prevent race condition between destroying it + // and receiving new rate limit request. + FakeStreamPtr first_stream = std::move(ratelimit_requests_[0]); + + // The cluster locality entry should be added to the rate limit request sent + // on the response path. + expected_request_msg.mutable_descriptors()->Clear(); + descriptor = expected_request_msg.add_descriptors(); + entry = descriptor->add_entries(); + entry->set_key("cluster_locality_metadata"); + entry->set_value("foo"); + waitForRatelimitRequest(expected_request_msg); + + sendRateLimitResponse(envoy::service::ratelimit::v3::RateLimitResponse::OK, {}, + Http::TestResponseHeaderMapImpl{}, Http::TestRequestHeaderMapImpl{}, 0); + waitForSuccessfulUpstreamResponse(0); + cleanup(); + + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.ratelimit.ok")->value()); +} + } // namespace } // namespace Envoy From c653fb2b27f161f32db8fe1c82794c9133b4005d Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 15 Apr 2026 18:00:18 +0100 Subject: [PATCH 029/750] aws/signing: Fix new/free mismatch (#44466) `OPENSSL_free(ec_key)` is incorrect for an `EC_KEY*` allocated by `EC_KEY_new_by_curve_name()`. This is a raw free that skips cleanup of internal resources (group, point, `BIGNUM`, etc.), causing memory leaks and undefined behavior. Use `EC_KEY_free()` instead, consistent with all other call sites in the codebase. Fixes CodeQL alert cpp/new-free-mismatch (CWE-762). Signed-off-by: Ryan Northey --- source/extensions/common/aws/signers/sigv4a_key_derivation.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/common/aws/signers/sigv4a_key_derivation.cc b/source/extensions/common/aws/signers/sigv4a_key_derivation.cc index 8d839da64385c..bb387b4ca4d5c 100644 --- a/source/extensions/common/aws/signers/sigv4a_key_derivation.cc +++ b/source/extensions/common/aws/signers/sigv4a_key_derivation.cc @@ -72,7 +72,7 @@ absl::StatusOr SigV4AKeyDerivation::derivePrivateKey(absl::string_view // And set the private key we calculated above if (!EC_KEY_set_private_key(ec_key, priv_key_num)) { BN_free(priv_key_num); - OPENSSL_free(ec_key); + EC_KEY_free(ec_key); return absl::InvalidArgumentError("SigV4a private key could not be set"); } BN_free(priv_key_num); From 3c22c5471fbb1f772d326b58ea8729c6fc8ad1d3 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Wed, 15 Apr 2026 19:36:03 +0200 Subject: [PATCH 030/750] dynamic_modules: replace silent get_or_init with fail-fast set_factory_once for factory registration (#44472) ## Description This PR replaces `OnceLock::get_or_init` with `OnceLock::set` in all the factory registration macros to detect conflicting dual-load of standalone and consolidated `.so` modules at startup. Previously, if both a standalone `.so` and a consolidated `.so` were loaded in the same process, the second `get_or_init` silently no-oped (first writer wins). The second module's factories were completely ignored with no diagnostic. Now, a new `set_factory_once!` macro uses `OnceLock::set` and panics on duplicate registration. The panic is caught at the FFI boundary, causing `envoy_dynamic_module_on_program_init` to return `null`, which makes Envoy refuse to start. This is the correct behavior for a configuration conflict. This change is backward compatible. --- **Commit Message:** dynamic_modules: replace silent get_or_init with fail-fast set_factory_once for factory registration **Additional Description:** Replaces `OnceLock::get_or_init` with `OnceLock::set` in all the factory registration macros to detect conflicting dual-load of standalone and consolidated `.so` modules at startup. **Risk Level:** Low **Testing:** CI **Docs Changes:** N/A **Release Notes:** N/A Signed-off-by: Rohit Agrawal --- .../sdk/rust/src/cert_validator.rs | 19 +- .../dynamic_modules/sdk/rust/src/lib.rs | 211 +++++++++++++----- .../dynamic_modules/sdk/rust/src/lib_test.rs | 20 +- .../dynamic_modules/sdk/rust/src/tracer.rs | 7 +- .../sdk/rust/src/upstream_http_tcp_bridge.rs | 7 +- 5 files changed, 189 insertions(+), 75 deletions(-) diff --git a/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs b/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs index 99ebae9f1c77f..3c9a4975ac501 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs @@ -258,10 +258,21 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_config_new( name.length, )); let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length); - let new_config_fn = NEW_CERT_VALIDATOR_CONFIG_FUNCTION - .get() - .expect("NEW_CERT_VALIDATOR_CONFIG_FUNCTION must be set"); - match new_config_fn(name_str, config_slice) { + init_cert_validator_config( + name_str, + config_slice, + NEW_CERT_VALIDATOR_CONFIG_FUNCTION + .get() + .expect("NEW_CERT_VALIDATOR_CONFIG_FUNCTION must be set"), + ) +} + +pub(crate) fn init_cert_validator_config( + name: &str, + config: &[u8], + new_config_fn: &crate::NewCertValidatorConfigFunction, +) -> abi::envoy_dynamic_module_type_cert_validator_config_module_ptr { + match new_config_fn(name, config) { Some(config) => wrap_into_c_void_ptr!(config), None => std::ptr::null(), } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs index 47afe7228f80b..7c5adbe653dc0 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs @@ -99,10 +99,16 @@ macro_rules! declare_init_functions { ($f:ident, $new_http_filter_config_fn:expr, $new_http_filter_per_route_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION - .get_or_init(|| $new_http_filter_config_fn); - envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION - .get_or_init(|| $new_http_filter_per_route_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION, + $new_http_filter_config_fn, + "NEW_HTTP_FILTER_CONFIG_FUNCTION" + ); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION, + $new_http_filter_per_route_config_fn, + "NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char @@ -114,8 +120,11 @@ macro_rules! declare_init_functions { ($f:ident, $new_http_filter_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION - .get_or_init(|| $new_http_filter_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION, + $new_http_filter_config_fn, + "NEW_HTTP_FILTER_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char @@ -351,6 +360,42 @@ macro_rules! envoy_log { }; } +/// Guard macro that ensures each factory `OnceLock` is registered by exactly one module. +/// +/// When the same module is re-initialized (e.g. static modules loaded multiple times via +/// `newDynamicModuleByName`, or per-route config triggering a second init), the function +/// pointer will be identical and the re-registration is silently accepted (idempotent). +/// +/// If a *different* module (standalone `.so` or consolidated `.so`) tries to register a +/// different factory function for the same slot, this macro logs a critical message via +/// `envoy_log_critical!` and panics. The panic is caught at the FFI boundary by Envoy, +/// which converts it into a null return from `envoy_dynamic_module_on_program_init`, +/// causing Envoy to refuse to start — the correct behaviour for a data-correctness issue. +/// +/// In contrast, the previous `get_or_init` approach silently ignored the second writer, +/// making the second module's factories unreachable at runtime with no diagnostic. +#[macro_export] +macro_rules! set_factory_once { + ($static:expr, $fn:expr, $name:literal) => { + if let Err(new_val) = $static.set($fn) { + if *$static.get().unwrap() != new_val { + $crate::envoy_log_critical!( + "Duplicate factory registration for {}. A different module already registered this \ + factory. Check dynamic_module_config for conflicting standalone and consolidated \ + .so loads.", + $name, + ); + panic!( + "Duplicate factory registration for {}. A different module already registered this \ + factory. Check dynamic_module_config for conflicting standalone and consolidated \ + .so loads.", + $name, + ); + } + } + }; +} + /// The function signature for the program init function. /// /// This is called when the dynamic module is loaded, and it must return true on success, and false @@ -524,8 +569,11 @@ macro_rules! declare_network_filter_init_functions { ($f:ident, $new_network_filter_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_NETWORK_FILTER_CONFIG_FUNCTION - .get_or_init(|| $new_network_filter_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_NETWORK_FILTER_CONFIG_FUNCTION, + $new_network_filter_config_fn, + "NEW_NETWORK_FILTER_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char @@ -661,56 +709,95 @@ macro_rules! declare_all_init_functions { }; (@register http : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION, + $fn, + "NEW_HTTP_FILTER_CONFIG_FUNCTION" + ); }; (@register http_per_route : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION, + $fn, + "NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION" + ); }; (@register network : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_NETWORK_FILTER_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_NETWORK_FILTER_CONFIG_FUNCTION, + $fn, + "NEW_NETWORK_FILTER_CONFIG_FUNCTION" + ); }; (@register listener : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_LISTENER_FILTER_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_LISTENER_FILTER_CONFIG_FUNCTION, + $fn, + "NEW_LISTENER_FILTER_CONFIG_FUNCTION" + ); }; (@register udp_listener : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION, + $fn, + "NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION" + ); }; (@register bootstrap : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION, + $fn, + "NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION" + ); }; (@register load_balancer : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_LOAD_BALANCER_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_LOAD_BALANCER_CONFIG_FUNCTION, + $fn, + "NEW_LOAD_BALANCER_CONFIG_FUNCTION" + ); }; (@register cluster : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_CLUSTER_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_CLUSTER_CONFIG_FUNCTION, + $fn, + "NEW_CLUSTER_CONFIG_FUNCTION" + ); }; (@register cert_validator : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_CERT_VALIDATOR_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_CERT_VALIDATOR_CONFIG_FUNCTION, + $fn, + "NEW_CERT_VALIDATOR_CONFIG_FUNCTION" + ); }; (@register upstream_http_tcp_bridge : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION, + $fn, + "NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION" + ); }; (@register tracer : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_TRACER_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_TRACER_CONFIG_FUNCTION, + $fn, + "NEW_TRACER_CONFIG_FUNCTION" + ); }; (@register dns_resolver : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_DNS_RESOLVER_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_DNS_RESOLVER_CONFIG_FUNCTION, + $fn, + "NEW_DNS_RESOLVER_CONFIG_FUNCTION" + ); }; (@register transport_socket : $fn:expr) => { - envoy_proxy_dynamic_modules_rust_sdk::NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION - .get_or_init(|| $fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION, + $fn, + "NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION" + ); }; } @@ -758,8 +845,11 @@ macro_rules! declare_listener_filter_init_functions { pub extern "C" fn envoy_dynamic_module_on_program_init( server_factory_context_ptr: abi::envoy_dynamic_module_type_server_factory_context_envoy_ptr, ) -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_LISTENER_FILTER_CONFIG_FUNCTION - .get_or_init(|| $new_listener_filter_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_LISTENER_FILTER_CONFIG_FUNCTION, + $new_listener_filter_config_fn, + "NEW_LISTENER_FILTER_CONFIG_FUNCTION" + ); if ($f(server_factory_context_ptr)) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char @@ -813,8 +903,11 @@ macro_rules! declare_udp_listener_filter_init_functions { ($f:ident, $new_udp_listener_filter_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION - .get_or_init(|| $new_udp_listener_filter_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION, + $new_udp_listener_filter_config_fn, + "NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char @@ -917,8 +1010,11 @@ macro_rules! declare_bootstrap_init_functions { ($f:ident, $new_bootstrap_extension_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION - .get_or_init(|| $new_bootstrap_extension_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION, + $new_bootstrap_extension_config_fn, + "NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char @@ -1026,8 +1122,11 @@ macro_rules! declare_cluster_init_functions { ($f:ident, $new_cluster_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_CLUSTER_CONFIG_FUNCTION - .get_or_init(|| $new_cluster_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_CLUSTER_CONFIG_FUNCTION, + $new_cluster_config_fn, + "NEW_CLUSTER_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char @@ -1127,8 +1226,11 @@ macro_rules! declare_load_balancer_init_functions { ($f:ident, $new_lb_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_LOAD_BALANCER_CONFIG_FUNCTION - .get_or_init(|| $new_lb_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_LOAD_BALANCER_CONFIG_FUNCTION, + $new_lb_config_fn, + "NEW_LOAD_BALANCER_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char @@ -1198,8 +1300,11 @@ macro_rules! declare_cert_validator_init_functions { ($f:ident, $new_cert_validator_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_CERT_VALIDATOR_CONFIG_FUNCTION - .get_or_init(|| $new_cert_validator_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_CERT_VALIDATOR_CONFIG_FUNCTION, + $new_cert_validator_config_fn, + "NEW_CERT_VALIDATOR_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char @@ -1312,8 +1417,11 @@ macro_rules! declare_dns_resolver_init_functions { ($f:ident, $new_dns_resolver_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_DNS_RESOLVER_CONFIG_FUNCTION - .get_or_init(|| $new_dns_resolver_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_DNS_RESOLVER_CONFIG_FUNCTION, + $new_dns_resolver_config_fn, + "NEW_DNS_RESOLVER_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char @@ -1371,8 +1479,11 @@ macro_rules! declare_transport_socket_init_functions { ($f:ident, $new_transport_socket_factory_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION - .get_or_init(|| $new_transport_socket_factory_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION, + $new_transport_socket_factory_config_fn, + "NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs index 614bf10aa55a2..632f8297a8c6e 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs @@ -3080,26 +3080,12 @@ fn test_cert_validator_config_new_and_destroy() { } } - NEW_CERT_VALIDATOR_CONFIG_FUNCTION.get_or_init(|| { + let new_config_fn: NewCertValidatorConfigFunction = |_name: &str, _config: &[u8]| -> Option> { Some(Box::new(TestCertValidatorConfig)) - } - }); - - let name = "test"; - let config = b"config"; - let name_buf = abi::envoy_dynamic_module_type_envoy_buffer { - ptr: name.as_ptr() as *const _, - length: name.len(), - }; - let config_buf = abi::envoy_dynamic_module_type_envoy_buffer { - ptr: config.as_ptr() as *const _, - length: config.len(), - }; + }; - let config_ptr = unsafe { - envoy_dynamic_module_on_cert_validator_config_new(std::ptr::null_mut(), name_buf, config_buf) - }; + let config_ptr = cert_validator::init_cert_validator_config("test", b"config", &new_config_fn); assert!(!config_ptr.is_null()); unsafe { diff --git a/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs b/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs index 261cbf2b1d1f3..08fbd67890341 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs @@ -952,8 +952,11 @@ macro_rules! declare_tracer_init_functions { ($f:ident, $new_tracer_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_TRACER_CONFIG_FUNCTION - .get_or_init(|| $new_tracer_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_TRACER_CONFIG_FUNCTION, + $new_tracer_config_fn, + "NEW_TRACER_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char diff --git a/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs b/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs index 1a19f41b1201c..d4fc6f4bfc5a5 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs @@ -417,8 +417,11 @@ macro_rules! declare_upstream_http_tcp_bridge_init_functions { ($f:ident, $new_bridge_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION - .get_or_init(|| $new_bridge_config_fn); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION, + $new_bridge_config_fn, + "NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION" + ); if ($f()) { envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char From 17eb9afe28d348ecd834629b8e1e4a9a4520a45a Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Wed, 15 Apr 2026 19:58:01 +0200 Subject: [PATCH 031/750] coverage: adjust the LCOV numbers to reflect current state (#44475) ## Description This PR adjusts the LCOV numbers to reflect the current state as both these extensions now have good coverage and could be removed from the exception list: ``` source/common/http/http2: 96.6 source/common/memory: 98.1 source/extensions/common/aws: 97.5 source/extensions/common/aws/credential_providers: 100.0 source/extensions/common/wasm/ext: 100.0 source/extensions/clusters/dynamic_modules: 97.7 source/extensions/filters/common: 97.1 source/extensions/filters/http/a2a: 98.4 source/extensions/filters/http/oauth2: 97.6 source/extensions/filters/listener/dynamic_modules: 96.9 source/extensions/formatter/cel: 100.0 source/extensions/load_balancing_policies/ring_hash: 96.9 source/extensions/transport_sockets/proxy_protocol: 96.6 source/extensions/transport_sockets/tls/cert_validator/spiffe: 96.7 source/extensions/matching/input_matchers/cel_matcher: 100.0 ``` --- **Commit Message:** coverage: adjust the LCOV numbers to reflect current state **Additional Description:** Adjusts the LCOV numbers to reflect the current state **Risk Level:** N/A **Testing:** CI **Docs Changes:** N/A **Release Notes:** N/A Signed-off-by: Rohit Agrawal --- test/coverage.yaml | 68 +++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/test/coverage.yaml b/test/coverage.yaml index 5f0821a0b08d1..595f85e967073 100644 --- a/test/coverage.yaml +++ b/test/coverage.yaml @@ -6,85 +6,85 @@ directories: source/common: 96.4 source/common/api: 95.3 # some syscalls require sandboxing source/common/api/posix: 94.9 # setns requires Linux CAP_NET_ADMIN privileges - source/common/crypto: 91.0 # Static singleton initialization and OpenSSL internal error paths not testable - source/common/filesystem/posix: 96.4 # FileReadToEndNotReadable fails in some env; createPath can't test all failure branches. + source/common/crypto: 91.2 # Static singleton initialization and OpenSSL internal error paths not testable + source/common/filesystem/posix: 96.5 # FileReadToEndNotReadable fails in some env; createPath can't test all failure branches. source/common/http: 96.5 - source/common/http/http1: 93.4 # To be removed when http_inspector_use_balsa_parser is retired. + source/common/http/http1: 93.6 # To be removed when http_inspector_use_balsa_parser is retired. source/common/http/http2: 96.6 - source/common/json: 95.2 + source/common/json: 95.8 source/common/jwt: 86.2 - source/common/matcher: 94.8 + source/common/matcher: 95.2 source/common/memory: 98.1 # tcmalloc code path is not enabled in coverage build, only gperf tcmalloc, see PR#32589 source/common/network: 94.3 # Flaky, `activateFileEvents`, `startSecureTransport` and `ioctl`, listener_socket do not always report LCOV source/common/network/dns_resolver: 91.4 # A few lines of MacOS code not tested in linux scripts. Tested in MacOS scripts - source/common/quic: 93.3 + source/common/quic: 93.5 source/common/signal: 87.4 # Death tests don't report LCOV source/common/thread: 0.0 # Death tests don't report LCOV - source/common/tls: 94.1 # FIPS code paths impossible to trigger on non-FIPS builds and vice versa - source/common/tls/cert_validator: 95.0 + source/common/tls: 94.3 # FIPS code paths impossible to trigger on non-FIPS builds and vice versa + source/common/tls/cert_validator: 95.1 source/common/tls/private_key: 88.9 source/common/watchdog: 60.0 # Death tests don't report LCOV source/exe: 94.4 # increased by #32346, need coverage for terminate_handler and hot restart failures source/extensions/api_listeners: 55.0 # Many IS_ENVOY_BUG are not covered. - source/extensions/api_listeners/default_api_listener: 67.8 # Many IS_ENVOY_BUG are not covered. + source/extensions/api_listeners/default_api_listener: 68.9 # Many IS_ENVOY_BUG are not covered. source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface: 96.3 source/extensions/common/aws: 97.5 source/extensions/common/aws/credential_providers: 100.0 - source/extensions/common/proxy_protocol: 94.6 # Adjusted for security patch - source/extensions/common/tap: 95.1 - source/extensions/common/wasm: 95.3 # flaky: be careful adjusting + source/extensions/common/proxy_protocol: 95.1 # Adjusted for security patch + source/extensions/common/tap: 95.2 + source/extensions/common/wasm: 95.6 # flaky: be careful adjusting source/extensions/common/wasm/ext: 100.0 - source/extensions/clusters/dynamic_modules: 95.5 + source/extensions/clusters/dynamic_modules: 97.7 source/extensions/filters/common: 97.1 source/extensions/filters/common/fault: 94.5 source/extensions/filters/common/lua: 95.6 - source/extensions/filters/http/a2a: 93 # TODO(tyxia) Under active development + source/extensions/filters/http/a2a: 98.4 source/extensions/filters/http/cache: 95.9 source/extensions/filters/http/dynamic_forward_proxy: 94.8 - source/extensions/filters/http/dynamic_modules: 95.2 + source/extensions/filters/http/dynamic_modules: 95.5 source/extensions/filters/http/decompressor: 95.9 - source/extensions/filters/http/ext_proc: 96.4 - source/extensions/filters/http/grpc_json_reverse_transcoder: 94.8 - source/extensions/filters/http/grpc_json_transcoder: 94.0 # TODO(#28232) + source/extensions/filters/http/ext_proc: 96.5 + source/extensions/filters/http/grpc_json_reverse_transcoder: 94.9 + source/extensions/filters/http/grpc_json_transcoder: 94.1 # TODO(#28232) source/extensions/filters/http/ip_tagging: 95.9 source/extensions/filters/http/kill_request: 91.7 # Death tests don't report LCOV - source/extensions/filters/http/mcp_json_rest_bridge: 80 # TODO(guoyilin42): adjust up. Overriden to check-in boilerplate. - source/extensions/filters/http/mcp_router: 80 # TODO(yanavlasov): adjust up. Overriden to just scheck-in boilerplate. + source/extensions/filters/http/mcp_json_rest_bridge: 94.0 + source/extensions/filters/http/mcp_router: 82.3 source/extensions/filters/http/oauth2: 97.6 source/extensions/filters/listener: 96.5 - source/extensions/filters/listener/dynamic_modules: 95.5 + source/extensions/filters/listener/dynamic_modules: 96.9 source/extensions/filters/listener/original_src: 92.1 - source/extensions/filters/listener/tls_inspector: 94.4 + source/extensions/filters/listener/tls_inspector: 94.5 source/extensions/filters/network/dubbo_proxy: 96.2 source/extensions/filters/network/mongo_proxy: 96.1 - source/extensions/filters/network/reverse_tunnel: 92.0 + source/extensions/filters/network/reverse_tunnel: 95.9 source/extensions/filters/network/sni_cluster: 88.9 source/extensions/formatter/cel: 100.0 - source/extensions/formatter/file_content: 96.2 + source/extensions/formatter/file_content: 96.3 source/extensions/internal_redirect: 86.2 source/extensions/internal_redirect/safe_cross_scheme: 81.3 source/extensions/internal_redirect/allow_listed_routes: 85.7 source/extensions/internal_redirect/previous_routes: 89.3 - source/extensions/load_balancing_policies/common: 96.3 - source/extensions/load_balancing_policies/maglev: 94.9 + source/extensions/load_balancing_policies/common: 96.4 + source/extensions/load_balancing_policies/maglev: 95.2 source/extensions/load_balancing_policies/round_robin: 96.4 source/extensions/load_balancing_policies/ring_hash: 96.9 source/extensions/rate_limit_descriptors: 95.0 source/extensions/rate_limit_descriptors/expr: 96.1 source/extensions/stat_sinks/graphite_statsd: 82.8 # Death tests don't report LCOV source/extensions/stat_sinks/statsd: 85.2 # Death tests don't report LCOV - source/extensions/tracers/zipkin: 95.6 - source/extensions/transport_sockets/proxy_protocol: 96.2 - source/extensions/transport_sockets/tls/cert_mappers/filter_state_override: 96.2 - source/extensions/transport_sockets/tls/cert_validator/spiffe: 96.2 + source/extensions/tracers/zipkin: 96.2 + source/extensions/transport_sockets/proxy_protocol: 96.6 + source/extensions/transport_sockets/tls/cert_mappers/filter_state_override: 96.4 + source/extensions/transport_sockets/tls/cert_validator/spiffe: 96.7 source/extensions/wasm_runtime/wamr: 0.0 # Not enabled in coverage build source/extensions/wasm_runtime/wasmtime: 0.0 # Not enabled in coverage build source/extensions/watchdog: 83.3 # Death tests within extensions - source/extensions/listener_managers: 77.3 - source/extensions/listener_managers/validation_listener_manager: 77.3 + source/extensions/listener_managers: 77.8 + source/extensions/listener_managers/validation_listener_manager: 77.8 source/extensions/watchdog/profile_action: 86.1 - source/server: 91.0 # flaky: be careful adjusting. See https://github.com/envoyproxy/envoy/issues/15239 - source/server/config_validation: 93.1 + source/server: 93.3 # flaky: be careful adjusting. See https://github.com/envoyproxy/envoy/issues/15239 + source/server/config_validation: 93.4 source/extensions/health_checkers: 96.1 source/extensions/health_checkers/http: 94.6 source/extensions/health_checkers/grpc: 92.3 From 550d5724c9851716a08edfc2805f939cabfa1ff1 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 15 Apr 2026 14:21:56 -0400 Subject: [PATCH 032/750] Integrate MCP router with MCP cluster (#44453) Make MCP router use MCP cluster as the source the list of servers. If the cluster is not an MCP cluster MCP router falls back to the filter config. This is a temporary state to avoid disruption to the existing operators that use MCP router. The server configuration will be removed from the filter in a follow up PR. Risk Level: low Testing: unit tests Docs Changes: no Release Notes: no Platform Specific Features: no Signed-off-by: Yan Avlasov --- .../extensions/filters/http/mcp_router/BUILD | 1 + .../filters/http/mcp_router/filter_config.cc | 18 +- .../filters/http/mcp_router/filter_config.h | 37 +++- .../filters/http/mcp_router/mcp_router.cc | 29 +++ .../filters/http/mcp_router/mcp_router.h | 2 + test/extensions/filters/http/mcp_router/BUILD | 4 + .../mcp_router/mcp_router_integration_test.cc | 190 +++++++++++++----- .../http/mcp_router/mcp_router_test.cc | 92 +++++++++ 8 files changed, 321 insertions(+), 52 deletions(-) diff --git a/source/extensions/filters/http/mcp_router/BUILD b/source/extensions/filters/http/mcp_router/BUILD index 8caca1a061feb..d3ee92fb326ce 100644 --- a/source/extensions/filters/http/mcp_router/BUILD +++ b/source/extensions/filters/http/mcp_router/BUILD @@ -33,6 +33,7 @@ envoy_cc_library( "//source/extensions/filters/common/mcp:filter_state_lib", "@abseil-cpp//absl/strings", "@abseil-cpp//absl/types:variant", + "@envoy_api//envoy/extensions/clusters/mcp_multicluster/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/mcp_router/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/http/mcp_router/filter_config.cc b/source/extensions/filters/http/mcp_router/filter_config.cc index d3bc713d9ef24..01512a1193b61 100644 --- a/source/extensions/filters/http/mcp_router/filter_config.cc +++ b/source/extensions/filters/http/mcp_router/filter_config.cc @@ -56,8 +56,7 @@ McpRouterStats generateStats(const std::string& prefix, Stats::Scope& scope) { return McpRouterStats{MCP_ROUTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))}; } -std::vector -parseBackends(const envoy::extensions::filters::http::mcp_router::v3::McpRouter& config) { +template std::vector parseBackends(const Config& config) { std::vector result; for (const auto& server : config.servers()) { McpBackendConfig backend; @@ -93,6 +92,21 @@ const McpBackendConfig* McpRouterConfigImpl::findBackend(const std::string& name return nullptr; } +McpRouterClusterConfigImpl::McpRouterClusterConfigImpl( + const envoy::extensions::clusters::mcp_multicluster::v3::ClusterConfig& proto_config, + McpRouterConfigSharedPtr base_config) + : base_config_(std::move(base_config)), backends_(parseBackends(proto_config)), + default_backend_name_(backends_.size() == 1 ? backends_[0].name : "") {} + +const McpBackendConfig* McpRouterClusterConfigImpl::findBackend(const std::string& name) const { + for (const auto& backend : backends_) { + if (backend.name == name) { + return &backend; + } + } + return base_config_->findBackend(name); +} + } // namespace McpRouter } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/mcp_router/filter_config.h b/source/extensions/filters/http/mcp_router/filter_config.h index 3af125777fe4f..7aa83950fbbda 100644 --- a/source/extensions/filters/http/mcp_router/filter_config.h +++ b/source/extensions/filters/http/mcp_router/filter_config.h @@ -5,6 +5,7 @@ #include #include +#include "envoy/extensions/clusters/mcp_multicluster/v3/cluster.pb.h" #include "envoy/extensions/filters/http/mcp_router/v3/mcp_router.pb.h" #include "envoy/server/filter_config.h" #include "envoy/stats/scope.h" @@ -99,6 +100,8 @@ class McpRouterConfig { virtual McpRouterStats& stats() = 0; }; +using McpRouterConfigSharedPtr = std::shared_ptr; + class McpRouterConfigImpl : public McpRouterConfig { public: McpRouterConfigImpl( @@ -133,7 +136,39 @@ class McpRouterConfigImpl : public McpRouterConfig { McpRouterStats stats_; }; -using McpRouterConfigSharedPtr = std::shared_ptr; +// This class overrides configuration of the MCP filter with the list of MCP servers based on +// cluster configuration. +// TODO(yanavlasov): use only cluster config for MCP servers once list of servers is removed from +// the filter config. +class McpRouterClusterConfigImpl : public McpRouterConfig { +public: + McpRouterClusterConfigImpl( + const envoy::extensions::clusters::mcp_multicluster::v3::ClusterConfig& proto_config, + McpRouterConfigSharedPtr base_config); + + const std::vector& backends() const override { return backends_; } + bool isMultiplexing() const override { return backends_.size() > 1; } + const std::string& defaultBackendName() const override { return default_backend_name_; } + Server::Configuration::FactoryContext& factoryContext() const override { + return base_config_->factoryContext(); + } + const McpBackendConfig* findBackend(const std::string& name) const override; + + bool hasSessionIdentity() const override { return base_config_->hasSessionIdentity(); } + const SubjectSource& subjectSource() const override { return base_config_->subjectSource(); } + ValidationMode validationMode() const override { return base_config_->validationMode(); } + bool shouldEnforceValidation() const override { return base_config_->shouldEnforceValidation(); } + const std::string& metadataNamespace() const override { + return base_config_->metadataNamespace(); + } + + McpRouterStats& stats() override { return base_config_->stats(); } + +private: + const McpRouterConfigSharedPtr base_config_; + const std::vector backends_; + const std::string default_backend_name_; +}; } // namespace McpRouter } // namespace HttpFilters diff --git a/source/extensions/filters/http/mcp_router/mcp_router.cc b/source/extensions/filters/http/mcp_router/mcp_router.cc index bda4dee3ae351..91c7667e61bd1 100644 --- a/source/extensions/filters/http/mcp_router/mcp_router.cc +++ b/source/extensions/filters/http/mcp_router/mcp_router.cc @@ -95,6 +95,29 @@ void McpRouterFilter::onDestroy() { single_backend_callback_ = nullptr; } +absl::StatusOr +McpRouterFilter::getClusterConfig() { + OptRef cluster_info = decoder_callbacks_->clusterInfo(); + if (!cluster_info.has_value()) { + return absl::NotFoundError("No cluster info"); + } + + const auto& typed_filter_metadata = cluster_info.ref().metadata().typed_filter_metadata(); + auto it = typed_filter_metadata.find("envoy.clusters.mcp_multicluster"); + if (it == typed_filter_metadata.end()) { + return absl::NotFoundError( + fmt::format("Metadata for 'envoy.clusters.mcp_multicluster' not found in cluster '{}'", + cluster_info.ref().name())); + } + + envoy::extensions::clusters::mcp_multicluster::v3::ClusterConfig cluster_config; + if (!it->second.UnpackTo(&cluster_config)) { + return absl::InvalidArgumentError("Failed to parse ClusterConfig metadata: {}"); + } + + return cluster_config; +} + Http::FilterHeadersStatus McpRouterFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { // TODO(botengyao): also supports /GET endpoints. @@ -106,6 +129,12 @@ Http::FilterHeadersStatus McpRouterFilter::decodeHeaders(Http::RequestHeaderMap& request_headers_ = &headers; + auto cluster_config_or = getClusterConfig(); + if (cluster_config_or.ok()) { + config_ = + std::make_shared(cluster_config_or.value(), std::move(config_)); + } + // Extract session ID from header auto session_header = headers.get(Http::LowerCaseString(std::string(kSessionIdHeader))); if (!session_header.empty()) { diff --git a/source/extensions/filters/http/mcp_router/mcp_router.h b/source/extensions/filters/http/mcp_router/mcp_router.h index 4ebd17b5df252..eac5b131edc52 100644 --- a/source/extensions/filters/http/mcp_router/mcp_router.h +++ b/source/extensions/filters/http/mcp_router/mcp_router.h @@ -153,6 +153,8 @@ class McpRouterFilter : public Http::StreamDecoderFilter, void sendHttpError(uint64_t status_code, const std::string& message); Http::RequestHeaderMapPtr createUpstreamHeaders(const McpBackendConfig& backend, const std::string& backend_session_id = ""); + absl::StatusOr + getClusterConfig(); McpRouterConfigSharedPtr config_; Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; diff --git a/test/extensions/filters/http/mcp_router/BUILD b/test/extensions/filters/http/mcp_router/BUILD index f303e231cb0d6..27cb72ae75a13 100644 --- a/test/extensions/filters/http/mcp_router/BUILD +++ b/test/extensions/filters/http/mcp_router/BUILD @@ -26,6 +26,7 @@ envoy_extension_cc_test( srcs = ["mcp_router_test.cc"], extension_names = ["envoy.filters.http.mcp_router"], deps = [ + "//envoy/http:async_client_interface", "//source/common/buffer:buffer_lib", "//source/common/http:message_lib", "//source/extensions/filters/http/mcp_router:mcp_router_lib", @@ -35,6 +36,7 @@ envoy_extension_cc_test( "//test/mocks/server:factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/clusters/mcp_multicluster/v3:pkg_cc_proto", ], ) @@ -57,9 +59,11 @@ envoy_extension_cc_test( extension_names = ["envoy.filters.http.mcp_router"], rbe_pool = "6gig", deps = [ + "//source/extensions/clusters/mcp_multicluster:cluster", "//source/extensions/filters/http/mcp:config", "//source/extensions/filters/http/mcp_router:config", "//test/integration:http_integration_lib", + "@envoy_api//envoy/extensions/clusters/mcp_multicluster/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/mcp/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/mcp_router/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/mcp_router/mcp_router_integration_test.cc b/test/extensions/filters/http/mcp_router/mcp_router_integration_test.cc index 72076287b4ee8..b4572f15af8f2 100644 --- a/test/extensions/filters/http/mcp_router/mcp_router_integration_test.cc +++ b/test/extensions/filters/http/mcp_router/mcp_router_integration_test.cc @@ -1,3 +1,6 @@ +#include + +#include "envoy/extensions/clusters/mcp_multicluster/v3/cluster.pb.h" #include "envoy/extensions/filters/http/mcp/v3/mcp.pb.h" #include "envoy/extensions/filters/http/mcp_router/v3/mcp_router.pb.h" @@ -11,10 +14,18 @@ namespace HttpFilters { namespace McpRouter { namespace { -class McpRouterIntegrationTest : public testing::TestWithParam, +// Make this test suite run with server config in either filter or cluster. +// TODO(yanavlasov): remove this parameterization once server config is removed from the filter. +enum class ConfigSource { Filter, Cluster }; +using TestParams = std::tuple; + +class McpRouterIntegrationTest : public testing::TestWithParam, public HttpIntegrationTest { public: - McpRouterIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) {} + McpRouterIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, ipVersion()) {} + + Network::Address::IpVersion ipVersion() const { return std::get<0>(GetParam()); } + ConfigSource configSource() const { return std::get<1>(GetParam()); } void createUpstreams() override { // Create two fake upstreams for MCP backends (time and tools) @@ -40,7 +51,7 @@ class McpRouterIntegrationTest : public testing::TestWithParamadd_endpoints(); auto* time_lb = time_locality->add_lb_endpoints(); time_lb->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_address( - Network::Test::getLoopbackAddressString(GetParam())); + Network::Test::getLoopbackAddressString(ipVersion())); time_lb->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value( fake_upstreams_[0]->localAddress()->ip()->port()); @@ -56,30 +67,61 @@ class McpRouterIntegrationTest : public testing::TestWithParamadd_endpoints(); auto* tools_lb = tools_locality->add_lb_endpoints(); tools_lb->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_address( - Network::Test::getLoopbackAddressString(GetParam())); + Network::Test::getLoopbackAddressString(ipVersion())); tools_lb->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value( fake_upstreams_[1]->localAddress()->ip()->port()); + + // Add MCP multicluster. + auto* multicluster = bootstrap.mutable_static_resources()->add_clusters(); + multicluster->set_name("multicluster"); + multicluster->mutable_connect_timeout()->set_seconds(5); + multicluster->set_lb_policy(envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED); + multicluster->mutable_cluster_type()->set_name("envoy.clusters.mcp_multicluster"); + + // Configure the MCP multicluster. + envoy::extensions::clusters::mcp_multicluster::v3::ClusterConfig multicluster_config; + auto* server = multicluster_config.add_servers(); + server->set_name("time"); + auto* mcp_cluster = server->mutable_mcp_cluster(); + mcp_cluster->set_cluster("mcp_time_backend"); + mcp_cluster->set_host_rewrite_literal("time.mcp.example.com"); + + server = multicluster_config.add_servers(); + server->set_name("tools"); + mcp_cluster = server->mutable_mcp_cluster(); + mcp_cluster->set_cluster("mcp_tools_backend"); + mcp_cluster->set_host_rewrite_literal("tools.mcp.example.com"); + + multicluster->mutable_cluster_type()->mutable_typed_config()->PackFrom(multicluster_config); }); // MCP router as terminal filter - config_helper_.prependFilter(R"EOF( - name: envoy.filters.http.mcp_router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_router.v3.McpRouter - servers: - - name: time - mcp_cluster: - cluster: mcp_time_backend - path: /mcp - timeout: 5s - host_rewrite_literal: time.mcp.example.com - - name: tools - mcp_cluster: - cluster: mcp_tools_backend - path: /mcp - timeout: 5s - host_rewrite_literal: tools.mcp.example.com - )EOF"); + if (configSource() == ConfigSource::Cluster) { + config_helper_.prependFilter(R"EOF( + name: envoy.filters.http.mcp_router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_router.v3.McpRouter + )EOF"); + } else { + config_helper_.prependFilter(R"EOF( + name: envoy.filters.http.mcp_router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_router.v3.McpRouter + servers: + - name: time + mcp_cluster: + cluster: mcp_time_backend + path: /mcp + timeout: 5s + host_rewrite_literal: time.mcp.example.com + - name: tools + mcp_cluster: + cluster: mcp_tools_backend + path: /mcp + timeout: 5s + host_rewrite_literal: tools.mcp.example.com + )EOF"); + } // MCP filter (validates JSON-RPC, sets metadata) config_helper_.prependFilter(R"EOF( @@ -91,8 +133,9 @@ class McpRouterIntegrationTest : public testing::TestWithParambegin(); it != filters->end();) { if (it->name() == "envoy.filters.http.router") { @@ -101,6 +144,13 @@ class McpRouterIntegrationTest : public testing::TestWithParammutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster("multicluster"); + } }); initialize(); @@ -114,8 +164,10 @@ class McpRouterIntegrationTest : public testing::TestWithParamadd_endpoints(); auto* time_lb = time_locality->add_lb_endpoints(); time_lb->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_address( - Network::Test::getLoopbackAddressString(GetParam())); + Network::Test::getLoopbackAddressString(ipVersion())); time_lb->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value( fake_upstreams_[0]->localAddress()->ip()->port()); + + // Add MCP multicluster. + auto* multicluster = bootstrap.mutable_static_resources()->add_clusters(); + multicluster->set_name("multicluster"); + multicluster->mutable_connect_timeout()->set_seconds(5); + multicluster->set_lb_policy(envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED); + multicluster->mutable_cluster_type()->set_name("envoy.clusters.mcp_multicluster"); + + // Configure the MCP multicluster. + envoy::extensions::clusters::mcp_multicluster::v3::ClusterConfig multicluster_config; + auto* server = multicluster_config.add_servers(); + server->set_name("time"); + auto* mcp_cluster = server->mutable_mcp_cluster(); + mcp_cluster->set_cluster("mcp_time_backend"); + mcp_cluster->set_host_rewrite_literal("time.mcp.example.com"); + + multicluster->mutable_cluster_type()->mutable_typed_config()->PackFrom(multicluster_config); }); // MCP router with session identity and ENFORCE validation - config_helper_.prependFilter(R"EOF( - name: envoy.filters.http.mcp_router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_router.v3.McpRouter - servers: - - name: time - mcp_cluster: - cluster: mcp_time_backend - path: /mcp - timeout: 5s - session_identity: - identity: - header: - name: x-user-id - validation: - mode: ENFORCE - )EOF"); + if (configSource() == ConfigSource::Cluster) { + config_helper_.prependFilter(R"EOF( + name: envoy.filters.http.mcp_router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_router.v3.McpRouter + session_identity: + identity: + header: + name: x-user-id + validation: + mode: ENFORCE + )EOF"); + } else { + config_helper_.prependFilter(R"EOF( + name: envoy.filters.http.mcp_router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_router.v3.McpRouter + servers: + - name: time + mcp_cluster: + cluster: mcp_time_backend + path: /mcp + timeout: 5s + session_identity: + identity: + header: + name: x-user-id + validation: + mode: ENFORCE + )EOF"); + } config_helper_.prependFilter(R"EOF( name: envoy.filters.http.mcp @@ -2177,8 +2260,9 @@ class McpRouterSubjectValidationIntegrationTest : public McpRouterIntegrationTes )EOF"); config_helper_.addConfigModifier( - [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& - hcm) { + [this]( + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { auto* filters = hcm.mutable_http_filters(); for (auto it = filters->begin(); it != filters->end();) { if (it->name() == "envoy.filters.http.router") { @@ -2187,6 +2271,13 @@ class McpRouterSubjectValidationIntegrationTest : public McpRouterIntegrationTes ++it; } } + if (configSource() == ConfigSource::Cluster) { + hcm.mutable_route_config() + ->mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster("multicluster"); + } }); HttpIntegrationTest::initialize(); @@ -2200,9 +2291,10 @@ class McpRouterSubjectValidationIntegrationTest : public McpRouterIntegrationTes } }; -INSTANTIATE_TEST_SUITE_P(IpVersions, McpRouterSubjectValidationIntegrationTest, - testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - TestUtility::ipTestParamsToString); +INSTANTIATE_TEST_SUITE_P( + IpVersions, McpRouterSubjectValidationIntegrationTest, + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + testing::Values(ConfigSource::Filter, ConfigSource::Cluster))); // Subject mismatch returns 403 TEST_P(McpRouterSubjectValidationIntegrationTest, SubjectMismatchReturns403) { diff --git a/test/extensions/filters/http/mcp_router/mcp_router_test.cc b/test/extensions/filters/http/mcp_router/mcp_router_test.cc index 78a6dac2e8529..82c2a59287c66 100644 --- a/test/extensions/filters/http/mcp_router/mcp_router_test.cc +++ b/test/extensions/filters/http/mcp_router/mcp_router_test.cc @@ -1,3 +1,6 @@ +#include "envoy/extensions/clusters/mcp_multicluster/v3/cluster.pb.h" +#include "envoy/http/async_client.h" + #include "source/common/http/message_impl.h" #include "source/extensions/filters/http/mcp_router/mcp_router.h" @@ -5,6 +8,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/stream_info/mocks.h" +#include "test/mocks/upstream/cluster.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -16,6 +20,7 @@ namespace HttpFilters { namespace McpRouter { namespace { +using testing::_; using testing::AnyNumber; using testing::NiceMock; using testing::ReturnRef; @@ -124,6 +129,32 @@ TEST_F(McpRouterConfigTest, DefaultMetadataNamespace) { EXPECT_EQ(config.metadataNamespace(), "envoy.filters.http.mcp"); } +// Verifies McpRouterClusterConfigImpl parses ClusterConfig proto correctly. +TEST_F(McpRouterConfigTest, McpRouterClusterConfigImplParsesProto) { + envoy::extensions::clusters::mcp_multicluster::v3::ClusterConfig proto_config; + auto* server = proto_config.add_servers(); + server->set_name("cluster_backend"); + server->mutable_mcp_cluster()->set_cluster("target_cluster"); + server->mutable_mcp_cluster()->set_path("/custom_mcp"); + server->mutable_mcp_cluster()->mutable_timeout()->set_seconds(10); + + envoy::extensions::filters::http::mcp_router::v3::McpRouter base_proto; + auto base_config = std::make_shared(base_proto, "test.", *store_.rootScope(), + factory_context_); + + McpRouterClusterConfigImpl cluster_config(proto_config, base_config); + + EXPECT_EQ(cluster_config.backends().size(), 1); + EXPECT_FALSE(cluster_config.isMultiplexing()); + + const McpBackendConfig* backend = cluster_config.findBackend("cluster_backend"); + ASSERT_NE(backend, nullptr); + EXPECT_EQ(backend->name, "cluster_backend"); + EXPECT_EQ(backend->cluster_name, "target_cluster"); + EXPECT_EQ(backend->path, "/custom_mcp"); + EXPECT_EQ(backend->timeout, std::chrono::milliseconds(10000)); +} + class BackendStreamCallbacksTest : public testing::Test {}; // Verifies successful response correctly populates BackendResponse fields. @@ -435,6 +466,67 @@ class McpRouterFilterTest : public testing::Test { Stats::TestUtil::TestStore store_; }; +TEST_F(McpRouterFilterTest, UsingClusterConfigForListOfServers) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("initialize"); + + auto config = createConfig(proto_config); + + // These vectors have to outlive the filter object + std::vector http_callbacks; + std::vector>> http_streams; + + McpRouterFilter filter(config); + filter.setDecoderFilterCallbacks(decoder_callbacks_); + + envoy::extensions::clusters::mcp_multicluster::v3::ClusterConfig cluster_config; + auto* backend = cluster_config.add_servers(); + backend->set_name("backend"); + backend->mutable_mcp_cluster()->set_cluster("backend_cluster"); + + (*decoder_callbacks_.cluster_info_->metadata_ + .mutable_typed_filter_metadata())["envoy.clusters.mcp_multicluster"] + .PackFrom(cluster_config); + + Http::TestRequestHeaderMapImpl headers{ + {":method", "POST"}, {":path", "/mcp"}, {"content-type", "application/json"}}; + + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .Times(AnyNumber()) + .WillRepeatedly(testing::Invoke([](Http::ResponseHeaderMap& headers, bool) { + EXPECT_NE("403", headers.getStatusValue()); + })); + + filter.decodeHeaders(headers, false); + + // Add "backend_cluster" to the list of cluster manager. + factory_context_.server_factory_context_.cluster_manager_.initializeThreadLocalClusters( + {"backend_cluster"}); + // MCP router is expected to use list of servers from the cluster metadata, which contains + // "backend_cluster". If it does use cluster metadata, the cluster lookup will succeed and + // a new HTTP stream will be opened. Otherwise this expectation will fail. + EXPECT_CALL( + factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_.async_client_, + start(_, _)) + .WillRepeatedly( + [&http_callbacks, &http_streams](Http::AsyncClient::StreamCallbacks& callbacks, + const Http::AsyncClient::StreamOptions&) { + http_callbacks.push_back(&callbacks); + http_streams.emplace_back(std::make_unique>()); + return http_streams.back().get(); + }); + + // This test only validates that correct cluster is selected. + const std::string body = + R"({"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-06-18"}})"; + Buffer::OwnedImpl buffer(body); + filter.decodeData(buffer, true); +} + // Verifies subject extraction from dynamic metadata succeeds. TEST_F(McpRouterFilterTest, MetadataSubjectExtractionSuccess) { envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; From 7f73dbced6b136b1b329dcb2ff2c063a50f85693 Mon Sep 17 00:00:00 2001 From: Ting Pan Date: Wed, 15 Apr 2026 15:33:48 -0400 Subject: [PATCH 033/750] HTTP: add allow_obs_text configuration to HTTP/2 and HTTP/3 protocol options (#43971) Commit Message: HTTP: add allow_obs_text configuration to HTTP/2 and HTTP/3 protocol options Additional Description: This change introduces a new configuration knob, allow_obs_text, to the Envoy HTTP/2 and HTTP/3 protocol options to control the validation of 'obs-text' characters (0x80-0xFF) in header field values. By default, this option is enabled to maintain consistency with existing Shinkansen behaviors and to avoid breaking legacy clients that rely on these characters. The implementation updates the underlying codec logic for both protocols to respect this setting during header validation. Comprehensive unit tests have been added for both HTTP/2 (OgHttp2) and HTTP/3 to verify that headers containing obsolete text are accepted or rejected as expected based on the configuration. Risk Level: low Testing: added unit tests Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Ting Pan --- api/envoy/config/core/v3/protocol.proto | 16 ++++- source/common/http/http2/codec_impl.cc | 2 + source/common/quic/envoy_quic_stream.h | 4 ++ test/common/http/http2/codec_impl_test.cc | 71 +++++++++++++++++-- .../quic/envoy_quic_server_stream_test.cc | 37 ++++++++++ ...fault_header_validator_integration_test.cc | 2 +- 6 files changed, 124 insertions(+), 8 deletions(-) diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index ca94d04e5f4a1..9a793ed941d9b 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -541,7 +541,7 @@ message KeepaliveSettings { [(validate.rules).duration = {gte {nanos: 1000000}}]; } -// [#next-free-field: 20] +// [#next-free-field: 21] message Http2ProtocolOptions { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.Http2ProtocolOptions"; @@ -774,6 +774,12 @@ message Http2ProtocolOptions { // request failures. google.protobuf.UInt32Value max_header_field_size_kb = 19 [(validate.rules).uint32 = {lte: 256 gte: 64}]; + + // Whether to disallow obsolete text for oghttp2 in header field values. + // If not set, it defaults to false. + // From RFC 9110, https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5: + // obs-text = %x80-FF + google.protobuf.BoolValue disallow_obs_text = 20; } // [#not-implemented-hide:] @@ -785,7 +791,7 @@ message GrpcProtocolOptions { } // A message which allows using HTTP/3. -// [#next-free-field: 9] +// [#next-free-field: 10] message Http3ProtocolOptions { QuicProtocolOptions quic_protocol_options = 1; @@ -827,6 +833,12 @@ message Http3ProtocolOptions { // Disables connection level flow control for HTTP/3 streams. This is useful in situations where the streams share the same connection // but originate from different end-clients, so that each stream can make progress independently at non-front-line proxies. bool disable_connection_flow_control_for_streams = 8; + + // Whether to disallow obsolete text in header field values. + // If not set, it defaults to true for alignment with current behavior. + // As defined in RFC 9110, https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5: + // an obs-text character is a character in the range %x80-FF + google.protobuf.BoolValue disallow_obs_text = 9; } // A message to control transformations to the :scheme header diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 3752eaf92f4d0..e02141e75fa60 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -2082,6 +2082,8 @@ ConnectionImpl::Http2Options::Http2Options( og_options_.max_header_field_size = max_headers_kb * 1024; og_options_.allow_extended_connect = http2_options.allow_connect(); og_options_.allow_different_host_and_authority = true; + og_options_.allow_obs_text = + !PROTOBUF_GET_WRAPPED_OR_DEFAULT(http2_options, disallow_obs_text, false); if (!PROTOBUF_GET_WRAPPED_OR_DEFAULT(http2_options, enable_huffman_encoding, true)) { if (http2_options.has_hpack_table_size() && http2_options.hpack_table_size().value() == 0) { og_options_.compression_option = http2::adapter::OgHttp2Session::Options::DISABLE_COMPRESSION; diff --git a/source/common/quic/envoy_quic_stream.h b/source/common/quic/envoy_quic_stream.h index a9e48b495bc88..81491dcd0ec38 100644 --- a/source/common/quic/envoy_quic_stream.h +++ b/source/common/quic/envoy_quic_stream.h @@ -9,6 +9,7 @@ #include "envoy/http/codec.h" #include "source/common/http/codec_helper.h" +#include "source/common/protobuf/utility.h" #include "source/common/quic/envoy_quic_simulated_watermark_buffer.h" #include "source/common/quic/envoy_quic_utils.h" @@ -50,6 +51,9 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, if (http3_options_.disable_connection_flow_control_for_streams()) { quic_stream_.DisableConnectionFlowControlForThisStream(); } + if (!PROTOBUF_GET_WRAPPED_OR_DEFAULT(http3_options_, disallow_obs_text, true)) { + header_validator_.SetObsTextOption(http2::adapter::ObsTextOption::kAllow); + } } ~EnvoyQuicStream() override = default; diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index 5b139938a6feb..e53b7872079a1 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -135,6 +135,12 @@ class Http2CodecImplTestFixture { return stream_ids; } + static std::unique_ptr + createHttp2Options(const envoy::config::core::v3::Http2ProtocolOptions& http2_options, + uint32_t max_headers_kb) { + return std::make_unique(http2_options, max_headers_kb); + } + static Http2SettingsTuple smallWindowHttp2Settings() { return std::make_tuple(CommonUtility::OptionsLimits::DEFAULT_HPACK_TABLE_SIZE, CommonUtility::OptionsLimits::DEFAULT_MAX_CONCURRENT_STREAMS, @@ -211,11 +217,13 @@ class Http2CodecImplTestFixture { // Ensure that tests driveToCompletion(). Some tests set `expect_buffered_data_on_teardown_` to // indicate that they purposefully leave buffered data. - if (expect_buffered_data_on_teardown_) { - EXPECT_TRUE(client_wrapper_->buffer_.length() > 0 || server_wrapper_->buffer_.length() > 0); - } else { - EXPECT_EQ(client_wrapper_->buffer_.length(), 0); - EXPECT_EQ(server_wrapper_->buffer_.length(), 0); + if (client_wrapper_ != nullptr && server_wrapper_ != nullptr) { + if (expect_buffered_data_on_teardown_) { + EXPECT_TRUE(client_wrapper_->buffer_.length() > 0 || server_wrapper_->buffer_.length() > 0); + } else { + EXPECT_EQ(client_wrapper_->buffer_.length(), 0); + EXPECT_EQ(server_wrapper_->buffer_.length(), 0); + } } } @@ -570,6 +578,59 @@ class Http2CodecImplTest : public ::testing::TestWithParamset_value(true); + initialize(); + + InSequence s; + TestRequestHeaderMapImpl request_headers; + HttpTestUtility::addDefaultHeaders(request_headers); + + HeaderString header_name; + header_name.setCopy("custom-header"); + HeaderString header_value; + setHeaderStringUnvalidated(header_value, "foo\x80"); + request_headers.addViaMove(std::move(header_name), std::move(header_value)); + + // We don't expect onResetStream because the error might be detected before the stream is fully + // established on the server. + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); + driveToCompletion(); + EXPECT_FALSE(server_wrapper_->status_.ok()); + // Drain the buffer as we expect a connection error and some data might be left. + server_wrapper_->buffer_.drain(server_wrapper_->buffer_.length()); +} + +TEST_P(Http2CodecImplTest, DisallowObsTextBehaviorAllow) { + if (skipForUhv() || http2_implementation_ != Http2Impl::Oghttp2) { + return; + } + + // With disallow_obs_text = false, the request is accepted. + server_http2_options_.mutable_disallow_obs_text()->set_value(false); + initialize(); + + InSequence s; + TestRequestHeaderMapImpl request_headers; + HttpTestUtility::addDefaultHeaders(request_headers); + + HeaderString header_name; + header_name.setCopy("custom-header"); + HeaderString header_value; + setHeaderStringUnvalidated(header_value, "foo\x80"); + request_headers.addViaMove(std::move(header_name), std::move(header_value)); + + EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); + driveToCompletion(); + EXPECT_TRUE(server_wrapper_->status_.ok()); +} + TEST_P(Http2CodecImplTest, SimpleRequestResponse) { initialize(); diff --git a/test/common/quic/envoy_quic_server_stream_test.cc b/test/common/quic/envoy_quic_server_stream_test.cc index 47ae27058787a..91bea50096d6e 100644 --- a/test/common/quic/envoy_quic_server_stream_test.cc +++ b/test/common/quic/envoy_quic_server_stream_test.cc @@ -984,5 +984,42 @@ TEST_F(EnvoyQuicServerStreamTest, DuplicatedPathHeader) { quic_stream_->OnStreamHeaderList(true, 0, header_list); } +TEST_F(EnvoyQuicServerStreamTest, DisallowObsTextBehavior) { + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)).Times(testing::AnyNumber()); + { + // Default behavior is to disallow obs text. + envoy::config::core::v3::Http3ProtocolOptions options; + auto stream = std::make_unique( + stream_id_ + 12, &quic_session_, quic::BIDIRECTIONAL, stats_, options, + envoy::config::core::v3::HttpProtocolOptions::ALLOW); + // \x80 is obsolete text + EXPECT_EQ(Http::HeaderUtility::HeaderValidationResult::REJECT, + stream->validateHeader("custom-header", "foo\x80")); + EXPECT_EQ(Http::HeaderUtility::HeaderValidationResult::ACCEPT, + stream->validateHeader("custom-header", "foo")); + } + { + envoy::config::core::v3::Http3ProtocolOptions options; + options.mutable_disallow_obs_text()->set_value(true); + auto stream = std::make_unique( + stream_id_ + 16, &quic_session_, quic::BIDIRECTIONAL, stats_, options, + envoy::config::core::v3::HttpProtocolOptions::ALLOW); + // \x80 is obsolete text + EXPECT_EQ(Http::HeaderUtility::HeaderValidationResult::REJECT, + stream->validateHeader("custom-header", "foo\x80")); + EXPECT_EQ(Http::HeaderUtility::HeaderValidationResult::ACCEPT, + stream->validateHeader("custom-header", "foo")); + } + { + envoy::config::core::v3::Http3ProtocolOptions options; + options.mutable_disallow_obs_text()->set_value(false); + auto stream = std::make_unique( + stream_id_ + 20, &quic_session_, quic::BIDIRECTIONAL, stats_, options, + envoy::config::core::v3::HttpProtocolOptions::ALLOW); + EXPECT_EQ(Http::HeaderUtility::HeaderValidationResult::ACCEPT, + stream->validateHeader("custom-header", "foo\x80")); + } +} + } // namespace Quic } // namespace Envoy diff --git a/test/integration/default_header_validator_integration_test.cc b/test/integration/default_header_validator_integration_test.cc index 5e93fddbc53df..a2852cea46394 100644 --- a/test/integration/default_header_validator_integration_test.cc +++ b/test/integration/default_header_validator_integration_test.cc @@ -80,7 +80,7 @@ class DownstreamUhvIntegrationTest : public HttpProtocolIntegrationTest { // All codecs allow the following characters that are outside of RFC "<>[]^`{}\| std::string additionally_allowed_characters(R"--("<>[]^`{}\|)--"); if (downstream_protocol_ == Http::CodecType::HTTP2) { - // Both nghttp2 and oghttp2 allow extended ASCII >= 0x80 in path + // Both nghttp2 and oghttp2 allow extended ASCII >= 0x80 in path. additionally_allowed_characters += generateExtendedAsciiString(); } return additionally_allowed_characters; From a99a8b011a08de4002957872016bbb7368d9ca0b Mon Sep 17 00:00:00 2001 From: Ting Pan Date: Wed, 15 Apr 2026 15:34:22 -0400 Subject: [PATCH 034/750] QUIC: Add memory_reduction_timeout configuration (#44408) Commit Message: quic: add memory_reduction_timeout configuration support Additional Description: Added a new configuration option `memory_reduction_timeout` to `QuicProtocolOptions` to allow setting a timeout for memory reduction callbacks when the network is idle. Risk Level: low Testing: N/A Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Ting Pan --- api/envoy/config/core/v3/protocol.proto | 8 +++++- .../common/quic/envoy_quic_server_session.cc | 8 ++++++ .../quic/envoy_quic_server_session_test.cc | 26 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index 9a793ed941d9b..153916f09765a 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -57,7 +57,7 @@ message QuicKeepAliveSettings { } // QUIC protocol options which apply to both downstream and upstream connections. -// [#next-free-field: 12] +// [#next-free-field: 13] message QuicProtocolOptions { // Config for QUIC connection migration across network interfaces, i.e. cellular to WIFI, upon // network change events from the platform, i.e. the current network gets @@ -173,6 +173,12 @@ message QuicProtocolOptions { // If absent, the feature will be disabled. // [#not-implemented-hide:] ConnectionMigrationSettings connection_migration = 11; + + // Timeout for a QUIC connection to schedule memory reduction callback when the network has been idle for a while. + // This value should be smaller than the idle timeout to take effect. + // If not specified, memory reduction is set to infinite by QUIC connection (disabled). + google.protobuf.Duration memory_reduction_timeout = 12 + [(validate.rules).duration = {gte {seconds: 1}}]; } message UpstreamHttpProtocolOptions { diff --git a/source/common/quic/envoy_quic_server_session.cc b/source/common/quic/envoy_quic_server_session.cc index 81c432769ced2..09f6b1630a5d8 100644 --- a/source/common/quic/envoy_quic_server_session.cc +++ b/source/common/quic/envoy_quic_server_session.cc @@ -214,6 +214,14 @@ void EnvoyQuicServerSession::setHttp3Options( } } } + if (http3_options.has_quic_protocol_options()) { + const uint64_t memory_reduction_timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT( + http3_options.quic_protocol_options(), memory_reduction_timeout, 0); + if (memory_reduction_timeout_ms > 0) { + connection()->SetMemoryReductionTimeout( + quic::QuicTime::Delta::FromMilliseconds(memory_reduction_timeout_ms)); + } + } set_allow_extended_connect(http3_options_->allow_extended_connect()); if (http3_options_->disable_qpack()) { DisableHuffmanEncoding(); diff --git a/test/common/quic/envoy_quic_server_session_test.cc b/test/common/quic/envoy_quic_server_session_test.cc index 0046ceeb057f9..788a31ff9e48f 100644 --- a/test/common/quic/envoy_quic_server_session_test.cc +++ b/test/common/quic/envoy_quic_server_session_test.cc @@ -1346,6 +1346,32 @@ TEST_F(EnvoyQuicServerSessionTest, SessionIdleCallbacksIdempotency) { EXPECT_CALL(session_idle_list_, RemoveSession(_)); } +TEST_F(EnvoyQuicServerSessionTest, MemoryReductionTimeoutTest) { + envoy::config::core::v3::Http3ProtocolOptions http3_options; + auto* quic_options = http3_options.mutable_quic_protocol_options(); + quic_options->mutable_memory_reduction_timeout()->set_seconds(300); + + // Mark handshake complete and set connection idle timeout to a large duration. + quic::test::QuicConnectionPeer::GetIdleNetworkDetector(quic_connection_) + .SetTimeouts(quic::QuicTime::Delta::Infinite(), quic::QuicTime::Delta::FromSeconds(600)); + + envoy_quic_session_.setHttp3Options(http3_options); + + // Trigger SetAlarm. + quic::test::QuicConnectionPeer::GetIdleNetworkDetector(quic_connection_) + .OnPacketReceived(connection_helper_.GetClock()->Now()); + + // Check the alarm deadline. + quic::QuicAlarmProxy idle_detector_alarm = + quic::test::QuicConnectionPeer::GetIdleNetworkDetectorAlarm(quic_connection_); + + EXPECT_TRUE(idle_detector_alarm.IsSet()); + EXPECT_EQ(connection_helper_.GetClock()->Now() + quic::QuicTime::Delta::FromSeconds(300), + idle_detector_alarm.deadline()); + + installReadFilter(); +} + class EnvoyQuicServerSessionTestWillNotInitialize : public EnvoyQuicServerSessionTest { void SetUp() override {} void TearDown() override { From de4868777365628e649d957aa597202be3a0ed1c Mon Sep 17 00:00:00 2001 From: Ting Pan Date: Wed, 15 Apr 2026 22:21:48 -0400 Subject: [PATCH 035/750] HTTP: add overload action to close idle HTTP connections (#43612) Commit Message: HTTP: add overload action to close idle HTTP connections Additional Description: This change introduces the "CloseIdleHttpConnections" overload action to proactively terminate idle downstream connections during resource overload. The implementation currently supports QUIC connections only, with HTTP/2 support planned for a follow-up change. A periodic timer is established on worker threads upon action activation to invoke the closing logic across active listeners, guarded by a reloadable runtime feature. For QUIC, a new configuration option is added to enable this behavior, which leverages the session idle list to terminate sessions. Risk Level: low Testing: added overload integration test Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Ting Pan --- changelogs/current.yaml | 5 ++ .../overload_manager/overload_manager.rst | 6 ++ envoy/network/connection_handler.h | 11 ++++ envoy/server/overload/overload_manager.h | 8 ++- .../overload/thread_local_overload_state.h | 15 +++++ .../connection_handler_impl.cc | 12 ++++ .../connection_handler_impl.h | 1 + source/common/quic/active_quic_listener.cc | 25 ++++++-- source/common/quic/active_quic_listener.h | 4 +- source/common/quic/envoy_quic_dispatcher.cc | 1 + source/server/worker_impl.cc | 45 +++++++++++++++ source/server/worker_impl.h | 5 ++ test/integration/overload_integration_test.cc | 57 +++++++++++++++++++ test/mocks/network/mocks.h | 1 + test/server/worker_impl_test.cc | 52 +++++++++++++++++ 15 files changed, 241 insertions(+), 7 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 4321951cd4e1e..df7fd9a6eadd4 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1018,6 +1018,11 @@ new_features: - area: ratelimit change: | Support populating rate limit descriptors from cluster locality metadata. +- area: http + change: | + Added a new overload action :ref:`envoy.overload_actions.close_idle_http_connections ` + that closes idle downstream HTTP connections when the overload action is active. Currently only HTTP/3 idle connections + trimming is supported, and HTTP/1 and HTTP/2 support are to be implemented. - area: contrib change: | diff --git a/docs/root/configuration/operations/overload_manager/overload_manager.rst b/docs/root/configuration/operations/overload_manager/overload_manager.rst index 73acf6b58b989..bdb0539ed5a06 100644 --- a/docs/root/configuration/operations/overload_manager/overload_manager.rst +++ b/docs/root/configuration/operations/overload_manager/overload_manager.rst @@ -149,6 +149,12 @@ The following overload actions are supported: - Envoy will reset expensive streams to terminate them. See :ref:`below ` for details on configuration. + * - envoy.overload_actions.close_idle_http_connections + - Envoy will close idle downstream HTTP/3 QUIC connections when the action is active. + When the action is *saturated*, connections will be closed aggressively (ignoring the idle timer threshold). + When the action is in a *scaled active* state, the idle timer threshold is still respected. + Note that this action is currently only supported for HTTP/3 QUIC connections. + .. _config_overload_manager_shrink_heap: Shrink Heap diff --git a/envoy/network/connection_handler.h b/envoy/network/connection_handler.h index ed665428b6309..b9317772a19a9 100644 --- a/envoy/network/connection_handler.h +++ b/envoy/network/connection_handler.h @@ -127,6 +127,11 @@ class ConnectionHandler { */ virtual const std::string& statPrefix() const PURE; + /** + * Close idle HTTP connections. + */ + virtual void closeIdleHttpConnections(bool is_saturated) PURE; + /** * Used by ConnectionHandler to manage listeners. */ @@ -172,6 +177,12 @@ class ConnectionHandler { */ virtual void onFilterChainDraining( const std::list& draining_filter_chains) PURE; + + // New method for handling idle connection closing + virtual void onCloseIdleHttpConnections(bool /*is_saturated*/) { + // Default implementation does nothing. + // Specific listener types (TCP, QUIC) will override this. + } }; using ActiveListenerPtr = std::unique_ptr; diff --git a/envoy/server/overload/overload_manager.h b/envoy/server/overload/overload_manager.h index 7e1aaa30eda8f..85714afbd0188 100644 --- a/envoy/server/overload/overload_manager.h +++ b/envoy/server/overload/overload_manager.h @@ -42,16 +42,20 @@ class OverloadActionNameValues { // Overload action to reset streams using excessive memory. const std::string ResetStreams = "envoy.overload_actions.reset_high_memory_stream"; + // Overload action to terminate idle downstream HTTP connections. + const std::string CloseIdleHttpConnections = "envoy.overload_actions.close_idle_http_connections"; + // This should be kept current with the Overload actions available. // This is the last member of this class to duplicating the strings with // proper lifetime guarantees. - const std::array WellKnownActions = {StopAcceptingRequests, + const std::array WellKnownActions = {StopAcceptingRequests, DisableHttpKeepAlive, StopAcceptingConnections, RejectIncomingConnections, ShrinkHeap, ReduceTimeouts, - ResetStreams}; + ResetStreams, + CloseIdleHttpConnections}; }; using OverloadActionNames = ConstSingleton; diff --git a/envoy/server/overload/thread_local_overload_state.h b/envoy/server/overload/thread_local_overload_state.h index 7bf014ec29776..fef659053ff63 100644 --- a/envoy/server/overload/thread_local_overload_state.h +++ b/envoy/server/overload/thread_local_overload_state.h @@ -49,6 +49,12 @@ using OverloadProactiveResources = ConstSingletoninvokeListenerMethod( + [is_saturated](Network::ConnectionHandler::ActiveListener& active_listener) { + if (active_listener.listener() != nullptr && + !active_listener.listener()->shouldBypassOverloadManager()) { + active_listener.onCloseIdleHttpConnections(is_saturated); + } + }); + } +} + Network::InternalListenerOptRef ConnectionHandlerImpl::findByAddress(const Network::Address::InstanceConstSharedPtr& address) { ASSERT(address->type() == Network::Address::Type::EnvoyInternal); diff --git a/source/common/listener_manager/connection_handler_impl.h b/source/common/listener_manager/connection_handler_impl.h index c5a704ed9a70b..917b090876638 100644 --- a/source/common/listener_manager/connection_handler_impl.h +++ b/source/common/listener_manager/connection_handler_impl.h @@ -57,6 +57,7 @@ class ConnectionHandlerImpl : public ConnectionHandler, void enableListeners() override; void setListenerRejectFraction(UnitFloat reject_fraction) override; const std::string& statPrefix() const override { return per_handler_stat_prefix_; } + void closeIdleHttpConnections(bool is_saturated) override; // Network::TcpConnectionHandler Event::Dispatcher& dispatcher() override { return dispatcher_; } diff --git a/source/common/quic/active_quic_listener.cc b/source/common/quic/active_quic_listener.cc index a15cb612171df..392258d6a5162 100644 --- a/source/common/quic/active_quic_listener.cc +++ b/source/common/quic/active_quic_listener.cc @@ -1,15 +1,19 @@ #include "source/common/quic/active_quic_listener.h" +#include #include #include +#include "envoy/common/optref.h" #include "envoy/extensions/quic/connection_id_generator/v3/envoy_deterministic_connection_id_generator.pb.h" #include "envoy/extensions/quic/crypto_stream/v3/crypto_stream.pb.h" #include "envoy/extensions/quic/proof_source/v3/proof_source.pb.h" #include "envoy/network/exception.h" +#include "envoy/server/overload/overload_manager.h" #include "source/common/common/logger.h" #include "source/common/config/utility.h" +#include "source/common/http/session_idle_list.h" #include "source/common/http/utility.h" #include "source/common/network/socket_option_impl.h" #include "source/common/network/udp_listener_impl.h" @@ -39,7 +43,7 @@ ActiveQuicListener::ActiveQuicListener( EnvoyQuicProofSourceFactoryInterface& proof_source_factory, QuicConnectionIdGeneratorPtr&& cid_generator, QuicConnectionIdWorkerSelector worker_selector, EnvoyQuicConnectionDebugVisitorFactoryInterfaceOptRef debug_visitor_factory, - bool reject_new_connections) + bool reject_new_connections, bool enable_session_idle_list) : Server::ActiveUdpListenerBase( worker_index, concurrency, parent, *listen_socket, std::make_unique( @@ -91,12 +95,12 @@ ActiveQuicListener::ActiveQuicListener( listen_socket_.setSocketOption(IPPROTO_IP, IP_RECVTOS, &optval, optlen); } } - // TODO(panting): Pass in a non-null session_idle_list when configured. quic_dispatcher_ = std::make_unique( crypto_config_.get(), quic_config, &version_manager_, std::move(connection_helper), std::move(alarm_factory), quic::kQuicDefaultConnectionIdLength, parent, *config_, stats_, per_worker_stats_, dispatcher, listen_socket_, quic_stat_names, crypto_server_stream_factory_, - *connection_id_generator_, debug_visitor_factory, /*session_idle_list=*/nullptr); + *connection_id_generator_, debug_visitor_factory, + enable_session_idle_list ? std::make_unique(dispatcher) : nullptr); absl::AnyInvocable on_can_write_cb = [&]() { quic_dispatcher_->OnCanWrite(); }; @@ -272,6 +276,10 @@ void ActiveQuicListener::closeConnectionsWithFilterChain(const Network::FilterCh quic_dispatcher_->closeConnectionsWithFilterChain(filter_chain); } +void ActiveQuicListener::onCloseIdleHttpConnections(bool is_saturated) { + quic_dispatcher_->closeIdleQuicConnections(is_saturated); +} + ActiveQuicListenerFactory::ActiveQuicListenerFactory( const envoy::config::listener::v3::QuicProtocolOptions& config, uint32_t concurrency, QuicStatNames& quic_stat_names, ProtobufMessage::ValidationVisitor& validation_visitor, @@ -447,12 +455,21 @@ ActiveQuicListenerFactory::createActiveQuicListener( EnvoyQuicCryptoServerStreamFactoryInterface& crypto_server_stream_factory, EnvoyQuicProofSourceFactoryInterface& proof_source_factory, QuicConnectionIdGeneratorPtr&& cid_generator) { + bool enable_session_idle_list = false; + for (const auto& action : + context_.serverFactoryContext().bootstrap().overload_manager().actions()) { + if (action.name() == Server::OverloadActionNames::get().CloseIdleHttpConnections) { + enable_session_idle_list = true; + break; + } + } return std::make_unique( runtime, worker_index, concurrency, dispatcher, parent, std::move(listen_socket), listener_config, quic_config, kernel_worker_routing, enabled, quic_stat_names, packets_to_read_to_connection_count_ratio, crypto_server_stream_factory, proof_source_factory, std::move(cid_generator), worker_selector_, - makeOptRefFromPtr(connection_debug_visitor_factory_.get()), reject_new_connections_); + makeOptRefFromPtr(connection_debug_visitor_factory_.get()), reject_new_connections_, + enable_session_idle_list); } } // namespace Quic diff --git a/source/common/quic/active_quic_listener.h b/source/common/quic/active_quic_listener.h index c354f44ad707b..377d041e128cc 100644 --- a/source/common/quic/active_quic_listener.h +++ b/source/common/quic/active_quic_listener.h @@ -42,7 +42,7 @@ class ActiveQuicListener : public Envoy::Server::ActiveUdpListenerBase, QuicConnectionIdGeneratorPtr&& cid_generator, QuicConnectionIdWorkerSelector worker_selector, EnvoyQuicConnectionDebugVisitorFactoryInterfaceOptRef debug_visitor_factory, - bool reject_new_connections = false); + bool reject_new_connections = false, bool enable_session_idle_list = false); ~ActiveQuicListener() override; @@ -73,6 +73,8 @@ class ActiveQuicListener : public Envoy::Server::ActiveUdpListenerBase, void onFilterChainDraining( const std::list& draining_filter_chains) override; + void onCloseIdleHttpConnections(bool is_saturated) override; + protected: Event::Dispatcher& dispatcher() { return dispatcher_; } diff --git a/source/common/quic/envoy_quic_dispatcher.cc b/source/common/quic/envoy_quic_dispatcher.cc index 6ec210a8fdb9c..225b95d489d20 100644 --- a/source/common/quic/envoy_quic_dispatcher.cc +++ b/source/common/quic/envoy_quic_dispatcher.cc @@ -215,6 +215,7 @@ void EnvoyQuicDispatcher::updateListenerConfig(Network::ListenerConfig& new_list void EnvoyQuicDispatcher::closeIdleQuicConnections(bool is_saturated) { // This method is called from the worker thread, triggered by the // Overload Manager. + ASSERT(session_idle_list_ != nullptr); session_idle_list_->MaybeTerminateIdleSessions(is_saturated); } diff --git a/source/server/worker_impl.cc b/source/server/worker_impl.cc index 9b0604ecb8282..c14cc3498c5ce 100644 --- a/source/server/worker_impl.cc +++ b/source/server/worker_impl.cc @@ -1,5 +1,6 @@ #include "source/server/worker_impl.h" +#include #include #include @@ -16,6 +17,9 @@ namespace Envoy { namespace Server { namespace { +constexpr std::chrono::milliseconds kCloseIdleHttpConnectionsInterval = + std::chrono::milliseconds(100); + std::unique_ptr getHandler(Event::Dispatcher& dispatcher, uint32_t index, OverloadManager& overload_manager, OverloadManager& null_overload_manager) { @@ -59,6 +63,10 @@ WorkerImpl::WorkerImpl(ThreadLocal::Instance& tls, ListenerHooks& hooks, overload_manager.registerForAction( OverloadActionNames::get().ResetStreams, *dispatcher_, [this](OverloadActionState state) { resetStreamsUsingExcessiveMemory(state); }); + + overload_manager.registerForAction( + OverloadActionNames::get().CloseIdleHttpConnections, *dispatcher_, + [this](OverloadActionState state) { closeIdleHttpConnectionsCb(state.phase()); }); } void WorkerImpl::addListener(absl::optional overridden_listener, @@ -125,6 +133,7 @@ void WorkerImpl::stop() { // It's possible for the server to cleanly shut down while cluster initialization during startup // is happening, so we might not yet have a thread. if (thread_) { + close_idle_connection_timer_ = nullptr; dispatcher_->exit(); thread_->join(); } @@ -186,5 +195,41 @@ void WorkerImpl::resetStreamsUsingExcessiveMemory(OverloadActionState state) { reset_streams_counter_.add(streams_reset_count); } +void WorkerImpl::closeIdleHttpConnectionsCb(OverloadActionState::Phase phase) { + if (close_idle_http_connections_state_ == phase) { + return; + } + + close_idle_http_connections_state_ = phase; + + // Lazy initialize the timer if it does not exist. + if (close_idle_connection_timer_ == nullptr) { + close_idle_connection_timer_ = dispatcher_->createTimer([this]() { + maybeCloseIdleHttpConnections(); + ASSERT(close_idle_http_connections_state_ != OverloadActionState::Phase::Inactive); + close_idle_connection_timer_->enableTimer(kCloseIdleHttpConnectionsInterval); + }); + } + + if (close_idle_http_connections_state_ != OverloadActionState::Phase::Inactive) { + if (!close_idle_connection_timer_->enabled()) { + close_idle_connection_timer_->enableTimer(kCloseIdleHttpConnectionsInterval); + } + } else { + ASSERT(close_idle_connection_timer_->enabled()); + close_idle_connection_timer_->disableTimer(); + } +} + +void WorkerImpl::maybeCloseIdleHttpConnections() { + if (handler_) { + // If state is saturated, aggressive closure is triggered (ignoring the + // idle timer threshold). Otherwise, it's a "scaled active" state which + // respects it. + handler_->closeIdleHttpConnections(close_idle_http_connections_state_ == + OverloadActionState::Phase::Saturated); + } +} + } // namespace Server } // namespace Envoy diff --git a/source/server/worker_impl.h b/source/server/worker_impl.h index 5f056d66f60e3..47ab58e11f751 100644 --- a/source/server/worker_impl.h +++ b/source/server/worker_impl.h @@ -74,6 +74,8 @@ class WorkerImpl : public Worker, Logger::Loggable { void stopAcceptingConnectionsCb(OverloadActionState state); void rejectIncomingConnectionsCb(OverloadActionState state); void resetStreamsUsingExcessiveMemory(OverloadActionState state); + void closeIdleHttpConnectionsCb(OverloadActionState::Phase phase); + void maybeCloseIdleHttpConnections(); ThreadLocal::Instance& tls_; ListenerHooks& hooks_; @@ -83,6 +85,9 @@ class WorkerImpl : public Worker, Logger::Loggable { Stats::Counter& reset_streams_counter_; Thread::ThreadPtr thread_; WatchDogSharedPtr watch_dog_; + Event::TimerPtr close_idle_connection_timer_; + OverloadActionState::Phase close_idle_http_connections_state_ = + OverloadActionState::Phase::Inactive; }; } // namespace Server diff --git a/test/integration/overload_integration_test.cc b/test/integration/overload_integration_test.cc index b069f79559684..6b45429ccd435 100644 --- a/test/integration/overload_integration_test.cc +++ b/test/integration/overload_integration_test.cc @@ -298,6 +298,63 @@ TEST_P(OverloadIntegrationTest, BypassOverloadManagerTest) { codec_client_->close(); } +// TODO: add another test for HTTP2 +TEST_P(OverloadIntegrationTest, CloseIdleQuicConnectionsWhenOverloaded) { + if (downstreamProtocol() != Http::CodecType::HTTP3) { + return; // only relevant for HTTP/3 + } + + initializeOverloadManager( + TestUtility::parseYaml(R"EOF( + name: "envoy.overload_actions.close_idle_http_connections" + triggers: + - name: "envoy.resource_monitors.testonly.fake_resource_monitor" + scaled: + scaling_threshold: 0.8 + saturation_threshold: 0.9 + )EOF")); + + // 1. Establish a QUIC connection + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + // Wait for the connection to be fully established. + test_server_->waitForCounterGe("http.config_test.downstream_cx_http3_total", 1); + + // 2. Send a request and wait for the response to complete. + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "sni.lyft.com"}}; + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + + // At this point, the EnvoyQuicServerSession should have added itself to the + // EnvoyQuicDispatcher's idle list. + + // 2. Trigger the overload state + updateResource(0.95); // Set pressure to 0.95, above the 0.9 saturation threshold + test_server_->waitForGaugeEq("overload.envoy.overload_actions.close_idle_http_connections.active", + 1); + + // 3. Advance time to trigger the check_idle_connection_timer (which runs every 100ms). + timeSystem().advanceTimeWait(std::chrono::milliseconds(100)); + + // 4. Wait for the connection to be closed by the server. + ASSERT_TRUE(codec_client_->waitForDisconnect()); + + // Check that the close reason was correct (this stat is incremented in + // EnvoyQuicDispatcher) + test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + + codec_client_->close(); + + // Deactivate overload state + updateResource(0.7); + test_server_->waitForGaugeEq("overload.envoy.overload_actions.close_idle_http_connections.active", + 0); +} + class Http2RawFrameOverloadIntegrationTest : public BaseOverloadIntegrationTest, public Http2RawFrameIntegrationTest, public testing::Test { diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index 6243d8d4aa3cb..ce5d6e9107486 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -582,6 +582,7 @@ class MockConnectionHandler : public virtual ConnectionHandler { MOCK_METHOD(void, enableListeners, ()); MOCK_METHOD(void, setListenerRejectFraction, (UnitFloat), (override)); MOCK_METHOD(const std::string&, statPrefix, (), (const)); + MOCK_METHOD(void, closeIdleHttpConnections, (bool), (override)); uint64_t num_handler_connections_{}; }; diff --git a/test/server/worker_impl_test.cc b/test/server/worker_impl_test.cc index cf4694318064f..695f654ebc24f 100644 --- a/test/server/worker_impl_test.cc +++ b/test/server/worker_impl_test.cc @@ -10,6 +10,7 @@ #include "test/mocks/server/instance.h" #include "test/mocks/server/overload_manager.h" #include "test/mocks/thread_local/mocks.h" +#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "absl/synchronization/notification.h" @@ -159,6 +160,57 @@ TEST_F(WorkerImplTest, WorkerInvokesProvidedCallback) { worker_.stop(); } +class WorkerOverloadTest : public testing::Test, public Event::TestUsingSimulatedTime { +public: + WorkerOverloadTest() + : api_(Api::createApiForTest(simTime())), + dispatcher_(api_->allocateDispatcher("worker_test")), + stat_names_(api_->rootScope().symbolTable()) {} + + NiceMock tls_; + DefaultListenerHooks hooks_; + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + WorkerStatNames stat_names_; + NiceMock overload_manager_; +}; + +TEST_F(WorkerOverloadTest, CloseIdleHttpConnections) { + OverloadActionCb captured_cb; + EXPECT_CALL(overload_manager_, registerForAction(_, _, _)) + .WillRepeatedly(Invoke([&](const std::string& name, Event::Dispatcher&, OverloadActionCb cb) { + if (name == OverloadActionNames::get().CloseIdleHttpConnections) { + captured_cb = cb; + } + return true; + })); + + auto* handler = new NiceMock(); + auto* dispatcher_ptr = dispatcher_.get(); + WorkerImpl worker(tls_, hooks_, std::move(dispatcher_), Network::ConnectionHandlerPtr{handler}, + overload_manager_, *api_, stat_names_); + + ASSERT_TRUE(captured_cb != nullptr); + + auto trigger_and_advance = [&](OverloadActionState state, std::chrono::milliseconds duration = + std::chrono::milliseconds(100)) { + captured_cb(state); + simTime().advanceTimeAndRun(duration, *dispatcher_ptr, Event::Dispatcher::RunType::NonBlock); + }; + + // 1. Transition to scaling (active) + EXPECT_CALL(*handler, closeIdleHttpConnections(false)); + trigger_and_advance(OverloadActionState(UnitFloat(0.5))); + + // 2. Transition to saturated + EXPECT_CALL(*handler, closeIdleHttpConnections(true)); + trigger_and_advance(OverloadActionState::saturated()); + + // 3. Transition back to inactive + EXPECT_CALL(*handler, closeIdleHttpConnections(_)).Times(0); + trigger_and_advance(OverloadActionState::inactive()); +} + } // namespace } // namespace Server } // namespace Envoy From b234fcc7971e1bf63836f6161cea83b3dd20adce Mon Sep 17 00:00:00 2001 From: Henry Wang Date: Wed, 15 Apr 2026 23:36:59 -0400 Subject: [PATCH 036/750] contrib/golang: address race condition in Go HTTP filter (#44429) --- .../filters/http/source/go/pkg/http/shim.go | 10 +++ .../filters/http/source/golang_filter.cc | 10 +++ .../filters/http/source/golang_filter.h | 2 + contrib/golang/filters/http/test/BUILD | 2 + .../filters/http/test/golang_filter_test.cc | 76 ++++++++++++++++++- 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/contrib/golang/filters/http/source/go/pkg/http/shim.go b/contrib/golang/filters/http/source/go/pkg/http/shim.go index 56fb64884aa5c..6576cf8ecae3c 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/shim.go +++ b/contrib/golang/filters/http/source/go/pkg/http/shim.go @@ -157,6 +157,9 @@ func getRequest(r *C.httpRequest) *httpRequest { func getState(s *C.processState) *processState { r := s.req req := getRequest(r) + if req == nil { + return nil + } if s.is_encoding == 0 { return &req.decodingState.processState } @@ -237,6 +240,10 @@ func envoyGoFilterOnHttpHeader(s *C.processState, endStream, headerNum, headerBy //export envoyGoFilterOnHttpData func envoyGoFilterOnHttpData(s *C.processState, endStream, buffer, length uint64) uint64 { state := getState(s) + if state == nil { + // safe to do as the C++ side hasDestroyed() check prevents acting on the returned value + return uint64(api.Continue) + } req := state.request if req.pInfo.paniced { @@ -389,6 +396,9 @@ func envoyGoFilterOnHttpDestroy(r *C.httpRequest, reason uint64) { //export envoyGoRequestSemaDec func envoyGoRequestSemaDec(r *C.httpRequest) { req := getRequest(r) + if req == nil { + return + } defer req.recoverPanic() req.resumeWaitCallback() } diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index f1a626f2e3427..104e7fc35c2f8 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -404,12 +404,19 @@ void Filter::continueStatusInternal(ProcessorState& state, GolangStatus status) case FilterState::ProcessingTrailer: state.continueDoData(); + if (hasDestroyed()) { + return; + } state.continueProcessing(); break; default: ASSERT(0, "unexpected state"); } + + if (hasDestroyed()) { + return; + } } ENVOY_LOG(debug, @@ -426,6 +433,9 @@ void Filter::continueStatusInternal(ProcessorState& state, GolangStatus status) auto done = doDataGo(state, state.getBufferData(), state.getEndStream()); if (done) { state.continueDoData(); + if (hasDestroyed()) { + return; + } } else { // do not process trailers when data is not finished return; diff --git a/contrib/golang/filters/http/source/golang_filter.h b/contrib/golang/filters/http/source/golang_filter.h index 3e86b9f8e6e02..3160d712bcc5a 100644 --- a/contrib/golang/filters/http/source/golang_filter.h +++ b/contrib/golang/filters/http/source/golang_filter.h @@ -348,6 +348,8 @@ class Filter : public Http::StreamFilter, void deferredDeleteRequest(HttpRequestInternal* req); private: + friend class TestFilter; + bool hasDestroyed() { Thread::LockGuard lock(mutex_); return has_destroyed_; diff --git a/contrib/golang/filters/http/test/BUILD b/contrib/golang/filters/http/test/BUILD index fe9da4ac74a74..3c5c9f2c96550 100644 --- a/contrib/golang/filters/http/test/BUILD +++ b/contrib/golang/filters/http/test/BUILD @@ -33,7 +33,9 @@ envoy_cc_test( ], rbe_pool = "6gig", deps = [ + "//contrib/golang/common/dso/test:dso_mocks", "//contrib/golang/filters/http/source:golang_filter_lib", + "//source/common/network:address_lib", "//source/common/stream_info:stream_info_lib", "//test/mocks/api:api_mocks", "//test/mocks/http:http_mocks", diff --git a/contrib/golang/filters/http/test/golang_filter_test.cc b/contrib/golang/filters/http/test/golang_filter_test.cc index 53b0725f6ae84..d6583926a3e43 100644 --- a/contrib/golang/filters/http/test/golang_filter_test.cc +++ b/contrib/golang/filters/http/test/golang_filter_test.cc @@ -5,6 +5,7 @@ #include "source/common/buffer/buffer_impl.h" #include "source/common/http/message_impl.h" +#include "source/common/network/address_impl.h" #include "source/common/stream_info/stream_info_impl.h" #include "test/common/stats/stat_test_utility.h" @@ -21,6 +22,7 @@ #include "test/test_common/utility.h" #include "absl/strings/str_format.h" +#include "contrib/golang/common/dso/test/mocks.h" #include "contrib/golang/filters/http/source/golang_filter.h" #include "gmock/gmock.h" @@ -28,18 +30,24 @@ using testing::_; using testing::AtLeast; using testing::InSequence; using testing::Invoke; +using testing::Return; +using testing::SaveArg; namespace Envoy { namespace Extensions { namespace HttpFilters { namespace Golang { -namespace { class TestFilter : public Filter { public: + using Filter::continueStatusInternal; using Filter::Filter; + DecodingProcessorState& testDecodingState() { return decoding_state_; } + HttpRequestInternal* testReq() { return req_; } }; +namespace { + class GolangHttpFilterTest : public testing::Test { public: GolangHttpFilterTest() { @@ -191,6 +199,72 @@ TEST_F(GolangHttpFilterTest, InvalidConfigForRouteConfigFilter) { "golang filter failed to parse plugin config"); } +// Regression test for https://github.com/envoyproxy/envoy/issues/44320. +TEST_F(GolangHttpFilterTest, BufferedDataAfterDestroyDuringContinue) { + auto dso_lib = std::make_shared>(); + ON_CALL(*dso_lib, envoyGoFilterNewHttpPluginConfig(_)).WillByDefault(Return(1)); + ON_CALL(*dso_lib, envoyGoFilterOnHttpHeader(_, _, _, _)) + .WillByDefault(Return(static_cast(GolangStatus::Running))); + + bool destroyed = false; + bool data_called_after_destroy = false; + ON_CALL(*dso_lib, envoyGoFilterOnHttpData(_, _, _, _)) + .WillByDefault(Invoke( + [&destroyed, &data_called_after_destroy](processState*, GoUint64, GoUint64, GoUint64) { + if (destroyed) { + data_called_after_destroy = true; + } + return static_cast(GolangStatus::Continue); + })); + ON_CALL(*dso_lib, envoyGoFilterOnHttpDestroy(_, _)) + .WillByDefault(Invoke([&destroyed](httpRequest*, int) { destroyed = true; })); + + const auto yaml = R"EOF( + library_id: test + library_path: test + plugin_name: test + )EOF"; + envoy::extensions::filters::http::golang::v3alpha::Config proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + NiceMock mock_context; + auto config = std::make_shared(proto_config, dso_lib, "", mock_context); + config->newGoPluginConfig(); + + Network::Address::InstanceConstSharedPtr addr( + (*Network::Address::PipeInstance::create("/test/test.sock")).release()); + NiceMock mock_callbacks; + NiceMock mock_enc_callbacks; + NiceMock mock_connection; + ON_CALL(mock_callbacks, connection()) + .WillByDefault(Return(OptRef{mock_connection})); + mock_connection.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr); + mock_connection.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr); + EXPECT_CALL(mock_callbacks.dispatcher_, isThreadSafe()).WillRepeatedly(Return(true)); + + auto filter = std::make_shared(config, dso_lib, 0); + filter->setDecoderFilterCallbacks(mock_callbacks); + filter->setEncoderFilterCallbacks(mock_enc_callbacks); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter->decodeHeaders(request_headers, false)); + + Buffer::OwnedImpl body("request body"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter->decodeData(body, false)); + + EXPECT_CALL(mock_callbacks, continueDecoding()).WillOnce(Invoke([&filter]() { + filter->onDestroy(); + })); + + filter->continueStatusInternal(filter->testDecodingState(), GolangStatus::Continue); + + EXPECT_FALSE(data_called_after_destroy) + << "envoyGoFilterOnHttpData must not be called after onDestroy"; + + ASSERT_NE(nullptr, filter->testReq()); + delete filter->testReq(); +} + } // namespace } // namespace Golang } // namespace HttpFilters From 0614ecae8c9d20e26940156ba5b92b71b79ecd74 Mon Sep 17 00:00:00 2001 From: Derek Argueta Date: Thu, 16 Apr 2026 06:09:27 -0500 Subject: [PATCH 037/750] lua: add stats API for creating counters, gauges, and histograms (#44334) Adds a `stats()` method to the Lua stream handle that returns a stats scope object, allowing Lua scripts to create and manipulate Envoy stats directly. This has been a requested feature for a while - being able to emit custom metrics from Lua scripts without needing to write a native filter. The API provides: - `counter(name)` - create/get a counter with `inc()`, `add()`, `value()` methods - `gauge(name)` - create/get a gauge with `inc()`, `dec()`, `add()`, `sub()`, `set()`, `value()` methods - `histogram(name, unit)` - create/get a histogram with `recordValue()` method Example usage: ```lua function envoy_on_request(request_handle) local stats = request_handle:stats() local counter = stats:counter("my_requests") counter:inc() local gauge = stats:gauge("active_connections") gauge:set(10) local histogram = stats:histogram("request_latency", "ms") histogram:recordValue(42) end ``` Stats are prefixed with `lua.` plus the optional `stat_prefix` from the filter config. Gauges use `NeverImport` mode since they track local process state and shouldn't be accumulated across hot restarts. Risk Level: low Testing: unit tests & integration test Docs Changes: done Release Notes: done Platform Specific Features: no --------- Signed-off-by: Derek Argueta Co-authored-by: Derek Argueta --- changelogs/current.yaml | 5 + .../http/http_filters/lua_filter.rst | 164 ++++++++++ .../extensions/filters/http/lua/lua_filter.cc | 20 +- .../extensions/filters/http/lua/lua_filter.h | 21 +- .../extensions/filters/http/lua/wrappers.cc | 117 +++++++ source/extensions/filters/http/lua/wrappers.h | 151 +++++++++ .../filters/http/lua/lua_filter_test.cc | 72 ++++- .../filters/http/lua/lua_integration_test.cc | 70 +++++ .../filters/http/lua/wrappers_test.cc | 289 ++++++++++++++++++ 9 files changed, 906 insertions(+), 3 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index df7fd9a6eadd4..54594f85a38f1 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -387,6 +387,11 @@ removed_config_or_runtime: and legacy code path. new_features: +- area: lua + change: | + Added stats API support to the :ref:`Lua filter `, allowing Lua + scripts to create and increment counters, gauges, and histograms via + ``handle:streamInfo():stats()``. - area: dynamic_modules change: | Added ``envoy_dynamic_module_callback_is_validation_mode`` ABI callback that allows dynamic diff --git a/docs/root/configuration/http/http_filters/lua_filter.rst b/docs/root/configuration/http/http_filters/lua_filter.rst index 261896b6442ec..7b43ba7e192cc 100644 --- a/docs/root/configuration/http/http_filters/lua_filter.rst +++ b/docs/root/configuration/http/http_filters/lua_filter.rst @@ -719,6 +719,17 @@ an empty metadata object. Returns a :ref:`route object `. +``stats()`` +^^^^^^^^^^^ + +.. code-block:: lua + + local stats = handle:stats() + +Returns a stats scope object that can be used to create and modify stats (counters, gauges, and +histograms). See :ref:`Stats scope object API ` for +available methods. + .. _config_http_filters_lua_header_wrapper: Header object API @@ -1796,3 +1807,156 @@ Below is an example of a ``metadata`` in a :ref:`route entry ` Returns a :ref:`metadata object `. + +.. _config_http_filters_lua_stats_scope_wrapper: + +Stats scope object API +---------------------- + +.. code-block:: lua + + local stats = handle:stats() + +``handle:stats()`` returns a stats scope object that can be used to create and +modify stats (counters, gauges, and histograms). Stats created through this API +are prefixed with ``lua.`` followed by the optional ``stat_prefix`` from the Lua +filter configuration. + +counter() +^^^^^^^^^ + +.. code-block:: lua + + local counter = stats:counter("my_counter") + +Returns a :ref:`counter object ` with +the given name. The counter is created if it doesn't exist. + +gauge() +^^^^^^^ + +.. code-block:: lua + + local gauge = stats:gauge("my_gauge") + +Returns a :ref:`gauge object ` with +the given name. The gauge is created if it doesn't exist. Gauges created through +Lua use ``NeverImport`` mode, meaning they track local process state and are not +aggregated across the cluster during hot restart. + +histogram() +^^^^^^^^^^^ + +.. code-block:: lua + + local histogram = stats:histogram("my_histogram", "ms") + +Returns a :ref:`histogram object ` +with the given name and unit. The second argument specifies the unit and must be +one of: ``"unspecified"``, ``"bytes"``, ``"microseconds"``, ``"milliseconds"``, or ``"ms"`` +(shorthand for milliseconds). + +.. _config_http_filters_lua_counter_wrapper: + +Counter object API +------------------ + +inc() +^^^^^ + +.. code-block:: lua + + counter:inc() + +Increments the counter by 1. + +add() +^^^^^ + +.. code-block:: lua + + counter:add(5) + +Adds the specified value to the counter. The value must be a non-negative integer. + +value() +^^^^^^^ + +.. code-block:: lua + + local val = counter:value() + +Returns the current value of the counter. + +.. _config_http_filters_lua_gauge_wrapper: + +Gauge object API +---------------- + +inc() +^^^^^ + +.. code-block:: lua + + gauge:inc() + +Increments the gauge by 1. + +dec() +^^^^^ + +.. code-block:: lua + + gauge:dec() + +Decrements the gauge by 1. + +add() +^^^^^ + +.. code-block:: lua + + gauge:add(5) + +Adds the specified value to the gauge. The value must be a non-negative integer. + +sub() +^^^^^ + +.. code-block:: lua + + gauge:sub(3) + +Subtracts the specified value from the gauge. The value must be a non-negative integer. + +set() +^^^^^ + +.. code-block:: lua + + gauge:set(10) + +Sets the gauge to the specified value. The value must be a non-negative integer (zero is valid). + +value() +^^^^^^^ + +.. code-block:: lua + + local val = gauge:value() + +Returns the current value of the gauge. + +.. _config_http_filters_lua_histogram_wrapper: + +Histogram object API +-------------------- + +recordValue() +^^^^^^^^^^^^^ + +.. code-block:: lua + + histogram:recordValue(42) + +Records a value in the histogram. The value must be a non-negative integer. diff --git a/source/extensions/filters/http/lua/lua_filter.cc b/source/extensions/filters/http/lua/lua_filter.cc index 5e47ac97aede4..7b82dc8db1ccc 100644 --- a/source/extensions/filters/http/lua/lua_filter.cc +++ b/source/extensions/filters/http/lua/lua_filter.cc @@ -216,6 +216,10 @@ PerLuaCodeSetup::PerLuaCodeSetup(const std::string& lua_code, ThreadLocal::SlotA lua_state_.registerType(); lua_state_.registerType(); lua_state_.registerType(); + lua_state_.registerType(); + lua_state_.registerType(); + lua_state_.registerType(); + lua_state_.registerType(); const Filters::Common::Lua::InitializerList initializers( // EnvoyTimestampResolution "enum". @@ -833,7 +837,11 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::lua::v3::Lua& : cluster_manager_(cluster_manager), clear_route_cache_( proto_config.has_clear_route_cache() ? proto_config.clear_route_cache().value() : true), - stats_(generateStats(stats_prefix, proto_config.stat_prefix(), scope)) { + stats_(generateStats(stats_prefix, proto_config.stat_prefix(), scope)), + lua_stats_scope_( + scope.createScope(proto_config.stat_prefix().empty() + ? absl::StrCat(stats_prefix, "lua") + : absl::StrCat(stats_prefix, "lua.", proto_config.stat_prefix()))) { if (proto_config.has_default_source_code()) { if (!proto_config.inline_code().empty()) { throw EnvoyException("Error: Only one of `inline_code` or `default_source_code` can be set " @@ -989,6 +997,16 @@ int StreamHandleWrapper::luaFilterContext(lua_State* state) { return 1; } +int StreamHandleWrapper::luaStats(lua_State* state) { + ASSERT(state_ == State::Running); + if (stats_scope_wrapper_.get() != nullptr) { + stats_scope_wrapper_.pushStack(); + } else { + stats_scope_wrapper_.reset(StatsScopeWrapper::create(state, callbacks_.statsScope()), true); + } + return 1; +} + void Filter::DecoderCallbacks::respond(Http::ResponseHeaderMapPtr&& headers, Buffer::Instance* body, lua_State*) { uint64_t status = Http::Utility::getResponseStatus(*headers); diff --git a/source/extensions/filters/http/lua/lua_filter.h b/source/extensions/filters/http/lua/lua_filter.h index 46badebe33b1b..59a0f30989bfb 100644 --- a/source/extensions/filters/http/lua/lua_filter.h +++ b/source/extensions/filters/http/lua/lua_filter.h @@ -135,6 +135,12 @@ class FilterCallbacks { * @return absl::string_view the value of filter config name. */ virtual const absl::string_view filterConfigName() const PURE; + + /** + * @return Stats::Scope& the stats scope for creating custom Lua stats. The scope + * is pre-configured with the appropriate lua stat prefix. + */ + virtual Stats::Scope& statsScope() PURE; }; class Filter; @@ -210,7 +216,8 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject public_key_wrapper_; Filters::Common::Lua::LuaDeathRef virtual_host_wrapper_; Filters::Common::Lua::LuaDeathRef route_wrapper_; + Filters::Common::Lua::LuaDeathRef stats_scope_wrapper_; State state_{State::Running}; std::function yield_callback_; Http::AsyncClient::Request* http_request_{}; @@ -457,6 +471,7 @@ class FilterConfig : Logger::Loggable { bool clearRouteCache() const { return clear_route_cache_; } const LuaFilterStats& stats() const { return stats_; } + Stats::Scope& luaStatsScope() const { return *lua_stats_scope_; } Upstream::ClusterManager& cluster_manager_; @@ -471,6 +486,8 @@ class FilterConfig : Logger::Loggable { PerLuaCodeSetupPtr default_lua_code_setup_; absl::flat_hash_map per_lua_code_setups_map_; LuaFilterStats stats_; + // Sub-scope pre-configured with the lua stat prefix. + Stats::ScopeSharedPtr lua_stats_scope_; }; using FilterConfigConstSharedPtr = std::shared_ptr; @@ -591,6 +608,7 @@ class Filter : public Http::StreamFilter, private Filters::Common::Lua::LuaLogga const absl::string_view filterConfigName() const override { return callbacks_->filterConfigName(); } + Stats::Scope& statsScope() override { return parent_.config_->luaStatsScope(); } Filter& parent_; Http::StreamDecoderFilterCallbacks* callbacks_{}; @@ -624,6 +642,7 @@ class Filter : public Http::StreamFilter, private Filters::Common::Lua::LuaLogga const absl::string_view filterConfigName() const override { return callbacks_->filterConfigName(); } + Stats::Scope& statsScope() override { return parent_.config_->luaStatsScope(); } Filter& parent_; Http::StreamEncoderFilterCallbacks* callbacks_{}; diff --git a/source/extensions/filters/http/lua/wrappers.cc b/source/extensions/filters/http/lua/wrappers.cc index 14d92cf51b914..538e61d290873 100644 --- a/source/extensions/filters/http/lua/wrappers.cc +++ b/source/extensions/filters/http/lua/wrappers.cc @@ -7,6 +7,7 @@ #include "source/common/http/header_utility.h" #include "source/common/http/utility.h" #include "source/common/protobuf/protobuf.h" +#include "source/common/stats/utility.h" #include "source/extensions/filters/common/lua/protobuf_converter.h" #include "source/extensions/filters/common/lua/wrappers.h" #include "source/extensions/http/header_formatters/preserve_case/preserve_case_formatter.h" @@ -530,6 +531,122 @@ int RouteWrapper::luaMetadata(lua_State* state) { return 1; } +int CounterWrapper::luaInc(lua_State*) { + counter_.inc(); + return 0; +} + +int CounterWrapper::luaAdd(lua_State* state) { + const lua_Integer amount = luaL_checkinteger(state, 2); + if (amount < 0) { + luaL_error(state, "counter add amount must be non-negative"); + } + counter_.add(static_cast(amount)); + return 0; +} + +int CounterWrapper::luaValue(lua_State* state) { + lua_pushnumber(state, static_cast(counter_.value())); + return 1; +} + +int GaugeWrapper::luaInc(lua_State*) { + gauge_.inc(); + return 0; +} + +int GaugeWrapper::luaDec(lua_State*) { + gauge_.dec(); + return 0; +} + +int GaugeWrapper::luaAdd(lua_State* state) { + const lua_Integer amount = luaL_checkinteger(state, 2); + if (amount < 0) { + luaL_error(state, "gauge add amount must be non-negative"); + } + gauge_.add(static_cast(amount)); + return 0; +} + +int GaugeWrapper::luaSub(lua_State* state) { + const lua_Integer amount = luaL_checkinteger(state, 2); + if (amount < 0) { + luaL_error(state, "gauge sub amount must be non-negative"); + } + gauge_.sub(static_cast(amount)); + return 0; +} + +int GaugeWrapper::luaSet(lua_State* state) { + const lua_Integer value = luaL_checkinteger(state, 2); + if (value < 0) { + luaL_error(state, "gauge set value must be non-negative"); + } + gauge_.set(static_cast(value)); + return 0; +} + +int GaugeWrapper::luaValue(lua_State* state) { + lua_pushnumber(state, static_cast(gauge_.value())); + return 1; +} + +int HistogramWrapper::luaRecordValue(lua_State* state) { + const lua_Integer value = luaL_checkinteger(state, 2); + if (value < 0) { + luaL_error(state, "histogram value must be non-negative"); + } + histogram_.recordValue(static_cast(value)); + return 0; +} + +int StatsScopeWrapper::luaCounter(lua_State* state) { + const char* name = luaL_checkstring(state, 2); + Stats::Counter& counter = Stats::Utility::counterFromElements(scope_, {Stats::DynamicName(name)}); + CounterWrapper::create(state, counter); + return 1; +} + +int StatsScopeWrapper::luaGauge(lua_State* state) { + const char* name = luaL_checkstring(state, 2); + // Use NeverImport mode - Lua gauges track local state and should not be + // accumulated across hot restarts. + Stats::Gauge& gauge = Stats::Utility::gaugeFromElements(scope_, {Stats::DynamicName(name)}, + Stats::Gauge::ImportMode::NeverImport); + GaugeWrapper::create(state, gauge); + return 1; +} + +int StatsScopeWrapper::luaHistogram(lua_State* state) { + const char* name = luaL_checkstring(state, 2); + + // Parse optional unit parameter (default: Unspecified). + Stats::Histogram::Unit unit = Stats::Histogram::Unit::Unspecified; + if (lua_gettop(state) >= 3 && !lua_isnil(state, 3)) { + const absl::string_view unit_str = luaL_checkstring(state, 3); + if (unit_str == "ms" || unit_str == "milliseconds") { + unit = Stats::Histogram::Unit::Milliseconds; + } else if (unit_str == "bytes") { + unit = Stats::Histogram::Unit::Bytes; + } else if (unit_str == "microseconds") { + unit = Stats::Histogram::Unit::Microseconds; + } else if (unit_str == "unspecified") { + unit = Stats::Histogram::Unit::Unspecified; + } else { + luaL_error(state, + "invalid histogram unit '%s', expected 'ms', 'milliseconds', 'microseconds', " + "'bytes', or 'unspecified'", + std::string(unit_str).c_str()); + } + } + + Stats::Histogram& histogram = + Stats::Utility::histogramFromElements(scope_, {Stats::DynamicName(name)}, unit); + HistogramWrapper::create(state, histogram); + return 1; +} + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/lua/wrappers.h b/source/extensions/filters/http/lua/wrappers.h index 3d26af1d4d116..4452266316102 100644 --- a/source/extensions/filters/http/lua/wrappers.h +++ b/source/extensions/filters/http/lua/wrappers.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/http/header_map.h" +#include "envoy/stats/scope.h" #include "envoy/stream_info/stream_info.h" #include "source/common/crypto/utility.h" @@ -522,6 +523,156 @@ class RouteWrapper : public Filters::Common::Lua::BaseLuaObject { Filters::Common::Lua::LuaDeathRef metadata_wrapper_; }; +/** + * Lua wrapper for a stats Counter. + */ +class CounterWrapper : public Filters::Common::Lua::BaseLuaObject { +public: + explicit CounterWrapper(Stats::Counter& counter) : counter_(counter) {} + + static ExportedFunctions exportedFunctions() { + return {{"inc", static_luaInc}, {"add", static_luaAdd}, {"value", static_luaValue}}; + } + +private: + /** + * Increment the counter by 1. + * @return nothing. + */ + DECLARE_LUA_FUNCTION(CounterWrapper, luaInc); + + /** + * Add an amount to the counter. + * @param 1 (int): amount to add. + * @return nothing. + */ + DECLARE_LUA_FUNCTION(CounterWrapper, luaAdd); + + /** + * Get the current value of the counter. + * @return int current value. + */ + DECLARE_LUA_FUNCTION(CounterWrapper, luaValue); + + Stats::Counter& counter_; +}; + +/** + * Lua wrapper for a stats Gauge. + */ +class GaugeWrapper : public Filters::Common::Lua::BaseLuaObject { +public: + explicit GaugeWrapper(Stats::Gauge& gauge) : gauge_(gauge) {} + + static ExportedFunctions exportedFunctions() { + return {{"inc", static_luaInc}, {"dec", static_luaDec}, {"add", static_luaAdd}, + {"sub", static_luaSub}, {"set", static_luaSet}, {"value", static_luaValue}}; + } + +private: + /** + * Increment the gauge by 1. + * @return nothing. + */ + DECLARE_LUA_FUNCTION(GaugeWrapper, luaInc); + + /** + * Decrement the gauge by 1. + * @return nothing. + */ + DECLARE_LUA_FUNCTION(GaugeWrapper, luaDec); + + /** + * Add an amount to the gauge. + * @param 1 (int): amount to add. + * @return nothing. + */ + DECLARE_LUA_FUNCTION(GaugeWrapper, luaAdd); + + /** + * Subtract an amount from the gauge. + * @param 1 (int): amount to subtract. + * @return nothing. + */ + DECLARE_LUA_FUNCTION(GaugeWrapper, luaSub); + + /** + * Set the gauge to a specific value. + * @param 1 (int): value to set. + * @return nothing. + */ + DECLARE_LUA_FUNCTION(GaugeWrapper, luaSet); + + /** + * Get the current value of the gauge. + * @return int current value. + */ + DECLARE_LUA_FUNCTION(GaugeWrapper, luaValue); + + Stats::Gauge& gauge_; +}; + +/** + * Lua wrapper for a stats Histogram. + */ +class HistogramWrapper : public Filters::Common::Lua::BaseLuaObject { +public: + explicit HistogramWrapper(Stats::Histogram& histogram) : histogram_(histogram) {} + + static ExportedFunctions exportedFunctions() { return {{"recordValue", static_luaRecordValue}}; } + +private: + /** + * Record a value in the histogram. + * @param 1 (int): value to record. + * @return nothing. + */ + DECLARE_LUA_FUNCTION(HistogramWrapper, luaRecordValue); + + Stats::Histogram& histogram_; +}; + +/** + * Lua wrapper for a stats Scope. Allows Lua scripts to create and access + * counters, gauges, and histograms. + */ +class StatsScopeWrapper : public Filters::Common::Lua::BaseLuaObject { +public: + explicit StatsScopeWrapper(Stats::Scope& scope) : scope_(scope) {} + + static ExportedFunctions exportedFunctions() { + return {{"counter", static_luaCounter}, + {"gauge", static_luaGauge}, + {"histogram", static_luaHistogram}}; + } + +private: + /** + * Get or create a counter with the given name. + * @param 1 (string): counter name. + * @return CounterWrapper handle. + */ + DECLARE_LUA_FUNCTION(StatsScopeWrapper, luaCounter); + + /** + * Get or create a gauge with the given name. + * @param 1 (string): gauge name. + * @return GaugeWrapper handle. + */ + DECLARE_LUA_FUNCTION(StatsScopeWrapper, luaGauge); + + /** + * Get or create a histogram with the given name. + * @param 1 (string): histogram name. + * @param 2 (string, optional): unit - "ms"/"milliseconds", "microseconds", "bytes", or + * "unspecified" (default). + * @return HistogramWrapper handle. + */ + DECLARE_LUA_FUNCTION(StatsScopeWrapper, luaHistogram); + + Stats::Scope& scope_; +}; + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/lua/lua_filter_test.cc b/test/extensions/filters/http/lua/lua_filter_test.cc index ca8aa74bbf13b..c6365b8701736 100644 --- a/test/extensions/filters/http/lua/lua_filter_test.cc +++ b/test/extensions/filters/http/lua/lua_filter_test.cc @@ -152,6 +152,9 @@ class LuaHttpFilterTest : public testing::Test { EXPECT_EQ(0, stats_store_.counter("test.lua.errors").value()); } + // stats_store_ must be declared before config_ so that the sub-scope held by + // FilterConfig (lua_stats_scope_) is destroyed before the store itself. + Stats::TestUtil::TestStore stats_store_; NiceMock server_factory_context_; NiceMock tls_; NiceMock api_; @@ -167,7 +170,6 @@ class LuaHttpFilterTest : public testing::Test { NiceMock connection_; NiceMock stream_info_; Tracing::MockSpan child_span_; - Stats::TestUtil::TestStore stats_store_; const std::string HEADER_ONLY_SCRIPT{R"EOF( function envoy_on_request(request_handle) @@ -4499,6 +4501,74 @@ TEST_F(LuaHttpFilterTest, GetRouteFromHandleNoRoute) { EXPECT_EQ(2, stats_store_.counter("test.lua.executions").value()); } +// Test stats() API from Lua script. +TEST_F(LuaHttpFilterTest, StatsApi) { + const std::string SCRIPT{R"EOF( + function envoy_on_request(request_handle) + local stats = request_handle:stats() + + -- Test counter. + local counter = stats:counter("my_counter") + counter:inc() + counter:add(5) + + -- Test gauge. + local gauge = stats:gauge("my_gauge") + gauge:set(100) + gauge:inc() + gauge:dec() + gauge:add(10) + gauge:sub(5) + + -- Test histogram. + local histogram = stats:histogram("my_histogram", "ms") + histogram:recordValue(42) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + // Verify stats were created with the correct prefix. + EXPECT_EQ(6, stats_store_.counter("test.lua.my_counter").value()); + + auto gauge = stats_store_.findGaugeByString("test.lua.my_gauge"); + ASSERT_TRUE(gauge.has_value()); + EXPECT_EQ(105, gauge->get().value()); + + auto histogram = stats_store_.findHistogramByString("test.lua.my_histogram"); + ASSERT_TRUE(histogram.has_value()); + EXPECT_EQ(Stats::Histogram::Unit::Milliseconds, histogram->get().unit()); +} + +// Test stats() API with custom stat_prefix. +TEST_F(LuaHttpFilterTest, StatsApiWithPrefix) { + const std::string SCRIPT{R"EOF( + function envoy_on_request(request_handle) + local stats = request_handle:stats() + local counter = stats:counter("requests") + counter:inc() + end + )EOF"}; + + InSequence s; + envoy::extensions::filters::http::lua::v3::Lua lua_config; + lua_config.mutable_default_source_code()->set_inline_string(SCRIPT); + lua_config.set_stat_prefix("custom_prefix"); + envoy::extensions::filters::http::lua::v3::LuaPerRoute per_route_config; + setupConfig(lua_config, per_route_config); + setupFilter(); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + // Verify the counter was created with the custom prefix. + EXPECT_EQ(1, stats_store_.counter("test.lua.custom_prefix.requests").value()); +} + } // namespace } // namespace Lua } // namespace HttpFilters diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index d58a16e9f227a..a66c0aefe476d 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -2725,5 +2725,75 @@ name: lua cleanup(); } +// Test the stats() API for creating counters, gauges, and histograms from Lua. +TEST_P(LuaIntegrationTest, StatsApi) { + if (!testing_downstream_filter_) { + GTEST_SKIP() << "Stats API test only runs for downstream filter"; + } + + const std::string filter_config = + R"EOF( +name: lua +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + stat_prefix: stats_test + default_source_code: + inline_string: | + function envoy_on_request(request_handle) + local stats = request_handle:stats() + + -- Create and increment a counter. + local counter = stats:counter("requests") + counter:inc() + counter:add(2) + + -- Create and set a gauge. + local gauge = stats:gauge("active_requests") + gauge:set(10) + gauge:inc() + gauge:dec() + + -- Create and record histogram values. + local histogram = stats:histogram("request_latency", "ms") + histogram:recordValue(50) + histogram:recordValue(100) + end + + function envoy_on_response(response_handle) + local stats = response_handle:stats() + + -- Increment the same counter (should accumulate). + local counter = stats:counter("requests") + counter:inc() + + -- Update the gauge. + local gauge = stats:gauge("active_requests") + gauge:sub(5) + end +)EOF"; + + initializeFilter(filter_config); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + sendRequestAndWaitForResponse(default_request_headers_, 0, default_response_headers_, 0); + + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + + // Verify the counter was incremented correctly (inc + add(2) + inc = 4). + test_server_->waitForCounterEq("http.config_test.lua.stats_test.requests", 4); + + // Verify the gauge value (set(10) + inc - dec - sub(5) = 5). + test_server_->waitForGaugeEq("http.config_test.lua.stats_test.active_requests", 5); + + // Verify histogram exists (we can't easily check recorded values in integration tests, + // but we can verify the histogram was created by checking it appears in stats). + test_server_->waitForCounterEq("http.config_test.lua.stats_test.executions", 2); + test_server_->waitForCounterEq("http.config_test.lua.stats_test.errors", 0); + + cleanup(); +} + } // namespace } // namespace Envoy diff --git a/test/extensions/filters/http/lua/wrappers_test.cc b/test/extensions/filters/http/lua/wrappers_test.cc index f1b3706435250..9918989aa307c 100644 --- a/test/extensions/filters/http/lua/wrappers_test.cc +++ b/test/extensions/filters/http/lua/wrappers_test.cc @@ -11,6 +11,7 @@ #include "test/extensions/filters/common/lua/lua_wrappers.h" #include "test/mocks/router/mocks.h" +#include "test/mocks/stats/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" @@ -1875,6 +1876,294 @@ TEST_F(LuaRouteWrapperTest, GetMetadataNoRoute) { wrapper.reset(); } +class LuaStatsScopeWrapperTest + : public Filters::Common::Lua::LuaWrappersTestBase { +public: + void setup(const std::string& script) override { + Filters::Common::Lua::LuaWrappersTestBase::setup(script); + state_->registerType(); + state_->registerType(); + state_->registerType(); + } + +protected: + Stats::TestUtil::TestStore store_; +}; + +// Test counter creation and operations. +TEST_F(LuaStatsScopeWrapperTest, CounterOperations) { + const std::string SCRIPT{R"EOF( + function callMe(object) + local counter = object:counter("test_counter") + testPrint(tostring(counter:value())) + counter:inc() + testPrint(tostring(counter:value())) + counter:add(5) + testPrint(tostring(counter:value())) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Filters::Common::Lua::LuaDeathRef wrapper( + StatsScopeWrapper::create(coroutine_->luaState(), *store_.rootScope()->createScope("lua")), + true); + EXPECT_CALL(printer_, testPrint("0")); + EXPECT_CALL(printer_, testPrint("1")); + EXPECT_CALL(printer_, testPrint("6")); + start("callMe"); + + // Verify the counter was created with the correct prefix. + EXPECT_EQ(6, store_.counter("lua.test_counter").value()); + wrapper.reset(); +} + +// Test counter with negative add fails. +TEST_F(LuaStatsScopeWrapperTest, CounterNegativeAddFails) { + const std::string SCRIPT{R"EOF( + function callMe(object) + local counter = object:counter("test_counter") + counter:add(-1) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Filters::Common::Lua::LuaDeathRef wrapper( + StatsScopeWrapper::create(coroutine_->luaState(), *store_.rootScope()->createScope("lua")), + true); + EXPECT_THROW_WITH_MESSAGE(start("callMe"), Filters::Common::Lua::LuaException, + "[string \"...\"]:4: counter add amount must be non-negative"); + wrapper.reset(); +} + +// Test gauge creation and operations. +TEST_F(LuaStatsScopeWrapperTest, GaugeOperations) { + const std::string SCRIPT{R"EOF( + function callMe(object) + local gauge = object:gauge("test_gauge") + testPrint(tostring(gauge:value())) + gauge:set(100) + testPrint(tostring(gauge:value())) + gauge:inc() + testPrint(tostring(gauge:value())) + gauge:dec() + testPrint(tostring(gauge:value())) + gauge:add(10) + testPrint(tostring(gauge:value())) + gauge:sub(5) + testPrint(tostring(gauge:value())) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Filters::Common::Lua::LuaDeathRef wrapper( + StatsScopeWrapper::create(coroutine_->luaState(), *store_.rootScope()->createScope("lua")), + true); + EXPECT_CALL(printer_, testPrint("0")); + EXPECT_CALL(printer_, testPrint("100")); + EXPECT_CALL(printer_, testPrint("101")); + EXPECT_CALL(printer_, testPrint("100")); + EXPECT_CALL(printer_, testPrint("110")); + EXPECT_CALL(printer_, testPrint("105")); + start("callMe"); + + // Verify the gauge was created with the correct prefix. + EXPECT_EQ(105, store_.gauge("lua.test_gauge", Stats::Gauge::ImportMode::NeverImport).value()); + wrapper.reset(); +} + +// Test gauge with negative value operations fail. +TEST_F(LuaStatsScopeWrapperTest, GaugeNegativeValueFails) { + const std::string SCRIPT_SET{R"EOF( + function callMe(object) + local gauge = object:gauge("test_gauge") + gauge:set(-1) + end + )EOF"}; + + const std::string SCRIPT_ADD{R"EOF( + function callMe(object) + local gauge = object:gauge("test_gauge") + gauge:add(-1) + end + )EOF"}; + + const std::string SCRIPT_SUB{R"EOF( + function callMe(object) + local gauge = object:gauge("test_gauge") + gauge:sub(-1) + end + )EOF"}; + + // Test set with negative value. + setup(SCRIPT_SET); + Filters::Common::Lua::LuaDeathRef wrapper1( + StatsScopeWrapper::create(coroutine_->luaState(), *store_.rootScope()->createScope("lua")), + true); + EXPECT_THROW_WITH_MESSAGE(start("callMe"), Filters::Common::Lua::LuaException, + "[string \"...\"]:4: gauge set value must be non-negative"); + wrapper1.reset(); + + // Test add with negative value. + setup(SCRIPT_ADD); + Filters::Common::Lua::LuaDeathRef wrapper2( + StatsScopeWrapper::create(coroutine_->luaState(), *store_.rootScope()->createScope("lua")), + true); + EXPECT_THROW_WITH_MESSAGE(start("callMe"), Filters::Common::Lua::LuaException, + "[string \"...\"]:4: gauge add amount must be non-negative"); + wrapper2.reset(); + + // Test sub with negative value. + setup(SCRIPT_SUB); + Filters::Common::Lua::LuaDeathRef wrapper3( + StatsScopeWrapper::create(coroutine_->luaState(), *store_.rootScope()->createScope("lua")), + true); + EXPECT_THROW_WITH_MESSAGE(start("callMe"), Filters::Common::Lua::LuaException, + "[string \"...\"]:4: gauge sub amount must be non-negative"); + wrapper3.reset(); +} + +// Test histogram creation and recording. +TEST_F(LuaStatsScopeWrapperTest, HistogramOperations) { + const std::string SCRIPT{R"EOF( + function callMe(object) + local histogram = object:histogram("test_histogram") + histogram:recordValue(10) + histogram:recordValue(20) + histogram:recordValue(30) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Filters::Common::Lua::LuaDeathRef wrapper( + StatsScopeWrapper::create(coroutine_->luaState(), *store_.rootScope()->createScope("lua")), + true); + start("callMe"); + + // Verify the histogram was created with the correct prefix and unit. + auto histogram = store_.findHistogramByString("lua.test_histogram"); + ASSERT_TRUE(histogram.has_value()); + EXPECT_EQ(Stats::Histogram::Unit::Unspecified, histogram->get().unit()); + wrapper.reset(); +} + +// Test histogram with different units. +TEST_F(LuaStatsScopeWrapperTest, HistogramUnits) { + const std::string SCRIPT{R"EOF( + function callMe(object) + local ms_histogram = object:histogram("latency", "ms") + ms_histogram:recordValue(150) + + local bytes_histogram = object:histogram("size", "bytes") + bytes_histogram:recordValue(1024) + + local us_histogram = object:histogram("latency_us", "microseconds") + us_histogram:recordValue(500) + + local unspecified_histogram = object:histogram("count", "unspecified") + unspecified_histogram:recordValue(42) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Filters::Common::Lua::LuaDeathRef wrapper( + StatsScopeWrapper::create(coroutine_->luaState(), *store_.rootScope()->createScope("lua")), + true); + start("callMe"); + + // Verify histograms were created with correct units. + auto latency = store_.findHistogramByString("lua.latency"); + ASSERT_TRUE(latency.has_value()); + EXPECT_EQ(Stats::Histogram::Unit::Milliseconds, latency->get().unit()); + + auto size = store_.findHistogramByString("lua.size"); + ASSERT_TRUE(size.has_value()); + EXPECT_EQ(Stats::Histogram::Unit::Bytes, size->get().unit()); + + auto latency_us = store_.findHistogramByString("lua.latency_us"); + ASSERT_TRUE(latency_us.has_value()); + EXPECT_EQ(Stats::Histogram::Unit::Microseconds, latency_us->get().unit()); + + auto count = store_.findHistogramByString("lua.count"); + ASSERT_TRUE(count.has_value()); + EXPECT_EQ(Stats::Histogram::Unit::Unspecified, count->get().unit()); + + wrapper.reset(); +} + +// Test histogram with invalid unit. +TEST_F(LuaStatsScopeWrapperTest, HistogramInvalidUnit) { + const std::string SCRIPT{R"EOF( + function callMe(object) + local histogram = object:histogram("test_histogram", "invalid_unit") + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Filters::Common::Lua::LuaDeathRef wrapper( + StatsScopeWrapper::create(coroutine_->luaState(), *store_.rootScope()->createScope("lua")), + true); + EXPECT_THROW_WITH_MESSAGE( + start("callMe"), Filters::Common::Lua::LuaException, + "[string \"...\"]:3: invalid histogram unit 'invalid_unit', expected 'ms', 'milliseconds', " + "'microseconds', 'bytes', or 'unspecified'"); + wrapper.reset(); +} + +// Test histogram with negative value fails. +TEST_F(LuaStatsScopeWrapperTest, HistogramNegativeValueFails) { + const std::string SCRIPT{R"EOF( + function callMe(object) + local histogram = object:histogram("test_histogram") + histogram:recordValue(-1) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Filters::Common::Lua::LuaDeathRef wrapper( + StatsScopeWrapper::create(coroutine_->luaState(), *store_.rootScope()->createScope("lua")), + true); + EXPECT_THROW_WITH_MESSAGE(start("callMe"), Filters::Common::Lua::LuaException, + "[string \"...\"]:4: histogram value must be non-negative"); + wrapper.reset(); +} + +// Test that stats are correctly prefixed. +TEST_F(LuaStatsScopeWrapperTest, StatsPrefix) { + const std::string SCRIPT{R"EOF( + function callMe(object) + local counter = object:counter("my.counter") + counter:inc() + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Filters::Common::Lua::LuaDeathRef wrapper( + StatsScopeWrapper::create(coroutine_->luaState(), + *store_.rootScope()->createScope("http.lua.custom")), + true); + start("callMe"); + + // Verify the counter was created with the full prefix. + EXPECT_EQ(1, store_.counter("http.lua.custom.my.counter").value()); + wrapper.reset(); +} + } // namespace } // namespace Lua } // namespace HttpFilters From f3b3974c0f97d3f7ea8ccd354caf84fd33f142ec Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 16 Apr 2026 12:53:45 +0100 Subject: [PATCH 038/750] tests: Fix endemic loadstats flake (#44489) Fix: #41523 properly Signed-off-by: Ryan Northey --- test/integration/BUILD | 2 -- .../load_stats_integration_test.cc | 23 ++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/test/integration/BUILD b/test/integration/BUILD index 554e65195d50c..904929878c743 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1749,9 +1749,7 @@ envoy_cc_test( envoy_cc_test( name = "load_stats_integration_test", - size = "large", srcs = ["load_stats_integration_test.cc"], - rbe_pool = "4core", deps = [ ":http_integration_lib", "//test/config:utility_lib", diff --git a/test/integration/load_stats_integration_test.cc b/test/integration/load_stats_integration_test.cc index 4e01daad97a61..242ea42ffe8c5 100644 --- a/test/integration/load_stats_integration_test.cc +++ b/test/integration/load_stats_integration_test.cc @@ -227,6 +227,27 @@ class LoadStatsIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, upstream_locality_stats->total_issued_requests() + local_upstream_locality_stats.total_issued_requests()); // Unlike most stats, current requests in progress replaces old requests in progress. + + // Merge load_metric_stats. + for (int k = 0; k < local_upstream_locality_stats.load_metric_stats_size(); ++k) { + const auto& local_metric = local_upstream_locality_stats.load_metric_stats(k); + bool found_metric = false; + for (int l = 0; l < upstream_locality_stats->load_metric_stats_size(); ++l) { + auto* metric = upstream_locality_stats->mutable_load_metric_stats(l); + if (metric->metric_name() == local_metric.metric_name()) { + found_metric = true; + metric->set_num_requests_finished_with_metric( + metric->num_requests_finished_with_metric() + + local_metric.num_requests_finished_with_metric()); + metric->set_total_metric_value(metric->total_metric_value() + + local_metric.total_metric_value()); + break; + } + } + if (!found_metric) { + upstream_locality_stats->add_load_metric_stats()->CopyFrom(local_metric); + } + } break; } } @@ -457,7 +478,7 @@ class LoadStatsIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, const uint64_t request_size_ = 1024; const uint64_t response_size_ = 512; - const uint32_t load_report_interval_ms_ = 500; + const uint32_t load_report_interval_ms_ = 1000; }; INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, LoadStatsIntegrationTest, From 2cc8acde2fce83b073208526bc937034e17c640e Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Thu, 16 Apr 2026 15:04:07 +0200 Subject: [PATCH 039/750] Promote http geoip filter to alpha (#44483) Commit Message: Http geoip filter has sufficient production burn time and has been used by many companies. Tagging @barroca to confirm that filter has been used for long time at Spotify prod. Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Kateryna Nezdolii --- api/envoy/extensions/filters/http/geoip/v3/BUILD | 1 - api/envoy/extensions/filters/http/geoip/v3/geoip.proto | 3 --- source/extensions/extensions_metadata.yaml | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/api/envoy/extensions/filters/http/geoip/v3/BUILD b/api/envoy/extensions/filters/http/geoip/v3/BUILD index 3962396d6ce39..504c6c70514ac 100644 --- a/api/envoy/extensions/filters/http/geoip/v3/BUILD +++ b/api/envoy/extensions/filters/http/geoip/v3/BUILD @@ -8,6 +8,5 @@ api_proto_package( deps = [ "//envoy/config/core/v3:pkg", "@xds//udpa/annotations:pkg", - "@xds//xds/annotations/v3:pkg", ], ) diff --git a/api/envoy/extensions/filters/http/geoip/v3/geoip.proto b/api/envoy/extensions/filters/http/geoip/v3/geoip.proto index 770354478f947..5dca58db4ac96 100644 --- a/api/envoy/extensions/filters/http/geoip/v3/geoip.proto +++ b/api/envoy/extensions/filters/http/geoip/v3/geoip.proto @@ -4,8 +4,6 @@ package envoy.extensions.filters.http.geoip.v3; import "envoy/config/core/v3/extension.proto"; -import "xds/annotations/v3/status.proto"; - import "udpa/annotations/status.proto"; import "validate/validate.proto"; @@ -14,7 +12,6 @@ option java_outer_classname = "GeoipProto"; option java_multiple_files = true; option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/geoip/v3;geoipv3"; option (udpa.annotations.file_status).package_version_status = ACTIVE; -option (xds.annotations.v3.file_status).work_in_progress = true; // [#protodoc-title: Geoip] // Geoip :ref:`configuration overview `. diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 12d0121bae3be..f29a3fe9d5982 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -390,7 +390,7 @@ envoy.filters.http.geoip: categories: - envoy.filters.http security_posture: unknown - status: wip + status: alpha type_urls: - envoy.extensions.filters.http.geoip.v3.Geoip envoy.filters.network.geoip: From 1660ebdbd755281f01f6817464827a73e73deaa8 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 16 Apr 2026 14:39:54 +0100 Subject: [PATCH 040/750] ci: Add deflake target (#44490) this provides a standardized way to run deflaking tests this should be backported to allow for repairing release branches in a follow up i will add this as a ci workflow Signed-off-by: Ryan Northey --- ci/do_ci.sh | 19 +++++++++++++++++++ test/integration/README.md | 15 +++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 7692879df983c..972e1c610dfe0 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -368,6 +368,25 @@ case $CI_TARGET in echo "Generated cache: ${TOTAL_SIZE}" ;; + deflake) + ENVOY_DEFLAKE_RUNS=${ENVOY_DEFLAKE_RUNS:-1000} + if [[ -z "$ENVOY_DEFLAKE_TARGET" || -z "$ENVOY_DEFLAKE_TEST" ]]; then + echo "Both ENVOY_DEFLAKE_TARGET and ENVOY_DEFLAKE_TEST must be set to use deflake" >&2 + exit 1 + fi + _BAZEL_ARGS=( + "$ENVOY_DEFLAKE_TARGET" + "${BAZEL_BUILD_OPTIONS[@]}" + --test_arg=--gtest_filter="$ENVOY_DEFLAKE_TEST" + --runs_per_test="${ENVOY_DEFLAKE_RUNS}" + --test_arg="-l trace" + --cache_test_results=no) + if [[ -n "$ENVOY_DEFLAKE_JOBS" ]]; then + _BAZEL_ARGS+=(--jobs="$ENVOY_DEFLAKE_JOBS") + fi + bazel test "${_BAZEL_ARGS[@]}" + ;; + format-api|check_and_fix_proto_format) setup_clang_toolchain echo "Check and fix proto format ..." diff --git a/test/integration/README.md b/test/integration/README.md index 6207f7b8678bc..dfe501f1ea4dd 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -166,6 +166,19 @@ bazel test //test/integration:http2_upstream_integration_test \ --jobs 60 --local_test_jobs=60 --runs_per_test=1000 --test_arg="-l trace" ``` +You can also run the above command via `do_ci.sh`, eg: + +```console + +$ export ENVOY_DEFLAKE_JOBS="60" # optional +$ export ENVOY_DEFLAKE_RUNS="10" # optional, defaults to 1000 +$ export BAZEL_BUILD_EXTRA_OPTIONS="--config=clang --config=tsan" # set the type of test/toolchain +$ export ENVOY_DEFLAKE_TARGET=//test/integration:load_stats_integration_test # required +$ export ENVOY_DEFLAKE_TEST=IpVersionsClientType/LoadStatsIntegrationTest.SuccessWithCustomMetrics/IPv4_GoogleGrpc # required +$ ./ci/do_ci.sh deflake + +``` + For hard to reproduce flakes, a sometimes useful tool is `stress`, available via `apt install stress`. Running stress alongsize `bazel test` (in another window, starting it after the build completes) can be a great help in reproducing issues, @@ -183,5 +196,3 @@ Once you've managed to reproduce your test flake, you get to figure out what's going on. If your failure mode isn't documented below, ideally some combination of cerr << logging and trace logs will help you sort out what is going on (and please add to this document as you figure it out!) - - From dd5bc203c881e94c7b499b1d828af7a433d6f8ff Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Thu, 16 Apr 2026 10:09:35 -0400 Subject: [PATCH 041/750] original_dst filter is robust to untrusted upstream (#44398) original_dst listener filter is robust to untrusted upstream by default because it does not observe or act on untrusted inputs from upstream servers. This change is only to make an internal robot happy that verifies the match between extension security posture with product's threat model. Signed-off-by: Yan Avlasov --- source/extensions/extensions_metadata.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index f29a3fe9d5982..9ea3bf9e9fcdc 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -797,7 +797,7 @@ envoy.filters.listener.local_ratelimit: envoy.filters.listener.original_dst: categories: - envoy.filters.listener - security_posture: robust_to_untrusted_downstream + security_posture: robust_to_untrusted_downstream_and_upstream status: stable type_urls: - envoy.extensions.filters.listener.original_dst.v3.OriginalDst From d1007387e560b30863502683b1cc78458030bccc Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Thu, 16 Apr 2026 07:53:37 -0700 Subject: [PATCH 042/750] quic/udp: fix 'occured' -> 'occurred' in capsule error log messages (#44485) **Description:** Two ENVOY_LOG error messages contained `error occured`: - `source/common/quic/http_datagram_handler.cc` line 83 - `source/extensions/upstreams/http/udp/upstream_request.cc` line 79 Fixed to `occurred`. String-literal-only change; no code or behavior change. Signed-off-by: SAY-5 Co-authored-by: SAY-5 --- source/common/quic/http_datagram_handler.cc | 2 +- source/extensions/upstreams/http/udp/upstream_request.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/common/quic/http_datagram_handler.cc b/source/common/quic/http_datagram_handler.cc index 19c4c0e82678b..dcec34ca08f7a 100644 --- a/source/common/quic/http_datagram_handler.cc +++ b/source/common/quic/http_datagram_handler.cc @@ -80,7 +80,7 @@ bool HttpDatagramHandler::encodeCapsuleFragment(absl::string_view capsule_fragme // If a CapsuleParser object fails to parse a capsule fragment, the corresponding stream should // be reset. Returning false in this method resets the stream. if (!capsule_parser_.IngestCapsuleFragment(capsule_fragment)) { - ENVOY_LOG(error, fmt::format("Capsule parsing error occured: capsule_fragment = {}", + ENVOY_LOG(error, fmt::format("Capsule parsing error occurred: capsule_fragment = {}", capsule_fragment)); return false; } diff --git a/source/extensions/upstreams/http/udp/upstream_request.cc b/source/extensions/upstreams/http/udp/upstream_request.cc index 94f4d0aa67c1f..ff995f4943b0c 100644 --- a/source/extensions/upstreams/http/udp/upstream_request.cc +++ b/source/extensions/upstreams/http/udp/upstream_request.cc @@ -76,7 +76,7 @@ void UdpUpstream::encodeData(Buffer::Instance& data, bool end_stream) { for (const Buffer::RawSlice& slice : data.getRawSlices()) { absl::string_view mem_slice(static_cast(slice.mem_), slice.len_); if (!capsule_parser_.IngestCapsuleFragment(mem_slice)) { - ENVOY_LOG_MISC(error, "Capsule ingestion error occured: slice = {}", mem_slice); + ENVOY_LOG_MISC(error, "Capsule ingestion error occurred: slice = {}", mem_slice); break; } } From 29d3a88a5ffd50696d477ea979997ae0a241431b Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Thu, 16 Apr 2026 08:03:12 -0700 Subject: [PATCH 043/750] quic: fix 'occured' -> 'occurred' typo in http_datagram_handler log (#44484) `ENVOY_LOG` error message in `source/common/quic/http_datagram_handler.cc:83` read `Capsule parsing error occured: capsule_fragment = {}`. Surfaced in envoy-admin logs during capsule parse failures. String-literal-only change. Signed-off-by: SAY-5 Co-authored-by: SAY-5 From eca86c723c43c7e693239e7f742b13857b8282b0 Mon Sep 17 00:00:00 2001 From: botengyao Date: Thu, 16 Apr 2026 12:29:40 -0400 Subject: [PATCH 044/750] mcp: allow wildcard accept (#44496) Risk Level: low --------- Signed-off-by: Boteng Yao --- source/extensions/filters/http/mcp/mcp_filter.cc | 9 +++++++-- .../filters/http/mcp/mcp_filter_test.cc | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/source/extensions/filters/http/mcp/mcp_filter.cc b/source/extensions/filters/http/mcp/mcp_filter.cc index bac68759cf372..bfe39332f0b07 100644 --- a/source/extensions/filters/http/mcp/mcp_filter.cc +++ b/source/extensions/filters/http/mcp/mcp_filter.cc @@ -139,8 +139,9 @@ bool McpFilter::isValidMcpSseRequest(const Http::RequestHeaderMap& headers) cons } for (size_t i = 0; i < accepts.size(); ++i) { - if (absl::StrContains(accepts[i]->value().getStringView(), - Http::Headers::get().ContentTypeValues.TextEventStream)) { + const absl::string_view value = accepts[i]->value().getStringView(); + if (absl::StrContains(value, Http::Headers::get().ContentTypeValues.TextEventStream) || + absl::StrContains(value, "*/*")) { return true; } } @@ -177,6 +178,10 @@ bool McpFilter::isValidMcpPostRequest(const Http::RequestHeaderMap& headers) con for (size_t i = 0; i < accepts.size(); ++i) { const absl::string_view value = accepts[i]->value().getStringView(); + if (absl::StrContains(value, "*/*")) { + has_sse = true; + has_json = true; + } if (!has_sse && absl::StrContains(value, Http::Headers::get().ContentTypeValues.TextEventStream)) { has_sse = true; diff --git a/test/extensions/filters/http/mcp/mcp_filter_test.cc b/test/extensions/filters/http/mcp/mcp_filter_test.cc index 5fe1c288f587e..ea68a720a42f1 100644 --- a/test/extensions/filters/http/mcp/mcp_filter_test.cc +++ b/test/extensions/filters/http/mcp/mcp_filter_test.cc @@ -81,6 +81,13 @@ TEST_F(McpFilterTest, SseRequestWithMultipleAcceptValues) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); } +// Test SSE request with wildcard accept +TEST_F(McpFilterTest, SseRequestWithWildcardAccept) { + Http::TestRequestHeaderMapImpl headers{{":method", "GET"}, {"accept", "*/*"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); +} + // Test non-SSE GET request passes through in PASS_THROUGH mode TEST_F(McpFilterTest, NonSseGetRequestPassThrough) { Http::TestRequestHeaderMapImpl headers{{":method", "GET"}, {"accept", "text/html"}}; @@ -118,6 +125,14 @@ TEST_F(McpFilterTest, PostWithoutProperAcceptHeaders) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); } +// Test POST request with wildcard accept +TEST_F(McpFilterTest, PostWithWildcardAccept) { + Http::TestRequestHeaderMapImpl headers{ + {":method", "POST"}, {"content-type", "application/json"}, {"accept", "*/*"}}; + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); +} + // Test REJECT_NO_MCP mode - reject non-MCP traffic TEST_F(McpFilterTest, RejectNoMcpMode) { setupRejectMode(); From 41fe744a59cbad56d4f28e2e502bd1a32b7cf262 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 16 Apr 2026 17:47:34 +0100 Subject: [PATCH 045/750] ci/workflows: Remove repo constraint on _load_env (#44497) as this is only intended to be called from dispatch wfs its better to always allow Signed-off-by: Ryan Northey --- .github/workflows/_load_env.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index 8422dddd14532..e7ca999ee13cb 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -55,7 +55,6 @@ env: jobs: request: - if: ${{ github.repository == 'envoyproxy/envoy' || vars.ENVOY_CI }} runs-on: ubuntu-24.04 outputs: build-image: ${{ toJSON(fromJSON(steps.env.outputs.data).request.build-image) }} From c0c6ef19f521517a2a9430d33dc416ac0250d77c Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 16 Apr 2026 13:32:50 -0400 Subject: [PATCH 046/750] mobile: add a knob for early data (#44480) Commit Message: Early data requests has been enabled by default. Add a new knob `enableEarlyData()` with a bool to allow use to explicitly disable this feature. Risk Level: low Testing: new unit test Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A --------- Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- mobile/library/cc/BUILD | 1 + mobile/library/cc/engine_builder.cc | 13 +++++++ mobile/library/cc/engine_builder.h | 2 ++ .../engine/EnvoyConfiguration.java | 34 ++++++++++--------- .../envoymobile/engine/JniLibrary.java | 16 ++++----- .../impl/NativeCronvoyEngineBuilderImpl.java | 15 ++++---- mobile/library/jni/jni_impl.cc | 27 ++++++++------- .../envoyproxy/envoymobile/EngineBuilder.kt | 13 +++++++ .../library/objective-c/EnvoyConfiguration.h | 2 ++ .../library/objective-c/EnvoyConfiguration.mm | 3 ++ mobile/library/swift/EngineBuilder.swift | 13 +++++++ mobile/test/cc/unit/envoy_config_test.cc | 11 ++++++ .../engine/EnvoyConfigurationTest.kt | 13 +++++++ 13 files changed, 119 insertions(+), 44 deletions(-) diff --git a/mobile/library/cc/BUILD b/mobile/library/cc/BUILD index 4e87b6b7fcd07..37e7802387e5f 100644 --- a/mobile/library/cc/BUILD +++ b/mobile/library/cc/BUILD @@ -42,6 +42,7 @@ envoy_cc_library( "@envoy_api//envoy/config/metrics/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/compression/brotli/decompressor/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/compression/gzip/decompressor/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/early_data/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/alternate_protocols_cache/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/decompressor/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/dynamic_forward_proxy/v3:pkg_cc_proto", diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index fb104d31a477e..2eb20c53338ed 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -12,6 +12,7 @@ #include "envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.h" #include "envoy/extensions/filters/http/router/v3/router.pb.h" #include "envoy/extensions/http/header_formatters/preserve_case/v3/preserve_case.pb.h" +#include "envoy/extensions/early_data/v3/default_early_data_policy.pb.h" #if defined(__APPLE__) #include "envoy/extensions/network/dns_resolver/apple/v3/apple_dns_resolver.pb.h" @@ -231,6 +232,11 @@ EngineBuilder& EngineBuilder::enableHttp3(bool http3_on) { return *this; } +EngineBuilder& EngineBuilder::enableEarlyData(bool early_data_on) { + enable_early_data_ = early_data_on; + return *this; +} + EngineBuilder& EngineBuilder::addQuicConnectionOption(std::string option) { quic_connection_options_.push_back(std::move(option)); return *this; @@ -579,6 +585,13 @@ std::unique_ptr EngineBuilder::generate backoff->mutable_base_interval()->set_nanos(250000000); backoff->mutable_max_interval()->set_seconds(60); + if (!enable_early_data_) { + auto* early_data = route_to->mutable_early_data_policy(); + early_data->set_name("envoy.route.early_data_policy.default"); + ::envoy::extensions::early_data::v3::DefaultEarlyDataPolicy config; + early_data->mutable_typed_config()->PackFrom(config); + } + for (auto filter = native_filter_chain_.rbegin(); filter != native_filter_chain_.rend(); ++filter) { auto* native_filter = hcm->add_http_filters(); diff --git a/mobile/library/cc/engine_builder.h b/mobile/library/cc/engine_builder.h index e0a8e535fcd99..cdb7d27869349 100644 --- a/mobile/library/cc/engine_builder.h +++ b/mobile/library/cc/engine_builder.h @@ -157,6 +157,7 @@ class EngineBuilder { EngineBuilder& enableBrotliDecompression(bool brotli_decompression_on); EngineBuilder& enableSocketTagging(bool socket_tagging_on); EngineBuilder& enableHttp3(bool http3_on); + EngineBuilder& enableEarlyData(bool early_data_on); EngineBuilder& addQuicConnectionOption(std::string option); EngineBuilder& addQuicClientConnectionOption(std::string option); // Deprecated, use addQuicConnectionOption() instead. @@ -321,6 +322,7 @@ class EngineBuilder { bool enforce_trust_chain_verification_ = true; std::string upstream_tls_sni_; bool enable_http3_ = true; + bool enable_early_data_{true}; std::string http3_connection_options_ = ""; std::string http3_client_connection_options_ = ""; // EVMB is to distinguish Envoy Mobile client connections. diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java index a6f5c22817675..b45f5b1549264 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java @@ -38,6 +38,7 @@ public enum TrustChainVerification { public final int dnsNumRetries; public final boolean enableDrainPostDnsRefresh; public final boolean enableHttp3; + public final boolean enableEarlyData; public final String http3ConnectionOptions; public final String http3ClientConnectionOptions; public final Map quicHints; @@ -149,14 +150,14 @@ public EnvoyConfiguration( int dnsFailureRefreshSecondsBase, int dnsFailureRefreshSecondsMax, int dnsQueryTimeoutSeconds, int dnsMinRefreshSeconds, List dnsPreresolveHostnames, boolean enableDNSCache, int dnsCacheSaveIntervalSeconds, int dnsNumRetries, boolean enableDrainPostDnsRefresh, - boolean enableHttp3, String http3ConnectionOptions, String http3ClientConnectionOptions, - Map quicHints, List quicCanonicalSuffixes, - boolean enableGzipDecompression, boolean enableBrotliDecompression, - int numTimeoutsToTriggerPortMigration, boolean enableSocketTagging, - boolean enableInterfaceBinding, int h2ConnectionKeepaliveIdleIntervalMilliseconds, - int h2ConnectionKeepaliveTimeoutSeconds, int maxConnectionsPerHost, - int streamIdleTimeoutSeconds, int perTryIdleTimeoutSeconds, String appVersion, String appId, - TrustChainVerification trustChainVerification, + boolean enableHttp3, boolean enableEarlyData, String http3ConnectionOptions, + String http3ClientConnectionOptions, Map quicHints, + List quicCanonicalSuffixes, boolean enableGzipDecompression, + boolean enableBrotliDecompression, int numTimeoutsToTriggerPortMigration, + boolean enableSocketTagging, boolean enableInterfaceBinding, + int h2ConnectionKeepaliveIdleIntervalMilliseconds, int h2ConnectionKeepaliveTimeoutSeconds, + int maxConnectionsPerHost, int streamIdleTimeoutSeconds, int perTryIdleTimeoutSeconds, + String appVersion, String appId, TrustChainVerification trustChainVerification, List nativeFilterChain, List httpPlatformFilterFactories, Map stringAccessors, @@ -180,6 +181,7 @@ public EnvoyConfiguration( this.dnsNumRetries = dnsNumRetries; this.enableDrainPostDnsRefresh = enableDrainPostDnsRefresh; this.enableHttp3 = enableHttp3; + this.enableEarlyData = enableEarlyData; this.http3ConnectionOptions = http3ConnectionOptions; this.http3ClientConnectionOptions = http3ClientConnectionOptions; this.quicHints = new HashMap<>(); @@ -247,14 +249,14 @@ public long createBootstrap() { dnsRefreshSeconds, dnsFailureRefreshSecondsBase, dnsFailureRefreshSecondsMax, dnsQueryTimeoutSeconds, dnsMinRefreshSeconds, dnsPreresolve, enableDNSCache, dnsCacheSaveIntervalSeconds, dnsNumRetries, enableDrainPostDnsRefresh, enableHttp3, - http3ConnectionOptions, http3ClientConnectionOptions, quicHints, quicSuffixes, - enableGzipDecompression, enableBrotliDecompression, numTimeoutsToTriggerPortMigration, - enableSocketTagging, enableInterfaceBinding, h2ConnectionKeepaliveIdleIntervalMilliseconds, - h2ConnectionKeepaliveTimeoutSeconds, maxConnectionsPerHost, streamIdleTimeoutSeconds, - perTryIdleTimeoutSeconds, appVersion, appId, enforceTrustChainVerification, filterChain, - enablePlatformCertificatesValidation, upstreamTlsSni, runtimeGuards, - h3ConnectionKeepaliveInitialIntervalMilliseconds, useQuicPlatformPacketWriter, - enableQuicConnectionMigration, migrateIdleQuicConnection, + enableEarlyData, http3ConnectionOptions, http3ClientConnectionOptions, quicHints, + quicSuffixes, enableGzipDecompression, enableBrotliDecompression, + numTimeoutsToTriggerPortMigration, enableSocketTagging, enableInterfaceBinding, + h2ConnectionKeepaliveIdleIntervalMilliseconds, h2ConnectionKeepaliveTimeoutSeconds, + maxConnectionsPerHost, streamIdleTimeoutSeconds, perTryIdleTimeoutSeconds, appVersion, + appId, enforceTrustChainVerification, filterChain, enablePlatformCertificatesValidation, + upstreamTlsSni, runtimeGuards, h3ConnectionKeepaliveInitialIntervalMilliseconds, + useQuicPlatformPacketWriter, enableQuicConnectionMigration, migrateIdleQuicConnection, maxIdleTimeBeforeQuicMigrationSeconds, maxTimeOnNonDefaultNetworkSeconds); } } diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java index 1390696bf64d3..3cda908eeb242 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -339,14 +339,14 @@ public static native long createBootstrap( long dnsFailureRefreshSecondsBase, long dnsFailureRefreshSecondsMax, long dnsQueryTimeoutSeconds, long dnsMinRefreshSeconds, byte[][] dnsPreresolveHostnames, boolean enableDNSCache, long dnsCacheSaveIntervalSeconds, int dnsNumRetries, - boolean enableDrainPostDnsRefresh, boolean enableHttp3, String http3ConnectionOptions, - String http3ClientConnectionOptions, byte[][] quicHints, byte[][] quicCanonicalSuffixes, - boolean enableGzipDecompression, boolean enableBrotliDecompression, - int numTimeoutsToTriggerPortMigration, boolean enableSocketTagging, - boolean enableInterfaceBinding, long h2ConnectionKeepaliveIdleIntervalMilliseconds, - long h2ConnectionKeepaliveTimeoutSeconds, long maxConnectionsPerHost, - long streamIdleTimeoutSeconds, long perTryIdleTimeoutSeconds, String appVersion, String appId, - boolean trustChainVerification, byte[][] filterChain, + boolean enableDrainPostDnsRefresh, boolean enableHttp3, boolean enableEarlyData, + String http3ConnectionOptions, String http3ClientConnectionOptions, byte[][] quicHints, + byte[][] quicCanonicalSuffixes, boolean enableGzipDecompression, + boolean enableBrotliDecompression, int numTimeoutsToTriggerPortMigration, + boolean enableSocketTagging, boolean enableInterfaceBinding, + long h2ConnectionKeepaliveIdleIntervalMilliseconds, long h2ConnectionKeepaliveTimeoutSeconds, + long maxConnectionsPerHost, long streamIdleTimeoutSeconds, long perTryIdleTimeoutSeconds, + String appVersion, String appId, boolean trustChainVerification, byte[][] filterChain, boolean enablePlatformCertificatesValidation, String upstreamTlsSni, byte[][] runtimeGuards, long h3ConnectionKeepaliveInitialIntervalMilliseconds, boolean useQuicPlatformPacketWriter, boolean enableQuicConnectionMigration, boolean migrateIdleQuicConnection, diff --git a/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java b/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java index 0d543ce7ab176..f5a367843c746 100644 --- a/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java +++ b/mobile/library/java/org/chromium/net/impl/NativeCronvoyEngineBuilderImpl.java @@ -341,13 +341,14 @@ private EnvoyConfiguration createEnvoyConfiguration() { mDnsRefreshSeconds, mDnsFailureRefreshSecondsBase, mDnsFailureRefreshSecondsMax, mDnsQueryTimeoutSeconds, mDnsMinRefreshSeconds, mDnsPreresolveHostnames, mEnableDNSCache, mDnsCacheSaveIntervalSeconds, mDnsNumRetries.orElse(-1), mEnableDrainPostDnsRefresh, - quicEnabled(), quicConnectionOptions(), quicClientConnectionOptions(), quicHints(), - quicCanonicalSuffixes(), mEnableGzipDecompression, brotliEnabled(), - numTimeoutsToTriggerPortMigration(), mEnableSocketTag, mEnableInterfaceBinding, - mH2ConnectionKeepaliveIdleIntervalMilliseconds, mH2ConnectionKeepaliveTimeoutSeconds, - mMaxConnectionsPerHost, mStreamIdleTimeoutSeconds, mPerTryIdleTimeoutSeconds, mAppVersion, - mAppId, mTrustChainVerification, nativeFilterChain, platformFilterChain, stringAccessors, - keyValueStores, mRuntimeGuards, mEnablePlatformCertificatesValidation, mUpstreamTlsSni, + quicEnabled(), true /* enableEarlyData */, quicConnectionOptions(), + quicClientConnectionOptions(), quicHints(), quicCanonicalSuffixes(), + mEnableGzipDecompression, brotliEnabled(), numTimeoutsToTriggerPortMigration(), + mEnableSocketTag, mEnableInterfaceBinding, mH2ConnectionKeepaliveIdleIntervalMilliseconds, + mH2ConnectionKeepaliveTimeoutSeconds, mMaxConnectionsPerHost, mStreamIdleTimeoutSeconds, + mPerTryIdleTimeoutSeconds, mAppVersion, mAppId, mTrustChainVerification, nativeFilterChain, + platformFilterChain, stringAccessors, keyValueStores, mRuntimeGuards, + mEnablePlatformCertificatesValidation, mUpstreamTlsSni, mH3ConnectionKeepaliveInitialIntervalMilliseconds, mUseQuicPlatformPacketWriter && mUseV2NetworkMonitor, mEnableQuicConnectionMigration && mUseV2NetworkMonitor, mMigrateIdleQuicConnection, diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index a576b036c8111..a53cd424ab3d4 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -1142,12 +1142,12 @@ void configureBuilder( jlong dns_failure_refresh_seconds_max, jlong dns_query_timeout_seconds, jlong dns_min_refresh_seconds, jobjectArray dns_preresolve_hostnames, jboolean enable_dns_cache, jlong dns_cache_save_interval_seconds, jint dns_num_retries, - jboolean enable_drain_post_dns_refresh, jboolean enable_http3, jstring http3_connection_options, - jstring http3_client_connection_options, jobjectArray quic_hints, - jobjectArray quic_canonical_suffixes, jboolean enable_gzip_decompression, - jboolean enable_brotli_decompression, jint num_timeouts_to_trigger_port_migration, - jboolean enable_socket_tagging, jboolean enable_interface_binding, - jlong h2_connection_keepalive_idle_interval_milliseconds, + jboolean enable_drain_post_dns_refresh, jboolean enable_http3, jboolean enable_early_data, + jstring http3_connection_options, jstring http3_client_connection_options, + jobjectArray quic_hints, jobjectArray quic_canonical_suffixes, + jboolean enable_gzip_decompression, jboolean enable_brotli_decompression, + jint num_timeouts_to_trigger_port_migration, jboolean enable_socket_tagging, + jboolean enable_interface_binding, jlong h2_connection_keepalive_idle_interval_milliseconds, jlong h2_connection_keepalive_timeout_seconds, jlong max_connections_per_host, jlong stream_idle_timeout_seconds, jlong per_try_idle_timeout_seconds, jstring app_version, jstring app_id, jboolean trust_chain_verification, jobjectArray filter_chain, @@ -1183,6 +1183,7 @@ void configureBuilder( builder.enableBrotliDecompression(enable_brotli_decompression == JNI_TRUE); builder.enableSocketTagging(enable_socket_tagging == JNI_TRUE); builder.enableHttp3(enable_http3 == JNI_TRUE); + builder.enableEarlyData(enable_early_data == JNI_TRUE); builder.setHttp3ConnectionOptions( Envoy::JNI::javaStringToCppString(jni_helper, http3_connection_options)); builder.setHttp3ClientConnectionOptions( @@ -1248,12 +1249,12 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr jlong dns_query_timeout_seconds, jlong dns_min_refresh_seconds, jobjectArray dns_preresolve_hostnames, jboolean enable_dns_cache, jlong dns_cache_save_interval_seconds, jint dns_num_retries, - jboolean enable_drain_post_dns_refresh, jboolean enable_http3, jstring http3_connection_options, - jstring http3_client_connection_options, jobjectArray quic_hints, - jobjectArray quic_canonical_suffixes, jboolean enable_gzip_decompression, - jboolean enable_brotli_decompression, jint num_timeouts_to_trigger_port_migration, - jboolean enable_socket_tagging, jboolean enable_interface_binding, - jlong h2_connection_keepalive_idle_interval_milliseconds, + jboolean enable_drain_post_dns_refresh, jboolean enable_http3, jboolean enable_early_data, + jstring http3_connection_options, jstring http3_client_connection_options, + jobjectArray quic_hints, jobjectArray quic_canonical_suffixes, + jboolean enable_gzip_decompression, jboolean enable_brotli_decompression, + jint num_timeouts_to_trigger_port_migration, jboolean enable_socket_tagging, + jboolean enable_interface_binding, jlong h2_connection_keepalive_idle_interval_milliseconds, jlong h2_connection_keepalive_timeout_seconds, jlong max_connections_per_host, jlong stream_idle_timeout_seconds, jlong per_try_idle_timeout_seconds, jstring app_version, jstring app_id, jboolean trust_chain_verification, jobjectArray filter_chain, @@ -1270,7 +1271,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr disable_dns_refresh_on_network_change, dns_refresh_seconds, dns_failure_refresh_seconds_base, dns_failure_refresh_seconds_max, dns_query_timeout_seconds, dns_min_refresh_seconds, dns_preresolve_hostnames, enable_dns_cache, dns_cache_save_interval_seconds, dns_num_retries, - enable_drain_post_dns_refresh, enable_http3, http3_connection_options, + enable_drain_post_dns_refresh, enable_http3, enable_early_data, http3_connection_options, http3_client_connection_options, quic_hints, quic_canonical_suffixes, enable_gzip_decompression, enable_brotli_decompression, num_timeouts_to_trigger_port_migration, enable_socket_tagging, enable_interface_binding, diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt index 798efca14e63f..64bb2f4c2a9c0 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt @@ -47,6 +47,7 @@ open class EngineBuilder() { private var dnsNumRetries: Int? = null private var enableDrainPostDnsRefresh = false internal var enableHttp3 = true + private var enableEarlyData = true private var http3ConnectionOptions = "" private var http3ClientConnectionOptions = "" private var quicHints = mutableMapOf() @@ -229,6 +230,17 @@ open class EngineBuilder() { return this } + /** + * Specify whether to enable early data (0-RTT) support. Defaults to true. + * + * @param enableEarlyData whether or not to enable early data. + * @return This builder. + */ + fun enableEarlyData(enableEarlyData: Boolean): EngineBuilder { + this.enableEarlyData = enableEarlyData + return this + } + /** * Specify whether to do brotli response decompression or not. Defaults to false. * @@ -550,6 +562,7 @@ open class EngineBuilder() { dnsNumRetries ?: -1, enableDrainPostDnsRefresh, enableHttp3, + enableEarlyData, http3ConnectionOptions, http3ClientConnectionOptions, quicHints, diff --git a/mobile/library/objective-c/EnvoyConfiguration.h b/mobile/library/objective-c/EnvoyConfiguration.h index a22080fba2ea0..bb1041fac0e94 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.h +++ b/mobile/library/objective-c/EnvoyConfiguration.h @@ -24,6 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) UInt32 dnsCacheSaveIntervalSeconds; @property (nonatomic, assign) NSInteger dnsNumRetries; @property (nonatomic, assign) BOOL enableHttp3; +@property (nonatomic, assign) BOOL enableEarlyData; @property (nonatomic, strong) NSDictionary *quicHints; @property (nonatomic, strong) NSArray *quicCanonicalSuffixes; @property (nonatomic, assign) BOOL enableGzipDecompression; @@ -62,6 +63,7 @@ NS_ASSUME_NONNULL_BEGIN dnsCacheSaveIntervalSeconds:(UInt32)dnsCacheSaveIntervalSeconds dnsNumRetries:(NSInteger)dnsNumRetries enableHttp3:(BOOL)enableHttp3 + enableEarlyData:(BOOL)enableEarlyData quicHints:(NSDictionary *)quicHints quicCanonicalSuffixes:(NSArray *)quicCanonicalSuffixes enableGzipDecompression:(BOOL)enableGzipDecompression diff --git a/mobile/library/objective-c/EnvoyConfiguration.mm b/mobile/library/objective-c/EnvoyConfiguration.mm index 7164bc3921163..2f6e830299df8 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.mm +++ b/mobile/library/objective-c/EnvoyConfiguration.mm @@ -78,6 +78,7 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds dnsCacheSaveIntervalSeconds:(UInt32)dnsCacheSaveIntervalSeconds dnsNumRetries:(NSInteger)dnsNumRetries enableHttp3:(BOOL)enableHttp3 + enableEarlyData:(BOOL)enableEarlyData quicHints:(NSDictionary *)quicHints quicCanonicalSuffixes:(NSArray *)quicCanonicalSuffixes enableGzipDecompression:(BOOL)enableGzipDecompression @@ -124,6 +125,7 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds self.dnsCacheSaveIntervalSeconds = dnsCacheSaveIntervalSeconds; self.dnsNumRetries = dnsNumRetries; self.enableHttp3 = enableHttp3; + self.enableEarlyData = enableEarlyData; self.quicHints = quicHints; self.quicCanonicalSuffixes = quicCanonicalSuffixes; self.enableGzipDecompression = enableGzipDecompression; @@ -167,6 +169,7 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds } builder.enableHttp3(self.enableHttp3); + builder.enableEarlyData(self.enableEarlyData); for (NSString *host in self.quicHints) { builder.addQuicHint([host toCXXString], [[self.quicHints objectForKey:host] intValue]); } diff --git a/mobile/library/swift/EngineBuilder.swift b/mobile/library/swift/EngineBuilder.swift index a6e98c3461de7..94c4f3d80443e 100644 --- a/mobile/library/swift/EngineBuilder.swift +++ b/mobile/library/swift/EngineBuilder.swift @@ -19,6 +19,7 @@ open class EngineBuilder: NSObject { private var enableGzipDecompression: Bool = true private var enableBrotliDecompression: Bool = false private var enableHttp3: Bool = true + private var enableEarlyData: Bool = true private var quicHints: [String: Int] = [:] private var quicCanonicalSuffixes: [String] = [] private var enableInterfaceBinding: Bool = false @@ -191,6 +192,17 @@ open class EngineBuilder: NSObject { return self } + /// Specify whether to enable early data (0-RTT) support. Defaults to true. + /// + /// - parameter enableEarlyData: whether or not to enable early data. + /// + /// - returns: This builder. + @discardableResult + public func enableEarlyData(_ enableEarlyData: Bool) -> Self { + self.enableEarlyData = enableEarlyData + return self + } + /// Add a host port pair that's known to support QUIC. /// /// - parameter host: the string representation of the host name @@ -541,6 +553,7 @@ open class EngineBuilder: NSObject { dnsCacheSaveIntervalSeconds: self.dnsCacheSaveIntervalSeconds, dnsNumRetries: self.dnsNumRetries, enableHttp3: self.enableHttp3, + enableEarlyData: self.enableEarlyData, quicHints: self.quicHints.mapValues { NSNumber(value: $0) }, quicCanonicalSuffixes: self.quicCanonicalSuffixes, enableGzipDecompression: self.enableGzipDecompression, diff --git a/mobile/test/cc/unit/envoy_config_test.cc b/mobile/test/cc/unit/envoy_config_test.cc index a9c3e4560c38b..f5f784cc45625 100644 --- a/mobile/test/cc/unit/envoy_config_test.cc +++ b/mobile/test/cc/unit/envoy_config_test.cc @@ -335,6 +335,17 @@ TEST(TestConfig, DisableHttp3) { Not(HasSubstr("envoy.extensions.filters.http.alternate_protocols_cache.v3.FilterConfig"))); } +TEST(TestConfig, EnableEarlyData) { + EngineBuilder engine_builder; + + std::unique_ptr bootstrap = engine_builder.generateBootstrap(); + EXPECT_THAT(bootstrap->ShortDebugString(), Not(HasSubstr("early_data_policy"))); + + engine_builder.enableEarlyData(false); + bootstrap = engine_builder.generateBootstrap(); + EXPECT_THAT(bootstrap->ShortDebugString(), HasSubstr("envoy.route.early_data_policy.default")); +} + TEST(TestConfig, UdpSocketReceiveBufferSize) { EngineBuilder engine_builder; engine_builder.enableHttp3(true); diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt b/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt index c98996fccae59..6c36f2bbe5878 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt @@ -82,6 +82,7 @@ class EnvoyConfigurationTest { dnsNumRetries: Int? = 3, enableDrainPostDnsRefresh: Boolean = false, enableHttp3: Boolean = true, + enableEarlyData: Boolean = true, http3ConnectionOptions: String = "5RTO", http3ClientConnectionOptions: String = "MPQC", quicHints: Map = mapOf("www.abc.com" to 443, "www.def.com" to 443), @@ -136,6 +137,7 @@ class EnvoyConfigurationTest { dnsNumRetries ?: -1, enableDrainPostDnsRefresh, enableHttp3, + enableEarlyData, http3ConnectionOptions, http3ClientConnectionOptions, quicHints, @@ -327,4 +329,15 @@ class EnvoyConfigurationTest { assertThat(resolvedTemplate).contains("QuicPlatformPacketWriterConfig") assertThat(resolvedTemplate).contains("connection_migration { migrate_idle_connections { } }") } + + @Test + fun `configuration resolves early data policy when early data disabled`() { + JniLibrary.loadTestLibrary() + val envoyConfiguration = buildTestEnvoyConfiguration( + enableEarlyData = false + ) + + val resolvedTemplate = TestJni.createProtoString(envoyConfiguration) + assertThat(resolvedTemplate).contains("envoy.route.early_data_policy.default") + } } From fb6085ff0e91f27c289e6c26014740bae42f8539 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 16 Apr 2026 20:06:02 +0100 Subject: [PATCH 047/750] ci: Add deflake workflow (#44498) Signed-off-by: Ryan Northey --- .github/workflows/envoy-deflake.yml | 58 +++++++++++++++++++++++++++++ ci/do_ci.sh | 3 ++ ci/docker-compose.yml | 4 ++ 3 files changed, 65 insertions(+) create mode 100644 .github/workflows/envoy-deflake.yml diff --git a/.github/workflows/envoy-deflake.yml b/.github/workflows/envoy-deflake.yml new file mode 100644 index 0000000000000..aef3a52d53a3b --- /dev/null +++ b/.github/workflows/envoy-deflake.yml @@ -0,0 +1,58 @@ +name: Envoy/deflake + +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + target: + description: 'Bazel target to test' + type: string + required: true + test: + description: 'C++ test' + type: string + required: true + bazel-args: + description: Toolchain/test type + type: string + default: >- + --config=clang + runs: + description: 'Number of runs to test' + type: number + default: 1000 + + +jobs: + env: + secrets: + lock-app-key: ${{ secrets.ENVOY_CI_MUTEX_APP_KEY }} + lock-app-id: ${{ secrets.ENVOY_CI_MUTEX_APP_ID }} + permissions: + contents: read + uses: ./.github/workflows/_load_env.yml + + deflake: + permissions: + actions: read + contents: read + packages: read + needs: env + uses: ./.github/workflows/_run.yml + with: + bazel-extra: >- + --config=rbe + ${{ inputs.bazel-args }} + rbe: true + request: ${{ needs.env.outputs.request }} + target: deflake + trusted: true + steps-pre: | + - shell: bash + run: | + echo ENVOY_DEFLAKE_JOBS=300 >> $GITHUB_ENV + echo ENVOY_DEFLAKE_RUNS=${{ inputs.runs }} >> $GITHUB_ENV + echo ENVOY_DEFLAKE_TARGET=${{ inputs.target }} >> $GITHUB_ENV + echo ENVOY_DEFLAKE_TEST=${{ inputs.test }} >> $GITHUB_ENV diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 972e1c610dfe0..85981a915942a 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -384,6 +384,9 @@ case $CI_TARGET in if [[ -n "$ENVOY_DEFLAKE_JOBS" ]]; then _BAZEL_ARGS+=(--jobs="$ENVOY_DEFLAKE_JOBS") fi + echo "Deflake args: " >&2 + echo " ${_BAZEL_ARGS[*]}" >&2 + echo "" >&2 bazel test "${_BAZEL_ARGS[@]}" ;; diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index ee0695623a2eb..20151e3967896 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -41,6 +41,10 @@ x-envoy-build-base: &envoy-build-base - GITHUB_TOKEN - GITHUB_APP_ID - GITHUB_INSTALL_ID + - ENVOY_DEFLAKE_JOBS + - ENVOY_DEFLAKE_RUNS + - ENVOY_DEFLAKE_TARGET + - ENVOY_DEFLAKE_TEST # Build configuration - NUM_CPUS From 471992d5a6b0464910936ac588b1a7126a6a3bae Mon Sep 17 00:00:00 2001 From: Joe Kralicky Date: Thu, 16 Apr 2026 15:14:46 -0400 Subject: [PATCH 048/750] test: fix data race when generating mock connection ids (#44011) Fixes a data race that can occur when creating mock connections, caused by incrementing the static MockConnectionBase::next_id_ field. Signed-off-by: Joe Kralicky Co-authored-by: wbpcode --- test/mocks/network/connection.cc | 4 ++-- test/mocks/network/connection.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/mocks/network/connection.cc b/test/mocks/network/connection.cc index 8c7ba968ace12..e1105962dc434 100644 --- a/test/mocks/network/connection.cc +++ b/test/mocks/network/connection.cc @@ -12,7 +12,7 @@ namespace Network { MockConnectionCallbacks::MockConnectionCallbacks() = default; MockConnectionCallbacks::~MockConnectionCallbacks() = default; -uint64_t MockConnectionBase::next_id_; +std::atomic MockConnectionBase::next_id_; void MockConnectionBase::raiseEvent(Network::ConnectionEvent event) { if (event == Network::ConnectionEvent::RemoteClose || @@ -94,7 +94,7 @@ template static void initializeMockConnection(T& connection) { connection.local_close_reason_ = std::string(details); connection.raiseEvent(Network::ConnectionEvent::LocalClose); })); - ON_CALL(connection, id()).WillByDefault(Return(connection.next_id_)); + ON_CALL(connection, id()).WillByDefault(Return(connection.id_)); connection.stream_info_.downstream_connection_info_provider_->setConnectionID(connection.id_); ON_CALL(connection, state()).WillByDefault(ReturnPointee(&connection.state_)); diff --git a/test/mocks/network/connection.h b/test/mocks/network/connection.h index b089520cbcb4b..c53340d9961d9 100644 --- a/test/mocks/network/connection.h +++ b/test/mocks/network/connection.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -33,7 +34,7 @@ class MockConnectionBase { void runHighWatermarkCallbacks(); void runLowWatermarkCallbacks(); - static uint64_t next_id_; + static std::atomic next_id_; testing::NiceMock dispatcher_; std::list callbacks_; From a738b68ec785482cbe0f7dc62261608094ceba02 Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 16 Apr 2026 20:51:50 -0400 Subject: [PATCH 049/750] quic: fix early data behavior for async cert validation (#44451) Commit Message: change the timing of onUpstreamReadyForEarlyData() callback to accommodate async cert validation. The original call site only expects the client to be ready for early data in the `connect()` call stack, which doesn't happen when cert validation is done asynchronously. Risk Level: medium, might break QUIC 0-RTT requests Testing: new integration test Docs Changes: N/A Release Notes: Y Runtime guard: envoy.reloadable_features.fix_http3_early_data_timing --------- Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- changelogs/current.yaml | 4 + .../integration/client_integration_test.cc | 76 +++++++++++++++++++ source/common/http/http3/conn_pool.cc | 15 +++- source/common/runtime/runtime_features.cc | 1 + 4 files changed, 92 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 54594f85a38f1..a39a9ee2aa64d 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -159,6 +159,10 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: quic + change: | + Fixed HTTP/3 client connection pool to handle early data request correctly with asynchronous certificates + validation. This change is gated by the runtime guard ``envoy.reloadable_features.fix_http3_early_data_timing``. - area: router change: | Fixed a bug where the router's buffer overflow check double-counted the request body size when an diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index 2f97e469458c5..dc0f6a5293bff 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -1109,6 +1109,8 @@ TEST_P(ClientIntegrationTest, InvalidDomainReresolveWithNoAddresses) { TEST_P(ClientIntegrationTest, ReresolveAndDrain) { builder_.enableDrainPostDnsRefresh(true); + // 0-RTT requests will not populate some of the final stream intel fields, so skip the validation. + expect_data_streams_ = false; add_fake_dns_ = true; Network::OverrideAddrInfoDnsResolverFactory factory; Registry::InjectFactory inject_factory(factory); @@ -1814,5 +1816,79 @@ TEST_P(ClientIntegrationTest, NoSpaceAvailableWriteErrorSwallowed) { EXPECT_EQ(cc_.status_, "200"); } +TEST_P(ClientIntegrationTest, HttpsWithEarlyData) { + // Dummy comment to invalidate cache + if (getCodecType() != Http::CodecType::HTTP3 || + !Runtime::runtimeFeatureEnabled("envoy.reloadable_features.fix_http3_early_data_timing")) { + return; + } + EXPECT_CALL(helper_handle_->mock_helper(), isCleartextPermitted(_)).Times(0); + EXPECT_CALL(helper_handle_->mock_helper(), validateCertificateChain(_, _)).Times(AnyNumber()); + EXPECT_CALL(helper_handle_->mock_helper(), cleanupAfterCertificateValidation()) + .Times(AnyNumber()); + + autonomous_upstream_ = false; + + initialize(); + default_request_headers_.setScheme("https"); + stream_ = createNewStream(createDefaultStreamCallbacks()); + stream_->sendHeaders(std::make_unique(default_request_headers_), + true); + + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*BaseIntegrationTest::dispatcher_, + upstream_connection_)); + ASSERT_TRUE( + upstream_connection_->waitForNewStream(*BaseIntegrationTest::dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForEndStream(*BaseIntegrationTest::dispatcher_)); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request_->encodeData(10, true); + + terminal_callback_.waitReady(); + + ASSERT_EQ(cc_.on_headers_calls_, 1); + ASSERT_EQ(cc_.status_, "200"); + ASSERT_GE(cc_.on_data_calls_, 1); + ASSERT_EQ(cc_.on_complete_calls_, 1); + EXPECT_EQ(0, getCounterValue("cluster.base.upstream_cx_connect_with_0_rtt")); + + // Wait for session ticket to be received (QUIC sends it after handshake) + timeSystem().realSleepDoNotUseWithoutScrutiny(std::chrono::milliseconds(500)); + + // Close connection to force reconnect and use session ticket + ASSERT_TRUE(upstream_connection_->close()); + ASSERT_TRUE(upstream_connection_->waitForDisconnect()); + upstream_connection_.reset(); + ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_cx_destroy", 1)); + + // Reset terminal callback for the second request. + ConditionalInitializer terminal_callback; + cc_.terminal_callback_ = &terminal_callback; + // Skip validating final stream intel for the second request. + expect_data_streams_ = false; + + default_request_headers_.addCopy("second_request", "1"); + stream_ = createNewStream(createDefaultStreamCallbacks()); + stream_->sendHeaders(std::make_unique(default_request_headers_), + true); + + // Handle second request on upstream + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*BaseIntegrationTest::dispatcher_, + upstream_connection_)); + ASSERT_TRUE( + upstream_connection_->waitForNewStream(*BaseIntegrationTest::dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForEndStream(*BaseIntegrationTest::dispatcher_)); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request_->encodeData(10, true); + + terminal_callback.waitReady(); + + ASSERT_EQ(cc_.on_headers_calls_, 2); + ASSERT_EQ(cc_.status_, "200"); + + EXPECT_EQ(1, getCounterValue("cluster.base.upstream_cx_connect_with_0_rtt")); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.fix_http3_early_data_timing")) { + EXPECT_EQ(1, getCounterValue("cluster.base.upstream_rq_0rtt")); + } +} } // namespace } // namespace Envoy diff --git a/source/common/http/http3/conn_pool.cc b/source/common/http/http3/conn_pool.cc index 9063110cda578..cd08d636a860f 100644 --- a/source/common/http/http3/conn_pool.cc +++ b/source/common/http/http3/conn_pool.cc @@ -70,7 +70,9 @@ ActiveClient::ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent, return; } codec_client_->connect(); - if (readyForStream()) { + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.fix_http3_early_data_timing") && + readyForStream()) { // This client can send early data, so check if there are any pending streams can be sent // as early data. parent_.onUpstreamReadyForEarlyData(*this); @@ -90,9 +92,14 @@ void ActiveClient::onMaxStreamsChanged(uint32_t num_streams) { parent_.transitionActiveClientState(*this, ActiveClient::State::Ready); // If there's waiting streams, make sure the pool will now serve them. parent_.onUpstreamReady(); - } else if (currentUnusedCapacity() == 0 && state() == ActiveClient::State::ReadyForEarlyData) { - // With HTTP/3 this can only happen during a rejected 0-RTT handshake. - parent_.transitionActiveClientState(*this, ActiveClient::State::Busy); + } else if (state() == ActiveClient::State::ReadyForEarlyData) { + if (currentUnusedCapacity() == 0) { + // With HTTP/3 this can only happen during a rejected 0-RTT handshake. + parent_.transitionActiveClientState(*this, ActiveClient::State::Busy); + } else if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.fix_http3_early_data_timing")) { + parent_.onUpstreamReadyForEarlyData(*this); + } } } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 45b9fa4ddab72..610d9e050e207 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -54,6 +54,7 @@ RUNTIME_GUARD(envoy_reloadable_features_ext_authz_http_client_retries_respect_us RUNTIME_GUARD(envoy_reloadable_features_ext_proc_fail_close_spurious_resp); RUNTIME_GUARD(envoy_reloadable_features_ext_proc_inject_data_with_state_update); RUNTIME_GUARD(envoy_reloadable_features_ext_proc_stream_close_optimization); +RUNTIME_GUARD(envoy_reloadable_features_fix_http3_early_data_timing); RUNTIME_GUARD(envoy_reloadable_features_generic_proxy_codec_buffer_limit); RUNTIME_GUARD(envoy_reloadable_features_get_header_tag_from_header_map); RUNTIME_GUARD(envoy_reloadable_features_grpc_side_stream_flow_control); From a6f886eaba0c61ccaf003103b95c4c213f8766ed Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Fri, 17 Apr 2026 00:22:07 -0700 Subject: [PATCH 050/750] docs, http_capsule: fix 'occured' -> 'occurred' in geoip docs and UDP capsule log (#44502) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix remaining 3 occurrences of \`occured\` → \`occurred\` after #44485: --------- Signed-off-by: SAY-5 Co-authored-by: SAY-5 --- docs/root/configuration/http/http_filters/geoip_filter.rst | 2 +- .../configuration/listeners/network_filters/geoip_filter.rst | 2 +- .../udp/udp_proxy/session_filters/http_capsule/http_capsule.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/root/configuration/http/http_filters/geoip_filter.rst b/docs/root/configuration/http/http_filters/geoip_filter.rst index 827eb0b01ea7c..b743e19a57676 100644 --- a/docs/root/configuration/http/http_filters/geoip_filter.rst +++ b/docs/root/configuration/http/http_filters/geoip_filter.rst @@ -61,7 +61,7 @@ per geolocation database type (rooted at ``.maxmind.``). Database t ``.total``, Counter, Total number of lookups performed for a given geolocation database file. ``.hit``, Counter, Total number of successful lookups (with non empty lookup result) performed for a given geolocation database file. - ``.lookup_error``, Counter, Total number of errors that occured during lookups for a given geolocation database file. + ``.lookup_error``, Counter, Total number of errors that occurred during lookups for a given geolocation database file. ``.db_reload_success``, Counter, Total number of times when the geolocation database file was reloaded successfully. ``.db_reload_error``, Counter, Total number of times when the geolocation database file failed to reload. ``.db_build_epoch``, Gauge, The build timestamp of the geolocation database file represented as a Unix epoch value. diff --git a/docs/root/configuration/listeners/network_filters/geoip_filter.rst b/docs/root/configuration/listeners/network_filters/geoip_filter.rst index a9f276acfd8fb..b895489d458be 100644 --- a/docs/root/configuration/listeners/network_filters/geoip_filter.rst +++ b/docs/root/configuration/listeners/network_filters/geoip_filter.rst @@ -144,7 +144,7 @@ Database type can be one of `city_db `_, ``.total``, Counter, Total number of lookups performed for a given geolocation database file. ``.hit``, Counter, Total number of successful lookups (with non empty lookup result) performed for a given geolocation database file. - ``.lookup_error``, Counter, Total number of errors that occured during lookups for a given geolocation database file. + ``.lookup_error``, Counter, Total number of errors that occurred during lookups for a given geolocation database file. ``.db_reload_success``, Counter, Total number of times when the geolocation database file was reloaded successfully. ``.db_reload_error``, Counter, Total number of times when the geolocation database file failed to reload. ``.db_build_epoch``, Gauge, The build timestamp of the geolocation database file represented as a Unix epoch value. diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule.cc b/source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule.cc index 6a21371b0140d..c76152bb0232c 100644 --- a/source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule.cc +++ b/source/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule.cc @@ -33,7 +33,7 @@ WriteFilterStatus HttpCapsuleFilter::onWrite(Network::UdpRecvData& data) { for (const Buffer::RawSlice& slice : data.buffer_->getRawSlices()) { absl::string_view mem_slice(reinterpret_cast(slice.mem_), slice.len_); if (!capsule_parser_.IngestCapsuleFragment(mem_slice)) { - ENVOY_LOG(error, "Capsule ingestion error occured: slice length = {}", slice.len_); + ENVOY_LOG(error, "Capsule ingestion error occurred: slice length = {}", slice.len_); break; } } From 9f39d419d12371f42d53ee38dc7545af8c4e2d5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 06:03:31 +0000 Subject: [PATCH 051/750] build(deps): bump actions/download-artifact from 4 to 8 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- .github/workflows/_upload_gcs.yml | 2 +- .github/workflows/mobile-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_upload_gcs.yml b/.github/workflows/_upload_gcs.yml index 5535bebda962d..e61c4fb237cc4 100644 --- a/.github/workflows/_upload_gcs.yml +++ b/.github/workflows/_upload_gcs.yml @@ -25,7 +25,7 @@ jobs: matrix: artifact: ${{ fromJSON(inputs.artifacts) }} steps: - - uses: actions/download-artifact@v8.0.1 + - uses: actions/download-artifact@v8 with: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index 1cfa6f27a81d3..8effa161e3ab3 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -231,7 +231,7 @@ jobs: name: pypi url: https://pypi.org/p/envoy-mobile-client steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: python-wheel path: dist/ From 957cab2abb5a928132e07c519fb9bab2881b6453 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 06:04:04 +0000 Subject: [PATCH 052/750] build(deps): bump pygithub from 2.9.0 to 2.9.1 in /tools/base Bumps [pygithub](https://github.com/pygithub/pygithub) from 2.9.0 to 2.9.1. - [Release notes](https://github.com/pygithub/pygithub/releases) - [Changelog](https://github.com/PyGithub/PyGithub/blob/main/doc/changes.rst) - [Commits](https://github.com/pygithub/pygithub/compare/v2.9.0...v2.9.1) --- updated-dependencies: - dependency-name: pygithub dependency-version: 2.9.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index feef85fcb5acc..a0200d531db1e 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -1250,9 +1250,9 @@ pyflakes==3.4.0 \ --hash=sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58 \ --hash=sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f # via flake8 -pygithub==2.9.0 \ - --hash=sha256:5e2b260ce327bffce9b00f447b65953ef7078ffe93e5a5425624a3075483927c \ - --hash=sha256:a26abda1222febba31238682634cad11d8b966137ed6cc3c5e445b29a11cb0a4 +pygithub==2.9.1 \ + --hash=sha256:2ec78fca30092d51a42d76f4ddb02131b6f0c666a35dfdf364cf302cdda115b9 \ + --hash=sha256:59771d7ff63d54d427be2e7d0dad2208dfffc2b0a045fec959263787739b611c # via -r requirements.in pyjwt[crypto]==2.12.1 \ --hash=sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c \ From d6f69f83fafc352123f1e534ed9f198874668062 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 06:11:49 +0000 Subject: [PATCH 053/750] build(deps): bump packaging from 26.0 to 26.1 in /docs/tools/python Bumps [packaging](https://github.com/pypa/packaging) from 26.0 to 26.1. - [Release notes](https://github.com/pypa/packaging/releases) - [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/packaging/compare/26.0...26.1) --- updated-dependencies: - dependency-name: packaging dependency-version: '26.1' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- docs/tools/python/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tools/python/requirements.txt b/docs/tools/python/requirements.txt index 773816391980a..b9d78b91cd260 100644 --- a/docs/tools/python/requirements.txt +++ b/docs/tools/python/requirements.txt @@ -1046,9 +1046,9 @@ orjson==3.11.7 \ --hash=sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1 \ --hash=sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867 # via envoy-base-utils -packaging==26.0 \ - --hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \ - --hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529 +packaging==26.1 \ + --hash=sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f \ + --hash=sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de # via # -r requirements.in # aio-api-github From fc51f259b58ecb481ad75791310bafc28363d50e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:43:08 +0000 Subject: [PATCH 054/750] build(deps): bump github/codeql-action from 4.35.1 to 4.35.2 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.1 to 4.35.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/c10b8064de6f491fea524254123dbe5e09572f13...95e58e9a2cdfd71adc6e0353d5c52f41a045d225) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- .github/workflows/codeql-daily.yml | 4 ++-- .github/workflows/codeql-push.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 982d1d79f123a..9a09963776e9b 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -52,7 +52,7 @@ jobs: uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Initialize CodeQL - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # codeql-bundle-v4.35.1 + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # codeql-bundle-v4.35.2 with: languages: cpp trap-caching: false @@ -91,4 +91,4 @@ jobs: git clean -xdf - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # codeql-bundle-v4.35.1 + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # codeql-bundle-v4.35.2 diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 0155f90ec99c8..992efe7b85759 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -88,7 +88,7 @@ jobs: echo "MINIMAL_BUILD_TARGET=//source/common/common:assert_lib" > $GITHUB_ENV - name: Initialize CodeQL - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # codeql-bundle-v4.35.1 + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # codeql-bundle-v4.35.2 with: languages: cpp trap-caching: false @@ -129,4 +129,4 @@ jobs: - name: Perform CodeQL Analysis # if: ${{ env.BUILD_TARGETS != '' }} - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # codeql-bundle-v4.35.1 + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # codeql-bundle-v4.35.2 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 8fe6db29973a7..901d2fc4c6e49 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -41,6 +41,6 @@ jobs: retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 with: sarif_file: results.sarif From 1b7af978b8819e08d2a804556d2ca52d77a0169c Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 17 Apr 2026 13:11:59 +0100 Subject: [PATCH 055/750] test: Fix ws integration test flake (#44508) --- test/integration/BUILD | 6 +----- test/integration/websocket_integration_test.cc | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/test/integration/BUILD b/test/integration/BUILD index 904929878c743..9a464608cfc9a 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1692,12 +1692,8 @@ envoy_cc_test_library( envoy_cc_test( name = "websocket_integration_test", - size = "large", srcs = ["websocket_integration_test.cc"], - rbe_pool = "2core", - tags = [ - "cpu:3", - ], + rbe_pool = "linux_x64_small", deps = [ ":http_protocol_integration_lib", ":websocket_integration_test_lib", diff --git a/test/integration/websocket_integration_test.cc b/test/integration/websocket_integration_test.cc index c303ea2a23c98..0477ffc5104a0 100644 --- a/test/integration/websocket_integration_test.cc +++ b/test/integration/websocket_integration_test.cc @@ -922,7 +922,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradeRouteTimeoutWithRetries) { auto* route_config = hcm.mutable_route_config(); auto* virtual_host = route_config->mutable_virtual_hosts(0); auto* route = virtual_host->mutable_routes(0)->mutable_route(); - route->mutable_timeout()->set_nanos(200 * 1000 * 1000); // 200ms route timeout + route->mutable_timeout()->set_seconds(2); // 2s route timeout }); initialize(); @@ -936,7 +936,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradeRouteTimeoutWithRetries) { ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); - upstream_request_->encodeHeaders(upgradeFailedResponseHeaders(), false); + upstream_request_->encodeHeaders(upgradeFailedResponseHeaders(), true); // Wait for the first request to be reset or disconnected ASSERT_TRUE(waitForUpstreamDisconnectOrReset()); From a5f49d3ae14e9f90bb5d643a62d1a39e9b2bf050 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 17 Apr 2026 15:11:07 +0100 Subject: [PATCH 056/750] deps: Pin more (#44513) Signed-off-by: Ryan Northey --- .github/workflows/_upload_gcs.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/envoy-security-check.yml | 4 ++-- .github/workflows/mobile-release.yml | 2 +- ci/Dockerfile-buildkit | 2 +- ci/Dockerfile-distroless-testing | 7 +++++-- ci/matrix/Dockerfile | 2 +- 7 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/_upload_gcs.yml b/.github/workflows/_upload_gcs.yml index e61c4fb237cc4..e0ed663bc1060 100644 --- a/.github/workflows/_upload_gcs.yml +++ b/.github/workflows/_upload_gcs.yml @@ -25,7 +25,7 @@ jobs: matrix: artifact: ${{ fromJSON(inputs.artifacts) }} steps: - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index abd924731860c..1517db576c565 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -21,7 +21,7 @@ jobs: sudo rm -rf /usr/local/lib/android & sudo rm -rf /usr/share/dotnet & - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6 - name: Install deps shell: bash run: | diff --git a/.github/workflows/envoy-security-check.yml b/.github/workflows/envoy-security-check.yml index 958bc5f337847..fd8d836557ce4 100644 --- a/.github/workflows/envoy-security-check.yml +++ b/.github/workflows/envoy-security-check.yml @@ -68,7 +68,7 @@ jobs: # PR - name: Comment on PR if: matrix.action == 'comment' && github.event.workflow_run.pull_requests[0] - uses: actions/github-script@v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | try { @@ -101,7 +101,7 @@ jobs: # SLACK - name: Checkout repository (secure branch) if: matrix.action == 'slack' - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6 with: # Explicitly checkout main to avoid malicious code ref: main diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index 8effa161e3ab3..f4cc3fb123a34 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -231,7 +231,7 @@ jobs: name: pypi url: https://pypi.org/p/envoy-mobile-client steps: - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: python-wheel path: dist/ diff --git a/ci/Dockerfile-buildkit b/ci/Dockerfile-buildkit index 59ea7e827914d..73f813bdcd3c1 100644 --- a/ci/Dockerfile-buildkit +++ b/ci/Dockerfile-buildkit @@ -1,3 +1,3 @@ # We dont build from this dockerfile - we just parse the version, but storing # here means we get the dependabot updates -FROM moby/buildkit:v0.29.0 +FROM moby/buildkit:v0.29.0@sha256:0039c1d47e8748b5afea56f4e85f14febaf34452bd99d9552d2daa82262b5cc5 diff --git a/ci/Dockerfile-distroless-testing b/ci/Dockerfile-distroless-testing index 6059e4ce18183..b17186ec5f859 100644 --- a/ci/Dockerfile-distroless-testing +++ b/ci/Dockerfile-distroless-testing @@ -1,15 +1,18 @@ +FROM debian:trixie-slim@sha256:4ffb3a1511099754cddc70eb1b12e50ffdb67619aa0ab6c13fcd800a78ef7c7a as debian-base + + FROM envoyproxy/envoy:distroless-dev as distroless-dev FROM envoyproxy/envoy:contrib-distroless-dev as contrib-distroless-dev -FROM debian:trixie-slim as envoy-distroless +FROM debian-base as envoy-distroless COPY --from=distroless-dev / /distroless-dev CMD ["/bin/sh", "-c", "stat -c '%A' /distroless-dev/etc/envoy | grep -q '...x' && echo OK || (echo FAIL: Envoy config is not readable in distroless container; exit 1)"] -FROM debian:trixie-slim as envoy-contrib-distroless +FROM debian-base as envoy-contrib-distroless COPY --from=contrib-distroless-dev /usr/local/bin/envoy /usr/local/bin/envoy # TODO(phlax): Make this an error not warning, once it lands and ci is fixed CMD ["/bin/sh", "-c", "/usr/local/bin/envoy --version 2>&1 | grep -q '\\-contrib' && echo 'OK: contrib-distroless contains contrib binary' || (echo 'WARNING: contrib-distroless does NOT contain contrib binary - version:'; /usr/local/bin/envoy --version 2>&1; exit 0)"] diff --git a/ci/matrix/Dockerfile b/ci/matrix/Dockerfile index 49815657c23ef..540b0d20adc3d 100644 --- a/ci/matrix/Dockerfile +++ b/ci/matrix/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 +FROM ubuntu:22.04@sha256:962f6cadeae0ea6284001009daa4cc9a8c37e75d1f5191cf0eb83fe565b63dd7 ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=tmpfs,target=/var/cache/apt \ --mount=type=tmpfs,target=/var/lib/apt/lists \ From 0f5655fb8a2b2582a019ac19e6cda1af234080e6 Mon Sep 17 00:00:00 2001 From: Biren Roy Date: Fri, 17 Apr 2026 10:41:14 -0400 Subject: [PATCH 057/750] maglev: Adds a streamlined maglev table implementation for the single host case (#44478) Signed-off-by: Biren Roy --- .../maglev/maglev_lb.cc | 37 +++++++++++- .../maglev/maglev_lb.h | 20 +++++++ .../maglev/integration_test.cc | 59 +++++++++++-------- .../maglev/maglev_lb_benchmark.cc | 1 + .../maglev/maglev_lb_test.cc | 41 ++++++++++++- 5 files changed, 131 insertions(+), 27 deletions(-) diff --git a/source/extensions/load_balancing_policies/maglev/maglev_lb.cc b/source/extensions/load_balancing_policies/maglev/maglev_lb.cc index eeb6d2b2d39b6..cf2c24fd06b07 100644 --- a/source/extensions/load_balancing_policies/maglev/maglev_lb.cc +++ b/source/extensions/load_balancing_policies/maglev/maglev_lb.cc @@ -7,6 +7,7 @@ namespace Envoy { namespace Upstream { namespace { + bool shouldUseCompactTable(size_t num_hosts, uint64_t table_size) { // Don't use compact maglev on 32-bit platforms. if constexpr (!(ENVOY_BIT_ARRAY_SUPPORTED)) { @@ -41,7 +42,14 @@ class MaglevFactory : private Logger::Loggable { bool use_hostname_for_hashing, MaglevLoadBalancerStats& stats) { MaglevTableSharedPtr maglev_table; - if (shouldUseCompactTable(normalized_host_weights.size(), table_size)) { + if (normalized_host_weights.size() == 1) { + maglev_table = + std::make_shared(normalized_host_weights, max_normalized_weight, + table_size, use_hostname_for_hashing, stats); + ENVOY_LOG(debug, + "creating single host maglev table given table size {} and number of hosts {}", + table_size, normalized_host_weights.size()); + } else if (shouldUseCompactTable(normalized_host_weights.size(), table_size)) { maglev_table = std::make_shared(normalized_host_weights, max_normalized_weight, table_size, use_hostname_for_hashing, stats); @@ -189,6 +197,7 @@ void OriginalMaglevTable::constructImplementationInternals( } } } + CompactMaglevTable::CompactMaglevTable(const NormalizedHostWeightVector& normalized_host_weights, double max_normalized_weight, uint64_t table_size, bool use_hostname_for_hashing, @@ -254,6 +263,23 @@ void CompactMaglevTable::constructImplementationInternals( } } +DegenerateMaglevTable::DegenerateMaglevTable( + const NormalizedHostWeightVector& normalized_host_weights, double max_normalized_weight, + uint64_t table_size, bool use_hostname_for_hashing, MaglevLoadBalancerStats& stats) + : MaglevTable(table_size, stats) { + constructMaglevTableInternal(normalized_host_weights, max_normalized_weight, + use_hostname_for_hashing); +} + +void DegenerateMaglevTable::constructImplementationInternals( + std::vector& table_build_entries, double /*max_normalized_weight*/) { + ASSERT(table_build_entries.size() == 1, + "DegenerateMaglevTable is intended for the case of a single host!"); + TableBuildEntry& entry = table_build_entries[0]; + single_host_ = entry.host_; + ++entry.count_; +} + void OriginalMaglevTable::logMaglevTable(bool use_hostname_for_hashing) const { for (uint64_t i = 0; i < table_.size(); ++i) { const absl::string_view key_to_hash = hashKey(table_[i], use_hostname_for_hashing); @@ -275,6 +301,10 @@ void CompactMaglevTable::logMaglevTable(bool use_hostname_for_hashing) const { } } +void DegenerateMaglevTable::logMaglevTable(bool /*use_hostname_for_hashing*/) const { + ENVOY_LOG(trace, "maglev: single host {}", single_host_->address()->asString()); +} + MaglevTable::MaglevTable(uint64_t table_size, MaglevLoadBalancerStats& stats) : table_size_(table_size), stats_(stats) {} @@ -310,6 +340,11 @@ HostSelectionResponse CompactMaglevTable::chooseHost(uint64_t hash, uint32_t att return {host_table_[index]}; } +HostSelectionResponse DegenerateMaglevTable::chooseHost(uint64_t /*hash*/, + uint32_t /*attempt*/) const { + return {single_host_}; +} + MaglevLoadBalancer::MaglevLoadBalancer(const PrioritySet& priority_set, ClusterLbStats& stats, Stats::Scope& scope, Runtime::Loader& runtime, Random::RandomGenerator& random, diff --git a/source/extensions/load_balancing_policies/maglev/maglev_lb.h b/source/extensions/load_balancing_policies/maglev/maglev_lb.h index 51dae2eef361b..95b97ac5dfbe3 100644 --- a/source/extensions/load_balancing_policies/maglev/maglev_lb.h +++ b/source/extensions/load_balancing_policies/maglev/maglev_lb.h @@ -162,6 +162,26 @@ class CompactMaglevTable : public MaglevTable { std::vector host_table_; }; +// A simplified implementation for the case where there is only a single host. +class DegenerateMaglevTable : public MaglevTable { +public: + DegenerateMaglevTable(const NormalizedHostWeightVector& normalized_host_weights, + double max_normalized_weight, uint64_t table_size, + bool use_hostname_for_hashing, MaglevLoadBalancerStats& stats); + ~DegenerateMaglevTable() override = default; + + // ThreadAwareLoadBalancerBase::HashingLoadBalancer + HostSelectionResponse chooseHost(uint64_t hash, uint32_t attempt) const override; + + void logMaglevTable(bool use_hostname_for_hashing) const override; + +private: + void constructImplementationInternals(std::vector& table_build_entries, + double max_normalized_weight) override; + + HostConstSharedPtr single_host_; +}; + /** * Thread aware load balancer implementation for Maglev. */ diff --git a/test/extensions/load_balancing_policies/maglev/integration_test.cc b/test/extensions/load_balancing_policies/maglev/integration_test.cc index 119aaa7bdbea0..e28080ffad9d3 100644 --- a/test/extensions/load_balancing_policies/maglev/integration_test.cc +++ b/test/extensions/load_balancing_policies/maglev/integration_test.cc @@ -1,5 +1,6 @@ #include #include +#include #include "envoy/config/endpoint/v3/endpoint_components.pb.h" @@ -10,6 +11,7 @@ #include "test/integration/http_integration.h" +#include "absl/strings/substitute.h" #include "gtest/gtest.h" namespace Envoy { @@ -18,12 +20,16 @@ namespace LoadBalancingPolicies { namespace Maglev { namespace { -class MaglevIntegrationTest : public testing::TestWithParam, - public HttpIntegrationTest { +class MaglevIntegrationTest + : public testing::TestWithParam>, + public HttpIntegrationTest { public: - MaglevIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) { - // Create 3 different upstream server for stateful session test. - setUpstreamCount(3); + static Network::Address::IpVersion getIpVersion() { return std::get<0>(GetParam()); } + static uint32_t getNumUpstreams() { return std::get<1>(GetParam()); } + + MaglevIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, getIpVersion()) { + // Create the desired number of upstream servers for the stateful session test. + setUpstreamCount(getNumUpstreams()); config_helper_.addConfigModifier( [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& @@ -45,28 +51,20 @@ class MaglevIntegrationTest : public testing::TestWithParamname() == "cluster_0"); auto* endpoint = cluster_0->mutable_load_assignment()->mutable_endpoints()->Mutable(0); - constexpr absl::string_view endpoints_yaml = R"EOF( - lb_endpoints: - - endpoint: - address: - socket_address: - address: {} - port_value: 0 - - endpoint: - address: - socket_address: - address: {} - port_value: 0 + std::string endpoints_yaml = "lb_endpoints:"; + constexpr absl::string_view single_endpoint = R"EOF( - endpoint: address: socket_address: - address: {} + address: $0 port_value: 0 )EOF"; + for (uint32_t i = 0; i < getNumUpstreams(); ++i) { + absl::StrAppend(&endpoints_yaml, single_endpoint); + } - const std::string local_address = Network::Test::getLoopbackAddressString(GetParam()); - TestUtility::loadFromYaml( - fmt::format(endpoints_yaml, local_address, local_address, local_address), *endpoint); + const std::string local_address = Network::Test::getLoopbackAddressString(getIpVersion()); + TestUtility::loadFromYaml(absl::Substitute(endpoints_yaml, local_address), *endpoint); // If legacy API is used, set the LB policy by the old way. if (legacy_api) { @@ -95,6 +93,12 @@ class MaglevIntegrationTest : public testing::TestWithParam range(uint64_t bound) { + std::vector v(bound); + std::iota(v.begin(), v.end(), 0); + return v; + } + void runNormalLoadBalancing() { absl::optional unique_upstream_index; @@ -108,7 +112,7 @@ class MaglevIntegrationTest : public testing::TestWithParammakeRequestWithBody(request_headers, 0); - auto upstream_index = waitForNextUpstreamRequest({0, 1, 2}); + auto upstream_index = waitForNextUpstreamRequest(range(getNumUpstreams())); ASSERT(upstream_index.has_value()); if (unique_upstream_index.has_value()) { @@ -129,9 +133,14 @@ class MaglevIntegrationTest : public testing::TestWithParam>& params) { + return absl::StrCat(TestUtility::ipVersionToString(std::get<0>(params.param)), + std::get<1>(params.param), "Upstreams"); + }); TEST_P(MaglevIntegrationTest, NormalLoadBalancing) { initializeConfig(); diff --git a/test/extensions/load_balancing_policies/maglev/maglev_lb_benchmark.cc b/test/extensions/load_balancing_policies/maglev/maglev_lb_benchmark.cc index c20f315c3985c..505cc4f116a76 100644 --- a/test/extensions/load_balancing_policies/maglev/maglev_lb_benchmark.cc +++ b/test/extensions/load_balancing_policies/maglev/maglev_lb_benchmark.cc @@ -72,6 +72,7 @@ void benchmarkMaglevLoadBalancerBuildTable(::benchmark::State& state) { } } BENCHMARK(benchmarkMaglevLoadBalancerBuildTable) + ->Arg(1) ->Arg(100) ->Arg(200) ->Arg(500) diff --git a/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc b/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc index f72b2a632fa14..a8f7a0c318069 100644 --- a/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc +++ b/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc @@ -57,7 +57,7 @@ TEST(MaglevTableLogMaglevTableTest, MaglevTableLogMaglevTableTest) { NormalizedHostWeightVector normalized_host_weights = {{host1, 1}}; { - CompactMaglevTable table(normalized_host_weights, 1, 2, true, stats); + OriginalMaglevTable table(normalized_host_weights, 1, 2, true, stats); table.logMaglevTable(true); } @@ -65,6 +65,11 @@ TEST(MaglevTableLogMaglevTableTest, MaglevTableLogMaglevTableTest) { CompactMaglevTable table(normalized_host_weights, 1, 2, true, stats); table.logMaglevTable(true); } + + { + DegenerateMaglevTable table(normalized_host_weights, 1, 2, true, stats); + table.logMaglevTable(true); + } } // Note: ThreadAwareLoadBalancer base is heavily tested by RingHashLoadBalancerTest. Only basic @@ -147,6 +152,25 @@ TEST_F(MaglevLoadBalancerTest, DefaultMaglevTableSize) { EXPECT_EQ(defaultValue, lb_->tableSize()); }; +TEST_F(MaglevLoadBalancerTest, SingleHost) { + host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90")}; + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.runCallbacks({}, {}); + init(7); + + EXPECT_EQ("maglev_lb.min_entries_per_host", lb_->stats().min_entries_per_host_.name()); + EXPECT_EQ("maglev_lb.max_entries_per_host", lb_->stats().max_entries_per_host_.name()); + EXPECT_EQ(1, lb_->stats().min_entries_per_host_.value()); + EXPECT_EQ(1, lb_->stats().max_entries_per_host_.value()); + + // Always selects the single host: 127.0.0.1:90 + LoadBalancerPtr lb = lb_->factory()->create(lb_params_); + for (uint32_t i = 0; i < 5; ++i) { + TestLoadBalancerContext context(i); + EXPECT_EQ(host_set_.hosts_[0], lb->chooseHost(&context).host); + } +} + // Basic sanity tests. TEST_F(MaglevLoadBalancerTest, Basic) { host_set_.hosts_ = { @@ -511,6 +535,21 @@ TEST_F(MaglevLoadBalancerTest, Weighted) { } } +TEST_F(MaglevLoadBalancerTest, WeightedSingleHost) { + host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90", 7)}; + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.runCallbacks({}, {}); + init(17); + EXPECT_EQ(1, lb_->stats().min_entries_per_host_.value()); + EXPECT_EQ(1, lb_->stats().max_entries_per_host_.value()); + + LoadBalancerPtr lb = lb_->factory()->create(lb_params_); + for (uint32_t i = 0; i < 17; ++i) { + TestLoadBalancerContext context(i); + EXPECT_EQ(host_set_.hosts_[0], lb->chooseHost(&context).host); + } +} + // Locality weighted sanity test when localities have the same weights. Host weights for hosts in // different localities shouldn't matter. TEST_F(MaglevLoadBalancerTest, LocalityWeightedSameLocalityWeights) { From 1728d6a1695f6f4dbb5f1a24dfa32fbc15868e67 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 17 Apr 2026 16:03:41 +0100 Subject: [PATCH 058/750] distribution/docker: Minor fix for buildkit image (#44516) Signed-off-by: Ryan Northey --- distribution/docker/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/docker/build.sh b/distribution/docker/build.sh index a9985bd09e3f5..a5d10d617d57d 100755 --- a/distribution/docker/build.sh +++ b/distribution/docker/build.sh @@ -48,7 +48,7 @@ fi # Setting environments for buildx tools config_env() { - BUILDKIT_VERSION=$(grep '^FROM moby/buildkit:' ci/Dockerfile-buildkit | cut -d ':' -f2) + BUILDKIT_VERSION=$(grep '^FROM moby/buildkit:' ci/Dockerfile-buildkit | cut -d ':' -f2-) echo ">> BUILDX: install ${BUILDKIT_VERSION}" if [[ "${DOCKER_PLATFORM}" == *","* ]]; then From c5cecdfea64b1d5492c765884020dbb7a6e8cdbc Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Fri, 17 Apr 2026 11:41:46 -0400 Subject: [PATCH 059/750] Graceful handling of errors when reading SO_ORIGINAL_DST option (#44378) Remove possibility of hard crash when SO_ORIGINAL_DST returns an invalid address. Such errors have not been observed in production and the change is purely defensive. Risk Level: low Testing: unit tests Docs Changes: no Release Notes: no Platform Specific Features: Linux only Signed-off-by: Yan Avlasov --- source/common/network/utility.cc | 9 ++++++++- test/common/network/utility_test.cc | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/source/common/network/utility.cc b/source/common/network/utility.cc index 9b203925d35b6..c4969de332014 100644 --- a/source/common/network/utility.cc +++ b/source/common/network/utility.cc @@ -376,7 +376,14 @@ Address::InstanceConstSharedPtr Utility::getOriginalDst(Socket& sock) { return nullptr; } - return Address::addressFromSockAddrOrDie(orig_addr, 0, -1, true /* default for v6 constructor */); + auto address_or_status = + Address::addressFromSockAddr(orig_addr, 0, true /* default for v6 constructor */); + if (!address_or_status.ok()) { + ENVOY_LOG_MISC(debug, "Failed to get address from sockaddr for getOriginalDst: {}", + address_or_status.status().message()); + return nullptr; + } + return *address_or_status; #else // TODO(zuercher): determine if connection redirection is possible under macOS (c.f. pfctl and diff --git a/test/common/network/utility_test.cc b/test/common/network/utility_test.cc index 6457394d144d4..36f0b4435461c 100644 --- a/test/common/network/utility_test.cc +++ b/test/common/network/utility_test.cc @@ -391,6 +391,15 @@ TEST(NetworkUtility, GetOriginalDst) { EXPECT_CALL(socket, getSocketOption(Eq(SOL_IP), Eq(SO_ORIGINAL_DST), _, _)) .WillOnce(DoAll(SetArg2Sockaddr(storage), Return(Api::SysCallIntResult{0, 0}))); EXPECT_EQ("12.34.56.78:9527", Utility::getOriginalDst(socket)->asString()); + + // Invalid family returned by SO_ORIGINAL_DST should cause addressFromSockAddr to fail and + // getOriginalDst to return nullptr + sin.sin_family = AF_UNSPEC; + EXPECT_CALL(socket, getSocketOption(Eq(SOL_IP), Eq(SO_ORIGINAL_DST), _, _)) + .WillOnce(DoAll(SetArg2Sockaddr(storage), Return(Api::SysCallIntResult{0, 0}))); + EXPECT_EQ(nullptr, Utility::getOriginalDst(socket)); + + sin.sin_family = AF_INET; #ifndef WIN32 // Transparent socket gets original dst from local address while connection tracking disabled EXPECT_CALL(socket, getSocketOption(Eq(SOL_IP), Eq(SO_ORIGINAL_DST), _, _)) From c9b95ddefc05e5620c0bca95a92fe4b7933fe84c Mon Sep 17 00:00:00 2001 From: Michal Kielan Date: Fri, 17 Apr 2026 18:24:13 +0200 Subject: [PATCH 060/750] Use plain atomic for last HC status (#44509) Commit Message: Use plain atomic for last HC status optional is not thread-safe. Use plain atomic and use 0 as the sentinel value (as noted in the upsteram.h comment) Risk Level: low Testing: unit tests: //test/common/upstream:hds_test, //test/common/upstream:health_checker_impl_test Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Michal Kielan --- source/common/upstream/upstream_impl.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 129b879bcfde9..1387c129ca66c 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -390,9 +390,12 @@ class HostImplBase : public Host, uint32_t healthFlagsGetAll() const override { return health_flags_; } void healthFlagsSetAll(uint32_t bits) override { health_flags_ |= bits; } - void setLastHealthCheckHttpStatus(uint64_t status) override { last_hc_http_status_ = status; } + void setLastHealthCheckHttpStatus(uint64_t status) override { + last_hc_http_status_.store(status, std::memory_order_relaxed); + } absl::optional lastHealthCheckHttpStatus() const override { - return last_hc_http_status_; + const uint64_t status = last_hc_http_status_.load(std::memory_order_relaxed); + return status == 0 ? absl::nullopt : absl::make_optional(status); } Host::HealthStatus healthStatus() const override { @@ -483,7 +486,8 @@ class HostImplBase : public Host, // flag access? May be we could refactor HealthFlag to contain all these statuses and flags in the // future. std::atomic eds_health_status_{}; - absl::optional> last_hc_http_status_ = absl::nullopt; + // 0 indicates no status has been set. + std::atomic last_hc_http_status_{0}; struct HostHandleImpl : HostHandle { HostHandleImpl(const std::shared_ptr& parent) : parent_(parent) { From daee25eb56be78dd5280381dd11d063781bf355a Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Fri, 17 Apr 2026 19:52:01 +0200 Subject: [PATCH 061/750] dynamic_modules: subscribe to worker local priority set in cluster LB (#44520) ## Description This PR moves the membership update subscription on `DynamicModuleLoadBalancer` from the cluster main priority set to the worker local priority set delivered through `LoadBalancerParams`. `LoadBalancerFactory::create` runs on each worker thread via `tls_.runOnAllThreads` posted from `postThreadLocalClusterUpdate`, so subscribing on the cluster main priority set required mutating the shared `Common::CallbackManager` callback list from worker threads while the main thread could iterate it via `PrioritySetImpl::runUpdateCallbacks`. The worker local priority set is updated only on the thread that owns the load balancer, via `ThreadLocalClusterManagerImpl::updateClusterMembership`, so subscribe, fire, and unsubscribe all stay on a single thread. --- **Commit Message:** dynamic_modules: subscribe to worker local priority set in cluster LB **Additional Description:** Moves the cluster LB membership update subscription onto the worker local priority set delivered via ``LoadBalancerParams``. **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** N/A **Release Notes:** Added Signed-off-by: Rohit Agrawal --- .../clusters/dynamic_modules/cluster.cc | 13 +-- .../clusters/dynamic_modules/cluster.h | 11 ++- .../clusters/dynamic_modules/cluster_test.cc | 80 +++++++++++++++---- 3 files changed, 81 insertions(+), 23 deletions(-) diff --git a/source/extensions/clusters/dynamic_modules/cluster.cc b/source/extensions/clusters/dynamic_modules/cluster.cc index 2342c9e9f9db4..9f38ac7f319ec 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.cc +++ b/source/extensions/clusters/dynamic_modules/cluster.cc @@ -31,8 +31,8 @@ struct DynamicModuleThreadAwareLoadBalancer : public Upstream::ThreadAwareLoadBa struct LoadBalancerFactory : public Upstream::LoadBalancerFactory { LoadBalancerFactory(DynamicModuleClusterHandleSharedPtr handle) : handle_(std::move(handle)) {} - Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams) override { - return std::make_unique(handle_); + Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams params) override { + return std::make_unique(handle_, params.priority_set); } DynamicModuleClusterHandleSharedPtr handle_; @@ -591,14 +591,15 @@ void DynamicModuleCluster::HttpCalloutCallback::onFailure(const Http::AsyncClien // ================================================================================================= DynamicModuleLoadBalancer::DynamicModuleLoadBalancer( - const DynamicModuleClusterHandleSharedPtr& handle) - : handle_(handle), in_module_lb_(nullptr) { + const DynamicModuleClusterHandleSharedPtr& handle, const Upstream::PrioritySet& priority_set) + : handle_(handle), priority_set_(priority_set), in_module_lb_(nullptr) { in_module_lb_ = handle_->cluster_->config()->on_cluster_lb_new_(handle_->cluster_->inModuleCluster(), this); - // Register for host membership updates if the module implements the hook. + // Register for host membership updates if the module implements the hook. Subscribe on the + // worker local priority set so the callback list is only mutated on this worker thread. if (handle_->cluster_->config()->on_cluster_lb_on_host_membership_update_ != nullptr) { - member_update_cb_ = handle_->cluster_->prioritySet().addMemberUpdateCb( + member_update_cb_ = priority_set_.addMemberUpdateCb( [this](const Upstream::HostVector& hosts_added, const Upstream::HostVector& hosts_removed) { hosts_added_ = &hosts_added; hosts_removed_ = &hosts_removed; diff --git a/source/extensions/clusters/dynamic_modules/cluster.h b/source/extensions/clusters/dynamic_modules/cluster.h index ab740ba5a0693..160c2500fb2f1 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.h +++ b/source/extensions/clusters/dynamic_modules/cluster.h @@ -485,7 +485,11 @@ class DynamicModuleAsyncHostSelectionHandle : public Upstream::AsyncHostSelectio */ class DynamicModuleLoadBalancer : public Upstream::LoadBalancer { public: - DynamicModuleLoadBalancer(const DynamicModuleClusterHandleSharedPtr& handle); + // ``priority_set`` must be the worker local priority set supplied via + // ``LoadBalancerParams::priority_set``. The membership update subscription is registered on it so + // that the callback list is mutated only on the thread that owns this load balancer instance. + DynamicModuleLoadBalancer(const DynamicModuleClusterHandleSharedPtr& handle, + const Upstream::PrioritySet& priority_set); ~DynamicModuleLoadBalancer() override; // Upstream::LoadBalancer. @@ -532,8 +536,13 @@ class DynamicModuleLoadBalancer : public Upstream::LoadBalancer { const Upstream::HostVector* hostsAdded() const { return hosts_added_; } const Upstream::HostVector* hostsRemoved() const { return hosts_removed_; } + // Returns the priority set that this load balancer subscribes to for host membership updates. + const Upstream::PrioritySet& memberUpdatePrioritySet() const { return priority_set_; } + private: const DynamicModuleClusterHandleSharedPtr handle_; + // Worker local priority set that backs the membership update subscription. + const Upstream::PrioritySet& priority_set_; envoy_dynamic_module_type_cluster_lb_module_ptr in_module_lb_; // Shared cancellation flag for the active async host selection. Set in chooseHost when the diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index 1f007ee86b219..2e7267bb87404 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -2104,7 +2104,7 @@ TEST_F(DynamicModuleClusterTest, AsyncHostSelectionCompleteWithHost) { // Create a handle for the async completion callback. auto handle = std::make_shared( std::dynamic_pointer_cast(cluster)); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_envoy_ptr = static_cast(lb_instance.get()); auto* context_ptr = static_cast(&context); @@ -2128,7 +2128,7 @@ TEST_F(DynamicModuleClusterTest, AsyncHostSelectionCompleteNullHost) { auto handle = std::make_shared( std::dynamic_pointer_cast(cluster)); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_envoy_ptr = static_cast(lb_instance.get()); auto* context_ptr = static_cast(&context); @@ -2152,7 +2152,7 @@ TEST_F(DynamicModuleClusterTest, AsyncHostSelectionCompleteEmptyDetails) { auto handle = std::make_shared( std::dynamic_pointer_cast(cluster)); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_envoy_ptr = static_cast(lb_instance.get()); auto* context_ptr = static_cast(&context); @@ -2631,7 +2631,7 @@ TEST_F(DynamicModuleClusterTest, AddHostsWithLocalityAndMetadataABI) { // Verify metadata through the LB host metadata ABI callbacks. auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); envoy_dynamic_module_type_envoy_buffer meta_result = {nullptr, 0}; @@ -2696,7 +2696,7 @@ TEST_F(DynamicModuleClusterTest, UpdateHostHealthABI) { // Verify via the LB health callback. auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); EXPECT_EQ(envoy_dynamic_module_type_host_health_Unhealthy, @@ -2771,7 +2771,7 @@ TEST_F(DynamicModuleClusterTest, LbHostMembershipUpdate) { // Create an LB. The cluster_no_op module implements on_cluster_lb_on_host_membership_update, // so the LB should register for membership updates. auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); // Add hosts - this should trigger the membership update callback on the LB. std::vector hosts; @@ -2793,7 +2793,7 @@ TEST_F(DynamicModuleClusterTest, LbMemberUpdateHostAddress) { auto cluster = std::dynamic_pointer_cast(result->first); auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); // When not in a membership update callback, the function should return false. @@ -2830,7 +2830,7 @@ TEST_F(DynamicModuleClusterTest, HostsPerLocalityWithLocality) { // Verify through the LB that locality grouping works. auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); // Should have 2 locality buckets (the healthy hosts per locality). @@ -2855,7 +2855,7 @@ TEST_F(DynamicModuleClusterTest, UpdateHostHealthAffectsHealthyHosts) { ASSERT_TRUE(addSimpleHosts(*cluster, {"127.0.0.1:10001", "127.0.0.1:10002"}, {1, 1}, hosts)); auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); // Both hosts should be healthy initially. @@ -2883,7 +2883,7 @@ TEST_F(DynamicModuleClusterTest, LbFindHostByAddress) { ASSERT_TRUE(addSimpleHosts(*cluster, {"127.0.0.1:10001", "127.0.0.1:10002"}, {1, 2}, hosts)); auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); // Find existing host by address via the LB callback. @@ -2920,7 +2920,7 @@ TEST_F(DynamicModuleClusterTest, LbGetHost) { ASSERT_TRUE(addSimpleHosts(*cluster, {"127.0.0.1:10001", "127.0.0.1:10002"}, {1, 2}, hosts)); auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); // Get host by index from all hosts. @@ -2956,7 +2956,7 @@ TEST_F(DynamicModuleClusterTest, LbGetHostIncludesUnhealthy) { EXPECT_TRUE(cluster->updateHostHealth(hosts[0], envoy_dynamic_module_type_host_health_Unhealthy)); auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); // get_host should still return both hosts. @@ -2987,7 +2987,7 @@ TEST_F(DynamicModuleClusterTest, AddHostsToPriority) { addSimpleHosts(*cluster, {"127.0.0.1:10002", "127.0.0.1:10003"}, {1, 1}, hosts_p1, 1)); auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); // Verify hosts at each priority level. @@ -3025,7 +3025,7 @@ TEST_F(DynamicModuleClusterTest, AddHostsWithPriorityABI) { // Verify via LB. auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); EXPECT_EQ(1, envoy_dynamic_module_callback_cluster_lb_get_hosts_count(lb_ptr, 0)); @@ -3061,7 +3061,7 @@ TEST_F(DynamicModuleClusterTest, AddHostsWithLocalityAndPriorityABI) { // Verify via LB. auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); EXPECT_EQ(0, envoy_dynamic_module_callback_cluster_lb_get_hosts_count(lb_ptr, 0)); @@ -3090,7 +3090,7 @@ TEST_F(DynamicModuleClusterTest, UpdateHostHealthAtNonZeroPriority) { addSimpleHosts(*cluster, {"127.0.0.1:10002", "127.0.0.1:10003"}, {1, 1}, hosts_p1, 1)); auto handle = std::make_shared(cluster); - auto lb_instance = std::make_unique(handle); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); auto* lb_ptr = static_cast(lb_instance.get()); // All hosts should be healthy initially. @@ -3114,6 +3114,54 @@ TEST_F(DynamicModuleClusterTest, UpdateHostHealthAtNonZeroPriority) { EXPECT_EQ(2, envoy_dynamic_module_callback_cluster_lb_get_healthy_host_count(lb_ptr, 1)); } +// The load balancer must register its membership update callback on the priority set supplied at +// construction, which production callers always fill with the worker local set from +// ``LoadBalancerParams``. Reference equality against the constructor argument proves the +// subscription target and would fail if the registration moved back to the cluster main set. +TEST_F(DynamicModuleClusterTest, LbMemberUpdateCbRegistersOnWorkerLocalPrioritySet) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()) << result.status().message(); + auto cluster = std::dynamic_pointer_cast(result->first); + auto handle = std::make_shared(cluster); + + Upstream::PrioritySetImpl worker_local; + auto lb_instance = std::make_unique(handle, worker_local); + + EXPECT_EQ(&lb_instance->memberUpdatePrioritySet(), &worker_local); + EXPECT_NE(&lb_instance->memberUpdatePrioritySet(), &cluster->prioritySet()); +} + +// Repeated construct, update, destroy cycle on the worker local priority set to cover the +// subscribe and unsubscribe paths under sanitizer builds. +TEST_F(DynamicModuleClusterTest, LbRepeatedConstructTeardownWithUpdates) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()) << result.status().message(); + auto cluster = std::dynamic_pointer_cast(result->first); + + Upstream::PrioritySetImpl worker_priority_set; + worker_priority_set.getOrCreateHostSet(0); + + std::vector hosts; + ASSERT_TRUE(addSimpleHosts(*cluster, {"127.0.0.1:10101", "127.0.0.1:10102"}, {1, 1}, hosts)); + + for (int i = 0; i < 16; ++i) { + auto handle = std::make_shared(cluster); + auto lb_instance = std::make_unique(handle, worker_priority_set); + + Upstream::HostVectorSharedPtr all_hosts(new Upstream::HostVector{hosts[0]}); + worker_priority_set.updateHosts( + 0, + Upstream::HostSetImpl::partitionHosts(all_hosts, Upstream::HostsPerLocalityImpl::empty()), + {}, {hosts[0]}, {}, absl::nullopt, absl::nullopt); + + all_hosts = std::make_shared(Upstream::HostVector{hosts[0], hosts[1]}); + worker_priority_set.updateHosts( + 0, + Upstream::HostSetImpl::partitionHosts(all_hosts, Upstream::HostsPerLocalityImpl::empty()), + {}, {hosts[1]}, {}, absl::nullopt, absl::nullopt); + } +} + } // namespace } // namespace DynamicModules } // namespace Clusters From 095e13d2740727c31e781f7abc4fb67ab1dd04ac Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 17 Apr 2026 18:53:30 +0100 Subject: [PATCH 062/750] test/ws: Follow up fixes from #44508 (#44518) Signed-off-by: Ryan Northey --- test/integration/BUILD | 1 + test/integration/websocket_integration_test.cc | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/test/integration/BUILD b/test/integration/BUILD index 9a464608cfc9a..415e5cc70c75a 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1694,6 +1694,7 @@ envoy_cc_test( name = "websocket_integration_test", srcs = ["websocket_integration_test.cc"], rbe_pool = "linux_x64_small", + shard_count = 4, deps = [ ":http_protocol_integration_lib", ":websocket_integration_test_lib", diff --git a/test/integration/websocket_integration_test.cc b/test/integration/websocket_integration_test.cc index 0477ffc5104a0..146e376718dde 100644 --- a/test/integration/websocket_integration_test.cc +++ b/test/integration/websocket_integration_test.cc @@ -262,8 +262,8 @@ TEST_P(WebsocketIntegrationTest, WebSocketConnectionIdleTimeout) { auto* route_config = hcm.mutable_route_config(); auto* virtual_host = route_config->mutable_virtual_hosts(0); auto* route = virtual_host->mutable_routes(0)->mutable_route(); - route->mutable_idle_timeout()->set_seconds(0); - route->mutable_idle_timeout()->set_nanos(200 * 1000 * 1000); + route->mutable_idle_timeout()->set_seconds(1); + route->mutable_idle_timeout()->set_nanos(0); }); initialize(); @@ -850,8 +850,8 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradePerTryTimeout) { auto* route_config = hcm.mutable_route_config(); auto* virtual_host = route_config->mutable_virtual_hosts(0); auto* route = virtual_host->mutable_routes(0)->mutable_route(); - route->mutable_retry_policy()->mutable_per_try_timeout()->set_nanos( - 200 * 1000 * 1000); // 200ms per-try timeout + route->mutable_retry_policy()->mutable_per_try_timeout()->set_seconds( + 2); // 2s per-try timeout (safe under msan/tsan) }); initialize(); @@ -886,7 +886,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradeRouteTimeout) { auto* route_config = hcm.mutable_route_config(); auto* virtual_host = route_config->mutable_virtual_hosts(0); auto* route = virtual_host->mutable_routes(0)->mutable_route(); - route->mutable_timeout()->set_nanos(200 * 1000 * 1000); // 200ms route timeout + route->mutable_timeout()->set_seconds(2); // 2s route timeout (safe under msan/tsan) }); initialize(); From f974a2c67b39451bb09e59af7fecbedcc194eb1b Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 17 Apr 2026 19:53:56 +0100 Subject: [PATCH 063/750] test/ext_authz: Shard to prevent timeout flake (#44519) Signed-off-by: Ryan Northey --- test/extensions/filters/http/ext_authz/BUILD | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/extensions/filters/http/ext_authz/BUILD b/test/extensions/filters/http/ext_authz/BUILD index be52f7f2f820d..56be0192ba29e 100644 --- a/test/extensions/filters/http/ext_authz/BUILD +++ b/test/extensions/filters/http/ext_authz/BUILD @@ -69,13 +69,11 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "ext_authz_integration_test", - size = "large", srcs = ["ext_authz_integration_test.cc"], - data = [ - "ext_authz.yaml", - ], + data = ["ext_authz.yaml"], extension_names = ["envoy.filters.http.ext_authz"], - rbe_pool = "6gig", + rbe_pool = "linux_x64_small", + shard_count = 4, deps = [ ":ext_authz_fuzz_proto_cc_proto", ":logging_test_filter_lib", From adcf073e0c4e627ea96467a95bd176040cfc99db Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 17 Apr 2026 20:44:47 +0100 Subject: [PATCH 064/750] ext_proc: fix logging info loss when gRPC stream closes before encodeComplete (#44492) hopefully fix: #44447 Signed-off-by: Ryan Northey --- source/extensions/filters/http/ext_proc/ext_proc.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index b083c76d379c2..43fe0899581e0 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -2007,6 +2007,14 @@ void Filter::onGrpcCloseWithStatus(Grpc::Status::GrpcStatus status) { processing_complete_ = true; stats_.streams_closed_.inc(); stats_.server_half_closed_.inc(); + + // In observability mode, capture logging info before closing the stream. + // encodeComplete() also calls logStreamInfo(), but the ordering is + // nondeterministic: if closeStream() nullifies stream_ first, that + // call becomes a no-op. + if (config_->observabilityMode()) { + logStreamInfo(); + } // Successful close. We can ignore the stream for the rest of our request // and response processing. closeStream(); From a624bd2e8d3f1bdd297d7dbfa9bd850a23b69420 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 17 Apr 2026 21:06:54 +0100 Subject: [PATCH 065/750] test: Attempt to fix flake in grpc transcoder test (#44510) by separating the tests Hopefully fix: #44427 Signed-off-by: Ryan Northey --- .../grpc_json_transcoder_integration_test.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc index 5a9990bbce34f..d13393560390c 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc @@ -1299,7 +1299,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UTF8) { false); } -TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidation) { +TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidationGrpcContentType) { HttpIntegrationTest::initialize(); // Transcoding does not occur from a request with the gRPC content type. @@ -1315,7 +1315,10 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidation) { Http::TestResponseHeaderMapImpl{ {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, "", true, false, R"({ "theme" : "Children")"); +} +TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidationUnknownPath) { + HttpIntegrationTest::initialize(); // Transcoding does not occur when unknown path is called. // HTTP Request to is passed directly to gRPC backend. // gRPC response is passed directly to HTTP client. @@ -1328,7 +1331,10 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidation) { Http::TestResponseHeaderMapImpl{ {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}}, "", true, false, R"({ "theme" : "Children")"); +} +TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidationUnknownQueryParam) { + HttpIntegrationTest::initialize(); // Transcoding does not occur when unknown query param is included. // HTTP Request to is passed directly to gRPC backend. // gRPC response is passed directly to HTTP client. From 445102bc6bc8f207c84923bb0c32be21980d9b45 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 17 Apr 2026 21:10:35 +0100 Subject: [PATCH 066/750] test: Fix dfp flake (#44504) Fix: #44491 Signed-off-by: Ryan Northey --- test/extensions/filters/http/dynamic_forward_proxy/BUILD | 3 +-- .../dynamic_forward_proxy/proxy_filter_integration_test.cc | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/extensions/filters/http/dynamic_forward_proxy/BUILD b/test/extensions/filters/http/dynamic_forward_proxy/BUILD index 2a0946b675689..1800e3b7d6803 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/BUILD +++ b/test/extensions/filters/http/dynamic_forward_proxy/BUILD @@ -115,7 +115,6 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "legacy_proxy_filter_integration_test", - size = "large", srcs = ["proxy_filter_integration_test.cc"], args = [ "--runtime-feature-disable-for-tests=envoy.reloadable_features.dfp_cluster_resolves_hosts", @@ -124,7 +123,7 @@ envoy_extension_cc_test( "//test/config/integration/certs", ], extension_names = ["envoy.filters.http.dynamic_forward_proxy"], - rbe_pool = "2core", + rbe_pool = "linux_x64_small", # TODO(envoyproxy/windows-dev): Diagnose failure shown on clang-cl build, see: # https://gist.github.com/wrowe/a152cb1d12c2f751916122aed39d8517 tags = ["fails_on_clang_cl"], diff --git a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc index 8a8e5f4f4ed58..acb030adafffb 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -710,6 +710,8 @@ TEST_P(ProxyFilterIntegrationTest, ParallelRequestsWithFakeResolver) { test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); // Start the next request before unblocking the resolve. auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + // Wait for both requests to be received downstream before unblocking DNS. + test_server_->waitForCounterEq("http.config_test.downstream_rq_total", 2); Network::TestResolver::unblockResolve(); ASSERT_TRUE(response1->waitForEndStream()); From b6bb1f4a979eeea55f1860b6ca3894d74c845083 Mon Sep 17 00:00:00 2001 From: Anton Kanugalawattage Date: Fri, 17 Apr 2026 16:15:11 -0400 Subject: [PATCH 067/750] dym(rust): add automock to EnvoyNetworkFilter (#44514) --- .../dynamic_modules/sdk/rust/src/lib_test.rs | 66 +++++++++++++++++++ .../dynamic_modules/sdk/rust/src/network.rs | 16 +++-- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs index 632f8297a8c6e..9820b68ea2110 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs @@ -5457,3 +5457,69 @@ fn test_bootstrap_extension_listener_lifecycle_default_noop() { envoy_dynamic_module_on_bootstrap_extension_config_destroy(config_ptr); } } + +// ============================================================================= +// MockEnvoyNetworkFilter Tests +// ============================================================================= + +#[test] +fn test_mock_envoy_network_filter_on_new_connection() { + struct TestNetworkFilter; + impl NetworkFilter for TestNetworkFilter { + fn on_new_connection( + &mut self, + envoy_filter: &mut network::MockEnvoyNetworkFilter, + ) -> abi::envoy_dynamic_module_type_on_network_filter_data_status { + assert_eq!(envoy_filter.get_connection_id(), 42); + assert_eq!(envoy_filter.get_remote_address(), ("10.0.0.1".to_string(), 1234)); + abi::envoy_dynamic_module_type_on_network_filter_data_status::Continue + } + } + + let mut mock = network::MockEnvoyNetworkFilter::new(); + mock.expect_get_connection_id().returning(|| 42); + mock + .expect_get_remote_address() + .returning(|| ("10.0.0.1".to_string(), 1234)); + + let mut filter = TestNetworkFilter; + assert_eq!( + filter.on_new_connection(&mut mock), + abi::envoy_dynamic_module_type_on_network_filter_data_status::Continue + ); +} + +#[test] +fn test_mock_envoy_network_filter_on_read() { + struct TestNetworkFilter; + impl NetworkFilter for TestNetworkFilter { + fn on_read( + &mut self, + envoy_filter: &mut network::MockEnvoyNetworkFilter, + data_length: usize, + _end_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_network_filter_data_status { + envoy_filter.drain_read_buffer(data_length); + envoy_filter.append_write_buffer(b"response"); + abi::envoy_dynamic_module_type_on_network_filter_data_status::Continue + } + } + + let mut mock = network::MockEnvoyNetworkFilter::new(); + mock + .expect_drain_read_buffer() + .with(mockall::predicate::eq(100)) + .times(1) + .returning(|_| ()); + mock + .expect_append_write_buffer() + .with(mockall::predicate::eq(b"response".as_slice())) + .times(1) + .returning(|_| ()); + + let mut filter = TestNetworkFilter; + assert_eq!( + filter.on_read(&mut mock, 100, false), + abi::envoy_dynamic_module_type_on_network_filter_data_status::Continue + ); +} diff --git a/source/extensions/dynamic_modules/sdk/rust/src/network.rs b/source/extensions/dynamic_modules/sdk/rust/src/network.rs index 633d055a8fc5a..0c5d284d0f4b1 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/network.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/network.rs @@ -162,14 +162,16 @@ pub trait NetworkFilter { /// The trait that represents the Envoy network filter. /// This is used in [`NetworkFilter`] to interact with the underlying Envoy network filter object. +#[automock] +#[allow(clippy::needless_lifetimes)] // Explicit lifetime specifiers are needed for mockall. pub trait EnvoyNetworkFilter { /// Get the read buffer chunks. This is valid after the first on_read callback for the lifetime /// of the connection. - fn get_read_buffer_chunks(&mut self) -> (Vec>, usize); + fn get_read_buffer_chunks<'a>(&'a mut self) -> (Vec>, usize); /// Get the write buffer chunks. This is valid after the first on_write callback for the lifetime /// of the connection. - fn get_write_buffer_chunks(&mut self) -> (Vec>, usize); + fn get_write_buffer_chunks<'a>(&'a mut self) -> (Vec>, usize); /// Drain bytes from the beginning of the read buffer. fn drain_read_buffer(&mut self, length: usize); @@ -228,23 +230,23 @@ pub trait EnvoyNetworkFilter { /// Get the requested server name (SNI). /// Returns None if SNI is not available. - fn get_requested_server_name(&self) -> Option>; + fn get_requested_server_name<'a>(&'a self) -> Option>; /// Get the direct remote (client) address and port without considering proxy protocol. /// Returns None if the address is not available or not an IP address. - fn get_direct_remote_address(&self) -> Option<(EnvoyBuffer<'_>, u32)>; + fn get_direct_remote_address<'a>(&'a self) -> Option<(EnvoyBuffer<'a>, u32)>; /// Get the SSL URI SANs from the peer certificate. /// Returns an empty vector if the connection is not SSL or no URI SANs are present. - fn get_ssl_uri_sans(&self) -> Vec>; + fn get_ssl_uri_sans<'a>(&'a self) -> Vec>; /// Get the SSL DNS SANs from the peer certificate. /// Returns an empty vector if the connection is not SSL or no DNS SANs are present. - fn get_ssl_dns_sans(&self) -> Vec>; + fn get_ssl_dns_sans<'a>(&'a self) -> Vec>; /// Get the SSL subject from the peer certificate. /// Returns None if the connection is not SSL or subject is not available. - fn get_ssl_subject(&self) -> Option>; + fn get_ssl_subject<'a>(&'a self) -> Option>; /// Set the filter state with the given key and byte value. /// Returns true if the operation is successful. From 8e4637d371636ff72986b0b07631d56d199b2859 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 17 Apr 2026 22:10:30 +0100 Subject: [PATCH 068/750] quic: Remove flaky ASSERT in `ShouldCreateOutgoingBidirectionalStream` (#44500) QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream() can legitimately return false, but we return true unconditionally to avoid a nullptr deref in QuicHttpClientConnectionImpl::newStream. The existing ASSERT on the parent's return value was therefore incorrect and triggered flakes (e.g. in buffer_accounting_integration_test on MSAN). Drop the ASSERT and update the comment. Fixes #41526 --------- Signed-off-by: Ryan Northey --- source/common/quic/envoy_quic_client_session.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/common/quic/envoy_quic_client_session.h b/source/common/quic/envoy_quic_client_session.h index cc40b03567315..ac16c73a7cda3 100644 --- a/source/common/quic/envoy_quic_client_session.h +++ b/source/common/quic/envoy_quic_client_session.h @@ -123,9 +123,10 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override; std::unique_ptr CreateQuicCryptoStream() override; bool ShouldCreateOutgoingBidirectionalStream() override { - ASSERT(quic::QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream()); - // Prefer creating an "invalid" stream outside of current stream bounds to - // crashing when dereferencing a nullptr in QuicHttpClientConnectionImpl::newStream + // quic::QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream() + // might return false, but we want to create the stream anyway + // because otherwise we crash dereferencing a nullptr, so we + // don't even ask it, and just return true. return true; } // QuicFilterManagerConnectionImpl From 73f90132ecbad2b2d24bee8e3555c43ed8eb3310 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Sat, 18 Apr 2026 02:13:00 +0200 Subject: [PATCH 069/750] dynamic_modules: lock the owner before touching its dispatcher in schedulers (#44521) ## Description Each of the eight scheduler classes under `source/extensions/**/dynamic_modules/` held a raw `Event::Dispatcher&` captured at construction and used it unconditionally from `commit`: ``` void commit(uint64_t event_id) { dispatcher_.post([owner = owner_weak_ptr_, event_id]() { if (auto s = owner.lock()) { s->onScheduled(event_id); } }); } ``` `commit` is invoked from background threads (for example, a tokio worker inside the shared `common/bootstrap` async-init runtime). The `weak_ptr.lock()` guard inside the lambda only protects the lambda body. The enclosing `dispatcher_.post` runs first and dereferences the captured reference even when the owning config, cluster, or filter has already been destroyed. Under `envoy --mode validate` the server tears the bootstrap context down synchronously after config parse, so a tokio task committing the init-success event races the main thread dispatcher's destruction and faults at a small offset inside a freed object. The dispatcher lifetime is bounded by the owner that references it in every case we care about. The fix is to take the weak pointer first, and access the dispatcher through the locked owner: ``` void commit(uint64_t event_id) { auto owner = owner_.lock(); if (!owner) { return; } owner->dispatcher().post([o = owner_, event_id]() { if (auto s = o.lock()) { s->onScheduled(event_id); } }); } ``` Holding the shared pointer for the duration of `post` keeps the dispatcher reference valid while the post queue is mutated. The same pattern is applied to every scheduler: bootstrap extension config, cluster, HTTP filter, HTTP filter config, network filter, network filter config, listener filter, and listener filter config. Filter schedulers (HTTP / network / listener) obtain the worker dispatcher through the filter's callbacks at commit time instead of at construction. The `scheduler_new` ABI callbacks no longer return nullptr when callbacks are not yet set, since a commit after teardown is now a safe no-op. --- **Commit Message:** dynamic_modules: lock the owner before touching its dispatcher in schedulers **Additional Description:** Removes the raw `Event::Dispatcher&` members from all dynamic modules scheduler classes and routes the dispatcher access through the owner locked by its `weak_ptr`. **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** N/A **Release Notes:** N/A Signed-off-by: Rohit Agrawal --- .../bootstrap/dynamic_modules/abi_impl.cc | 3 +- .../dynamic_modules/extension_config.h | 19 +-- .../clusters/dynamic_modules/cluster.h | 19 +-- .../filters/http/dynamic_modules/abi_impl.cc | 6 +- .../filters/http/dynamic_modules/filter.h | 20 +-- .../http/dynamic_modules/filter_config.h | 18 ++- .../listener/dynamic_modules/abi_impl.cc | 9 +- .../filters/listener/dynamic_modules/filter.h | 23 +-- .../listener/dynamic_modules/filter_config.h | 18 ++- .../network/dynamic_modules/abi_impl.cc | 9 +- .../filters/network/dynamic_modules/filter.h | 23 +-- .../network/dynamic_modules/filter_config.h | 18 ++- test/coverage.yaml | 2 +- .../clusters/dynamic_modules/cluster_test.cc | 20 +++ .../bootstrap/abi_impl_test.cc | 26 ++++ test/extensions/dynamic_modules/http/BUILD | 1 + .../dynamic_modules/http/abi_impl_test.cc | 133 ++++++++++++++++++ .../dynamic_modules/listener/abi_impl_test.cc | 103 ++++++++++++++ 18 files changed, 388 insertions(+), 82 deletions(-) diff --git a/source/extensions/bootstrap/dynamic_modules/abi_impl.cc b/source/extensions/bootstrap/dynamic_modules/abi_impl.cc index b52d380ec2623..c0eec93f7bfc7 100644 --- a/source/extensions/bootstrap/dynamic_modules/abi_impl.cc +++ b/source/extensions/bootstrap/dynamic_modules/abi_impl.cc @@ -22,8 +22,7 @@ envoy_dynamic_module_type_bootstrap_extension_config_scheduler_module_ptr envoy_dynamic_module_callback_bootstrap_extension_config_scheduler_new( envoy_dynamic_module_type_bootstrap_extension_config_envoy_ptr extension_config_envoy_ptr) { auto* config = static_cast(extension_config_envoy_ptr); - return new DynamicModuleBootstrapExtensionConfigScheduler(config->weak_from_this(), - config->main_thread_dispatcher_); + return new DynamicModuleBootstrapExtensionConfigScheduler(config->weak_from_this()); } void envoy_dynamic_module_callback_bootstrap_extension_config_scheduler_delete( diff --git a/source/extensions/bootstrap/dynamic_modules/extension_config.h b/source/extensions/bootstrap/dynamic_modules/extension_config.h index 49d900857a7bb..25a6389f0ddda 100644 --- a/source/extensions/bootstrap/dynamic_modules/extension_config.h +++ b/source/extensions/bootstrap/dynamic_modules/extension_config.h @@ -476,14 +476,19 @@ using DynamicModuleBootstrapExtensionConfigSharedPtr = */ class DynamicModuleBootstrapExtensionConfigScheduler { public: - DynamicModuleBootstrapExtensionConfigScheduler( - std::weak_ptr config, Event::Dispatcher& dispatcher) - : config_(std::move(config)), dispatcher_(dispatcher) {} + explicit DynamicModuleBootstrapExtensionConfigScheduler( + std::weak_ptr config) + : config_(std::move(config)) {} void commit(uint64_t event_id) { - dispatcher_.post([config = config_, event_id]() { - if (std::shared_ptr config_shared = config.lock()) { - config_shared->onScheduled(event_id); + // Lock the config so its dispatcher member stays valid across `post`. + auto config_shared = config_.lock(); + if (!config_shared) { + return; + } + config_shared->main_thread_dispatcher_.post([config = config_, event_id]() { + if (std::shared_ptr cs = config.lock()) { + cs->onScheduled(event_id); } }); } @@ -492,8 +497,6 @@ class DynamicModuleBootstrapExtensionConfigScheduler { // The config that this scheduler is associated with. Using a weak pointer to avoid unnecessarily // extending the lifetime of the config. std::weak_ptr config_; - // The dispatcher is used to post the event to the main thread. - Event::Dispatcher& dispatcher_; }; /** diff --git a/source/extensions/clusters/dynamic_modules/cluster.h b/source/extensions/clusters/dynamic_modules/cluster.h index 160c2500fb2f1..01c6e502fbcec 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.h +++ b/source/extensions/clusters/dynamic_modules/cluster.h @@ -433,25 +433,28 @@ class DynamicModuleClusterScheduler { * Creates a new scheduler for the given cluster. */ static DynamicModuleClusterScheduler* create(DynamicModuleCluster* cluster) { - return new DynamicModuleClusterScheduler(cluster->weak_from_this(), cluster->dispatcher_); + return new DynamicModuleClusterScheduler(cluster->weak_from_this()); } void commit(uint64_t event_id) { - dispatcher_.post([cluster = cluster_, event_id]() { - if (std::shared_ptr cluster_shared = cluster.lock()) { - cluster_shared->onScheduled(event_id); + // Lock the cluster so its dispatcher member stays valid across `post`. + auto cluster_shared = cluster_.lock(); + if (!cluster_shared) { + return; + } + cluster_shared->dispatcher_.post([cluster = cluster_, event_id]() { + if (std::shared_ptr cs = cluster.lock()) { + cs->onScheduled(event_id); } }); } private: - DynamicModuleClusterScheduler(std::weak_ptr cluster, - Event::Dispatcher& dispatcher) - : cluster_(std::move(cluster)), dispatcher_(dispatcher) {} + explicit DynamicModuleClusterScheduler(std::weak_ptr cluster) + : cluster_(std::move(cluster)) {} // Using a weak pointer to avoid unnecessarily extending the lifetime of the cluster. std::weak_ptr cluster_; - Event::Dispatcher& dispatcher_; }; /** diff --git a/source/extensions/filters/http/dynamic_modules/abi_impl.cc b/source/extensions/filters/http/dynamic_modules/abi_impl.cc index c67aa1ed0698a..a326689c3932c 100644 --- a/source/extensions/filters/http/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/http/dynamic_modules/abi_impl.cc @@ -1945,8 +1945,7 @@ envoy_dynamic_module_type_http_filter_scheduler_module_ptr envoy_dynamic_module_callback_http_filter_scheduler_new( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr) { auto filter = static_cast(filter_envoy_ptr); - return new DynamicModuleHttpFilterScheduler(filter->weak_from_this(), - filter->decoder_callbacks_->dispatcher()); + return new DynamicModuleHttpFilterScheduler(filter->weak_from_this()); } void envoy_dynamic_module_callback_http_filter_scheduler_delete( @@ -1966,8 +1965,7 @@ envoy_dynamic_module_type_http_filter_config_scheduler_module_ptr envoy_dynamic_module_callback_http_filter_config_scheduler_new( envoy_dynamic_module_type_http_filter_config_envoy_ptr filter_config_envoy_ptr) { auto filter_config = static_cast(filter_config_envoy_ptr); - return new DynamicModuleHttpFilterConfigScheduler(filter_config->weak_from_this(), - filter_config->main_thread_dispatcher_); + return new DynamicModuleHttpFilterConfigScheduler(filter_config->weak_from_this()); } void envoy_dynamic_module_callback_http_filter_config_scheduler_delete( diff --git a/source/extensions/filters/http/dynamic_modules/filter.h b/source/extensions/filters/http/dynamic_modules/filter.h index e713a0970f87b..154f5ca60010e 100644 --- a/source/extensions/filters/http/dynamic_modules/filter.h +++ b/source/extensions/filters/http/dynamic_modules/filter.h @@ -389,14 +389,20 @@ using DynamicModuleHttpFilterWeakPtr = std::weak_ptr; */ class DynamicModuleHttpFilterScheduler { public: - DynamicModuleHttpFilterScheduler(DynamicModuleHttpFilterWeakPtr filter, - Event::Dispatcher& dispatcher) - : filter_(std::move(filter)), dispatcher_(dispatcher) {} + explicit DynamicModuleHttpFilterScheduler(DynamicModuleHttpFilterWeakPtr filter) + : filter_(std::move(filter)) {} void commit(uint64_t event_id) { - dispatcher_.post([filter = filter_, event_id]() { - if (DynamicModuleHttpFilterSharedPtr filter_shared = filter.lock()) { - filter_shared->onScheduled(event_id); + // Lock the filter so the dispatcher reference obtained via its callbacks stays valid across + // `post`. + auto filter_shared = filter_.lock(); + if (!filter_shared || filter_shared->isDestroyed() || + filter_shared->decoder_callbacks_ == nullptr) { + return; + } + filter_shared->decoder_callbacks_->dispatcher().post([filter = filter_, event_id]() { + if (DynamicModuleHttpFilterSharedPtr fs = filter.lock()) { + fs->onScheduled(event_id); } }); } @@ -405,8 +411,6 @@ class DynamicModuleHttpFilterScheduler { // The filter that this scheduler is associated with. Using a weak pointer to avoid unnecessarily // extending the lifetime of the filter. DynamicModuleHttpFilterWeakPtr filter_; - // The dispatcher is used to post the event to the worker thread that filter_ is assigned to. - Event::Dispatcher& dispatcher_; }; } // namespace HttpFilters diff --git a/source/extensions/filters/http/dynamic_modules/filter_config.h b/source/extensions/filters/http/dynamic_modules/filter_config.h index e1642b0391297..ee56193f20cce 100644 --- a/source/extensions/filters/http/dynamic_modules/filter_config.h +++ b/source/extensions/filters/http/dynamic_modules/filter_config.h @@ -439,21 +439,25 @@ class DynamicModuleHttpFilterConfig class DynamicModuleHttpFilterConfigScheduler { public: - DynamicModuleHttpFilterConfigScheduler(std::weak_ptr config, - Event::Dispatcher& dispatcher) - : config_(std::move(config)), dispatcher_(dispatcher) {} + explicit DynamicModuleHttpFilterConfigScheduler( + std::weak_ptr config) + : config_(std::move(config)) {} void commit(uint64_t event_id) { - dispatcher_.post([config = config_, event_id]() { - if (std::shared_ptr config_shared = config.lock()) { - config_shared->onScheduled(event_id); + // Lock the config so its dispatcher member stays valid across `post`. + auto config_shared = config_.lock(); + if (!config_shared) { + return; + } + config_shared->main_thread_dispatcher_.post([config = config_, event_id]() { + if (std::shared_ptr cs = config.lock()) { + cs->onScheduled(event_id); } }); } private: std::weak_ptr config_; - Event::Dispatcher& dispatcher_; }; class DynamicModuleHttpPerRouteFilterConfig : public Router::RouteSpecificFilterConfig { diff --git a/source/extensions/filters/listener/dynamic_modules/abi_impl.cc b/source/extensions/filters/listener/dynamic_modules/abi_impl.cc index ffdc0d8c434fa..5aff1e248ebea 100644 --- a/source/extensions/filters/listener/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/listener/dynamic_modules/abi_impl.cc @@ -1124,11 +1124,7 @@ envoy_dynamic_module_type_listener_filter_scheduler_module_ptr envoy_dynamic_module_callback_listener_filter_scheduler_new( envoy_dynamic_module_type_listener_filter_envoy_ptr filter_envoy_ptr) { auto* filter = static_cast(filter_envoy_ptr); - Event::Dispatcher* dispatcher = filter->dispatcher(); - if (dispatcher == nullptr) { - return nullptr; - } - return new DynamicModuleListenerFilterScheduler(filter->weak_from_this(), *dispatcher); + return new DynamicModuleListenerFilterScheduler(filter->weak_from_this()); } void envoy_dynamic_module_callback_listener_filter_scheduler_delete( @@ -1147,8 +1143,7 @@ envoy_dynamic_module_type_listener_filter_config_scheduler_module_ptr envoy_dynamic_module_callback_listener_filter_config_scheduler_new( envoy_dynamic_module_type_listener_filter_config_envoy_ptr filter_config_envoy_ptr) { auto* filter_config = static_cast(filter_config_envoy_ptr); - return new DynamicModuleListenerFilterConfigScheduler(filter_config->weak_from_this(), - filter_config->main_thread_dispatcher_); + return new DynamicModuleListenerFilterConfigScheduler(filter_config->weak_from_this()); } void envoy_dynamic_module_callback_listener_filter_config_scheduler_delete( diff --git a/source/extensions/filters/listener/dynamic_modules/filter.h b/source/extensions/filters/listener/dynamic_modules/filter.h index fb57ddbe7add3..68d0cead21ad9 100644 --- a/source/extensions/filters/listener/dynamic_modules/filter.h +++ b/source/extensions/filters/listener/dynamic_modules/filter.h @@ -152,14 +152,23 @@ class DynamicModuleListenerFilter */ class DynamicModuleListenerFilterScheduler { public: - DynamicModuleListenerFilterScheduler(DynamicModuleListenerFilterWeakPtr filter, - Event::Dispatcher& dispatcher) - : filter_(std::move(filter)), dispatcher_(dispatcher) {} + explicit DynamicModuleListenerFilterScheduler(DynamicModuleListenerFilterWeakPtr filter) + : filter_(std::move(filter)) {} void commit(uint64_t event_id) { - dispatcher_.post([filter = filter_, event_id]() { - if (DynamicModuleListenerFilterSharedPtr filter_shared = filter.lock()) { - filter_shared->onScheduled(event_id); + // Lock the filter so the dispatcher reference obtained via its callbacks stays valid across + // `post`. + auto filter_shared = filter_.lock(); + if (!filter_shared) { + return; + } + Event::Dispatcher* dispatcher = filter_shared->dispatcher(); + if (dispatcher == nullptr) { + return; + } + dispatcher->post([filter = filter_, event_id]() { + if (DynamicModuleListenerFilterSharedPtr fs = filter.lock()) { + fs->onScheduled(event_id); } }); } @@ -168,8 +177,6 @@ class DynamicModuleListenerFilterScheduler { // The filter that this scheduler is associated with. Using a weak pointer to avoid unnecessarily // extending the lifetime of the filter. DynamicModuleListenerFilterWeakPtr filter_; - // The dispatcher is used to post the event to the worker thread that filter_ is assigned to. - Event::Dispatcher& dispatcher_; }; } // namespace ListenerFilters diff --git a/source/extensions/filters/listener/dynamic_modules/filter_config.h b/source/extensions/filters/listener/dynamic_modules/filter_config.h index 7e6987f2f72c1..2e19edfea1d4a 100644 --- a/source/extensions/filters/listener/dynamic_modules/filter_config.h +++ b/source/extensions/filters/listener/dynamic_modules/filter_config.h @@ -216,21 +216,25 @@ class DynamicModuleListenerFilterConfig */ class DynamicModuleListenerFilterConfigScheduler { public: - DynamicModuleListenerFilterConfigScheduler( - std::weak_ptr config, Event::Dispatcher& dispatcher) - : config_(std::move(config)), dispatcher_(dispatcher) {} + explicit DynamicModuleListenerFilterConfigScheduler( + std::weak_ptr config) + : config_(std::move(config)) {} void commit(uint64_t event_id) { - dispatcher_.post([config = config_, event_id]() { - if (std::shared_ptr config_shared = config.lock()) { - config_shared->onScheduled(event_id); + // Lock the config so its dispatcher member stays valid across `post`. + auto config_shared = config_.lock(); + if (!config_shared) { + return; + } + config_shared->main_thread_dispatcher_.post([config = config_, event_id]() { + if (std::shared_ptr cs = config.lock()) { + cs->onScheduled(event_id); } }); } private: std::weak_ptr config_; - Event::Dispatcher& dispatcher_; }; /** diff --git a/source/extensions/filters/network/dynamic_modules/abi_impl.cc b/source/extensions/filters/network/dynamic_modules/abi_impl.cc index eea9c114322ab..469a74eb6a23f 100644 --- a/source/extensions/filters/network/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/network/dynamic_modules/abi_impl.cc @@ -1173,11 +1173,7 @@ envoy_dynamic_module_type_network_filter_scheduler_module_ptr envoy_dynamic_module_callback_network_filter_scheduler_new( envoy_dynamic_module_type_network_filter_envoy_ptr filter_envoy_ptr) { auto* filter = static_cast(filter_envoy_ptr); - Event::Dispatcher* dispatcher = filter->dispatcher(); - if (dispatcher == nullptr) { - return nullptr; - } - return new DynamicModuleNetworkFilterScheduler(filter->weak_from_this(), *dispatcher); + return new DynamicModuleNetworkFilterScheduler(filter->weak_from_this()); } void envoy_dynamic_module_callback_network_filter_scheduler_delete( @@ -1196,8 +1192,7 @@ envoy_dynamic_module_type_network_filter_config_scheduler_module_ptr envoy_dynamic_module_callback_network_filter_config_scheduler_new( envoy_dynamic_module_type_network_filter_config_envoy_ptr filter_config_envoy_ptr) { auto* filter_config = static_cast(filter_config_envoy_ptr); - return new DynamicModuleNetworkFilterConfigScheduler(filter_config->weak_from_this(), - filter_config->main_thread_dispatcher_); + return new DynamicModuleNetworkFilterConfigScheduler(filter_config->weak_from_this()); } void envoy_dynamic_module_callback_network_filter_config_scheduler_delete( diff --git a/source/extensions/filters/network/dynamic_modules/filter.h b/source/extensions/filters/network/dynamic_modules/filter.h index add6c57d9b146..c4251ab9125c2 100644 --- a/source/extensions/filters/network/dynamic_modules/filter.h +++ b/source/extensions/filters/network/dynamic_modules/filter.h @@ -241,14 +241,23 @@ class DynamicModuleNetworkFilter : public Network::Filter, */ class DynamicModuleNetworkFilterScheduler { public: - DynamicModuleNetworkFilterScheduler(DynamicModuleNetworkFilterWeakPtr filter, - Event::Dispatcher& dispatcher) - : filter_(std::move(filter)), dispatcher_(dispatcher) {} + explicit DynamicModuleNetworkFilterScheduler(DynamicModuleNetworkFilterWeakPtr filter) + : filter_(std::move(filter)) {} void commit(uint64_t event_id) { - dispatcher_.post([filter = filter_, event_id]() { - if (DynamicModuleNetworkFilterSharedPtr filter_shared = filter.lock()) { - filter_shared->onScheduled(event_id); + // Lock the filter so the dispatcher reference obtained via its callbacks stays valid across + // `post`. + auto filter_shared = filter_.lock(); + if (!filter_shared) { + return; + } + Event::Dispatcher* dispatcher = filter_shared->dispatcher(); + if (dispatcher == nullptr) { + return; + } + dispatcher->post([filter = filter_, event_id]() { + if (DynamicModuleNetworkFilterSharedPtr fs = filter.lock()) { + fs->onScheduled(event_id); } }); } @@ -257,8 +266,6 @@ class DynamicModuleNetworkFilterScheduler { // The filter that this scheduler is associated with. Using a weak pointer to avoid unnecessarily // extending the lifetime of the filter. DynamicModuleNetworkFilterWeakPtr filter_; - // The dispatcher is used to post the event to the worker thread that filter_ is assigned to. - Event::Dispatcher& dispatcher_; }; } // namespace NetworkFilters diff --git a/source/extensions/filters/network/dynamic_modules/filter_config.h b/source/extensions/filters/network/dynamic_modules/filter_config.h index ac58cfda59200..ce2154c524a3b 100644 --- a/source/extensions/filters/network/dynamic_modules/filter_config.h +++ b/source/extensions/filters/network/dynamic_modules/filter_config.h @@ -221,21 +221,25 @@ class DynamicModuleNetworkFilterConfig */ class DynamicModuleNetworkFilterConfigScheduler { public: - DynamicModuleNetworkFilterConfigScheduler(std::weak_ptr config, - Event::Dispatcher& dispatcher) - : config_(std::move(config)), dispatcher_(dispatcher) {} + explicit DynamicModuleNetworkFilterConfigScheduler( + std::weak_ptr config) + : config_(std::move(config)) {} void commit(uint64_t event_id) { - dispatcher_.post([config = config_, event_id]() { - if (std::shared_ptr config_shared = config.lock()) { - config_shared->onScheduled(event_id); + // Lock the config so its dispatcher member stays valid across `post`. + auto config_shared = config_.lock(); + if (!config_shared) { + return; + } + config_shared->main_thread_dispatcher_.post([config = config_, event_id]() { + if (std::shared_ptr cs = config.lock()) { + cs->onScheduled(event_id); } }); } private: std::weak_ptr config_; - Event::Dispatcher& dispatcher_; }; /** diff --git a/test/coverage.yaml b/test/coverage.yaml index 595f85e967073..86382fcb9e89d 100644 --- a/test/coverage.yaml +++ b/test/coverage.yaml @@ -52,7 +52,7 @@ directories: source/extensions/filters/http/mcp_router: 82.3 source/extensions/filters/http/oauth2: 97.6 source/extensions/filters/listener: 96.5 - source/extensions/filters/listener/dynamic_modules: 96.9 + source/extensions/filters/listener/dynamic_modules: 96.7 source/extensions/filters/listener/original_src: 92.1 source/extensions/filters/listener/tls_inspector: 94.5 source/extensions/filters/network/dubbo_proxy: 96.2 diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index 2e7267bb87404..c6a8e49404b37 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -1140,6 +1140,26 @@ TEST_F(DynamicModuleClusterTest, OnScheduledAfterClusterDestroyed) { captured_cb(); } +// `commit` must not touch the dispatcher once the owning cluster has been destroyed. +TEST_F(DynamicModuleClusterTest, CommitAfterClusterDestroyedDoesNotTouchDispatcher) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()) << result.status().message(); + auto cluster = std::dynamic_pointer_cast(result->first); + ASSERT_NE(nullptr, cluster); + + auto* scheduler_ptr = envoy_dynamic_module_callback_cluster_scheduler_new(cluster.get()); + EXPECT_NE(scheduler_ptr, nullptr); + + cluster.reset(); + result = absl::InternalError("cleanup"); + + EXPECT_CALL(server_context_.dispatcher_, post(_)).Times(0); + + envoy_dynamic_module_callback_cluster_scheduler_commit(scheduler_ptr, 42); + + envoy_dynamic_module_callback_cluster_scheduler_delete(scheduler_ptr); +} + // Test calling onScheduled directly. TEST_F(DynamicModuleClusterTest, OnScheduledDirect) { auto result = createCluster(makeYamlConfig("cluster_no_op")); diff --git a/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc b/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc index 67f93936809f3..61e5521bcf6e4 100644 --- a/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc @@ -156,6 +156,32 @@ TEST_F(BootstrapAbiImplTest, OnScheduledAfterConfigDestroyed) { captured_cb(); } +// `commit` must not touch the dispatcher once the owning config has been destroyed. Exercises +// the validate-mode teardown race where a background thread may call `commit` after the main +// thread dispatcher has already been released. +TEST_F(BootstrapAbiImplTest, CommitAfterConfigDestroyedDoesNotTouchDispatcher) { + auto dynamic_module = + Extensions::DynamicModules::newDynamicModule(testDataDir() + "/libbootstrap_no_op.so", false); + ASSERT_TRUE(dynamic_module.ok()) << dynamic_module.status(); + + auto config = newDynamicModuleBootstrapExtensionConfig("test", "config", DefaultMetricsNamespace, + std::move(dynamic_module.value()), + dispatcher_, context_, context_.store_); + ASSERT_TRUE(config.ok()) << config.status(); + + auto* scheduler_ptr = envoy_dynamic_module_callback_bootstrap_extension_config_scheduler_new( + config.value()->thisAsVoidPtr()); + EXPECT_NE(scheduler_ptr, nullptr); + + config.value().reset(); + + EXPECT_CALL(dispatcher_, post(testing::_)).Times(0); + + envoy_dynamic_module_callback_bootstrap_extension_config_scheduler_commit(scheduler_ptr, 42); + + envoy_dynamic_module_callback_bootstrap_extension_config_scheduler_delete(scheduler_ptr); +} + // Test calling onScheduled directly. TEST_F(BootstrapAbiImplTest, OnScheduledDirect) { auto dynamic_module = diff --git a/test/extensions/dynamic_modules/http/BUILD b/test/extensions/dynamic_modules/http/BUILD index 5e0d9aeafc5c1..090025aa762d6 100644 --- a/test/extensions/dynamic_modules/http/BUILD +++ b/test/extensions/dynamic_modules/http/BUILD @@ -118,6 +118,7 @@ envoy_cc_test( "//source/extensions/filters/http/dynamic_modules:filter_lib", "//test/common/stats:stat_test_utility_lib", "//test/extensions/dynamic_modules:util", + "//test/mocks/event:event_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:server_factory_context_mocks", diff --git a/test/extensions/dynamic_modules/http/abi_impl_test.cc b/test/extensions/dynamic_modules/http/abi_impl_test.cc index 68794b3dfd311..675600a2ac000 100644 --- a/test/extensions/dynamic_modules/http/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/http/abi_impl_test.cc @@ -13,6 +13,7 @@ #include "test/common/stats/stat_test_utility.h" #include "test/extensions/dynamic_modules/util.h" +#include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/server_factory_context.h" @@ -3398,6 +3399,138 @@ TEST(ABIImpl, ReceivedBufferedResponseBody) { EXPECT_TRUE(envoy_dynamic_module_callback_http_received_buffered_response_body(&filter)); } +// ============================================================================= +// Tests for scheduler callbacks. +// ============================================================================= + +class DynamicModuleHttpFilterSchedulerTest : public testing::Test { +public: + void SetUp() override { + ON_CALL(context_, mainThreadDispatcher()) + .WillByDefault(testing::ReturnRef(main_thread_dispatcher_)); + ON_CALL(decoder_callbacks_, dispatcher()) + .WillByDefault(testing::ReturnRef(worker_thread_dispatcher_)); + + auto dynamic_module = Envoy::Extensions::DynamicModules::newDynamicModule( + testSharedObjectPath("no_op", "c"), false); + ASSERT_TRUE(dynamic_module.ok()) << dynamic_module.status().message(); + + auto filter_config_or_status = newDynamicModuleHttpFilterConfig( + "test_filter", "", DefaultMetricsNamespace, false, std::move(dynamic_module.value()), + *stats_store_.rootScope(), context_); + ASSERT_TRUE(filter_config_or_status.ok()) << filter_config_or_status.status().message(); + filter_config_ = filter_config_or_status.value(); + + filter_ = std::make_shared(filter_config_, symbol_table_, 0); + filter_->initializeInModuleFilter(); + } + + void TearDown() override { filter_.reset(); } + + void* filterPtr() { return static_cast(filter_.get()); } + void* configPtr() { return static_cast(filter_config_.get()); } + + Stats::SymbolTableImpl symbol_table_; + Stats::IsolatedStoreImpl stats_store_; + NiceMock context_; + NiceMock main_thread_dispatcher_; + NiceMock worker_thread_dispatcher_{"worker_0"}; + NiceMock decoder_callbacks_; + DynamicModuleHttpFilterConfigSharedPtr filter_config_; + std::shared_ptr filter_; +}; + +// Covers the happy path: `commit` posts to the dispatcher resolved via decoder callbacks. +TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitPostsToWorkerDispatcher) { + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + + auto* scheduler = envoy_dynamic_module_callback_http_filter_scheduler_new(filterPtr()); + ASSERT_NE(nullptr, scheduler); + + Event::PostCb captured_cb; + EXPECT_CALL(worker_thread_dispatcher_, post(testing::_)) + .WillOnce(testing::Invoke([&](Event::PostCb cb) { captured_cb = std::move(cb); })); + + envoy_dynamic_module_callback_http_filter_scheduler_commit(scheduler, 123); + ASSERT_TRUE(captured_cb); + captured_cb(); + + envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); +} + +// Covers the `!filter_shared` early return. +TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitAfterFilterDestroyedIsNoOp) { + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + + auto* scheduler = envoy_dynamic_module_callback_http_filter_scheduler_new(filterPtr()); + ASSERT_NE(nullptr, scheduler); + + filter_.reset(); + + EXPECT_CALL(worker_thread_dispatcher_, post(testing::_)).Times(0); + envoy_dynamic_module_callback_http_filter_scheduler_commit(scheduler, 123); + + envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); +} + +// Covers the `filter_shared->isDestroyed()` early return. +TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitAfterOnDestroyIsNoOp) { + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + + auto* scheduler = envoy_dynamic_module_callback_http_filter_scheduler_new(filterPtr()); + ASSERT_NE(nullptr, scheduler); + + filter_->onDestroy(); + + EXPECT_CALL(worker_thread_dispatcher_, post(testing::_)).Times(0); + envoy_dynamic_module_callback_http_filter_scheduler_commit(scheduler, 123); + + envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); +} + +// Covers the `decoder_callbacks_ == nullptr` early return. +TEST_F(DynamicModuleHttpFilterSchedulerTest, + HttpFilterSchedulerCommitWithoutDecoderCallbacksIsNoOp) { + auto* scheduler = envoy_dynamic_module_callback_http_filter_scheduler_new(filterPtr()); + ASSERT_NE(nullptr, scheduler); + + EXPECT_CALL(worker_thread_dispatcher_, post(testing::_)).Times(0); + envoy_dynamic_module_callback_http_filter_scheduler_commit(scheduler, 123); + + envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); +} + +// Covers the happy path for the config scheduler: `commit` posts to the main thread dispatcher. +TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterConfigSchedulerCommitPostsToMainDispatcher) { + auto* scheduler = envoy_dynamic_module_callback_http_filter_config_scheduler_new(configPtr()); + ASSERT_NE(nullptr, scheduler); + + Event::PostCb captured_cb; + EXPECT_CALL(main_thread_dispatcher_, post(testing::_)) + .WillOnce(testing::Invoke([&](Event::PostCb cb) { captured_cb = std::move(cb); })); + + envoy_dynamic_module_callback_http_filter_config_scheduler_commit(scheduler, 456); + ASSERT_TRUE(captured_cb); + captured_cb(); + + envoy_dynamic_module_callback_http_filter_config_scheduler_delete(scheduler); +} + +// Covers the `!config_shared` early return of the config scheduler. +TEST_F(DynamicModuleHttpFilterSchedulerTest, + HttpFilterConfigSchedulerCommitAfterConfigDestroyedIsNoOp) { + auto* scheduler = envoy_dynamic_module_callback_http_filter_config_scheduler_new(configPtr()); + ASSERT_NE(nullptr, scheduler); + + filter_.reset(); + filter_config_.reset(); + + EXPECT_CALL(main_thread_dispatcher_, post(testing::_)).Times(0); + envoy_dynamic_module_callback_http_filter_config_scheduler_commit(scheduler, 456); + + envoy_dynamic_module_callback_http_filter_config_scheduler_delete(scheduler); +} + } // namespace HttpFilters } // namespace DynamicModules } // namespace Extensions diff --git a/test/extensions/dynamic_modules/listener/abi_impl_test.cc b/test/extensions/dynamic_modules/listener/abi_impl_test.cc index 34c01b4f53c3e..bdf4ff843c944 100644 --- a/test/extensions/dynamic_modules/listener/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/listener/abi_impl_test.cc @@ -2105,6 +2105,20 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, envoy_dynamic_module_callback_listener_filter_config_scheduler_delete(scheduler); } +// Covers the `dispatcher == nullptr` early return of the listener filter scheduler. +TEST_F(DynamicModuleListenerFilterAbiCallbackTest, + ListenerFilterSchedulerCommitWithoutCallbacksIsNoOp) { + auto* scheduler = envoy_dynamic_module_callback_listener_filter_scheduler_new(filterPtr()); + ASSERT_NE(nullptr, scheduler); + + filter_->setCallbacksForTest(nullptr); + + EXPECT_CALL(worker_thread_dispatcher_, post(_)).Times(0); + envoy_dynamic_module_callback_listener_filter_scheduler_commit(scheduler, 123); + + envoy_dynamic_module_callback_listener_filter_scheduler_delete(scheduler); +} + // ============================================================================= // Tests for socket option callbacks. // ============================================================================= @@ -2625,6 +2639,95 @@ TEST_F(DynamicModuleListenerFilterHttpCalloutTest, FilterDestructionCancelsPendi filter_.reset(); } +// Covers the `on_listener_filter_http_callout_done_ == nullptr` branch of `onSuccess`. +TEST_F(DynamicModuleListenerFilterHttpCalloutTest, HttpCalloutOnSuccessWithoutCalloutDoneHook) { + NiceMock cluster; + NiceMock async_client; + Http::MockAsyncClientRequest request(&async_client); + const Http::AsyncClient::Callbacks* captured_callback = nullptr; + + EXPECT_CALL(cluster_manager_, getThreadLocalCluster("test_cluster")) + .WillOnce(testing::Return(&cluster)); + EXPECT_CALL(cluster, httpAsyncClient()).WillOnce(testing::ReturnRef(async_client)); + EXPECT_CALL(async_client, send_(testing::_, testing::_, testing::_)) + .WillOnce(testing::DoAll(testing::WithArg<1>([&](const Http::AsyncClient::Callbacks& cb) { + captured_callback = &cb; + }), + testing::Return(&request))); + + uint64_t callout_id = 0; + std::vector headers = { + {.key_ptr = ":method", .key_length = 7, .value_ptr = "GET", .value_length = 3}, + {.key_ptr = ":path", .key_length = 5, .value_ptr = "/test", .value_length = 5}, + {.key_ptr = "host", .key_length = 4, .value_ptr = "example.com", .value_length = 11}, + }; + + auto result = envoy_dynamic_module_callback_listener_filter_http_callout( + filterPtr(), &callout_id, {"test_cluster", 12}, headers.data(), headers.size(), {nullptr, 0}, + 5000); + ASSERT_EQ(envoy_dynamic_module_type_http_callout_init_result_Success, result); + ASSERT_NE(nullptr, captured_callback); + + filter_config_->on_listener_filter_http_callout_done_ = nullptr; + + Http::ResponseMessagePtr response = + std::make_unique(Http::ResponseHeaderMapImpl::create()); + response->headers().setStatus(200); + const_cast(captured_callback) + ->onSuccess(request, std::move(response)); +} + +// Covers the `on_listener_filter_http_callout_done_ == nullptr` branch of `onFailure` along with +// the `ExceedResponseBufferLimit` path of the reason switch. +TEST_F(DynamicModuleListenerFilterHttpCalloutTest, HttpCalloutOnFailureWithoutCalloutDoneHook) { + NiceMock cluster; + NiceMock async_client; + Http::MockAsyncClientRequest request(&async_client); + const Http::AsyncClient::Callbacks* captured_callback = nullptr; + + EXPECT_CALL(cluster_manager_, getThreadLocalCluster("test_cluster")) + .WillOnce(testing::Return(&cluster)); + EXPECT_CALL(cluster, httpAsyncClient()).WillOnce(testing::ReturnRef(async_client)); + EXPECT_CALL(async_client, send_(testing::_, testing::_, testing::_)) + .WillOnce(testing::DoAll(testing::WithArg<1>([&](const Http::AsyncClient::Callbacks& cb) { + captured_callback = &cb; + }), + testing::Return(&request))); + + uint64_t callout_id = 0; + std::vector headers = { + {.key_ptr = ":method", .key_length = 7, .value_ptr = "GET", .value_length = 3}, + {.key_ptr = ":path", .key_length = 5, .value_ptr = "/test", .value_length = 5}, + {.key_ptr = "host", .key_length = 4, .value_ptr = "example.com", .value_length = 11}, + }; + + auto result = envoy_dynamic_module_callback_listener_filter_http_callout( + filterPtr(), &callout_id, {"test_cluster", 12}, headers.data(), headers.size(), {nullptr, 0}, + 5000); + ASSERT_EQ(envoy_dynamic_module_type_http_callout_init_result_Success, result); + ASSERT_NE(nullptr, captured_callback); + + filter_config_->on_listener_filter_http_callout_done_ = nullptr; + + const_cast(captured_callback) + ->onFailure(request, Http::AsyncClient::FailureReason::ExceedResponseBufferLimit); +} + +// Covers `DynamicModuleListenerFilter::onScheduled` when `in_module_filter_` is null. +TEST_F(DynamicModuleListenerFilterAbiCallbackTest, + ListenerFilterOnScheduledWithoutInModuleFilterIsNoOp) { + auto filter = std::make_shared(filter_config_); + filter->onScheduled(42); +} + +// Covers the `on_listener_filter_config_scheduled_ == nullptr` branch of +// `DynamicModuleListenerFilterConfig::onScheduled`. +TEST_F(DynamicModuleListenerFilterAbiCallbackTest, + ListenerFilterConfigOnScheduledWithoutHookIsNoOp) { + filter_config_->on_listener_filter_config_scheduled_ = nullptr; + filter_config_->onScheduled(42); +} + } // namespace ListenerFilters } // namespace DynamicModules } // namespace Extensions From 0575c02c6aa8c32d30a8b153f30b3eca6fe702bd Mon Sep 17 00:00:00 2001 From: aakugan <164153358+aakugan@users.noreply.github.com> Date: Sat, 18 Apr 2026 20:53:15 +0530 Subject: [PATCH 070/750] reverse_tunnel: Add protos for contrib reverse tunnel reporting service (#44327) ## Commit Message Add protos for contrib reverse tunnel reporting service ## Additional Description: The reporting service allows Envoy to push reverse tunnel connection state to a remote server. Three proto files are introduced: 1. `event_reporter.proto`: Configuration for the EventReporter, which aggregates connection/disconnection events and fans them out to one or more pluggable clients. 2. `grpc_client.proto`: Configuration for the gRPC streaming client, which sends batched events to a cluster over a bidirectional stream using an ACK/NACK protocol similar to xDS. 3. `stream_reverse_tunnels.proto`: The gRPC service definition and request/response messages for the bidirectional reporting stream. Includes tunnel identity, nonce-based ACK/NACK, and server-driven send interval adjustment. All protos use v3alpha versioning under the contrib API path. Risk Level: Low Testing: Proto builds verified Docs Changes: N/A Release Notes: N/A Signed-off-by: aakugan --- api/BUILD | 2 + .../v3alpha/clients/grpc_client/BUILD | 13 +++ .../clients/grpc_client/grpc_client.proto | 50 ++++++++ .../grpc_client/stream_reverse_tunnels.proto | 108 ++++++++++++++++++ .../v3alpha/reporters/BUILD | 9 ++ .../v3alpha/reporters/event_reporter.proto | 32 ++++++ api/versioning/BUILD | 2 + docs/root/api-v3/config/contrib/contrib.rst | 1 + .../reverse_tunnel_reporter.rst | 11 ++ 9 files changed, 228 insertions(+) create mode 100644 api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/BUILD create mode 100644 api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.proto create mode 100644 api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/stream_reverse_tunnels.proto create mode 100644 api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/BUILD create mode 100644 api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.proto create mode 100644 docs/root/api-v3/config/contrib/reverse_tunnel_reporter/reverse_tunnel_reporter.rst diff --git a/api/BUILD b/api/BUILD index dc1eeafb03b36..1e362efbe52ca 100644 --- a/api/BUILD +++ b/api/BUILD @@ -104,6 +104,8 @@ proto_library( "//contrib/envoy/extensions/private_key_providers/kae/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/qat/v3alpha:pkg", "//contrib/envoy/extensions/regex_engines/hyperscan/v3alpha:pkg", + "//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client:pkg", + "//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters:pkg", "//contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha:pkg", "//contrib/envoy/extensions/stat_sinks/kafka/v3:pkg", "//contrib/envoy/extensions/tap_sinks/udp_sink/v3alpha:pkg", diff --git a/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/BUILD b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/BUILD new file mode 100644 index 0000000000000..6409469bbd62f --- /dev/null +++ b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/BUILD @@ -0,0 +1,13 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + has_services = True, + deps = [ + "//envoy/config/core/v3:pkg", + "@xds//udpa/annotations:pkg", + ], +) diff --git a/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.proto b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.proto new file mode 100644 index 0000000000000..b3c3236c03262 --- /dev/null +++ b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +package envoy.extensions.reverse_tunnel_reporters.v3alpha.clients.grpc_client; + +import "google/protobuf/duration.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.reverse_tunnel_reporters.v3alpha.clients.grpc_client"; +option java_outer_classname = "GrpcClientProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// Configuration for gRPC push-based connection event client. +// Actively pushes connection events to a cluster using grpc using some internal timing. +// [#next-free-field: 7] +message GrpcClientConfig { + // Stat prefix for this client's metrics. + string stat_prefix = 1; + + // Name of the cluster to send gRPC requests to. + // It must be present in the config otherwise the setup will throw error in the onServerInitialized. + string cluster = 2 [(validate.rules).string = {min_len: 1}]; + + // Default interval between sending batched connection events. + // Default is 5s. + google.protobuf.Duration default_send_interval = 3 [(validate.rules).duration = { + lte {seconds: 3600} + gte {nanos: 25000000} + }]; + + // Interval between connection retry attempts to the gRPC service. + // Connect timeouts are provided at the cluster level and will be handled by the http/2 client. + // How much time to wait after a failed connect before retrying. Default is 5s. + google.protobuf.Duration connect_retry_interval = 4 [(validate.rules).duration = { + lte {seconds: 3600} + gte {nanos: 25000000} + }]; + + // Maximum number of retry attempts for failed gRPC sends. + // Basically the cluster will have default_send_interval * max_retries time to respond. + // Default is 5. After this we will disconnect and try to connect again. + uint32 max_retries = 5; + + // Maximum events to buffer at any given time + // Default is 1,000,000. + uint32 max_buffer_count = 6; +} diff --git a/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/stream_reverse_tunnels.proto b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/stream_reverse_tunnels.proto new file mode 100644 index 0000000000000..9cc9ad323f3a9 --- /dev/null +++ b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/stream_reverse_tunnels.proto @@ -0,0 +1,108 @@ +syntax = "proto3"; + +package envoy.extensions.reverse_tunnel_reporters.v3alpha.clients.grpc_client; + +import "envoy/config/core/v3/base.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/rpc/status.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.reverse_tunnel_reporters.v3alpha.clients.grpc_client"; +option java_outer_classname = "StreamReverseTunnelsProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Reverse Tunnel Reporting Service] + +// ReverseTunnelReportingService allows Envoy instances to report reverse tunnel +// connection state changes to a management server for monitoring and coordination. +service ReverseTunnelReportingService { + // Bidirectional stream for reporting reverse tunnel connection state changes. + // The management server can control reporting intervals and acknowledge received reports. + rpc StreamReverseTunnels(stream StreamReverseTunnelsRequest) + returns (stream StreamReverseTunnelsResponse) { + } +} + +// Request message sent by Envoy to report reverse tunnel state changes. +// [#next-free-field: 6] +message StreamReverseTunnelsRequest { + // Node identifier for the reporting Envoy instance. + // This identifies which Envoy instance is sending the report. + config.core.v3.Node node = 1 [(validate.rules).message = {required: true}]; + + // List of reverse tunnels that were established since the last report. + // Each tunnel represents a new connection from a downstream Envoy. + repeated ReverseTunnel added_tunnels = 2; + + // List of tunnel names that were disconnected since the last report. + // Only the tunnel name is needed for removal notifications. + repeated string removed_tunnel_names = 3 + [(validate.rules).repeated = {items {string {min_len: 1}}}]; + + // Indicates whether this report contains all active tunnels (true) or + // only changes since the last report (false). Usually invoked only on server disconnects. + bool full_push = 4; + + // Unique nonce for this request to enable proper ACK/NACK handling. + // Must be non-negative and should increment for each request. + // This can also be modified to be used for checksum and tracking in the future. + int64 nonce = 5 [(validate.rules).int64 = {gte: 0}]; +} + +// Response message sent by the management server to control reporting behavior. +message StreamReverseTunnelsResponse { + // Node identifier acknowledging which Envoy instance this response is for. + // Should match the node from the corresponding request. + string node_id = 1; + + // Interval at which Envoy should send tunnel state reports. + // This is used to change the reporting_interval -> no need to repeat the same value. + google.protobuf.Duration report_interval = 2 [(validate.rules).duration = { + lte {seconds: 3600} + gte {nanos: 25000000} + }]; + + // Nonce from the request being acknowledged or rejected. + // Must match the nonce from the corresponding request. + int64 request_nonce = 3 [(validate.rules).int64 = {gte: 0}]; + + // Error details if the previous request failed processing. + // If populated, indicates the request was rejected (NACK). + // If empty, indicates successful processing (ACK). + // NACK will terminate the connection -> useful for logging rather than just some disconnect. + // So basically -> NACK then terminate. + google.rpc.Status error_detail = 4; +} + +// Represents a single reverse tunnel connection with its metadata. +message ReverseTunnel { + // Unique name to identify this tunnel connection. + // Must be unique within the reporting Envoy instance. + // This is also used for the reporting the disconnection with the associated tunnel initiator. + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // Identity information of the tunnel initiator (downstream Envoy). + // Contains ``node_id``, ``cluster_id``, and ``tenant_id`` for proper identification. + TunnelInitiatorIdentity identity = 2 [(validate.rules).message = {required: true}]; + + // Timestamp when this tunnel connection was created. + // Used for ordering events and debugging connection timing issues. + google.protobuf.Timestamp created_at = 3 [(validate.rules).timestamp = {required: true}]; +} + +message TunnelInitiatorIdentity { + // Required: Tenant identifier of the initiating Envoy instance. + string tenant_id = 1 [(validate.rules).string = {min_len: 1 max_len: 128}]; + + // Required: Cluster identifier of the initiating Envoy instance. + string cluster_id = 2 [(validate.rules).string = {min_len: 1 max_len: 128}]; + + // Required: Node identifier of the initiating Envoy instance. + string node_id = 3 [(validate.rules).string = {min_len: 1 max_len: 128}]; +} diff --git a/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/BUILD b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/BUILD new file mode 100644 index 0000000000000..5f552f08145ca --- /dev/null +++ b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@xds//udpa/annotations:pkg"], +) diff --git a/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.proto b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.proto new file mode 100644 index 0000000000000..1359e100dcaff --- /dev/null +++ b/api/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package envoy.extensions.reverse_tunnel_reporters.v3alpha.reporters; + +import "google/protobuf/any.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.reverse_tunnel_reporters.v3alpha.reporters"; +option java_outer_classname = "EventReporterProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +message ReverseConnectionReporterClient { + // Name to use to pick out the client should match the one reported by the factory. + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // Typed config for the client + google.protobuf.Any typed_config = 2 [(validate.rules).any = {required: true}]; +} + +// Configuration for the connection event reporter. +message EventReporterConfig { + // Stat prefix for this reporter's metrics. + // Metrics will be emitted as ``{stat_prefix}.events_pushed``, etc. + string stat_prefix = 1; + + // List of clients to report to. + repeated ReverseConnectionReporterClient clients = 2 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 48e88efea02f2..325d264642f2b 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -43,6 +43,8 @@ proto_library( "//contrib/envoy/extensions/private_key_providers/kae/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/qat/v3alpha:pkg", "//contrib/envoy/extensions/regex_engines/hyperscan/v3alpha:pkg", + "//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client:pkg", + "//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters:pkg", "//contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha:pkg", "//contrib/envoy/extensions/stat_sinks/kafka/v3:pkg", "//contrib/envoy/extensions/tap_sinks/udp_sink/v3alpha:pkg", diff --git a/docs/root/api-v3/config/contrib/contrib.rst b/docs/root/api-v3/config/contrib/contrib.rst index ac17d06076276..5ce1c24cdb09a 100644 --- a/docs/root/api-v3/config/contrib/contrib.rst +++ b/docs/root/api-v3/config/contrib/contrib.rst @@ -21,3 +21,4 @@ Contrib extensions tap_sinks/tap_sinks load_balancing_policies/peak_ewma/peak_ewma istio/istio + reverse_tunnel_reporter/reverse_tunnel_reporter diff --git a/docs/root/api-v3/config/contrib/reverse_tunnel_reporter/reverse_tunnel_reporter.rst b/docs/root/api-v3/config/contrib/reverse_tunnel_reporter/reverse_tunnel_reporter.rst new file mode 100644 index 0000000000000..6f174b730d66d --- /dev/null +++ b/docs/root/api-v3/config/contrib/reverse_tunnel_reporter/reverse_tunnel_reporter.rst @@ -0,0 +1,11 @@ +.. _extension_envoy.bootstrap.reverse_tunnel.reverse_tunnel_reporting_service: + +Reverse tunnel reporter +======================= + +.. toctree:: + :glob: + :maxdepth: 2 + + ../../../extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/* + ../../../extensions/reverse_tunnel_reporters/v3alpha/reporters/* From 5d0b81cb3ae67b0ef23c689a2c67799cbfbcde52 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Sat, 18 Apr 2026 17:48:20 +0200 Subject: [PATCH 071/750] dynamic_modules: harden cluster handle teardown and async host selection lifetime (#44524) ## Description Today, `DynamicModuleClusterHandle::~DynamicModuleClusterHandle` synchronously resets the cluster's lifecycle notifier handles before posting the final cluster destruction onto the main thread. When the last shared pointer to the handle is held by a per-worker load balancer, the destructor runs on a worker thread and those synchronous resets mutate main-thread-owned `ServerLifecycleNotifier` and drain manager state from off the main thread. Also, `envoy_dynamic_module_callback_cluster_lb_async_host_selection_complete` dereferences the raw `envoy_lb` pointer that the module stored at `on_cluster_lb_new` time: `lb->activeAsyncCancelled()`, `lb->activeAsyncDispatcher()` and `lb->handle()`. A cluster removed mid selection destroys the per-worker `DynamicModuleLoadBalancer` before the module's background task invokes the completion callback, so any of those three accesses can read freed memory. This PR fixes both the issues. --- **Commit Message:** dynamic_modules: harden cluster handle teardown and async host selection lifetime **Additional Description:** Moves cluster handle teardown onto the main dispatcher when running on a worker thread, and adds a process-wide registry of live `DynamicModuleLoadBalancer` instances so the async host selection completion ABI callback never dereferences a raw pointer to a destroyed instance. **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** N/A **Release Notes:** N/A Signed-off-by: Rohit Agrawal --- .../clusters/dynamic_modules/abi_impl.cc | 28 +++++--- .../clusters/dynamic_modules/cluster.cc | 62 ++++++++++++++-- .../clusters/dynamic_modules/cluster.h | 10 +++ .../extensions/clusters/dynamic_modules/BUILD | 1 + .../clusters/dynamic_modules/cluster_test.cc | 71 +++++++++++++++++++ 5 files changed, 160 insertions(+), 12 deletions(-) diff --git a/source/extensions/clusters/dynamic_modules/abi_impl.cc b/source/extensions/clusters/dynamic_modules/abi_impl.cc index 4cbe2b06b5d54..c574efa68b0c7 100644 --- a/source/extensions/clusters/dynamic_modules/abi_impl.cc +++ b/source/extensions/clusters/dynamic_modules/abi_impl.cc @@ -1078,7 +1078,9 @@ void envoy_dynamic_module_callback_cluster_lb_async_host_selection_complete( envoy_dynamic_module_type_cluster_lb_context_envoy_ptr context_envoy_ptr, envoy_dynamic_module_type_cluster_host_envoy_ptr host, envoy_dynamic_module_type_module_buffer details) { - auto* lb = getLb(lb_envoy_ptr); + using Envoy::Extensions::Clusters::DynamicModules::DynamicModuleClusterHandleSharedPtr; + using Envoy::Extensions::Clusters::DynamicModules::DynamicModuleLoadBalancer; + auto* lb_raw = static_cast(lb_envoy_ptr); // Copy the details string on the calling thread. The pointer is not valid after we return. std::string details_str; @@ -1086,14 +1088,24 @@ void envoy_dynamic_module_callback_cluster_lb_async_host_selection_complete( details_str.assign(details.ptr, details.length); } - auto cancelled = lb->activeAsyncCancelled(); - auto* dispatcher = lb->activeAsyncDispatcher(); + // The module may invoke this callback on any thread and race the load balancer destructor. + // Validate the raw pointer against the live registry and snapshot the state we need under the + // registry lock. + std::shared_ptr> cancelled; + Envoy::Event::Dispatcher* dispatcher = nullptr; + DynamicModuleClusterHandleSharedPtr handle; + const bool found = DynamicModuleLoadBalancer::withActiveInstance( + lb_raw, [&](const DynamicModuleLoadBalancer& lb) { + cancelled = lb.activeAsyncCancelled(); + dispatcher = lb.activeAsyncDispatcher(); + handle = lb.handle(); + }); + if (!found) { + return; + } if (dispatcher != nullptr) { - // Post all work to the worker thread. The host lookup and context access must happen - // on the worker thread because the module may call this from a background thread. - // Keep the cluster alive via the handle's shared_ptr until the callback fires. - auto handle = lb->handle(); + // Post to the worker thread. The handle keeps the cluster alive until the callback runs. dispatcher->post([context_envoy_ptr, host, details_str = std::move(details_str), cancelled = std::move(cancelled), handle = std::move(handle)]() { if (cancelled != nullptr && cancelled->load(std::memory_order_acquire)) { @@ -1111,7 +1123,7 @@ void envoy_dynamic_module_callback_cluster_lb_async_host_selection_complete( auto* context = getContext(context_envoy_ptr); Envoy::Upstream::HostConstSharedPtr host_shared; if (host != nullptr) { - host_shared = lb->handle()->cluster()->findHost(host); + host_shared = handle->cluster()->findHost(host); } context->onAsyncHostSelection(std::move(host_shared), std::move(details_str)); } diff --git a/source/extensions/clusters/dynamic_modules/cluster.cc b/source/extensions/clusters/dynamic_modules/cluster.cc index 9f38ac7f319ec..d9dbeb177bda0 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.cc +++ b/source/extensions/clusters/dynamic_modules/cluster.cc @@ -7,6 +7,7 @@ #include "envoy/upstream/locality.h" #include "source/common/buffer/buffer_impl.h" +#include "source/common/common/thread.h" #include "source/common/network/address_impl.h" #include "source/common/network/utility.h" #include "source/common/protobuf/protobuf.h" @@ -157,13 +158,24 @@ DynamicModuleClusterConfig::~DynamicModuleClusterConfig() { DynamicModuleClusterHandle::~DynamicModuleClusterHandle() { std::shared_ptr cluster = std::move(cluster_); cluster_.reset(); - // Release lifecycle handles eagerly while the lifecycle notifier is still valid. When the - // dispatcher destructor clears pending callbacks, the cluster destructor would otherwise try to - // unregister from already-destroyed lifecycle notifier lists. + if (cluster == nullptr) { + return; + } + Event::Dispatcher& dispatcher = cluster->dispatcher_; + // The lifecycle handle resets unregister from main-thread-owned notifiers. When the handle is + // destroyed on a worker thread, post the full teardown onto the main dispatcher. + if (!Thread::MainThread::isMainThread() && !Thread::TestThread::isTestThread()) { + dispatcher.post([cluster = std::move(cluster)]() mutable { + cluster->server_initialized_handle_.reset(); + cluster->shutdown_handle_.reset(); + cluster->drain_handle_.reset(); + cluster.reset(); + }); + return; + } cluster->server_initialized_handle_.reset(); cluster->shutdown_handle_.reset(); cluster->drain_handle_.reset(); - Event::Dispatcher& dispatcher = cluster->dispatcher_; dispatcher.post([cluster = std::move(cluster)]() mutable { cluster.reset(); }); } @@ -590,9 +602,48 @@ void DynamicModuleCluster::HttpCalloutCallback::onFailure(const Http::AsyncClien // DynamicModuleLoadBalancer // ================================================================================================= +namespace { +// Process-wide registry of live `DynamicModuleLoadBalancer` instances, consulted by the async +// host selection completion ABI callback to validate the raw pointer it receives from the module. +absl::Mutex& activeDynamicModuleLoadBalancersMutex() { + static auto* m = new absl::Mutex(); + return *m; +} + +absl::flat_hash_set& activeDynamicModuleLoadBalancers() + ABSL_EXCLUSIVE_LOCKS_REQUIRED(activeDynamicModuleLoadBalancersMutex()) { + static auto* s = new absl::flat_hash_set(); + return *s; +} + +void registerActiveDynamicModuleLoadBalancer(const DynamicModuleLoadBalancer* lb) { + absl::MutexLock lock(&activeDynamicModuleLoadBalancersMutex()); + activeDynamicModuleLoadBalancers().insert(lb); +} + +void unregisterActiveDynamicModuleLoadBalancer(const DynamicModuleLoadBalancer* lb) { + absl::MutexLock lock(&activeDynamicModuleLoadBalancersMutex()); + activeDynamicModuleLoadBalancers().erase(lb); +} +} // namespace + +bool DynamicModuleLoadBalancer::withActiveInstance( + const DynamicModuleLoadBalancer* lb, + absl::FunctionRef f) { + absl::MutexLock lock(&activeDynamicModuleLoadBalancersMutex()); + if (!activeDynamicModuleLoadBalancers().contains(lb)) { + return false; + } + f(*lb); + return true; +} + DynamicModuleLoadBalancer::DynamicModuleLoadBalancer( const DynamicModuleClusterHandleSharedPtr& handle, const Upstream::PrioritySet& priority_set) : handle_(handle), priority_set_(priority_set), in_module_lb_(nullptr) { + // Register before invoking any module hook so a concurrent async host selection completion can + // validate its raw pointer against a live instance. + registerActiveDynamicModuleLoadBalancer(this); in_module_lb_ = handle_->cluster_->config()->on_cluster_lb_new_(handle_->cluster_->inModuleCluster(), this); @@ -612,6 +663,9 @@ DynamicModuleLoadBalancer::DynamicModuleLoadBalancer( } DynamicModuleLoadBalancer::~DynamicModuleLoadBalancer() { + // Unregister before tearing down module-side state so any concurrent async host selection + // completion observes the instance as gone and drops the event. + unregisterActiveDynamicModuleLoadBalancer(this); if (in_module_lb_ != nullptr && handle_->cluster_->config()->on_cluster_lb_destroy_ != nullptr) { handle_->cluster_->config()->on_cluster_lb_destroy_(in_module_lb_); } diff --git a/source/extensions/clusters/dynamic_modules/cluster.h b/source/extensions/clusters/dynamic_modules/cluster.h index 01c6e502fbcec..f428ec58db04f 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.h +++ b/source/extensions/clusters/dynamic_modules/cluster.h @@ -26,6 +26,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" +#include "absl/functional/function_ref.h" namespace Envoy { namespace Extensions { @@ -542,6 +543,15 @@ class DynamicModuleLoadBalancer : public Upstream::LoadBalancer { // Returns the priority set that this load balancer subscribes to for host membership updates. const Upstream::PrioritySet& memberUpdatePrioritySet() const { return priority_set_; } + /** + * Looks up `lb` in the process-wide registry of live instances. Returns true and invokes `f` + * with the instance under the registry lock when found, false otherwise. Used by the async + * host selection completion ABI callback, which receives a raw pointer from the module that + * may outlive the load balancer. + */ + static bool withActiveInstance(const DynamicModuleLoadBalancer* lb, + absl::FunctionRef f); + private: const DynamicModuleClusterHandleSharedPtr handle_; // Worker local priority set that backs the membership update subscription. diff --git a/test/extensions/clusters/dynamic_modules/BUILD b/test/extensions/clusters/dynamic_modules/BUILD index 929ac16c7196a..7880b24743416 100644 --- a/test/extensions/clusters/dynamic_modules/BUILD +++ b/test/extensions/clusters/dynamic_modules/BUILD @@ -36,6 +36,7 @@ envoy_cc_test( "//test/mocks/upstream:priority_set_mocks", "//test/mocks/upstream:thread_local_cluster_mocks", "//test/test_common:environment_lib", + "//test/test_common:thread_factory_for_test_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/clusters/dynamic_modules/v3:pkg_cc_proto", diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index c6a8e49404b37..7550325635f38 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -16,6 +16,7 @@ #include "test/mocks/upstream/priority_set.h" #include "test/mocks/upstream/thread_local_cluster.h" #include "test/test_common/environment.h" +#include "test/test_common/thread_factory_for_test.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -1187,6 +1188,36 @@ TEST_F(DynamicModuleClusterTest, HandleDestructorDispatchesToMainThread) { handle.reset(); } +// Covers that a worker-thread handle destruction posts the full teardown to the main dispatcher. +TEST_F(DynamicModuleClusterTest, HandleDestructorFromWorkerThreadDefersAllTeardownToMainThread) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()) << result.status().message(); + auto cluster = std::dynamic_pointer_cast(result->first); + ASSERT_NE(nullptr, cluster); + + // The internal handle held by the thread-aware load balancer also posts on its main-thread + // destruction when `result` is torn down, so allow repeated posts and capture the first one + // from the worker-thread handle destruction. + Event::PostCb captured_cb; + EXPECT_CALL(server_context_.dispatcher_, post(_)) + .WillRepeatedly(testing::Invoke([&](Event::PostCb cb) { + if (!captured_cb) { + captured_cb = std::move(cb); + } + })); + + auto handle = std::make_shared(cluster); + auto& thread_factory = Thread::threadFactoryForTest(); + auto thread = thread_factory.createThread([&]() { handle.reset(); }); + thread->join(); + + ASSERT_TRUE(captured_cb); + EXPECT_NE(cluster.use_count(), 1); + captured_cb(); + cluster.reset(); + result = absl::InternalError("cleanup"); +} + // Test that the server_initialized lifecycle callback is invoked. TEST_F(DynamicModuleClusterTest, ServerInitializedCallback) { // Capture the PostInit callback registered during cluster construction. @@ -2181,6 +2212,46 @@ TEST_F(DynamicModuleClusterTest, AsyncHostSelectionCompleteEmptyDetails) { nullptr, {nullptr, 0}); } +// Covers that `async_host_selection_complete` drops the event when the owning load balancer has +// already been destroyed. +TEST_F(DynamicModuleClusterTest, AsyncHostSelectionCompleteAfterLbDestroyedDropsEvent) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()); + auto& [cluster, lb] = result.value(); + + auto handle = std::make_shared( + std::dynamic_pointer_cast(cluster)); + auto lb_instance = std::make_unique(handle, cluster->prioritySet()); + void* raw_lb_ptr = lb_instance.get(); + + lb_instance.reset(); + + NiceMock context; + EXPECT_CALL(context, onAsyncHostSelection(_, _)).Times(0); + + envoy_dynamic_module_callback_cluster_lb_async_host_selection_complete( + raw_lb_ptr, static_cast(&context), nullptr, {"dropped", 7}); +} + +// Covers pointer reuse: a new load balancer allocated at the same address as a freed one must +// still be found by the registry. +TEST_F(DynamicModuleClusterTest, AsyncHostSelectionCompleteRegistersFreshInstancePerLb) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()); + auto& [cluster, lb] = result.value(); + + auto handle = std::make_shared( + std::dynamic_pointer_cast(cluster)); + + auto first = std::make_unique(handle, cluster->prioritySet()); + first.reset(); + auto second = std::make_unique(handle, cluster->prioritySet()); + + bool found = DynamicModuleLoadBalancer::withActiveInstance( + second.get(), [](const DynamicModuleLoadBalancer&) {}); + EXPECT_TRUE(found); +} + // ============================================================================= // HTTP Callout Tests // ============================================================================= From f00883b60391e9d290da85c3838fce1b303f78f4 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Sat, 18 Apr 2026 11:58:13 -0400 Subject: [PATCH 072/750] router: return DEADLINE_EXCEEDED for gRPC timeout instead of UNAVAILABLE (#44479) When the router enforces a gRPC request timeout (global or per-try), it now returns gRPC status DEADLINE_EXCEEDED (4) instead of UNAVAILABLE (14). Previously, the router sent an HTTP 504 local reply which was mapped to UNAVAILABLE via the standard HTTP-to-gRPC status mapping. This caused clients to incorrectly retry timed-out requests, since UNAVAILABLE signals "try again" while DEADLINE_EXCEEDED signals "deadline already expired." The fix passes DeadlineExceeded explicitly to sendLocalReply() for gRPC timeout responses, bypassing the lossy HTTP-to-gRPC status code mapping. Risk: low Testing: unit tests for global and per-try timeout on gRPC requests Runtime guard: envoy.reloadable_features.grpc_timeout_returns_deadline_exceeded Fixes envoyproxy/envoy#10184 --------- Signed-off-by: Jonh Wendell --- changelogs/current.yaml | 8 ++ source/common/router/router.cc | 12 ++- source/common/router/router.h | 3 +- source/common/runtime/runtime_features.cc | 4 + test/common/router/router_test.cc | 89 +++++++++++++++++++++++ 5 files changed, 112 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index a39a9ee2aa64d..8b9bf3834175f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -159,6 +159,14 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: grpc + change: | + Fixed the router to return ``DEADLINE_EXCEEDED`` (gRPC status 4) instead of ``UNAVAILABLE`` (gRPC + status 14) when the router itself enforces a gRPC request timeout. Previously, the router sent an + HTTP 504 local reply which was mapped to ``UNAVAILABLE`` via the standard HTTP-to-gRPC status mapping, + causing clients to incorrectly retry requests that had already timed out. This behavior is disabled + by default and can be enabled by setting the runtime guard + ``envoy.reloadable_features.grpc_timeout_returns_deadline_exceeded`` to ``true``. - area: quic change: | Fixed HTTP/3 client connection pool to handle early data request correctly with asynchronous certificates diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 0e4d06b10ae9e..cb61c97f146ae 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -1453,11 +1453,17 @@ void Filter::onUpstreamTimeoutAbort(StreamInfo::CoreResponseFlag response_flags, const absl::string_view body = timeout_response_code_ == Http::Code::GatewayTimeout ? "upstream request timeout" : ""; - onUpstreamAbort(timeout_response_code_, response_flags, body, false, details); + const absl::optional grpc_status = + (grpc_request_ && Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.grpc_timeout_returns_deadline_exceeded")) + ? absl::make_optional(Grpc::Status::WellKnownGrpcStatus::DeadlineExceeded) + : absl::nullopt; + onUpstreamAbort(timeout_response_code_, response_flags, body, false, details, grpc_status); } void Filter::onUpstreamAbort(Http::Code code, StreamInfo::CoreResponseFlag response_flags, - absl::string_view body, bool dropped, absl::string_view details) { + absl::string_view body, bool dropped, absl::string_view details, + absl::optional grpc_status) { // If we have not yet sent anything downstream, send a response with an appropriate status code. // Otherwise just reset the ongoing response. callbacks_->streamInfo().setResponseFlag(response_flags); @@ -1474,7 +1480,7 @@ void Filter::onUpstreamAbort(Http::Code code, StreamInfo::CoreResponseFlag respo } modify_headers_(headers); }, - absl::nullopt, details); + grpc_status, details); } bool Filter::maybeRetryReset(Http::StreamResetReason reset_reason, diff --git a/source/common/router/router.h b/source/common/router/router.h index 857abde5b554b..11465b0c15aea 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -597,7 +597,8 @@ class Filter : Logger::Loggable, // headers (e.g. due to a reset). Handles recording stats and responding // downstream if appropriate. void onUpstreamAbort(Http::Code code, StreamInfo::CoreResponseFlag response_flag, - absl::string_view body, bool dropped, absl::string_view details); + absl::string_view body, bool dropped, absl::string_view details, + absl::optional grpc_status = absl::nullopt); void onUpstreamComplete(UpstreamRequest& upstream_request); // Reset all in-flight upstream requests. void resetAll(); diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 610d9e050e207..38bd22731c7cd 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -184,6 +184,10 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_disable_quic_ip_packet_info_socket // compliance restrictions. FALSE_RUNTIME_GUARD(envoy_reloadable_features_google_grpc_disable_tls_13); +// TODO(jwendell): Flip to true to make gRPC timeout responses return DEADLINE_EXCEEDED +// instead of UNAVAILABLE. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_grpc_timeout_returns_deadline_exceeded); + // TODO(yanavlasov): Flip to true after prod testing. // Controls whether a stream stays open when HTTP/2 or HTTP/3 upstream half closes // before downstream. diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 3617d522a60a0..1b5c5bf3aae34 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -1767,6 +1767,95 @@ TEST_F(RouterTest, UpstreamTimeout) { EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); } +// Verify that a gRPC request hitting the global response timeout gets +// DEADLINE_EXCEEDED (grpc-status 4) instead of UNAVAILABLE. +TEST_F(RouterTest, GrpcUpstreamTimeout) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.grpc_timeout_returns_deadline_exceeded", "true"}}); + + NiceMock encoder; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder, &response_decoder, Http::Protocol::Http10); + + expectResponseTimerCreate(); + + Http::TestRequestHeaderMapImpl headers{{"content-type", "application/grpc"}, + {"grpc-timeout", "20S"}}; + HttpTestUtility::addDefaultHeaders(headers); + callbacks_.is_grpc_request_ = true; + router_->decodeHeaders(headers, false); + Buffer::OwnedImpl data; + router_->decodeData(data, true); + EXPECT_EQ(1U, + callbacks_.route_->virtual_host_->virtual_cluster_.stats().upstream_rq_total_.value()); + + EXPECT_CALL(callbacks_.stream_info_, + setResponseFlag(StreamInfo::CoreResponseFlag::UpstreamRequestTimeout)); + EXPECT_CALL(encoder.stream_, resetStream(Http::StreamResetReason::LocalReset)); + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, + {"content-type", "application/grpc"}, + {"grpc-status", "4"}, + {"grpc-message", "upstream request timeout"}}; + EXPECT_CALL(callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), true)); + EXPECT_CALL(*router_->retry_state_, shouldRetryReset(_, _, _, _)).Times(0); + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginTimeout, _)); + response_timeout_->invokeCallback(); + + EXPECT_EQ(1U, + cm_.thread_local_cluster_.cluster_.info_->stats_store_.counter("upstream_rq_timeout") + .value()); + EXPECT_EQ(1UL, cm_.thread_local_cluster_.conn_pool_.host_->stats().rq_timeout_.value()); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); +} + +// Verify that a gRPC request hitting the per-try timeout gets +// DEADLINE_EXCEEDED (grpc-status 4) instead of UNAVAILABLE. +TEST_F(RouterTest, GrpcUpstreamPerTryTimeout) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.grpc_timeout_returns_deadline_exceeded", "true"}}); + + NiceMock encoder; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder, &response_decoder, Http::Protocol::Http10); + Http::TestRequestHeaderMapImpl headers{{"content-type", "application/grpc"}, + {"grpc-timeout", "20S"}, + {"x-envoy-internal", "true"}, + {"x-envoy-upstream-rq-per-try-timeout-ms", "5"}}; + HttpTestUtility::addDefaultHeaders(headers); + callbacks_.is_grpc_request_ = true; + router_->decodeHeaders(headers, false); + + expectPerTryTimerCreate(); + expectResponseTimerCreate(); + + Buffer::OwnedImpl data; + router_->decodeData(data, true); + EXPECT_EQ(1U, + callbacks_.route_->virtual_host_->virtual_cluster_.stats().upstream_rq_total_.value()); + + EXPECT_CALL(callbacks_.stream_info_, + setResponseFlag(StreamInfo::CoreResponseFlag::UpstreamRequestTimeout)); + EXPECT_CALL(encoder.stream_, resetStream(Http::StreamResetReason::LocalReset)); + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, + {"content-type", "application/grpc"}, + {"grpc-status", "4"}, + {"grpc-message", "upstream request timeout"}}; + EXPECT_CALL(callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), true)); + EXPECT_CALL( + cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginTimeout, absl::optional(504))); + per_try_timeout_->invokeCallback(); + + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("upstream_rq_per_try_timeout") + .value()); + EXPECT_EQ(1UL, cm_.thread_local_cluster_.conn_pool_.host_->stats().rq_timeout_.value()); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); +} + // Verify the timeout budget histograms are filled out correctly when using a // global and per-try timeout in a successful request. TEST_F(RouterTest, TimeoutBudgetHistogramStat) { From 746c7ad0929d295e1628181a2b24bc47c673b032 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Sun, 19 Apr 2026 07:29:27 +0200 Subject: [PATCH 073/750] dynamic_modules: defer bootstrap extension timer destruction onto the main thread (#44523) ## Description `DynamicModuleBootstrapExtensionTimer` wraps an `Event::TimerPtr` and the destructor of `Event::Timer` deregisters the timer from the dispatcher's timer list, which is only safe on the thread that owns the dispatcher. The `envoy_dynamic_module_callback_bootstrap_extension_timer_delete` ABI callback previously destroyed the wrapper inline on whatever thread the module called from, including a tokio worker running the bootstrap async init future. A background thread destructor triggers a cross-thread mutation of the dispatcher's timer list. This change posts the deletion to the owning config's main thread dispatcher when the caller is not the main thread or a test thread. On the main thread, the wrapper is deleted synchronously so that shutdown, where the dispatcher no longer drains posted callbacks, keeps behaving as before. If the owning config has already been released, the dispatcher reference is no longer reachable and the wrapper is deleted inline as a best effort; this path is only reachable when the module calls `timer_delete` after the server has dropped the config, for example during validate mode teardown. --- **Commit Message:** dynamic_modules: defer bootstrap extension timer destruction onto the main thread **Additional Description:** `timer_delete` now posts the wrapper's destruction to the owning config's main thread dispatcher when the caller is not on the main or test thread, so the underlying Envoy timer is never deregistered off the dispatcher thread. **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** N/A **Release Notes:** N/A --------- Signed-off-by: Rohit Agrawal --- source/extensions/bootstrap/dynamic_modules/abi_impl.cc | 7 +++++++ source/extensions/dynamic_modules/abi/abi.h | 7 ++++++- .../extensions/dynamic_modules/sdk/rust/src/bootstrap.rs | 9 +++++++-- .../extensions/dynamic_modules/sdk/rust/src/lib_test.rs | 5 ++++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/source/extensions/bootstrap/dynamic_modules/abi_impl.cc b/source/extensions/bootstrap/dynamic_modules/abi_impl.cc index c0eec93f7bfc7..9aa38248a1e72 100644 --- a/source/extensions/bootstrap/dynamic_modules/abi_impl.cc +++ b/source/extensions/bootstrap/dynamic_modules/abi_impl.cc @@ -5,6 +5,7 @@ #include "envoy/server/admin.h" #include "source/common/buffer/buffer_impl.h" +#include "source/common/common/thread.h" #include "source/common/stats/symbol_table.h" #include "source/common/stats/utility.h" #include "source/extensions/bootstrap/dynamic_modules/extension.h" @@ -490,6 +491,12 @@ bool envoy_dynamic_module_callback_bootstrap_extension_timer_enabled( void envoy_dynamic_module_callback_bootstrap_extension_timer_delete( envoy_dynamic_module_type_bootstrap_extension_timer_module_ptr timer_ptr) { + using namespace Envoy; + // The underlying `Event::Timer` `deregisters` from the dispatcher's timer list in its + // destructor, which is only safe on the dispatcher thread. Callers that hold the timer handle + // from a context that may be dropped off the main thread must route deletion through the + // scheduler ABI. + ASSERT_IS_MAIN_OR_TEST_THREAD(); delete static_cast(timer_ptr); } diff --git a/source/extensions/dynamic_modules/abi/abi.h b/source/extensions/dynamic_modules/abi/abi.h index 543183789e0d9..a2c63c472df9d 100644 --- a/source/extensions/dynamic_modules/abi/abi.h +++ b/source/extensions/dynamic_modules/abi/abi.h @@ -8078,7 +8078,12 @@ bool envoy_dynamic_module_callback_bootstrap_extension_timer_enabled( * a timer created by envoy_dynamic_module_callback_bootstrap_extension_timer_new. The timer is * automatically disabled before deletion. * - * This must be called on the main thread. + * This must be called on the main thread. The underlying Envoy timer `deregisters` from the + * main thread dispatcher's timer list in its destructor, so invoking this callback from any + * other thread is undefined behavior. If the module cannot guarantee main-thread deletion (for + * example, when the handle is owned by an async task that may complete on a worker thread), it + * must route deletion through the scheduler ABI so that the actual call lands on the main + * thread. * * @param timer_ptr is the pointer to the timer created by * envoy_dynamic_module_callback_bootstrap_extension_timer_new. diff --git a/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs b/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs index 7df017bd952a8..82380dba1f015 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs @@ -566,10 +566,15 @@ impl EnvoyBootstrapExtensionConfigScheduler for Box abi::envoy_dynamic_module_type_on_network_filter_data_status { assert_eq!(envoy_filter.get_connection_id(), 42); - assert_eq!(envoy_filter.get_remote_address(), ("10.0.0.1".to_string(), 1234)); + assert_eq!( + envoy_filter.get_remote_address(), + ("10.0.0.1".to_string(), 1234) + ); abi::envoy_dynamic_module_type_on_network_filter_data_status::Continue } } From f724ec128aa9d138bd5d05eca6b2c3d0cff30dcd Mon Sep 17 00:00:00 2001 From: basundhara-c Date: Sun, 19 Apr 2026 20:00:04 -0700 Subject: [PATCH 074/750] ext_authz: add shadow mode for HTTP filter (#44481) ## Description Add a `shadow_mode` config option to the HTTP ExtAuthZ filter. When enabled, the filter calls the authorization service as normal but never sends a local reply on deny/error. Instead it writes the decision (status, body, headers) into filter state so a downstream filter can read and optionally enforce it. This allows downstream filters to make a decision based on the metadata set by ext-authz --- **Commit Message:** ext_authz: add shadow mode for HTTP ExtAuthZ filter **Additional Description:** Added a `shadow_mode` config option to the HTTP ExtAuthZ filter. **Risk Level:** Low **Testing: A**dded Tests **Docs Changes:** Added **Release Notes:** Added --------- Signed-off-by: Basundhara Chakrabarty --- .../filters/http/ext_authz/v3/ext_authz.proto | 60 ++- changelogs/current.yaml | 10 + .../filters/http/ext_authz/ext_authz.cc | 100 +++- .../filters/http/ext_authz/ext_authz.h | 59 ++- .../ext_authz/ext_authz_integration_test.cc | 42 ++ .../filters/http/ext_authz/ext_authz_test.cc | 451 ++++++++++++++++++ 6 files changed, 719 insertions(+), 3 deletions(-) diff --git a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index 1a668dd3efd06..2cc6e689b6e20 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -30,7 +30,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // External Authorization :ref:`configuration overview `. // [#extension: envoy.filters.http.ext_authz] -// [#next-free-field: 32] +// [#next-free-field: 33] message ExtAuthz { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.ext_authz.v3.ExtAuthz"; @@ -359,6 +359,64 @@ message ExtAuthz { // // Defaults to ``false``. bool enforce_response_header_limits = 31; + + // When set to ``true``, the filter operates in shadow mode. In shadow mode the + // filter still calls the external authorization service and processes the response, + // but never terminates the request. Instead of sending a local reply on a denied or + // error response, the filter writes the authorization decision (engine result, status + // code, response headers) into the request's + // :ref:`FilterState ` as a + // :ref:`ShadowDecision + // ` object so + // that subsequent filters can read and optionally enforce it. + // + // The FilterState key is the filter's configured ``name`` in the filter chain with a + // ``.shadow`` suffix (``envoy.filters.http.ext_authz.shadow`` by default). Multiple ext_authz + // filters in the same chain must already have distinct names and therefore write to distinct + // keys automatically. + // + // The auth server's denied-response body is intentionally **not** carried on the + // ShadowDecision: bodies can be arbitrarily large and no downstream consumer in the + // shadow-comparison flow needs them. A consumer that wants to reproduce the auth + // server's full denied response must read it from its own source of truth rather + // than replaying it from FilterState. + // + // Header and query-parameter mutations from an OK response are still applied to the + // request as usual. + // + // Defaults to ``false``. + bool shadow_mode = 32; +} + +// Serialized form of the shadow-mode authorization decision written to FilterState +// when :ref:`shadow_mode +// ` is +// enabled. Consumed by a downstream filter that decides whether to enforce the +// decision. +message ShadowDecision { + // The decision the auth server returned. + enum CheckResult { + UNSPECIFIED = 0; + OK = 1; + DENIED = 2; + ERROR = 3; + } + + // The decision the auth server returned. + CheckResult check_result = 1; + + // Response status code associated with the decision. For ``DENIED`` and ``ERROR`` this is + // the code the filter would have set on termination (the auth server's code for ``DENIED``, + // or :ref:`status_on_error + // ` fallback + // for ``ERROR``). For ``OK`` this defaults to ``200`` so consumers always see a populated + // value — the upstream response code is not known at shadow-decision time. + uint32 status_code = 2 [(validate.rules).uint32 = {lte: 599 gte: 100 ignore_empty: true}]; + + // Response headers the auth server asked to set on a denied response + // (e.g. ``WWW-Authenticate``, ``Set-Cookie``). Populated for ``DENIED`` only. + // Preserves ordering and duplicate header names. + repeated config.core.v3.HeaderValue response_headers = 3; } // Configuration for buffering the request data. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8b9bf3834175f..56d2f0e624afd 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -399,6 +399,16 @@ removed_config_or_runtime: and legacy code path. new_features: +- area: ext_authz + change: | + Added :ref:`shadow_mode + ` to the HTTP + ext_authz filter. When enabled, the filter calls the authorization service as normal but never + terminates the request. The authorization decision is written to FilterState as a + :ref:`ShadowDecision ` + under the filter's configured name with a ``.shadow`` suffix + (``envoy.filters.http.ext_authz.shadow`` by default) so that a subsequent filter can read + and optionally enforce it. - area: lua change: | Added stats API support to the :ref:`Lua filter `, allowing Lua diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index 74f3f4ee3e14b..404093d2f704f 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -12,6 +12,7 @@ #include "source/common/common/macros.h" #include "source/common/common/matchers.h" #include "source/common/http/utility.h" +#include "source/common/protobuf/utility.h" #include "source/common/router/config_impl.h" #include "source/extensions/filters/common/processing_effect/processing_effect.h" @@ -82,7 +83,7 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::ext_authz::v3 : allow_partial_message_(config.with_request_body().allow_partial_message()), failure_mode_allow_(config.failure_mode_allow()), failure_mode_allow_header_add_(config.failure_mode_allow_header_add()), - clear_route_cache_(config.clear_route_cache()), + shadow_mode_(config.shadow_mode()), clear_route_cache_(config.clear_route_cache()), max_request_bytes_(config.with_request_body().max_request_bytes()), max_denied_response_body_bytes_(config.max_denied_response_body_bytes()), @@ -406,6 +407,21 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, if (!config_->filterEnabled(decoder_callbacks_->streamInfo().dynamicMetadata())) { stats_.disabled_.inc(); if (config_->denyAtDisable()) { + if (config_->shadowMode()) { + ENVOY_STREAM_LOG(trace, + "ext_authz filter is disabled (deny_at_disable). Shadow mode: setting " + "filter state and continuing.", + *decoder_callbacks_); + auto response = std::make_unique(); + response->status = Filters::Common::ExtAuthz::CheckStatus::Denied; + response->status_code = config_->statusOnError(); + setShadowFilterState(*response); + // Set the response flag so access logs reflect what enforce mode would have logged, + // enabling operators to correlate shadow-denied-on-disable with real denials. + decoder_callbacks_->streamInfo().setResponseFlag( + StreamInfo::CoreResponseFlag::UnauthorizedExternalService); + return Http::FilterHeadersStatus::Continue; + } ENVOY_STREAM_LOG(trace, "ext_authz filter is disabled. Deny the request.", *decoder_callbacks_); decoder_callbacks_->streamInfo().setResponseFlag( @@ -674,6 +690,7 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { response->dynamic_metadata); } } + switch (response->status) { case CheckStatus::OK: { // Any changes to request headers or query parameters can affect how the request is going to be @@ -959,6 +976,9 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { if (cluster_) { config_->incCounter(cluster_->statsScope(), config_->ext_authz_ok_); } + if (config_->shadowMode()) { + setShadowFilterState(*response); + } stats_.ok_.inc(); continueDecoding(); break; @@ -987,6 +1007,13 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { } } + // In shadow mode, set the dynamic metadata and continue without sending a local reply. + if (config_->shadowMode()) { + setShadowFilterState(*response); + continueDecoding(); + break; + } + // Check headers are valid. if (config_->validateMutations()) { for (const auto& [key, value] : response->headers_to_set) { @@ -1047,6 +1074,13 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { } stats_.error_.inc(); + // In shadow mode, always continue on error (equivalent to forced fail-open). + if (config_->shadowMode()) { + setShadowFilterState(*response); + continueDecoding(); + break; + } + // Validate error response headers and clear custom attributes if invalid. validateAndClearInvalidErrorResponseAttributes(response); @@ -1255,6 +1289,70 @@ void Filter::addErrorResponseHeaders( } } +void ShadowDecisionObject::populateProto(ShadowDecisionProto& msg) const { + msg.set_check_result(check_result_); + if (status_code_ != static_cast(0)) { + msg.set_status_code(static_cast(status_code_)); + } + for (const auto& [key, value] : response_headers_) { + auto* hv = msg.add_response_headers(); + hv->set_key(key); + hv->set_value(value); + } +} + +ProtobufTypes::MessagePtr ShadowDecisionObject::serializeAsProto() const { + auto msg = std::make_unique(); + populateProto(*msg); + return msg; +} + +absl::optional ShadowDecisionObject::serializeAsString() const { + ShadowDecisionProto msg; + populateProto(msg); + return MessageUtil::getJsonStringFromMessageOrError(msg); +} + +void Filter::setShadowFilterState(Filters::Common::ExtAuthz::Response& response) { + using Filters::Common::ExtAuthz::CheckStatus; + using ShadowDecisionProto = envoy::extensions::filters::http::ext_authz::v3::ShadowDecision; + + ShadowDecisionProto::CheckResult check_result = ShadowDecisionProto::UNSPECIFIED; + Http::Code status_code = static_cast(0); + Filters::Common::ExtAuthz::UnsafeHeaderVector response_headers; + + switch (response.status) { + case CheckStatus::OK: + check_result = ShadowDecisionProto::OK; + status_code = Http::Code::OK; + break; + case CheckStatus::Denied: + check_result = ShadowDecisionProto::DENIED; + status_code = response.status_code; + response_headers = std::move(response.headers_to_set); + stats_.shadow_denied_.inc(); + break; + case CheckStatus::Error: + check_result = ShadowDecisionProto::ERROR; + status_code = response.status_code != static_cast(0) ? response.status_code + : config_->statusOnError(); + stats_.shadow_error_.inc(); + break; + default: + IS_ENVOY_BUG("unexpected CheckStatus value in shadow mode"); + return; + } + + auto object = std::make_shared(check_result, status_code, + std::move(response_headers)); + + // Suffix the key with ".shadow" so it doesn't collide with ExtAuthzLoggingInfo, which + // already uses the filter's configured name as its FilterState key (with a different lifespan). + decoder_callbacks_->streamInfo().filterState()->setData( + absl::StrCat(decoder_callbacks_->filterConfigName(), ".shadow"), std::move(object), + StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); +} + } // namespace ExtAuthz } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index eec1f4fa71bba..0d937d1033a1b 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -50,7 +50,9 @@ namespace ExtAuthz { COUNTER(filter_state_name_collision) \ COUNTER(omitted_response_headers) \ COUNTER(request_header_limits_reached) \ - COUNTER(response_header_limits_reached) + COUNTER(response_header_limits_reached) \ + COUNTER(shadow_denied) \ + COUNTER(shadow_error) /** * Wrapper struct for ext_authz filter stats. @see stats_macros.h @@ -59,6 +61,53 @@ struct ExtAuthzFilterStats { ALL_EXT_AUTHZ_FILTER_STATS(GENERATE_COUNTER_STRUCT) }; +/** + * Shadow-mode authorization decision carried in FilterState when + * ``shadow_mode`` is enabled. A downstream filter reads this object and + * decides whether to enforce the decision. + */ +class ShadowDecisionObject : public Envoy::StreamInfo::FilterState::Object { +public: + using ShadowDecisionProto = envoy::extensions::filters::http::ext_authz::v3::ShadowDecision; + + ShadowDecisionObject(ShadowDecisionProto::CheckResult check_result, Http::Code status_code, + Filters::Common::ExtAuthz::UnsafeHeaderVector response_headers) + : check_result_(check_result), status_code_(status_code), + response_headers_(std::move(response_headers)) {} + + ShadowDecisionProto::CheckResult checkResult() const { return check_result_; } + Http::Code statusCode() const { return status_code_; } + const Filters::Common::ExtAuthz::UnsafeHeaderVector& responseHeaders() const { + return response_headers_; + } + + ProtobufTypes::MessagePtr serializeAsProto() const override; + + absl::optional serializeAsString() const override; + + // Expose check_result and status_code as individual fields so access-log formatters + // and CEL expressions can read them without paying the cost of serializing the full + // ShadowDecision to JSON. + bool hasFieldSupport() const override { return true; } + Envoy::StreamInfo::FilterState::Object::FieldType + getField(absl::string_view field_name) const override { + if (field_name == "check_result") { + return absl::string_view(ShadowDecisionProto::CheckResult_Name(check_result_)); + } + if (field_name == "status_code" && status_code_ != static_cast(0)) { + return int64_t(static_cast(status_code_)); + } + return {}; + } + +private: + void populateProto(ShadowDecisionProto& msg) const; + + const ShadowDecisionProto::CheckResult check_result_; + const Http::Code status_code_; + const Filters::Common::ExtAuthz::UnsafeHeaderVector response_headers_; +}; + class ExtAuthzLoggingInfo : public Envoy::StreamInfo::FilterState::Object { public: explicit ExtAuthzLoggingInfo(const absl::optional filter_metadata) @@ -147,6 +196,8 @@ class FilterConfig { bool failureModeAllow() const { return failure_mode_allow_; } + bool shadowMode() const { return shadow_mode_; } + bool failureModeAllowHeaderAdd() const { return failure_mode_allow_header_add_; } bool clearRouteCache() const { return clear_route_cache_; } @@ -264,6 +315,7 @@ class FilterConfig { const bool allow_partial_message_; const bool failure_mode_allow_; const bool failure_mode_allow_header_add_; + const bool shadow_mode_; const bool clear_route_cache_; const uint32_t max_request_bytes_; const uint32_t max_denied_response_body_bytes_; @@ -475,6 +527,11 @@ class Filter : public Logger::Loggable, void addResponseHeaders(Http::HeaderMap& header_map, const Http::HeaderVector& headers); void initiateCall(const Http::RequestHeaderMap& headers); void continueDecoding(); + // In shadow mode, writes the authorization decision and response attributes into + // FilterState and increments the appropriate shadow stat counter. Takes the response + // by non-const reference so we can std::move ``headers_to_set`` into the object instead + // of copying. + void setShadowFilterState(Filters::Common::ExtAuthz::Response& response); bool isBufferFull(uint64_t num_bytes_processing) const; void updateLoggingInfo(const absl::optional& grpc_status); void updateEffect(const Filters::Common::ProcessingEffect::Effect effect); diff --git a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc index a593d24613735..3982766672437 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc @@ -53,6 +53,7 @@ struct GrpcInitializeConfigOpts { bool stats_expect_response_bytes = true; bool enforce_response_header_limits = false; uint32_t status_on_error_code = 0; + bool shadow_mode = false; }; struct WaitForSuccessfulUpstreamResponseOpts { @@ -154,6 +155,10 @@ class ExtAuthzGrpcIntegrationTest proto_config_.set_enforce_response_header_limits(true); } + if (opts.shadow_mode) { + proto_config_.set_shadow_mode(true); + } + if (opts.status_on_error_code > 0) { proto_config_.mutable_status_on_error()->set_code( static_cast(opts.status_on_error_code)); @@ -3466,6 +3471,43 @@ TEST_P(ExtAuthzGrpcIntegrationTest, ExtensionWithMatcherDynamicMetadata) { } } +// Verify that in shadow mode a denied response does not terminate the request — the request +// reaches the upstream and the client gets a 200 — and that the ShadowDecision is readable +// via %FILTER_STATE()% in access logs. +TEST_P(ExtAuthzGrpcIntegrationTest, ShadowModeDeniedReachesUpstream) { + GrpcInitializeConfigOpts opts; + opts.shadow_mode = true; + ext_authz_grpc_status_ = LoggingTestFilterConfig::PERMISSION_DENIED; + initializeConfig(opts); + + setDownstreamProtocol(Http::CodecType::HTTP1); + useAccessLog("%FILTER_STATE(envoy.filters.http.ext_authz.shadow:PLAIN)%"); + HttpIntegrationTest::initialize(); + + initiateClientConnection(0); + waitForExtAuthzRequest(expectedCheckRequest(Http::CodecType::HTTP1)); + + // Auth server denies the request. + ext_authz_request_->startGrpcStream(); + envoy::service::auth::v3::CheckResponse check_response; + check_response.mutable_status()->set_code(Grpc::Status::WellKnownGrpcStatus::PermissionDenied); + check_response.mutable_denied_response()->mutable_status()->set_code( + envoy::type::v3::StatusCode::Forbidden); + check_response.mutable_denied_response()->set_body("you shall not pass"); + ext_authz_request_->sendGrpcMessage(check_response); + ext_authz_request_->finishGrpcStream(Grpc::Status::Ok); + + // In shadow mode the request should reach the upstream despite the deny. + waitForSuccessfulUpstreamResponse("200"); + + // The shadow decision is exposed in the access log via FilterState. + const std::string log = waitForAccessLog(access_log_name_); + EXPECT_THAT(log, testing::HasSubstr("DENIED")); + EXPECT_THAT(log, testing::HasSubstr("403")); + + cleanup(); +} + // Regression test for https://github.com/envoyproxy/envoy/issues/17344 TEST(ExtConfigValidateTest, Validate) { Server::TestComponentFactory component_factory; diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index e0f0faeb2714a..9ba634d404048 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -6521,6 +6521,457 @@ TEST_F(HttpFilterTest, MultipleSetCookieHeadersOnSuccess) { EXPECT_EQ(1U, config_->stats().ok_.value()); } +// Shadow mode tests: when shadow_mode is enabled, the filter should never send a local reply. +// Instead it writes the authorization decision into FilterState and continues. + +namespace { +// The shadow filter state key is the filter's configured name with a ``.shadow`` suffix, +// distinct from the ExtAuthzLoggingInfo key which uses the bare filter config name. +constexpr absl::string_view kShadowFilterStateKey = "ext_authz_filter.shadow"; +} // namespace + +// Verify that in shadow mode a Denied response sets FilterState and continues (no local reply). +TEST_F(HttpFilterTest, ShadowModeDeniedSetsFilterStateAndContinues) { + InSequence s; + + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + shadow_mode: true + )EOF"); + + prepareCheck(); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + // The response flag should NOT be set in shadow mode. + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, + setResponseFlag(Envoy::StreamInfo::CoreResponseFlag::UnauthorizedExternalService)) + .Times(0); + + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::Denied; + response.status_code = Http::Code::Unauthorized; + response.body = "Access denied"; + response.headers_to_set = {{"x-auth-reason", "unauthorized"}}; + request_callbacks_->onComplete(std::make_unique(response)); + + const auto* shadow = + decoder_filter_callbacks_.streamInfo().filterState()->getDataReadOnly( + kShadowFilterStateKey); + ASSERT_NE(shadow, nullptr); + EXPECT_EQ(shadow->checkResult(), + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision::DENIED); + EXPECT_EQ(shadow->statusCode(), Http::Code::Unauthorized); + ASSERT_EQ(shadow->responseHeaders().size(), 1); + EXPECT_EQ(shadow->responseHeaders()[0].first, "x-auth-reason"); + EXPECT_EQ(shadow->responseHeaders()[0].second, "unauthorized"); + + // Exercise serializeAsProto (populates all non-empty branches) and serializeAsString. + auto serialized = shadow->serializeAsProto(); + ASSERT_NE(serialized, nullptr); + const auto& proto = + dynamic_cast( + *serialized); + EXPECT_EQ(proto.check_result(), + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision::DENIED); + EXPECT_EQ(proto.status_code(), 401); + ASSERT_EQ(proto.response_headers().size(), 1); + EXPECT_EQ(proto.response_headers()[0].key(), "x-auth-reason"); + EXPECT_EQ(proto.response_headers()[0].value(), "unauthorized"); + + // serializeAsString returns JSON — parse it back to a proto so the assertions are robust + // against MessageUtil JSON-option changes (whitespace, field ordering, etc.). + auto serialized_str = shadow->serializeAsString(); + ASSERT_TRUE(serialized_str.has_value()); + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision decoded_from_json; + TestUtility::loadFromJson(*serialized_str, decoded_from_json); + EXPECT_EQ(decoded_from_json.check_result(), + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision::DENIED); + EXPECT_EQ(decoded_from_json.status_code(), 401); + ASSERT_EQ(decoded_from_json.response_headers().size(), 1); + EXPECT_EQ(decoded_from_json.response_headers()[0].key(), "x-auth-reason"); + EXPECT_EQ(decoded_from_json.response_headers()[0].value(), "unauthorized"); + + // Field-level access for access-log formatters and CEL. + EXPECT_TRUE(shadow->hasFieldSupport()); + EXPECT_EQ(absl::get(shadow->getField("check_result")), "DENIED"); + EXPECT_EQ(absl::get(shadow->getField("status_code")), 401); + // Unknown field returns monostate. + EXPECT_TRUE(absl::holds_alternative(shadow->getField("unknown"))); + + EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); + // In shadow mode, denied stats are still incremented (the decision was deny). + EXPECT_EQ(1U, config_->stats().denied_.value()); + // Denied response headers should NOT be applied to the request (they are response-destined + // headers like WWW-Authenticate). They are available in FilterState instead. + EXPECT_EQ("", request_headers_.get_("x-auth-reason")); +} + +// Verify that in shadow mode an Error response sets FilterState and continues (no local reply). +TEST_F(HttpFilterTest, ShadowModeErrorSetsFilterStateAndContinues) { + InSequence s; + + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + shadow_mode: true + )EOF"); + + prepareCheck(); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, + setResponseFlag(Envoy::StreamInfo::CoreResponseFlag::UnauthorizedExternalService)) + .Times(0); + + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::Error; + response.body = "auth service error"; + request_callbacks_->onComplete(std::make_unique(response)); + + const auto* shadow = + decoder_filter_callbacks_.streamInfo().filterState()->getDataReadOnly( + kShadowFilterStateKey); + ASSERT_NE(shadow, nullptr); + EXPECT_EQ(shadow->checkResult(), + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision::ERROR); + // Default status_on_error is 403. + EXPECT_EQ(shadow->statusCode(), Http::Code::Forbidden); + + // getField exposes the ERROR enum name and the fallback status code. + EXPECT_EQ(absl::get(shadow->getField("check_result")), "ERROR"); + EXPECT_EQ(absl::get(shadow->getField("status_code")), 403); + + EXPECT_EQ(1U, config_->stats().shadow_error_.value()); + // In shadow mode, error stats are still incremented (the auth service returned an error). + EXPECT_EQ(1U, config_->stats().error_.value()); +} + +// Verify that in shadow mode an OK response sets FilterState and continues as normal. +TEST_F(HttpFilterTest, ShadowModeOkSetsFilterState) { + InSequence s; + + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + shadow_mode: true + )EOF"); + + prepareCheck(); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + request_callbacks_->onComplete(std::make_unique(response)); + + const auto* shadow = + decoder_filter_callbacks_.streamInfo().filterState()->getDataReadOnly( + kShadowFilterStateKey); + ASSERT_NE(shadow, nullptr); + EXPECT_EQ(shadow->checkResult(), + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision::OK); + // OK defaults to 200 so consumers see a populated status_code for every check_result. + EXPECT_EQ(shadow->statusCode(), Http::Code::OK); + + // Exercise serializeAsProto on the OK branch. + auto serialized = shadow->serializeAsProto(); + ASSERT_NE(serialized, nullptr); + const auto& proto = + dynamic_cast( + *serialized); + EXPECT_EQ(proto.check_result(), + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision::OK); + EXPECT_EQ(proto.status_code(), 200); + EXPECT_TRUE(proto.response_headers().empty()); + + EXPECT_EQ(absl::get(shadow->getField("check_result")), "OK"); + EXPECT_EQ(absl::get(shadow->getField("status_code")), 200); + + EXPECT_EQ(1U, config_->stats().ok_.value()); + EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); + EXPECT_EQ(0U, config_->stats().shadow_error_.value()); +} + +// Verify that in shadow mode + deny_at_disable, the filter sets FilterState and continues +// instead of sending a local reply. +TEST_F(HttpFilterTest, ShadowModeDenyAtDisable) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + shadow_mode: true + filter_enabled: + runtime_key: "http.ext_authz.enabled" + default_value: + numerator: 0 + denominator: HUNDRED + deny_at_disable: + runtime_key: "http.ext_authz.deny_at_disable" + default_value: + value: true + )EOF"); + + ON_CALL(factory_context_.runtime_loader_.snapshot_, + featureEnabled("http.ext_authz.enabled", + testing::Matcher(Percent(0)))) + .WillByDefault(Return(false)); + + ON_CALL(factory_context_.runtime_loader_.snapshot_, + featureEnabled("http.ext_authz.enabled", false)) + .WillByDefault(Return(true)); + + // Check should NOT be called since the filter is disabled. + EXPECT_CALL(*client_, check(_, _, _, _)).Times(0); + + // Response flag SHOULD be set even in shadow mode, so the access log reflects what enforce + // mode would have logged. + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, + setResponseFlag(Envoy::StreamInfo::CoreResponseFlag::UnauthorizedExternalService)); + + // Filter should continue, not stop. + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); + + const auto* shadow = + decoder_filter_callbacks_.streamInfo().filterState()->getDataReadOnly( + kShadowFilterStateKey); + ASSERT_NE(shadow, nullptr); + EXPECT_EQ(shadow->checkResult(), + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision::DENIED); + EXPECT_EQ(shadow->statusCode(), Http::Code::Forbidden); + + EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); + EXPECT_EQ(1U, config_->stats().disabled_.value()); +} + +// Verify that when shadow_mode is false (default), the filter sends local replies as before +// and does NOT write the shadow decision to FilterState. +TEST_F(HttpFilterTest, ShadowModeDisabledPreservesExistingBehaviour) { + InSequence s; + + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + )EOF"); + + prepareCheck(); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, + setResponseFlag(Envoy::StreamInfo::CoreResponseFlag::UnauthorizedExternalService)); + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()).Times(0); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::Denied; + response.status_code = Http::Code::Forbidden; + request_callbacks_->onComplete(std::make_unique(response)); + + // Local reply should have been sent — denied counter should be incremented, not shadow_denied. + EXPECT_EQ(1U, config_->stats().denied_.value()); + EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); + // FilterState should not contain a shadow decision when shadow_mode is disabled. + EXPECT_FALSE(decoder_filter_callbacks_.streamInfo().filterState()->hasData( + kShadowFilterStateKey)); +} + +// Verify that shadow mode works with the auth server's own dynamic_metadata alongside +// the shadow FilterState decision — the two coexist on different storage paths. +TEST_F(HttpFilterTest, ShadowModeDeniedWithAuthServerDynamicMetadata) { + InSequence s; + + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + shadow_mode: true + )EOF"); + + prepareCheck(); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + // The auth server's own dynamic_metadata is still emitted to dynamic metadata + // (this is existing behavior, independent of shadow mode). + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, setDynamicMetadata(_, _)) + .WillOnce( + Invoke([](const std::string& ns, const Protobuf::Struct& returned_dynamic_metadata) { + EXPECT_EQ(ns, "envoy.filters.http.ext_authz"); + EXPECT_EQ(returned_dynamic_metadata.fields().at("custom_key").string_value(), + "custom_value"); + })); + + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::Denied; + response.status_code = Http::Code::Forbidden; + (*response.dynamic_metadata.mutable_fields())["custom_key"] = + ValueUtil::stringValue("custom_value"); + request_callbacks_->onComplete(std::make_unique(response)); + + // Shadow decision is in FilterState, not dynamic metadata. + const auto* shadow = + decoder_filter_callbacks_.streamInfo().filterState()->getDataReadOnly( + kShadowFilterStateKey); + ASSERT_NE(shadow, nullptr); + EXPECT_EQ(shadow->checkResult(), + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision::DENIED); + + EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); +} + +// Verify that shadow_mode combined with failure_mode_allow on an Error response still +// continues the request and writes the ShadowDecision. Shadow mode short-circuits before +// the failure_mode_allow branch, so the failure_mode_allowed_ counter is NOT incremented. +TEST_F(HttpFilterTest, ShadowModeWithFailureModeAllowOnError) { + InSequence s; + + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + shadow_mode: true + failure_mode_allow: true + )EOF"); + + prepareCheck(); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + // Neither the failure_mode_allow response flag nor setResponseFlag should fire in the + // Error+shadow path. + EXPECT_CALL(decoder_filter_callbacks_.stream_info_, + setResponseFlag(Envoy::StreamInfo::CoreResponseFlag::UnauthorizedExternalService)) + .Times(0); + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::Error; + request_callbacks_->onComplete(std::make_unique(response)); + + const auto* shadow = + decoder_filter_callbacks_.streamInfo().filterState()->getDataReadOnly( + kShadowFilterStateKey); + ASSERT_NE(shadow, nullptr); + EXPECT_EQ(shadow->checkResult(), + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision::ERROR); + EXPECT_EQ(shadow->statusCode(), Http::Code::Forbidden); + + EXPECT_EQ(1U, config_->stats().shadow_error_.value()); + EXPECT_EQ(1U, config_->stats().error_.value()); + // Shadow mode short-circuits before the failure_mode_allow branch. + EXPECT_EQ(0U, config_->stats().failure_mode_allowed_.value()); +} + +// Verify that shadow_mode combined with with_request_body buffers the body, dispatches the +// auth check with the body included, and writes the ShadowDecision on Deny. +TEST_F(HttpFilterTest, ShadowModeWithRequestBody) { + InSequence s; + + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + shadow_mode: true + with_request_body: + max_request_bytes: 10 + allow_partial_message: true + )EOF"); + + ON_CALL(decoder_filter_callbacks_, connection()) + .WillByDefault(Return(OptRef{connection_})); + ON_CALL(decoder_filter_callbacks_, decodingBuffer()).WillByDefault(Return(&data_)); + ON_CALL(decoder_filter_callbacks_, addDecodedData(_, _)) + .WillByDefault(Invoke([&](Buffer::Instance& data, bool) { data_.add(data); })); + connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr_); + connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr_); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers_, false)); + + // Feed enough body to trigger the auth call (max_request_bytes=10 with allow_partial_message). + Buffer::OwnedImpl body("0123456789"); + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(body, true)); + + // Auth server denies — shadow mode must continue without a local reply. + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::Denied; + response.status_code = Http::Code::Forbidden; + request_callbacks_->onComplete(std::make_unique(response)); + + const auto* shadow = + decoder_filter_callbacks_.streamInfo().filterState()->getDataReadOnly( + kShadowFilterStateKey); + ASSERT_NE(shadow, nullptr); + EXPECT_EQ(shadow->checkResult(), + envoy::extensions::filters::http::ext_authz::v3::ShadowDecision::DENIED); + EXPECT_EQ(shadow->statusCode(), Http::Code::Forbidden); + EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); +} + } // namespace } // namespace ExtAuthz } // namespace HttpFilters From ba013701a75fff0a74c39799d25550a5c6ed1f99 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Sun, 19 Apr 2026 21:36:27 -0700 Subject: [PATCH 075/750] Update QUICHE from f4cb73412 to ba02ee8fc (#44517) https://github.com/google/quiche/compare/f4cb73412..ba02ee8fc ``` $ git log f4cb73412..ba02ee8fc --date=short --no-merges --format="%ad %al %s" 2026-04-16 martinduke Permanently cancel subgroups if the stream has STOP_SENDING. 2026-04-16 haoyuewang QUIC_BUG on the size of connection_state_map_. 2026-04-16 ianswett Move Bbr2ProbeBwMode::CyclePhase to bbr2_misc.h and rename it to ProbePhase. 2026-04-15 reubent Reset streams which surpass MAX_HEADER_LIST_SIZE 2026-04-15 ianswett Deprecate --gfe2_reloadable_flag_quic_send_connection_close_on_max_age. 2026-04-14 birenroy Enabling rolled out flags. 2026-04-14 martinduke Deprecate gfe2_reloadable_flag_quic_disconnect_early_exit. 2026-04-14 vasilvv Merge QuicStreamSendBufferBase into QuicStreamSendBufferInlining. ``` Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Ryan Hamilton --- bazel/deps.yaml | 2 +- bazel/external/quiche.BUILD | 18 ------------------ bazel/repository_locations.bzl | 4 ++-- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index abdfe24b6adf3..f0c75f945b6a6 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -296,7 +296,7 @@ quiche: project_name: "QUICHE" project_desc: "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols" project_url: "https://github.com/google/quiche" - release_date: "2026-04-14" + release_date: "2026-04-16" use_category: - controlplane - dataplane_core diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 53318f70a940b..885ddcbc8bfc4 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -3986,7 +3986,6 @@ envoy_quic_cc_library( ":quic_core_server_id_lib", ":quic_core_session_notifier_interface_lib", ":quic_core_stream_frame_data_producer_lib", - ":quic_core_stream_send_buffer_base_lib", ":quic_core_stream_send_buffer_inlining_lib", ":quic_core_stream_sequencer_buffer_lib", ":quic_core_types_lib", @@ -4064,22 +4063,6 @@ envoy_quic_cc_library( deps = [":quic_core_types_lib"], ) -envoy_quic_cc_library( - name = "quic_core_stream_send_buffer_base_lib", - srcs = ["quiche/quic/core/quic_stream_send_buffer_base.cc"], - hdrs = ["quiche/quic/core/quic_stream_send_buffer_base.h"], - deps = [ - ":quic_core_interval_lib", - ":quic_core_interval_set_lib", - ":quic_core_types_lib", - ":quic_platform_base", - ":quic_platform_bug_tracker", - ":quiche_common_mem_slice", - "@abseil-cpp//absl/strings", - "@abseil-cpp//absl/types:span", - ], -) - envoy_quic_cc_library( name = "quic_core_inlined_string_view_lib", hdrs = ["quiche/quic/core/quic_inlined_string_view.h"], @@ -4100,7 +4083,6 @@ envoy_quic_cc_library( ":quic_core_interval_deque_lib", ":quic_core_interval_lib", ":quic_core_interval_set_lib", - ":quic_core_stream_send_buffer_base_lib", ":quic_core_types_lib", ":quic_platform_base", ":quic_platform_bug_tracker", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index e603f2c71222c..85281d09f16d5 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -553,8 +553,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/simdutf/simdutf/releases/download/v{version}/singleheader.zip"], ), quiche = dict( - version = "f4cb73412317e1ec397f37ec8e218396dad18186", - sha256 = "5798004d460932ef454f38a341773eba614097e581cbaa50513a8a0a446921a8", + version = "ba02ee8fc1c0ab27d55f6bbb9e1ac7c9705c3e69", + sha256 = "670791b54c8f73eafbb25226b27132bad5d336b70f8543be9d39bbfd9dc7222c", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", ), From 87fb1821bee4dc89267d945632b3ba5d679f9069 Mon Sep 17 00:00:00 2001 From: Garima Uttam <130280997+garimauttam@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:10:33 +0530 Subject: [PATCH 076/750] oauth2: percent-encode '+' in token request body parameters (#43693) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `PercentEncoding::encode` in `oauth_client.cc` was missing `+` from its reserved charset (`":/=&?"`). In `application/x-www-form-urlencoded`, an unencoded `+` is interpreted as a space by the server — so OAuth2 credentials containing `+` (e.g. Azure AD / Entra ID client secrets) were silently mangled, resulting in `401 AADSTS7000215: Invalid client secret`. ### Why it matters Base64-encoded secrets commonly contain `+`. This caused silent, hard-to-debug auth failures with no indication the secret was being corrupted in transit. ### Change - `":/=&?"` → `":/=&?+"` across all 9 `PercentEncoding::encode` calls in `asyncGetAccessToken` and `asyncRefreshAccessToken` - Covers all auth types (`UrlEncodedBody`, `BasicAuth`, `TlsClientAuth`) and all parameters (`client_id`, `client_secret`, `redirect_uri`, `code_verifier`, `refresh_token`) ### Tests 3 new unit tests in `oauth_test.cc` assert `+` → `%2B` for `client_secret`, `client_id`, and `refresh_token`. Fixes #43686 --------- Signed-off-by: garima-uttam --- .../filters/http/oauth2/oauth_client.cc | 37 +++++++------ .../filters/http/oauth2/oauth_test.cc | 54 +++++++++++++++++++ 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/source/extensions/filters/http/oauth2/oauth_client.cc b/source/extensions/filters/http/oauth2/oauth_client.cc index f57f9c5c4b1eb..9e47fb38842ec 100644 --- a/source/extensions/filters/http/oauth2/oauth_client.cc +++ b/source/extensions/filters/http/oauth2/oauth_client.cc @@ -24,6 +24,8 @@ namespace HttpFilters { namespace Oauth2 { namespace { +constexpr absl::string_view WwwFormUrlEncodedReservedCharacters = ":/=&?+"; + constexpr const char* UrlBodyTemplateWithCredentialsForAuthCode = "grant_type=authorization_code&code={0}&client_id={1}&client_secret={2}&redirect_uri={3}&code_" "verifier={4}"; @@ -52,16 +54,18 @@ void OAuth2ClientImpl::asyncGetAccessToken(const std::string& auth_code, ASSERT(state_ == OAuthState::Idle); state_ = OAuthState::PendingAccessToken; - const auto encoded_cb_url = Http::Utility::PercentEncoding::encode(cb_url, ":/=&?"); + const auto encoded_cb_url = + Http::Utility::PercentEncoding::encode(cb_url, WwwFormUrlEncodedReservedCharacters); Http::RequestMessagePtr request = createPostRequest(); std::string body; switch (auth_type) { case AuthType::UrlEncodedBody: - body = fmt::format(UrlBodyTemplateWithCredentialsForAuthCode, auth_code, - Http::Utility::PercentEncoding::encode(client_id, ":/=&?"), - Http::Utility::PercentEncoding::encode(secret, ":/=&?"), encoded_cb_url, - code_verifier); + body = fmt::format( + UrlBodyTemplateWithCredentialsForAuthCode, auth_code, + Http::Utility::PercentEncoding::encode(client_id, WwwFormUrlEncodedReservedCharacters), + Http::Utility::PercentEncoding::encode(secret, WwwFormUrlEncodedReservedCharacters), + encoded_cb_url, code_verifier); break; case AuthType::BasicAuth: { const auto basic_auth_token = absl::StrCat(client_id, ":", secret); @@ -76,9 +80,10 @@ void OAuth2ClientImpl::asyncGetAccessToken(const std::string& auth_code, case AuthType::TlsClientAuth: // For mTLS, authentication is done via the client certificate in the TLS handshake. // No client_secret is sent in the request body or headers. - body = fmt::format(UrlBodyTemplateWithoutSecretForAuthCode, auth_code, - Http::Utility::PercentEncoding::encode(client_id, ":/=&?"), encoded_cb_url, - code_verifier); + body = fmt::format( + UrlBodyTemplateWithoutSecretForAuthCode, auth_code, + Http::Utility::PercentEncoding::encode(client_id, WwwFormUrlEncodedReservedCharacters), + encoded_cb_url, code_verifier); break; } @@ -99,10 +104,11 @@ void OAuth2ClientImpl::asyncRefreshAccessToken(const std::string& refresh_token, switch (auth_type) { case AuthType::UrlEncodedBody: - body = fmt::format(UrlBodyTemplateWithCredentialsForRefreshToken, - Http::Utility::PercentEncoding::encode(refresh_token, ":/=&?"), - Http::Utility::PercentEncoding::encode(client_id, ":/=&?"), - Http::Utility::PercentEncoding::encode(secret, ":/=&?")); + body = fmt::format( + UrlBodyTemplateWithCredentialsForRefreshToken, + Http::Utility::PercentEncoding::encode(refresh_token, WwwFormUrlEncodedReservedCharacters), + Http::Utility::PercentEncoding::encode(client_id, WwwFormUrlEncodedReservedCharacters), + Http::Utility::PercentEncoding::encode(secret, WwwFormUrlEncodedReservedCharacters)); break; case AuthType::BasicAuth: { const auto basic_auth_token = absl::StrCat(client_id, ":", secret); @@ -117,9 +123,10 @@ void OAuth2ClientImpl::asyncRefreshAccessToken(const std::string& refresh_token, case AuthType::TlsClientAuth: // For mTLS, authentication is done via the client certificate in the TLS handshake. // No client_secret is sent in the request body or headers. - body = fmt::format(UrlBodyTemplateWithoutSecretForRefreshToken, - Http::Utility::PercentEncoding::encode(refresh_token, ":/=&?"), - Http::Utility::PercentEncoding::encode(client_id, ":/=&?")); + body = fmt::format( + UrlBodyTemplateWithoutSecretForRefreshToken, + Http::Utility::PercentEncoding::encode(refresh_token, WwwFormUrlEncodedReservedCharacters), + Http::Utility::PercentEncoding::encode(client_id, WwwFormUrlEncodedReservedCharacters)); break; } diff --git a/test/extensions/filters/http/oauth2/oauth_test.cc b/test/extensions/filters/http/oauth2/oauth_test.cc index 79055a321956d..b4b839eda58be 100644 --- a/test/extensions/filters/http/oauth2/oauth_test.cc +++ b/test/extensions/filters/http/oauth2/oauth_test.cc @@ -695,6 +695,60 @@ TEST_F(OAuth2ClientTest, RequestAccessTokenRetryPolicy) { client_->asyncGetAccessToken("a", "b", "c", "d", "e"); } +TEST_F(OAuth2ClientTest, TestGetAccessTokenPlusInSecret) { + EXPECT_CALL(request_, cancel()).Times(testing::AnyNumber()); + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillRepeatedly( + Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + const std::string body = message->body().toString(); + EXPECT_NE(std::string::npos, body.find("client_secret=abc%2Bdef")); + EXPECT_EQ(std::string::npos, body.find("client_secret=abc+def")); + callbacks_.push_back(&cb); + return &request_; + })); + + client_->setCallbacks(*mock_callbacks_); + client_->asyncGetAccessToken("auth_code", "client_id", "abc+def", "http://cb", "verifier"); + EXPECT_EQ(1, callbacks_.size()); +} + +TEST_F(OAuth2ClientTest, TestGetAccessTokenPlusInClientId) { + EXPECT_CALL(request_, cancel()).Times(testing::AnyNumber()); + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillRepeatedly( + Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + const std::string body = message->body().toString(); + EXPECT_NE(std::string::npos, body.find("client_id=id%2Btest")); + EXPECT_EQ(std::string::npos, body.find("client_id=id+test")); + callbacks_.push_back(&cb); + return &request_; + })); + + client_->setCallbacks(*mock_callbacks_); + client_->asyncGetAccessToken("auth_code", "id+test", "secret", "http://cb", "verifier"); + EXPECT_EQ(1, callbacks_.size()); +} + +TEST_F(OAuth2ClientTest, TestRefreshAccessTokenPlusInRefreshToken) { + EXPECT_CALL(request_, cancel()).Times(testing::AnyNumber()); + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillRepeatedly( + Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + const std::string body = message->body().toString(); + EXPECT_NE(std::string::npos, body.find("refresh_token=tok%2Ben")); + EXPECT_EQ(std::string::npos, body.find("refresh_token=tok+en")); + callbacks_.push_back(&cb); + return &request_; + })); + + client_->setCallbacks(*mock_callbacks_); + client_->asyncRefreshAccessToken("tok+en", "client_id", "secret"); + EXPECT_EQ(1, callbacks_.size()); +} + } // namespace Oauth2 } // namespace HttpFilters } // namespace Extensions From 5386adb2f327d8f3849e07c5ebb8af2d005fb27b Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 20 Apr 2026 04:57:01 -0400 Subject: [PATCH 077/750] Add strict parsing of chunked encoding (#44319) Risk Level: low, disabled by default Testing: unit tests Docs Changes: n/a Release Notes: yes Platform Specific Features: no Runtime guard: envoy.reloadable_features.strict_chunk_parsing Signed-off-by: Yan Avlasov --- changelogs/current.yaml | 5 ++++ source/common/http/http1/balsa_parser.cc | 4 +++ source/common/runtime/runtime_features.cc | 2 ++ test/integration/integration_test.cc | 32 +++++++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 56d2f0e624afd..f02280a10e171 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -156,6 +156,11 @@ minor_behavior_changes: If previously constructing a ``StreamRateLimiter`` using ``max_buffered_data``, use ``StreamRateLimiter::simpleTokenBucket(limit_kbps, time_source)`` to initialize an equivalent ``TokenBucket``. +- area: http + change: | + Enable strict parsing of HTTP/1 chunked encoding. Strict parsing is disabled by default and can be + enabled by setting the ``envoy.reloadable_features.mcp_filter_use_new_metadata_namespace`` runtime + flag to ``false``. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/source/common/http/http1/balsa_parser.cc b/source/common/http/http1/balsa_parser.cc index 33fa63cf303da..583db79fbfd44 100644 --- a/source/common/http/http1/balsa_parser.cc +++ b/source/common/http/http1/balsa_parser.cc @@ -153,6 +153,10 @@ BalsaParser::BalsaParser(MessageType type, ParserCallbacks* connection, size_t m http_validation_policy.require_content_length_if_body_required = false; http_validation_policy.disallow_invalid_header_characters_in_response = true; http_validation_policy.disallow_lone_cr_in_chunk_extension = true; + + http_validation_policy.disallow_stray_data_after_chunk = + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_chunk_parsing"); + framer_.set_http_validation_policy(http_validation_policy); framer_.set_balsa_headers(&headers_); diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 38bd22731c7cd..d842f7b0cad18 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -229,6 +229,8 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_http2_use_oghttp2); FALSE_RUNTIME_GUARD(envoy_reloadable_features_dynamic_modules_strip_custom_stat_prefix); // TODO(haoyuewang): Flip true after prod testing. FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_disable_data_read_immediately); +// TODO(yavlasov): Flip to true after prod testing. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_strict_chunk_parsing); // Block of non-boolean flags. Use of int flags is deprecated. Do not add more. ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index b1e66223ebed6..68b09793930a2 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -941,6 +941,38 @@ TEST_P(IntegrationTest, TestSmuggling) { } } +TEST_P(IntegrationTest, TestInvalidChunkedEncoding) { + autonomous_upstream_ = true; + config_helper_.addRuntimeOverride("envoy.reloadable_features.strict_chunk_parsing", "true"); +#ifdef ENVOY_ENABLE_UHV + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif + config_helper_.disableDelayClose(); + initialize(); + std::string response; + const std::string request = "GET / HTTP/1.1\r\nHost: host\r\ntransfer-encoding: chunked\r\n\r\n" + "1\r\nAAAAAA\r\n0\r\n\r\nXXXXXX"; + sendRawHttpAndWaitForResponse(lookupPort("http"), request.c_str(), &response, false); + EXPECT_THAT(response, StartsWith("HTTP/1.1 400 Bad Request\r\n")); +} + +TEST_P(IntegrationTest, TestInvalidChunkedEncodingDefault) { + autonomous_upstream_ = true; +#ifdef ENVOY_ENABLE_UHV + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif + config_helper_.disableDelayClose(); + initialize(); + std::string response; + const std::string request = + "GET / HTTP/1.1\r\nHost: host\r\nConnection: close\r\ntransfer-encoding: chunked\r\n\r\n" + "1\r\nAAAAAA\r\n0\r\n\r\nXXXXXX"; + sendRawHttpAndWaitForResponse(lookupPort("http"), request.c_str(), &response, false); + EXPECT_THAT(response, StartsWith("HTTP/1.1 200 OK\r\n")); +} + TEST_P(IntegrationTest, TestInvalidTransferEncoding) { #ifdef ENVOY_ENABLE_UHV config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", From 1f97f06e996752dd6848e556156669a849383f48 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 20 Apr 2026 15:25:00 +0100 Subject: [PATCH 078/750] test/ext_proc: Increase sharding (#44530) as this can still flake in msan Signed-off-by: Ryan Northey --- test/extensions/filters/http/ext_proc/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 41223535a0b53..0d13c577869df 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -471,7 +471,7 @@ envoy_extension_cc_test( "envoy.filters.http.set_metadata", ], rbe_pool = "4core", - shard_count = 8, + shard_count = 12, tags = [ "cpu:3", "skip_on_windows", From 8438a4b9b52b048972d5e58159b9497f1e7a924d Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 20 Apr 2026 15:25:10 +0100 Subject: [PATCH 079/750] test/ws: Increase sharding further (#44529) as this can still flake in msan Signed-off-by: Ryan Northey --- test/integration/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/BUILD b/test/integration/BUILD index 415e5cc70c75a..f49f41eb67d93 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1694,7 +1694,7 @@ envoy_cc_test( name = "websocket_integration_test", srcs = ["websocket_integration_test.cc"], rbe_pool = "linux_x64_small", - shard_count = 4, + shard_count = 8, deps = [ ":http_protocol_integration_lib", ":websocket_integration_test_lib", From 1622bbc2cf9afecf244e921051da38e8207d3898 Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Mon, 20 Apr 2026 11:38:32 -0400 Subject: [PATCH 080/750] secret_provider: atomically replace thread-local-storage value (#44289) Commit Message: secret_provider: atomically replace thread-local-storage value Additional Description: Refactor the code to use the `set()` pattern that does an immutable atomic replacement of the thread-local-storage value rather than updating the pointed value contents. Risk Level: low Testing: N/A Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Adi Suissa-Peleg --- source/common/secret/secret_provider_impl.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/common/secret/secret_provider_impl.cc b/source/common/secret/secret_provider_impl.cc index e6c4ac8847f2e..9c32d5eb4ac92 100644 --- a/source/common/secret/secret_provider_impl.cc +++ b/source/common/secret/secret_provider_impl.cc @@ -47,8 +47,9 @@ absl::Status ThreadLocalGenericSecretProvider::update() { RETURN_IF_NOT_OK_REF(value_or_error.status()); value = std::move(value_or_error.value()); } - tls_->runOnAllThreads( - [value = std::move(value)](OptRef tls) { tls->value_ = value; }); + tls_->set([value = std::move(value)](Event::Dispatcher&) { + return std::make_shared(value); + }); return absl::OkStatus(); } From 42dc4931fd1b82dbe426242c9e83d03ec6379f48 Mon Sep 17 00:00:00 2001 From: Guy Daich Date: Mon, 20 Apr 2026 12:03:03 -0500 Subject: [PATCH 081/750] attributes: support peer_certificate (#43773) Commit Message: Added ``connection.peer_certificate`` and ``upstream.peer_certificate`` attributes. Additional Description: provides access to attributes that are useful for cert authorization scenarios in extensions that rely on attributes for access to this sort of stream info. Risk Level: Low Testing: unit tests Docs Changes: yes Release Notes: yes Platform Specific Features: No Optional Fixes #43599 --------- Signed-off-by: Guy Daich --- changelogs/current.yaml | 4 + .../arch_overview/advanced/attributes.rst | 2 + .../extensions/filters/common/expr/context.cc | 105 ++++++++++-------- .../extensions/filters/common/expr/context.h | 1 + .../filters/common/expr/context_test.cc | 65 +++++++++++ .../ext_proc/ext_proc_integration_test.cc | 4 + 6 files changed, 132 insertions(+), 49 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index f02280a10e171..420f83669e936 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1079,5 +1079,9 @@ new_features: change: | Promoted the dynamic modules HTTP filter (``envoy.filters.http.dynamic_modules``) from alpha to stable. +- area: attributes + change: | + Added ``connection.peer_certificate`` and ``upstream.peer_certificate`` attributes that provide the + PEM-encoded peer certificate for downstream and upstream TLS connections respectively. deprecated: diff --git a/docs/root/intro/arch_overview/advanced/attributes.rst b/docs/root/intro/arch_overview/advanced/attributes.rst index 6c842cd86338d..286caa3ea1549 100644 --- a/docs/root/intro/arch_overview/advanced/attributes.rst +++ b/docs/root/intro/arch_overview/advanced/attributes.rst @@ -123,6 +123,7 @@ RBAC): connection.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the downstream TLS connection connection.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the downstream TLS connection connection.sha256_peer_certificate_digest, string, SHA256 digest of the peer certificate in the downstream TLS connection if present + connection.peer_certificate, string, PEM-encoded peer certificate in the downstream TLS connection if present connection.transport_failure_reason, string, The transport failure reason e.g. certificate validation failed The following additional attributes are available upon the downstream connection termination: @@ -153,6 +154,7 @@ The following attributes are available once the upstream connection is establish upstream.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the upstream TLS connection upstream.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the upstream TLS connection upstream.sha256_peer_certificate_digest, string, SHA256 digest of the peer certificate in the upstream TLS connection if present + upstream.peer_certificate, string, PEM-encoded peer certificate in the upstream TLS connection if present upstream.local_address, string, The local address of the upstream connection upstream.transport_failure_reason, string, The upstream transport failure reason e.g. certificate validation failed upstream.request_attempt_count, uint, The count of upstream request attempts. A value of ‘0’ indicates that the request was never attempted upstream diff --git a/source/extensions/filters/common/expr/context.cc b/source/extensions/filters/common/expr/context.cc index 24074d773f58f..e5e8d851ee2e0 100644 --- a/source/extensions/filters/common/expr/context.cc +++ b/source/extensions/filters/common/expr/context.cc @@ -41,55 +41,62 @@ convertHeaderEntry(Protobuf::Arena& arena, // SSL Extractors implementation const SslExtractorsValues& SslExtractorsValues::get() { - CONSTRUCT_ON_FIRST_USE(SslExtractorsValues, - absl::flat_hash_map{ - {TLSVersion, - [](const Ssl::ConnectionInfo& info) -> absl::optional { - return CelValue::CreateString(&info.tlsVersion()); - }}, - {SubjectLocalCertificate, - [](const Ssl::ConnectionInfo& info) -> absl::optional { - return CelValue::CreateString(&info.subjectLocalCertificate()); - }}, - {SubjectPeerCertificate, - [](const Ssl::ConnectionInfo& info) -> absl::optional { - return CelValue::CreateString(&info.subjectPeerCertificate()); - }}, - {URISanLocalCertificate, - [](const Ssl::ConnectionInfo& info) -> absl::optional { - if (info.uriSanLocalCertificate().empty()) { - return {}; - } - return CelValue::CreateString(&info.uriSanLocalCertificate()[0]); - }}, - {URISanPeerCertificate, - [](const Ssl::ConnectionInfo& info) -> absl::optional { - if (info.uriSanPeerCertificate().empty()) { - return {}; - } - return CelValue::CreateString(&info.uriSanPeerCertificate()[0]); - }}, - {DNSSanLocalCertificate, - [](const Ssl::ConnectionInfo& info) -> absl::optional { - if (info.dnsSansLocalCertificate().empty()) { - return {}; - } - return CelValue::CreateString(&info.dnsSansLocalCertificate()[0]); - }}, - {DNSSanPeerCertificate, - [](const Ssl::ConnectionInfo& info) -> absl::optional { - if (info.dnsSansPeerCertificate().empty()) { - return {}; - } - return CelValue::CreateString(&info.dnsSansPeerCertificate()[0]); - }}, - {SHA256PeerCertificateDigest, - [](const Ssl::ConnectionInfo& info) -> absl::optional { - if (info.sha256PeerCertificateDigest().empty()) { - return {}; - } - return CelValue::CreateString(&info.sha256PeerCertificateDigest()); - }}}); + CONSTRUCT_ON_FIRST_USE( + SslExtractorsValues, + absl::flat_hash_map{ + {TLSVersion, + [](const Ssl::ConnectionInfo& info) -> absl::optional { + return CelValue::CreateString(&info.tlsVersion()); + }}, + {SubjectLocalCertificate, + [](const Ssl::ConnectionInfo& info) -> absl::optional { + return CelValue::CreateString(&info.subjectLocalCertificate()); + }}, + {SubjectPeerCertificate, + [](const Ssl::ConnectionInfo& info) -> absl::optional { + return CelValue::CreateString(&info.subjectPeerCertificate()); + }}, + {URISanLocalCertificate, + [](const Ssl::ConnectionInfo& info) -> absl::optional { + if (info.uriSanLocalCertificate().empty()) { + return {}; + } + return CelValue::CreateString(&info.uriSanLocalCertificate()[0]); + }}, + {URISanPeerCertificate, + [](const Ssl::ConnectionInfo& info) -> absl::optional { + if (info.uriSanPeerCertificate().empty()) { + return {}; + } + return CelValue::CreateString(&info.uriSanPeerCertificate()[0]); + }}, + {DNSSanLocalCertificate, + [](const Ssl::ConnectionInfo& info) -> absl::optional { + if (info.dnsSansLocalCertificate().empty()) { + return {}; + } + return CelValue::CreateString(&info.dnsSansLocalCertificate()[0]); + }}, + {DNSSanPeerCertificate, + [](const Ssl::ConnectionInfo& info) -> absl::optional { + if (info.dnsSansPeerCertificate().empty()) { + return {}; + } + return CelValue::CreateString(&info.dnsSansPeerCertificate()[0]); + }}, + {SHA256PeerCertificateDigest, + [](const Ssl::ConnectionInfo& info) -> absl::optional { + if (info.sha256PeerCertificateDigest().empty()) { + return {}; + } + return CelValue::CreateString(&info.sha256PeerCertificateDigest()); + }}, + {PeerCertificate, [](const Ssl::ConnectionInfo& info) -> absl::optional { + if (info.pemEncodedPeerCertificate().empty()) { + return {}; + } + return CelValue::CreateString(&info.pemEncodedPeerCertificate()); + }}}); } namespace { diff --git a/source/extensions/filters/common/expr/context.h b/source/extensions/filters/common/expr/context.h index faff1cdb60778..dbe3f05f3b846 100644 --- a/source/extensions/filters/common/expr/context.h +++ b/source/extensions/filters/common/expr/context.h @@ -71,6 +71,7 @@ constexpr absl::string_view URISanPeerCertificate = "uri_san_peer_certificate"; constexpr absl::string_view DNSSanLocalCertificate = "dns_san_local_certificate"; constexpr absl::string_view DNSSanPeerCertificate = "dns_san_peer_certificate"; constexpr absl::string_view SHA256PeerCertificateDigest = "sha256_peer_certificate_digest"; +constexpr absl::string_view PeerCertificate = "peer_certificate"; constexpr absl::string_view DownstreamTransportFailureReason = "transport_failure_reason"; // Source properties diff --git a/test/extensions/filters/common/expr/context_test.cc b/test/extensions/filters/common/expr/context_test.cc index e0f69363fd367..9c4fabd786933 100644 --- a/test/extensions/filters/common/expr/context_test.cc +++ b/test/extensions/filters/common/expr/context_test.cc @@ -584,6 +584,7 @@ TEST(Context, ConnectionAttributes) { info.setAttemptCount(upstream_request_attempt_count); EXPECT_CALL(*downstream_ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true)); + EXPECT_CALL(*upstream_ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true)); EXPECT_CALL(*upstream_host, address()).WillRepeatedly(Return(upstream_address)); EXPECT_CALL(*upstream_host, locality()).WillRepeatedly(ReturnRef(upstream_locality)); @@ -618,6 +619,38 @@ TEST(Context, ConnectionAttributes) { .WillRepeatedly(ReturnRef(peer_certificate_digest)); EXPECT_CALL(*upstream_ssl_info, sha256PeerCertificateDigest()) .WillRepeatedly(ReturnRef(peer_certificate_digest)); + const std::string peer_cert_pem = + "-----BEGIN CERTIFICATE-----\n" + "MIIEoTCCA4mgAwIBAgIUQRkh3sY/JN5+tu5NX3Tbyx0Y8mIwDQYJKoZIhvcNAQEL\n" + "BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM\n" + "DVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5n\n" + "aW5lZXJpbmcxEDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjQwNDA4MTA0MjUzWhcNMjYw\n" + "NDA4MTA0MjUzWjCBqDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx\n" + "FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsM\n" + "EEx5ZnQgRW5naW5lZXJpbmcxGzAZBgNVBAMMElRlc3QgRnJvbnRlbmQgVGVhbTEl\n" + "MCMGCSqGSIb3DQEJARYWZnJvbnRlbmQtdGVhbUBseWZ0LmNvbTCCASIwDQYJKoZI\n" + "hvcNAQEBBQADggEPADCCAQoCggEBAKfEnhbPuNbkPue6HWQS6TJK48my/JEh+3vb\n" + "HVjiaMKe9ERxXW19xfFXHBCaB4dRrVTxrKlS3XivQkTck1P99s2YkCvDYUns9B4o\n" + "mUnjj/mdVL0OPgdu5mfAmgKB5BqD2psSt117FzIT9AnXQ80pSpQHmDrC5ZSEYkqb\n" + "FAOU5QTp7AA5NJMB7ZKbgjeohehLwG92G8tk4ARgB1M/615sVdz3vlbOsa4VLDKS\n" + "UbgnGRNiQoVFzSUHQhb6cl+/hDtW2q5nBGiHW3zeYIdCM718XUPlOnOj45Y+2E0d\n" + "XVM3txLXJ0huWylitiCtK0jBpy7kSI7Ubcaw1LhWuYrwO6S8bdECAwEAAaOB8zCB\n" + "8DAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcD\n" + "AgYIKwYBBQUHAwEwdAYDVR0RBG0wa4Yfc3BpZmZlOi8vbHlmdC5jb20vZnJvbnRl\n" + "bmQtdGVhbYYYaHR0cDovL2Zyb250ZW5kLmx5ZnQuY29tgghseWZ0LmNvbYIMd3d3\n" + "Lmx5ZnQuY29thwQBAgMEhxAAAAABAAIAAwAAAAAAAAAEMB0GA1UdDgQWBBSS/zHJ\n" + "9Mtc3XtVgk7+VxF6kS1YDDAfBgNVHSMEGDAWgBQZ/nNEIOqmw8nxkUzZNY87irkj\n" + "AgDANBgkqhkiG9w0BAQsFAAOCAQEAnYBoTWYkhMMsr10lagEJOPMHK9EIz/h/W8Rc\n" + "r9DhREZA1+uEQrsFpzsqHhDqDEhjjmakU14VeNmTpZ+HUvDFY3YaAoZnXFYmg/6+\n" + "jtxLkzRjjtCIaEHRiiIS7xMw8wyhMcmoQY9mQNbyWonIVpykvYFf0h5fVo11BAv7\n" + "ELUKZeCqFJBifLdfME0cIub/PhoJfk/hM6X2lRUUe2wvtOP8Vd9wHfrzktJysSLI\n" + "TwHES7ftFo9+vYn5qM27PGW9TWPvCF2EFiUziqAoaZkP5YwiFEIY2N9uRFliXm1/\n" + "Jg3xZwtsjs+9jsVHQqKSUHivUR3s7NenUF8s3bOMtqkccaVcww==\n" + "-----END CERTIFICATE-----"; + EXPECT_CALL(*downstream_ssl_info, pemEncodedPeerCertificate()) + .WillRepeatedly(ReturnRef(peer_cert_pem)); + EXPECT_CALL(*upstream_ssl_info, pemEncodedPeerCertificate()) + .WillRepeatedly(ReturnRef(peer_cert_pem)); { auto value = connection[CelValue::CreateStringView(Undefined)]; @@ -751,6 +784,13 @@ TEST(Context, ConnectionAttributes) { EXPECT_EQ(peer_certificate_digest, value.value().StringOrDie().value()); } + { + auto value = connection[CelValue::CreateStringView(PeerCertificate)]; + EXPECT_TRUE(value.has_value()); + ASSERT_TRUE(value.value().IsString()); + EXPECT_EQ(peer_cert_pem, value.value().StringOrDie().value()); + } + { auto value = connection[CelValue::CreateStringView(ID)]; EXPECT_TRUE(value.has_value()); @@ -835,6 +875,13 @@ TEST(Context, ConnectionAttributes) { EXPECT_EQ(peer_certificate_digest, value.value().StringOrDie().value()); } + { + auto value = upstream[CelValue::CreateStringView(PeerCertificate)]; + EXPECT_TRUE(value.has_value()); + ASSERT_TRUE(value.value().IsString()); + EXPECT_EQ(peer_cert_pem, value.value().StringOrDie().value()); + } + { auto value = upstream[CelValue::CreateStringView(UpstreamLocalAddress)]; EXPECT_TRUE(value.has_value()); @@ -1255,12 +1302,15 @@ TEST(Context, ExtractSslInfoEmptyValues) { std::shared_ptr> upstream_host( new NiceMock()); auto downstream_ssl_info = std::make_shared>(); + auto upstream_ssl_info = std::make_shared>(); const std::string sni_name = "kittens.com"; info.downstream_connection_info_provider_->setRequestedServerName(sni_name); info.downstream_connection_info_provider_->setSslConnection(downstream_ssl_info); + info.upstreamInfo()->setUpstreamSslConnection(upstream_ssl_info); Protobuf::Arena arena; ConnectionWrapper connection(arena, info); + UpstreamWrapper upstream(arena, info); const std::string empty_str; EXPECT_CALL(*downstream_ssl_info, sha256PeerCertificateDigest()) .WillRepeatedly(ReturnRef(empty_str)); @@ -1269,6 +1319,21 @@ TEST(Context, ExtractSslInfoEmptyValues) { auto value = connection[CelValue::CreateStringView(SHA256PeerCertificateDigest)]; EXPECT_FALSE(value.has_value()); } + + // Test connection.peer_certificate when certificate is not presented + EXPECT_CALL(*downstream_ssl_info, pemEncodedPeerCertificate()) + .WillRepeatedly(ReturnRef(empty_str)); + { + auto value = connection[CelValue::CreateStringView(PeerCertificate)]; + EXPECT_FALSE(value.has_value()); + } + + // Test upstream.peer_certificate when certificate is not presented + EXPECT_CALL(*upstream_ssl_info, pemEncodedPeerCertificate()).WillRepeatedly(ReturnRef(empty_str)); + { + auto value = upstream[CelValue::CreateStringView(PeerCertificate)]; + EXPECT_FALSE(value.has_value()); + } } } // namespace diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index bd0d3a3099309..708b03d72fead 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -3831,6 +3831,8 @@ TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { proto_config_.mutable_request_attributes()->Add("request.size"); // tests int64 proto_config_.mutable_request_attributes()->Add("connection.mtls"); // tests bool proto_config_.mutable_request_attributes()->Add("connection.id"); // tests uint64 + proto_config_.mutable_request_attributes()->Add( + "connection.peer_certificate"); // tests string, not present without TLS proto_config_.mutable_request_attributes()->Add("response.code"); proto_config_.mutable_response_attributes()->Add("response.code"); // tests int64 proto_config_.mutable_response_attributes()->Add("response.code_details"); @@ -3855,6 +3857,8 @@ TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { EXPECT_EQ(proto_struct.fields().at("request.size").number_value(), 0); EXPECT_EQ(proto_struct.fields().at("connection.mtls").bool_value(), false); EXPECT_TRUE(proto_struct.fields().at("connection.id").has_number_value()); + // connection.peer_certificate is not present without TLS + EXPECT_FALSE(proto_struct.fields().contains("connection.peer_certificate")); // Make sure we did not include the attribute which was not yet available. EXPECT_EQ(proto_struct.fields().size(), 6); EXPECT_FALSE(proto_struct.fields().contains("response.code")); From 7c7fa8c416d668e3e1919cab17570b36ea7c1efc Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 20 Apr 2026 18:51:35 +0100 Subject: [PATCH 082/750] deps: Bump `envoy_examples` -> 0.2.3 (#44536) Signed-off-by: Ryan Northey --- docs/bazel/deps.yaml | 2 +- docs/bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/bazel/deps.yaml b/docs/bazel/deps.yaml index 92d39203e44fe..32e3ec6c66ad8 100644 --- a/docs/bazel/deps.yaml +++ b/docs/bazel/deps.yaml @@ -2,7 +2,7 @@ envoy_examples: project_name: "envoy-examples" project_desc: "Envoy proxy examples" project_url: "https://github.com/envoyproxy/examples" - release_date: "2026-04-02" + release_date: "2026-04-20" use_category: - test_only license: "Apache-2.0" diff --git a/docs/bazel/repository_locations.bzl b/docs/bazel/repository_locations.bzl index 96c371524dca4..bc9b85003645a 100644 --- a/docs/bazel/repository_locations.bzl +++ b/docs/bazel/repository_locations.bzl @@ -1,7 +1,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( envoy_examples = dict( - version = "0.2.2", - sha256 = "865726ea8c02dc7418f75d6aecf17ccb61ac4fad7a6a73649220e7520f6d0f8d", + version = "0.2.3", + sha256 = "a0fc7991a346333c1e3d17ab6d8a68bc3d0a4e76a6f6adf83b51eab9e7103bd8", strip_prefix = "examples-{version}", urls = ["https://github.com/envoyproxy/examples/archive/v{version}.tar.gz"], ), From 46454e3a2141dc6b2ca72609e12b14318bb6e42d Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 20 Apr 2026 20:23:38 +0100 Subject: [PATCH 083/750] test: Attempt to fix `ManyLargeResponseHeadersAccepted` flake (#44511) Hopefully fix: #44416 --------- Signed-off-by: Ryan Northey --- test/integration/http_integration.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 66f9b9141ce3b..89178b76d57f9 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -1464,6 +1464,14 @@ void HttpIntegrationTest::testLargeResponseHeaders(uint32_t size, uint32_t count // exceed `size` due to the keys and other headers. The actual request header count will exceed // `count` by four due to default headers. + config_helper_.addConfigModifier([](envoy::extensions::filters::network::http_connection_manager:: + v3::HttpConnectionManager& hcm) -> void { + // Disable route timeout to prevent 504 on slow CI (#44416). + auto* route = + hcm.mutable_route_config()->mutable_virtual_hosts(0)->mutable_routes(0)->mutable_route(); + route->mutable_timeout()->set_seconds(0); + }); + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { ConfigHelper::HttpProtocolOptions protocol_options; auto* http_protocol_options = protocol_options.mutable_common_http_protocol_options(); From cec0e62d7b69efe623e06652b44fd553de4f0317 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 19:31:56 +0000 Subject: [PATCH 084/750] deps/api: Bump `envoy_toolshed` -> 0.3.33 (#44537) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- api/bazel/deps.yaml | 2 +- api/bazel/repository_locations.bzl | 4 ++-- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/bazel/deps.yaml b/api/bazel/deps.yaml index a22d1b753c853..4ba113c620d46 100644 --- a/api/bazel/deps.yaml +++ b/api/bazel/deps.yaml @@ -73,7 +73,7 @@ envoy_toolshed: project_name: "envoy_toolshed" project_desc: "Tooling, libraries, runners and checkers for Envoy proxy's CI" project_url: "https://github.com/envoyproxy/toolshed" - release_date: "2026-04-06" + release_date: "2026-04-20" use_category: - build - controlplane diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 7d0d8744e2f0c..29910ab6953fd 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -74,8 +74,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/google/cel-spec/archive/v{version}.tar.gz"], ), envoy_toolshed = dict( - version = "0.3.32", - sha256 = "c97a787075422f9748ff3cf28eb160b8ef8b17e27c44083205ed6473d3945ae9", + version = "0.3.33", + sha256 = "0d35c07b06033cebf50c767b0acdd31f215035785da4ecb9fbc02c23f2d53805", strip_prefix = "toolshed-bazel-v{version}", urls = ["https://github.com/envoyproxy/toolshed/releases/download/bazel-v{version}/toolshed-bazel-v{version}.tar.gz"], ), diff --git a/bazel/deps.yaml b/bazel/deps.yaml index f0c75f945b6a6..58f129d5af702 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -799,7 +799,7 @@ envoy_toolshed: project_name: "envoy_toolshed" project_desc: "Tooling, libraries, runners and checkers for Envoy proxy's CI" project_url: "https://github.com/envoyproxy/toolshed" - release_date: "2026-04-06" + release_date: "2026-04-20" use_category: - build - controlplane diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 85281d09f16d5..2cc07f4ba1755 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -46,8 +46,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/bazelbuild/buildtools/archive/v{version}.tar.gz"], ), envoy_toolshed = dict( - version = "0.3.32", - sha256 = "c97a787075422f9748ff3cf28eb160b8ef8b17e27c44083205ed6473d3945ae9", + version = "0.3.33", + sha256 = "0d35c07b06033cebf50c767b0acdd31f215035785da4ecb9fbc02c23f2d53805", strip_prefix = "toolshed-bazel-v{version}", urls = ["https://github.com/envoyproxy/toolshed/releases/download/bazel-v{version}/toolshed-bazel-v{version}.tar.gz"], ), From d5d35ddf72aaa86722ea969ad0461fd4eccdd6bf Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Mon, 20 Apr 2026 18:33:35 -0400 Subject: [PATCH 085/750] ci: remove ENVOY_RBE env var, auto-detect RBE from bazel config (#44539) Instead of requiring users to set ENVOY_RBE=1 when using remote execution, detect it automatically by checking if `--config=rbe` is present in BAZEL_BUILD_EXTRA_OPTIONS. This avoids an issue where forgetting to set ENVOY_RBE causes --test_tmpdir to point to a local path that doesn't exist on remote executors. --------- Signed-off-by: Jonh Wendell Co-authored-by: phlax --- ci/build_setup.sh | 11 +++++++++-- ci/docker-compose.yml | 1 - 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ci/build_setup.sh b/ci/build_setup.sh index c91f8864ebccc..4bd4b6fe9e10d 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -115,11 +115,18 @@ BAZEL_BUILD_OPTIONS=( "${BAZEL_BUILD_EXTRA_OPTIONS[@]}" "${BAZEL_EXTRA_TEST_OPTIONS[@]}") - [[ "${ENVOY_BUILD_ARCH}" == "aarch64" ]] && BAZEL_BUILD_OPTIONS+=( "--test_env=HEAPCHECK=") -if [[ -z "${ENVOY_RBE}" ]]; then +rc_output=$(bazel "${BAZEL_STARTUP_OPTIONS[@]}" info --announce_rc "${BAZEL_BUILD_OPTIONS[@]}" 2>&1) || { + echo "bazel info failed:" >&2 + echo "$rc_output" >&2 + exit 1 +} +if grep -q "remote_executor" <<<"$rc_output"; then + + echo "Remote execution detected, not setting test_tmpdir." +else BAZEL_BUILD_OPTIONS+=("--test_tmpdir=${ENVOY_TEST_TMPDIR}") echo "Setting test_tmpdir to ${ENVOY_TEST_TMPDIR}." fi diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index 20151e3967896..265acca8c7204 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -49,7 +49,6 @@ x-envoy-build-base: &envoy-build-base # Build configuration - NUM_CPUS - ENVOY_BRANCH - - ENVOY_RBE - ENVOY_BUILD_IMAGE - ENVOY_DOCS_PATH - ENVOY_SRCDIR From 49c6a5818893d496ac88e76dc726a4a83834faf2 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Tue, 21 Apr 2026 10:02:06 +0200 Subject: [PATCH 086/750] dfp: cap DNS failure backoff to bound eviction delay after a race condition (#44542) Today, when `touch()` lands just before `onReResolveAlarm` fires, the eviction check is `false` by a narrow margin, so `startResolve` runs instead of removing the host. If DNS then fails, the next re-resolve alarm gets armed at the full failure backoff which can exceed the Host TTL by a large margin and leaves a stale host alive for a full backoff cycle past its TTL. This change caps the failure-path refresh interval such that the next alarm fires when the host is eligible for eviction. If the host is already past its TTL, the next alarm is scheduled one millisecond out so `onReResolveAlarm` can evict without further delay. The success path is unchanged where a successful resolve already reset the backoff and the normal DNS TTL cadence is used. Signed-off-by: Rohit Agrawal --- .../dynamic_forward_proxy/dns_cache_impl.cc | 22 +++++-- .../dns_cache_impl_test.cc | 60 +++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc index e245ce66e792d..9cbcdb8ecde12 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc @@ -562,11 +562,25 @@ void DnsCacheImpl::finishResolve(const std::string& host, dns_ttl.count() * 1000); } else { if (!config_.disable_dns_refresh_on_failure()) { - const uint64_t refresh_interval = - primary_host_info->failure_backoff_strategy_->nextBackOffMs(); - primary_host_info->refresh_timer_->enableTimer(std::chrono::milliseconds(refresh_interval)); + // Cap the failure backoff so the next re-resolve alarm fires no later than when the host + // becomes eligible for eviction. Without this cap, a touch() that lands just before + // onReResolveAlarm leaves the host active and schedules the next check at the full backoff, + // which can exceed host_ttl by a large margin. + const auto now = main_thread_dispatcher_.timeSource().monotonicTime().time_since_epoch(); + const auto elapsed = now - primary_host_info->host_info_->lastUsedTime(); + std::chrono::milliseconds refresh_interval( + primary_host_info->failure_backoff_strategy_->nextBackOffMs()); + if (elapsed >= host_ttl_) { + refresh_interval = std::chrono::milliseconds(1); + } else { + const auto until_eviction = + std::chrono::duration_cast(host_ttl_ - elapsed) + + std::chrono::milliseconds(1); + refresh_interval = std::min(refresh_interval, until_eviction); + } + primary_host_info->refresh_timer_->enableTimer(refresh_interval); ENVOY_LOG(debug, "DNS refresh rate reset for host '{}', (failure) refresh rate {} ms", host, - refresh_interval); + refresh_interval.count()); } } } diff --git a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc index 84300d45d313b..882388daa5edc 100644 --- a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc +++ b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc @@ -867,6 +867,66 @@ TEST_F(DnsCacheImplTest, ResolveFailureAfterResolveSuccess) { 1 /* added */, 1 /* removed */, 0 /* num hosts */); } +TEST_F(DnsCacheImplTest, ResolveFailureBackoffCappedByHostTtl) { + *config_.mutable_host_ttl() = Protobuf::util::TimeUtil::SecondsToDuration(1); + *config_.mutable_dns_refresh_rate() = Protobuf::util::TimeUtil::SecondsToDuration(60); + *config_.mutable_dns_min_refresh_rate() = Protobuf::util::TimeUtil::SecondsToDuration(1); + initialize(); + InSequence s; + + MockLoadDnsCacheEntryCallbacks callbacks; + Network::DnsResolver::ResolveCb resolve_cb; + Event::MockTimer* resolve_timer = new Event::MockTimer(&context_.server_context_.dispatcher_); + Event::MockTimer* timeout_timer = new Event::MockTimer(&context_.server_context_.dispatcher_); + EXPECT_CALL(*timeout_timer, enableTimer(std::chrono::milliseconds(5000), nullptr)); + EXPECT_CALL(*resolver_, resolve("foo.com", _, _)) + .WillOnce(DoAll(SaveArg<2>(&resolve_cb), Return(&resolver_->active_query_))); + auto result = dns_cache_->loadDnsCacheEntry("foo.com", 80, false, callbacks); + EXPECT_EQ(DnsCache::LoadDnsCacheEntryStatus::Loading, result.status_); + + EXPECT_CALL(*timeout_timer, disableTimer()); + EXPECT_CALL( + update_callbacks_, + onDnsHostAddOrUpdate("foo.com:80", DnsHostInfoEquals("10.0.0.1:80", "foo.com", false))); + EXPECT_CALL(callbacks, + onLoadDnsCacheComplete(DnsHostInfoEquals("10.0.0.1:80", "foo.com", false))); + EXPECT_CALL(update_callbacks_, + onDnsResolutionComplete("foo.com:80", + DnsHostInfoEquals("10.0.0.1:80", "foo.com", false), + Network::DnsResolver::ResolutionStatus::Completed)); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(dns_ttl_), _)); + resolve_cb(Network::DnsResolver::ResolutionStatus::Completed, "", + TestUtility::makeDnsResponse({"10.0.0.1"})); + + // touch() at T+900ms, then the alarm fires at T+1000ms: now-last_used=100ms < host_ttl=1000ms, + // so startResolve runs. + simTime().advanceTimeWait(std::chrono::milliseconds(900)); + dns_cache_->getHost("foo.com:80").value()->touch(); + simTime().advanceTimeWait(std::chrono::milliseconds(100)); + + EXPECT_CALL(*timeout_timer, enableTimer(std::chrono::milliseconds(5000), nullptr)); + EXPECT_CALL(*resolver_, resolve("foo.com", _, _)) + .WillOnce(DoAll(SaveArg<2>(&resolve_cb), Return(&resolver_->active_query_))); + resolve_timer->invokeCallback(); + + // DNS fails. The raw failure backoff is 60s (dns_refresh_rate). Without the cap the next alarm + // would fire at T+61s. With the cap, it fires at host_ttl-elapsed+1ms = 1000-100+1 = 901ms. + EXPECT_CALL(*timeout_timer, disableTimer()); + EXPECT_CALL(update_callbacks_, + onDnsResolutionComplete("foo.com:80", + DnsHostInfoEquals("10.0.0.1:80", "foo.com", false), + Network::DnsResolver::ResolutionStatus::Failure)); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(901), _)); + resolve_cb(Network::DnsResolver::ResolutionStatus::Failure, "", TestUtility::makeDnsResponse({})); + + // After the capped backoff now-last_used = 1001ms > host_ttl: the host is evicted. + simTime().advanceTimeWait(std::chrono::milliseconds(901)); + EXPECT_CALL(update_callbacks_, onDnsHostRemove("foo.com:80")); + resolve_timer->invokeCallback(); + checkStats(2 /* attempt */, 1 /* success */, 1 /* failure */, 1 /* address changed */, + 1 /* added */, 1 /* removed */, 0 /* num hosts */); +} + TEST_F(DnsCacheImplTest, DisableRefreshOnFailureContainsFailedHost) { config_.set_disable_dns_refresh_on_failure(true); From 882e12d28520ab5705064be744d48f0ebd10b370 Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Tue, 21 Apr 2026 10:36:07 +0200 Subject: [PATCH 087/750] [geop/http] Promote to stable (#44544) Commit Message: Promote http geoip filter to stable Additional Description: Followup for https://github.com/envoyproxy/envoy/pull/44483. Http geoip filter has sufficient production burn time and has been used by many companies. Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Kateryna Nezdolii --- changelogs/current.yaml | 4 ++++ source/extensions/extensions_metadata.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 420f83669e936..48d99c16aea87 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -2,6 +2,10 @@ date: Pending behavior_changes: # *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* +- area: geoip + change: | + :ref:`Envoy Http Geoip filter extension + ` has been promoted to stable status. - area: tcp_proxy change: | The TCP proxy filter now requires :ref:`max_early_data_bytes diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 9ea3bf9e9fcdc..44611700b2358 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -390,7 +390,7 @@ envoy.filters.http.geoip: categories: - envoy.filters.http security_posture: unknown - status: alpha + status: stable type_urls: - envoy.extensions.filters.http.geoip.v3.Geoip envoy.filters.network.geoip: From 62743d068ab49a5fbea6f278ba1ce9cea99fafe2 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:10:13 +0100 Subject: [PATCH 088/750] deps: Bump `wasmtime` -> 24.0.7 (#44549) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 58f129d5af702..37b390eb71c73 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -548,7 +548,7 @@ wasmtime: project_name: "wasmtime" project_desc: "A standalone runtime for WebAssembly" project_url: "https://github.com/bytecodealliance/wasmtime" - release_date: "2025-07-18" + release_date: "2026-04-09" use_category: - dataplane_ext extensions: diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 2cc07f4ba1755..8354d492753bd 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -503,8 +503,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/bytecodealliance/wasm-micro-runtime/archive/{version}.tar.gz"], ), wasmtime = dict( - version = "24.0.4", - sha256 = "d714d987a50cfc7d0b384ef4720e7c757cf4f5b7df617cbf38e432a3dc6c400d", + version = "24.0.7", + sha256 = "2cf72451e5031d8072623c4c3003699ee65ccc94edfab1d8b664744a8c611d4f", strip_prefix = "wasmtime-{version}", urls = ["https://github.com/bytecodealliance/wasmtime/archive/v{version}.tar.gz"], ), From ee031bc88ec10281cc0d2b6ec1cebc7054bda0bd Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 21 Apr 2026 12:29:54 +0100 Subject: [PATCH 089/750] deps/fips: Bump `go` -> 1.26.2 (#44547) Fix multiple CVEs and update to latest Signed-off-by: Ryan Northey --- bazel/repository_locations.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 8354d492753bd..31f769a2f3b24 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -720,14 +720,14 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/Kitware/CMake/releases/download/v{version}/cmake-{version}-linux-aarch64.tar.gz"], ), fips_go_linux_amd64 = dict( - version = "1.24.4", - sha256 = "77e5da33bb72aeaef1ba4418b6fe511bc4d041873cbf82e5aa6318740df98717", + version = "1.26.2", + sha256 = "990e6b4bbba816dc3ee129eaeaf4b42f17c2800b88a2166c265ac1a200262282", strip_prefix = "go", urls = ["https://dl.google.com/go/go{version}.linux-amd64.tar.gz"], ), fips_go_linux_arm64 = dict( - version = "1.24.4", - sha256 = "d5501ee5aca0f258d5fe9bfaed401958445014495dc115f202d43d5210b45241", + version = "1.26.2", + sha256 = "c958a1fe1b361391db163a485e21f5f228142d6f8b584f6bef89b26f66dc5b23", strip_prefix = "go", urls = ["https://dl.google.com/go/go{version}.linux-arm64.tar.gz"], ), From cd756278c1bd5c945baf7f076e076b78e003bfce Mon Sep 17 00:00:00 2001 From: yanjunxiang-google <78807980+yanjunxiang-google@users.noreply.github.com> Date: Tue, 21 Apr 2026 08:01:48 -0400 Subject: [PATCH 090/750] Avoid return continue in ext_proc STREAMED mode data processing (#44389) Fixes https://github.com/envoyproxy/envoy/issues/44101 This PR implements the solution options 1 listed in the above issue. It changes decodeData() for STREAMED/FULL_DUPLEX_STREAMED mode from "return Continue" to "return StopIterationNoBuffer". Thus no empty chunks are being process in the rest of the filter chain. This also avoids the exponential growth of the gRPC calls carrying such ext_proc created empty chunks with EoS=false. --------- Signed-off-by: Yanjun Xiang --- changelogs/current.yaml | 5 + source/common/runtime/runtime_features.cc | 1 + .../filters/http/ext_proc/processor_state.cc | 6 +- .../http/ext_proc/filter_full_duplex_test.cc | 12 +- .../filters/http/ext_proc/filter_test.cc | 139 ++++++++++++++---- .../http/ext_proc/filter_test_common.cc | 2 + 6 files changed, 128 insertions(+), 37 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 48d99c16aea87..fffdb72448d34 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -291,6 +291,11 @@ bug_fixes: intentionally not parseable (for example ``goo.gle/debugonly`` prefixes). This change can be reverted by setting runtime guard ``envoy.reloadable_features.cel_message_serialize_text_format`` to ``false``. +- area: ext_proc + change: | + Fixed a bug that unnecessary empty data chunks are processed by the filter chain. This + change can be reverted by setting the runtime guard + ``envoy.reloadable_features.ext_proc_return_stop_iteration`` to ``false``. - area: ratelimit change: | Fixed a bug in the gRPC rate limit client where the client could get into a bad state if the diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index d842f7b0cad18..b7f9406d197dc 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -53,6 +53,7 @@ RUNTIME_GUARD(envoy_reloadable_features_ext_authz_http_client_retries_respect_us // @yanjunxiang-google before removing. RUNTIME_GUARD(envoy_reloadable_features_ext_proc_fail_close_spurious_resp); RUNTIME_GUARD(envoy_reloadable_features_ext_proc_inject_data_with_state_update); +RUNTIME_GUARD(envoy_reloadable_features_ext_proc_return_stop_iteration); RUNTIME_GUARD(envoy_reloadable_features_ext_proc_stream_close_optimization); RUNTIME_GUARD(envoy_reloadable_features_fix_http3_early_data_timing); RUNTIME_GUARD(envoy_reloadable_features_generic_proxy_codec_buffer_limit); diff --git a/source/extensions/filters/http/ext_proc/processor_state.cc b/source/extensions/filters/http/ext_proc/processor_state.cc index 46916dba7dbff..91c86d5089910 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.cc +++ b/source/extensions/filters/http/ext_proc/processor_state.cc @@ -700,8 +700,12 @@ bool ProcessorState::isLastResponseAfterBodyResp(bool eos_seen_in_body) const { return false; } +// Return the status of onData() call for STREAMED and FULL_DUPLEX_STREAMED mode. Http::FilterDataStatus ProcessorState::getBodyCallbackResultInStreamedMode(bool end_stream) { - if (end_stream || callbackState() == ProcessorState::CallbackState::HeadersCallback) { + const bool return_stop_iteration = + (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.ext_proc_return_stop_iteration") || + end_stream || callbackState() == ProcessorState::CallbackState::HeadersCallback); + if (return_stop_iteration) { setPaused(true); return Http::FilterDataStatus::StopIterationNoBuffer; } diff --git a/test/extensions/filters/http/ext_proc/filter_full_duplex_test.cc b/test/extensions/filters/http/ext_proc/filter_full_duplex_test.cc index acd05cc7f781c..d6e9ca41afebc 100644 --- a/test/extensions/filters/http/ext_proc/filter_full_duplex_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_full_duplex_test.cc @@ -130,7 +130,7 @@ TEST_F(HttpFilterTest, DuplexStreamedBodyProcessingTestNormal) { // 7 request chunks are sent to the ext_proc server. Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); } processResponseBodyHelper(" AAAAA ", want_response_body); @@ -145,7 +145,7 @@ TEST_F(HttpFilterTest, DuplexStreamedBodyProcessingTestNormal) { for (int i = 0; i < 3; i++) { Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); processResponseBodyHelper(std::to_string(i), want_response_body); } @@ -157,7 +157,7 @@ TEST_F(HttpFilterTest, DuplexStreamedBodyProcessingTestNormal) { for (int i = 0; i < 10; i++) { Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 10); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); } // Send the last chunk. Buffer::OwnedImpl last_resp_chunk; @@ -222,7 +222,7 @@ TEST_F(HttpFilterTest, DuplexStreamedBodyProcessingTestWithTrailer) { // 7 request chunks are sent to the ext_proc server. Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); } EXPECT_EQ(FilterTrailersStatus::StopIteration, filter_->encodeTrailers(response_trailers_)); @@ -374,7 +374,7 @@ TEST_F(HttpFilterTest, DuplexStreamedBodyProcessingTestWithFilterConfigMissing) // 4 request chunks are sent to the ext_proc server. Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); } processResponseBody( @@ -489,7 +489,7 @@ TEST_F(HttpFilterTest, FullDuplexFailCloseWithDataInbound) { EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); processRequestHeaders(false, absl::nullopt); Buffer::OwnedImpl req_data("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, false)); // Fail close with gRPC error messages received. stream_callbacks_->onGrpcError(Grpc::Status::Internal, "error_message"); diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index 2e2a0d1ae0fab..469b1f67a455a 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -107,6 +107,8 @@ class HttpFilterTest : public testing::Test { {{"envoy.reloadable_features.ext_proc_stream_close_optimization", "true"}}); scoped_runtime_.mergeValues( {{"envoy.reloadable_features.ext_proc_inject_data_with_state_update", "true"}}); + scoped_runtime_.mergeValues( + {{"envoy.reloadable_features.ext_proc_return_stop_iteration", "true"}}); client_ = std::make_unique(); route_ = std::make_shared>(); EXPECT_CALL(*client_, start(_, _, _, _)).WillOnce(Invoke(this, &HttpFilterTest::doStart)); @@ -570,8 +572,10 @@ class HttpFilterTest : public testing::Test { void sendChunkRequestData(const uint32_t chunk_number, const bool send_grpc) { for (uint32_t i = 0; i < chunk_number; i++) { Buffer::OwnedImpl req_data("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); - if (send_grpc) { + if (!send_grpc) { + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + } else { + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, false)); processRequestBody(absl::nullopt, false); } } @@ -580,8 +584,10 @@ class HttpFilterTest : public testing::Test { void sendChunkResponseData(const uint32_t chunk_number, const bool send_grpc) { for (uint32_t i = 0; i < chunk_number; i++) { Buffer::OwnedImpl resp_data("bar"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); - if (send_grpc) { + if (!send_grpc) { + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + } else { + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_data, false)); processResponseBody(absl::nullopt, false); } } @@ -620,7 +626,7 @@ class HttpFilterTest : public testing::Test { uint32_t chunk_number = 3; for (uint32_t i = 0; i < chunk_number; i++) { Buffer::OwnedImpl resp_data(std::to_string(i)); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_data, false)); processResponseBody( [i, &want_response_body](const HttpBody& body, ProcessingResponse&, BodyResponse& resp) { auto* body_mut = resp.mutable_response()->mutable_body_mutation(); @@ -1788,7 +1794,7 @@ TEST_F(HttpFilterTest, StreamingSendRequestDataGrpcFail) { const uint32_t chunk_number = 20; sendChunkRequestData(chunk_number, true); // When sends one more chunk of data, gRPC call fails. - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, false)); test_time_->advanceTimeWait(std::chrono::microseconds(10)); // Oh no! The remote server had a failure! TestResponseHeaderMapImpl immediate_response_headers; @@ -1847,7 +1853,7 @@ TEST_F(HttpFilterTest, StreamingSendResponseDataGrpcFail) { sendChunkResponseData(chunk_number / 2, true); // When sends one more chunk of data, gRPC call fails. Buffer::OwnedImpl resp_data("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_data, false)); test_time_->advanceTimeWait(std::chrono::microseconds(10)); TestResponseHeaderMapImpl immediate_response_headers; EXPECT_CALL(encoder_callbacks_, @@ -1950,27 +1956,27 @@ TEST_F(HttpFilterTest, StreamingSendDataRandomGrpcLatency) { const uint32_t chunk_number = 5; Buffer::OwnedImpl req_data("foo"); // Latency 50 80 60 30 100. - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, false)); EXPECT_TRUE(last_request_.has_protocol_config()); processRequestBody(absl::nullopt, false, std::chrono::microseconds(50)); EXPECT_EQ(0, config_->stats().streams_closed_.value()); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, false)); EXPECT_FALSE(last_request_.has_protocol_config()); processRequestBody(absl::nullopt, false, std::chrono::microseconds(80)); EXPECT_EQ(0, config_->stats().streams_closed_.value()); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, false)); EXPECT_FALSE(last_request_.has_protocol_config()); processRequestBody(absl::nullopt, false, std::chrono::microseconds(60)); EXPECT_EQ(0, config_->stats().streams_closed_.value()); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, false)); EXPECT_FALSE(last_request_.has_protocol_config()); processRequestBody(absl::nullopt, false, std::chrono::microseconds(30)); EXPECT_EQ(0, config_->stats().streams_closed_.value()); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, false)); EXPECT_FALSE(last_request_.has_protocol_config()); processRequestBody(absl::nullopt, false, std::chrono::microseconds(100)); EXPECT_EQ(0, config_->stats().streams_closed_.value()); @@ -2077,7 +2083,7 @@ TEST_F(HttpFilterTest, PostStreamingBodies) { Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); want_response_body.add(resp_chunk.toString()); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); got_response_body.move(resp_chunk); EXPECT_FALSE(last_request_.has_protocol_config()); processResponseBody(absl::nullopt, false); @@ -2188,7 +2194,7 @@ TEST_F(HttpFilterTest, PostStreamingBodiesDifferentOrder) { Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); want_response_body.add(resp_chunk.toString()); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); got_response_body.move(resp_chunk); } @@ -2264,7 +2270,7 @@ TEST_F(HttpFilterTest, GetStreamingBodyAndChangeMode) { Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); want_response_body.add(resp_chunk.toString()); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); got_response_body.move(resp_chunk); } @@ -2275,16 +2281,12 @@ TEST_F(HttpFilterTest, GetStreamingBodyAndChangeMode) { }, false); - // A new body chunk should not be sent to the server, but should be queued - // because we didn't get all the responses yet Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); want_response_body.add(resp_chunk.toString()); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); got_response_body.move(resp_chunk); - // There should be two more messages outstanding, but not three, so respond - // just to them. for (int i = 0; i < 3; i++) { processResponseBody(absl::nullopt, false); } @@ -2356,7 +2358,7 @@ TEST_F(HttpFilterTest, GetStreamingBodyAndChangeModeDifferentOrder) { Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); want_response_body.add(resp_chunk.toString()); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); got_response_body.move(resp_chunk); } @@ -2367,8 +2369,6 @@ TEST_F(HttpFilterTest, GetStreamingBodyAndChangeModeDifferentOrder) { }, false); - // A new body chunk should not be sent to the server, but should be queued - // because we didn't get all the responses yet Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); want_response_body.add(resp_chunk.toString()); @@ -4439,7 +4439,7 @@ TEST_F(HttpFilterTest, HeaderRespReceivedBeforeBody) { for (int i = 0; i < 5; i++) { Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); } // Send body responses @@ -4538,7 +4538,7 @@ TEST_F(HttpFilterTest, HeaderRespReceivedAfterBodySent) { for (int i = 5; i < 10; i++) { Buffer::OwnedImpl resp_chunk; TestUtility::feedBufferWithRandomCharacters(resp_chunk, 100); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk, false)); } // Send body responses @@ -5746,7 +5746,7 @@ TEST_F(HttpFilterTest, CloseStreamOnRequestBodyWithTrailers) { EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); Buffer::OwnedImpl req_data("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(req_data, false)); EXPECT_EQ(FilterTrailersStatus::StopIteration, filter_->decodeTrailers(request_trailers_)); auto response = std::make_unique(); auto* body_response = response->mutable_request_body(); @@ -5776,7 +5776,7 @@ TEST_F(HttpFilterTest, CloseStreamOnResponseBodyWithTrailers) { EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, true)); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, false)); Buffer::OwnedImpl response_data("foo"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(response_data, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(response_data, false)); EXPECT_EQ(FilterTrailersStatus::StopIteration, filter_->encodeTrailers(response_trailers_)); auto response = std::make_unique(); auto* body_response = response->mutable_response_body(); @@ -6027,7 +6027,7 @@ TEST_F(HttpFilterTest, HttpEventTrafficStatsTest) { // Request Body Buffer::OwnedImpl chunk1("chunk1"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(chunk1, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(chunk1, false)); processRequestBody( [&](const HttpBody&, ProcessingResponse&, BodyResponse& resp) { @@ -6040,9 +6040,9 @@ TEST_F(HttpFilterTest, HttpEventTrafficStatsTest) { // Response Body Buffer::OwnedImpl resp_chunk1("resp_chunk1"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk1, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk1, false)); Buffer::OwnedImpl resp_chunk2("resp_chunk2"); - EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_chunk2, false)); + EXPECT_EQ(FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(resp_chunk2, false)); processResponseBody( [&](const HttpBody&, ProcessingResponse&, BodyResponse& resp) { @@ -6058,6 +6058,85 @@ TEST_F(HttpFilterTest, HttpEventTrafficStatsTest) { filter_->onDestroy(); } +// Streaming data test with runtime guard +// envoy.reloadable_features.ext_proc_return_stop_iteration sets to false. +// This test can be removed when the runtime guard is removed. +TEST_F(HttpFilterTest, StreamingSendDataRandomGrpcLatencyReturnContinue) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SKIP" + response_header_mode: "SKIP" + request_body_mode: "STREAMED" + )EOF"); + scoped_runtime_.mergeValues( + {{"envoy.reloadable_features.ext_proc_return_stop_iteration", "false"}}); + + HttpTestUtility::addDefaultHeaders(request_headers_); + request_headers_.setMethod("POST"); + EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillRepeatedly(Return(nullptr)); + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); + + const uint32_t chunk_number = 5; + Buffer::OwnedImpl req_data("foo"); + // Latency 50 80 60 30 100. + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_TRUE(last_request_.has_protocol_config()); + processRequestBody(absl::nullopt, false, std::chrono::microseconds(50)); + EXPECT_EQ(0, config_->stats().streams_closed_.value()); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_FALSE(last_request_.has_protocol_config()); + processRequestBody(absl::nullopt, false, std::chrono::microseconds(80)); + EXPECT_EQ(0, config_->stats().streams_closed_.value()); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_FALSE(last_request_.has_protocol_config()); + processRequestBody(absl::nullopt, false, std::chrono::microseconds(60)); + EXPECT_EQ(0, config_->stats().streams_closed_.value()); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_FALSE(last_request_.has_protocol_config()); + processRequestBody(absl::nullopt, false, std::chrono::microseconds(30)); + EXPECT_EQ(0, config_->stats().streams_closed_.value()); + + EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, false)); + EXPECT_FALSE(last_request_.has_protocol_config()); + processRequestBody(absl::nullopt, false, std::chrono::microseconds(100)); + EXPECT_EQ(0, config_->stats().streams_closed_.value()); + + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); + response_headers_.addCopy(LowerCaseString(":status"), "200"); + EXPECT_EQ(0, config_->stats().streams_closed_.value()); + + EXPECT_EQ(FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, false)); + EXPECT_EQ(1, config_->stats().streams_closed_.value()); + Buffer::OwnedImpl resp_data("bar"); + EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false)); + EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); + EXPECT_EQ(1, config_->stats().streams_closed_.value()); + filter_->onDestroy(); + + EXPECT_EQ(1, config_->stats().streams_started_.value()); + uint32_t total_msg = chunk_number; + EXPECT_EQ(total_msg, config_->stats().stream_msgs_sent_.value()); + EXPECT_EQ(total_msg, config_->stats().stream_msgs_received_.value()); + EXPECT_EQ(0, config_->stats().streams_failed_.value()); + EXPECT_EQ(1, config_->stats().streams_closed_.value()); + + auto& grpc_calls_in = getGrpcCalls(envoy::config::core::v3::TrafficDirection::INBOUND); + EXPECT_TRUE(grpc_calls_in.header_stats_ == nullptr); + EXPECT_TRUE(grpc_calls_in.trailer_stats_ == nullptr); + EXPECT_TRUE(grpc_calls_in.body_stats_ != nullptr); + checkGrpcCallBody(*grpc_calls_in.body_stats_, chunk_number, Grpc::Status::Ok, + std::chrono::microseconds(320), std::chrono::microseconds(100), + std::chrono::microseconds(30)); + + expectNoGrpcCall(envoy::config::core::v3::TrafficDirection::OUTBOUND); +} + } // namespace } // namespace ExternalProcessing } // namespace HttpFilters diff --git a/test/extensions/filters/http/ext_proc/filter_test_common.cc b/test/extensions/filters/http/ext_proc/filter_test_common.cc index 9301d51167f29..c3f27d39db9fb 100644 --- a/test/extensions/filters/http/ext_proc/filter_test_common.cc +++ b/test/extensions/filters/http/ext_proc/filter_test_common.cc @@ -69,6 +69,8 @@ void HttpFilterTest::initialize(std::string&& yaml, bool is_upstream_filter) { {{"envoy.reloadable_features.ext_proc_stream_close_optimization", "true"}}); scoped_runtime_.mergeValues( {{"envoy.reloadable_features.ext_proc_inject_data_with_state_update", "true"}}); + scoped_runtime_.mergeValues( + {{"envoy.reloadable_features.ext_proc_return_stop_iteration", "true"}}); client_ = std::make_unique(); route_ = std::make_shared>(); EXPECT_CALL(*client_, start(_, _, _, _)).WillOnce(Invoke(this, &HttpFilterTest::doStart)); From f81e6bd689238d65eefefef9bc374c092fb56973 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 21 Apr 2026 14:20:13 +0100 Subject: [PATCH 091/750] test/ext_proc: Increase sharding (#44553) Signed-off-by: Ryan Northey --- test/extensions/filters/http/ext_proc/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 0d13c577869df..ee473f563b37e 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -471,7 +471,7 @@ envoy_extension_cc_test( "envoy.filters.http.set_metadata", ], rbe_pool = "4core", - shard_count = 12, + shard_count = 16, tags = [ "cpu:3", "skip_on_windows", From 8f6aedec900bdf4afee5c2f4318288dc9903a242 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Tue, 21 Apr 2026 09:21:01 -0400 Subject: [PATCH 092/750] changelog: move geoip promotion to minor_behavior section (#44554) Looking at the rendered changelog page, it's listed as ``` Incompatible behavior changes Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required ``` That's clearly not the case. Signed-off-by: Jonh Wendell --- changelogs/current.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index fffdb72448d34..f9751bc4b7263 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -2,10 +2,6 @@ date: Pending behavior_changes: # *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* -- area: geoip - change: | - :ref:`Envoy Http Geoip filter extension - ` has been promoted to stable status. - area: tcp_proxy change: | The TCP proxy filter now requires :ref:`max_early_data_bytes @@ -41,6 +37,10 @@ behavior_changes: minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* +- area: geoip + change: | + :ref:`Envoy Http Geoip filter extension + ` has been promoted to stable status. - area: load_balancing change: | Changed the default behavior of ``OrcaWeightManager`` to prefer named metrics over application From 2b97797b05ad0e28407e28fe90d30e5de1c4c537 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 21 Apr 2026 14:54:02 +0100 Subject: [PATCH 093/750] contrib/test: Fix release test (#44555) currently breaks on a release version Signed-off-by: Ryan Northey --- contrib/exe/contrib_version_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/exe/contrib_version_test.cc b/contrib/exe/contrib_version_test.cc index 57e7e55782453..313024e7e45d8 100644 --- a/contrib/exe/contrib_version_test.cc +++ b/contrib/exe/contrib_version_test.cc @@ -14,7 +14,7 @@ TEST(ContribVersionTest, BuildVersionContainsSuffix) { const auto& fields = build_version.metadata().fields(); ASSERT_NE(fields.find(BuildVersionMetadataKeys::get().BuildLabel), fields.end()); EXPECT_THAT(fields.at(BuildVersionMetadataKeys::get().BuildLabel).string_value(), - testing::EndsWith("-contrib")); + testing::EndsWith("contrib")); } } // namespace Envoy From 1b737e6f12255c75e2dff80473c75655576beaaa Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 21 Apr 2026 16:21:12 +0100 Subject: [PATCH 094/750] test/ads: Attempt to fix flaky `MultipleVhdsOverAds` (#44558) hopefully fix: #44425 Signed-off-by: Ryan Northey --- test/integration/ads_integration_test.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index 941ecf09be344..f07ad500ce902 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -3156,6 +3156,9 @@ TEST_P(AdsIntegrationTest, MultipleVhdsOverAds) { // reachable from listener_1. sendDiscoveryResponse( Config::TestTypeUrl::get().VirtualHost, {}, {}, {"route_config_0/foo"}, "1"); + + test_server_->waitForCounterGe("http.ads_test.rds.vhds.route_config_0.config_reload", 2); + send_request_and_verify("http1", foo_request_headers, true); send_request_and_verify("http1", bar_request_headers); From 21c00567b79d1c5b98419537b234d58f1932af20 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:50:32 +0000 Subject: [PATCH 095/750] deps: Bump `skywalking_data_collect_protocol` -> 10.4.0 (#44559) Fix #44545 Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 37b390eb71c73..98951a5814e3a 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -1346,7 +1346,7 @@ skywalking_data_collect_protocol: project_name: "skywalking-data-collect-protocol" project_desc: "Data Collect Protocols of Apache SkyWalking" project_url: "https://github.com/apache/skywalking-data-collect-protocol" - release_date: "2025-11-10" + release_date: "2026-04-21" use_category: - observability_ext extensions: diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 31f769a2f3b24..8ff3ec9b7d56d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -278,10 +278,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/open-telemetry/opentelemetry-cpp/archive/refs/tags/v{version}.tar.gz"], ), skywalking_data_collect_protocol = dict( - sha256 = "5b7c49eff204c423b3d1ffc3b9ec84f2d77838b30464e4a3d6158cf0b6a8429a", + sha256 = "70e63dd30d9dfdcf0a0ef0976e19c7e21fa1411af13ec31452bd1da37e578e35", urls = ["https://github.com/apache/skywalking-data-collect-protocol/archive/v{version}.tar.gz"], strip_prefix = "skywalking-data-collect-protocol-{version}", - version = "10.3.0", + version = "10.4.0", ), cpp2sky = dict( sha256 = "d7e52f517de5a1dc7d927dd63a2e5aa5cf8c2dcfd8fcf6b64e179978daf1c3ed", From 0533de0acca281110945e5726bbb306fbb12bde5 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Tue, 21 Apr 2026 13:36:38 -0400 Subject: [PATCH 096/750] openssl: Add more compat functions (#44561) `SSL_get_group_id`, `SSL_get_group_name` and `SSL_get_all_group_names` Signed-off-by: Jonh Wendell --- compat/openssl/BUILD | 3 +++ compat/openssl/patch/include/openssl/ssl.h.sh | 3 +++ .../openssl/source/SSL_get_all_group_names.c | 12 +++++++++ compat/openssl/source/SSL_get_group_id.c | 12 +++++++++ compat/openssl/source/SSL_get_group_name.c | 12 +++++++++ compat/openssl/test/test_ssl.cc | 27 +++++++++++++++++++ 6 files changed, 69 insertions(+) create mode 100644 compat/openssl/source/SSL_get_all_group_names.c create mode 100644 compat/openssl/source/SSL_get_group_id.c create mode 100644 compat/openssl/source/SSL_get_group_name.c diff --git a/compat/openssl/BUILD b/compat/openssl/BUILD index 6e68f8833a197..626a8eb9b52f1 100644 --- a/compat/openssl/BUILD +++ b/compat/openssl/BUILD @@ -693,6 +693,7 @@ mapping_func_filegroup( "SSL_free", "SSL_get_all_cipher_names", "SSL_get_all_signature_algorithm_names", + "SSL_get_all_group_names", "SSL_get_all_version_names", "SSL_get_certificate", "SSL_get_cipher_by_value", @@ -700,6 +701,8 @@ mapping_func_filegroup( "SSL_get_client_CA_list", "SSL_get_current_cipher", "SSL_get_curve_id", + "SSL_get_group_id", + "SSL_get_group_name", "SSL_get_error", "SSL_get_ex_data_X509_STORE_CTX_idx", "SSL_get_ex_data", diff --git a/compat/openssl/patch/include/openssl/ssl.h.sh b/compat/openssl/patch/include/openssl/ssl.h.sh index dbed0f696650a..879c0211a1a36 100755 --- a/compat/openssl/patch/include/openssl/ssl.h.sh +++ b/compat/openssl/patch/include/openssl/ssl.h.sh @@ -122,6 +122,8 @@ uncomment.sh "$1" --comment -h \ --uncomment-func-decl SSL_early_callback_ctx_extension_get \ --uncomment-func-decl SSL_get_cipher_by_value \ --uncomment-func-decl SSL_get_curve_id \ + --uncomment-func-decl SSL_get_group_id \ + --uncomment-func-decl SSL_get_group_name \ --uncomment-func-decl SSL_get_curve_name \ --uncomment-func-decl SSL_get_peer_signature_algorithm \ --uncomment-func-decl SSL_get_signature_algorithm_name \ @@ -218,6 +220,7 @@ uncomment.sh "$1" --comment -h \ --uncomment-func-decl SSL_get_all_signature_algorithm_names \ --uncomment-func-decl SSL_get_all_version_names \ --uncomment-func-decl SSL_get_all_curve_names \ + --uncomment-func-decl SSL_get_all_group_names \ --uncomment-macro SSL_GROUP_SECP256R1 \ --uncomment-macro SSL_GROUP_SECP384R1 \ --uncomment-macro SSL_GROUP_SECP521R1 \ diff --git a/compat/openssl/source/SSL_get_all_group_names.c b/compat/openssl/source/SSL_get_all_group_names.c new file mode 100644 index 0000000000000..e5197ea786665 --- /dev/null +++ b/compat/openssl/source/SSL_get_all_group_names.c @@ -0,0 +1,12 @@ +#include + + +/* + * https://github.com/google/boringssl/blob/098695591f3a2665fccef83a3732ecfc99acdcdd/src/include/openssl/ssl.h#L2355 + * + * SSL_get_all_group_names outputs a list of all group names. This is the same + * as SSL_get_all_curve_names. + */ +size_t SSL_get_all_group_names(const char **out, size_t max_out) { + return SSL_get_all_curve_names(out, max_out); +} diff --git a/compat/openssl/source/SSL_get_group_id.c b/compat/openssl/source/SSL_get_group_id.c new file mode 100644 index 0000000000000..93734d856324e --- /dev/null +++ b/compat/openssl/source/SSL_get_group_id.c @@ -0,0 +1,12 @@ +#include + + +/* + * https://github.com/google/boringssl/blob/098695591f3a2665fccef83a3732ecfc99acdcdd/src/include/openssl/ssl.h#L2342 + * + * SSL_get_group_id returns the ID of the group used by the current TLS + * connection. This is the same as SSL_get_curve_id. + */ +uint16_t SSL_get_group_id(const SSL *ssl) { + return SSL_get_curve_id(ssl); +} diff --git a/compat/openssl/source/SSL_get_group_name.c b/compat/openssl/source/SSL_get_group_name.c new file mode 100644 index 0000000000000..ff7dede8f17a4 --- /dev/null +++ b/compat/openssl/source/SSL_get_group_name.c @@ -0,0 +1,12 @@ +#include + + +/* + * https://github.com/google/boringssl/blob/098695591f3a2665fccef83a3732ecfc99acdcdd/src/include/openssl/ssl.h#L2349 + * + * SSL_get_group_name returns a human-readable name for the group + * specified by the given TLS group ID. This is the same as SSL_get_curve_name. + */ +const char *SSL_get_group_name(uint16_t group_id) { + return SSL_get_curve_name(group_id); +} diff --git a/compat/openssl/test/test_ssl.cc b/compat/openssl/test/test_ssl.cc index 2512be7402908..950ad0ad11adb 100644 --- a/compat/openssl/test/test_ssl.cc +++ b/compat/openssl/test/test_ssl.cc @@ -642,6 +642,7 @@ TEST_P(SSLTestWithCurves, test_SSL_get_curve_id) { ASSERT_TRUE(SSL_connect(ssl.get()) > 0) << (ERR_print_errors_fp(stderr), ""); ASSERT_EQ(curves.expected_curve, SSL_get_curve_id(ssl.get())); + ASSERT_EQ(curves.expected_curve, SSL_get_group_id(ssl.get())); char buf[sizeof(MESSAGE)]; ASSERT_EQ(sizeof(MESSAGE), SSL_write(ssl.get(), MESSAGE, sizeof(MESSAGE))); @@ -668,6 +669,15 @@ TEST(SSLTest, test_SSL_get_curve_name) { EXPECT_STREQ("X25519Kyber768Draft00", SSL_get_curve_name(SSL_GROUP_X25519_KYBER768_DRAFT00)); } +TEST(SSLTest, test_SSL_get_group_name) { + EXPECT_STREQ("P-256", SSL_get_group_name(SSL_CURVE_SECP256R1)); + EXPECT_STREQ("P-384", SSL_get_group_name(SSL_CURVE_SECP384R1)); + EXPECT_STREQ("P-521", SSL_get_group_name(SSL_CURVE_SECP521R1)); + EXPECT_STREQ("X25519", SSL_get_group_name(SSL_CURVE_X25519)); + EXPECT_STREQ("X25519MLKEM768", SSL_get_group_name(SSL_GROUP_X25519_MLKEM768)); + EXPECT_STREQ("X25519Kyber768Draft00", SSL_get_group_name(SSL_GROUP_X25519_KYBER768_DRAFT00)); +} + struct Sigalgs { const char* server_sigalgs; const char* client_sigalgs; @@ -2040,6 +2050,23 @@ TEST(SSLTest, test_SSL_get_all_curve_names) { } } +TEST(SSLTest, test_SSL_get_all_group_names) { + size_t curve_size = SSL_get_all_curve_names(nullptr, 0); + size_t group_size = SSL_get_all_group_names(nullptr, 0); + ASSERT_EQ(group_size, curve_size); + ASSERT_GT(group_size, 0); + + std::unique_ptr curve_names(new const char*[curve_size]); + std::unique_ptr group_names(new const char*[group_size]); + + SSL_get_all_curve_names(curve_names.get(), curve_size); + SSL_get_all_group_names(group_names.get(), group_size); + + for (size_t i = 0; i < group_size; i++) { + ASSERT_STREQ(curve_names.get()[i], group_names.get()[i]); + } +} + struct SSLFunc { const char *name; From a054fb856c818ac9960cea9609786ca2bbdbca89 Mon Sep 17 00:00:00 2001 From: Vanessa Reimer Date: Tue, 21 Apr 2026 22:24:55 -0600 Subject: [PATCH 097/750] Update QUICHE from ba02ee8fc to e68fe05e7 (#44533) https://github.com/google/quiche/compare/ba02ee8fc..e68fe05e7 ``` $ git log ba02ee8fc..e68fe05e7 --date=short --no-merges --format="%ad %al %s" 2026-04-20 quiche-dev Add support for sending OHTTP request bodies in multiple chunks. 2026-04-17 ianswett Branch Bbr2Sender to create Bbr3Sender and no long have modes extend from Bbr2ModeBase. ``` Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Vanessa Reimer --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 98951a5814e3a..c3a4a8c0fcbed 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -296,7 +296,7 @@ quiche: project_name: "QUICHE" project_desc: "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols" project_url: "https://github.com/google/quiche" - release_date: "2026-04-16" + release_date: "2026-04-20" use_category: - controlplane - dataplane_core diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 8ff3ec9b7d56d..26d70a5227a71 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -553,8 +553,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/simdutf/simdutf/releases/download/v{version}/singleheader.zip"], ), quiche = dict( - version = "ba02ee8fc1c0ab27d55f6bbb9e1ac7c9705c3e69", - sha256 = "670791b54c8f73eafbb25226b27132bad5d336b70f8543be9d39bbfd9dc7222c", + version = "e68fe05e70da74a3ea282d927c76f76b4bc4e710", + sha256 = "08033a0886b470d4ea836a6b785ef6ef7d638265e5523a37718cdd6d1ef6a409", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", ), From c83d0a1012719f76e3ea7bc703ea693261fab9c6 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 22 Apr 2026 06:25:51 -0400 Subject: [PATCH 098/750] Apply nghttp2 CVE-2026-27135 patch (#44565) Signed-off-by: Yan Avlasov --- .../nghttp2-CVE-2026-27135_part1.diff | 468 ++++++++++++++++++ .../nghttp2-CVE-2026-27135_part2.diff | 110 ++++ .../nghttp2-CVE-2026-27135_part3.diff | 50 ++ .../nghttp2-CVE-2026-27135_part4.diff | 26 + bazel/repositories.bzl | 4 + changelogs/current.yaml | 3 + 6 files changed, 661 insertions(+) create mode 100644 bazel/foreign_cc/nghttp2-CVE-2026-27135_part1.diff create mode 100644 bazel/foreign_cc/nghttp2-CVE-2026-27135_part2.diff create mode 100644 bazel/foreign_cc/nghttp2-CVE-2026-27135_part3.diff create mode 100644 bazel/foreign_cc/nghttp2-CVE-2026-27135_part4.diff diff --git a/bazel/foreign_cc/nghttp2-CVE-2026-27135_part1.diff b/bazel/foreign_cc/nghttp2-CVE-2026-27135_part1.diff new file mode 100644 index 0000000000000..b1b2d5da7648b --- /dev/null +++ b/bazel/foreign_cc/nghttp2-CVE-2026-27135_part1.diff @@ -0,0 +1,468 @@ +diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c +index 4e36c342d1..c2092d887b 100644 +--- a/tests/nghttp2_session_test.c ++++ b/tests/nghttp2_session_test.c +@@ -157,6 +157,7 @@ static const MunitTest tests[] = { + munit_void_test(test_nghttp2_session_set_stream_user_data), + munit_void_test(test_nghttp2_session_no_rfc7540_priorities), + munit_void_test(test_nghttp2_session_stream_reset_ratelim), ++ munit_void_test(test_nghttp2_session_verify_iframe_state), + munit_void_test(test_nghttp2_http_mandatory_headers), + munit_void_test(test_nghttp2_http_content_length), + munit_void_test(test_nghttp2_http_content_length_mismatch), +@@ -10160,6 +10161,443 @@ void test_nghttp2_session_stream_reset_ratelim(void) { + nghttp2_option_del(option); + } + ++static int term_session_on_invalid_frame_recv_callback( ++ nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, ++ void *user_data) { ++ int rv; ++ (void)frame; ++ (void)lib_error_code; ++ (void)user_data; ++ ++ rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); ++ ++ assert_int(0, ==, rv); ++ ++ return 0; ++} ++ ++static int term_session_on_begin_frame_callback(nghttp2_session *session, ++ const nghttp2_frame_hd *hd, ++ void *user_data) { ++ int rv; ++ (void)hd; ++ (void)user_data; ++ ++ rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); ++ ++ assert_int(0, ==, rv); ++ ++ return 0; ++} ++ ++static int term_session_on_frame_recv_callback(nghttp2_session *session, ++ const nghttp2_frame *frame, ++ void *user_data) { ++ int rv; ++ (void)frame; ++ (void)user_data; ++ ++ rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); ++ ++ assert_int(0, ==, rv); ++ ++ return 0; ++} ++ ++static int term_session_on_data_chunk_recv_callback( ++ nghttp2_session *session, uint8_t flags, int32_t stream_id, ++ const uint8_t *data, size_t len, void *user_data) { ++ int rv; ++ (void)flags; ++ (void)stream_id; ++ (void)data; ++ (void)len; ++ (void)user_data; ++ ++ rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); ++ ++ assert_int(0, ==, rv); ++ ++ return 0; ++} ++ ++static int term_session_unpack_extension_callback(nghttp2_session *session, ++ void **payload, ++ const nghttp2_frame_hd *hd, ++ void *user_data) { ++ int rv; ++ (void)payload; ++ (void)hd; ++ (void)user_data; ++ ++ rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); ++ ++ assert_int(0, ==, rv); ++ ++ return 0; ++} ++ ++static int term_session_on_extension_chunk_recv_callback( ++ nghttp2_session *session, const nghttp2_frame_hd *hd, const uint8_t *data, ++ size_t len, void *user_data) { ++ int rv; ++ (void)hd; ++ (void)data; ++ (void)len; ++ (void)user_data; ++ ++ rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); ++ ++ assert_int(0, ==, rv); ++ ++ return NGHTTP2_ERR_CANCEL; ++} ++ ++void test_nghttp2_session_verify_iframe_state(void) { ++ nghttp2_session *session; ++ nghttp2_session_callbacks callbacks; ++ nghttp2_bufs bufs; ++ nghttp2_buf *buf; ++ nghttp2_frame frame; ++ nghttp2_extension extfr; ++ nghttp2_ext_priority_update priority_update; ++ nghttp2_ext_altsvc altsvc; ++ nghttp2_frame_hd hd; ++ uint8_t size_err_hd[NGHTTP2_FRAME_HDLEN]; ++ nghttp2_ssize rv; ++ const char field_value[] = "i"; ++ nghttp2_option *option; ++ nghttp2_hd_deflater deflater; ++ size_t nvlen; ++ nghttp2_nv *nva; ++ nghttp2_mem *mem = nghttp2_mem_default(); ++ my_user_data ud; ++ nghttp2_buf udbuf; ++ const char uddata[] = "hello world"; ++ ++ nghttp2_frame_hd_init(&hd, 0, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1); ++ nghttp2_frame_pack_frame_hd(size_err_hd, &hd); ++ ++ nghttp2_buf_init2(&ud.scratchbuf, 4096, mem); ++ nghttp2_buf_init2(&udbuf, 4096, mem); ++ ++ nghttp2_frame_hd_init(&hd, sizeof(uddata) - 1, 111, 0xab, 1000000007); ++ nghttp2_frame_pack_frame_hd(udbuf.last, &hd); ++ udbuf.last += NGHTTP2_FRAME_HDLEN; ++ udbuf.last = nghttp2_cpymem(udbuf.last, uddata, sizeof(uddata) - 1); ++ ++ nghttp2_option_new(&option); ++ nghttp2_option_set_builtin_recv_extension_type(option, ++ NGHTTP2_PRIORITY_UPDATE); ++ nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC); ++ nghttp2_option_set_user_recv_extension_type(option, 111); ++ ++ frame_pack_bufs_init(&bufs); ++ ++ /* ALTSVC + on_invalid_frame_recv_callback + FRAME_SIZE_ERROR */ ++ memset(&callbacks, 0, sizeof(callbacks)); ++ callbacks.on_invalid_frame_recv_callback = ++ term_session_on_invalid_frame_recv_callback; ++ ++ nghttp2_session_client_new2(&session, &callbacks, NULL, option); ++ ++ extfr.payload = &altsvc; ++ nghttp2_frame_altsvc_init(&extfr, 0, NULL, 0, NULL, 0); ++ nghttp2_bufs_reset(&bufs); ++ nghttp2_frame_pack_altsvc(&bufs, &extfr); ++ ++ buf = &bufs.head->buf; ++ ++ rv = nghttp2_session_mem_recv2(session, buf->pos, nghttp2_bufs_len(&bufs)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_bufs_len(&bufs), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ rv = nghttp2_session_mem_recv2(session, size_err_hd, sizeof(size_err_hd)); ++ ++ assert_ptrdiff((nghttp2_ssize)sizeof(size_err_hd), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ nghttp2_session_del(session); ++ ++ /* HEADERS + on_begin_frame_callback + FRAME_SIZE_ERROR */ ++ memset(&callbacks, 0, sizeof(callbacks)); ++ callbacks.on_begin_frame_callback = term_session_on_begin_frame_callback; ++ ++ nghttp2_session_server_new(&session, &callbacks, NULL); ++ ++ nvlen = ARRLEN(reqnv); ++ nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); ++ nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, ++ NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); ++ nghttp2_hd_deflate_init(&deflater, mem); ++ nghttp2_bufs_reset(&bufs); ++ rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); ++ ++ assert_ptrdiff(0, ==, rv); ++ ++ nghttp2_frame_headers_free(&frame.headers, mem); ++ ++ buf = &bufs.head->buf; ++ ++ rv = nghttp2_session_mem_recv2(session, buf->pos, nghttp2_bufs_len(&bufs)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_bufs_len(&bufs), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ rv = nghttp2_session_mem_recv2(session, size_err_hd, sizeof(size_err_hd)); ++ ++ assert_ptrdiff((nghttp2_ssize)sizeof(size_err_hd), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ nghttp2_hd_deflate_free(&deflater); ++ nghttp2_session_del(session); ++ ++ /* Any frame (other than HEADERS) + on_begin_frame_callback + ++ FRAME_SIZE_ERROR */ ++ memset(&callbacks, 0, sizeof(callbacks)); ++ callbacks.on_begin_frame_callback = term_session_on_begin_frame_callback; ++ ++ nghttp2_session_server_new(&session, &callbacks, NULL); ++ ++ nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_NO_ERROR); ++ nghttp2_bufs_reset(&bufs); ++ nghttp2_frame_pack_rst_stream(&bufs, &frame.rst_stream); ++ ++ buf = &bufs.head->buf; ++ ++ rv = nghttp2_session_mem_recv2(session, buf->pos, nghttp2_bufs_len(&bufs)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_bufs_len(&bufs), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ rv = nghttp2_session_mem_recv2(session, size_err_hd, sizeof(size_err_hd)); ++ ++ assert_ptrdiff((nghttp2_ssize)sizeof(size_err_hd), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ nghttp2_session_del(session); ++ ++ /* PRIORITY_UPDATE + on_frame_recv_callback + FRAME_SIZE_ERROR */ ++ memset(&callbacks, 0, sizeof(callbacks)); ++ callbacks.on_frame_recv_callback = term_session_on_frame_recv_callback; ++ ++ nghttp2_session_server_new2(&session, &callbacks, NULL, option); ++ ++ extfr.payload = &priority_update; ++ nghttp2_frame_priority_update_init(&extfr, 1, (uint8_t *)field_value, ++ sizeof(field_value) - 1); ++ nghttp2_bufs_reset(&bufs); ++ nghttp2_frame_pack_priority_update(&bufs, &extfr); ++ ++ buf = &bufs.head->buf; ++ ++ rv = nghttp2_session_mem_recv2(session, buf->pos, nghttp2_bufs_len(&bufs)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_bufs_len(&bufs), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ rv = nghttp2_session_mem_recv2(session, size_err_hd, sizeof(size_err_hd)); ++ ++ assert_ptrdiff((nghttp2_ssize)sizeof(size_err_hd), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ nghttp2_session_del(session); ++ ++ /* ALTSVC + on_frame_recv_callback + FRAME_SIZE_ERROR */ ++ memset(&callbacks, 0, sizeof(callbacks)); ++ callbacks.on_frame_recv_callback = term_session_on_frame_recv_callback; ++ ++ nghttp2_session_client_new2(&session, &callbacks, NULL, option); ++ ++ extfr.payload = &altsvc; ++ nghttp2_frame_altsvc_init(&extfr, 0, (uint8_t *)"nghttp2.org", ++ sizeof("nghttp2.org") - 1, (uint8_t *)"h2", ++ sizeof("h2") - 1); ++ nghttp2_bufs_reset(&bufs); ++ nghttp2_frame_pack_altsvc(&bufs, &extfr); ++ ++ buf = &bufs.head->buf; ++ ++ rv = nghttp2_session_mem_recv2(session, buf->pos, nghttp2_bufs_len(&bufs)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_bufs_len(&bufs), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ rv = nghttp2_session_mem_recv2(session, size_err_hd, sizeof(size_err_hd)); ++ ++ assert_ptrdiff((nghttp2_ssize)sizeof(size_err_hd), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ nghttp2_session_del(session); ++ ++ /* user-defined extension frame + on_frame_recv_callback + ++ FRAME_SIZE_ERROR */ ++ memset(&callbacks, 0, sizeof(callbacks)); ++ callbacks.on_frame_recv_callback = term_session_on_frame_recv_callback; ++ callbacks.unpack_extension_callback = unpack_extension_callback; ++ ++ nghttp2_session_server_new2(&session, &callbacks, &ud, option); ++ ++ nghttp2_buf_reset(&ud.scratchbuf); ++ ++ rv = nghttp2_session_mem_recv2(session, udbuf.pos, nghttp2_buf_len(&udbuf)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_buf_len(&udbuf), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ rv = nghttp2_session_mem_recv2(session, size_err_hd, sizeof(size_err_hd)); ++ ++ assert_ptrdiff((nghttp2_ssize)sizeof(size_err_hd), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ nghttp2_session_del(session); ++ ++ /* DATA + on_data_chunk_recv_callback + FRAME_SIZE_ERROR*/ ++ memset(&callbacks, 0, sizeof(callbacks)); ++ callbacks.on_data_chunk_recv_callback = ++ term_session_on_data_chunk_recv_callback; ++ ++ nghttp2_session_server_new(&session, &callbacks, NULL); ++ ++ nvlen = ARRLEN(reqnv); ++ nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); ++ nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, ++ NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); ++ nghttp2_hd_deflate_init(&deflater, mem); ++ nghttp2_bufs_reset(&bufs); ++ rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); ++ ++ assert_ptrdiff(0, ==, rv); ++ ++ nghttp2_frame_headers_free(&frame.headers, mem); ++ ++ buf = &bufs.head->buf; ++ ++ rv = nghttp2_session_mem_recv2(session, buf->pos, nghttp2_bufs_len(&bufs)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_bufs_len(&bufs), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_READ_HEAD, ==, ++ session->iframe.state); ++ ++ nghttp2_frame_hd_init(&hd, 10, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 1); ++ nghttp2_bufs_reset(&bufs); ++ nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); ++ bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; ++ memset(bufs.head->buf.last, 0, 10); ++ bufs.head->buf.last += 10; ++ ++ buf = &bufs.head->buf; ++ ++ rv = nghttp2_session_mem_recv2(session, buf->pos, nghttp2_bufs_len(&bufs)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_bufs_len(&bufs), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ rv = nghttp2_session_mem_recv2(session, size_err_hd, sizeof(size_err_hd)); ++ ++ assert_ptrdiff((nghttp2_ssize)sizeof(size_err_hd), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ nghttp2_hd_deflate_free(&deflater); ++ nghttp2_session_del(session); ++ ++ /* user-defined extension frame + on_extension_chunk_recv_callback + ++ FRAME_SIZE_ERROR */ ++ memset(&callbacks, 0, sizeof(callbacks)); ++ callbacks.on_extension_chunk_recv_callback = ++ term_session_on_extension_chunk_recv_callback; ++ callbacks.unpack_extension_callback = unpack_extension_callback; ++ ++ nghttp2_session_server_new2(&session, &callbacks, &ud, option); ++ ++ nghttp2_buf_reset(&ud.scratchbuf); ++ ++ rv = nghttp2_session_mem_recv2(session, udbuf.pos, nghttp2_buf_len(&udbuf)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_buf_len(&udbuf), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ rv = nghttp2_session_mem_recv2(session, size_err_hd, sizeof(size_err_hd)); ++ ++ assert_ptrdiff((nghttp2_ssize)sizeof(size_err_hd), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ nghttp2_session_del(session); ++ ++ /* user-defined extension frame + unpack_extension_callback + ++ FRAME_SIZE_ERROR */ ++ memset(&callbacks, 0, sizeof(callbacks)); ++ callbacks.unpack_extension_callback = term_session_unpack_extension_callback; ++ ++ nghttp2_session_server_new2(&session, &callbacks, &ud, option); ++ ++ nghttp2_buf_reset(&ud.scratchbuf); ++ ++ rv = nghttp2_session_mem_recv2(session, udbuf.pos, nghttp2_buf_len(&udbuf)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_buf_len(&udbuf), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ rv = nghttp2_session_mem_recv2(session, size_err_hd, sizeof(size_err_hd)); ++ ++ assert_ptrdiff((nghttp2_ssize)sizeof(size_err_hd), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ nghttp2_session_del(session); ++ ++ /* PRIORITY_UPDATE on stream 1 + FRAME_SIZE_ERROR */ ++ memset(&callbacks, 0, sizeof(callbacks)); ++ ++ nghttp2_session_server_new2(&session, &callbacks, NULL, option); ++ ++ extfr.payload = &priority_update; ++ nghttp2_frame_priority_update_init(&extfr, 1, (uint8_t *)field_value, ++ sizeof(field_value) - 1); ++ nghttp2_bufs_reset(&bufs); ++ nghttp2_frame_pack_priority_update(&bufs, &extfr); ++ buf = &bufs.head->buf; ++ /* Set invalid stream ID 1 */ ++ buf->pos[5 + sizeof(uint32_t) - 1] = 1; ++ ++ rv = nghttp2_session_mem_recv2(session, buf->pos, nghttp2_bufs_len(&bufs)); ++ ++ assert_ptrdiff((nghttp2_ssize)nghttp2_bufs_len(&bufs), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ rv = nghttp2_session_mem_recv2(session, size_err_hd, sizeof(size_err_hd)); ++ ++ assert_ptrdiff((nghttp2_ssize)sizeof(size_err_hd), ==, rv); ++ assert_enum(nghttp2_inbound_state, NGHTTP2_IB_IGN_ALL, ==, ++ session->iframe.state); ++ ++ nghttp2_session_del(session); ++ ++ nghttp2_buf_free(&udbuf, mem); ++ nghttp2_buf_free(&ud.scratchbuf, mem); ++ nghttp2_bufs_free(&bufs); ++ nghttp2_option_del(option); ++} ++ + static void check_nghttp2_http_recv_headers_fail( + nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id, + int stream_state, const nghttp2_nv *nva, size_t nvlen) { +diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h +index 50061978c0..73d7e4e251 100644 +--- a/tests/nghttp2_session_test.h ++++ b/tests/nghttp2_session_test.h +@@ -153,6 +153,7 @@ munit_void_test_decl(test_nghttp2_session_no_closed_streams) + munit_void_test_decl(test_nghttp2_session_set_stream_user_data) + munit_void_test_decl(test_nghttp2_session_no_rfc7540_priorities) + munit_void_test_decl(test_nghttp2_session_stream_reset_ratelim) ++munit_void_test_decl(test_nghttp2_session_verify_iframe_state) + munit_void_test_decl(test_nghttp2_http_mandatory_headers) + munit_void_test_decl(test_nghttp2_http_content_length) + munit_void_test_decl(test_nghttp2_http_content_length_mismatch) diff --git a/bazel/foreign_cc/nghttp2-CVE-2026-27135_part2.diff b/bazel/foreign_cc/nghttp2-CVE-2026-27135_part2.diff new file mode 100644 index 0000000000000..c19b7e585bf70 --- /dev/null +++ b/bazel/foreign_cc/nghttp2-CVE-2026-27135_part2.diff @@ -0,0 +1,110 @@ +# comment(@adip): The following transform at: +# @@ -6581,13 +6597,17 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, +# doesn't match the original transform because the corresponding code in OSS +# has been modified between v1.65 and v1.68. +diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c +index bcea547343..0fbcc930b9 100644 +--- a/lib/nghttp2_session.c ++++ b/lib/nghttp2_session.c +@@ -5506,6 +5506,10 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + return rv; + } + ++ if (iframe->state == NGHTTP2_IB_IGN_ALL) { ++ return (nghttp2_ssize)inlen; ++ } ++ + on_begin_frame_called = 1; + + rv = session_process_headers_frame(session); +@@ -5871,6 +5875,10 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + if (nghttp2_is_fatal(rv)) { + return rv; + } ++ ++ if (iframe->state == NGHTTP2_IB_IGN_ALL) { ++ return (nghttp2_ssize)inlen; ++ } + } + } + +@@ -6114,6 +6122,10 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + return rv; + } + ++ if (iframe->state == NGHTTP2_IB_IGN_ALL) { ++ return (nghttp2_ssize)inlen; ++ } ++ + session_inbound_frame_reset(session); + + break; +@@ -6416,6 +6428,10 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + if (nghttp2_is_fatal(rv)) { + return rv; + } ++ ++ if (iframe->state == NGHTTP2_IB_IGN_ALL) { ++ return (nghttp2_ssize)inlen; ++ } + } else { + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + } +@@ -6581,13 +6597,17 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + rv = session->callbacks.on_data_chunk_recv_callback( + session, iframe->frame.hd.flags, iframe->frame.hd.stream_id, + in - readlen, (size_t)data_readlen, session->user_data); +- if (rv == NGHTTP2_ERR_PAUSE) { +- return (nghttp2_ssize)(in - first); +- } +- +- if (nghttp2_is_fatal(rv)) { +- return NGHTTP2_ERR_CALLBACK_FAILURE; +- } ++ if (nghttp2_is_fatal(rv)) { ++ return NGHTTP2_ERR_CALLBACK_FAILURE; ++ } ++ ++ if (iframe->state == NGHTTP2_IB_IGN_ALL) { ++ return (nghttp2_ssize)inlen; ++ } ++ ++ if (rv == NGHTTP2_ERR_PAUSE) { ++ return (nghttp2_ssize)(in - first); ++ } + } + } + } +@@ -6667,6 +687,10 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + return rv; + } + ++ if (iframe->state == NGHTTP2_IB_IGN_ALL) { ++ return (nghttp2_ssize)inlen; ++ } ++ + if (rv != 0) { + busy = 1; + +@@ -6685,6 +6709,10 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + return rv; + } + ++ if (iframe->state == NGHTTP2_IB_IGN_ALL) { ++ return (nghttp2_ssize)inlen; ++ } ++ + session_inbound_frame_reset(session); + + break; +@@ -6713,6 +6741,10 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + return rv; + } + ++ if (iframe->state == NGHTTP2_IB_IGN_ALL) { ++ return (nghttp2_ssize)inlen; ++ } ++ + session_inbound_frame_reset(session); + + break; diff --git a/bazel/foreign_cc/nghttp2-CVE-2026-27135_part3.diff b/bazel/foreign_cc/nghttp2-CVE-2026-27135_part3.diff new file mode 100644 index 0000000000000..84314435ca3a0 --- /dev/null +++ b/bazel/foreign_cc/nghttp2-CVE-2026-27135_part3.diff @@ -0,0 +1,50 @@ +diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c +index edc2aaaae9..264ae9d38e 100644 +--- a/lib/nghttp2_frame.c ++++ b/lib/nghttp2_frame.c +@@ -750,6 +750,16 @@ void nghttp2_frame_unpack_altsvc_payload(nghttp2_extension *frame, + uint8_t *p; + + altsvc = frame->payload; ++ ++ if (payloadlen == 0) { ++ altsvc->origin = NULL; ++ altsvc->origin_len = 0; ++ altsvc->field_value = NULL; ++ altsvc->field_value_len = 0; ++ ++ return; ++ } ++ + p = payload; + + altsvc->origin = p; +diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c +index bda96b97b3..27fd513428 100644 +--- a/tests/nghttp2_frame_test.c ++++ b/tests/nghttp2_frame_test.c +@@ -515,6 +515,24 @@ void test_nghttp2_frame_pack_altsvc(void) { + + nghttp2_frame_altsvc_free(&oframe, mem); + nghttp2_frame_altsvc_free(&frame, mem); ++ ++ /* 0 length origin and field_value */ ++ nghttp2_frame_altsvc_init(&frame, 0, NULL, 0, NULL, 0); ++ ++ payloadlen = 2; ++ ++ nghttp2_bufs_reset(&bufs); ++ nghttp2_frame_pack_altsvc(&bufs, &frame); ++ ++ assert_size(NGHTTP2_FRAME_HDLEN + payloadlen, ==, nghttp2_bufs_len(&bufs)); ++ ++ nghttp2_frame_unpack_altsvc_payload(&oframe, 0, NULL, 0); ++ ++ assert_size(0, ==, oaltsvc.origin_len); ++ assert_null(oaltsvc.origin); ++ assert_size(0, ==, oaltsvc.field_value_len); ++ assert_null(oaltsvc.field_value); ++ + nghttp2_bufs_free(&bufs); + } + diff --git a/bazel/foreign_cc/nghttp2-CVE-2026-27135_part4.diff b/bazel/foreign_cc/nghttp2-CVE-2026-27135_part4.diff new file mode 100644 index 0000000000000..79619fcbedb64 --- /dev/null +++ b/bazel/foreign_cc/nghttp2-CVE-2026-27135_part4.diff @@ -0,0 +1,26 @@ +diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c +index 1f4ea54c62..bcea547343 100644 +--- a/lib/nghttp2_session.c ++++ b/lib/nghttp2_session.c +@@ -5423,6 +5423,10 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + busy = 1; + + rv = session_on_data_received_fail_fast(session); ++ if (nghttp2_is_fatal(rv)) { ++ return rv; ++ } ++ + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (nghttp2_ssize)inlen; + } +@@ -5433,10 +5437,6 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + break; + } + +- if (nghttp2_is_fatal(rv)) { +- return rv; +- } +- + rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); + if (rv < 0) { + rv = nghttp2_session_terminate_session_with_reason( diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index f77d97316132f..1b1aa0ecebdc3 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -609,6 +609,10 @@ def _nghttp2(): "@envoy//bazel/foreign_cc:nghttp2.patch", "@envoy//bazel/foreign_cc:nghttp2_huffman.patch", "@envoy//bazel/foreign_cc:nghttp2_max_hd_nv.patch", + "@envoy//bazel/foreign_cc:nghttp2-CVE-2026-27135_part1.diff", + "@envoy//bazel/foreign_cc:nghttp2-CVE-2026-27135_part2.diff", + "@envoy//bazel/foreign_cc:nghttp2-CVE-2026-27135_part3.diff", + "@envoy//bazel/foreign_cc:nghttp2-CVE-2026-27135_part4.diff", ], ) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index f9751bc4b7263..2773102575eab 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -399,6 +399,9 @@ bug_fixes: bootstrap extensions were completely skipped in validation mode, which meant their configs were not validated and any providers or singletons they register were unavailable to downstream config elements that depend on them. +- area: http2 + change: | + Apply nghttp2 CVE-2026-27135 patch. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` From 1cff7a8b995709961a5a47b68bb55e07305b9816 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 22 Apr 2026 07:06:02 -0400 Subject: [PATCH 099/750] Use a different file descriptor for fake sockets (#44566) Using file descriptor 42 caused tests that use FakeConnectionSocket to be flaky internally. File descriptor with value 42 could be used by internal runtime and closing the descriptor caused tests errors due to infra. Pick a descriptor number that is much less likely to be in use. The other fix is to actually open a new socket and let OS pick descriptor, but it has a negative impact on fuzzing perf. Risk Level: low Testing: unit tests Docs Changes: no Release Notes: no Platform Specific Features: no Signed-off-by: Yan Avlasov --- .../filters/listener/common/fuzz/listener_filter_fakes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/filters/listener/common/fuzz/listener_filter_fakes.h b/test/extensions/filters/listener/common/fuzz/listener_filter_fakes.h index e6a24b2a04788..1e92188107264 100644 --- a/test/extensions/filters/listener/common/fuzz/listener_filter_fakes.h +++ b/test/extensions/filters/listener/common/fuzz/listener_filter_fakes.h @@ -11,7 +11,7 @@ namespace Envoy { namespace Extensions { namespace ListenerFilters { -static constexpr int kFakeSocketFd = 42; +static constexpr int kFakeSocketFd = 65534; class FakeConnectionSocket : public Network::MockConnectionSocket { public: From 54375ed3f8ee9bb0814c3ace617e14990347e6ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:12:49 +0100 Subject: [PATCH 100/750] build(deps): bump debian from `4ffb3a1` to `cedb1ef` in /ci (#44568) Bumps debian from `4ffb3a1` to `cedb1ef`. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=debian&package-manager=docker&previous-version=trixie-slim&new-version=trixie-slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ci/Dockerfile-distroless-testing | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-distroless-testing b/ci/Dockerfile-distroless-testing index b17186ec5f859..f7ca162875574 100644 --- a/ci/Dockerfile-distroless-testing +++ b/ci/Dockerfile-distroless-testing @@ -1,4 +1,4 @@ -FROM debian:trixie-slim@sha256:4ffb3a1511099754cddc70eb1b12e50ffdb67619aa0ab6c13fcd800a78ef7c7a as debian-base +FROM debian:trixie-slim@sha256:cedb1ef40439206b673ee8b33a46a03a0c9fa90bf3732f54704f99cb061d2c5a as debian-base FROM envoyproxy/envoy:distroless-dev as distroless-dev From 9239fe7bb60fbb1fb39aa817eadab2581ac85af2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:13:25 +0100 Subject: [PATCH 101/750] build(deps): bump gitpython from 3.1.46 to 3.1.47 in /tools/base (#44569) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index a0200d531db1e..ac0baa30de4a6 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -767,9 +767,9 @@ gitdb==4.0.11 \ --hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \ --hash=sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b # via gitpython -gitpython==3.1.46 \ - --hash=sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f \ - --hash=sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058 +gitpython==3.1.47 \ + --hash=sha256:489f590edfd6d20571b2c0e72c6a6ac6915ee8b8cd04572330e3842207a78905 \ + --hash=sha256:dba27f922bd2b42cb54c87a8ab3cb6beb6bf07f3d564e21ac848913a05a8a3cd # via -r requirements.in humanfriendly==10.0 \ --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 \ From 28cf244802ff8b2fc48e3bd46744ef3f20747e41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:14:10 +0100 Subject: [PATCH 102/750] build(deps): bump actions/checkout from 6.0.0 to 6.0.2 (#44527) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_cve_fetch.yml | 2 +- .github/workflows/_cve_scan.yml | 2 +- .github/workflows/_precheck_deps.yml | 2 +- .github/workflows/codeql-daily.yml | 2 +- .github/workflows/codeql-push.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/envoy-dependency.yml | 4 ++-- .github/workflows/envoy-security-check.yml | 2 +- .github/workflows/mobile-release.yml | 2 +- .github/workflows/pr_notifier.yml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/toolchain-test.yml | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/_cve_fetch.yml b/.github/workflows/_cve_fetch.yml index 016136242ff8b..ba7c60fa84424 100644 --- a/.github/workflows/_cve_fetch.yml +++ b/.github/workflows/_cve_fetch.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set vars id: vars run: | diff --git a/.github/workflows/_cve_scan.yml b/.github/workflows/_cve_scan.yml index c59b8beb5dad5..0237e10b812af 100644 --- a/.github/workflows/_cve_scan.yml +++ b/.github/workflows/_cve_scan.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set vars id: vars run: | diff --git a/.github/workflows/_precheck_deps.yml b/.github/workflows/_precheck_deps.yml index 6c59b976c4c6a..cdd1d274c5d4c 100644 --- a/.github/workflows/_precheck_deps.yml +++ b/.github/workflows/_precheck_deps.yml @@ -52,7 +52,7 @@ jobs: if: ${{ inputs.dependency-review }} steps: - name: Checkout Repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ fromJSON(inputs.request).request.sha }} persist-credentials: false diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 9a09963776e9b..89f88841db3a0 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -49,7 +49,7 @@ jobs: /usr/local/lib/android - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Initialize CodeQL uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # codeql-bundle-v4.35.2 diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 992efe7b85759..e724b6e7dcd7b 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -55,7 +55,7 @@ jobs: /usr/local/lib/android - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 1517db576c565..09661ae639023 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -21,7 +21,7 @@ jobs: sudo rm -rf /usr/local/lib/android & sudo rm -rf /usr/share/dotnet & - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install deps shell: bash run: | diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 4f66e79aa33ee..6a286db746779 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -146,7 +146,7 @@ jobs: path: envoy fetch-depth: 0 token: ${{ steps.appauth.outputs.token }} - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 name: Checkout Envoy build tools repository with: repository: envoyproxy/envoy-build-tools @@ -251,7 +251,7 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run dependency checker run: | TODAY_DATE=$(date -u -I"date") diff --git a/.github/workflows/envoy-security-check.yml b/.github/workflows/envoy-security-check.yml index fd8d836557ce4..ebd4912c0b29c 100644 --- a/.github/workflows/envoy-security-check.yml +++ b/.github/workflows/envoy-security-check.yml @@ -101,7 +101,7 @@ jobs: # SLACK - name: Checkout repository (secure branch) if: matrix.action == 'slack' - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: # Explicitly checkout main to avoid malicious code ref: main diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index f4cc3fb123a34..3353a64a076e9 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -107,7 +107,7 @@ jobs: include: - output: envoy steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Add safe directory diff --git a/.github/workflows/pr_notifier.yml b/.github/workflows/pr_notifier.yml index 2f01a2e829b87..11ec3988c4c80 100644 --- a/.github/workflows/pr_notifier.yml +++ b/.github/workflows/pr_notifier.yml @@ -24,7 +24,7 @@ jobs: || !contains(github.actor, '[bot]')) }} steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Notify about PRs run: | ARGS=() diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 901d2fc4c6e49..d5aac896d7e09 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -22,7 +22,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/toolchain-test.yml b/.github/workflows/toolchain-test.yml index bfbc731637e00..7d726bfc5ad77 100644 --- a/.github/workflows/toolchain-test.yml +++ b/.github/workflows/toolchain-test.yml @@ -32,7 +32,7 @@ jobs: name: "Test: ${{ matrix.name }}" steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run matrix test run: | cd ci/matrix From 4793b4cff26999c6f078eb775b3e003c90f9ac12 Mon Sep 17 00:00:00 2001 From: Thomas Gschwendtner <35153175+tgschwen@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:23:54 -0400 Subject: [PATCH 103/750] Add Paul Ogilby and Peng Gao to security team (#44562) Add Paul Ogilby and Peng Gao to the Envoy security team in OWNERS.md Signed-off-by: Thomas Gschwendtner <35153175+tgschwen@users.noreply.github.com> --- OWNERS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OWNERS.md b/OWNERS.md index fbe042ac23ac7..b0a0ef8cf9e0f 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -102,6 +102,8 @@ without further review. * Antonio Leonti ([antoniovleonti](https://github.com/antoniovleonti)) (leonti@google.com) * Takeshi Yoneda ([mathetake](https://github.com/mathetake)) (tyoneda@netflix.com) * Reuben Tanner ([reubent-goog](https://github.com/reubent-goog)) (reubent@google.com) +* Paul Ogilby ([paul-r-gall](https://github.com/paul-r-gall)) (pgal@google.com) +* Peng Gao ([penguingao](https://github.com/penguingao)) (pengg@google.com) # Emeritus maintainers From c979a44b214163b51f028402828cbabadac57658 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:24:49 +0100 Subject: [PATCH 104/750] deps: Bump `fips_cmake_linux_x86_64` -> 4.3.2 (#44577) Fix #44572 Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index c3a4a8c0fcbed..515b75485f6a5 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -841,7 +841,7 @@ fips_cmake_linux_x86_64: project_name: "CMake (Linux x86_64)" project_desc: "Cross-platform family of tools designed to build, test and package software" project_url: "https://cmake.org/" - release_date: "2026-03-27" + release_date: "2026-04-21" use_category: - build - dataplane_core diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 26d70a5227a71..f1338a664cdbf 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -708,8 +708,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/ninja-build/ninja/archive/refs/tags/v{version}.tar.gz"], ), fips_cmake_linux_x86_64 = dict( - version = "4.3.1", - sha256 = "208d76804009cbe8ec9aea0aa052c857c6e59bd289b43b9941c99324dc78b1d8", + version = "4.3.2", + sha256 = "791ae3604841ca03cb3889a3ad89165346e4b180ae3448efd4b0caa9ef46d245", strip_prefix = "cmake-{version}-linux-x86_64", urls = ["https://github.com/Kitware/CMake/releases/download/v{version}/cmake-{version}-linux-x86_64.tar.gz"], ), From 85bc6e3b2d98dbffb44a1fd0acf49fbc3527e1b8 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:25:24 +0100 Subject: [PATCH 105/750] deps: Bump `fips_cmake_linux_aarch64` -> 4.3.2 (#44578) Fix #44571 Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 515b75485f6a5..e528da5e00aa7 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -828,7 +828,7 @@ fips_cmake_linux_aarch64: project_name: "CMake (Linux aarch64)" project_desc: "Cross-platform family of tools designed to build, test and package software" project_url: "https://cmake.org/" - release_date: "2026-03-27" + release_date: "2026-04-21" use_category: - build - dataplane_core diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index f1338a664cdbf..cde29ed8429e6 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -714,8 +714,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/Kitware/CMake/releases/download/v{version}/cmake-{version}-linux-x86_64.tar.gz"], ), fips_cmake_linux_aarch64 = dict( - version = "4.3.1", - sha256 = "2c0eca48ac7d0e3a8b4120b801d48903b0630c8ff1e73c44a90398a300dec1ac", + version = "4.3.2", + sha256 = "377079ab739f5765176f427609d9a2015b756ea20d5cba908d279c3731a2f481", strip_prefix = "cmake-{version}-linux-aarch64", urls = ["https://github.com/Kitware/CMake/releases/download/v{version}/cmake-{version}-linux-aarch64.tar.gz"], ), From f640689e6fd77f8aed4cd63f941b67eb3ada0837 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 22 Apr 2026 12:53:58 +0100 Subject: [PATCH 106/750] deps/cve: Exclude CVE-2026-27135 (#44579) as patch applied Signed-off-by: Ryan Northey --- tools/dependency/cve.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/dependency/cve.yaml b/tools/dependency/cve.yaml index b49e2a832f584..b555cee92dabf 100644 --- a/tools/dependency/cve.yaml +++ b/tools/dependency/cve.yaml @@ -52,3 +52,5 @@ ignored_cves: # This CVE assumes an untrusted client that can affect a shared connection between an intervening proxy and # gRPC server, and does not affect Envoy directly - CVE-2023-32732 +# Patch applied https://github.com/envoyproxy/envoy/pull/44565 +- CVE-2026-27135 From 011a587518e6fd834a661e172afe0ea50cbb59e0 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 22 Apr 2026 12:56:49 +0100 Subject: [PATCH 107/750] distribution/docker: Bump ubuntu 22.04 -> 962f6ca (#44580) Signed-off-by: Ryan Northey --- distribution/docker/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/docker/Dockerfile-envoy b/distribution/docker/Dockerfile-envoy index 68600b3a7d2e9..5201aa96bcd22 100644 --- a/distribution/docker/Dockerfile-envoy +++ b/distribution/docker/Dockerfile-envoy @@ -1,6 +1,6 @@ ARG BUILD_OS=ubuntu ARG BUILD_TAG=22.04 -ARG BUILD_SHA=eb29ed27b0821dca09c2e28b39135e185fc1302036427d5f4d70a41ce8fd7659 +ARG BUILD_SHA=962f6cadeae0ea6284001009daa4cc9a8c37e75d1f5191cf0eb83fe565b63dd7 ARG ENVOY_VRP_BASE_IMAGE=envoy-base From 080522bf396dd344fe28dfc98c29eea4e7fab012 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 22 Apr 2026 15:07:08 +0100 Subject: [PATCH 108/750] test/idle_timeout: Fix flake (#44584) Scale RequestTimeoutMs by TIMEOUT_FACTOR to match IdleTimeoutMs. The hard-coded 200ms races test setup on loaded/sanitizer runs, causing the request timer to fire before the test body executes. fix #26979 Signed-off-by: Ryan Northey --- test/integration/idle_timeout_integration_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/idle_timeout_integration_test.cc b/test/integration/idle_timeout_integration_test.cc index cc21a60f330b6..09dc7d35298b0 100644 --- a/test/integration/idle_timeout_integration_test.cc +++ b/test/integration/idle_timeout_integration_test.cc @@ -125,7 +125,7 @@ class IdleTimeoutIntegrationTest : public HttpProtocolIntegrationTest { } static constexpr uint64_t IdleTimeoutMs = 300 * TIMEOUT_FACTOR; - static constexpr uint64_t RequestTimeoutMs = 200; + static constexpr uint64_t RequestTimeoutMs = 200 * TIMEOUT_FACTOR; bool enable_global_idle_timeout_{false}; bool enable_per_stream_idle_timeout_{false}; bool enable_request_timeout_{false}; From be0fc28eb4e082e963f922c57fe0e55094a839ef Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 22 Apr 2026 15:25:12 +0100 Subject: [PATCH 109/750] changelogs: Pre-release cleanups (#44581) Signed-off-by: Ryan Northey --- changelogs/current.yaml | 67 +++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2773102575eab..21d26fe8a348a 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -105,7 +105,7 @@ minor_behavior_changes: Added ``received_immediate_response`` flag in the ``ExtProcLoggingInfo`` filter state. - area: happy_eyeballs change: | - Happy Eyeballs handles interleaving of non-IP addresses. The restriction against ``additional_addresses`` + Happy Eyeballs now handles interleaving of non-IP addresses. The restriction against ``additional_addresses`` containing non-IP addresses is removed. This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.happy_eyeballs_sort_non_ip_addresses`` to ``false``. - area: ext_authz @@ -124,7 +124,7 @@ minor_behavior_changes: to the OAuth2 HTTP filter. - area: oauth2 change: | - The oauth2 filter now removes the OauthExpires cookie from a user's browser upon logging out. + The OAuth2 filter now removes the ``OauthExpires`` cookie from a user's browser upon logging out. Previously this cookie persisted beyond the logout despite the session no longer existing. - area: proto_api_scrubber change: | @@ -133,10 +133,6 @@ minor_behavior_changes: is set to ``true`` in the :ref:`ProtoApiScrubberConfig `, unknown fields will now be scrubbed. This is disabled by default. -- area: stat_sinks - change: | - OpenTelemetry :ref:`SinkConfig ` - stopped reporting empty delta counters and histograms. - area: http change: | The ``route()``, ``clusterInfo()`` and ``virtualHost()`` methods on the HTTP filter callbacks @@ -162,17 +158,16 @@ minor_behavior_changes: ``TokenBucket``. - area: http change: | - Enable strict parsing of HTTP/1 chunked encoding. Strict parsing is disabled by default and can be - enabled by setting the ``envoy.reloadable_features.mcp_filter_use_new_metadata_namespace`` runtime - flag to ``false``. + Strict parsing of HTTP/1 chunked encoding is now available. It is disabled by default and can be + enabled by setting the runtime guard ``envoy.reloadable_features.strict_chunk_parsing`` to ``true``. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* - area: grpc change: | - Fixed the router to return ``DEADLINE_EXCEEDED`` (gRPC status 4) instead of ``UNAVAILABLE`` (gRPC - status 14) when the router itself enforces a gRPC request timeout. Previously, the router sent an - HTTP 504 local reply which was mapped to ``UNAVAILABLE`` via the standard HTTP-to-gRPC status mapping, + Fixed the router to return ``DEADLINE_EXCEEDED`` (gRPC status ``4``) instead of ``UNAVAILABLE`` (gRPC + status ``14``) when the router itself enforces a gRPC request timeout. Previously, the router sent an + HTTP ``504`` local reply which was mapped to ``UNAVAILABLE`` via the standard HTTP-to-gRPC status mapping, causing clients to incorrectly retry requests that had already timed out. This behavior is disabled by default and can be enabled by setting the runtime guard ``envoy.reloadable_features.grpc_timeout_returns_deadline_exceeded`` to ``true``. @@ -189,7 +184,7 @@ bug_fixes: change: | Fixed a crash in scoped RDS when on-demand update is triggered for a scope that uses inline ``route_configuration`` instead of ``route_configuration_name``. The null RDS provider dereference - is now guarded and the on-demand callback returns false for inline-config scopes. + is now guarded and the on-demand callback returns ``false`` for inline-config scopes. - area: hot_restart change: | Fixed hot restart for listeners with a network namespace in the address. Previously, socket @@ -273,7 +268,7 @@ bug_fixes: - area: oauth2 change: | Fixed OAuth2 refresh requests so host rewriting no longer overrides the original ``Host`` header value. -- area: watch-dog +- area: watchdog change: | Fixed a bug where the worker thread watchdogs were configured using the main thread's configuration. This change can be reverted by setting the runtime guard ``envoy.restart_features.worker_threads_watchdog_fix`` @@ -293,7 +288,7 @@ bug_fixes: ``envoy.reloadable_features.cel_message_serialize_text_format`` to ``false``. - area: ext_proc change: | - Fixed a bug that unnecessary empty data chunks are processed by the filter chain. This + Fixed a bug where unnecessary empty data chunks are processed by the filter chain. This change can be reverted by setting the runtime guard ``envoy.reloadable_features.ext_proc_return_stop_iteration`` to ``false``. - area: ratelimit @@ -304,7 +299,7 @@ bug_fixes: assertions to enforce correct usage patterns. - area: ext_authz change: | - Fixed a bug where headers from a denied authorization response (non-200) were not properly propagated + Fixed a bug where headers from a denied authorization response (non-``200``) were not properly propagated to the client. - area: formatter change: | @@ -324,12 +319,12 @@ bug_fixes: change: | Fixed a crash (SIGABRT) when destroying OdCDS handles on worker threads. The handle no longer holds a direct reference to the subscription, preventing thread-safety issues during destruction. The - subscription now persists in ClusterManagerImpl and is looked up by a config source key. + subscription now persists in ``ClusterManagerImpl`` and is looked up by a config source key. - area: ext_authz change: | Fixed the HTTP ext_authz client to respect ``status_on_error`` configuration when the authorization server returns a 5xx error or when HTTP call failures occur. Previously, these error scenarios always - returned 403 Forbidden regardless of the configured error status. + returned ``403 Forbidden`` regardless of the configured error status. - area: release change: | Published contrib binaries now include the ``-contrib`` suffix in their version string. @@ -342,7 +337,7 @@ bug_fixes: - area: mcp_router change: | Fixed MCP router to support session-less backends that do not return ``mcp-session-id`` - headers. Previously, this caused a spurious 500 error. + headers. Previously, this caused a spurious ``500`` error. - area: health_check change: | Fixed a race condition where active health checks could start before required upstream TLS SDS secrets @@ -351,8 +346,8 @@ bug_fixes: to ``false``. - area: upstream change: | - Fixed an out-of-bounds issue in ThreadAwareLoadBalancerBase that could occur during mid-batch EDS host updates - due to eagerly calling refresh() before the deferred priority state resize. + Fixed an out-of-bounds issue in ``ThreadAwareLoadBalancerBase`` that could occur during mid-batch EDS host updates + due to eagerly calling ``refresh()`` before the deferred priority state resize. - area: upstream change: | Fixed ``ProdClusterInfoFactory`` of HDS to honor ``alt_stat_name`` when creating cluster stats scopes, so @@ -386,7 +381,7 @@ bug_fixes: ``decodeTrailers()``, and ``decodeMetadata()`` are blocked after a downstream reset. - area: dynamic_modules change: | - Fixed a bug where dynamic module filter may result in a incomplete body being sent to upstream + Fixed a bug where dynamic module filter may result in an incomplete body being sent to upstream or downstream when some filters before or after the dynamic module filter in the chain buffered the body and the dynamic module filter did not. - area: io_socket @@ -458,8 +453,8 @@ new_features: change: | Added :ref:`ShrinkHeapConfig ` typed configuration for the ``envoy.overload_actions.shrink_heap`` overload action. This allows - operators to configure the timer interval (``timer_interval``, minimum 1s, default 10s) and - the memory release threshold (``max_unfreed_memory_bytes``, default 100MB) passed to + operators to configure the timer interval (``timer_interval``, minimum ``1s``, default ``10s``) and + the memory release threshold (``max_unfreed_memory_bytes``, default ``100MB``) passed to ``tcmalloc::MallocExtension::ReleaseMemoryToSystem()``. - area: dns_resolver change: | @@ -483,7 +478,7 @@ new_features: - area: tls change: | Added a per-connection filter state object to select a workload trust domain in the SPIFFE validator in - the multi-tenant deployments. + multi-tenant deployments. - area: tls change: | Extended TLS certificate compression (RFC 8879): added brotli to QUIC (which already supported zlib), @@ -506,7 +501,7 @@ new_features: change: | Rust SDK now provides an opt-in ``CatchUnwind`` wrapper for filter callbacks. When a wrapped callback panics, Envoy logs the panic and returns a fail-closed error (e.g. - HTTP 500, stream reset, connection close) instead of aborting the process. + HTTP ``500``, stream reset, connection close) instead of aborting the process. - area: http_11_proxy change: | Added ability to configure a default proxy address that is used when the proxy address is not @@ -519,7 +514,7 @@ new_features: ``response_direction_config``, strong ``ETag`` response headers are weakened (``W/`` prefix) instead of removed when compression is applied, allowing caches and conditional requests to work while indicating the body was modified by compression. When both ``weaken_etag_on_compress`` - and ``disable_on_etag_header`` are true, the new field takes precedence. + and ``disable_on_etag_header`` are ``true``, the new field takes precedence. - area: golang change: | Added ``DownstreamSslConnection()`` method to the Golang HTTP filter's ``StreamInfo`` interface, @@ -594,7 +589,7 @@ new_features: - area: stats change: | Added support for cluster-level stats matcher, allowing more granular control over which stats - are enabled and reported at the cluster level. This the stats matcher could be configured via + are enabled and reported at the cluster level. The stats matcher could be configured via the xDS API dynamically on a per-cluster basis. See :ref:`envoy.stats_matcher ` for more details. - area: stats @@ -617,26 +612,26 @@ new_features: - area: access_log change: | Added stats customization support for the :ref:`access logger `. -- area: dynamic modules +- area: dynamic_modules change: | Introduced the extended ABI forward compatibility mechanism for dynamic modules where modules built with an SDK version can be loaded by Envoy binaries of the next Envoy version. For example, a module built with the v1.38 SDK can now be loaded by an Envoy binary of v1.39. -- area: dynamic modules +- area: dynamic_modules change: | Added drain and shutdown lifecycle hooks for bootstrap dynamic modules. -- area: dynamic modules +- area: dynamic_modules change: | Added support for dynamic modules authors to register any combination of HTTP, network, listener, UDP listener, and bootstrap filters in the Rust SDK. -- area: dynamic modules +- area: dynamic_modules change: | Added connection state and flow control ABI callbacks for the dynamic module network filter, including ``read_disable``, ``read_enabled``, ``get_connection_state``, ``enable_half_close``, ``is_half_close_enabled``, ``get_buffer_limit``, ``set_buffer_limits``, and ``above_high_watermark``. -- area: dynamic modules +- area: dynamic_modules change: | Added socket property getter and SSL/TLS information ABI callbacks for the dynamic module listener filter, including ``get_requested_server_name``, ``get_detected_transport_protocol``, @@ -1030,7 +1025,7 @@ new_features: change: | Added support for MCP ``tools/call`` response transcoding. Support for ``tools/list`` is planned. This filter is currently a work-in-progress and not recommended for production use. -- area: http_filter +- area: http change: | Added support for clear route cache in the :ref:`set_filter_state http filter `. When ``clear_route_cache`` is set, the filter will clear the route cache for the current request after applying filter state updates. @@ -1048,14 +1043,14 @@ new_features: change: | Propagate upstream TCP RST to downstream when detected close type is RemoteReset. This behavioral change can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.propagate_upstream_rst_through_tunneled_tcp_proxy`` to false. + ``envoy.reloadable_features.propagate_upstream_rst_through_tunneled_tcp_proxy`` to ``false``. - area: tcp_proxy change: | HttpUpstream now maps remote-originated HTTP stream reset reasons (RemoteReset, RemoteRefusedStreamReset, RemoteConnectionFailure) to RemoteClose with DetectedCloseType::RemoteReset, enabling RST propagation through tunneled tcp_proxy. This behavioral change can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.map_http_stream_reset_to_tcp_rst`` to false. + ``envoy.reloadable_features.map_http_stream_reset_to_tcp_rst`` to ``false``. - area: ratelimit change: | Support populating rate limit descriptors from cluster metadata. From f1d9c1dead35e1dd39bdf7019ca26f2939e50aca Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 22 Apr 2026 16:15:31 +0100 Subject: [PATCH 110/750] deps: Bump `rustls-webpki` -> 0.103.13 (#44586) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit address RUSTSEC-2026-0104 / GHSA-82j2-j2ch-gfr8 Updates all three Cargo lockfiles to use rustls-webpki 0.103.13 (from 0.103.12), addressing the reachable panic in certificate revocation list parsing (RUSTSEC-2026-0104 / GHSA-82j2-j2ch-gfr8). This clears code-scanning alert #378. The change is lockfile-only (no Cargo.toml modifications). rustls-webpki is a transitive dependency via: hickory-resolver → rustls 0.23.37 → rustls-webpki. Signed-off-by: Ryan Northey --- Cargo.Bazel.lock | 14 +++++++------- Cargo.lock | 4 ++-- docs/Cargo.Bazel.lock | 14 +++++++------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.Bazel.lock b/Cargo.Bazel.lock index f1fddba3d8089..d1d5a91f5ad27 100644 --- a/Cargo.Bazel.lock +++ b/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "f853e38d6b1ff233bd49d987189c7baf375aecdb13730d651420d43946303a37", + "checksum": "9e9be21e7a4781243f0bd2c76b8dfcb754ff750302be2a13d401ef862b6b3297", "crates": { "aho-corasick 1.1.4": { "name": "aho-corasick", @@ -6532,7 +6532,7 @@ "alias": "pki_types" }, { - "id": "rustls-webpki 0.103.12", + "id": "rustls-webpki 0.103.13", "target": "webpki" }, { @@ -6633,14 +6633,14 @@ ], "license_file": "LICENSE-APACHE" }, - "rustls-webpki 0.103.12": { + "rustls-webpki 0.103.13": { "name": "rustls-webpki", - "version": "0.103.12", + "version": "0.103.13", "package_url": "https://github.com/rustls/webpki", "repository": { "Http": { - "url": "https://static.crates.io/crates/rustls-webpki/0.103.12/download", - "sha256": "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" + "url": "https://static.crates.io/crates/rustls-webpki/0.103.13/download", + "sha256": "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" } }, "targets": [ @@ -6689,7 +6689,7 @@ "selects": {} }, "edition": "2021", - "version": "0.103.12" + "version": "0.103.13" }, "license": "ISC", "license_ids": [ diff --git a/Cargo.lock b/Cargo.lock index 9f195d662c32f..87d71e4286d23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -998,9 +998,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.12" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", diff --git a/docs/Cargo.Bazel.lock b/docs/Cargo.Bazel.lock index 7369c489c847e..6890f21b2c208 100644 --- a/docs/Cargo.Bazel.lock +++ b/docs/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "6c229299d001117048a532a667e66e1943ca53f008d36013a5fd3f15ebd71189", + "checksum": "928a2ef1c0edb34383ab8517c21ab5ccf9e21205b5abc240a3a2309094117da0", "crates": { "aho-corasick 1.1.4": { "name": "aho-corasick", @@ -6532,7 +6532,7 @@ "alias": "pki_types" }, { - "id": "rustls-webpki 0.103.12", + "id": "rustls-webpki 0.103.13", "target": "webpki" }, { @@ -6633,14 +6633,14 @@ ], "license_file": "LICENSE-APACHE" }, - "rustls-webpki 0.103.12": { + "rustls-webpki 0.103.13": { "name": "rustls-webpki", - "version": "0.103.12", + "version": "0.103.13", "package_url": "https://github.com/rustls/webpki", "repository": { "Http": { - "url": "https://static.crates.io/crates/rustls-webpki/0.103.12/download", - "sha256": "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" + "url": "https://static.crates.io/crates/rustls-webpki/0.103.13/download", + "sha256": "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" } }, "targets": [ @@ -6689,7 +6689,7 @@ "selects": {} }, "edition": "2021", - "version": "0.103.12" + "version": "0.103.13" }, "license": "ISC", "license_ids": [ From 3ac01b612b44996807eaa37385233ce80d3d41d0 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 22 Apr 2026 16:43:29 +0100 Subject: [PATCH 111/750] test/ext_proc: Fix spinning loop (and hang) in serverReceiveBodyDuplexStreamed (#44583) by using early-return on failure i have been increasing sharding - but it seems in reality the test is just hanging we possibly want to decrease the shards if this works --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- .../ext_proc_full_duplex_integration_test.cc | 23 +++++++++++++++---- .../ext_proc/ext_proc_integration_common.cc | 15 +++++++++--- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc index f01ede8523b30..217075bee2485 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc @@ -100,7 +100,10 @@ TEST_P(ExtProcIntegrationTest, LargeBodyTestDuplexStreamed) { const std::string body_sent(2 * 1024 * 1024, 's'); initializeConfigDuplexStreamed(false); - // Sends 30 consecutive request, each carrying 2MB data. + // Sends 30 consecutive requests, each carrying 2MB data. + // The processor gRPC connection is established once on the first iteration and reused across all + // iterations; each iteration opens a new gRPC stream on that persistent connection. This matches + // real ext_proc behavior and avoids the per-iteration connection-churn race. for (int i = 0; i < 30; i++) { codec_client_ = makeHttpConnection(lookupPort("http")); Http::TestRequestHeaderMapImpl default_headers; @@ -111,13 +114,21 @@ TEST_P(ExtProcIntegrationTest, LargeBodyTestDuplexStreamed) { request_encoder_ = &encoder_decoder.first; IntegrationStreamDecoderPtr response = std::move(encoder_decoder.second); codec_client_->sendData(*request_encoder_, body_sent, true); - // The ext_proc server receives the headers. + + // On the first iteration, wait for the gRPC connection to be established. + if (i == 0) { + EXPECT_TRUE(grpc_upstreams_[0]->waitForHttpConnection(*dispatcher_, processor_connection_)); + } + // Each iteration opens a new gRPC stream on the persistent processor connection. ProcessingRequest header_request; - serverReceiveHeaderReq(header_request); + EXPECT_TRUE(processor_connection_->waitForNewStream(*dispatcher_, processor_stream_)); + EXPECT_TRUE(processor_stream_->waitForGrpcMessage(*dispatcher_, header_request)); + EXPECT_TRUE(header_request.has_request_headers()); + // The ext_proc server receives the body. uint32_t total_req_body_msg = serverReceiveBodyDuplexStreamed(body_sent, processor_stream_); EXPECT_GT(total_req_body_msg, 0); - // The ext_proc server sends back the header response. + // The ext_proc server sends back the header response (startGrpcStream is per-stream). serverSendHeaderResp(); // The ext_proc server sends back body responses, which include 50 chunks, // and each chunk contains 64KB data, thus totally ~3MB per request. @@ -131,7 +142,9 @@ TEST_P(ExtProcIntegrationTest, LargeBodyTestDuplexStreamed) { EXPECT_THAT(upstream_request_->headers(), ContainsHeader("x-new-header", "new")); EXPECT_EQ(upstream_request_->body().toString(), body_upstream); verifyDownstreamResponse(*response, 200); - TearDown(); + + // Clean up per-iteration upstream/downstream state; processor_connection_ stays alive. + cleanupUpstreamAndDownstream(); } } diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc index 25605c33f1a1d..636e8e163a5f0 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc @@ -841,12 +841,18 @@ uint32_t ExtProcIntegrationTest::serverReceiveBodyDuplexStreamed(absl::string_vi uint32_t total_req_body_msg = 0; while (!end_stream) { ProcessingRequest body_request; - EXPECT_TRUE(processor_stream->waitForGrpcMessage(*dispatcher_, body_request)); + if (!processor_stream->waitForGrpcMessage(*dispatcher_, body_request)) { + ADD_FAILURE() << "Timed out waiting for gRPC message in serverReceiveBodyDuplexStreamed"; + return total_req_body_msg; + } if (response) { if (body_request.has_response_trailers()) { end_stream = true; } else { - EXPECT_TRUE(body_request.has_response_body()); + if (!body_request.has_response_body()) { + ADD_FAILURE() << "Expected response body message but got unexpected message type"; + return total_req_body_msg; + } body_received = absl::StrCat(body_received, body_request.response_body().body()); end_stream = body_request.response_body().end_of_stream(); total_req_body_msg++; @@ -855,7 +861,10 @@ uint32_t ExtProcIntegrationTest::serverReceiveBodyDuplexStreamed(absl::string_vi if (body_request.has_request_trailers()) { end_stream = true; } else { - EXPECT_TRUE(body_request.has_request_body()); + if (!body_request.has_request_body()) { + ADD_FAILURE() << "Expected request body message but got unexpected message type"; + return total_req_body_msg; + } body_received = absl::StrCat(body_received, body_request.request_body().body()); end_stream = body_request.request_body().end_of_stream(); total_req_body_msg++; From 638a32e6a7454495cfba256c5e424e04cbd4d163 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 22 Apr 2026 17:44:47 +0100 Subject: [PATCH 112/750] test/ext_proc: Revert incorrect sharding (#44588) Signed-off-by: Ryan Northey --- test/extensions/filters/http/ext_proc/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index ee473f563b37e..03adae9be4cae 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -471,7 +471,7 @@ envoy_extension_cc_test( "envoy.filters.http.set_metadata", ], rbe_pool = "4core", - shard_count = 16, + shard_count = 4, tags = [ "cpu:3", "skip_on_windows", From c2117809b2a44ad619896cb784aa0f53c6b7b493 Mon Sep 17 00:00:00 2001 From: Florent Lecoultre <104759954+fl0Lec@users.noreply.github.com> Date: Wed, 22 Apr 2026 21:25:26 +0200 Subject: [PATCH 113/750] conn_pool: add upstream_rq_active_overflow counter (#44164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Problem `upstream_rq_pending_overflow` is currently incremented in two fundamentally different failure modes inside `conn_pool_base.cc`: 1. **Pending queue full** — in `newStreamImpl()`, when `pendingRequests().canCreate()` returns false. The request is rejected before ever being queued; this is a true pending overflow. 2. **Max active requests exceeded** — in `attachStreamToClient()`, when `requests().canCreate()` returns false. The request has already been dequeued and is in the process of being attached to a ready upstream connection; this is an active request overflow, not a pending one. Labelling path 2 as a "pending overflow" is semantically incorrect. More importantly, there is currently no way to distinguish between the two from metrics alone. ### Motivation This ambiguity makes incident investigation significantly harder. In practice, when `max_active_requests` is the bottleneck, `upstream_rq_pending_active` reads near zero while `upstream_rq_pending_overflow` fires continuously. An operator has no way to know from the metric alone which circuit breaker is responsible, leading to misdiagnosis and wasted time. ### What this PR does 1. **Adds `upstream_rq_active_overflow`** — a new counter incremented exclusively when path 2 overflows (`attachStreamToClient()`, `max_requests` circuit breaker). This gives operators a dedicated, unambiguous signal for active request saturation. 2. **Fixes `upstream_rq_pending_overflow`** — removes the spurious increment on path 2, making it exclusive to the pending queue saturation case. This is guarded behind runtime flag `envoy.reloadable_features.skip_pending_overflow_count_on_active_rq` (default `true`) so existing dashboards and alerts continue to work. Operators can migrate to the new metric and then leave the flag enabled permanently. ## Tests - 2 new unit tests in `test/common/conn_pool/conn_pool_base_test.cc` (`MaxActiveRequestsOverflow`, `MaxActiveRequestsOverflowLegacy`) - 3 integration tests in `test/integration/circuit_breakers_integration_test.cc` updated to assert `upstream_rq_active_overflow == 1` and `upstream_rq_pending_overflow == 0` ## Docs & Changelog - `docs/root/configuration/upstream/cluster_manager/cluster_stats.rst`: new stat row added - `docs/root/intro/arch_overview/upstream/circuit_breaking.rst`: max_requests section updated - `changelogs/current.yaml`: bug_fix entry added --- Commit Message: conn_pool: add upstream_rq_active_overflow counter Additional Description: In `attachStreamToClient()`, requests rejected due to the `max_requests` circuit breaker now increment `upstream_rq_active_overflow` instead of `upstream_rq_pending_overflow`, making the two circuit breakers distinguishable via metrics. Risk Level: Low Testing: Unit tests and integration tests added/updated Docs Changes: Updated cluster_stats.rst and circuit_breaking.rst Release Notes: Added to changelogs/current.yaml under bug_fixes Platform Specific Features: Runtime guard: `envoy.reloadable_features.skip_pending_overflow_count_on_active_rq` (default true; set false to restore the previous behavior of incrementing `upstream_rq_pending_overflow` on both code paths) --------- Signed-off-by: Florent Lecoultre --- changelogs/current.yaml | 10 +++++ .../cluster_manager/cluster_stats.rst | 1 + .../upstream/circuit_breaking.rst | 9 +++- envoy/upstream/upstream.h | 1 + source/common/conn_pool/conn_pool_base.cc | 9 +++- source/common/conn_pool/conn_pool_base.h | 3 ++ source/common/runtime/runtime_features.cc | 1 + test/common/conn_pool/conn_pool_base_test.cc | 44 +++++++++++++++++++ .../aggregate/cluster_integration_test.cc | 10 ++--- .../circuit_breakers_integration_test.cc | 9 ++-- 10 files changed, 85 insertions(+), 12 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 21d26fe8a348a..af4aa48ea8abe 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -37,6 +37,16 @@ behavior_changes: minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* +- area: conn_pool + change: | + Added new ``upstream_rq_active_overflow`` counter incremented when a request is rejected + due to the ``max_requests`` circuit breaker being exhausted in ``attachStreamToClient()``. + Previously this condition incorrectly incremented ``upstream_rq_pending_overflow``, making + it impossible to distinguish pending queue saturation from active request saturation via + metrics alone. The new counter is now the authoritative signal for this path by default; + set runtime flag ``envoy.reloadable_features.skip_pending_overflow_count_on_active_rq`` to + ``false`` to also increment ``upstream_rq_pending_overflow`` on this path, preserving + backwards compatibility with existing dashboards and alerts. - area: geoip change: | :ref:`Envoy Http Geoip filter extension diff --git a/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst index e7b6153e405d3..e3c0c9608f931 100644 --- a/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst +++ b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst @@ -79,6 +79,7 @@ Every cluster has a statistics tree rooted at *cluster..* with the followi upstream_rq_total, Counter, Total requests upstream_rq_active, Gauge, Total active requests upstream_rq_pending_total, Counter, Total requests pending a connection pool connection + upstream_rq_active_overflow, Counter, Total requests rejected because the ``max_requests`` circuit breaker was exhausted while attaching to a ready upstream connection (see ``envoy.reloadable_features.skip_pending_overflow_count_on_active_rq``) upstream_rq_pending_overflow, Counter, Total requests that overflowed connection pool or requests (mainly for HTTP/2 and above) circuit breaking and were failed upstream_rq_pending_failure_eject, Counter, Total requests that were failed due to a connection pool connection failure or remote connection termination upstream_rq_pending_active, Gauge, Total active requests pending a connection pool connection diff --git a/docs/root/intro/arch_overview/upstream/circuit_breaking.rst b/docs/root/intro/arch_overview/upstream/circuit_breaking.rst index 9096c5641ff82..6b347bf76260a 100644 --- a/docs/root/intro/arch_overview/upstream/circuit_breaking.rst +++ b/docs/root/intro/arch_overview/upstream/circuit_breaking.rst @@ -33,8 +33,13 @@ configure and code each application independently. Envoy supports various types :ref:`upstream_rq_pending_overflow ` counter for the cluster will increment. For HTTP/3 the equivalent to HTTP/2's :ref:`max concurrent streams ` is :ref:`max concurrent streams ` * **Cluster maximum requests**: The maximum number of requests that can be outstanding to all hosts - in a cluster at any given time. If this circuit breaker overflows the :ref:`upstream_rq_pending_overflow ` - counter for the cluster will increment. + in a cluster at any given time. If this circuit breaker overflows the + :ref:`upstream_rq_active_overflow ` counter for the cluster + will increment. By default, the legacy :ref:`upstream_rq_pending_overflow ` + counter is no longer incremented for this path; set the runtime flag + ``envoy.reloadable_features.skip_pending_overflow_count_on_active_rq`` to ``false`` to also + increment ``upstream_rq_pending_overflow`` on this path, preserving backwards compatibility + with existing dashboards and alerts. * **Cluster maximum active retries**: The maximum number of retries that can be outstanding to all hosts in a cluster at any given time. In general we recommend using :ref:`retry budgets `; however, if static circuit breaking is preferred it should aggressively circuit break retries. This is so that retries for sporadic failures are allowed, but the overall retry volume cannot diff --git a/envoy/upstream/upstream.h b/envoy/upstream/upstream.h index 99fdecb7ebf58..d593610adffe5 100644 --- a/envoy/upstream/upstream.h +++ b/envoy/upstream/upstream.h @@ -751,6 +751,7 @@ class PrioritySet { COUNTER(upstream_rq_completed) \ COUNTER(upstream_rq_maintenance_mode) \ COUNTER(upstream_rq_max_duration_reached) \ + COUNTER(upstream_rq_active_overflow) \ COUNTER(upstream_rq_pending_failure_eject) \ COUNTER(upstream_rq_pending_overflow) \ COUNTER(upstream_rq_pending_total) \ diff --git a/source/common/conn_pool/conn_pool_base.cc b/source/common/conn_pool/conn_pool_base.cc index 2c1aa0f6fd233..9827ade187de1 100644 --- a/source/common/conn_pool/conn_pool_base.cc +++ b/source/common/conn_pool/conn_pool_base.cc @@ -59,7 +59,9 @@ ConnPoolImplBase::ConnPoolImplBase( transport_socket_options_(transport_socket_options), cluster_connectivity_state_(state), upstream_ready_cb_(dispatcher_.createSchedulableCallback([this]() { onUpstreamReady(); })), create_new_connection_load_shed_(overload_manager.getLoadShedPoint( - Server::LoadShedPointName::get().ConnectionPoolNewConnection)) { + Server::LoadShedPointName::get().ConnectionPoolNewConnection)), + skip_pending_overflow_on_active_rq_(Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.skip_pending_overflow_count_on_active_rq")) { ENVOY_LOG_ONCE_IF(trace, create_new_connection_load_shed_ == nullptr, "LoadShedPoint envoy.load_shed_points.connection_pool_new_connection is not " "found. Is it configured?"); @@ -241,7 +243,10 @@ void ConnPoolImplBase::attachStreamToClient(Envoy::ConnectionPool::ActiveClient& ENVOY_LOG(debug, "max streams overflow"); onPoolFailure(client.real_host_description_, absl::string_view(), ConnectionPool::PoolFailureReason::Overflow, context); - traffic_stats.upstream_rq_pending_overflow_.inc(); + traffic_stats.upstream_rq_active_overflow_.inc(); + if (!skip_pending_overflow_on_active_rq_) { + traffic_stats.upstream_rq_pending_overflow_.inc(); + } return; } ENVOY_CONN_LOG(debug, "creating stream", client); diff --git a/source/common/conn_pool/conn_pool_base.h b/source/common/conn_pool/conn_pool_base.h index 278d484ddf491..e93d27b1d03a7 100644 --- a/source/common/conn_pool/conn_pool_base.h +++ b/source/common/conn_pool/conn_pool_base.h @@ -421,6 +421,9 @@ class ConnPoolImplBase : protected Logger::Loggable { Event::SchedulableCallbackPtr upstream_ready_cb_; Common::DebugRecursionChecker recursion_checker_; Server::LoadShedPoint* create_new_connection_load_shed_{nullptr}; + +protected: + bool skip_pending_overflow_on_active_rq_; }; } // namespace ConnectionPool diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index b7f9406d197dc..e9d2c7d87c409 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -96,6 +96,7 @@ RUNTIME_GUARD(envoy_reloadable_features_reset_ignore_upstream_reason); RUNTIME_GUARD(envoy_reloadable_features_reset_with_error); RUNTIME_GUARD(envoy_reloadable_features_safe_http2_options); RUNTIME_GUARD(envoy_reloadable_features_skip_dns_lookup_for_proxied_requests); +RUNTIME_GUARD(envoy_reloadable_features_skip_pending_overflow_count_on_active_rq); RUNTIME_GUARD(envoy_reloadable_features_strict_stats_matcher_unpacked); RUNTIME_GUARD(envoy_reloadable_features_tcp_proxy_odcds_over_ads_fix); RUNTIME_GUARD(envoy_reloadable_features_test_feature_true); diff --git a/test/common/conn_pool/conn_pool_base_test.cc b/test/common/conn_pool/conn_pool_base_test.cc index f2c1f5f6c1df6..ea227fddf9539 100644 --- a/test/common/conn_pool/conn_pool_base_test.cc +++ b/test/common/conn_pool/conn_pool_base_test.cc @@ -95,6 +95,7 @@ class TestConnPoolImplBase : public ConnPoolImplBase { (const Upstream::HostDescriptionConstSharedPtr& n, absl::string_view, ConnectionPool::PoolFailureReason, AttachContext&)); MOCK_METHOD(void, onPoolReady, (ActiveClient&, AttachContext&)); + void setSkipPendingOverflowForTest(bool value) { skip_pending_overflow_on_active_rq_ = value; } }; class ConnPoolImplBaseTest : public testing::Test { @@ -690,5 +691,48 @@ TEST_F(ConnPoolImplDispatcherBaseTest, PoolDrainsWithEarlyDataStreams) { closeStream(); } +// Test that when max_active_requests circuit breaker fires in attachStreamToClient(), +// upstream_rq_active_overflow is incremented and upstream_rq_pending_overflow is not +// (runtime flag enabled by default). +TEST_F(ConnPoolImplDispatcherBaseTest, MaxActiveRequestsOverflow) { + // Allow 2 concurrent streams per connection so the client stays Ready after the first stream, + // and cap active requests at 1 so the second newStreamImpl() overflows the circuit breaker. + concurrent_streams_ = 2; + cluster_->resetResourceManager(1024, 1024, 1, 1, 1); + + // Attach first stream — rq counter reaches its limit (1/1); client stays Ready. + newActiveClientAndStream(ActiveClient::State::Ready); + + // Second stream: finds the Ready client, attachStreamToClient() overflows. + EXPECT_CALL(pool_, onPoolFailure(_, _, ConnectionPool::PoolFailureReason::Overflow, _)); + pool_.newStreamImpl(context_, /*can_send_early_data=*/false); + + EXPECT_EQ(1U, cluster_->traffic_stats_->upstream_rq_active_overflow_.value()); + EXPECT_EQ(0U, cluster_->traffic_stats_->upstream_rq_pending_overflow_.value()); + + closeStreamAndDrainClient(); +} + +// Test legacy behavior: when the runtime flag is disabled, both upstream_rq_active_overflow +// and upstream_rq_pending_overflow are incremented for the max_active_requests path. +TEST_F(ConnPoolImplDispatcherBaseTest, MaxActiveRequestsOverflowLegacy) { + // Simulate the legacy behavior where skip_pending_overflow_count_on_active_rq is false. + // We set the cached flag directly since the pool is constructed before the test body runs. + pool_.setSkipPendingOverflowForTest(false); + + concurrent_streams_ = 2; + cluster_->resetResourceManager(1024, 1024, 1, 1, 1); + + newActiveClientAndStream(ActiveClient::State::Ready); + + EXPECT_CALL(pool_, onPoolFailure(_, _, ConnectionPool::PoolFailureReason::Overflow, _)); + pool_.newStreamImpl(context_, /*can_send_early_data=*/false); + + EXPECT_EQ(1U, cluster_->traffic_stats_->upstream_rq_active_overflow_.value()); + EXPECT_EQ(1U, cluster_->traffic_stats_->upstream_rq_pending_overflow_.value()); + + closeStreamAndDrainClient(); +} + } // namespace ConnectionPool } // namespace Envoy diff --git a/test/extensions/clusters/aggregate/cluster_integration_test.cc b/test/extensions/clusters/aggregate/cluster_integration_test.cc index fdb1cc51e5635..fe6f7a17783c4 100644 --- a/test/extensions/clusters/aggregate/cluster_integration_test.cc +++ b/test/extensions/clusters/aggregate/cluster_integration_test.cc @@ -532,7 +532,7 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxRequests) { // the cluster1 circuit breaker is closed test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_open", 0); test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_rq", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_pending_overflow", 0); + test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_active_overflow", 0); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -555,7 +555,7 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxRequests) { // the cluster1 circuit breaker opens test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_open", 1); test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_rq", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_pending_overflow", 0); + test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_active_overflow", 0); // send a second request to the aggregate cluster auto aggregate_cluster_response2 = codec_client_->makeHeaderOnlyRequest( @@ -576,7 +576,7 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxRequests) { // the cluster1 circuit breaker remains open and overflows test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_open", 1); test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_rq", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_pending_overflow", 1); + test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_active_overflow", 1); // the requests to the aggregate cluster route affect the cluster1 circuit breaker state // send a third request directly to cluster1 to confirm this @@ -595,7 +595,7 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxRequests) { // the cluster1 circuit breaker remains open and overflows again test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_open", 1); test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_rq", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_pending_overflow", 2); + test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_active_overflow", 2); // respond to the first request to the aggregate cluster upstream_request_->encodeHeaders(default_response_headers_, true); @@ -612,7 +612,7 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxRequests) { // the cluster1 circuit breaker closes test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_open", 0); test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_rq", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_pending_overflow", 2); + test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_active_overflow", 2); cleanupUpstreamAndDownstream(); } diff --git a/test/integration/circuit_breakers_integration_test.cc b/test/integration/circuit_breakers_integration_test.cc index ddea875046902..fd093e53944f6 100644 --- a/test/integration/circuit_breakers_integration_test.cc +++ b/test/integration/circuit_breakers_integration_test.cc @@ -59,7 +59,8 @@ TEST_P(CircuitBreakersIntegrationTest, CircuitBreakersWithOutlierDetection) { EXPECT_EQ("503", response->headers().getStatusValue()); test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_503", 1); - EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_pending_overflow")->value(), 1); + EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_active_overflow")->value(), 1); + EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_pending_overflow")->value(), 0); EXPECT_EQ(test_server_->counter("cluster.cluster_0.outlier_detection.ejections_enforced_total") ->value(), @@ -105,7 +106,8 @@ TEST_P(CircuitBreakersIntegrationTest, CircuitBreakerRuntime) { EXPECT_EQ("503", response->headers().getStatusValue()); test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_503", 1); - EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_pending_overflow")->value(), 1); + EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_active_overflow")->value(), 1); + EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_pending_overflow")->value(), 0); EXPECT_EQ(test_server_->counter("cluster.cluster_0.outlier_detection.ejections_enforced_total") ->value(), @@ -173,7 +175,8 @@ TEST_P(CircuitBreakersIntegrationTest, CircuitBreakerRuntimeProto) { EXPECT_EQ("503", response->headers().getStatusValue()); test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_503", 1); - EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_pending_overflow")->value(), 1); + EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_active_overflow")->value(), 1); + EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_pending_overflow")->value(), 0); EXPECT_EQ(test_server_->counter("cluster.cluster_0.outlier_detection.ejections_enforced_total") ->value(), From 88a627accfac82e9f1fa0467ce1dadbe0f80b216 Mon Sep 17 00:00:00 2001 From: Biren Roy Date: Wed, 22 Apr 2026 19:15:13 -0400 Subject: [PATCH 114/750] Replaces a shared_ptr with a unique_ptr in a data structure (#44589) The `std::shared_ptr` is not necessary for a private member of `FilterMatchState` that is initialized by the data structure itself, and never shared via an accessor or copy operation. This is just a small attempt to push back against the shared pointer incontinence that appears to be prevalent in the Envoy codebase. Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: Signed-off-by: Biren Roy --- source/common/http/matching/data_impl.h | 2 -- source/extensions/filters/http/match_delegate/config.h | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/source/common/http/matching/data_impl.h b/source/common/http/matching/data_impl.h index 243c2a831c47c..dff568c6dd8ca 100644 --- a/source/common/http/matching/data_impl.h +++ b/source/common/http/matching/data_impl.h @@ -70,8 +70,6 @@ class HttpMatchingDataImpl : public HttpMatchingData { const ResponseTrailerMap* response_trailers_{}; }; -using HttpMatchingDataImplSharedPtr = std::shared_ptr; - struct HttpFilterActionContext { // Identify whether the filter is in downstream filter chain or upstream filter chain. const bool is_downstream_ = true; diff --git a/source/extensions/filters/http/match_delegate/config.h b/source/extensions/filters/http/match_delegate/config.h index fec2e09e8abc4..9576b294b85fe 100644 --- a/source/extensions/filters/http/match_delegate/config.h +++ b/source/extensions/filters/http/match_delegate/config.h @@ -33,7 +33,7 @@ class DelegatingStreamFilter : public Logger::Loggable, bool skipFilter() const { return skip_filter_; } void onStreamInfo(const StreamInfo::StreamInfo& stream_info) { if (matching_data_ == nullptr) { - matching_data_ = std::make_shared(stream_info); + matching_data_ = std::make_unique(stream_info); } } @@ -49,7 +49,7 @@ class DelegatingStreamFilter : public Logger::Loggable, Matcher::MatchTreeSharedPtr match_tree_; Envoy::Http::StreamFilterBase* base_filter_{}; - Envoy::Http::Matching::HttpMatchingDataImplSharedPtr matching_data_; + std::unique_ptr matching_data_; bool match_tree_evaluated_{}; bool skip_filter_{}; }; From 278b14c4a3b0d22c3dbad2208bf666bfb010f03d Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Thu, 23 Apr 2026 08:17:04 +0200 Subject: [PATCH 115/750] dynamic_modules: add access_logger arm and unify access logger FFI in the SDK (#44597) ## Description A single RUST dynamic module can now host multiple access loggers via the new `access_logger:` arm of `declare_all_init_functions!`, which registers a `NewAccessLoggerConfigFunction` factory that dispatches to different `AccessLoggerConfig` implementations by `logger_name`. The C ABI is unchanged. --- **Commit Message:** dynamic_modules: add access_logger arm and unify access logger FFI in the SDK **Additional Description:** Added a way for a single RUST dynamic module can now host multiple access loggers. **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** N/A **Release Notes:** N/A Signed-off-by: Rohit Agrawal --- changelogs/current.yaml | 6 + .../sdk/rust/src/access_log.rs | 273 +++++++++++------- .../dynamic_modules/sdk/rust/src/lib.rs | 30 ++ .../dynamic_modules/sdk/rust/src/lib_test.rs | 140 +++++++++ .../access_loggers/dynamic_modules/BUILD | 1 + .../dynamic_modules/config_test.cc | 43 +++ .../rust/access_log_integration_test.rs | 30 +- 7 files changed, 422 insertions(+), 101 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index af4aa48ea8abe..fe0b1a5a0023b 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -476,6 +476,12 @@ new_features: Added :ref:`tracer ` support for dynamic modules, enabling custom distributed tracing backends to be implemented in dynamic modules. +- area: dynamic_modules + change: | + Rust SDK: a single dynamic module can now host multiple access loggers via the new + ``access_logger:`` arm of ``declare_all_init_functions!``, which registers a factory that + dispatches to different ``AccessLoggerConfig`` implementations by ``logger_name``. The + legacy ``declare_access_logger!`` macro is preserved as a single-config shim. - area: dynamic_modules change: | Added upstream HTTP TCP bridge extension for dynamic modules. This enables modules to transform diff --git a/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs b/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs index ce47b4a50fe4f..6a9d33a2ec796 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs @@ -1,6 +1,11 @@ //! Access logger support for dynamic modules. //! //! This module provides traits and types for implementing access loggers as dynamic modules. +//! The recommended entry point is the `access_logger:` arm of +//! [`crate::declare_all_init_functions!`], which registers a factory through +//! [`crate::NEW_ACCESS_LOGGER_CONFIG_FUNCTION`] and lets a single module dispatch by +//! `logger_name`. The legacy [`crate::declare_access_logger!`] macro is preserved as a +//! single-config shim over the same factory. use crate::{abi, EnvoyBuffer}; use std::ffi::c_void; @@ -197,12 +202,25 @@ impl MetricsContext { } /// Trait that the dynamic module must implement to provide the access logger configuration. -pub trait AccessLoggerConfig: Sized + Send + Sync + 'static { +/// +/// The trait is dyn-safe: the +/// [`NewAccessLoggerConfigFunction`](crate::NewAccessLoggerConfigFunction) factory returns `Box`, which lets a single module host multiple logger implementations and +/// dispatch between them by `name`. Methods bounded by `Self: Sized` are excluded from the vtable, +/// so [`AccessLoggerConfig::new`] remains callable from `impl` blocks via the legacy +/// [`crate::declare_access_logger!`] shim. +pub trait AccessLoggerConfig: Send + Sync { /// Create a new configuration from the provided name and config bytes. /// /// The `ctx` provides access to metrics definition APIs. Metrics should be defined /// during configuration creation and the handles stored in the config for later use. - fn new(ctx: &ConfigContext, name: &str, config: &[u8]) -> Result; + /// + /// Only invoked by the legacy [`crate::declare_access_logger!`] shim. Callers using + /// [`crate::declare_all_init_functions!`]'s `access_logger:` arm provide a free factory + /// function instead and do not need to implement this method. + fn new(ctx: &ConfigContext, name: &str, config: &[u8]) -> Result + where + Self: Sized; /// Create a logger instance. Called per-thread for thread-local loggers. /// @@ -1232,10 +1250,145 @@ impl LogContext { } } -/// Macro to declare access logger entry points. +/// Wrapper that stores the trait object together with the config-scope Envoy pointer. +/// +/// Envoy does not re-pass the config-scope pointer to the per-worker logger callback, so the +/// SDK captures it at config creation and reuses it to build a [`MetricsContext`] in +/// [`envoy_dynamic_module_on_access_logger_new`]. +struct AccessLoggerConfigHandle { + inner: Box, + config_envoy_ptr: *mut c_void, +} + +// SAFETY: `inner` is `Send + Sync` via the `AccessLoggerConfig` bound. `config_envoy_ptr` +// addresses Envoy's `DynamicModuleAccessLogConfig`, which is thread-safe for the operations +// invoked through `MetricsContext`. +unsafe impl Send for AccessLoggerConfigHandle {} +unsafe impl Sync for AccessLoggerConfigHandle {} + +/// # Safety +/// +/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed +/// by the Envoy dynamic module ABI. +#[no_mangle] +pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_config_new( + config_envoy_ptr: *mut c_void, + name: abi::envoy_dynamic_module_type_envoy_buffer, + config: abi::envoy_dynamic_module_type_envoy_buffer, +) -> *const c_void { + // The name and config originate from protobuf string/bytes fields, so they are valid + // UTF-8 and within bounds when received here. Mirrors the http filter FFI entry point. + let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )); + let config_bytes = std::slice::from_raw_parts(config.ptr as *const _, config.length); + let ctx = ConfigContext::new(config_envoy_ptr); + + envoy_dynamic_module_on_access_logger_config_new_impl( + &ctx, + name_str, + config_bytes, + crate::NEW_ACCESS_LOGGER_CONFIG_FUNCTION + .get() + .expect("NEW_ACCESS_LOGGER_CONFIG_FUNCTION must be set"), + config_envoy_ptr, + ) +} + +/// Testable wrapper for [`envoy_dynamic_module_on_access_logger_config_new`]. /// -/// This macro generates the required C ABI functions that Envoy calls to interact with -/// the access logger implementation. +/// Mirrors `http::envoy_dynamic_module_on_http_filter_config_new_impl`: the FFI entry point +/// extracts the inputs and resolves the registered factory; this function performs the +/// `Option`-to-pointer conversion that unit tests can drive directly. +pub fn envoy_dynamic_module_on_access_logger_config_new_impl( + ctx: &ConfigContext, + name: &str, + config: &[u8], + new_fn: &crate::NewAccessLoggerConfigFunction, + config_envoy_ptr: *mut c_void, +) -> *const c_void { + match new_fn(ctx, name, config) { + Some(inner) => Box::into_raw(Box::new(AccessLoggerConfigHandle { + inner, + config_envoy_ptr, + })) as *const c_void, + None => ptr::null(), + } +} + +/// # Safety +/// +/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed +/// by the Envoy dynamic module ABI. +#[no_mangle] +pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_config_destroy( + config_ptr: *const c_void, +) { + drop(Box::from_raw(config_ptr as *mut AccessLoggerConfigHandle)); +} + +/// # Safety +/// +/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed +/// by the Envoy dynamic module ABI. +#[no_mangle] +pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_new( + config_ptr: *const c_void, + logger_envoy_ptr: *mut c_void, +) -> *const c_void { + let handle = &*(config_ptr as *const AccessLoggerConfigHandle); + let metrics = MetricsContext::new(handle.config_envoy_ptr); + let logger: Box = handle.inner.create_logger(metrics, logger_envoy_ptr); + crate::wrap_into_c_void_ptr!(logger) +} + +/// # Safety +/// +/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed +/// by the Envoy dynamic module ABI. +#[no_mangle] +pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_log( + envoy_ptr: *mut c_void, + logger_ptr: *mut c_void, + log_type: abi::envoy_dynamic_module_type_access_log_type, +) { + let logger = &mut *(logger_ptr as *mut Box); + let access_log_type = AccessLogType::from_abi(log_type); + let ctx = LogContext::new(envoy_ptr, access_log_type); + logger.log(&ctx); +} + +/// # Safety +/// +/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed +/// by the Envoy dynamic module ABI. +#[no_mangle] +pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_destroy(logger_ptr: *mut c_void) { + crate::drop_wrapped_c_void_ptr!(logger_ptr, AccessLogger); +} + +/// # Safety +/// +/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed +/// by the Envoy dynamic module ABI. +#[no_mangle] +pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_flush(logger_ptr: *mut c_void) { + let logger = &mut *(logger_ptr as *mut Box); + logger.flush(); +} + +/// Declare access-logger entry points for a single user-supplied config type. +/// +/// This is the legacy single-config entry point. New callers should prefer +/// [`crate::declare_all_init_functions!`]'s `access_logger:` arm, which dispatches to +/// different config types by `logger_name` and lets a single module host multiple loggers. +/// +/// Like every other single-type init macro in the SDK (for example +/// [`crate::declare_bootstrap_init_functions!`] or +/// [`crate::declare_cluster_init_functions!`]), this macro emits +/// `envoy_dynamic_module_on_program_init` and therefore cannot be combined with another +/// init macro in the same crate. /// /// # Example /// @@ -1246,21 +1399,16 @@ impl LogContext { /// struct MyLoggerConfig { /// format: String, /// logs_counter: CounterHandle, -/// config_envoy_ptr: *mut std::ffi::c_void, /// } /// -/// unsafe impl Send for MyLoggerConfig {} -/// unsafe impl Sync for MyLoggerConfig {} -/// /// impl AccessLoggerConfig for MyLoggerConfig { -/// fn new(ctx: &ConfigContext, name: &str, config: &[u8]) -> Result { +/// fn new(ctx: &ConfigContext, _name: &str, config: &[u8]) -> Result { /// let logs_counter = ctx /// .define_counter("logs_total") /// .ok_or("Failed to define counter")?; /// Ok(Self { /// format: String::from_utf8_lossy(config).to_string(), /// logs_counter, -/// config_envoy_ptr: ctx.envoy_ptr(), /// }) /// } /// @@ -1297,94 +1445,25 @@ impl LogContext { #[macro_export] macro_rules! declare_access_logger { ($config_type:ty) => { - /// Wrapper that stores both the config and the envoy pointer for metrics access. - struct AccessLoggerConfigWrapper { - config: $config_type, - config_envoy_ptr: *mut ::std::ffi::c_void, - } - - unsafe impl Send for AccessLoggerConfigWrapper {} - unsafe impl Sync for AccessLoggerConfigWrapper {} - - #[no_mangle] - pub extern "C" fn envoy_dynamic_module_on_access_logger_config_new( - config_envoy_ptr: *mut ::std::ffi::c_void, - name: $crate::abi::envoy_dynamic_module_type_envoy_buffer, - config: $crate::abi::envoy_dynamic_module_type_envoy_buffer, - ) -> *const ::std::ffi::c_void { - let name_str = unsafe { - let slice = ::std::slice::from_raw_parts(name.ptr as *const u8, name.length); - ::std::str::from_utf8(slice).unwrap_or("") - }; - let config_bytes = - unsafe { ::std::slice::from_raw_parts(config.ptr as *const u8, config.length) }; - - let ctx = $crate::access_log::ConfigContext::new(config_envoy_ptr); - match <$config_type as $crate::access_log::AccessLoggerConfig>::new( - &ctx, - name_str, - config_bytes, - ) { - Ok(c) => { - let wrapper = AccessLoggerConfigWrapper { - config: c, - config_envoy_ptr, - }; - Box::into_raw(Box::new(wrapper)) as *const ::std::ffi::c_void - }, - Err(_) => ::std::ptr::null(), - } - } - - #[no_mangle] - pub extern "C" fn envoy_dynamic_module_on_access_logger_config_destroy( - config_ptr: *const ::std::ffi::c_void, - ) { - unsafe { - drop(Box::from_raw(config_ptr as *mut AccessLoggerConfigWrapper)); - } - } - #[no_mangle] - pub extern "C" fn envoy_dynamic_module_on_access_logger_new( - config_ptr: *const ::std::ffi::c_void, - logger_envoy_ptr: *mut ::std::ffi::c_void, - ) -> *const ::std::ffi::c_void { - let wrapper = unsafe { &*(config_ptr as *const AccessLoggerConfigWrapper) }; - let metrics = $crate::access_log::MetricsContext::new(wrapper.config_envoy_ptr); - let logger = wrapper.config.create_logger(metrics, logger_envoy_ptr); - Box::into_raw(Box::new(logger)) as *const ::std::ffi::c_void - } - - #[no_mangle] - pub extern "C" fn envoy_dynamic_module_on_access_logger_log( - envoy_ptr: *mut ::std::ffi::c_void, - logger_ptr: *mut ::std::ffi::c_void, - log_type: $crate::abi::envoy_dynamic_module_type_access_log_type, - ) { - let logger = unsafe { &mut *(logger_ptr as *mut Box) }; - let access_log_type = $crate::access_log::AccessLogType::from_abi(log_type); - let ctx = $crate::access_log::LogContext::new(envoy_ptr, access_log_type); - logger.log(&ctx); - } - - #[no_mangle] - pub extern "C" fn envoy_dynamic_module_on_access_logger_destroy( - logger_ptr: *mut ::std::ffi::c_void, - ) { - unsafe { - drop(Box::from_raw( - logger_ptr as *mut Box, - )); + pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { + fn __single_access_logger_factory( + ctx: &$crate::access_log::ConfigContext, + name: &str, + config: &[u8], + ) -> ::std::option::Option<::std::boxed::Box> { + match <$config_type as $crate::access_log::AccessLoggerConfig>::new(ctx, name, config) { + Ok(c) => ::std::option::Option::Some(::std::boxed::Box::new(c) + as ::std::boxed::Box), + Err(_) => ::std::option::Option::None, + } } - } - - #[no_mangle] - pub extern "C" fn envoy_dynamic_module_on_access_logger_flush( - logger_ptr: *mut ::std::ffi::c_void, - ) { - let logger = unsafe { &mut *(logger_ptr as *mut Box) }; - logger.flush(); + $crate::set_factory_once!( + $crate::NEW_ACCESS_LOGGER_CONFIG_FUNCTION, + __single_access_logger_factory as $crate::NewAccessLoggerConfigFunction, + "NEW_ACCESS_LOGGER_CONFIG_FUNCTION" + ); + $crate::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char } }; } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs index 7c5adbe653dc0..22955d0fec261 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs @@ -609,6 +609,7 @@ macro_rules! declare_network_filter_init_functions { /// - `tracer:` — [`NewTracerConfigFunction`] for tracers /// - `dns_resolver:` — [`NewDnsResolverConfigFunction`] for DNS resolvers /// - `transport_socket:` — [`NewTransportSocketFactoryConfigFunction`] for transport sockets +/// - `access_logger:` — [`NewAccessLoggerConfigFunction`] for access loggers /// /// # Examples /// @@ -799,6 +800,13 @@ macro_rules! declare_all_init_functions { "NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION" ); }; + (@register access_logger : $fn:expr) => { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_ACCESS_LOGGER_CONFIG_FUNCTION, + $fn, + "NEW_ACCESS_LOGGER_CONFIG_FUNCTION" + ); + }; } /// The function signature for the new network filter configuration function. @@ -1025,6 +1033,28 @@ macro_rules! declare_bootstrap_init_functions { }; } +// ================================================================================================= +// Access Logger Dynamic Module +// ================================================================================================= + +/// The function signature for creating a new access logger configuration. +/// +/// The `ctx` provides access to the metrics-defining APIs that should be invoked at +/// configuration time. The `name` is the value of `logger_name` from the `dynamic_modules` +/// access-log configuration, allowing a single module to dispatch to different logger +/// implementations. Returning `None` causes Envoy to reject the access-log configuration. +pub type NewAccessLoggerConfigFunction = fn( + ctx: &access_log::ConfigContext, + name: &str, + config: &[u8], +) -> Option>; + +/// The global factory function for access logger configurations. This is set via the +/// `access_logger:` arm of [`declare_all_init_functions!`] (or the legacy +/// [`declare_access_logger!`] shim) and is not intended to be set directly. +pub static NEW_ACCESS_LOGGER_CONFIG_FUNCTION: OnceLock = + OnceLock::new(); + // ================================================================================================= // Cluster Dynamic Module // ================================================================================================= diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs index 56f3795ea6fa5..675d41b74b5ef 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs @@ -5526,3 +5526,143 @@ fn test_mock_envoy_network_filter_on_read() { abi::envoy_dynamic_module_type_on_network_filter_data_status::Continue ); } + +// ============================================================================= +// Access Logger unit tests +// ============================================================================= + +#[test] +fn test_envoy_dynamic_module_on_access_logger_config_new_impl() { + struct TestAccessLoggerConfig; + impl access_log::AccessLoggerConfig for TestAccessLoggerConfig { + fn new(_ctx: &access_log::ConfigContext, _name: &str, _config: &[u8]) -> Result { + Ok(TestAccessLoggerConfig) + } + fn create_logger( + &self, + _metrics: access_log::MetricsContext, + _logger_envoy_ptr: *mut std::ffi::c_void, + ) -> Box { + unimplemented!() + } + } + + let ctx = access_log::ConfigContext::new(std::ptr::null_mut()); + let mut new_fn: NewAccessLoggerConfigFunction = |_, _, _| Some(Box::new(TestAccessLoggerConfig)); + let result = access_log::envoy_dynamic_module_on_access_logger_config_new_impl( + &ctx, + "test_logger", + b"test_config", + &new_fn, + std::ptr::null_mut(), + ); + assert!(!result.is_null()); + + unsafe { + access_log::envoy_dynamic_module_on_access_logger_config_destroy(result); + } + + // None should result in a null pointer (e.g. unknown logger name). + new_fn = |_, _, _| None; + let result = access_log::envoy_dynamic_module_on_access_logger_config_new_impl( + &ctx, + "test_logger", + b"test_config", + &new_fn, + std::ptr::null_mut(), + ); + assert!(result.is_null()); +} + +#[test] +fn test_envoy_dynamic_module_on_access_logger_config_destroy() { + // This test ensures the wrapped trait object is dropped exactly once on `_destroy`. + static DROPPED: AtomicBool = AtomicBool::new(false); + struct TestAccessLoggerConfig; + impl access_log::AccessLoggerConfig for TestAccessLoggerConfig { + fn new(_ctx: &access_log::ConfigContext, _name: &str, _config: &[u8]) -> Result { + Ok(TestAccessLoggerConfig) + } + fn create_logger( + &self, + _metrics: access_log::MetricsContext, + _logger_envoy_ptr: *mut std::ffi::c_void, + ) -> Box { + unimplemented!() + } + } + impl Drop for TestAccessLoggerConfig { + fn drop(&mut self) { + DROPPED.store(true, std::sync::atomic::Ordering::SeqCst); + } + } + + let ctx = access_log::ConfigContext::new(std::ptr::null_mut()); + let new_fn: NewAccessLoggerConfigFunction = |_, _, _| Some(Box::new(TestAccessLoggerConfig)); + let config_ptr = access_log::envoy_dynamic_module_on_access_logger_config_new_impl( + &ctx, + "test_logger", + b"test_config", + &new_fn, + std::ptr::null_mut(), + ); + assert!(!config_ptr.is_null()); + + unsafe { + access_log::envoy_dynamic_module_on_access_logger_config_destroy(config_ptr); + } + assert!(DROPPED.load(std::sync::atomic::Ordering::SeqCst)); +} + +#[test] +fn test_envoy_dynamic_module_on_access_logger_new_destroy() { + // Round-trip the per-worker logger to ensure both `_new` and `_destroy` correctly own and + // free the boxed trait object. + static LOGGER_DROPPED: AtomicBool = AtomicBool::new(false); + + struct TestAccessLoggerConfig; + impl access_log::AccessLoggerConfig for TestAccessLoggerConfig { + fn new(_ctx: &access_log::ConfigContext, _name: &str, _config: &[u8]) -> Result { + Ok(TestAccessLoggerConfig) + } + fn create_logger( + &self, + _metrics: access_log::MetricsContext, + _logger_envoy_ptr: *mut std::ffi::c_void, + ) -> Box { + Box::new(TestAccessLogger) + } + } + + struct TestAccessLogger; + impl access_log::AccessLogger for TestAccessLogger { + fn log(&mut self, _ctx: &access_log::LogContext) {} + } + impl Drop for TestAccessLogger { + fn drop(&mut self) { + LOGGER_DROPPED.store(true, std::sync::atomic::Ordering::SeqCst); + } + } + + let ctx = access_log::ConfigContext::new(std::ptr::null_mut()); + let new_fn: NewAccessLoggerConfigFunction = |_, _, _| Some(Box::new(TestAccessLoggerConfig)); + let config_ptr = access_log::envoy_dynamic_module_on_access_logger_config_new_impl( + &ctx, + "test_logger", + b"test_config", + &new_fn, + std::ptr::null_mut(), + ); + assert!(!config_ptr.is_null()); + + let logger_ptr = unsafe { + access_log::envoy_dynamic_module_on_access_logger_new(config_ptr, std::ptr::null_mut()) + }; + assert!(!logger_ptr.is_null()); + + unsafe { + access_log::envoy_dynamic_module_on_access_logger_destroy(logger_ptr as *mut _); + access_log::envoy_dynamic_module_on_access_logger_config_destroy(config_ptr); + } + assert!(LOGGER_DROPPED.load(std::sync::atomic::Ordering::SeqCst)); +} diff --git a/test/extensions/access_loggers/dynamic_modules/BUILD b/test/extensions/access_loggers/dynamic_modules/BUILD index 51d57b602f422..85ac807853e85 100644 --- a/test/extensions/access_loggers/dynamic_modules/BUILD +++ b/test/extensions/access_loggers/dynamic_modules/BUILD @@ -18,6 +18,7 @@ envoy_cc_test( "//test/extensions/dynamic_modules/test_data/c:access_log_missing_logger_log", "//test/extensions/dynamic_modules/test_data/c:access_log_missing_logger_new", "//test/extensions/dynamic_modules/test_data/c:access_log_no_op", + "//test/extensions/dynamic_modules/test_data/rust:access_log_integration_test", ], deps = [ "//envoy/registry", diff --git a/test/extensions/access_loggers/dynamic_modules/config_test.cc b/test/extensions/access_loggers/dynamic_modules/config_test.cc index ff9bd2b9034bc..7245c137945db 100644 --- a/test/extensions/access_loggers/dynamic_modules/config_test.cc +++ b/test/extensions/access_loggers/dynamic_modules/config_test.cc @@ -201,6 +201,49 @@ logger_name: test_logger EnvoyException, "Failed to resolve symbol.*logger_destroy"); } +// Verifies that the unified Rust SDK factory correctly rejects unknown logger names by +// returning `None`, which the C++ factory translates into an `InvalidArgumentError` with the +// standard "Failed to initialize" message. This exercises the dispatch-by-name code path +// installed via the `access_logger:` arm of `declare_all_init_functions!`. +class DynamicModuleAccessLogFactoryRustTest : public testing::Test { +public: + DynamicModuleAccessLogFactoryRustTest() { + TestEnvironment::setEnvVar( + "ENVOY_DYNAMIC_MODULES_SEARCH_PATH", + TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/dynamic_modules/test_data/rust"), + 1); + } + + DynamicModuleAccessLogFactory factory_; +}; + +TEST_F(DynamicModuleAccessLogFactoryRustTest, UnknownLoggerNameRejectedAtConfigLoad) { + NiceMock context; + NiceMock options; + ON_CALL(options, concurrency()).WillByDefault(testing::Return(1)); + ON_CALL(context.server_context_, options()).WillByDefault(testing::ReturnRef(options)); + ScopedThreadLocalServerContextSetter setter(context.server_context_); + + const std::string yaml = R"EOF( +dynamic_module_config: + name: access_log_integration_test + do_not_close: true +logger_name: unknown_logger +logger_config: + "@type": type.googleapis.com/google.protobuf.StringValue + value: test_config +)EOF"; + + envoy::extensions::access_loggers::dynamic_modules::v3::DynamicModuleAccessLog proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + + AccessLog::FilterPtr filter; + EXPECT_THROW_WITH_REGEX( + factory_.createAccessLogInstance(proto_config, std::move(filter), context, {}), + EnvoyException, "Failed to initialize dynamic module access logger config"); +} + } // namespace } // namespace DynamicModules } // namespace AccessLoggers diff --git a/test/extensions/dynamic_modules/test_data/rust/access_log_integration_test.rs b/test/extensions/dynamic_modules/test_data/rust/access_log_integration_test.rs index 2dad1cdf19ac6..ce658feded87e 100644 --- a/test/extensions/dynamic_modules/test_data/rust/access_log_integration_test.rs +++ b/test/extensions/dynamic_modules/test_data/rust/access_log_integration_test.rs @@ -1,13 +1,19 @@ //! Integration test module for access logger dynamic modules. //! -//! This module implements a simple access logger that records log events and flush calls. +//! This module exercises the unified `declare_all_init_functions!` registration: a single +//! `.so` registers both an HTTP filter factory and an access-logger factory that dispatches +//! to different implementations by `logger_name`. The C++ side rejects unknown logger names +//! at config load time when the factory returns `None`. use envoy_proxy_dynamic_modules_rust_sdk::access_log::*; use envoy_proxy_dynamic_modules_rust_sdk::*; use std::sync::atomic::{AtomicU32, Ordering}; -declare_init_functions!(init, new_nop_http_filter_config_fn); -declare_access_logger!(TestAccessLoggerConfig); +declare_all_init_functions!( + init, + http: new_nop_http_filter_config_fn, + access_logger: new_access_logger_config_fn, +); /// Global counter for log events. static LOG_COUNT: AtomicU32 = AtomicU32::new(0); @@ -21,7 +27,7 @@ fn init() -> bool { true } -/// Dummy HTTP filter config function (required by declare_init_functions). +/// Dummy HTTP filter config function (required by declare_all_init_functions). fn new_nop_http_filter_config_fn( _envoy_filter_config: &mut EC, _name: &str, @@ -30,6 +36,22 @@ fn new_nop_http_filter_config_fn Option> { + match name { + "test_logger" => TestAccessLoggerConfig::new(ctx, name, config) + .ok() + .map(|c| Box::new(c) as Box), + _ => None, + } +} + /// Access logger configuration. struct TestAccessLoggerConfig { _name: String, From 36b6d5b7410c4f310304c040bf2e398f4b5506f3 Mon Sep 17 00:00:00 2001 From: William Dauchy Date: Thu, 23 Apr 2026 08:20:14 +0200 Subject: [PATCH 116/750] golang: skip dispatcher post for continue/sendLocalReply on worker thread (#44503) Commit Message: When the Go plugin callback returns synchronously on the Envoy worker thread, continueStatus() and sendLocalReply() were unconditionally posting a closure to the dispatcher. This added one full event loop cycle of latency per request phase (headers, data, trailers) on both the request and response paths. The original code held mutex_ for the entire function scope, which made calling the internal methods inline impossible: they call hasDestroyed() which re-acquires the non-reentrant mutex, causing a deadlock. Fix by scoping the mutex to the precondition checks only, then executing inline when isThreadSafe() confirms we're already on the worker thread. This is safe because onDestroy() also runs on the same worker thread, so has_destroyed_ cannot change between releasing the lock and calling the internal method. For the off-thread (Go goroutine) path, behavior is unchanged: the closure is posted to the dispatcher as before. Additional Description: The same pattern (lock + dispatcher.post) exists in many sibling methods (injectData, getHeader, setHeader, metadata getters, etc.). Only the methods whose internal implementation re-enters hasDestroyed() have the deadlock constraint, but the latency argument applies to all of them. This PR targets continueStatus and sendLocalReply as the highest-value targets since they sit on the filter-chain continuation hot path. Remaining methods can be converted in follow-up PRs. Risk Level: Low. The optimization only applies when already on the worker thread; the off-thread path is unchanged byte-for-byte. Testing: 4 new unit tests in golang_filter_test.cc covering both the inline (isThreadSafe=true, post count=0) and dispatcher-post (isThreadSafe=false, post count=1) paths for continueStatus and sendLocalReply. Existing Go integration tests (golang_filter_integration_test) exercise the real cgo-on-stack callback path end-to-end. Docs Changes: N/A Release Notes: Not required (internal optimization, no API change) Platform Specific Features: N/A [Optional Runtime guard:] N/A [Optional Fixes #Issue] N/A [Optional Deprecated:] N/A [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] N/A Signed-off-by: William Dauchy --- .../filters/http/source/golang_filter.cc | 71 +++++++---- .../filters/http/test/golang_filter_test.cc | 110 ++++++++++++++++++ 2 files changed, 157 insertions(+), 24 deletions(-) diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index 104e7fc35c2f8..eb3ed282bb341 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -455,6 +455,7 @@ void Filter::sendLocalReplyInternal( ProcessorState& state, Http::Code response_code, absl::string_view body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, absl::string_view details) { + ASSERT(state.isThreadSafe()); ENVOY_LOG(debug, "sendLocalReply Internal, state: {}, response code: {}", state.stateStr(), int(response_code)); @@ -471,23 +472,30 @@ CAPIStatus Filter::sendLocalReply(ProcessorState& state, Http::Code response_code, std::string body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, std::string details) { - // lock until this function return since it may running in a Go thread. - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { - ENVOY_LOG(debug, "golang filter has been destroyed"); - return CAPIStatus::CAPIFilterIsDestroy; + bool on_worker_thread = state.isThreadSafe(); + { + Thread::LockGuard lock(mutex_); + if (has_destroyed_) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return CAPIStatus::CAPIFilterIsDestroy; + } + if (!state.isProcessingInGo()) { + ENVOY_LOG(debug, "golang filter is not processing Go"); + return CAPIStatus::CAPINotInGo; + } + ENVOY_LOG(debug, "sendLocalReply, response code: {}", int(response_code)); } - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; + + // See continueStatus() for the reentrancy/deadlock rationale. + if (on_worker_thread) { + sendLocalReplyInternal(state, response_code, body_text, modify_headers, grpc_status, details); + return CAPIStatus::CAPIOK; } - ENVOY_LOG(debug, "sendLocalReply, response code: {}", int(response_code)); auto weak_ptr = weak_from_this(); state.getDispatcher().post([this, &state, weak_ptr, response_code, body_text, modify_headers, grpc_status, details] { if (!weak_ptr.expired() && !hasDestroyed()) { - ASSERT(state.isThreadSafe()); sendLocalReplyInternal(state, response_code, body_text, modify_headers, grpc_status, details); } else { ENVOY_LOG(debug, "golang filter has gone or destroyed in sendLocalReply"); @@ -509,25 +517,38 @@ CAPIStatus Filter::sendPanicReply(ProcessorState& state, absl::string_view detai } CAPIStatus Filter::continueStatus(ProcessorState& state, GolangStatus status) { - // lock until this function return since it may running in a Go thread. - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { - ENVOY_LOG(debug, "golang filter has been destroyed"); - return CAPIStatus::CAPIFilterIsDestroy; - } - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; + // isThreadSafe() is a pure thread-id comparison, safe to read without the mutex. + bool on_worker_thread = state.isThreadSafe(); + { + Thread::LockGuard lock(mutex_); + if (has_destroyed_) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return CAPIStatus::CAPIFilterIsDestroy; + } + if (!state.isProcessingInGo()) { + ENVOY_LOG(debug, "golang filter is not processing Go"); + return CAPIStatus::CAPINotInGo; + } + ENVOY_LOG(debug, "golang filter continue from Go, status: {}, state: {}", int(status), + state.stateStr()); + } + // Lock released before calling into the filter chain to avoid deadlocking: + // continueStatusInternal() calls hasDestroyed() which re-acquires the non-reentrant mutex_. + // + // NB: inline execution means continueStatusInternal re-enters the filter chain + // (continueProcessing / continueDoData / sendLocalReply) while the cgo call is still on the + // stack. This is the same reentrancy model as the existing addData inline path. The unit tests + // verify the branch selection but do not reproduce the full cgo-on-stack scenario; the existing + // Go integration tests (golang_filter_integration_test) exercise that path end-to-end. + + if (on_worker_thread) { + continueStatusInternal(state, status); + return CAPIStatus::CAPIOK; } - ENVOY_LOG(debug, "golang filter continue from Go, status: {}, state: {}", int(status), - state.stateStr()); auto weak_ptr = weak_from_this(); - // TODO: skip post event to dispatcher, and return continue in the caller, - // when it's invoked in the current envoy thread, for better performance & latency. state.getDispatcher().post([this, &state, weak_ptr, status] { if (!weak_ptr.expired() && !hasDestroyed()) { - ASSERT(state.isThreadSafe()); continueStatusInternal(state, status); } else { ENVOY_LOG(debug, "golang filter has gone or destroyed in continueStatus event"); @@ -556,6 +577,8 @@ CAPIStatus Filter::addData(ProcessorState& state, absl::string_view data, bool i } if (state.isThreadSafe()) { + // NB: unlike continueStatus/sendLocalReply, we can hold mutex_ here because + // state.addData() does not call hasDestroyed() or re-acquire the mutex. Buffer::OwnedImpl buffer; buffer.add(data); state.addData(buffer, is_streaming); diff --git a/contrib/golang/filters/http/test/golang_filter_test.cc b/contrib/golang/filters/http/test/golang_filter_test.cc index d6583926a3e43..5bc625be4312b 100644 --- a/contrib/golang/filters/http/test/golang_filter_test.cc +++ b/contrib/golang/filters/http/test/golang_filter_test.cc @@ -40,8 +40,10 @@ namespace Golang { class TestFilter : public Filter { public: + using Filter::continueStatus; using Filter::continueStatusInternal; using Filter::Filter; + using Filter::sendLocalReply; DecodingProcessorState& testDecodingState() { return decoding_state_; } HttpRequestInternal* testReq() { return req_; } }; @@ -265,6 +267,114 @@ TEST_F(GolangHttpFilterTest, BufferedDataAfterDestroyDuringContinue) { delete filter->testReq(); } +// Helper to set up a mock-based filter for dispatcher post tests. +struct MockFilterContext { + std::shared_ptr> dso_lib; + std::shared_ptr config; + NiceMock mock_context; + Network::Address::InstanceConstSharedPtr addr; + NiceMock mock_callbacks; + NiceMock mock_enc_callbacks; + NiceMock mock_connection; + std::shared_ptr filter; + + void setup(bool thread_safe) { + dso_lib = std::make_shared>(); + ON_CALL(*dso_lib, envoyGoFilterNewHttpPluginConfig(_)).WillByDefault(Return(1)); + ON_CALL(*dso_lib, envoyGoFilterOnHttpHeader(_, _, _, _)) + .WillByDefault(Return(static_cast(GolangStatus::Running))); + + const auto yaml = R"EOF( + library_id: test + library_path: test + plugin_name: test + )EOF"; + envoy::extensions::filters::http::golang::v3alpha::Config proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + config = std::make_shared(proto_config, dso_lib, "", mock_context); + config->newGoPluginConfig(); + + addr.reset((*Network::Address::PipeInstance::create("/test/test.sock")).release()); + ON_CALL(mock_callbacks, connection()) + .WillByDefault(Return(OptRef{mock_connection})); + mock_connection.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr); + mock_connection.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr); + EXPECT_CALL(mock_callbacks.dispatcher_, isThreadSafe()).WillRepeatedly(Return(thread_safe)); + + filter = std::make_shared(config, dso_lib, 0); + filter->setDecoderFilterCallbacks(mock_callbacks); + filter->setEncoderFilterCallbacks(mock_enc_callbacks); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter->decodeHeaders(request_headers, false)); + } + + void teardown() { + filter->onDestroy(); + delete filter->testReq(); + } +}; + +// Verify that continueStatus executes inline when already on the worker thread, +// bypassing dispatcher.post for lower latency. +TEST_F(GolangHttpFilterTest, ContinueStatusSkipsPostOnWorkerThread) { + MockFilterContext ctx; + ctx.setup(true); + + EXPECT_CALL(ctx.mock_callbacks.dispatcher_, post(_)).Times(0); + EXPECT_CALL(ctx.mock_callbacks, continueDecoding()); + + auto status = ctx.filter->continueStatus(ctx.filter->testDecodingState(), GolangStatus::Continue); + EXPECT_EQ(CAPIStatus::CAPIOK, status); + + ctx.teardown(); +} + +// Verify that continueStatus posts to the dispatcher when not on the worker thread. +TEST_F(GolangHttpFilterTest, ContinueStatusPostsOffWorkerThread) { + MockFilterContext ctx; + ctx.setup(false); + + EXPECT_CALL(ctx.mock_callbacks.dispatcher_, post(_)); + + auto status = ctx.filter->continueStatus(ctx.filter->testDecodingState(), GolangStatus::Continue); + EXPECT_EQ(CAPIStatus::CAPIOK, status); + + ctx.teardown(); +} + +// Verify that sendLocalReply executes inline when already on the worker thread. +TEST_F(GolangHttpFilterTest, SendLocalReplySkipsPostOnWorkerThread) { + MockFilterContext ctx; + ctx.setup(true); + + EXPECT_CALL(ctx.mock_callbacks.dispatcher_, post(_)).Times(0); + EXPECT_CALL(ctx.mock_callbacks, sendLocalReply(Http::Code::BadRequest, "bad request", _, _, _)); + + auto status = + ctx.filter->sendLocalReply(ctx.filter->testDecodingState(), Http::Code::BadRequest, + "bad request", nullptr, Grpc::Status::WellKnownGrpcStatus::Ok, ""); + EXPECT_EQ(CAPIStatus::CAPIOK, status); + + ctx.teardown(); +} + +// Verify that sendLocalReply posts to the dispatcher when not on the worker thread. +TEST_F(GolangHttpFilterTest, SendLocalReplyPostsOffWorkerThread) { + MockFilterContext ctx; + ctx.setup(false); + + EXPECT_CALL(ctx.mock_callbacks.dispatcher_, post(_)); + + auto status = + ctx.filter->sendLocalReply(ctx.filter->testDecodingState(), Http::Code::BadRequest, + "bad request", nullptr, Grpc::Status::WellKnownGrpcStatus::Ok, ""); + EXPECT_EQ(CAPIStatus::CAPIOK, status); + + ctx.teardown(); +} + } // namespace } // namespace Golang } // namespace HttpFilters From ccf09257f56f4c16ba8553798c0b98f2504be1ae Mon Sep 17 00:00:00 2001 From: kpramesh Date: Thu, 23 Apr 2026 09:33:15 +0100 Subject: [PATCH 117/750] Aws lc fips build fixes (#43953) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Use of generative AI **I used generative AI to help fix the AWS-LC FIPS build.** I am not a C++ developer, so this PR should be reviewed carefully before merging. I was able to compile Envoy successfully with the changes below, but I may not have fully understood all implications. Please review thoroughly. --- ## Additional Description This PR fixes several build failures encountered when building Envoy with `--config=aws-lc-fips`, particularly on aarch64 and in container/CI environments. These fixes address the build infrastructure issues that prevent the AWS-LC FIPS build from completing. **Note for native aarch64 builds:** The build also requires `BAZEL_USE_HOST_SYSROOT=True` (set as an environment variable before building). This is an existing Envoy option documented in `bazel/repo.bzl` and is not changed in this PR. --- ## Risk Level Low – changes are limited to build configuration and external dependency setup, not runtime code. --- ## Testing **Production build (successful):** ```bash bazel build -c opt //source/exe:envoy-static \ --config=aws-lc-fips \ --config=sizeopt \ --define=no_debug_info=1 \ --copt=-ffunction-sections \ --copt=-fdata-sections \ --linkopt=-Wl,--gc-sections \ --linkopt=-Wl,-s \ --strip=always \ --fission=no \ --jobs=63 ``` **Debug build (successful):** ```bash bazel build //source/exe:envoy-static --config=aws-lc-fips --spawn_strategy=standalone ``` **Environment:** Native aarch64 Linux build with `BAZEL_USE_HOST_SYSROOT=True` exported. --- ## Docs Changes None. Consider documenting `BAZEL_USE_HOST_SYSROOT=True` for native aarch64 AWS-LC FIPS builds in a follow-up. --- ## Release Notes ```release-note build: Fix AWS-LC FIPS build for aarch64 and container environments (Go deps, tar extraction, sandbox) ``` --- ## Platform Specific Features - Linux aarch64 (native build) - Linux x86_64 (expected to work; not tested) --- ## Fixes #43904 ----------------------------------------------- BUILD machine ------------------------------------------------- ``` NAME="Amazon Linux" VERSION="2023" ID="amzn" ID_LIKE="fedora" VERSION_ID="2023" PLATFORM_ID="platform:al2023" PRETTY_NAME="Amazon Linux 2023.3.20240312" ANSI_COLOR="0;33" CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2023" HOME_URL="https://aws.amazon.com/linux/amazon-linux-2023/" DOCUMENTATION_URL="https://docs.aws.amazon.com/linux/" SUPPORT_URL="https://aws.amazon.com/premiumsupport/" BUG_REPORT_URL="https://github.com/amazonlinux/amazon-linux-2023" VENDOR_NAME="AWS" VENDOR_URL="https://aws.amazon.com/" SUPPORT_END="2028-03-15" ``` -------------------------------------------------------------------------------- EXPLANATION OF CHANGES (for review comments or PR description) -------------------------------------------------------------------------------- 1. Go dependency resolution (bazel/dependency_imports.bzl) - Problem: com_github_spf13_afero and com_github_lyft_protoc_gen_star_v2 import golang.org/x/text and golang.org/x/tools, but Gazelle didn't resolve them, causing "missing strict dependencies" errors. - Change: Added build_directives to tell Gazelle how to resolve these imports to @org_golang_x_text and @org_golang_x_tools. Also added org_golang_x_tools go_repository if it wasn't present. 2. Tar ownership (bazel/external/aws_lc.genrule_cmd) - Problem: The AWS-LC FIPS build downloads and extracts Clang, Go, Ninja, and CMake tarballs. These archives contain ownership metadata (uid 11827, gid 9000). In containers or sandboxes, those uid/gid don't exist, so tar fails with "Cannot change ownership: Invalid argument". - Change: Added --no-same-owner to all 5 tar commands so extracted files use the current user's uid/gid. Safe for build toolchains and works in restricted environments. 3. AWS-LC genrule sandbox (bazel/external/aws_lc.BUILD) - Problem: The AWS-LC genrule runs CMake, which writes files during configure. Bazel's sandbox makes the execroot read-only for some actions, causing "Read-only file system" when CMake runs configure_file. - Change: Added tags = ["no-sandbox"] to the AWS-LC genrule so it runs outside the sandbox. Only this genrule is affected; other build actions remain sandboxed. 4. Host sysroot (environment variable - not a code change) - Problem: On native aarch64, the hermetic sysroot may not have the expected libc layout, causing linker errors like "cannot find /lib/aarch64-linux-gnu/libc.so.6". - Change: Use BAZEL_USE_HOST_SYSROOT=True (existing Envoy option) to use the host's libc. Document this for native aarch64 builds. -------------------------------------------------------------------------------- SUMMARY TABLE -------------------------------------------------------------------------------- | File | Change | Purpose | |-------------------------------|---------------------------|--------------------------------------------| | bazel/dependency_imports.bzl | build_directives | Fix Go "missing strict dependencies" | | bazel/external/aws_lc.genrule_cmd | --no-same-owner on tar | Fix tar ownership errors in containers | | bazel/external/aws_lc.BUILD | tags = ["no-sandbox"] | Fix CMake "Read-only file system" | | Environment | BAZEL_USE_HOST_SYSROOT=True | Use host libc on native aarch64 (document) | ================================================================================ --------- Signed-off-by: Ramesh KP Signed-off-by: kpramesh Co-authored-by: Ramesh KP Co-authored-by: phlax --- bazel/deps.yaml | 39 +++++++ bazel/external/aws_lc.BUILD | 94 ++++++++++++++++- bazel/external/aws_lc.genrule_cmd | 166 +++++++----------------------- bazel/external/aws_lc_build.bzl | 36 +++++++ bazel/repositories.bzl | 15 +++ bazel/repository_locations.bzl | 18 ++++ source/common/tls/aws_lc_compat.h | 20 ++++ source/common/tls/context_impl.cc | 1 + 8 files changed, 257 insertions(+), 132 deletions(-) create mode 100644 bazel/external/aws_lc_build.bzl diff --git a/bazel/deps.yaml b/bazel/deps.yaml index e528da5e00aa7..2115ef7f2bf0d 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -889,6 +889,45 @@ fips_ninja: license: "Apache-2.0" license_url: "https://github.com/ninja-build/ninja/blob/v{version}/COPYING" +fips_clang_ppc64le: + project_name: "Clang (Linux ppc64le)" + project_desc: "LLVM/Clang compiler for ppc64le FIPS builds" + project_url: "https://llvm.org/" + release_date: "2022-03-23" + use_category: + - build + - dataplane_core + - controlplane + cpe: "cpe:2.3:a:llvm:clang:*" + license: "Apache-2.0 WITH LLVM-exception" + license_url: "https://github.com/llvm/llvm-project/blob/llvmorg-{version}/LICENSE.TXT" + +fips_go_ppc64le: + project_name: "Go (Linux ppc64le)" + project_desc: "Go programming language (Linux ppc64le)" + project_url: "https://golang.org/" + release_date: "2026-04-07" + use_category: + - build + - dataplane_core + - controlplane + cpe: "cpe:2.3:a:golang:go:*" + license: "BSD-3-Clause" + license_url: "https://go.dev/LICENSE" + +fips_cmake_src: + project_name: "CMake (source)" + project_desc: "CMake built from source for ppc64le FIPS builds" + project_url: "https://cmake.org/" + release_date: "2026-04-21" + use_category: + - build + - dataplane_core + - controlplane + cpe: "cpe:2.3:a:kitware:cmake:*" + license: "BSD-3-Clause" + license_url: "https://github.com/Kitware/CMake/blob/v{version}/Copyright.txt" + fp16: project_name: "Conversion to/from half-precision floating point formats" project_desc: "Header-only library for conversion to/from half-precision floating point formats." diff --git a/bazel/external/aws_lc.BUILD b/bazel/external/aws_lc.BUILD index 5a2b19aa813de..e3bbdf80e8134 100644 --- a/bazel/external/aws_lc.BUILD +++ b/bazel/external/aws_lc.BUILD @@ -1,3 +1,4 @@ +load("@envoy//bazel/external:aws_lc_build.bzl", "aws_lc_build_command") load("@rules_cc//cc:defs.bzl", "cc_library") licenses(["notice"]) # Apache 2 @@ -23,6 +24,67 @@ cc_library( deps = [":crypto"], ) +genrule( + name = "ninja_bin", + srcs = [ + "@fips_ninja//:all", + "@fips_ninja//:configure.py", + ], + outs = ["ninja"], + cmd = """ +set -eo pipefail +SRC_DIR=$$(dirname $(location @fips_ninja//:configure.py)) +OUT_FILE=$$(realpath $@) +PYTHON_BIN=$$(realpath $(PYTHON3)) +cd "$$SRC_DIR" +if ! "$${PYTHON_BIN}" ./configure.py --bootstrap --with-python="$${PYTHON_BIN}" > /tmp/ninja_build.log 2>&1; then + cat /tmp/ninja_build.log >&2 + exit 1 +fi +cp ninja "$$OUT_FILE" +""", + toolchains = [ + "@rules_python//python:current_py_toolchain", + "@bazel_tools//tools/cpp:current_cc_toolchain", + ], + tools = [ + "@bazel_tools//tools/cpp:current_cc_toolchain", + "@rules_python//python:current_py_toolchain", + ], +) + +genrule( + name = "cmake_bin", + srcs = [ + "@fips_cmake_src//:all", + "@fips_cmake_src//:bootstrap", + ], + outs = ["cmake"], + cmd = """ +set -eo pipefail +SRC_DIR=$$(dirname $(location @fips_cmake_src//:bootstrap)) +MAKE_BIN=$$(dirname $(location @rules_foreign_cc//toolchains/private:make_tool)) +export PATH="$${MAKE_BIN}:$$PATH" +CLANG_BIN=$$(dirname $(location @fips_clang_ppc64le//:bin/clang)) +export CC="$${CLANG_BIN}/clang" +export CXX="$${CLANG_BIN}/clang++" +cd "$$SRC_DIR" +./bootstrap --parallel=$$(nproc) +make -j$$(nproc) +cp bin/cmake $@ +""", + target_compatible_with = select({ + "@platforms//cpu:x86_64": ["@platforms//:incompatible"], + "@platforms//cpu:aarch64": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + tools = [ + "@fips_clang_ppc64le//:bin/clang", + "@fips_clang_ppc64le//:bin/clang++", + "@rules_foreign_cc//toolchains/private:make_tool", + ], +) + genrule( name = "build", srcs = glob(["**"]), @@ -30,6 +92,34 @@ genrule( "crypto/libcrypto.a", "ssl/libssl.a", ], - cmd = "$(location {}) $(location crypto/libcrypto.a) $(location ssl/libssl.a)".format("@envoy//bazel/external:aws_lc.genrule_cmd"), - tools = ["@envoy//bazel/external:aws_lc.genrule_cmd"], + cmd = select(aws_lc_build_command()), + tools = [ + ":ninja_bin", + "@envoy//bazel/external:aws_lc.genrule_cmd", + ] + select({ + "@platforms//cpu:x86_64": [ + "@fips_cmake_linux_x86_64//:all", + "@fips_cmake_linux_x86_64//:bin/cmake", + "@fips_go_linux_amd64//:all", + "@fips_go_linux_amd64//:bin/go", + "@llvm_toolchain_llvm//:bin/clang", + "@llvm_toolchain_llvm//:bin/clang++", + ], + "@platforms//cpu:aarch64": [ + "@fips_cmake_linux_aarch64//:all", + "@fips_cmake_linux_aarch64//:bin/cmake", + "@fips_go_linux_arm64//:all", + "@fips_go_linux_arm64//:bin/go", + "@llvm_toolchain_llvm//:bin/clang", + "@llvm_toolchain_llvm//:bin/clang++", + ], + "@platforms//cpu:ppc64le": [ + ":cmake_bin", + "@fips_clang_ppc64le//:bin/clang", + "@fips_clang_ppc64le//:bin/clang++", + "@fips_go_ppc64le//:all", + "@fips_go_ppc64le//:bin/go", + ], + "//conditions:default": [], + }), ) diff --git a/bazel/external/aws_lc.genrule_cmd b/bazel/external/aws_lc.genrule_cmd index 82139095e5f78..ebd099c9bf8b9 100755 --- a/bazel/external/aws_lc.genrule_cmd +++ b/bazel/external/aws_lc.genrule_cmd @@ -5,11 +5,11 @@ set -e export CXXFLAGS='' export LDFLAGS='' -# BoringSSL build as described in the Security Policy for BoringCrypto module (2022-05-06): -# https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp4407.pdf +# AWS-LC FIPS build. +# https://github.com/aws/aws-lc/blob/main/crypto/fipsmodule/FIPS.md -OS=`uname` -ARCH=`uname -m` +OS=$(uname) +ARCH=$(uname -m) # This works only on Linux-x86_64, Linux-ppc64le, and Linux-aarch64. if [[ "$OS" != "Linux" || ("$ARCH" != "x86_64" && "$ARCH" != "aarch64" && "$ARCH" != "ppc64le") ]]; then @@ -17,150 +17,56 @@ if [[ "$OS" != "Linux" || ("$ARCH" != "x86_64" && "$ARCH" != "aarch64" && "$ARCH exit 1 fi +# Execroot (read-only in sandbox). Build in writable $TMPDIR. +EXECROOT="$PWD" +BUILD_DIR=$(mktemp -d) +trap 'rm -rf $BUILD_DIR' EXIT -# Bazel magic. -# ROOT=$(dirname $(rootpath boringssl/BUILDING.md))/.. -ROOT=./external -pushd "$ROOT" - -# Build tools requirements (from section 12.1 of https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp4407.pdf): -# - Clang compiler version 12.0.0 (https://releases.llvm.org/download.html) -# - Go programming language version 1.16.5 (https://golang.org/dl/) -# - Ninja build system version 1.10.2 (https://github.com/ninja-build/ninja/releases) -# - Cmake version 3.20.1 (https://cmake.org/download/) - -# Override $PATH for build tools, to avoid picking up anything else. -export PATH="$(dirname `which cmake`):/usr/bin:/bin" +# Go needs GOCACHE in a writable dir (sandbox has no HOME) +export GOCACHE="$BUILD_DIR/gocache" +mkdir -p "$GOCACHE" +export HOME="$BUILD_DIR" # Clang -VERSION=14.0.0 -if [[ "$ARCH" == "x86_64" ]]; then - PLATFORM="x86_64-linux-gnu-ubuntu-20.04" - SHA256=61582215dafafb7b576ea30cc136be92c877ba1f1c31ddbbd372d6d65622fef5 -elif [[ "$ARCH" == "ppc64le" ]]; then - PLATFORM="powerpc64le-linux-ubuntu-18.04" - SHA256=2d504c4920885c86b306358846178bc2232dfac83b47c3b1d05861a8162980e6 -else - PLATFORM="aarch64-linux-gnu" - SHA256=1792badcd44066c79148ffeb1746058422cc9d838462be07e3cb19a4b724a1ee -fi +export PATH="${CLANG_BIN_DIR}:$PATH" -curl -sLO https://github.com/llvm/llvm-project/releases/download/llvmorg-"$VERSION"/clang+llvm-"$VERSION"-"$PLATFORM".tar.xz -tar xf clang+llvm-"$VERSION"-"$PLATFORM".tar.xz - -export HOME="$PWD" -printf "set(CMAKE_C_COMPILER \"clang\")\nset(CMAKE_CXX_COMPILER \"clang++\")\n" > ${HOME}/toolchain -export PATH="$PWD/clang+llvm-$VERSION-$PLATFORM/bin:$PATH" - -if [[ `clang --version | head -1 | awk '{print $3}'` != "$VERSION" ]]; then - echo "ERROR: Clang version doesn't match. Expected: ${VERSION}, Got: $(clang --version)" - exit 1 -fi +# Toolchain file in writable dir +printf 'set(CMAKE_C_COMPILER "%s/clang")\nset(CMAKE_CXX_COMPILER "%s/clang++")\n' "$CLANG_BIN_DIR" "$CLANG_BIN_DIR" > "$BUILD_DIR/toolchain" # Go -VERSION=1.18.1 -if [[ "$ARCH" == "x86_64" ]]; then - PLATFORM="linux-amd64" - SHA256=b3b815f47ababac13810fc6021eb73d65478e0b2db4b09d348eefad9581a2334 -elif [[ "$ARCH" == "ppc64le" ]]; then - PLATFORM="linux-ppc64le" - SHA256=33db623d1eecf362fe365107c12efc90eff0b9609e0b3345e258388019cb552a -else - PLATFORM="linux-arm64" - SHA256=56a91851c97fb4697077abbca38860f735c32b38993ff79b088dac46e4735633 -fi - -curl -sLO https://dl.google.com/go/go"$VERSION"."$PLATFORM".tar.gz \ - && echo "$SHA256" go"$VERSION"."$PLATFORM".tar.gz | sha256sum --check -tar xf go"$VERSION"."$PLATFORM".tar.gz - -export GOPATH="$PWD/gopath" -export GOROOT="$PWD/go" -export PATH="$GOPATH/bin:$GOROOT/bin:$PATH" - -if [[ `go version | awk '{print $3}'` != "go$VERSION" ]]; then - echo "ERROR: Go version doesn't match." - exit 1 +if [[ -n "${GOROOT:-}" ]]; then + export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}" fi # Ninja -VERSION=1.10.2 -SHA256=ce35865411f0490368a8fc383f29071de6690cbadc27704734978221f25e2bed -curl -sLO https://github.com/ninja-build/ninja/archive/refs/tags/v"$VERSION".tar.gz \ - && echo "$SHA256" v"$VERSION".tar.gz | sha256sum --check -tar -xvf v"$VERSION".tar.gz -cd ninja-"$VERSION" -python3 ./configure.py --bootstrap - -export PATH="$PWD:$PATH" - -if [[ `ninja --version` != "$VERSION" ]]; then - echo "ERROR: Ninja version doesn't match." - exit 1 -fi -cd .. +export PATH="${NINJA_BIN_DIR}:$PATH" # CMake -VERSION=3.22.1 -if [[ "$ARCH" != "ppc64le" ]]; then - if [[ "$ARCH" == "x86_64" ]]; then - PLATFORM="linux-x86_64" - SHA256=73565c72355c6652e9db149249af36bcab44d9d478c5546fd926e69ad6b43640 - else - PLATFORM="linux-aarch64" - SHA256=601443375aa1a48a1a076bda7e3cca73af88400463e166fffc3e1da3ce03540b - fi - - curl -sLO https://github.com/Kitware/CMake/releases/download/v"$VERSION"/cmake-"$VERSION"-"$PLATFORM".tar.gz \ - && echo "$SHA256" cmake-"$VERSION"-"$PLATFORM".tar.gz | sha256sum --check - tar xf cmake-"$VERSION"-"$PLATFORM".tar.gz - export PATH="$PWD/cmake-$VERSION-$PLATFORM/bin:$PATH" -else - PLATFORM="linux-ppc64le" - echo "Building cmake for ppc64le" - - curl -sL -o cmake-$VERSION-$PLATFORM.tar.gz https://github.com/Kitware/CMake/releases/download/v"$VERSION"/cmake-"$VERSION".tar.gz - tar xf cmake-"$VERSION"-"$PLATFORM".tar.gz - - cd cmake-"$VERSION" - ./bootstrap && make - export PATH="$PWD/bin:$PATH" - cd .. -fi - -if [[ `cmake --version | head -n1` != "cmake version $VERSION" ]]; then - echo "PATH: $PATH" - echo "PLATFORM: $PLATFORM" - echo "ERROR: CMake version doesn't match. Expected: ${VERSION}, Got: $(cmake --version | head -n1)" - exit 1 +if [[ -n "${CMAKE_BIN_DIR:-}" ]]; then + export PATH="${CMAKE_BIN_DIR}:${PATH}" fi -echo "Cmake installed successfully" -echo "PWD: $PWD" - -# Clean after previous build. -rm -rf aws_lc/build - -# Build BoringSSL. -cd aws_lc - -# Setting -fPIC only affects the compilation of the non-module code in libcrypto.a, -# because the FIPS module itself is already built with -fPIC. -mkdir build && cd build && cmake -GNinja -DCMAKE_TOOLCHAIN_FILE=${HOME}/toolchain -DFIPS=1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. +# Copy source to writable dir (AWS-LC configure_file writes to source tree) +cp -rL "$EXECROOT/external/aws_lc" "$BUILD_DIR/aws_lc_src" + +# Build in writable dir (sandbox-safe) +cd "$BUILD_DIR" +cmake -GNinja \ + -DCMAKE_TOOLCHAIN_FILE="$BUILD_DIR/toolchain" \ + -DFIPS=1 \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_FLAGS="-fPIC" \ + -DCMAKE_CXX_FLAGS="-fPIC" \ + -S "$BUILD_DIR/aws_lc_src" \ + -B . ninja export GTEST_FILTER="-SSLTest.HostMatching" -#ninja run_tests ./crypto/crypto_test -echo "created build directory and built aws_lc with ninja" - -# Verify correctness of the FIPS build. -if [[ `tool/bssl isfips` != "1" ]]; then +if [[ $(tool/bssl isfips) != "1" ]]; then echo "ERROR: BoringSSL tool didn't report FIPS build." exit 1 fi -# Move compiled libraries to the expected destinations. -popd -mv $ROOT/aws_lc/build/crypto/libcrypto.a $1 -mv $ROOT/aws_lc/build/ssl/libssl.a $2 +cp "$BUILD_DIR/crypto/libcrypto.a" "$EXECROOT/$1" +cp "$BUILD_DIR/ssl/libssl.a" "$EXECROOT/$2" diff --git a/bazel/external/aws_lc_build.bzl b/bazel/external/aws_lc_build.bzl new file mode 100644 index 0000000000000..323164b5a53bc --- /dev/null +++ b/bazel/external/aws_lc_build.bzl @@ -0,0 +1,36 @@ +AWS_LC_BUILD_CMD = """ +set -eo pipefail +export CLANG_BIN_DIR="$$(cd $$(dirname $(location %s)) && pwd)" +export PATH="$${CLANG_BIN_DIR}:$$PATH" +export NINJA_BIN_DIR="$$(cd $$(dirname $(location :ninja_bin)) && pwd)" +export PATH="$${NINJA_BIN_DIR}:$$PATH" +GO_BINDIR=$$(realpath $$(dirname $(location %s//:bin/go))) +export GOROOT=$$(dirname "$${GO_BINDIR}") +export GOPATH="$${GOROOT}/gopath" +mkdir -p "$$GOPATH" +export PATH="$${GOPATH}/bin:$${GO_BINDIR}:$${PATH}" +CMAKE_BIN_DIR=$$(realpath $$(dirname $(location %s))) +export PATH="$${CMAKE_BIN_DIR}:$$PATH" +$(location @envoy//bazel/external:aws_lc.genrule_cmd) $(location crypto/libcrypto.a) $(location ssl/libssl.a) +""" + +SUPPORTED_ARCHES = ["x86_64", "aarch64", "ppc64le"] + +def aws_lc_build_command(): + result = {} + for arch in SUPPORTED_ARCHES: + if arch == "ppc64le": + cmake_label = ":cmake_bin" + clang_label = "@fips_clang_%s//:bin/clang" % arch + go_label = "@fips_go_%s" % arch + else: + cmake_label = "@fips_cmake_linux_%s//:bin/cmake" % arch + clang_label = "@llvm_toolchain_llvm//:bin/clang" + go_label = "@fips_go_linux_%s" % ("amd64" if arch == "x86_64" else "arm64") + result["@platforms//cpu:%s" % arch] = AWS_LC_BUILD_CMD % ( + clang_label, + go_label, + cmake_label, + ) + result["//conditions:default"] = "echo 'Unsupported arch for AWS-LC' && exit 1" + return result diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 1b1aa0ecebdc3..7df0b1c741fd4 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -317,6 +317,21 @@ def _aws_lc(): name = "aws_lc", build_file = "@envoy//bazel/external:aws_lc.BUILD", ) + CMAKE_SOURCE_BUILD_CONTENT = "%s\nexports_files([\"bootstrap\"])" % BUILD_ALL_CONTENT + external_http_archive( + name = "fips_cmake_src", + build_file_content = CMAKE_SOURCE_BUILD_CONTENT, + ) + CLANG_BUILD_CONTENT = "%s\nexports_files([\"bin/clang\", \"bin/clang++\"])" % BUILD_ALL_CONTENT + external_http_archive( + name = "fips_clang_ppc64le", + build_file_content = CLANG_BUILD_CONTENT, + ) + GO_BUILD_CONTENT = "%s\nexports_files([\"bin/go\"])" % _build_all_content(["test/**"]) + external_http_archive( + name = "fips_go_ppc64le", + build_file_content = GO_BUILD_CONTENT, + ) def _openssl(): external_http_archive( diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index cde29ed8429e6..99ce9a21accd6 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -731,6 +731,24 @@ REPOSITORY_LOCATIONS_SPEC = dict( strip_prefix = "go", urls = ["https://dl.google.com/go/go{version}.linux-arm64.tar.gz"], ), + fips_clang_ppc64le = dict( + version = "14.0.0", + sha256 = "2d504c4920885c86b306358846178bc2232dfac83b47c3b1d05861a8162980e6", + strip_prefix = "clang+llvm-{version}-powerpc64le-linux-ubuntu-18.04", + urls = ["https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/clang+llvm-{version}-powerpc64le-linux-ubuntu-18.04.tar.xz"], + ), + fips_go_ppc64le = dict( + version = "1.26.2", + sha256 = "62b7645dd2404052535617c59e91cf03c7aa28e332dbaddbe4c0d7de7bcc6736", + strip_prefix = "go", + urls = ["https://dl.google.com/go/go{version}.linux-ppc64le.tar.gz"], + ), + fips_cmake_src = dict( + version = "4.3.2", + sha256 = "b0231eb39b3c3cabdc568c619df78208a7bd95ea10c9b2236d61218bac1b367d", + strip_prefix = "cmake-{version}", + urls = ["https://github.com/Kitware/CMake/releases/download/v{version}/cmake-{version}.tar.gz"], + ), ) def _compiled_protoc_deps(locations, versions): diff --git a/source/common/tls/aws_lc_compat.h b/source/common/tls/aws_lc_compat.h index 351a85acb98c5..d8816cfb952d5 100644 --- a/source/common/tls/aws_lc_compat.h +++ b/source/common/tls/aws_lc_compat.h @@ -1,5 +1,7 @@ #pragma once +#include + // Aws-lc can be utilized as an alternative to boringssl // This file provides API translation from boringssl to aws-lc when Envoy is compiled with aws-lc // As of now, aws-lc is only compiled with Envoy for the ``ppc64le`` platform @@ -10,6 +12,24 @@ namespace Envoy { #ifdef OPENSSL_IS_AWSLC #define sk_X509_NAME_find sk_X509_NAME_find_awslc + +// AWS-LC does not support BoringSSL's compliance policy API. +// Define the enum and a stub that returns failure so call sites +// can handle it through normal error paths without #ifdef guards. +enum ssl_compliance_policy_t { + ssl_compliance_policy_fips_202205 = 1, +}; + +inline int SSL_CTX_set_compliance_policy(SSL_CTX* /*ctx*/, + enum ssl_compliance_policy_t /*policy*/) { + return 0; +} + +// AWS-LC's X509_NAME_dup takes non-const X509_NAME*, unlike BoringSSL which accepts const. +// Wrap it so call sites stay const-correct for other targets. +inline X509_NAME* X509_NAME_dup(const X509_NAME* name) { + return ::X509_NAME_dup(const_cast(name)); +} #endif } // namespace Envoy diff --git a/source/common/tls/context_impl.cc b/source/common/tls/context_impl.cc index 6aa236ddd111e..39888edb8e8a9 100644 --- a/source/common/tls/context_impl.cc +++ b/source/common/tls/context_impl.cc @@ -26,6 +26,7 @@ #include "source/common/protobuf/utility.h" #include "source/common/runtime/runtime_features.h" #include "source/common/stats/utility.h" +#include "source/common/tls/aws_lc_compat.h" #include "source/common/tls/cert_compression.h" #include "source/common/tls/cert_validator/factory.h" #include "source/common/tls/stats.h" From 7be853f757fbd07d6495cafb4b2cc31cdbbcd492 Mon Sep 17 00:00:00 2001 From: Achieve3318 Date: Thu, 23 Apr 2026 20:09:18 +0800 Subject: [PATCH 118/750] fix uhv underscore header sanitization use-after-free path (#44086) ## Summary This PR fixes a potential use-after-free path in underscore-header sanitization for Envoy default UHV when `headers_with_underscores_action` is `DROP_HEADER`. In `sanitizeHeadersWithUnderscores()`, header names were collected as non-owning `absl::string_view` values and then removed from the same header map. With duplicate underscore headers (for example, `x_foo` appearing multiple times), removing one header can invalidate storage referenced by later views. This change stores owning copies of header names (`std::string`) before mutation, preventing reads from dangling references during removal. Fixes: #44032 ## Changes - Updated: - `source/extensions/http/header_validators/envoy_default/header_validator.cc` - Replaced: - `std::vector` with `std::vector` - `push_back(header_name)` with `emplace_back(header_name)` - Added regression tests for duplicate underscore headers: - `test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc` - `test/extensions/http/header_validators/envoy_default/http2_header_validator_test.cc` ## Test Plan - Ran: - `//test/extensions/http/header_validators/envoy_default:http1_header_validator_test` - `//test/extensions/http/header_validators/envoy_default:http2_header_validator_test` - Both passed locally. --------- Signed-off-by: Achieve3318 Signed-off-by: code Co-authored-by: code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../envoy_default/header_validator.cc | 17 ++++++++++++----- .../http1_header_validator_test.cc | 17 +++++++++++++++++ .../http2_header_validator_test.cc | 17 +++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/source/extensions/http/header_validators/envoy_default/header_validator.cc b/source/extensions/http/header_validators/envoy_default/header_validator.cc index 9d1a848849b0a..3402e8bc79a5a 100644 --- a/source/extensions/http/header_validators/envoy_default/header_validator.cc +++ b/source/extensions/http/header_validators/envoy_default/header_validator.cc @@ -8,6 +8,7 @@ #include "source/common/runtime/runtime_features.h" #include "source/extensions/http/header_validators/envoy_default/character_tables.h" +#include "absl/container/flat_hash_set.h" #include "absl/container/node_hash_set.h" #include "absl/strings/match.h" @@ -634,21 +635,27 @@ void HeaderValidator::sanitizeHeadersWithUnderscores(::Envoy::Http::HeaderMap& h return; } - std::vector drop_headers; + // Store owning copies because removing one duplicate header can free the + // underlying storage for another matching entry. Using absl::string_view + // would still be unsafe here as the view may dangle after the first removal. + // We only need unique names. + absl::flat_hash_set drop_headers; header_map.iterate([&drop_headers](const ::Envoy::Http::HeaderEntry& header_entry) -> ::Envoy::Http::HeaderMap::Iterate { const absl::string_view header_name = header_entry.key().getStringView(); if (absl::StrContains(header_name, '_')) { - drop_headers.push_back(header_name); + drop_headers.emplace(header_name); } return ::Envoy::Http::HeaderMap::Iterate::Continue; }); ASSERT(drop_headers.empty() || underscore_action == HeaderValidatorConfig::DROP_HEADER); - for (auto& name : drop_headers) { - stats_.incDroppedHeadersWithUnderscores(); - header_map.remove(::Envoy::Http::LowerCaseString(name)); + for (const auto& name : drop_headers) { + const size_t removed_headers = header_map.remove(::Envoy::Http::LowerCaseString(name)); + for (size_t i = 0; i < removed_headers; ++i) { + stats_.incDroppedHeadersWithUnderscores(); + } } } diff --git a/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc b/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc index e8a6ad73950df..f5324eb6fd474 100644 --- a/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc +++ b/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc @@ -497,6 +497,23 @@ TEST_F(Http1HeaderValidatorTest, ValidateRequestHeaderMapDropUnderscoreHeaders) {{":scheme", "https"}, {":method", "GET"}, {":path", "/"}, {":authority", "envoy.com"}})); } +TEST_F(Http1HeaderValidatorTest, ValidateRequestHeaderMapDropDuplicateUnderscoreHeaders) { + ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, + {":method", "GET"}, + {":path", "/"}, + {":authority", "envoy.com"}, + {"x_foo", "bar"}}; + headers.addCopy("x_foo", "baz"); + auto uhv = createH1(drop_headers_with_underscores_config); + + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + EXPECT_EQ( + headers, + TestRequestHeaderMapImpl( + {{":scheme", "https"}, {":method", "GET"}, {":path", "/"}, {":authority", "envoy.com"}})); +} + TEST_F(Http1HeaderValidatorTest, RejectUnderscoreHeadersFromRequestHeadersWhenConfigured) { ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, {":method", "GET"}, diff --git a/test/extensions/http/header_validators/envoy_default/http2_header_validator_test.cc b/test/extensions/http/header_validators/envoy_default/http2_header_validator_test.cc index 75a817eb3745c..58094adcfbce7 100644 --- a/test/extensions/http/header_validators/envoy_default/http2_header_validator_test.cc +++ b/test/extensions/http/header_validators/envoy_default/http2_header_validator_test.cc @@ -260,6 +260,23 @@ TEST_F(Http2HeaderValidatorTest, ValidateRequestHeaderMapDropUnderscoreHeaders) {{":scheme", "https"}, {":method", "GET"}, {":path", "/"}, {":authority", "envoy.com"}})); } +TEST_F(Http2HeaderValidatorTest, ValidateRequestHeaderMapDropDuplicateUnderscoreHeaders) { + ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, + {":method", "GET"}, + {":path", "/"}, + {":authority", "envoy.com"}, + {"x_foo", "bar"}}; + headers.addCopy("x_foo", "baz"); + auto uhv = createH2ServerUhv(drop_headers_with_underscores_config); + + EXPECT_ACCEPT(uhv->validateRequestHeaders(headers)); + EXPECT_ACCEPT(uhv->transformRequestHeaders(headers)); + EXPECT_EQ( + headers, + ::Envoy::Http::TestRequestHeaderMapImpl( + {{":scheme", "https"}, {":method", "GET"}, {":path", "/"}, {":authority", "envoy.com"}})); +} + TEST_F(Http2HeaderValidatorTest, ValidateRequestHeaderMapRejectUnderscoreHeaders) { ::Envoy::Http::TestRequestHeaderMapImpl headers{{":scheme", "https"}, {":method", "GET"}, From 7a86d60b03917a9b0eba92a6941b3ba0499aea61 Mon Sep 17 00:00:00 2001 From: bsoumith <63022931+bsoumith@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:24:56 -0700 Subject: [PATCH 119/750] Scone support for QUIC in envoy (#44543) Add SCONE support to Envoy Mobile This commit introduces support for SCONE (Standardized Communication with Network Elements) in Envoy Mobile. Key changes include: - API to enable/disable SCONE in EngineBuilder. - SconeState object to store SCONE data within FilterState. - Integration with QUICHE to receive SCONE packet updates via EnvoyQuicClientSession::OnSconePacket. - Propagation of SCONE bandwidth and timestamp to the mobile application via envoy_stream_intel. - Integration tests to verify SCONE value propagation, including delayed and multiple update scenarios. - Added new terms (bitrate, scone_max_kbps) to spelling dictionary. --------- Signed-off-by: bsoumith --- api/envoy/config/core/v3/protocol.proto | 7 +- mobile/library/cc/engine_builder.cc | 9 + mobile/library/cc/engine_builder.h | 2 + mobile/library/common/http/BUILD | 1 + mobile/library/common/http/client.cc | 13 + mobile/library/common/http/client.h | 2 +- mobile/library/common/types/c_types.h | 4 + mobile/library/jni/jni_impl.cc | 1 + mobile/library/jni/jni_utility.cc | 4 + mobile/test/cc/unit/envoy_config_test.cc | 4 +- mobile/test/common/http/client_test.cc | 95 ++++++- mobile/test/common/integration/BUILD | 1 + .../integration/client_integration_test.cc | 236 ++++++++++++++++++ source/common/quic/BUILD | 12 + .../common/quic/envoy_quic_client_session.cc | 21 ++ .../common/quic/envoy_quic_client_session.h | 2 + source/common/quic/envoy_quic_utils.cc | 3 + source/common/quic/scone_state.cc | 9 + source/common/quic/scone_state.h | 25 ++ test/common/quic/BUILD | 1 + .../quic/envoy_quic_client_session_test.cc | 33 +++ test/common/quic/envoy_quic_utils_test.cc | 8 + tools/spelling/spelling_dictionary.txt | 2 + 23 files changed, 487 insertions(+), 8 deletions(-) create mode 100644 source/common/quic/scone_state.cc create mode 100644 source/common/quic/scone_state.h diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index 153916f09765a..d5d66be23423f 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -57,7 +57,7 @@ message QuicKeepAliveSettings { } // QUIC protocol options which apply to both downstream and upstream connections. -// [#next-free-field: 13] +// [#next-free-field: 14] message QuicProtocolOptions { // Config for QUIC connection migration across network interfaces, i.e. cellular to WIFI, upon // network change events from the platform, i.e. the current network gets @@ -179,6 +179,11 @@ message QuicProtocolOptions { // If not specified, memory reduction is set to infinite by QUIC connection (disabled). google.protobuf.Duration memory_reduction_timeout = 12 [(validate.rules).duration = {gte {seconds: 1}}]; + + // If true, the QUIC connection will signal support for `SCONE `_ (Standard + // Communication with Network Elements) and process SCONE packets. + // If not present, the QUICHE default behavior will be used. + google.protobuf.BoolValue enable_scone = 13; } message UpstreamHttpProtocolOptions { diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index 2eb20c53338ed..726e3c1546057 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -237,6 +237,11 @@ EngineBuilder& EngineBuilder::enableEarlyData(bool early_data_on) { return *this; } +EngineBuilder& EngineBuilder::enableScone(bool enable) { + scone_enabled_ = enable; + return *this; +} + EngineBuilder& EngineBuilder::addQuicConnectionOption(std::string option) { quic_connection_options_.push_back(std::move(option)); return *this; @@ -966,6 +971,10 @@ std::unique_ptr EngineBuilder::generate } } + if (scone_enabled_) { + quic_protocol_options->mutable_enable_scone()->set_value(true); + } + if (use_quic_platform_packet_writer_ || enable_quic_connection_migration_) { envoy_mobile::extensions::quic_packet_writer::platform::QuicPlatformPacketWriterConfig writer_config; diff --git a/mobile/library/cc/engine_builder.h b/mobile/library/cc/engine_builder.h index cdb7d27869349..3f9ceedcc3c1e 100644 --- a/mobile/library/cc/engine_builder.h +++ b/mobile/library/cc/engine_builder.h @@ -158,6 +158,7 @@ class EngineBuilder { EngineBuilder& enableSocketTagging(bool socket_tagging_on); EngineBuilder& enableHttp3(bool http3_on); EngineBuilder& enableEarlyData(bool early_data_on); + EngineBuilder& enableScone(bool enable); EngineBuilder& addQuicConnectionOption(std::string option); EngineBuilder& addQuicClientConnectionOption(std::string option); // Deprecated, use addQuicConnectionOption() instead. @@ -323,6 +324,7 @@ class EngineBuilder { std::string upstream_tls_sni_; bool enable_http3_ = true; bool enable_early_data_{true}; + bool scone_enabled_ = false; std::string http3_connection_options_ = ""; std::string http3_client_connection_options_ = ""; // EVMB is to distinguish Envoy Mobile client connections. diff --git a/mobile/library/common/http/BUILD b/mobile/library/common/http/BUILD index a06c83a836e90..1fa94ccc52b2d 100644 --- a/mobile/library/common/http/BUILD +++ b/mobile/library/common/http/BUILD @@ -42,6 +42,7 @@ envoy_cc_library( "@envoy//source/common/http:headers_lib", "@envoy//source/common/http:utility_lib", "@envoy//source/common/network:socket_lib", + "@envoy//source/common/quic:scone_state", "@envoy//source/common/stats:timespan_lib", ], ) diff --git a/mobile/library/common/http/client.cc b/mobile/library/common/http/client.cc index da2c06246f197..01bc31277d2f6 100644 --- a/mobile/library/common/http/client.cc +++ b/mobile/library/common/http/client.cc @@ -6,6 +6,7 @@ #include "source/common/http/header_map_impl.h" #include "source/common/http/headers.h" #include "source/common/http/utility.h" +#include "source/common/quic/scone_state.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" @@ -408,6 +409,18 @@ void Client::DirectStream::saveLatestStreamIntel() { } stream_intel_.stream_id = static_cast(stream_handle_); stream_intel_.attempt_count = info.attemptCount().value_or(0); + + const auto* scone_state = + info.filterState().getDataReadOnly(Envoy::Quic::SconeStateKey); + if (scone_state && scone_state->scone_max_kbps.has_value() && + scone_state->timestamp_ms.has_value()) { + // Only update if the new timestamp from scone_state is greater than the last recorded + // timestamp. + if (scone_state->timestamp_ms.value() > stream_intel_.scone_timestamp_ms) { + stream_intel_.scone_max_kbps = scone_state->scone_max_kbps.value(); + stream_intel_.scone_timestamp_ms = scone_state->timestamp_ms.value(); + } + } } void Client::DirectStream::saveFinalStreamIntel() { diff --git a/mobile/library/common/http/client.h b/mobile/library/common/http/client.h index 16238bce2a440..b8e996d38458a 100644 --- a/mobile/library/common/http/client.h +++ b/mobile/library/common/http/client.h @@ -349,7 +349,7 @@ class Client : public Logger::Loggable { // read faster than the mobile caller can process it. bool explicit_flow_control_ = false; // Latest intel data retrieved from the StreamInfo. - envoy_stream_intel stream_intel_{-1, -1, 0, 0}; + envoy_stream_intel stream_intel_{-1, -1, 0, 0, -1, -1}; envoy_final_stream_intel envoy_final_stream_intel_{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, -1}; StreamInfo::BytesMeterSharedPtr bytes_meter_; diff --git a/mobile/library/common/types/c_types.h b/mobile/library/common/types/c_types.h index 40ef2f0f488ea..3a0a684191ebc 100644 --- a/mobile/library/common/types/c_types.h +++ b/mobile/library/common/types/c_types.h @@ -154,6 +154,10 @@ typedef struct { // of bytes received before decompression. consumed_bytes_from_response omits the number // number of bytes related to the Status Line, and is after decompression. uint64_t consumed_bytes_from_response; + // The latest SCONE maximum bitrate received from the network, in kbps. + int64_t scone_max_kbps; + // Time since epoch when SCONE value was received, -1 if no new value + int64_t scone_timestamp_ms; } envoy_stream_intel; /** diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index a53cd424ab3d4..f542ad286e78c 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -1183,6 +1183,7 @@ void configureBuilder( builder.enableBrotliDecompression(enable_brotli_decompression == JNI_TRUE); builder.enableSocketTagging(enable_socket_tagging == JNI_TRUE); builder.enableHttp3(enable_http3 == JNI_TRUE); + // TODO(bsoumith): Expose enable_scone to mobile language bindings builder.enableEarlyData(enable_early_data == JNI_TRUE); builder.setHttp3ConnectionOptions( Envoy::JNI::javaStringToCppString(jni_helper, http3_connection_options)); diff --git a/mobile/library/jni/jni_utility.cc b/mobile/library/jni/jni_utility.cc index 41fe3ec4a2cda..70b33eac96f5b 100644 --- a/mobile/library/jni/jni_utility.cc +++ b/mobile/library/jni/jni_utility.cc @@ -707,6 +707,9 @@ envoy_stream_intel javaStreamIntelToCppStreamIntel(JniHelper& jni_helper, /* connection_id= */ static_cast(java_connection_id), /* attempt_count= */ static_cast(java_attempt_count), /* consumed_bytes_from_response= */ static_cast(java_consumed_bytes_from_response), + // TODO(bsoumith): Implement SCONE propagation in mobile language bindings + /* scone_max_kbps= */ -1, + /* scone_timestamp_ms= */ -1, }; } @@ -716,6 +719,7 @@ LocalRefUniquePtr cppStreamIntelToJavaStreamIntel(JniHelper& jni_helper jni_helper.findClassFromCache("io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel"); auto java_stream_intel_init_method_id = jni_helper.getMethodIdFromCache(java_stream_intel_class, "", "(JJJJ)V"); + // TODO(bsoumith): Implement SCONE propagation in mobile language bindings return jni_helper.newObject(java_stream_intel_class, java_stream_intel_init_method_id, static_cast(stream_intel.stream_id), static_cast(stream_intel.connection_id), diff --git a/mobile/test/cc/unit/envoy_config_test.cc b/mobile/test/cc/unit/envoy_config_test.cc index f5f784cc45625..a7fb65be1e7b2 100644 --- a/mobile/test/cc/unit/envoy_config_test.cc +++ b/mobile/test/cc/unit/envoy_config_test.cc @@ -86,7 +86,8 @@ TEST(TestConfig, ConfigIsApplied) { .addRuntimeGuard("quic_no_tcp_delay", true) .enableDnsCache(true, /* save_interval_seconds */ 101) .addDnsPreresolveHostnames({"lyft.com", "google.com"}) - .setDeviceOs("probably-ubuntu-on-CI"); + .setDeviceOs("probably-ubuntu-on-CI") + .enableScone(true); std::unique_ptr bootstrap = engine_builder.generateBootstrap(); const std::string config_str = bootstrap->ShortDebugString(); @@ -113,6 +114,7 @@ TEST(TestConfig, ConfigIsApplied) { "key: \"app_version\" value { string_value: \"1.2.3\" } }", "key: \"app_id\" value { string_value: \"1234-1234-1234\" } }", "initial_stream_window_size { value: 6291456 }", + "enable_scone { value: true }", "initial_connection_window_size { value: 15728640 }"}; for (const auto& string : must_contain) { diff --git a/mobile/test/common/http/client_test.cc b/mobile/test/common/http/client_test.cc index 0e15da98cc84b..f05b653db01cf 100644 --- a/mobile/test/common/http/client_test.cc +++ b/mobile/test/common/http/client_test.cc @@ -1,16 +1,22 @@ -#include +#include +#include +#include "envoy/http/header_map.h" +#include "envoy/stream_info/filter_state.h" #include "source/common/buffer/buffer_impl.h" +#include "source/common/http/header_map_impl.h" +#include "source/common/quic/scone_state.h" #include "source/common/stats/isolated_store_impl.h" #include "test/common/http/common.h" #include "test/common/mocks/common/mocks.h" #include "test/common/mocks/event/mocks.h" #include "test/mocks/buffer/mocks.h" +#include "test/mocks/common.h" #include "test/mocks/event/mocks.h" #include "test/mocks/http/api_listener.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/upstream/mocks.h" +#include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -25,10 +31,7 @@ using testing::ContainsRegex; using testing::Eq; using testing::NiceMock; using testing::Return; -using testing::ReturnPointee; using testing::ReturnRef; -using testing::SaveArg; -using testing::WithArg; namespace Envoy { namespace Http { @@ -151,6 +154,10 @@ class ClientTest : public testing::TestWithParam { http_client_.startStream(stream_, std::move(stream_callbacks), explicit_flow_control_ != OFF); } + Client::DirectStreamSharedPtr getDirectStream(envoy_stream_t stream_handle) { + return http_client_.getStream(stream_handle, Client::GetStreamFilters::AllowOnlyForOpenStreams); + } + void resumeDataIfEarlyResume(int32_t bytes) { if (explicit_flow_control_ == EARLY_RESUME) { auto callbacks = dynamic_cast(response_encoder_); @@ -999,5 +1006,83 @@ TEST_P(ExplicitFlowControlTest, CancelWithStreamComplete) { ASSERT_EQ(callbacks_called.on_complete_calls_, 0); } +TEST_P(ClientTest, SaveLatestStreamIntelPopulatesScone) { + StreamCallbacksCalled callbacks_called; + createStream(createDefaultStreamCallbacks(callbacks_called)); + + auto scone_state = std::make_shared(); + scone_state->scone_max_kbps = 100; + scone_state->timestamp_ms = 12345; + stream_info_.filter_state_->setData(Quic::SconeStateKey, scone_state, + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + + auto stream_ptr = getDirectStream(stream_); + ASSERT_NE(stream_ptr, nullptr); + + stream_ptr->saveLatestStreamIntel(); + + EXPECT_EQ(stream_ptr->stream_intel_.scone_max_kbps, 100); + EXPECT_EQ(stream_ptr->stream_intel_.scone_timestamp_ms, 12345); + EXPECT_TRUE(scone_state->scone_max_kbps.has_value()); + EXPECT_TRUE(scone_state->timestamp_ms.has_value()); +} + +TEST_P(ClientTest, SaveLatestStreamIntelPersistsScone) { + StreamCallbacksCalled callbacks_called; + createStream(createDefaultStreamCallbacks(callbacks_called)); + + auto scone_state = std::make_shared(); + scone_state->scone_max_kbps = 100; + scone_state->timestamp_ms = 12345; + stream_info_.filter_state_->setData(Quic::SconeStateKey, scone_state, + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + + auto stream_ptr = getDirectStream(stream_); + ASSERT_NE(stream_ptr, nullptr); + + stream_ptr->saveLatestStreamIntel(); + EXPECT_EQ(stream_ptr->stream_intel_.scone_max_kbps, 100); + EXPECT_EQ(stream_ptr->stream_intel_.scone_timestamp_ms, 12345); + + stream_ptr->saveLatestStreamIntel(); + EXPECT_EQ(stream_ptr->stream_intel_.scone_max_kbps, 100); + EXPECT_EQ(stream_ptr->stream_intel_.scone_timestamp_ms, 12345); +} + +TEST_P(ClientTest, SaveLatestStreamIntelWithNoSconeData) { + StreamCallbacksCalled callbacks_called; + createStream(createDefaultStreamCallbacks(callbacks_called)); + + auto stream_ptr = getDirectStream(stream_); + ASSERT_NE(stream_ptr, nullptr); + + stream_ptr->saveLatestStreamIntel(); + EXPECT_EQ(stream_ptr->stream_intel_.scone_max_kbps, -1); + EXPECT_EQ(stream_ptr->stream_intel_.scone_timestamp_ms, -1); +} + +TEST_P(ClientTest, SaveLatestStreamIntelWithZeroSconeValue) { + StreamCallbacksCalled callbacks_called; + createStream(createDefaultStreamCallbacks(callbacks_called)); + + auto scone_state = std::make_shared(); + scone_state->scone_max_kbps = 0; + scone_state->timestamp_ms = 12345; + stream_info_.filter_state_->setData(Quic::SconeStateKey, scone_state, + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + + auto stream_ptr = getDirectStream(stream_); + ASSERT_NE(stream_ptr, nullptr); + + stream_ptr->saveLatestStreamIntel(); + EXPECT_EQ(stream_ptr->stream_intel_.scone_max_kbps, 0); + EXPECT_EQ(stream_ptr->stream_intel_.scone_timestamp_ms, 12345); + EXPECT_TRUE(scone_state->scone_max_kbps.has_value()); + EXPECT_TRUE(scone_state->timestamp_ms.has_value()); +} + } // namespace Http } // namespace Envoy diff --git a/mobile/test/common/integration/BUILD b/mobile/test/common/integration/BUILD index 4e96a1b6c888c..74dff840b8056 100644 --- a/mobile/test/common/integration/BUILD +++ b/mobile/test/common/integration/BUILD @@ -40,6 +40,7 @@ envoy_cc_test( "@envoy//test/test_common:threadsafe_singleton_injector_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_build_config//:test_extensions", + "@quiche//:quic_test_tools_test_utils_lib", ], ) diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index dc0f6a5293bff..ab6b12c4caa5f 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -29,6 +29,7 @@ #include "library/common/network/network_types.h" #include "library/common/network/proxy_settings.h" #include "library/common/types/c_types.h" +#include "quiche/quic/test_tools/quic_test_utils.h" using testing::_; using testing::AnyNumber; @@ -1890,5 +1891,240 @@ TEST_P(ClientIntegrationTest, HttpsWithEarlyData) { EXPECT_EQ(1, getCounterValue("cluster.base.upstream_rq_0rtt")); } } + +class MockRecvMsgOsSysCalls : public Api::OsSysCallsImpl { +public: + MockRecvMsgOsSysCalls() { + ON_CALL(*this, recvmsg(_, _, _)) + .WillByDefault(Invoke([this](os_fd_t sockfd, msghdr* msg, int flags) { + auto result = Api::OsSysCallsImpl::recvmsg(sockfd, msg, flags); + int16_t bandwidth = scone_bandwidth_.exchange(-1); + if (result.return_value_ <= 0 || bandwidth == -1) { + return result; + } + + std::vector new_buffer(result.return_value_); + if (quic::test::MaybeUpdateSconePacket( + reinterpret_cast(msg->msg_iov[0].iov_base), new_buffer.data(), + result.return_value_, static_cast(bandwidth))) { + memcpy(msg->msg_iov[0].iov_base, new_buffer.data(), result.return_value_); + } + return result; + })); + } + + MOCK_METHOD(Api::SysCallSizeResult, recvmsg, (os_fd_t sockfd, msghdr* msg, int flags), + (override)); + + std::atomic scone_bandwidth_{-1}; +}; + +TEST_P(ClientIntegrationTest, SconeValuePropagation) { + if (upstreamProtocol() != Http::CodecType::HTTP3) { + return; + } + + const int16_t expected_bandwidth = 127; + + MockRecvMsgOsSysCalls sys_calls; + TestThreadsafeSingletonInjector injector(&sys_calls); + + builder_.enableScone(true); + initialize(); + + int64_t captured_scone_max_kbps = -1; + int64_t captured_scone_timestamp_ms = -1; + + EnvoyStreamCallbacks stream_callbacks = createDefaultStreamCallbacks(); + stream_callbacks.on_headers_ = [&](const Http::ResponseHeaderMap& headers, bool, + envoy_stream_intel intel) { + cc_.on_headers_calls_++; + cc_.status_ = absl::StrCat(headers.getStatusValue()); + captured_scone_max_kbps = intel.scone_max_kbps; + captured_scone_timestamp_ms = intel.scone_timestamp_ms; + }; + + stream_ = createNewStream(std::move(stream_callbacks)); + sys_calls.scone_bandwidth_.store(expected_bandwidth); + stream_->sendHeaders(std::make_unique(default_request_headers_), + true); + + cc_.terminal_callback_->waitReady(); + + EXPECT_EQ(captured_scone_max_kbps, expected_bandwidth); + EXPECT_GT(captured_scone_timestamp_ms, 0); + + int64_t captured_scone_max_kbps2 = -1; + int64_t captured_scone_timestamp_ms2 = -1; + EnvoyStreamCallbacks stream_callbacks2 = createDefaultStreamCallbacks(); + stream_callbacks2.on_headers_ = [&](const Http::ResponseHeaderMap& headers, bool, + envoy_stream_intel intel) { + cc_.on_headers_calls_++; + cc_.status_ = absl::StrCat(headers.getStatusValue()); + captured_scone_max_kbps2 = intel.scone_max_kbps; + captured_scone_timestamp_ms2 = intel.scone_timestamp_ms; + }; + + ConditionalInitializer terminal_callback2; + cc_.terminal_callback_ = &terminal_callback2; + + auto stream2 = createNewStream(std::move(stream_callbacks2)); + stream2->sendHeaders(std::make_unique(default_request_headers_), + true); + + terminal_callback2.waitReady(); + + EXPECT_EQ(captured_scone_max_kbps2, expected_bandwidth); + EXPECT_GT(captured_scone_timestamp_ms2, 0); +} + +TEST_P(ClientIntegrationTest, SconeValuePropagationDelayed) { + if (upstreamProtocol() != Http::CodecType::HTTP3) { + return; + } + + const int16_t expected_bandwidth = 50; + + MockRecvMsgOsSysCalls sys_calls; + TestThreadsafeSingletonInjector injector(&sys_calls); + + builder_.enableScone(true); + initialize(); + + int64_t captured_scone_max_kbps_headers = -1; + int64_t captured_scone_max_kbps_data = -1; + int64_t captured_scone_timestamp_ms_headers = -1; + int64_t captured_scone_timestamp_ms_data = -1; + + absl::Notification headers_received; + absl::Notification data_received; + + EnvoyStreamCallbacks stream_callbacks = createDefaultStreamCallbacks(); + stream_callbacks.on_headers_ = [&](const Http::ResponseHeaderMap& headers, bool, + envoy_stream_intel intel) { + cc_.on_headers_calls_++; + cc_.status_ = absl::StrCat(headers.getStatusValue()); + captured_scone_max_kbps_headers = intel.scone_max_kbps; + captured_scone_timestamp_ms_headers = intel.scone_timestamp_ms; + if (!headers_received.HasBeenNotified()) { + headers_received.Notify(); + } + }; + stream_callbacks.on_data_ = [&](const Buffer::Instance&, uint64_t, bool, + envoy_stream_intel intel) { + cc_.on_data_calls_++; + captured_scone_max_kbps_data = intel.scone_max_kbps; + captured_scone_timestamp_ms_data = intel.scone_timestamp_ms; + if (!data_received.HasBeenNotified()) { + data_received.Notify(); + } + }; + + stream_ = createNewStream(std::move(stream_callbacks)); + stream_->sendHeaders(std::make_unique(default_request_headers_), + true); + + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*BaseIntegrationTest::dispatcher_, + upstream_connection_)); + ASSERT_TRUE( + upstream_connection_->waitForNewStream(*BaseIntegrationTest::dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForEndStream(*BaseIntegrationTest::dispatcher_)); + + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + + headers_received.WaitForNotification(); + + EXPECT_EQ(captured_scone_max_kbps_headers, -1); + EXPECT_EQ(captured_scone_timestamp_ms_headers, -1); + + sys_calls.scone_bandwidth_.store(expected_bandwidth); + + upstream_request_->encodeData(10, false); + + data_received.WaitForNotification(); + + EXPECT_EQ(captured_scone_max_kbps_data, expected_bandwidth); + EXPECT_GT(captured_scone_timestamp_ms_data, 0); + + upstream_request_->encodeData(0, true); + + terminal_callback_.waitReady(); +} + +TEST_P(ClientIntegrationTest, SconeValuePropagationMultipleUpdates) { + if (upstreamProtocol() != Http::CodecType::HTTP3) { + return; + } + + const int16_t expected_bandwidth_1 = 10; + const int16_t expected_bandwidth_2 = 20; + + MockRecvMsgOsSysCalls sys_calls; + TestThreadsafeSingletonInjector injector(&sys_calls); + + builder_.enableScone(true); + initialize(); + + int64_t captured_scone_max_kbps_headers = -1; + int64_t captured_scone_max_kbps_data = -1; + int64_t captured_scone_timestamp_ms_headers = -1; + int64_t captured_scone_timestamp_ms_data = -1; + + absl::Notification headers_received; + absl::Notification data_received; + + EnvoyStreamCallbacks stream_callbacks = createDefaultStreamCallbacks(); + stream_callbacks.on_headers_ = [&](const Http::ResponseHeaderMap& headers, bool, + envoy_stream_intel intel) { + cc_.on_headers_calls_++; + cc_.status_ = absl::StrCat(headers.getStatusValue()); + captured_scone_max_kbps_headers = intel.scone_max_kbps; + captured_scone_timestamp_ms_headers = intel.scone_timestamp_ms; + if (!headers_received.HasBeenNotified()) { + headers_received.Notify(); + } + }; + stream_callbacks.on_data_ = [&](const Buffer::Instance&, uint64_t, bool, + envoy_stream_intel intel) { + cc_.on_data_calls_++; + captured_scone_max_kbps_data = intel.scone_max_kbps; + captured_scone_timestamp_ms_data = intel.scone_timestamp_ms; + if (!data_received.HasBeenNotified()) { + data_received.Notify(); + } + }; + + stream_ = createNewStream(std::move(stream_callbacks)); + sys_calls.scone_bandwidth_.store(expected_bandwidth_1); + stream_->sendHeaders(std::make_unique(default_request_headers_), + true); + + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*BaseIntegrationTest::dispatcher_, + upstream_connection_)); + ASSERT_TRUE( + upstream_connection_->waitForNewStream(*BaseIntegrationTest::dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForEndStream(*BaseIntegrationTest::dispatcher_)); + + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + + headers_received.WaitForNotification(); + + EXPECT_EQ(captured_scone_max_kbps_headers, expected_bandwidth_1); + EXPECT_GT(captured_scone_timestamp_ms_headers, 0); + + sys_calls.scone_bandwidth_.store(expected_bandwidth_2); + + upstream_request_->encodeData(10, false); + + data_received.WaitForNotification(); + + EXPECT_EQ(captured_scone_max_kbps_data, expected_bandwidth_2); + EXPECT_GT(captured_scone_timestamp_ms_data, 0); + EXPECT_GE(captured_scone_timestamp_ms_data, captured_scone_timestamp_ms_headers); + + upstream_request_->encodeData(0, true); + + terminal_callback_.waitReady(); +} } // namespace } // namespace Envoy diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index e80d5bdc7bde4..b9c1faa5796af 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -78,6 +78,17 @@ envoy_cc_library( ]), ) +envoy_cc_library( + name = "scone_state", + srcs = envoy_select_enable_http3(["scone_state.cc"]), + hdrs = envoy_select_enable_http3(["scone_state.h"]), + deps = envoy_select_enable_http3([ + "@abseil-cpp//absl/strings:string_view", + "@abseil-cpp//absl/types:optional", + "//envoy/stream_info:filter_state_interface", + ]), +) + envoy_cc_library( name = "quic_stat_names_lib", srcs = ["quic_stat_names.cc"], @@ -311,6 +322,7 @@ envoy_cc_library( ]), deps = envoy_select_enable_http3([ ":envoy_quic_client_connection_lib", + ":scone_state", ":envoy_quic_client_crypto_stream_factory_lib", ":envoy_quic_proof_verifier_lib", ":envoy_quic_stream_lib", diff --git a/source/common/quic/envoy_quic_client_session.cc b/source/common/quic/envoy_quic_client_session.cc index 90009e3e384cb..2e0388b22b987 100644 --- a/source/common/quic/envoy_quic_client_session.cc +++ b/source/common/quic/envoy_quic_client_session.cc @@ -11,6 +11,9 @@ #include "source/common/quic/envoy_quic_utils.h" #include "source/common/quic/quic_filter_manager_connection_impl.h" #include "source/common/quic/quic_network_connectivity_observer_impl.h" +#include "source/common/quic/scone_state.h" + +#include "quiche/quic/core/quic_bandwidth.h" namespace Envoy { namespace Quic { @@ -116,6 +119,10 @@ EnvoyQuicClientSession::EnvoyQuicClientSession( #ifdef ENVOY_ENABLE_HTTP_DATAGRAMS http_datagram_support_ = quic::HttpDatagramSupport::kRfc; #endif + + streamInfo().filterState()->setData(SconeStateKey, std::make_shared(), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); } EnvoyQuicClientSession::~EnvoyQuicClientSession() { @@ -353,5 +360,19 @@ void EnvoyQuicClientSession::StartDraining() { } } +void EnvoyQuicClientSession::OnSconePacket(quic::QuicBandwidth bandwidth) { + auto* state = streamInfo().filterState()->getDataMutable(SconeStateKey); + if (state) { + state->scone_max_kbps = bandwidth.ToKBitsPerSecond(); + auto now = dispatcher_.timeSource().systemTime(); + state->timestamp_ms = + std::chrono::duration_cast(now.time_since_epoch()).count(); + ENVOY_CONN_LOG(debug, "SCONE update: {} kbps at {} ms", *this, bandwidth.ToKBitsPerSecond(), + state->timestamp_ms.value()); + } else { + IS_ENVOY_BUG("SconeState not found in FilterState"); + } +} + } // namespace Quic } // namespace Envoy diff --git a/source/common/quic/envoy_quic_client_session.h b/source/common/quic/envoy_quic_client_session.h index ac16c73a7cda3..ba941ea2f59c5 100644 --- a/source/common/quic/envoy_quic_client_session.h +++ b/source/common/quic/envoy_quic_client_session.h @@ -10,6 +10,7 @@ #include "source/common/quic/quic_network_connectivity_observer.h" #include "source/common/quic/quic_stat_names.h" #include "source/common/quic/quic_transport_socket_factory.h" +#include "source/common/quic/scone_state.h" #include "quiche/quic/core/http/quic_spdy_client_session.h" @@ -79,6 +80,7 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, quic::HttpDatagramSupport LocalHttpDatagramSupport() override { return http_datagram_support_; } std::vector GetAlpnsToOffer() const override; void OnConfigNegotiated() override; + void OnSconePacket(quic::QuicBandwidth bandwidth) override; // quic::QuicSpdyClientSessionBase bool ShouldKeepConnectionAlive() const override; diff --git a/source/common/quic/envoy_quic_utils.cc b/source/common/quic/envoy_quic_utils.cc index 243529673bc11..fb6e9bb23774b 100644 --- a/source/common/quic/envoy_quic_utils.cc +++ b/source/common/quic/envoy_quic_utils.cc @@ -410,6 +410,9 @@ void convertQuicConfig(const envoy::config::core::v3::QuicProtocolOptions& confi quic_config.SetIdleNetworkTimeout(quic::QuicTimeDelta::FromSeconds( DurationUtil::durationToSeconds(config.idle_network_timeout()))); } + if (config.has_enable_scone()) { + quic_config.set_parse_scone_packets(config.enable_scone().value()); + } } void configQuicInitialFlowControlWindow(const envoy::config::core::v3::QuicProtocolOptions& config, diff --git a/source/common/quic/scone_state.cc b/source/common/quic/scone_state.cc new file mode 100644 index 0000000000000..0bd811e0f2ca1 --- /dev/null +++ b/source/common/quic/scone_state.cc @@ -0,0 +1,9 @@ +#include "source/common/quic/scone_state.h" + +namespace Envoy { +namespace Quic { + +SconeState::~SconeState() = default; + +} // namespace Quic +} // namespace Envoy diff --git a/source/common/quic/scone_state.h b/source/common/quic/scone_state.h new file mode 100644 index 0000000000000..448367df5ebea --- /dev/null +++ b/source/common/quic/scone_state.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "envoy/stream_info/filter_state.h" + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace Envoy { +namespace Quic { + +// This struct stores the latest SCONE maximum bitrate received from the network. +// It's designed to be stored in the FilterState. +struct SconeState : public StreamInfo::FilterState::Object { + ~SconeState() override; + absl::optional scone_max_kbps; + absl::optional timestamp_ms; +}; + +// Unique key to access SconeState within the FilterState. +constexpr absl::string_view SconeStateKey = "envoy.quic.scone_state"; + +} // namespace Quic +} // namespace Envoy diff --git a/test/common/quic/BUILD b/test/common/quic/BUILD index edc1293b9e264..c6ce9b3efe621 100644 --- a/test/common/quic/BUILD +++ b/test/common/quic/BUILD @@ -201,6 +201,7 @@ envoy_cc_test( "//source/common/quic:envoy_quic_alarm_factory_lib", "//source/common/quic:envoy_quic_client_connection_lib", "//source/common/quic:envoy_quic_client_session_lib", + "//source/common/quic:scone_state", "//source/common/quic:envoy_quic_connection_helper_lib", "//source/common/quic:quic_client_packet_writer_factory_impl_lib", "//source/extensions/quic/crypto_stream:envoy_quic_crypto_client_stream_lib", diff --git a/test/common/quic/envoy_quic_client_session_test.cc b/test/common/quic/envoy_quic_client_session_test.cc index 89c8f23f1358c..0d40ecf95163a 100644 --- a/test/common/quic/envoy_quic_client_session_test.cc +++ b/test/common/quic/envoy_quic_client_session_test.cc @@ -10,6 +10,7 @@ #include "source/common/quic/envoy_quic_connection_helper.h" #include "source/common/quic/envoy_quic_utils.h" #include "source/common/quic/quic_client_packet_writer_factory_impl.h" +#include "source/common/quic/scone_state.h" #include "source/extensions/quic/crypto_stream/envoy_quic_crypto_client_stream.h" #include "test/common/quic/test_utils.h" @@ -29,6 +30,7 @@ #include "gtest/gtest.h" #include "quiche/quic/core/crypto/null_encrypter.h" #include "quiche/quic/core/deterministic_connection_id_generator.h" +#include "quiche/quic/core/quic_bandwidth.h" #include "quiche/quic/test_tools/crypto_test_utils.h" #include "quiche/quic/test_tools/quic_session_peer.h" #include "quiche/quic/test_tools/quic_test_utils.h" @@ -918,5 +920,36 @@ TEST_P(EnvoyQuicClientSessionAllowMmsgTest, UsesRecvMmsgWhenNoGroAndMmsgAllowed) dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit)); } +TEST_P(EnvoyQuicClientSessionTest, OnSconePacketUpdatesFilterState) { + envoy_quic_session_->OnSconePacket(quic::QuicBandwidth::FromKBitsPerSecond(100)); + + auto filter_state = envoy_quic_session_->streamInfo().filterState(); + ASSERT_TRUE(filter_state->hasData(SconeStateKey)); + auto scone_state = filter_state->getDataReadOnly(SconeStateKey); + ASSERT_TRUE(scone_state->scone_max_kbps.has_value()); + EXPECT_EQ(scone_state->scone_max_kbps.value(), 100); + ASSERT_TRUE(scone_state->timestamp_ms.has_value()); + EXPECT_GT(scone_state->timestamp_ms.value(), 0); + + // Verify that multiple calls to OnSconePacket with different bandwidth values correctly update + // the scone_max_kbps + envoy_quic_session_->OnSconePacket(quic::QuicBandwidth::FromKBitsPerSecond(200)); + ASSERT_TRUE(scone_state->scone_max_kbps.has_value()); + EXPECT_EQ(scone_state->scone_max_kbps.value(), 200); + ASSERT_TRUE(scone_state->timestamp_ms.has_value()); + + // Verify that the SconeState object persists across multiple calls + envoy_quic_session_->OnSconePacket(quic::QuicBandwidth::FromKBitsPerSecond(300)); + ASSERT_TRUE(scone_state->scone_max_kbps.has_value()); + EXPECT_EQ(scone_state->scone_max_kbps.value(), 300); + ASSERT_TRUE(scone_state->timestamp_ms.has_value()); +} + +TEST_P(EnvoyQuicClientSessionTest, SconeStateInitialization) { + auto filter_state = envoy_quic_session_->streamInfo().filterState(); + ASSERT_TRUE(filter_state->hasData(SconeStateKey)); + auto scone_state = filter_state->getDataReadOnly(SconeStateKey); + EXPECT_FALSE(scone_state->scone_max_kbps.has_value()); +} } // namespace Quic } // namespace Envoy diff --git a/test/common/quic/envoy_quic_utils_test.cc b/test/common/quic/envoy_quic_utils_test.cc index 43f6812fe8133..72c21426ed154 100644 --- a/test/common/quic/envoy_quic_utils_test.cc +++ b/test/common/quic/envoy_quic_utils_test.cc @@ -237,6 +237,14 @@ TEST(EnvoyQuicUtilsTest, ConvertQuicConfig) { quic_ccopts.append(quic::QuicTagToString(ccopt)); } EXPECT_EQ(quic_ccopts, "6RTOAKD4"); + + config.mutable_enable_scone()->set_value(true); + convertQuicConfig(config, quic_config); + EXPECT_TRUE(quic_config.parse_scone_packets()); + + config.mutable_enable_scone()->set_value(false); + convertQuicConfig(config, quic_config); + EXPECT_FALSE(quic_config.parse_scone_packets()); } TEST(EnvoyQuicUtilsTest, HeaderMapMaxSizeLimit) { diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 5db448e2d06f0..b0f9bc0af162f 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1700,3 +1700,5 @@ nblk chr IWYU CRTP +bitrate +kbps From c95e4b597a4872c80daf6f7c61ae7bc920af0714 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 23 Apr 2026 17:43:42 +0100 Subject: [PATCH 120/750] shared_pool: fix leak when last reference drops on worker thread during dispatcher teardown (#44570) Fixes dfp asan flake --------- Signed-off-by: Ryan Northey Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- changelogs/current.yaml | 7 + source/common/shared_pool/shared_pool.h | 10 +- .../dynamic_forward_proxy/dns_cache_impl.cc | 21 ++- test/common/shared_pool/shared_pool_test.cc | 53 +++++++ .../dns_cache_impl_test.cc | 146 +++++++++++++++++- 5 files changed, 225 insertions(+), 12 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index fe0b1a5a0023b..971b4a290a9cf 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -407,6 +407,13 @@ bug_fixes: - area: http2 change: | Apply nghttp2 CVE-2026-27135 patch. +- area: shared_pool + change: | + Fixed a leak in ``ObjectSharedPool`` when the last reference to a pooled object is released + on a non-main thread after the main dispatcher has started teardown. The queued cross-thread + deletion now carries ownership, so the object is freed whether or not the dispatcher runs + the queued callback. Most visibly this fixes a LeakSanitizer leak in + ``StrictDnsClusterImpl`` involving the ``Locality`` shared pool during server shutdown. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/shared_pool/shared_pool.h b/source/common/shared_pool/shared_pool.h index 898dc82dc0728..024237c2ad321 100644 --- a/source/common/shared_pool/shared_pool.h +++ b/source/common/shared_pool/shared_pool.h @@ -89,7 +89,15 @@ class ObjectSharedPool auto this_shared_ptr = this->shared_from_this(); // Used for testing to simulate some race condition scenarios sync_.syncPoint(DeleteObjectOnMainThread); - dispatcher_.post([ptr, this_shared_ptr] { this_shared_ptr->deleteObjectOnMainThread(ptr); }); + // Transfer ownership of `ptr` into a std::unique_ptr captured by the posted lambda. + // If the main dispatcher is torn down before the post runs (e.g. a worker thread + // releases the last shared_ptr during server shutdown), the dispatcher destroys the + // queued callback; the captured unique_ptr's destructor then frees the object, avoiding + // a leak. If the lambda does run, release() hands the raw pointer to the main-thread + // cleanup path, which performs the object_pool_ bookkeeping and deletes the object. + dispatcher_.post([owned_ptr = std::unique_ptr(ptr), this_shared_ptr]() mutable { + this_shared_ptr->deleteObjectOnMainThread(owned_ptr.release()); + }); } } diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc index 9cbcdb8ecde12..4188e5abba034 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc @@ -293,7 +293,7 @@ void DnsCacheImpl::onReResolveAlarm(const std::string& host) { auto last_used_time = primary_host.host_info_->lastUsedTime(); ENVOY_LOG(debug, "host='{}' TTL check: now={} last_used={} TTL {}", host, now_duration.count(), last_used_time.count(), host_ttl_.count()); - if ((now_duration - last_used_time) > host_ttl_) { + if ((now_duration - last_used_time) >= host_ttl_) { ENVOY_LOG(debug, "host='{}' TTL expired, removing", host); removeHost(host, primary_host, true); } else { @@ -568,19 +568,26 @@ void DnsCacheImpl::finishResolve(const std::string& host, // which can exceed host_ttl by a large margin. const auto now = main_thread_dispatcher_.timeSource().monotonicTime().time_since_epoch(); const auto elapsed = now - primary_host_info->host_info_->lastUsedTime(); - std::chrono::milliseconds refresh_interval( + const std::chrono::milliseconds raw_backoff_ms( primary_host_info->failure_backoff_strategy_->nextBackOffMs()); + std::chrono::milliseconds refresh_interval(raw_backoff_ms); if (elapsed >= host_ttl_) { - refresh_interval = std::chrono::milliseconds(1); + refresh_interval = std::chrono::milliseconds(0); } else { const auto until_eviction = - std::chrono::duration_cast(host_ttl_ - elapsed) + - std::chrono::milliseconds(1); + std::chrono::duration_cast(host_ttl_ - elapsed); refresh_interval = std::min(refresh_interval, until_eviction); } + // Floor the result at min_refresh_interval_ (dns_min_refresh_rate) to prevent arming + // a ms-scale alarm that can kick rapid-fire resolves and race with dispatcher/resolver + // teardown in integration tests (observed as a LeakSanitizer leak in + // proxy_filter_integration_test DoubleResolution). + refresh_interval = + std::max(refresh_interval, + std::chrono::duration_cast(min_refresh_interval_)); primary_host_info->refresh_timer_->enableTimer(refresh_interval); - ENVOY_LOG(debug, "DNS refresh rate reset for host '{}', (failure) refresh rate {} ms", host, - refresh_interval.count()); + ENVOY_LOG(debug, "DNS refresh rate reset for host '{}', (failure) raw={} ms armed={} ms", + host, raw_backoff_ms.count(), refresh_interval.count()); } } } diff --git a/test/common/shared_pool/shared_pool_test.cc b/test/common/shared_pool/shared_pool_test.cc index e11d33233a130..7d90ed31c8745 100644 --- a/test/common/shared_pool/shared_pool_test.cc +++ b/test/common/shared_pool/shared_pool_test.cc @@ -1,3 +1,4 @@ +#include #include #include "source/common/event/timer_impl.h" @@ -13,6 +14,21 @@ namespace Envoy { namespace SharedPool { +namespace { +struct Counted { + explicit Counted(int v) : v_(v) { ++live; } + Counted(const Counted& o) : v_(o.v_) { ++live; } + Counted(Counted&& o) noexcept : v_(o.v_) { ++live; } + ~Counted() { --live; } + bool operator==(const Counted& o) const { return v_ == o.v_; } + int v_; + inline static std::atomic live{0}; +}; +struct CountedHash { + size_t operator()(const Counted& c) const { return static_cast(c.v_); } +}; +} // namespace + class SharedPoolTest : public testing::Test { protected: SharedPoolTest() @@ -222,5 +238,42 @@ TEST_F(SharedPoolTest, HashCollision) { EXPECT_EQ(0, pool->poolSize()); } +TEST_F(SharedPoolTest, DispatcherTeardownDropsCrossThreadDeleteFreesObject) { + ASSERT_EQ(0, Counted::live.load()); + + testing::NiceMock dispatcher; + std::vector posted_cbs; + EXPECT_CALL(dispatcher, post(testing::_)).WillRepeatedly(testing::Invoke([&](Event::PostCb cb) { + posted_cbs.push_back(std::move(cb)); + })); + + auto pool = std::make_shared>(dispatcher); + auto obj = pool->getObject(Counted{7}); + ASSERT_EQ(1, Counted::live.load()); + ASSERT_TRUE(posted_cbs.empty()); + + // Drop the last shared_ptr from a different thread, triggering the cross-thread delete path. + Thread::ThreadFactory& thread_factory = Thread::threadFactoryForTest(); + auto thread = thread_factory.createThread([&]() { obj.reset(); }); + thread->join(); + + // One callback should have been posted (the deferred delete). + ASSERT_EQ(1u, posted_cbs.size()); + // The object is still alive — held by the captured unique_ptr inside the posted lambda. + EXPECT_EQ(1, Counted::live.load()); + + // Simulate dispatcher teardown: drop the queued callback without running it. + // The captured unique_ptr destructor must free the object. + posted_cbs.clear(); + + // Regression assertion: prior to the fix, the raw T* was captured by value and the + // callback destructor could not free it, causing a leak (live would still be 1). + EXPECT_EQ(0, Counted::live.load()); + + // Release the pool; the object is already destroyed, so this is a no-op for the counter. + pool.reset(); + EXPECT_EQ(0, Counted::live.load()); +} + } // namespace SharedPool } // namespace Envoy diff --git a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc index 882388daa5edc..4bf75efde53d7 100644 --- a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc +++ b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc @@ -910,17 +910,153 @@ TEST_F(DnsCacheImplTest, ResolveFailureBackoffCappedByHostTtl) { resolve_timer->invokeCallback(); // DNS fails. The raw failure backoff is 60s (dns_refresh_rate). Without the cap the next alarm - // would fire at T+61s. With the cap, it fires at host_ttl-elapsed+1ms = 1000-100+1 = 901ms. + // would fire at T+61s. The cap reduces it to host_ttl-elapsed = 1000-100 = 900ms, which is + // then floored at dns_min_refresh_rate = 1000ms because the cap kicked in. EXPECT_CALL(*timeout_timer, disableTimer()); EXPECT_CALL(update_callbacks_, onDnsResolutionComplete("foo.com:80", DnsHostInfoEquals("10.0.0.1:80", "foo.com", false), Network::DnsResolver::ResolutionStatus::Failure)); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(901), _)); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(1000), _)); + resolve_cb(Network::DnsResolver::ResolutionStatus::Failure, "", TestUtility::makeDnsResponse({})); + + // After the floored backoff now-last_used = 1100ms > host_ttl (1000ms): the host is evicted. + simTime().advanceTimeWait(std::chrono::milliseconds(1000)); + EXPECT_CALL(update_callbacks_, onDnsHostRemove("foo.com:80")); + resolve_timer->invokeCallback(); + checkStats(2 /* attempt */, 1 /* success */, 1 /* failure */, 1 /* address changed */, + 1 /* added */, 1 /* removed */, 0 /* num hosts */); +} + +TEST_F(DnsCacheImplTest, ResolveFailureBackoffZeroFlooredByMinRefresh) { + // Stub random() to return 0 so the jittered exponential backoff yields 0 + // (nextBackOffMs() returns random() % base_interval == 0). + ON_CALL(context_.server_context_.api_.random_, random()).WillByDefault(Return(0)); + *config_.mutable_dns_failure_refresh_rate()->mutable_base_interval() = + Protobuf::util::TimeUtil::SecondsToDuration(2); + *config_.mutable_dns_failure_refresh_rate()->mutable_max_interval() = + Protobuf::util::TimeUtil::SecondsToDuration(5); + *config_.mutable_host_ttl() = Protobuf::util::TimeUtil::SecondsToDuration(60); + *config_.mutable_dns_min_refresh_rate() = Protobuf::util::TimeUtil::SecondsToDuration(1); + initialize(); + InSequence s; + + MockLoadDnsCacheEntryCallbacks callbacks; + Network::DnsResolver::ResolveCb resolve_cb; + Event::MockTimer* resolve_timer = new Event::MockTimer(&context_.server_context_.dispatcher_); + Event::MockTimer* timeout_timer = new Event::MockTimer(&context_.server_context_.dispatcher_); + EXPECT_CALL(*timeout_timer, enableTimer(std::chrono::milliseconds(5000), nullptr)); + EXPECT_CALL(*resolver_, resolve("foo.com", _, _)) + .WillOnce(DoAll(SaveArg<2>(&resolve_cb), Return(&resolver_->active_query_))); + auto result = dns_cache_->loadDnsCacheEntry("foo.com", 80, false, callbacks); + EXPECT_EQ(DnsCache::LoadDnsCacheEntryStatus::Loading, result.status_); + + EXPECT_CALL(*timeout_timer, disableTimer()); + EXPECT_CALL( + update_callbacks_, + onDnsHostAddOrUpdate("foo.com:80", DnsHostInfoEquals("10.0.0.1:80", "foo.com", false))); + EXPECT_CALL(callbacks, + onLoadDnsCacheComplete(DnsHostInfoEquals("10.0.0.1:80", "foo.com", false))); + EXPECT_CALL(update_callbacks_, + onDnsResolutionComplete("foo.com:80", + DnsHostInfoEquals("10.0.0.1:80", "foo.com", false), + Network::DnsResolver::ResolutionStatus::Completed)); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(dns_ttl_), _)); + resolve_cb(Network::DnsResolver::ResolutionStatus::Completed, "", + TestUtility::makeDnsResponse({"10.0.0.1"})); + + checkStats(1 /* attempt */, 1 /* success */, 0 /* failure */, 1 /* address changed */, + 1 /* added */, 0 /* removed */, 1 /* num hosts */); + + // Advance a small amount so elapsed is well below host_ttl. + simTime().advanceTimeWait(std::chrono::milliseconds(100)); + + EXPECT_CALL(*timeout_timer, enableTimer(std::chrono::milliseconds(5000), nullptr)); + EXPECT_CALL(*resolver_, resolve("foo.com", _, _)) + .WillOnce(DoAll(SaveArg<2>(&resolve_cb), Return(&resolver_->active_query_))); + resolve_timer->invokeCallback(); + + // DNS fails. With random()==0 the jittered backoff returns 0. The TTL cap does not kick in + // (host_ttl=60s >> elapsed=100ms). The floor at dns_min_refresh_rate (1s) lifts it to 1000ms. + EXPECT_CALL(*timeout_timer, disableTimer()); + EXPECT_CALL(update_callbacks_, onDnsHostAddOrUpdate(_, _)).Times(0); + EXPECT_CALL(callbacks, onLoadDnsCacheComplete(_)).Times(0); + EXPECT_CALL(update_callbacks_, + onDnsResolutionComplete("foo.com:80", + DnsHostInfoEquals("10.0.0.1:80", "foo.com", false), + Network::DnsResolver::ResolutionStatus::Failure)); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(1000), _)); resolve_cb(Network::DnsResolver::ResolutionStatus::Failure, "", TestUtility::makeDnsResponse({})); + checkStats(2 /* attempt */, 1 /* success */, 1 /* failure */, 1 /* address changed */, + 1 /* added */, 0 /* removed */, 1 /* num hosts */); +} + +TEST_F(DnsCacheImplTest, ResolveFailurePastHostTtlFlooredByMinRefresh) { + *config_.mutable_host_ttl() = Protobuf::util::TimeUtil::SecondsToDuration(1); + *config_.mutable_dns_refresh_rate() = Protobuf::util::TimeUtil::SecondsToDuration(60); + *config_.mutable_dns_min_refresh_rate() = Protobuf::util::TimeUtil::SecondsToDuration(1); + initialize(); + InSequence s; + + MockLoadDnsCacheEntryCallbacks callbacks; + Network::DnsResolver::ResolveCb resolve_cb; + Event::MockTimer* resolve_timer = new Event::MockTimer(&context_.server_context_.dispatcher_); + Event::MockTimer* timeout_timer = new Event::MockTimer(&context_.server_context_.dispatcher_); + EXPECT_CALL(*timeout_timer, enableTimer(std::chrono::milliseconds(5000), nullptr)); + EXPECT_CALL(*resolver_, resolve("foo.com", _, _)) + .WillOnce(DoAll(SaveArg<2>(&resolve_cb), Return(&resolver_->active_query_))); + auto result = dns_cache_->loadDnsCacheEntry("foo.com", 80, false, callbacks); + EXPECT_EQ(DnsCache::LoadDnsCacheEntryStatus::Loading, result.status_); + + EXPECT_CALL(*timeout_timer, disableTimer()); + EXPECT_CALL( + update_callbacks_, + onDnsHostAddOrUpdate("foo.com:80", DnsHostInfoEquals("10.0.0.1:80", "foo.com", false))); + EXPECT_CALL(callbacks, + onLoadDnsCacheComplete(DnsHostInfoEquals("10.0.0.1:80", "foo.com", false))); + EXPECT_CALL(update_callbacks_, + onDnsResolutionComplete("foo.com:80", + DnsHostInfoEquals("10.0.0.1:80", "foo.com", false), + Network::DnsResolver::ResolutionStatus::Completed)); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(dns_ttl_), _)); + resolve_cb(Network::DnsResolver::ResolutionStatus::Completed, "", + TestUtility::makeDnsResponse({"10.0.0.1"})); + + checkStats(1 /* attempt */, 1 /* success */, 0 /* failure */, 1 /* address changed */, + 1 /* added */, 0 /* removed */, 1 /* num hosts */); + + // Advance a small amount so the alarm fires before TTL (now - last_used = 100ms < 1000ms = + // host_ttl), which causes onReResolveAlarm to startResolve rather than evict. + simTime().advanceTimeWait(std::chrono::milliseconds(100)); + + EXPECT_CALL(*timeout_timer, enableTimer(std::chrono::milliseconds(5000), nullptr)); + EXPECT_CALL(*resolver_, resolve("foo.com", _, _)) + .WillOnce(DoAll(SaveArg<2>(&resolve_cb), Return(&resolver_->active_query_))); + resolve_timer->invokeCallback(); + + // Advance past the TTL boundary so that elapsed = 1000ms >= host_ttl (1000ms) when + // finishResolve runs. + simTime().advanceTimeWait(std::chrono::milliseconds(900)); - // After the capped backoff now-last_used = 1001ms > host_ttl: the host is evicted. - simTime().advanceTimeWait(std::chrono::milliseconds(901)); + // DNS fails. elapsed >= host_ttl so the past-TTL branch sets refresh_interval to 0. + // The unconditional floor then lifts it to dns_min_refresh_rate (1000ms). + EXPECT_CALL(*timeout_timer, disableTimer()); + EXPECT_CALL(update_callbacks_, onDnsHostAddOrUpdate(_, _)).Times(0); + EXPECT_CALL(callbacks, onLoadDnsCacheComplete(_)).Times(0); + EXPECT_CALL(update_callbacks_, + onDnsResolutionComplete("foo.com:80", + DnsHostInfoEquals("10.0.0.1:80", "foo.com", false), + Network::DnsResolver::ResolutionStatus::Failure)); + // Past-TTL branch: raw backoff is discarded and refresh_interval becomes 0; + // the unconditional floor lifts it to dns_min_refresh_rate (1000ms). + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(1000), _)); + resolve_cb(Network::DnsResolver::ResolutionStatus::Failure, "", TestUtility::makeDnsResponse({})); + + checkStats(2 /* attempt */, 1 /* success */, 1 /* failure */, 1 /* address changed */, + 1 /* added */, 0 /* removed */, 1 /* num hosts */); + + // After the floored interval now-last_used = 2000ms >= host_ttl (1000ms): the host is evicted. + simTime().advanceTimeWait(std::chrono::milliseconds(1000)); EXPECT_CALL(update_callbacks_, onDnsHostRemove("foo.com:80")); resolve_timer->invokeCallback(); checkStats(2 /* attempt */, 1 /* success */, 1 /* failure */, 1 /* address changed */, @@ -1036,6 +1172,7 @@ TEST_F(DnsCacheImplTest, ResolveFailureWithFailureRefreshRate) { Protobuf::util::TimeUtil::SecondsToDuration(7); *config_.mutable_dns_failure_refresh_rate()->mutable_max_interval() = Protobuf::util::TimeUtil::SecondsToDuration(10); + *config_.mutable_dns_min_refresh_rate() = Protobuf::util::TimeUtil::SecondsToDuration(1); initialize(); InSequence s; @@ -1089,6 +1226,7 @@ TEST_F(DnsCacheImplTest, ResolveFailureAfterResolveSuccessWithFailureRefreshRate Protobuf::util::TimeUtil::SecondsToDuration(2); *config_.mutable_dns_failure_refresh_rate()->mutable_max_interval() = Protobuf::util::TimeUtil::SecondsToDuration(5); + *config_.mutable_dns_min_refresh_rate() = Protobuf::util::TimeUtil::SecondsToDuration(1); initialize(); InSequence s; From d7bc39f82046a525b600bf5f8cc6ee63471bfe70 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 23 Apr 2026 17:51:51 +0100 Subject: [PATCH 121/750] ci/codeql: ignore bundled libc++ headers in CodeQL analysis (#44609) Signed-off-by: Ryan Northey --- .github/codeql/codeql-config.yml | 15 +++++++++++++++ .github/workflows/codeql-daily.yml | 1 + .github/workflows/codeql-push.yml | 1 + 3 files changed, 17 insertions(+) create mode 100644 .github/codeql/codeql-config.yml diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000000000..66554f85ab465 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,15 @@ +name: "Envoy CodeQL config" + +# The CodeQL workflows download a prebuilt clang bundle and untar it into +# $GITHUB_WORKSPACE/bin/clang18.1.8/. Bazel is then pointed at that path via +# BAZEL_LLVM_PATH, which causes CodeQL to see the bundled libc++ headers as +# project source files. Alerts whose primary location is inside those headers +# (e.g. cpp/new-free-mismatch in ) are structural false positives +# from dataflow losing precision across allocator/template instantiations -- +# they are not fixable in Envoy and are not indicative of real libc++ bugs. +# +# Scope is intentionally narrow: only libc++ (clang bundle) headers, matched +# by the standard v1 layout. Do not broaden to bin/** or to libstdc++ +# (include/c++/** without the v1/ suffix). +paths-ignore: +- '**/include/c++/v1/**' diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 89f88841db3a0..9e7b8ff9e6f82 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -56,6 +56,7 @@ jobs: with: languages: cpp trap-caching: false + config-file: ./.github/codeql/codeql-config.yml - name: Install deps shell: bash diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index e724b6e7dcd7b..d6947b0ea36fb 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -92,6 +92,7 @@ jobs: with: languages: cpp trap-caching: false + config-file: ./.github/codeql/codeql-config.yml - name: Install deps shell: bash From a3401625ddb0e329f53ba205228f20cd7360816e Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 23 Apr 2026 18:01:26 +0100 Subject: [PATCH 122/750] test: disable flaky chained FULL_DUPLEX_STREAMED ext_proc integration tests (#44608) workaround: #44605 --------- Signed-off-by: Ryan Northey --- .../ext_proc_full_duplex_integration_test.cc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc index 217075bee2485..adf4b9dd4a51b 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc @@ -596,7 +596,8 @@ TEST_P(ExtProcIntegrationTest, TwoExtProcFiltersInResponseProcessing) { } // without trailers, server fully buffers the message before sending back the response. -TEST_P(ExtProcIntegrationTest, TwoExtProcFiltersBothDuplexInBothDirection) { +// TODO(#44605): Flaky due to production bug in chained FULL_DUPLEX_STREAMED ext_proc filters. +TEST_P(ExtProcIntegrationTest, DISABLED_TwoExtProcFiltersBothDuplexInBothDirection) { twoExtProcFiltersFullDuplexConfig(); const std::string body_sent(5 * 1024, 's'); @@ -654,7 +655,8 @@ TEST_P(ExtProcIntegrationTest, TwoExtProcFiltersBothDuplexInBothDirection) { } // Without trailers, server buffers one chunks of body before sending back the response. -TEST_P(ExtProcIntegrationTest, TwoExtProcFiltersBothDuplexInBothDirectionNoTrailerRandom) { +// TODO(#44605): Flaky due to production bug in chained FULL_DUPLEX_STREAMED ext_proc filters. +TEST_P(ExtProcIntegrationTest, DISABLED_TwoExtProcFiltersBothDuplexInBothDirectionNoTrailerRandom) { twoExtProcFiltersFullDuplexConfig(); const std::string body_sent(10 * 1024, 's'); @@ -828,8 +830,9 @@ TEST_P(ExtProcIntegrationTest, KeepContentLengthDuplexStreamed) { } // With trailers, request direction, fully buffered +// TODO(#44605): Flaky due to production bug in chained FULL_DUPLEX_STREAMED ext_proc filters. TEST_P(ExtProcIntegrationTest, - TwoExtProcFiltersBothDuplexInRequestDirectionWithTrailerFullyBuffered) { + DISABLED_TwoExtProcFiltersBothDuplexInRequestDirectionWithTrailerFullyBuffered) { two_ext_proc_filters_ = true; config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap&) { // Filter-1 @@ -900,7 +903,9 @@ TEST_P(ExtProcIntegrationTest, } // With trailers, both directions, server fully buffers. -TEST_P(ExtProcIntegrationTest, TwoExtProcFiltersBothDuplexInBothDirectionWithTrailerFullyBuffered) { +// TODO(#44605): Flaky due to production bug in chained FULL_DUPLEX_STREAMED ext_proc filters. +TEST_P(ExtProcIntegrationTest, + DISABLED_TwoExtProcFiltersBothDuplexInBothDirectionWithTrailerFullyBuffered) { twoExtProcFiltersFullDuplexConfig(); const std::string body_sent(10 * 1024, 's'); From 3249660f926b58ab5ae89922eee5b4281bc6b0c3 Mon Sep 17 00:00:00 2001 From: Xin Date: Thu, 23 Apr 2026 14:40:14 -0400 Subject: [PATCH 123/750] ext_proc: reordering fields to have better alignment ==> reduce RAM usage (#44603) Commit Message: better alignment of fields in ProcessState for RAM reduction. Additional Description: Risk Level: LOW, no new code path. Testing: existing tests. Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Xin Zhuang --- .../filters/http/ext_proc/processor_state.h | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/source/extensions/filters/http/ext_proc/processor_state.h b/source/extensions/filters/http/ext_proc/processor_state.h index d388bbbf16486..ff2f24446bdb8 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.h +++ b/source/extensions/filters/http/ext_proc/processor_state.h @@ -92,15 +92,15 @@ class ProcessorState : public Logger::Loggable { const std::vector& untyped_cluster_metadata_forwarding_namespaces, const std::vector& typed_cluster_metadata_forwarding_namespaces, bool allow_content_length_header) - : filter_(filter), traffic_direction_(traffic_direction), + : filter_(filter), allow_content_length_header_(allow_content_length_header), + traffic_direction_(traffic_direction), untyped_forwarding_namespaces_(&untyped_forwarding_namespaces), typed_forwarding_namespaces_(&typed_forwarding_namespaces), untyped_receiving_namespaces_(&untyped_receiving_namespaces), untyped_cluster_metadata_forwarding_namespaces_( &untyped_cluster_metadata_forwarding_namespaces), typed_cluster_metadata_forwarding_namespaces_( - &typed_cluster_metadata_forwarding_namespaces), - allow_content_length_header_(allow_content_length_header) {} + &typed_cluster_metadata_forwarding_namespaces) {} ProcessorState(const ProcessorState&) = delete; virtual ~ProcessorState() = default; ProcessorState& operator=(const ProcessorState&) = delete; @@ -357,9 +357,15 @@ class ProcessorState : public Logger::Loggable { bool partial_body_processed_ : 1 = false; // If true, the server wants to see the headers - bool send_headers_ : 1; + bool send_headers_ : 1 = false; // If true, the server wants to see the trailers - bool send_trailers_ : 1; + bool send_trailers_ : 1 = false; + // Flag to track whether Envoy already received the new timeout message. + // Envoy should receive at most one such message in one particular state. + bool new_timeout_received_ : 1 = false; + // If true, the attributes for this processing state have already been sent. + bool attributes_sent_ : 1 = false; + const bool allow_content_length_header_ : 1; // The specific mode for body handling envoy::extensions::filters::http::ext_proc::v3::ProcessingMode_BodySendMode body_mode_; @@ -371,9 +377,6 @@ class ProcessorState : public Logger::Loggable { Http::RequestOrResponseHeaderMap* headers_ = nullptr; Http::HeaderMap* trailers_ = nullptr; Event::TimerPtr message_timer_; - // Flag to track whether Envoy already received the new timeout message. - // Envoy should receive at most one such message in one particular state. - bool new_timeout_received_{false}; ChunkQueue chunk_queue_; absl::optional call_start_time_ = absl::nullopt; const envoy::config::core::v3::TrafficDirection traffic_direction_; @@ -383,9 +386,6 @@ class ProcessorState : public Logger::Loggable { const std::vector* untyped_receiving_namespaces_{}; const std::vector* untyped_cluster_metadata_forwarding_namespaces_{}; const std::vector* typed_cluster_metadata_forwarding_namespaces_{}; - // If true, the attributes for this processing state have already been sent. - bool attributes_sent_{}; - const bool allow_content_length_header_; private: virtual void clearRouteCache(const envoy::service::ext_proc::v3::CommonResponse&) {} From ac8ea19f77a183dac1ee31dcc4b6f2f345765377 Mon Sep 17 00:00:00 2001 From: bsoumith <63022931+bsoumith@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:43:25 -0700 Subject: [PATCH 124/750] Scone release note (#44610) Commit Message: docs: add release notes for SCONE support Additional Description: This PR adds the missing release notes for the SCONE (Standardized Communication with Network Elements) support feature. The implementation was recently merged in #44543. This follow-up documents: 1. The new enable_scone field in QuicProtocolOptions. 2. The enableScone() API addition to the Envoy Mobile EngineBuilder. 3. The new scone_max_kbps and scone_timestamp_ms fields in the envoy_stream_intel struct used by the mobile library. Addressing feedback from @adi-envoy. Risk Level: Low (documentation-only change) Testing: N/A Docs Changes: Yes Release Notes: Yes (this PR adds them) Platform Specific Features: Envoy Mobile Fixes commit: #44543 (7a86d60b03917a9b0eba92a6941b3ba0499aea61) --------- Signed-off-by: bsoumith --- changelogs/current.yaml | 7 +++++++ mobile/docs/root/intro/version_history.rst | 1 + 2 files changed, 8 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 971b4a290a9cf..195616c8e98ea 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1113,5 +1113,12 @@ new_features: change: | Added ``connection.peer_certificate`` and ``upstream.peer_certificate`` attributes that provide the PEM-encoded peer certificate for downstream and upstream TLS connections respectively. +- area: quic + change: | + Added SCONE (Standardized Communication with Network Elements) support for QUIC. This includes + a new :ref:`enable_scone ` + field in ``QuicProtocolOptions``, and support in Envoy Mobile via a new ``enableScone()`` API in + ``EngineBuilder``. SCONE bandwidth and timestamp data are now propagated to the application via + new ``scone_max_kbps`` and ``scone_timestamp_ms`` fields in the ``envoy_stream_intel`` struct. deprecated: diff --git a/mobile/docs/root/intro/version_history.rst b/mobile/docs/root/intro/version_history.rst index c9f3660937fe1..1ac9abad25a95 100644 --- a/mobile/docs/root/intro/version_history.rst +++ b/mobile/docs/root/intro/version_history.rst @@ -54,6 +54,7 @@ Features: - build: Add a build feature ``exclude_certificates`` to disable inclusion of the Envoy Mobile certificate list, for use when using platform certificate validation. - build: Add a build feature ``envoy_http_datagrams`` to allow disabling HTTP Datagram support. (:issue:`#23564 <23564>`) - android: log cleared JNI exceptions to platform layer as `jni_cleared_pending_exception` events (:issue:`#26133 <26133>`). +- api: Add support for SCONE (Standardized Communication with Network Elements) via ``enableScone()`` in ``EngineBuilder``. SCONE data is propagated via new fields in ``envoy_stream_intel``. (#44543) 0.5.0 (September 2, 2022) =========================== From 81a09e26235d371bdf32954a73672957aa362585 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 23 Apr 2026 20:34:36 +0100 Subject: [PATCH 125/750] changelogs: Add summary for release v1.38.0 (#44613) Signed-off-by: Ryan Northey Co-authored-by: Jonh Wendell --- changelogs/summary.md | 102 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/changelogs/summary.md b/changelogs/summary.md index e69de29bb2d1d..105d52e03b25b 100644 --- a/changelogs/summary.md +++ b/changelogs/summary.md @@ -0,0 +1,102 @@ +## Summary of changes + +## Breaking changes + +- **tcp_proxy**: `max_early_data_bytes` must be set explicitly for `upstream_connect_mode` values other than `IMMEDIATE`; missing configurations now fail validation at startup. +- **on_demand**: the on-demand filter no longer performs internal redirects after a successful CDS fetch, so earlier filters are not invoked twice (revertible via `envoy.reloadable_features.on_demand_cluster_no_recreate_stream`). +- **BoringSSL/FIPS**: the `--define=boringssl=fips` flag has been removed; use `--config=boringssl-fips`. +- **TLS**: `enforce_rsa_key_usage` now defaults to `true`; the option will be removed in the next release. +- **ext_proc**: the `processing_effect_lib` has moved from `extensions/filters/http/ext_proc` to `extensions/filters/common/processing_effect`. + +### Dynamic modules +- New extension points: tracers, TLS certificate validators, custom clusters, load balancing policies, input matchers, upstream HTTP-to-TCP bridge, and listener filters with HTTP callouts. +- Bootstrap extensions gained init-manager integration, drain/shutdown lifecycle hooks, listener-lifecycle callbacks, timer and admin-handler APIs, and metrics support. +- Network filter callbacks for flow-control and connection state (`read_disable`, watermarks, half-close, buffer limits, etc.) and persistent read/write buffers across callbacks. +- Listener-filter socket and TLS introspection (SNI, ALPN, JA3/JA4, SSL SANs/subject) plus `write_to_socket`/`close_socket` callbacks enabling Postgres SSL, MySQL, and similar protocol negotiation. +- Module loading from local file paths and remote HTTP sources (SHA256-verified, cached, with optional NACK-on-cache-miss). +- Process-wide function and shared-data registries for zero-copy cross-module interactions. +- Rust SDK: unified `declare_all_init_functions!` macro for registering any combination of HTTP/network/listener/UDP/bootstrap/access-logger filters, opt-in `CatchUnwind` panic wrapper, multi-logger support. +- Custom metrics on load balancers with configurable `metrics_namespace`, `get_host_health_by_address` fast path, host-membership update callbacks. +- ABI forward-compatibility: modules built against the v1.38 SDK can be loaded by a v1.39 Envoy binary. +- New `envoy_dynamic_module_callback_is_validation_mode` callback and typed filter-state support. + +### MCP (Model Context Protocol) and A2A +- MCP router: full method coverage — `resources/list|read|subscribe|unsubscribe`, `resources/templates/list`, `prompts/list|get`, `completion/complete`, `logging/setLevel`, plus `notifications/cancelled` and `notifications/roots/list_changed`. +- SSE streaming support: pass-through for `tools/call` and fan-out aggregation for `tools/list`, `initialize`, `resources/list`, and `prompts/list`. +- MCP filter: HTTP DELETE session termination, relaxed `application/json` Content-Type matching, optional `traceparent`/`tracestate`/baggage propagation from MCP parameters, statistics added to the MCP router, and default metadata namespace changed to `envoy.filters.http.mcp`. +- New **MCP JSON REST Bridge** HTTP filter (work-in-progress) transcoding JSON-RPC to REST, with `tools/call` request transcoding and session negotiation. +- Added parsing support for the **A2A (Agent2Agent)** JSON-RPC protocol. + +### HTTP, routing and protocol +- HTTP/2: new `max_header_field_size_kb` to raise the nghttp2 64 KiB per-header limit; applied the nghttp2 **CVE-2026-27135** patch. +- HTTP/1: optional strict chunked-encoding parsing behind a runtime guard. +- Optional **JSON format for the `x-forwarded-client-cert` (XFCC)** header. +- New `envoy.filters.http.sse_to_metadata` filter (extract SSE event values into dynamic metadata, useful for LLM token-usage metrics), with a pluggable `envoy.content_parsers.json` parser. +- New `envoy.filters.http.file_server` filter for serving files directly from disk. +- Refactored `route()`, `clusterInfo()`, and `virtualHost()` to return `OptRef`, with new `*SharedPtr()` companions. +- Happy Eyeballs now handles interleaving of non-IP addresses. + +### TLS, security and authorization +- TLS certificate compression (RFC 8879) extended: brotli added to QUIC, and both brotli and zlib added to TCP TLS. +- `enforce_rsa_key_usage` defaults to `true` on upstream TLS contexts; the option will be removed next release. +- On-demand upstream certificate fetching via SDS using the `envoy.tls.certificate_selectors.on_demand_secret` extension. +- Exposed verified issuer SHA-256 fingerprint and serial number via `%DOWNSTREAM_PEER_ISSUER_FINGERPRINT_256%` / `%DOWNSTREAM_PEER_ISSUER_SERIAL%` and corresponding Lua accessors. +- Per-connection SPIFFE trust-domain selection for multi-tenant deployments; reduced file-watch overhead and support for `watched_directory`. +- **ext_authz** — `shadow_mode` (decision written to filter state without terminating requests), `path_override`, honoring `status_on_error` on 5xx/HTTP-call failures, fix for propagating headers from denied responses. +- **OAuth2** — per-route configuration, `TLS_CLIENT_AUTH` (RFC 8705 mTLS client auth), `OauthExpires` cookie cleared on logout, `oauth2_encrypt_tokens` runtime guard removed (encryption now default, opt-out via `disable_token_encryption`). +- **RBAC** header matcher now validates each header value individually (guarded) to prevent concatenation-based bypasses. +- Query-parameter values added via `query_parameter_mutations` are now URL-encoded to prevent injection. +- **OpenSSL** can now be used as an alternative to the default BoringSSL (build with `--config=openssl` Bazel flag); HTTP/3 (QUIC) is disabled and OpenSSL builds are not covered by the Envoy security policy. + +### Observability +- New formatters: `SPAN_ID`, `QUERY_PARAMS`, `UPSTREAM_LOCAL_CLOSE_REASON`, `DOWNSTREAM_LOCAL_CLOSE_REASON`, `UPSTREAM_DETECTED_CLOSE_TYPE`, `DOWNSTREAM_DETECTED_CLOSE_TYPE`, `%UPSTREAM_HOSTS_ATTEMPTED%` and related attempt/connection-ID formatters, `%FILE_CONTENT(...)%`, `%SECRET(name)%`. +- `*_WITHOUT_PORT` address formatters accept an optional `MASK_PREFIX_LEN` to emit CIDR-masked addresses. +- Prometheus admin endpoint supports the **protobuf exposition format** and **Prometheus native histograms**. +- Cluster-level and listener-level stats matchers, plus stats-scope metric-count limits. +- OpenTelemetry stat sink can now export metrics over **HTTP** (OTLP/HTTP) without a collector sidecar. +- Access loggers: stats customization and gauge support in the stats access logger; network filters can register as access loggers; new `asn_org` geoip field; log events on OpenTelemetry spans. + +### Routing, load balancing and upstream +- Coalesced load-balancer rebuilds during EDS batch host updates — significant CPU-spike reduction on large clusters. +- Passive degraded-host detection (`detect_degraded_hosts`) via the `x-envoy-degraded` response header. +- Redis Cluster zone-aware routing (`LOCAL_ZONE_AFFINITY` / `LOCAL_ZONE_AFFINITY_REPLICAS_AND_PRIMARY`, Valkey only). +- New `upstream_rq_active_overflow` counter distinguishing active-RQ saturation from pending-queue saturation. +- ODCDS over ADS fix for tcp_proxy; SRDS late-listener init fix; drop_overload now uses cached EDS. +- EDS metadata comparison uses a cached hash for O(1) per-host comparison. +- ORCA weight manager prefers named metrics over application utilization by default. + +### Rate limiting +- `is_negative_hits` on `hits_addend` to refund tokens to the budget. +- New `RemoteAddressMatch` rate-limit action (CIDR-based, with inversion and formatter substitution). +- Per-descriptor `x-ratelimit-*` response headers and shadow mode in the local rate limit filter. +- `timeout: 0s` in HTTP ext_authz and HTTP rate-limit filters now means "no timeout", aligning with other Envoy timeouts. + +### Memory, resource and connection management +- Replaced the custom timer-based tcmalloc release with tcmalloc's native `ProcessBackgroundActions` / `SetBackgroundReleaseRate`. +- New `MemoryAllocatorManager` fields (`soft_memory_limit_bytes`, `max_per_cpu_cache_size_bytes`, `max_unfreed_memory_bytes`). +- Typed `ShrinkHeapConfig` for the `shrink_heap` overload action. +- **cgroup v2** support in the CPU utilization resource monitor, with automatic v1/v2 detection. +- New `per_connection_buffer_high_watermark_timeout` on listeners and clusters to close connections stuck above the watermark. +- Fixed a resource leak in global connection-limit tracking under load shedding. + +### xDS and configuration +- `set_node_on_first_message_only` now supported in Delta-xDS. +- Delta-xDS failover fix for `initial_resource_versions` on reconnect. +- `--mode validate` now creates bootstrap extensions, actually validating their configs. +- CEL expressions that attempt to read response-path data on the request path are automatically re-evaluated when the data becomes available. +- New `HttpResponseLocalReplyMatchInput` matcher input to distinguish local replies from upstream responses. +- New `HickoryDnsResolverConfig` — DNS resolver built on Hickory DNS. + +### TCP proxy and PROXY protocol +- New `proxy_protocol_tlv_merge_policy` (`ADD_IF_ABSENT`, `OVERWRITE_BY_TYPE_IF_EXISTS_OR_ADD`, `APPEND_IF_EXISTS_OR_ADD`). +- Option to emit an access-log entry when a connection is accepted. +- `max_early_data_bytes` is now **required** when using non-`IMMEDIATE` `upstream_connect_mode`. + +### Other notable changes and fixes +- Router returns `DEADLINE_EXCEEDED` (instead of `UNAVAILABLE`) on router-enforced gRPC timeouts (opt-in). +- Hot restart fixed for listeners with a network-namespace address. +- HTTP/3 client pool fix for early-data requests with async certificate validation. +- Fixes for HTTP/1 zombie-stream FD leaks, internal-redirect hang on buffer overflow, keep-alive header preservation, reset-stream filter-chain safety, idle-timer-before-connected behaviour, and a worker-thread watchdog configuration bug. +- Several ext_proc fixes: two ext_procs in the same chain, CEL message text-format serialization, empty-data-chunk handling. +- Geoip HTTP filter promoted to **stable**. +- Published contrib binaries now carry a `-contrib` version suffix. From e026e46fcfefc0599307c8329d1c1cdff9658341 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 23 Apr 2026 22:20:01 +0100 Subject: [PATCH 126/750] test: Fix flaky histogram assertion in OpenTelemetry custom conversion test (#44616) Signed-off-by: Ryan Northey --- .../open_telemetry_integration_test.cc | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc b/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc index 0d79b7024ff6b..0c3d57ac174f3 100644 --- a/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc +++ b/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc @@ -134,6 +134,20 @@ class OpenTelemetryIntegrationTest driver_.sendResponse(otlp_collector_request_); } + // Drain any metrics-export streams that piled up on the connection while we were + // looping above. Each iteration of the loop only handles one stream, but the + // stats flush timer keeps firing and queuing more. Leaving them unresponded to + // keeps cluster.otlp_collector.upstream_rq_active > 0, which then trips the + // subsequent expectUpstreamRequestFinished() wait in the EnvoyGrpc variant. + while (true) { + FakeStreamPtr pending; + if (!fake_metrics_service_connection_->waitForNewStream(*dispatcher_, pending, + std::chrono::milliseconds(10))) { + break; + } + driver_.sendResponse(pending); + } + EXPECT_TRUE(known_counter_exists); EXPECT_TRUE(known_gauge_exists); EXPECT_TRUE(known_histogram_exists); @@ -434,8 +448,20 @@ class OpenTelemetryIntegrationTestCustomConversion : public OpenTelemetryIntegra } if (metric.name() == getFullStatName("custom.upstream_rq_time") && metric.has_histogram()) { + // Require exactly 1 data point for cluster_0. + // otlp_collector's own upstream_rq_time + // sample (from stats-export RPCs) may or may + // include another data point, which we are not + // interested in. + const int data_points_with_cluster_0 = + std::ranges::count_if(metric.histogram().data_points(), [](const auto& dp) { + return std::ranges::any_of(dp.attributes(), [](const auto& attr) { + return attr.key() == "envoy.cluster_name" && + attr.value().string_value() == "cluster_0"; + }); + }); + EXPECT_EQ(1, data_points_with_cluster_0); known_histogram_exists = true; - EXPECT_EQ(1, metric.histogram().data_points().size()); } } } From f1dd21b16c244bda00edfb5ffce577e12d0d2ec2 Mon Sep 17 00:00:00 2001 From: "publish-envoy[bot]" <140627008+publish-envoy[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 21:30:59 +0000 Subject: [PATCH 127/750] repo: Release v1.38.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of changes ## Breaking changes - **tcp_proxy**: `max_early_data_bytes` must be set explicitly for `upstream_connect_mode` values other than `IMMEDIATE`; missing configurations now fail validation at startup. - **on_demand**: the on-demand filter no longer performs internal redirects after a successful CDS fetch, so earlier filters are not invoked twice (revertible via `envoy.reloadable_features.on_demand_cluster_no_recreate_stream`). - **BoringSSL/FIPS**: the `--define=boringssl=fips` flag has been removed; use `--config=boringssl-fips`. - **TLS**: `enforce_rsa_key_usage` now defaults to `true`; the option will be removed in the next release. - **ext_proc**: the `processing_effect_lib` has moved from `extensions/filters/http/ext_proc` to `extensions/filters/common/processing_effect`. ### Dynamic modules - New extension points: tracers, TLS certificate validators, custom clusters, load balancing policies, input matchers, upstream HTTP-to-TCP bridge, and listener filters with HTTP callouts. - Bootstrap extensions gained init-manager integration, drain/shutdown lifecycle hooks, listener-lifecycle callbacks, timer and admin-handler APIs, and metrics support. - Network filter callbacks for flow-control and connection state (`read_disable`, watermarks, half-close, buffer limits, etc.) and persistent read/write buffers across callbacks. - Listener-filter socket and TLS introspection (SNI, ALPN, JA3/JA4, SSL SANs/subject) plus `write_to_socket`/`close_socket` callbacks enabling Postgres SSL, MySQL, and similar protocol negotiation. - Module loading from local file paths and remote HTTP sources (SHA256-verified, cached, with optional NACK-on-cache-miss). - Process-wide function and shared-data registries for zero-copy cross-module interactions. - Rust SDK: unified `declare_all_init_functions!` macro for registering any combination of HTTP/network/listener/UDP/bootstrap/access-logger filters, opt-in `CatchUnwind` panic wrapper, multi-logger support. - Custom metrics on load balancers with configurable `metrics_namespace`, `get_host_health_by_address` fast path, host-membership update callbacks. - ABI forward-compatibility: modules built against the v1.38 SDK can be loaded by a v1.39 Envoy binary. - New `envoy_dynamic_module_callback_is_validation_mode` callback and typed filter-state support. ### MCP (Model Context Protocol) and A2A - MCP router: full method coverage — `resources/list|read|subscribe|unsubscribe`, `resources/templates/list`, `prompts/list|get`, `completion/complete`, `logging/setLevel`, plus `notifications/cancelled` and `notifications/roots/list_changed`. - SSE streaming support: pass-through for `tools/call` and fan-out aggregation for `tools/list`, `initialize`, `resources/list`, and `prompts/list`. - MCP filter: HTTP DELETE session termination, relaxed `application/json` Content-Type matching, optional `traceparent`/`tracestate`/baggage propagation from MCP parameters, statistics added to the MCP router, and default metadata namespace changed to `envoy.filters.http.mcp`. - New **MCP JSON REST Bridge** HTTP filter (work-in-progress) transcoding JSON-RPC to REST, with `tools/call` request transcoding and session negotiation. - Added parsing support for the **A2A (Agent2Agent)** JSON-RPC protocol. ### HTTP, routing and protocol - HTTP/2: new `max_header_field_size_kb` to raise the nghttp2 64 KiB per-header limit; applied the nghttp2 **CVE-2026-27135** patch. - HTTP/1: optional strict chunked-encoding parsing behind a runtime guard. - Optional **JSON format for the `x-forwarded-client-cert` (XFCC)** header. - New `envoy.filters.http.sse_to_metadata` filter (extract SSE event values into dynamic metadata, useful for LLM token-usage metrics), with a pluggable `envoy.content_parsers.json` parser. - New `envoy.filters.http.file_server` filter for serving files directly from disk. - Refactored `route()`, `clusterInfo()`, and `virtualHost()` to return `OptRef`, with new `*SharedPtr()` companions. - Happy Eyeballs now handles interleaving of non-IP addresses. ### TLS, security and authorization - TLS certificate compression (RFC 8879) extended: brotli added to QUIC, and both brotli and zlib added to TCP TLS. - `enforce_rsa_key_usage` defaults to `true` on upstream TLS contexts; the option will be removed next release. - On-demand upstream certificate fetching via SDS using the `envoy.tls.certificate_selectors.on_demand_secret` extension. - Exposed verified issuer SHA-256 fingerprint and serial number via `%DOWNSTREAM_PEER_ISSUER_FINGERPRINT_256%` / `%DOWNSTREAM_PEER_ISSUER_SERIAL%` and corresponding Lua accessors. - Per-connection SPIFFE trust-domain selection for multi-tenant deployments; reduced file-watch overhead and support for `watched_directory`. - **ext_authz** — `shadow_mode` (decision written to filter state without terminating requests), `path_override`, honoring `status_on_error` on 5xx/HTTP-call failures, fix for propagating headers from denied responses. - **OAuth2** — per-route configuration, `TLS_CLIENT_AUTH` (RFC 8705 mTLS client auth), `OauthExpires` cookie cleared on logout, `oauth2_encrypt_tokens` runtime guard removed (encryption now default, opt-out via `disable_token_encryption`). - **RBAC** header matcher now validates each header value individually (guarded) to prevent concatenation-based bypasses. - Query-parameter values added via `query_parameter_mutations` are now URL-encoded to prevent injection. - **OpenSSL** can now be used as an alternative to the default BoringSSL (build with `--config=openssl` Bazel flag); HTTP/3 (QUIC) is disabled and OpenSSL builds are not covered by the Envoy security policy. ### Observability - New formatters: `SPAN_ID`, `QUERY_PARAMS`, `UPSTREAM_LOCAL_CLOSE_REASON`, `DOWNSTREAM_LOCAL_CLOSE_REASON`, `UPSTREAM_DETECTED_CLOSE_TYPE`, `DOWNSTREAM_DETECTED_CLOSE_TYPE`, `%UPSTREAM_HOSTS_ATTEMPTED%` and related attempt/connection-ID formatters, `%FILE_CONTENT(...)%`, `%SECRET(name)%`. - `*_WITHOUT_PORT` address formatters accept an optional `MASK_PREFIX_LEN` to emit CIDR-masked addresses. - Prometheus admin endpoint supports the **protobuf exposition format** and **Prometheus native histograms**. - Cluster-level and listener-level stats matchers, plus stats-scope metric-count limits. - OpenTelemetry stat sink can now export metrics over **HTTP** (OTLP/HTTP) without a collector sidecar. - Access loggers: stats customization and gauge support in the stats access logger; network filters can register as access loggers; new `asn_org` geoip field; log events on OpenTelemetry spans. ### Routing, load balancing and upstream - Coalesced load-balancer rebuilds during EDS batch host updates — significant CPU-spike reduction on large clusters. - Passive degraded-host detection (`detect_degraded_hosts`) via the `x-envoy-degraded` response header. - Redis Cluster zone-aware routing (`LOCAL_ZONE_AFFINITY` / `LOCAL_ZONE_AFFINITY_REPLICAS_AND_PRIMARY`, Valkey only). - New `upstream_rq_active_overflow` counter distinguishing active-RQ saturation from pending-queue saturation. - ODCDS over ADS fix for tcp_proxy; SRDS late-listener init fix; drop_overload now uses cached EDS. - EDS metadata comparison uses a cached hash for O(1) per-host comparison. - ORCA weight manager prefers named metrics over application utilization by default. ### Rate limiting - `is_negative_hits` on `hits_addend` to refund tokens to the budget. - New `RemoteAddressMatch` rate-limit action (CIDR-based, with inversion and formatter substitution). - Per-descriptor `x-ratelimit-*` response headers and shadow mode in the local rate limit filter. - `timeout: 0s` in HTTP ext_authz and HTTP rate-limit filters now means "no timeout", aligning with other Envoy timeouts. ### Memory, resource and connection management - Replaced the custom timer-based tcmalloc release with tcmalloc's native `ProcessBackgroundActions` / `SetBackgroundReleaseRate`. - New `MemoryAllocatorManager` fields (`soft_memory_limit_bytes`, `max_per_cpu_cache_size_bytes`, `max_unfreed_memory_bytes`). - Typed `ShrinkHeapConfig` for the `shrink_heap` overload action. - **cgroup v2** support in the CPU utilization resource monitor, with automatic v1/v2 detection. - New `per_connection_buffer_high_watermark_timeout` on listeners and clusters to close connections stuck above the watermark. - Fixed a resource leak in global connection-limit tracking under load shedding. ### xDS and configuration - `set_node_on_first_message_only` now supported in Delta-xDS. - Delta-xDS failover fix for `initial_resource_versions` on reconnect. - `--mode validate` now creates bootstrap extensions, actually validating their configs. - CEL expressions that attempt to read response-path data on the request path are automatically re-evaluated when the data becomes available. - New `HttpResponseLocalReplyMatchInput` matcher input to distinguish local replies from upstream responses. - New `HickoryDnsResolverConfig` — DNS resolver built on Hickory DNS. ### TCP proxy and PROXY protocol - New `proxy_protocol_tlv_merge_policy` (`ADD_IF_ABSENT`, `OVERWRITE_BY_TYPE_IF_EXISTS_OR_ADD`, `APPEND_IF_EXISTS_OR_ADD`). - Option to emit an access-log entry when a connection is accepted. - `max_early_data_bytes` is now **required** when using non-`IMMEDIATE` `upstream_connect_mode`. ### Other notable changes and fixes - Router returns `DEADLINE_EXCEEDED` (instead of `UNAVAILABLE`) on router-enforced gRPC timeouts (opt-in). - Hot restart fixed for listeners with a network-namespace address. - HTTP/3 client pool fix for early-data requests with async certificate validation. - Fixes for HTTP/1 zombie-stream FD leaks, internal-redirect hang on buffer overflow, keep-alive header preservation, reset-stream filter-chain safety, idle-timer-before-connected behaviour, and a worker-thread watchdog configuration bug. - Several ext_proc fixes: two ext_procs in the same chain, CEL message text-format serialization, empty-data-chunk handling. - Geoip HTTP filter promoted to **stable**. - Published contrib binaries now carry a `-contrib` version suffix. **Docker images**: https://hub.docker.com/r/envoyproxy/envoy/tags?page=1&name=v1.38.0 **Docs**: https://www.envoyproxy.io/docs/envoy/v1.38.0/ **Release notes**: https://www.envoyproxy.io/docs/envoy/v1.38.0/version_history/v1.38/v1.38.0 **Full changelog**: https://github.com/envoyproxy/envoy/compare/v1.37.0...v1.38.0 --- VERSION.txt | 2 +- changelogs/current.yaml | 9 +-------- test/extensions/filters/http/ext_proc/BUILD | 1 + 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index f5dd2631bd141..ebeef2f2d6192 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.38.0-dev +1.38.0 diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 195616c8e98ea..dd29a3d07da3d 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,7 +1,6 @@ -date: Pending +date: April 23, 2026 behavior_changes: -# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* - area: tcp_proxy change: | The TCP proxy filter now requires :ref:`max_early_data_bytes @@ -36,7 +35,6 @@ behavior_changes: codebase have been updated. minor_behavior_changes: -# *Changes that may cause incompatibilities for some users, but should not for most* - area: conn_pool change: | Added new ``upstream_rq_active_overflow`` counter incremented when a request is rejected @@ -172,7 +170,6 @@ minor_behavior_changes: enabled by setting the runtime guard ``envoy.reloadable_features.strict_chunk_parsing`` to ``true``. bug_fixes: -# *Changes expected to improve the state of the world and are unlikely to have negative effects* - area: grpc change: | Fixed the router to return ``DEADLINE_EXCEEDED`` (gRPC status ``4``) instead of ``UNAVAILABLE`` (gRPC @@ -416,7 +413,6 @@ bug_fixes: ``StrictDnsClusterImpl`` involving the ``Locality`` shared pool during server shutdown. removed_config_or_runtime: -# *Normally occurs at the end of the* :ref:`deprecation period ` - area: oauth2 change: | Removed the runtime guard ``envoy.reloadable_features.oauth2_encrypt_tokens``. Token cookies (access, @@ -1085,7 +1081,6 @@ new_features: Added a new overload action :ref:`envoy.overload_actions.close_idle_http_connections ` that closes idle downstream HTTP connections when the overload action is active. Currently only HTTP/3 idle connections trimming is supported, and HTTP/1 and HTTP/2 support are to be implemented. - - area: contrib change: | Added a new :ref:`Kafka stats sink ` contrib extension @@ -1120,5 +1115,3 @@ new_features: field in ``QuicProtocolOptions``, and support in Envoy Mobile via a new ``enableScone()`` API in ``EngineBuilder``. SCONE bandwidth and timestamp data are now propagated to the application via new ``scone_max_kbps`` and ``scone_timestamp_ms`` fields in the ``envoy_stream_intel`` struct. - -deprecated: diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 03adae9be4cae..e3d2050b5a185 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -224,6 +224,7 @@ envoy_extension_cc_test( ], }), extension_names = ["envoy.filters.http.ext_proc"], + rbe_pool = "6gig", tags = ["skip_on_windows"], deps = [ ":filter_test_common_lib", From ff5b78da47cd0dfa581a7dbed61baa3b67fe7668 Mon Sep 17 00:00:00 2001 From: "publish-envoy[bot]" <140627008+publish-envoy[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 23:18:46 +0000 Subject: [PATCH 128/750] repo: Dev v1.39.0 --- VERSION.txt | 2 +- changelogs/1.38.0.yaml | 1117 +++++++++++++++++++++++++++++++++++++++ changelogs/current.yaml | 1114 +------------------------------------- changelogs/summary.md | 102 ---- 4 files changed, 1125 insertions(+), 1210 deletions(-) create mode 100644 changelogs/1.38.0.yaml diff --git a/VERSION.txt b/VERSION.txt index ebeef2f2d6192..23c39f6ec55c5 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.38.0 +1.39.0-dev diff --git a/changelogs/1.38.0.yaml b/changelogs/1.38.0.yaml new file mode 100644 index 0000000000000..dd29a3d07da3d --- /dev/null +++ b/changelogs/1.38.0.yaml @@ -0,0 +1,1117 @@ +date: April 23, 2026 + +behavior_changes: +- area: tcp_proxy + change: | + The TCP proxy filter now requires :ref:`max_early_data_bytes + ` to be + explicitly set when using :ref:`upstream_connect_mode + ` modes + other than ``IMMEDIATE`` (i.e., ``ON_DOWNSTREAM_DATA`` or ``ON_DOWNSTREAM_TLS_HANDSHAKE``). The field + can be set to ``0`` to disable early data buffering while still using delayed connection modes. + Configurations using these modes without ``max_early_data_bytes`` will now fail validation at startup. +- area: on_demand + change: | + The on-demand filter, when doing on-demand CDS, will no longer do internal redirects after CDS is + successful. Filters appearing in the filter chain before the on-demand filter will no longer be + invoked twice. This behavior can be temporarily reverted by setting the runtime guard + ``envoy.reloadable_features.on_demand_cluster_no_recreate_stream`` to ``false``. +- area: boringssl/fips + change: | + The previous flag for specifying FIPS builds (i.e., ``--define=boringssl=fips``) will no longer work and + has been replaced by ``--config=boringssl-fips``. This change will allow us to better support custom + SSL libraries, and will allow FIPS-compliant Envoy to be built with the imminent switch to Bazel bzlmod. +- area: tls + change: | + Set + :ref:`enforce_rsa_key_usage ` + to ``true`` by default. The handshake will fail if the keyUsage extension is present and incompatible with the TLS usage. In + the next version of Envoy, this option will be removed and the enforcing behavior will always be used. +- area: ext_proc + change: | + The ``processing_effect_lib`` has been moved from the :ref:`extensions/filters/http/ext_proc + ` namespace to the + ``extensions/filters/common/processing_effect`` namespace. All path references within the Envoy + codebase have been updated. + +minor_behavior_changes: +- area: conn_pool + change: | + Added new ``upstream_rq_active_overflow`` counter incremented when a request is rejected + due to the ``max_requests`` circuit breaker being exhausted in ``attachStreamToClient()``. + Previously this condition incorrectly incremented ``upstream_rq_pending_overflow``, making + it impossible to distinguish pending queue saturation from active request saturation via + metrics alone. The new counter is now the authoritative signal for this path by default; + set runtime flag ``envoy.reloadable_features.skip_pending_overflow_count_on_active_rq`` to + ``false`` to also increment ``upstream_rq_pending_overflow`` on this path, preserving + backwards compatibility with existing dashboards and alerts. +- area: geoip + change: | + :ref:`Envoy Http Geoip filter extension + ` has been promoted to stable status. +- area: load_balancing + change: | + Changed the default behavior of ``OrcaWeightManager`` to prefer named metrics over application + utilization when computing utilization. This behavior can be reverted by setting the runtime guard + ``envoy.reloadable_features.orca_weight_manager_use_named_metrics_first`` to ``false``. +- area: compressor + change: | + Strong ``ETag`` removal when compressing now uses the same weak ``W/`` check as + :ref:`weaken_etag_on_compress + `. + Previously removal applied only when ``ETag`` length was greater than 2; two-character strong + values are now removed as well (the only two-character weak form is ``W/``). +- area: memory + change: | + Replaced the custom timer-based tcmalloc memory release with tcmalloc's native + ``ProcessBackgroundActions`` and ``SetBackgroundReleaseRate`` APIs. This provides more comprehensive + background memory management including per-CPU cache reclamation, cache shuffling, and size class + resizing, in addition to memory release. The ``tcmalloc.released_by_timer`` stat has been removed. +- area: mcp + change: | + Changed the default metadata namespace for the MCP filter from ``mcp_proxy`` to ``envoy.filters.http.mcp``. + This change can be reverted by setting the runtime guard + ``envoy.reloadable_features.mcp_filter_use_new_metadata_namespace`` to ``false``. +- area: ext_authz + change: | + Changed the behavior of ``timeout: 0s`` in the HTTP ext_authz filter to mean "no timeout" (infinite) + instead of immediate timeout. Previously, ``timeout: 0s`` would cause requests to fail immediately. + This aligns with other Envoy timeout configurations where ``0`` means disabled or infinite. +- area: ratelimit + change: | + Changed the behavior of ``timeout: 0s`` in the HTTP rate limit filter to mean "no timeout" (infinite) + instead of immediate timeout. Previously, ``timeout: 0s`` would cause requests to fail immediately. + This aligns with other Envoy timeout configurations where ``0`` means disabled or infinite. +- area: upstream + change: | + EDS host metadata comparison during ``updateDynamicHostList`` now uses a cached metadata hash + instead of ``MessageDifferencer::Equivalent``. The hash is computed once when metadata is set, + making per-host comparison O(1). In rare cases where two semantically equivalent metadata messages + have different serializations, this may cause a spurious metadata update (false positive) but will + never miss an actual change. +- area: upstream + change: | + ``ProdClusterInfoFactory`` now honors ``alt_stat_name`` when creating cluster stats scopes, so + stats emitted through that path use the alternate cluster name consistently with other upstream + cluster implementations. +- area: histograms + change: | + Updated libcircllhist to 0.3.2, which changes how bucket bounds are interpreted. This should not impact + production monitoring if the number of samples in the histograms is high. Affected tests were adjusted + to account for histogram changes. +- area: stat_sinks + change: | + OpenTelemetry :ref:`SinkConfig ` + stopped reporting empty delta counters and histograms. +- area: mcp + change: | + Relaxed the MCP filter POST Content-Type check from an exact match on ``application/json`` to a + prefix match, so that ``application/json; charset=utf-8`` and similar media-type parameters are + accepted. +- area: ext_proc + change: | + Added ``received_immediate_response`` flag in the ``ExtProcLoggingInfo`` filter state. +- area: happy_eyeballs + change: | + Happy Eyeballs now handles interleaving of non-IP addresses. The restriction against ``additional_addresses`` + containing non-IP addresses is removed. This behavior can be reverted by setting the runtime + guard ``envoy.reloadable_features.happy_eyeballs_sort_non_ip_addresses`` to ``false``. +- area: ext_authz + change: | + Added tracking bits for processing effect for request headers and failed open occurrence in the + ``ExtAuthzLoggingInfo``. This new data will be automatically collected and can be accessed via + ``requestProcessingEffect()`` and ``failedOpen()``. +- area: dynamic_modules + change: | + Now all the dynamic module extension factories (HTTP, network, listener, UDP listener, and so on) will + serialize the ``google.protobuf.Struct`` configuration message to JSON string and pass it to the + dynamic module side as the configuration. +- area: oauth2 + change: | + Added :ref:`per-route configuration support ` + to the OAuth2 HTTP filter. +- area: oauth2 + change: | + The OAuth2 filter now removes the ``OauthExpires`` cookie from a user's browser upon logging out. + Previously this cookie persisted beyond the logout despite the session no longer existing. +- area: proto_api_scrubber + change: | + If :ref:`scrub_unknown_fields + ` + is set to ``true`` in the :ref:`ProtoApiScrubberConfig + `, + unknown fields will now be scrubbed. This is disabled by default. +- area: http + change: | + The ``route()``, ``clusterInfo()`` and ``virtualHost()`` methods on the HTTP filter callbacks + and stream info interfaces now are refactored to return ``OptRef`` instead of shared + pointers. And additional methods ``routeSharedPtr()``, ``clusterInfoSharedPtr()`` and + ``virtualHostSharedPtr()`` are added to return shared pointers. + The shorter ``route()``, ``clusterInfo()`` and ``virtualHost()`` methods are preferred for + most use cases where the returned reference is not used beyond the scope of the current function + and the caller does not want to manage shared pointer ownership. The new shared pointer methods + are intended for use cases where the caller needs to keep the ownerships to the route, cluster, + or virtual host. +- area: http + change: | + ``StreamRateLimiter`` constructor was updated to always take a pre-constructed and initialized + ``TokenBucket``, and remove the first argument ``max_buffered_data`` and the ``TimeSource&`` + argument. + The ``write_stats_cb`` callback signature was changed from taking a ``bool`` argument + "something is buffered" to a ``uint64_t`` "how many bytes are buffered". + If already initializing with a ``TokenBucket``, you may need to call ``maybeReset`` on that + bucket before passing it to ``StreamRateLimiter`` to ensure equivalent initialization. + If previously constructing a ``StreamRateLimiter`` using ``max_buffered_data``, use + ``StreamRateLimiter::simpleTokenBucket(limit_kbps, time_source)`` to initialize an equivalent + ``TokenBucket``. +- area: http + change: | + Strict parsing of HTTP/1 chunked encoding is now available. It is disabled by default and can be + enabled by setting the runtime guard ``envoy.reloadable_features.strict_chunk_parsing`` to ``true``. + +bug_fixes: +- area: grpc + change: | + Fixed the router to return ``DEADLINE_EXCEEDED`` (gRPC status ``4``) instead of ``UNAVAILABLE`` (gRPC + status ``14``) when the router itself enforces a gRPC request timeout. Previously, the router sent an + HTTP ``504`` local reply which was mapped to ``UNAVAILABLE`` via the standard HTTP-to-gRPC status mapping, + causing clients to incorrectly retry requests that had already timed out. This behavior is disabled + by default and can be enabled by setting the runtime guard + ``envoy.reloadable_features.grpc_timeout_returns_deadline_exceeded`` to ``true``. +- area: quic + change: | + Fixed HTTP/3 client connection pool to handle early data request correctly with asynchronous certificates + validation. This change is gated by the runtime guard ``envoy.reloadable_features.fix_http3_early_data_timing``. +- area: router + change: | + Fixed a bug where the router's buffer overflow check double-counted the request body size when an + upstream filter (e.g. the buffer filter) had already buffered all data. This caused requests with + body size between half and full buffer limit to incorrectly cancel internal redirects. +- area: router + change: | + Fixed a crash in scoped RDS when on-demand update is triggered for a scope that uses inline + ``route_configuration`` instead of ``route_configuration_name``. The null RDS provider dereference + is now guarded and the on-demand callback returns ``false`` for inline-config scopes. +- area: hot_restart + change: | + Fixed hot restart for listeners with a network namespace in the address. Previously, socket + hand-off didn't work cleanly because the namespace was not included in the ``PassListenSocket`` + request, causing the parent to always fall back to binding a new socket. +- area: dynamic_modules + change: | + Fixed the dynamic modules network filter to always set a local close reason when closing connections. This + resolves an error when a dynamic modules network filter closes a connection that has an HTTP filter present. +- area: dynamic_modules + change: | + Fixed a bug where dynamic module extensions do not handle the ``google.protobuf.Struct`` configuration + properly as the API definition requires. + The dynamic module extension factories now serialize the ``Struct`` to JSON string and pass the string + to the dynamic module side as the configuration. +- area: http_11_proxy + change: | + Fixed bug where providing an empty inner socket config would cause Envoy to crash. +- area: tls + change: | + Fixed on-demand TLS selector to enforce session resumption settings. +- area: http + change: | + Fixed crash if a downstream watermark is hit by network filter writes before the HTTP codec is created. +- area: http + change: | + Allow ``Keep-Alive`` response headers added via ``response_headers_to_add`` to be preserved instead + of being stripped by HCM sanitization. Upstream ``Keep-Alive`` headers are now stripped in the router + before user-configured response headers are applied, so user-added ``Keep-Alive`` headers survive. + Protected by runtime guard ``envoy.reloadable_features.preserve_downstream_keepalive``. +- area: http + change: | + Fixed a bug where the internal redirect logic may hang up a request when the request buffer is + overflowed. +- area: load_report + change: | + Fixed a race condition during load-report shutdown with the ADS stream by introducing proper cleanup + of the gRPC stream. +- area: http + change: | + Fixed a potential file descriptor leak where HTTP/1.1 connections with zombie streams (waiting for codec + completion) would not be properly closed when in draining state. This could occur when a response was + sent before the request was fully received, causing connections to remain open indefinitely. This change + can be reverted by setting the runtime guard + ``envoy.reloadable_features.http1_close_connection_on_zombie_stream_complete`` to ``false``. +- area: overload_manager + change: | + Fixed a resource leak in global connection limit tracking that caused permanent connection rejections + when using load shedding (e.g., ``envoy.load_shed_points.tcp_listener_accept``). When connections were + rejected due to load shedding after passing the global connection limit check, the allocated connection + limit resource was not released, causing the connection counter to become incorrect and leading to + ``failed_updates`` in the resource monitor. This resulted in permanent connection rejections even after + load subsided. The fix ensures that connection limit resources are properly released when connections + are rejected due to load shedding. Also added defensive resource cleanup for edge cases where address + processing fails (e.g., ``localAddress()`` or ``peerAddress()`` errors). +- area: drop_overload + change: | + Fixed a bug where ``drop_overload`` failed to use cached EDS resources. +- area: scoped_rds + change: | + Fixed a bug where SRDS subscriptions would never start when a listener with scoped routes was + added after server initialization completed. The ``SrdsFactory`` interface + was incorrectly using the server-level init manager instead of the listener-level init manager, + causing the SRDS init target to be silently discarded. +- area: xds + change: | + Fixed a bug where in delta-xDS when xDS-Failover is configured (gated by the experimental + ``envoy.restart_features.xds_failover_support`` runtime guard), in some cases the + :ref:`initial_resource_versions ` + field was not updated correctly when attempting to reconnect to the xDS server. +- area: odcds + change: | + Fixed a bug where using OD-CDS in tcp_proxy over ADS would not work in some + cases. This change could be reverted temporarily by setting the runtime guard + ``envoy.reloadable_features.tcp_proxy_odcds_over_ads_fix`` to ``false``. +- area: http + change: | + Fixed upstream client to not close connection when idle timeout fires before the connection is + established. This change can be reverted by setting the runtime guard + ``envoy.reloadable_features.codec_client_enable_idle_timer_only_when_connected`` to ``false``. +- area: oauth2 + change: | + Fixed OAuth2 refresh requests so host rewriting no longer overrides the original ``Host`` header value. +- area: watchdog + change: | + Fixed a bug where the worker thread watchdogs were configured using the main thread's configuration. + This change can be reverted by setting the runtime guard ``envoy.restart_features.worker_threads_watchdog_fix`` + to ``false``. +- area: ext_proc + change: | + Fixed a bug that prevented two ext_proc filters from being configured in the same filter chain. This + change can be reverted by setting the runtime guard + ``envoy.reloadable_features.ext_proc_inject_data_with_state_update`` to ``false``. +- area: ext_proc + change: | + Fixed message-valued CEL attribute serialization (for example + ``xds.virtual_host_metadata``) to use protobuf text format instead of debug string output. + This restores ext_proc compatibility with protobuf 30+ where debug-string output is + intentionally not parseable (for example ``goo.gle/debugonly`` prefixes). This change can + be reverted by setting runtime guard + ``envoy.reloadable_features.cel_message_serialize_text_format`` to ``false``. +- area: ext_proc + change: | + Fixed a bug where unnecessary empty data chunks are processed by the filter chain. This + change can be reverted by setting the runtime guard + ``envoy.reloadable_features.ext_proc_return_stop_iteration`` to ``false``. +- area: ratelimit + change: | + Fixed a bug in the gRPC rate limit client where the client could get into a bad state if the + callbacks were not properly released after a request completion, leading to potential use-after-free + issues. The fix ensures that callbacks and request references are cleared after completion, and adds + assertions to enforce correct usage patterns. +- area: ext_authz + change: | + Fixed a bug where headers from a denied authorization response (non-``200``) were not properly propagated + to the client. +- area: formatter + change: | + Added support for the ``UPSTREAM_LOCAL_CLOSE_REASON`` log formatter. +- area: formatter + change: | + Fixed the log formatter in HTTP router upstream logs by correctly setting the downstream connection's + ``ConnectionInfoProvider`` in the ``StreamInfo``. +- area: spiffe + change: | + Reduced the number of file watches needed by :ref:`trust_bundles + ` when the validator + is used in multiple places. Added support for :ref:`watched_directory + ` to support Kubernetes environments that rely on + atomic symbolic file updates. Added content hashing during file watching to handle excessive watch notifications. +- area: odcds + change: | + Fixed a crash (SIGABRT) when destroying OdCDS handles on worker threads. The handle no longer holds + a direct reference to the subscription, preventing thread-safety issues during destruction. The + subscription now persists in ``ClusterManagerImpl`` and is looked up by a config source key. +- area: ext_authz + change: | + Fixed the HTTP ext_authz client to respect ``status_on_error`` configuration when the authorization + server returns a 5xx error or when HTTP call failures occur. Previously, these error scenarios always + returned ``403 Forbidden`` regardless of the configured error status. +- area: release + change: | + Published contrib binaries now include the ``-contrib`` suffix in their version string. +- area: header_mutation + change: | + Fixed an issue where query parameter values added via ``query_parameter_mutations`` were not URL-encoded, + allowing query parameter injection attacks. Values from formatters like ``%REQ(header)%`` are now properly + URL-encoded when added to the query string. This behavior is controlled by the runtime guard + ``envoy.reloadable_features.header_mutation_url_encode_query_params``. +- area: mcp_router + change: | + Fixed MCP router to support session-less backends that do not return ``mcp-session-id`` + headers. Previously, this caused a spurious ``500`` error. +- area: health_check + change: | + Fixed a race condition where active health checks could start before required upstream TLS SDS secrets + were fetched, causing intermittent health check failures `#43116 `_. + This fix can be disabled by setting runtime guard ``envoy.reloadable_features.health_check_after_cluster_warming`` + to ``false``. +- area: upstream + change: | + Fixed an out-of-bounds issue in ``ThreadAwareLoadBalancerBase`` that could occur during mid-batch EDS host updates + due to eagerly calling ``refresh()`` before the deferred priority state resize. +- area: upstream + change: | + Fixed ``ProdClusterInfoFactory`` of HDS to honor ``alt_stat_name`` when creating cluster stats scopes, so + stats emitted through that path now use the alternate cluster name consistently with other upstream + cluster implementations. +- area: access_log + change: | + Fixed a crash on listener removal with a process-level access log rate limiter + :ref:`ProcessRateLimitFilter `. +- area: json + change: | + Fixed an off-by-one write in ``JsonEscaper::escapeString()`` that could corrupt the string null terminator + when the input string ends with a control character. +- area: network + change: | + Fixed a crash in ``Utility::getAddressWithPort`` when called with a scoped IPv6 address (e.g., ``fe80::1%eth0``). +- area: rbac + change: | + Fixed RBAC header matcher to validate each header value individually instead of concatenating multiple header values + into a single string. This prevents potential bypasses when requests contain multiple values for the same header. + The new behavior is enabled by the runtime guard ``envoy.reloadable_features.rbac_match_headers_individually``. +- area: contrib + change: | + Fixed a segfault from a timer thread-safety violation, a ring buffer overflow, and incorrect alpha + calculation in the ``peak_ewma`` load balancer. +- area: http + change: | + Fixed an issue where filter chain execution could continue on HTTP streams that had been reset but not yet + destroyed. This could cause use-after-free conditions when filter callbacks were invoked on filters that + had already received ``onDestroy()``. The fix ensures that ``decodeHeaders()``, ``decodeData()``, + ``decodeTrailers()``, and ``decodeMetadata()`` are blocked after a downstream reset. +- area: dynamic_modules + change: | + Fixed a bug where dynamic module filter may result in an incomplete body being sent to upstream + or downstream when some filters before or after the dynamic module filter in the chain + buffered the body and the dynamic module filter did not. +- area: io_socket + change: | + Fixed an issue where a userspace IO socket could fail to drain the write buffer of a connection + when the peer had closed for reads, causing the connection to remain open indefinitely. +- area: config_validation + change: | + Fixed ``--mode validate`` to create bootstrap extensions during config validation. Previously, + bootstrap extensions were completely skipped in validation mode, which meant their configs were + not validated and any providers or singletons they register were unavailable to downstream config + elements that depend on them. +- area: http2 + change: | + Apply nghttp2 CVE-2026-27135 patch. +- area: shared_pool + change: | + Fixed a leak in ``ObjectSharedPool`` when the last reference to a pooled object is released + on a non-main thread after the main dispatcher has started teardown. The queued cross-thread + deletion now carries ownership, so the object is freed whether or not the dispatcher runs + the queued callback. Most visibly this fixes a LeakSanitizer leak in + ``StrictDnsClusterImpl`` involving the ``Locality`` shared pool during server shutdown. + +removed_config_or_runtime: +- area: oauth2 + change: | + Removed the runtime guard ``envoy.reloadable_features.oauth2_encrypt_tokens``. Token cookies (access, + ID, refresh) remain encrypted by default; use the ``disable_token_encryption`` filter configuration + to turn encryption off if needed. +- area: tcp_proxy + change: | + Removed runtime guard ``envoy.reloadable_features.tcp_proxy_set_idle_timer_immediately_on_new_connection`` + and legacy code path. + +new_features: +- area: ext_authz + change: | + Added :ref:`shadow_mode + ` to the HTTP + ext_authz filter. When enabled, the filter calls the authorization service as normal but never + terminates the request. The authorization decision is written to FilterState as a + :ref:`ShadowDecision ` + under the filter's configured name with a ``.shadow`` suffix + (``envoy.filters.http.ext_authz.shadow`` by default) so that a subsequent filter can read + and optionally enforce it. +- area: lua + change: | + Added stats API support to the :ref:`Lua filter `, allowing Lua + scripts to create and increment counters, gauges, and histograms via + ``handle:streamInfo():stats()``. +- area: dynamic_modules + change: | + Added ``envoy_dynamic_module_callback_is_validation_mode`` ABI callback that allows dynamic + modules to check if the server is running in config validation mode. +- area: ratelimit + change: | + Added ``is_negative_hits`` boolean to the ``hits_addend`` + configuration in the rate limit API. This allows rate limit + requests to add to the rate limit budget (refill previously + consumed tokens) via + :ref:`HitsAddend.is_negative_hits + `, + or per descriptor in + :ref:`RateLimitDescriptor.is_negative_hits + `. +- area: redis + change: | + Added zone-aware routing support for Redis Cluster proxy. New read policies + ``LOCAL_ZONE_AFFINITY`` and ``LOCAL_ZONE_AFFINITY_REPLICAS_AND_PRIMARY`` route read requests + to replicas in the same availability zone. Zone discovery is enabled via + :ref:`enable_zone_discovery + `. + Note: This feature currently works with Valkey only. +- area: overload_manager + change: | + Added :ref:`ShrinkHeapConfig ` typed + configuration for the ``envoy.overload_actions.shrink_heap`` overload action. This allows + operators to configure the timer interval (``timer_interval``, minimum ``1s``, default ``10s``) and + the memory release threshold (``max_unfreed_memory_bytes``, default ``100MB``) passed to + ``tcmalloc::MallocExtension::ReleaseMemoryToSystem()``. +- area: dns_resolver + change: | + Added :ref:`HickoryDnsResolverConfig + `, a new DNS + resolver using the `Hickory DNS `_ library. +- area: dynamic_modules + change: | + Added :ref:`tracer ` + support for dynamic modules, enabling custom distributed tracing backends to be implemented + in dynamic modules. +- area: dynamic_modules + change: | + Rust SDK: a single dynamic module can now host multiple access loggers via the new + ``access_logger:`` arm of ``declare_all_init_functions!``, which registers a factory that + dispatches to different ``AccessLoggerConfig`` implementations by ``logger_name``. The + legacy ``declare_access_logger!`` macro is preserved as a single-config shim. +- area: dynamic_modules + change: | + Added upstream HTTP TCP bridge extension for dynamic modules. This enables modules to transform + HTTP requests into raw TCP data for upstream connections and convert TCP responses back into HTTP + responses via explicit send callbacks. See :ref:`envoy.upstreams.http.dynamic_modules + `. +- area: filters + change: | + Added filters to update the filter state in :ref:`a listener filter `. +- area: tls + change: | + Added a per-connection filter state object to select a workload trust domain in the SPIFFE validator in + multi-tenant deployments. +- area: tls + change: | + Extended TLS certificate compression (RFC 8879): added brotli to QUIC (which already supported zlib), + and added brotli and zlib to TCP TLS. Controlled by runtime flag + ``envoy.reloadable_features.tls_certificate_compression_brotli`` (defaults to ``true``). + When disabled, QUIC retains zlib-only compression, while TCP TLS has no compression. +- area: tls + change: | + Exposed the SHA-256 fingerprint and serial number of the verified issuer (CA) certificate from + the validated downstream mTLS peer certificate chain. Accessible via the + ``%DOWNSTREAM_PEER_ISSUER_FINGERPRINT_256%`` and ``%DOWNSTREAM_PEER_ISSUER_SERIAL%`` + :ref:`access log formatters `, and via the + ``sha256PeerCertificateIssuerDigest()`` and ``serialNumberPeerCertificateIssuer()`` methods on + :ref:`downstreamSslConnection() ` in the Lua filter. +- area: dynamic_modules + change: | + Added custom metrics (counters, gauges, histograms) support to load balancer dynamic modules. + Modules can now define metrics during configuration and record them during host selection. +- area: dynamic_modules + change: | + Rust SDK now provides an opt-in ``CatchUnwind`` wrapper for filter callbacks. When a + wrapped callback panics, Envoy logs the panic and returns a fail-closed error (e.g. + HTTP ``500``, stream reset, connection close) instead of aborting the process. +- area: http_11_proxy + change: | + Added ability to configure a default proxy address that is used when the proxy address is not + configured via metadata. +- area: compressor + change: | + Added :ref:`weaken_etag_on_compress + ` + to the :ref:`compressor filter `. When enabled in + ``response_direction_config``, strong ``ETag`` response headers are weakened (``W/`` prefix) + instead of removed when compression is applied, allowing caches and conditional requests to + work while indicating the body was modified by compression. When both ``weaken_etag_on_compress`` + and ``disable_on_etag_header`` are ``true``, the new field takes precedence. +- area: golang + change: | + Added ``DownstreamSslConnection()`` method to the Golang HTTP filter's ``StreamInfo`` interface, + providing access to SSL/TLS connection information for the downstream connection. This includes + peer certificate details (subject, issuer, serial number, SANs, validity), TLS version, cipher + suite, and PEM-encoded certificates. This achieves feature parity with the Lua filter's + ``downstreamSslConnection()`` functionality. +- area: outlier_detection + change: | + Added :ref:`detect_degraded_hosts ` + to enable passive degraded host detection. When enabled, outlier detection marks hosts as degraded when they return + the ``x-envoy-degraded`` header. Degraded hosts are deprioritized in load balancing but remain in rotation (not + ejected). The degraded state is cleared using the same backoff algorithm as ejection. Defaults to ``false``. +- area: http2 + change: | + Added :ref:`max_header_field_size_kb + ` to configure the + maximum wire-encoded size in KiB of an individual HPACK-encoded header field that the HTTP/2 + codec will accept. This allows increasing the default nghttp2 per-header limit of 64 KiB on the + wire when larger single headers need to be supported. +- area: dynamic_modules + change: | + Added dynamic module input matcher extension that allows implementing custom matching logic + in external languages (Rust, Go, C) via dynamic modules. +- area: dynamic_modules + change: | + Added listener lifecycle event callbacks to the bootstrap dynamic module extension. Modules can + opt in via ``enable_listener_lifecycle`` to receive ``on_listener_add_or_update`` and + ``on_listener_removal`` notifications when listeners change in the ``ListenerManager``. +- area: memory + change: | + Added ``soft_memory_limit_bytes``, ``max_per_cpu_cache_size_bytes``, and ``max_unfreed_memory_bytes`` + fields to :ref:`MemoryAllocatorManager ` + for fine-grained control of tcmalloc memory management. +- area: dynamic_modules + change: | + Added :ref:`TLS certificate validator + ` + support for dynamic modules, enabling custom TLS certificate validation to be implemented in dynamic modules. +- area: dynamic_modules + change: | + Added filter state read/write support for dynamic module cert validators, allowing modules to set and + get string values in the connection's filter state during certificate chain verification. +- area: dynamic_modules + change: | + Added ``write_to_socket`` and ``close_socket`` ABI callbacks for the dynamic module listener + filter, enabling protocol negotiation use cases such as Postgres SSL and MySQL handshake at the listener + filter level. +- area: dynamic_modules + change: | + Added HTTP callout support for dynamic module listener filters, enabling listener filters to initiate + asynchronous HTTP requests to upstream clusters and receive responses via the ``send_http_callout`` ABI + callback and ``on_listener_filter_http_callout_done`` event hook. +- area: ext_authz + change: | + Added :ref:`path_override ` + to the HTTP ext_authz filter. When set, the request path sent to the authorization service is replaced + entirely by this value. Only one of ``path_prefix`` or ``path_override`` may be set; validation fails + at config load if both are specified. +- area: stats + change: | + Added support to limit the number of metrics stored in each scope within the stats library. +- area: stats + change: | + The admin prometheus stats endpoint now supports the protobuf exposition format, and will automatically + use it if the request contains the correct Accept header, or if query parameter ``prom_protobuf=1`` is + set. In a prometheus scrape configuration, add ``PrometheusProto`` to ``scrape_protocols`` to use + the protobuf format. Additionally, when using the protobuf exposition format, the admin prometheus stats + endpoint now supports `native histograms `_ + when using the prometheus protobuf exposition format, using query + ``/stats/prometheus?histogram_buckets=prometheusnative``. +- area: stats + change: | + Added support for cluster-level stats matcher, allowing more granular control over which stats + are enabled and reported at the cluster level. The stats matcher could be configured via + the xDS API dynamically on a per-cluster basis. + See :ref:`envoy.stats_matcher ` for more details. +- area: stats + change: | + Added support for listener-level stats matcher via ``typed_filter_metadata`` key + ``envoy.stats_matcher`` in the listener's + :ref:`metadata `. When present, + the provided :ref:`StatsMatcher ` replaces + the global stats matcher for that listener's stats scope, allowing fine-grained control over + which listener stats are created. + If the value of ``envoy.stats_matcher`` is set but does not contain a valid ``StatsMatcher`` + message, this will be treated as an invalid configuration and the listener will fail to start. + This strict validation can be disabled temporarily by setting the runtime guard + ``envoy.reloadable_features.strict_stats_matcher_unpacked`` to ``false``. +- area: tls + change: | + Added support for fetching certificates on-demand via SDS in the upstream TLS transport socket + using the extension :ref:`on-demand certificate selector + `. +- area: access_log + change: | + Added stats customization support for the :ref:`access logger `. +- area: dynamic_modules + change: | + Introduced the extended ABI forward compatibility mechanism for dynamic modules + where modules built with an SDK version can be loaded by Envoy + binaries of the next Envoy version. For example, a module built with the v1.38 SDK + can now be loaded by an Envoy binary of v1.39. +- area: dynamic_modules + change: | + Added drain and shutdown lifecycle hooks for bootstrap dynamic modules. +- area: dynamic_modules + change: | + Added support for dynamic modules authors to register any combination of HTTP, network, listener, + UDP listener, and bootstrap filters in the Rust SDK. +- area: dynamic_modules + change: | + Added connection state and flow control ABI callbacks for the dynamic module network filter, + including ``read_disable``, ``read_enabled``, ``get_connection_state``, ``enable_half_close``, + ``is_half_close_enabled``, ``get_buffer_limit``, ``set_buffer_limits``, and + ``above_high_watermark``. +- area: dynamic_modules + change: | + Added socket property getter and SSL/TLS information ABI callbacks for the dynamic module listener + filter, including ``get_requested_server_name``, ``get_detected_transport_protocol``, + ``get_requested_application_protocols``, ``get_ja3_hash``, ``get_ja4_hash``, ``is_ssl``, + ``get_ssl_uri_sans``, ``get_ssl_dns_sans``, and ``get_ssl_subject``. +- area: mcp_router + change: | + Added support for MCP resource methods ``resources/list``, ``resources/read``, + ``resources/subscribe``, and ``resources/unsubscribe``. +- area: mcp_router + change: | + Added support for MCP prompt methods ``prompts/list`` and ``prompts/get``. +- area: matching + change: | + Added an optional ``field`` parameter to + :ref:`FilterStateInput `. + When set, ``FilterStateInput`` calls ``getField()`` on the filter state object instead of + ``serializeAsString()``, enabling direct matching on individual fields within composite filter + state objects such as proxy protocol TLVs stored via ``tlv_location: FILTER_STATE``. +- area: ratelimit + change: | + Added per-descriptor ``x-ratelimit-*`` headers support. See the + :ref:`x_ratelimit_option ` + field documentation for more details. +- area: ratelimit + change: | + Added ``RemoteAddressMatch`` action to the rate limit filter. This action will generate a descriptor based on the remote address of the + downstream connection by matching it against specified CIDR ranges with support for inversion and formatter substitution. +- area: mcp_router + change: | + Added support for MCP client-to-server notification methods ``notifications/cancelled`` + and ``notifications/roots/list_changed``. +- area: dynamic_modules + change: | + Added typed filter state support for dynamic module HTTP and network filters. This allows modules + to set and get filter state objects using registered ``StreamInfo::FilterState::ObjectFactory`` + instances, enabling interoperability with built-in Envoy filters that expect specific typed objects. +- area: mcp_router + change: | + Added support for MCP completion method ``completion/complete`` with routing based on + ``ref/prompt`` or ``ref/resource``. +- area: mcp_router + change: | + Added support for MCP logging method ``logging/setLevel``. +- area: upstream + change: | + Coalesced load balancer rebuilds during EDS batch host updates. When multiple priorities change in a + single batch, each LB level (LoadBalancerBase, ZoneAwareLoadBalancerBase, EdfLoadBalancerBase, + ThreadAwareLoadBalancerBase) now defers expensive per-priority recalculations to a single pass after + the batch completes, reducing CPU spikes on large clusters. This behavior can be reverted by setting + the runtime guard ``envoy.reloadable_features.coalesce_lb_rebuilds_on_batch_update`` to ``false``. +- area: cel + change: | + Added functionality to reevaluate CEL expressions that attempt to read response path data on the + request path once the data is available. Allows CEL matching based on both request and response + headers. This may cause a behavior change for matchers that previously would silently fail to + match due to attempting to match response headers in the request path. This behavior can be + reverted by setting the runtime guard + ``envoy.reloadable_features.enable_cel_response_path_matching`` to ``false``. +- area: access_log + change: | + Added support for gauges in the :ref:`stats access logger `. +- area: network + change: | + Added access logging support for network filters, similar to HTTP filters, by allowing network filters to + register as access logger instances. +- area: formatter + change: | + Extended ``*_WITHOUT_PORT`` address formatters to accept an optional ``MASK_PREFIX_LEN`` parameter + that masks IP addresses and returns them in CIDR notation (e.g., ``%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(16)%`` + returns ``10.1.0.0/16`` for client IP ``10.1.10.23``). +- area: dynamic_modules + change: | + Added :ref:`cluster + ` + support for dynamic modules, enabling custom service discovery and host management to be implemented + in dynamic modules. +- area: dynamic_modules + change: | + Added server lifecycle callbacks (``on_server_initialized``, ``on_drain_started``, ``on_shutdown``) + for dynamic module custom clusters, enabling modules to react to server readiness, drain, and + shutdown events. +- area: dynamic_modules + change: | + Changed the ``details`` parameter in ``cluster_lb_async_host_selection_complete`` ABI callback + from raw ``const char*`` and ``size_t`` to ``envoy_dynamic_module_type_module_buffer`` for + consistency with the ABI style guide. +- area: stat_sinks + change: | + Added support for exporting OpenTelemetry metrics via HTTP. The new ``http_service`` field + in :ref:`SinkConfig ` + enables direct OTLP metrics export to backends that only accept HTTP (Dynatrace, Datadog, Elastic), + without requiring an intermediate collector sidecar. +- area: ratelimit + change: | + Added support for shadow mode in the local rate limit filter. +- area: config + change: | + Added support for :ref:`set_node_on_first_message_only + ` to Delta-xDS. + Guarded by runtime flag ``envoy.reloadable_features.xds_legacy_delta_skip_subsequent_node``. +- area: formatter + change: | + Added the new access log formatter ``DOWNSTREAM_LOCAL_CLOSE_REASON``. +- area: formatter + change: | + Added ``%UPSTREAM_DETECTED_CLOSE_TYPE%`` and ``%DOWNSTREAM_DETECTED_CLOSE_TYPE%`` to expose the detected + close type of downstream and upstream connections. The possible values are ``Normal``, ``LocalReset``, + and ``RemoteReset``. +- area: formatter + change: | + Added extensions for :ref:`%FILE_CONTENT(/path/to/file)% ` + and :ref:`%SECRET(name)% `. +- area: http_service + change: | + Added the ability for :ref:`request_headers_to_add ` + to use a formatter extension that can retrieve secrets, for including authentication tokens. This support is added for + all uses of this message, including the open telemetry, ``ext_proc``, and ``zipkin`` tracers. +- area: reverse_tunnel + change: | + Added optional tenant isolation support to the reverse tunnel network filter. When + ``enable_tenant_isolation`` is set, Envoy scopes cached reverse tunnel sockets with composite + ``@`` and ``@`` identifiers and rejects handshake headers that already + contain the ``@`` delimiter to prevent ambiguous lookups. +- area: tcp_proxy + change: | + Added an option to emit a log entry when the connection is accepted. +- area: mcp_router + change: | + Added SSE (Server-Sent Events) streaming support for MCP backend responses. The router now handles + SSE responses from backends for ``tools/call`` with direct pass-through streaming, and supports SSE + aggregation for fanout operations (``tools/list``, ``initialize``) with incremental event parsing. +- area: http + change: | + Added an optional :ref:`JSON format ` + for the :ref:`x-forwarded-client-cert ` + (XFCC) header. The format can be configured via :ref:`format + `. +- area: dynamic_modules + change: | + Added :ref:`load balancing policies + ` + support for dynamic modules, enabling custom load balancing algorithms to be implemented in dynamic modules. +- area: dynamic_modules + change: | + Added ``get_host_health_by_address`` ABI callback for dynamic module load balancers, providing O(1) + host health lookup by address string using the cross-priority host map. +- area: sse_parser + change: | + Extended the SSE parser utility to support all standard SSE fields: ``id``, ``event`` (as ``event_type``), + and ``retry``, in addition to the existing ``data`` field. The ``retry`` field is parsed as a ``uint32_t`` + and only accepts values consisting of ASCII digits per the SSE specification. +- area: http + change: | + Added :ref:`envoy.filters.http.sse_to_metadata ` filter for extracting + values from Server-Sent Events (SSE) streams and writing them to dynamic metadata. Useful for capturing + token usage metrics from LLM API responses. Supports pluggable content parsers for different SSE data formats. +- area: content_parsers + change: | + Added :ref:`envoy.content_parsers.json ` content parser for extracting + values from JSON content using JSON path selectors. Can be used by filters that need to parse structured + JSON data and extract specific fields into metadata. +- area: resource_monitors + change: | + Added cgroup v2 support to the CPU utilization resource monitor. The monitor now automatically + detects and selects between cgroup v1 and v2 at runtime by checking available cgroup files on + the system. This enables the resource monitor to work correctly in both cgroup v1 and v2 + environments without configuration changes. +- area: connection + change: | + Add support for closing connections when they stay above the buffer high watermark for a configured time. + This can be enabled by setting the :ref:`per_connection_buffer_high_watermark_timeout + ` + field on the listener and :ref:`per_connection_buffer_high_watermark_timeout + ` + field on the cluster. By default, the timeout is disabled. +- area: dynamic_modules + change: | + Added configurable :ref:`metrics_namespace + ` field + to ``DynamicModuleConfig``. This allows users to customize the prefix used for all metrics + created by dynamic modules. Metrics now appear with the standard ``envoy_`` prefix followed by + the namespace in prometheus output (e.g. ``envoy_myapp_requests_total``). The legacy behavior + (stripping the namespace prefix from prometheus output) can be restored by setting the runtime + guard ``envoy.reloadable_features.dynamic_modules_strip_custom_stat_prefix`` to ``true``. +- area: oauth2 + change: | + Added ``TLS_CLIENT_AUTH`` for the OAuth2 HTTP filter to support RFC 8705 mutual TLS client + authentication. In this mode ``token_secret`` is optional and ignored, and the token endpoint + cluster must be configured with mTLS. +- area: dynamic_modules + change: | + Added a process-wide function registry to the dynamic modules ABI. Modules can register + functions by name via ``envoy_dynamic_module_callback_register_function`` and other modules + can resolve them via ``envoy_dynamic_module_callback_get_function``, enabling zero-copy + cross-module interactions analogous to ``dlsym``. +- area: dynamic_modules + change: | + Added a process-wide shared data registry to the dynamic modules ABI. +- area: dynamic_modules + change: | + Added support for loading dynamic module binaries from local file paths via the new + :ref:`module ` + field in ``DynamicModuleConfig``. This allows specifying an absolute path to a ``.so`` file + via ``module.local.filename`` as an alternative to the name-based search path. +- area: dynamic_modules + change: | + Added support for fetching dynamic module binaries from remote HTTP sources via + ``module.remote`` in ``DynamicModuleConfig``. The module is downloaded asynchronously during + listener initialization with SHA256 verification, written to a temporary file, and loaded + via ``dlopen``. If the remote fetch fails, the filter is not installed and requests pass + through (fail-open). +- area: dynamic_modules + change: | + Added caching for remotely fetched dynamic modules. Since ``newDynamicModuleFromBytes`` + writes modules to a deterministic path based on SHA256, subsequent config updates + referencing the same SHA256 load from the existing file, avoiding redundant HTTP fetches. +- area: dynamic_modules + change: | + Added ``nack_on_cache_miss`` option for remote dynamic module sources. When enabled, + uncached remote modules cause configuration rejection (NACK) with a background fetch, + instead of blocking listener warming. This enables remote modules in ECDS and per-route + configurations where an init manager is not available. +- area: dynamic_modules + change: | + Network filter read and write buffers now persist after ``on_read``/``on_write`` callbacks, allowing + modules to access buffered data from ``on_scheduled`` and other callbacks. Added + ``envoy_dynamic_module_callback_network_filter_get_cluster_host_count`` to query cluster host counts + by name, enabling scale-to-zero and custom load balancing decisions in network filters. +- area: dynamic_modules + change: | + Added metrics definition and update support for bootstrap dynamic modules. +- area: dynamic_modules + change: | + Added timer API to the bootstrap extension dynamic modules ABI. +- area: dynamic_modules + change: | + Added admin handler API to the bootstrap extension dynamic modules ABI, enabling modules to + register custom admin HTTP endpoints. +- area: formatter + change: | + Added ``SPAN_ID`` :ref:`access log formatter ` to log the span ID of + the active (downstream) span for a request, complementing the existing ``TRACE_ID`` formatter. +- area: formatter + change: | + Added ``QUERY_PARAMS`` support for substitution formatter to log all query params. + They can either be logged in their original form or decoded. +- area: formatter + change: | + Added new access log formatters for tracking upstream hosts and connection IDs attempted during + request processing: ``%UPSTREAM_HOSTS_ATTEMPTED%``, ``%UPSTREAM_HOSTS_ATTEMPTED_WITHOUT_PORT%``, + ``%UPSTREAM_HOST_NAMES_ATTEMPTED%``, ``%UPSTREAM_HOST_NAMES_ATTEMPTED_WITHOUT_PORT%``, and + ``%UPSTREAM_CONNECTION_IDS_ATTEMPTED%``. These are useful for debugging retry behavior and + understanding which hosts were tried before a successful connection or final failure. +- area: dynamic_modules + change: | + Added init manager integration to the dynamic modules bootstrap extension ABI. An init target + is automatically registered for every bootstrap extension, blocking traffic until the module + signals readiness via ``signal_init_complete``. +- area: dynamic_modules + change: | + Added ``on_host_membership_update`` event hook and ``get_member_update_host_address`` callback + for dynamic module load balancers, enabling modules to receive notifications when hosts are + added or removed from the cluster and inspect the affected host addresses. +- area: geoip + change: | + Added ``asn_org`` field to :ref:`geo_field_keys + ` + to populate a header with the autonomous system organization name from the MaxMind ASN database. +- area: tracers + change: | + Added log events to spans created by the OpenTelemetry tracer. +- area: mcp_router + change: | + Added SSE response support for MCP ``resources/list`` fanout aggregation. +- area: mcp_router + change: | + Added support for MCP ``resources/templates/list`` method with fanout aggregation. +- area: tcp_proxy + change: | + Added :ref:`proxy_protocol_tlv_merge_policy + ` + to control how TLVs in ``proxy_protocol_tlvs`` are merged with existing PROXY protocol state. + Supports ``ADD_IF_ABSENT`` (default), ``OVERWRITE_BY_TYPE_IF_EXISTS_OR_ADD``, and + ``APPEND_IF_EXISTS_OR_ADD``. +- area: admin + change: | + Added ``filter`` query parameter support to the ``/clusters`` endpoint. The parameter accepts a RE2 + regular expression to filter clusters by name. Compatible with the ``format`` parameter for both + text and JSON output (e.g., ``/clusters?filter=service&format=json``). +- area: mcp_router + change: | + Added :ref:`statistics ` to the MCP router filter for + observability into request routing, fanout operations, and error conditions. +- area: mcp + change: | + Added HTTP DELETE session termination support to the MCP filter. DELETE requests with an + ``MCP-Session-Id`` header are now recognized as valid MCP traffic in ``REJECT_NO_MCP`` mode. +- area: a2a + change: | + Added parsing support for the A2A (Agent2Agent) protocol, enabling parsing of A2A JSON-RPC messages. +- area: mcp + change: | + Added options ``propagate_trace_context`` and ``propagate_baggage`` for extracting ``traceparent``, + ``tracestate``, and baggage from MCP parameters, respectively. +- area: mcp_json_rest_bridge + change: | + Added the MCP JSON REST Bridge HTTP filter configuration to transcode MCP JSON-RPC requests into + standard JSON-REST HTTP requests. +- area: http + change: | + Added :ref:`file_server http filter ` to allow responding with + file contents from the filesystem. +- area: mcp_router + change: | + Added SSE response support for MCP ``prompts/list`` fanout aggregation. +- area: mcp_json_rest_bridge + change: | + Added support for MCP session negotiation, including ``initialize`` and + ``notifications/initialized`` methods. This filter is currently a work-in-progress and not + recommended for production use. +- area: listener_manager + change: | + Added ``ListenerUpdateCallbacks`` interface to ``ListenerManager``, similar to the existing + ``ClusterUpdateCallbacks`` on ``ClusterManager``. +- area: matching + change: | + Added :ref:`local reply matcher input ` + to distinguish Envoy generated local replies from upstream responses. This matcher input returns ``true`` + for local replies and ``false`` for upstream responses, enabling ``custom_response`` filter policies to + selectively apply only to locally generated error responses. +- area: mcp_json_rest_bridge + change: | + Added support for MCP ``tools/call`` request transcoding. Support for ``tools/list`` and + ``tools/call`` response transcoding is planned. This filter is currently a work-in-progress and + not recommended for production use. +- area: ext_proc + change: | + Added :ref:`allow_content_length_header + ` to allow + the ext_proc filter to preserve the original ``Content-Length`` header or let ext_proc server modify it as needed. +- area: cluster + change: | + Added :ref:`MCP multi cluster ` extension that combines multiple MCP services + into a single one. This cluster type is primarily intended to support aggregation of multiple MCP services into one. +- area: tls + change: | + Enhanced TLS certificate validation failure messages for CRL-related errors in access logs. The + ``%DOWNSTREAM_TRANSPORT_FAILURE_REASON%`` and ``%UPSTREAM_TRANSPORT_FAILURE_REASON%`` access log + formatters now include the certificate's CRL Distribution Point (CRLDP) information when CRL + validation fails. For errors such as ``CRL for certificate was not provided``, ``CRL has expired``, ``CRL + is not yet valid``, or ``certificate revoked``, the error message now includes the certificate's + CRL distribution points (e.g., ``X509_verify_cert: certificate verification error at depth 0: certificate revocation + check against provided CRLs failed: unable to get certificate CRL, certificate CRL distribution points: + [http://crl.example.com/ca.crl, http://backup-crl.example.com/ca.crl]``). This provides better visibility into CRL + validation failures and helps operators identify connectivity or CRL server issues without requiring debug-level logging. +- area: lua + change: | + Added :ref:`set() ` to the Lua filter + state API, allowing Lua scripts to create and store filter state objects dynamically using + registered object factories. +- area: http + change: | + Fixed an issue where filter chain execution could continue on HTTP streams that had been reset but not yet + destroyed. This could cause use-after-free conditions when filter callbacks were invoked on filters that + had already received ``onDestroy()``. The fix ensures that ``decodeHeaders()``, ``decodeData()``, + ``decodeTrailers()``, and ``decodeMetadata()`` are blocked after a downstream reset. +- area: formatter + change: | + Added support for ``%TRACE_ID%`` in the custom response policy filter's local response policy to allow + trace ID to be substituted into the response body. +- area: redis + change: | + Added support for ``BITFIELD_RO`` in ``redis_proxy``. +- area: network_filter + change: | + Added support for + ``on_downstream_data`` (see + :ref:`envoy_v3_api_field_extensions.filters.network.set_filter_state.v3.Config.on_downstream_data`) + to the :ref:`set_filter_state network filter `, allowing + connection filter state to be populated after first receiving data from the downstream connection. +- area: oauth2 + change: | + Added :ref:`allow_failed_matcher ` + to allow requests to proceed to upstream as unauthenticated when OAuth validation fails (missing, invalid, or expired + credentials) and the request matches the configured matchers. This enables graceful degradation patterns where services + can handle both authenticated and unauthenticated requests. When triggered, all OAuth cookies are stripped and context + headers ``x-envoy-oauth-status: failed`` and ``x-envoy-oauth-failure-reason`` are added. The matcher evaluation follows + priority order: ``pass_through_matcher`` > ``allow_failed_matcher`` > ``deny_redirect_matcher`` > default OAuth behavior. +- area: mcp_json_rest_bridge + change: | + Added support for MCP ``tools/call`` response transcoding. Support for ``tools/list`` is planned. + This filter is currently a work-in-progress and not recommended for production use. +- area: http + change: | + Added support for clear route cache in the :ref:`set_filter_state http filter `. When + ``clear_route_cache`` is set, the filter will clear the route cache for the current request after applying filter state updates. + This is necessary if the route configuration may depend on the filter state values set. +- area: stateful_session + change: | + Added :ref:`status_on_strict_destination_not_found + ` + to the :ref:`stateful session filter `. When + :ref:`strict ` + mode is enabled and the requested destination is not found in the set of available endpoints, Envoy + will return the configured HTTP status code instead of the default ``503``. This does not apply when + the destination exists but is unhealthy. +- area: tcp_proxy + change: | + Propagate upstream TCP RST to downstream when detected close type is RemoteReset. + This behavioral change can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.propagate_upstream_rst_through_tunneled_tcp_proxy`` to ``false``. +- area: tcp_proxy + change: | + HttpUpstream now maps remote-originated HTTP stream reset reasons (RemoteReset, + RemoteRefusedStreamReset, RemoteConnectionFailure) to RemoteClose with + DetectedCloseType::RemoteReset, enabling RST propagation through tunneled tcp_proxy. + This behavioral change can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.map_http_stream_reset_to_tcp_rst`` to ``false``. +- area: ratelimit + change: | + Support populating rate limit descriptors from cluster metadata. +- area: ratelimit + change: | + Support populating rate limit descriptors from cluster locality metadata. +- area: http + change: | + Added a new overload action :ref:`envoy.overload_actions.close_idle_http_connections ` + that closes idle downstream HTTP connections when the overload action is active. Currently only HTTP/3 idle connections + trimming is supported, and HTTP/1 and HTTP/2 support are to be implemented. +- area: contrib + change: | + Added a new :ref:`Kafka stats sink ` contrib extension + (``envoy.stat_sinks.kafka``) that produces metrics directly to an Apache Kafka topic + using librdkafka. Supports both JSON and Protobuf serialization formats; the Protobuf + format uses the same ``StreamMetricsMessage`` wire format as the gRPC + :ref:`metrics_service ` sink, + allowing consumers to reuse existing deserializers. Configurable batching, delta counters, + tag-as-label emission, and full librdkafka producer tuning (including TLS and SASL + authentication) are supported via + :ref:`KafkaStatsSinkConfig `. +- area: tls + change: | + Added support for building Envoy with OpenSSL as an alternative to the default BoringSSL. + This is achieved through a BoringSSL compatibility layer (``bssl-compat``) that translates BoringSSL + API calls to OpenSSL, allowing Envoy's TLS code to remain unchanged. To build with OpenSSL, use + ``--config=openssl``. HTTP/3 (QUIC) is disabled for OpenSSL builds. Note that OpenSSL builds are + not currently covered by the `Envoy security policy `_. + See :repo:`bazel/SSL.md ` for details. +- area: dynamic_modules + change: | + Promoted the dynamic modules HTTP filter (``envoy.filters.http.dynamic_modules``) from alpha + to stable. +- area: attributes + change: | + Added ``connection.peer_certificate`` and ``upstream.peer_certificate`` attributes that provide the + PEM-encoded peer certificate for downstream and upstream TLS connections respectively. +- area: quic + change: | + Added SCONE (Standardized Communication with Network Elements) support for QUIC. This includes + a new :ref:`enable_scone ` + field in ``QuicProtocolOptions``, and support in Envoy Mobile via a new ``enableScone()`` API in + ``EngineBuilder``. SCONE bandwidth and timestamp data are now propagated to the application via + new ``scone_max_kbps`` and ``scone_timestamp_ms`` fields in the ``envoy_stream_intel`` struct. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index dd29a3d07da3d..9ecf0d6e48ce5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,1117 +1,17 @@ -date: April 23, 2026 +date: Pending behavior_changes: -- area: tcp_proxy - change: | - The TCP proxy filter now requires :ref:`max_early_data_bytes - ` to be - explicitly set when using :ref:`upstream_connect_mode - ` modes - other than ``IMMEDIATE`` (i.e., ``ON_DOWNSTREAM_DATA`` or ``ON_DOWNSTREAM_TLS_HANDSHAKE``). The field - can be set to ``0`` to disable early data buffering while still using delayed connection modes. - Configurations using these modes without ``max_early_data_bytes`` will now fail validation at startup. -- area: on_demand - change: | - The on-demand filter, when doing on-demand CDS, will no longer do internal redirects after CDS is - successful. Filters appearing in the filter chain before the on-demand filter will no longer be - invoked twice. This behavior can be temporarily reverted by setting the runtime guard - ``envoy.reloadable_features.on_demand_cluster_no_recreate_stream`` to ``false``. -- area: boringssl/fips - change: | - The previous flag for specifying FIPS builds (i.e., ``--define=boringssl=fips``) will no longer work and - has been replaced by ``--config=boringssl-fips``. This change will allow us to better support custom - SSL libraries, and will allow FIPS-compliant Envoy to be built with the imminent switch to Bazel bzlmod. -- area: tls - change: | - Set - :ref:`enforce_rsa_key_usage ` - to ``true`` by default. The handshake will fail if the keyUsage extension is present and incompatible with the TLS usage. In - the next version of Envoy, this option will be removed and the enforcing behavior will always be used. -- area: ext_proc - change: | - The ``processing_effect_lib`` has been moved from the :ref:`extensions/filters/http/ext_proc - ` namespace to the - ``extensions/filters/common/processing_effect`` namespace. All path references within the Envoy - codebase have been updated. +# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* minor_behavior_changes: -- area: conn_pool - change: | - Added new ``upstream_rq_active_overflow`` counter incremented when a request is rejected - due to the ``max_requests`` circuit breaker being exhausted in ``attachStreamToClient()``. - Previously this condition incorrectly incremented ``upstream_rq_pending_overflow``, making - it impossible to distinguish pending queue saturation from active request saturation via - metrics alone. The new counter is now the authoritative signal for this path by default; - set runtime flag ``envoy.reloadable_features.skip_pending_overflow_count_on_active_rq`` to - ``false`` to also increment ``upstream_rq_pending_overflow`` on this path, preserving - backwards compatibility with existing dashboards and alerts. -- area: geoip - change: | - :ref:`Envoy Http Geoip filter extension - ` has been promoted to stable status. -- area: load_balancing - change: | - Changed the default behavior of ``OrcaWeightManager`` to prefer named metrics over application - utilization when computing utilization. This behavior can be reverted by setting the runtime guard - ``envoy.reloadable_features.orca_weight_manager_use_named_metrics_first`` to ``false``. -- area: compressor - change: | - Strong ``ETag`` removal when compressing now uses the same weak ``W/`` check as - :ref:`weaken_etag_on_compress - `. - Previously removal applied only when ``ETag`` length was greater than 2; two-character strong - values are now removed as well (the only two-character weak form is ``W/``). -- area: memory - change: | - Replaced the custom timer-based tcmalloc memory release with tcmalloc's native - ``ProcessBackgroundActions`` and ``SetBackgroundReleaseRate`` APIs. This provides more comprehensive - background memory management including per-CPU cache reclamation, cache shuffling, and size class - resizing, in addition to memory release. The ``tcmalloc.released_by_timer`` stat has been removed. -- area: mcp - change: | - Changed the default metadata namespace for the MCP filter from ``mcp_proxy`` to ``envoy.filters.http.mcp``. - This change can be reverted by setting the runtime guard - ``envoy.reloadable_features.mcp_filter_use_new_metadata_namespace`` to ``false``. -- area: ext_authz - change: | - Changed the behavior of ``timeout: 0s`` in the HTTP ext_authz filter to mean "no timeout" (infinite) - instead of immediate timeout. Previously, ``timeout: 0s`` would cause requests to fail immediately. - This aligns with other Envoy timeout configurations where ``0`` means disabled or infinite. -- area: ratelimit - change: | - Changed the behavior of ``timeout: 0s`` in the HTTP rate limit filter to mean "no timeout" (infinite) - instead of immediate timeout. Previously, ``timeout: 0s`` would cause requests to fail immediately. - This aligns with other Envoy timeout configurations where ``0`` means disabled or infinite. -- area: upstream - change: | - EDS host metadata comparison during ``updateDynamicHostList`` now uses a cached metadata hash - instead of ``MessageDifferencer::Equivalent``. The hash is computed once when metadata is set, - making per-host comparison O(1). In rare cases where two semantically equivalent metadata messages - have different serializations, this may cause a spurious metadata update (false positive) but will - never miss an actual change. -- area: upstream - change: | - ``ProdClusterInfoFactory`` now honors ``alt_stat_name`` when creating cluster stats scopes, so - stats emitted through that path use the alternate cluster name consistently with other upstream - cluster implementations. -- area: histograms - change: | - Updated libcircllhist to 0.3.2, which changes how bucket bounds are interpreted. This should not impact - production monitoring if the number of samples in the histograms is high. Affected tests were adjusted - to account for histogram changes. -- area: stat_sinks - change: | - OpenTelemetry :ref:`SinkConfig ` - stopped reporting empty delta counters and histograms. -- area: mcp - change: | - Relaxed the MCP filter POST Content-Type check from an exact match on ``application/json`` to a - prefix match, so that ``application/json; charset=utf-8`` and similar media-type parameters are - accepted. -- area: ext_proc - change: | - Added ``received_immediate_response`` flag in the ``ExtProcLoggingInfo`` filter state. -- area: happy_eyeballs - change: | - Happy Eyeballs now handles interleaving of non-IP addresses. The restriction against ``additional_addresses`` - containing non-IP addresses is removed. This behavior can be reverted by setting the runtime - guard ``envoy.reloadable_features.happy_eyeballs_sort_non_ip_addresses`` to ``false``. -- area: ext_authz - change: | - Added tracking bits for processing effect for request headers and failed open occurrence in the - ``ExtAuthzLoggingInfo``. This new data will be automatically collected and can be accessed via - ``requestProcessingEffect()`` and ``failedOpen()``. -- area: dynamic_modules - change: | - Now all the dynamic module extension factories (HTTP, network, listener, UDP listener, and so on) will - serialize the ``google.protobuf.Struct`` configuration message to JSON string and pass it to the - dynamic module side as the configuration. -- area: oauth2 - change: | - Added :ref:`per-route configuration support ` - to the OAuth2 HTTP filter. -- area: oauth2 - change: | - The OAuth2 filter now removes the ``OauthExpires`` cookie from a user's browser upon logging out. - Previously this cookie persisted beyond the logout despite the session no longer existing. -- area: proto_api_scrubber - change: | - If :ref:`scrub_unknown_fields - ` - is set to ``true`` in the :ref:`ProtoApiScrubberConfig - `, - unknown fields will now be scrubbed. This is disabled by default. -- area: http - change: | - The ``route()``, ``clusterInfo()`` and ``virtualHost()`` methods on the HTTP filter callbacks - and stream info interfaces now are refactored to return ``OptRef`` instead of shared - pointers. And additional methods ``routeSharedPtr()``, ``clusterInfoSharedPtr()`` and - ``virtualHostSharedPtr()`` are added to return shared pointers. - The shorter ``route()``, ``clusterInfo()`` and ``virtualHost()`` methods are preferred for - most use cases where the returned reference is not used beyond the scope of the current function - and the caller does not want to manage shared pointer ownership. The new shared pointer methods - are intended for use cases where the caller needs to keep the ownerships to the route, cluster, - or virtual host. -- area: http - change: | - ``StreamRateLimiter`` constructor was updated to always take a pre-constructed and initialized - ``TokenBucket``, and remove the first argument ``max_buffered_data`` and the ``TimeSource&`` - argument. - The ``write_stats_cb`` callback signature was changed from taking a ``bool`` argument - "something is buffered" to a ``uint64_t`` "how many bytes are buffered". - If already initializing with a ``TokenBucket``, you may need to call ``maybeReset`` on that - bucket before passing it to ``StreamRateLimiter`` to ensure equivalent initialization. - If previously constructing a ``StreamRateLimiter`` using ``max_buffered_data``, use - ``StreamRateLimiter::simpleTokenBucket(limit_kbps, time_source)`` to initialize an equivalent - ``TokenBucket``. -- area: http - change: | - Strict parsing of HTTP/1 chunked encoding is now available. It is disabled by default and can be - enabled by setting the runtime guard ``envoy.reloadable_features.strict_chunk_parsing`` to ``true``. +# *Changes that may cause incompatibilities for some users, but should not for most* bug_fixes: -- area: grpc - change: | - Fixed the router to return ``DEADLINE_EXCEEDED`` (gRPC status ``4``) instead of ``UNAVAILABLE`` (gRPC - status ``14``) when the router itself enforces a gRPC request timeout. Previously, the router sent an - HTTP ``504`` local reply which was mapped to ``UNAVAILABLE`` via the standard HTTP-to-gRPC status mapping, - causing clients to incorrectly retry requests that had already timed out. This behavior is disabled - by default and can be enabled by setting the runtime guard - ``envoy.reloadable_features.grpc_timeout_returns_deadline_exceeded`` to ``true``. -- area: quic - change: | - Fixed HTTP/3 client connection pool to handle early data request correctly with asynchronous certificates - validation. This change is gated by the runtime guard ``envoy.reloadable_features.fix_http3_early_data_timing``. -- area: router - change: | - Fixed a bug where the router's buffer overflow check double-counted the request body size when an - upstream filter (e.g. the buffer filter) had already buffered all data. This caused requests with - body size between half and full buffer limit to incorrectly cancel internal redirects. -- area: router - change: | - Fixed a crash in scoped RDS when on-demand update is triggered for a scope that uses inline - ``route_configuration`` instead of ``route_configuration_name``. The null RDS provider dereference - is now guarded and the on-demand callback returns ``false`` for inline-config scopes. -- area: hot_restart - change: | - Fixed hot restart for listeners with a network namespace in the address. Previously, socket - hand-off didn't work cleanly because the namespace was not included in the ``PassListenSocket`` - request, causing the parent to always fall back to binding a new socket. -- area: dynamic_modules - change: | - Fixed the dynamic modules network filter to always set a local close reason when closing connections. This - resolves an error when a dynamic modules network filter closes a connection that has an HTTP filter present. -- area: dynamic_modules - change: | - Fixed a bug where dynamic module extensions do not handle the ``google.protobuf.Struct`` configuration - properly as the API definition requires. - The dynamic module extension factories now serialize the ``Struct`` to JSON string and pass the string - to the dynamic module side as the configuration. -- area: http_11_proxy - change: | - Fixed bug where providing an empty inner socket config would cause Envoy to crash. -- area: tls - change: | - Fixed on-demand TLS selector to enforce session resumption settings. -- area: http - change: | - Fixed crash if a downstream watermark is hit by network filter writes before the HTTP codec is created. -- area: http - change: | - Allow ``Keep-Alive`` response headers added via ``response_headers_to_add`` to be preserved instead - of being stripped by HCM sanitization. Upstream ``Keep-Alive`` headers are now stripped in the router - before user-configured response headers are applied, so user-added ``Keep-Alive`` headers survive. - Protected by runtime guard ``envoy.reloadable_features.preserve_downstream_keepalive``. -- area: http - change: | - Fixed a bug where the internal redirect logic may hang up a request when the request buffer is - overflowed. -- area: load_report - change: | - Fixed a race condition during load-report shutdown with the ADS stream by introducing proper cleanup - of the gRPC stream. -- area: http - change: | - Fixed a potential file descriptor leak where HTTP/1.1 connections with zombie streams (waiting for codec - completion) would not be properly closed when in draining state. This could occur when a response was - sent before the request was fully received, causing connections to remain open indefinitely. This change - can be reverted by setting the runtime guard - ``envoy.reloadable_features.http1_close_connection_on_zombie_stream_complete`` to ``false``. -- area: overload_manager - change: | - Fixed a resource leak in global connection limit tracking that caused permanent connection rejections - when using load shedding (e.g., ``envoy.load_shed_points.tcp_listener_accept``). When connections were - rejected due to load shedding after passing the global connection limit check, the allocated connection - limit resource was not released, causing the connection counter to become incorrect and leading to - ``failed_updates`` in the resource monitor. This resulted in permanent connection rejections even after - load subsided. The fix ensures that connection limit resources are properly released when connections - are rejected due to load shedding. Also added defensive resource cleanup for edge cases where address - processing fails (e.g., ``localAddress()`` or ``peerAddress()`` errors). -- area: drop_overload - change: | - Fixed a bug where ``drop_overload`` failed to use cached EDS resources. -- area: scoped_rds - change: | - Fixed a bug where SRDS subscriptions would never start when a listener with scoped routes was - added after server initialization completed. The ``SrdsFactory`` interface - was incorrectly using the server-level init manager instead of the listener-level init manager, - causing the SRDS init target to be silently discarded. -- area: xds - change: | - Fixed a bug where in delta-xDS when xDS-Failover is configured (gated by the experimental - ``envoy.restart_features.xds_failover_support`` runtime guard), in some cases the - :ref:`initial_resource_versions ` - field was not updated correctly when attempting to reconnect to the xDS server. -- area: odcds - change: | - Fixed a bug where using OD-CDS in tcp_proxy over ADS would not work in some - cases. This change could be reverted temporarily by setting the runtime guard - ``envoy.reloadable_features.tcp_proxy_odcds_over_ads_fix`` to ``false``. -- area: http - change: | - Fixed upstream client to not close connection when idle timeout fires before the connection is - established. This change can be reverted by setting the runtime guard - ``envoy.reloadable_features.codec_client_enable_idle_timer_only_when_connected`` to ``false``. -- area: oauth2 - change: | - Fixed OAuth2 refresh requests so host rewriting no longer overrides the original ``Host`` header value. -- area: watchdog - change: | - Fixed a bug where the worker thread watchdogs were configured using the main thread's configuration. - This change can be reverted by setting the runtime guard ``envoy.restart_features.worker_threads_watchdog_fix`` - to ``false``. -- area: ext_proc - change: | - Fixed a bug that prevented two ext_proc filters from being configured in the same filter chain. This - change can be reverted by setting the runtime guard - ``envoy.reloadable_features.ext_proc_inject_data_with_state_update`` to ``false``. -- area: ext_proc - change: | - Fixed message-valued CEL attribute serialization (for example - ``xds.virtual_host_metadata``) to use protobuf text format instead of debug string output. - This restores ext_proc compatibility with protobuf 30+ where debug-string output is - intentionally not parseable (for example ``goo.gle/debugonly`` prefixes). This change can - be reverted by setting runtime guard - ``envoy.reloadable_features.cel_message_serialize_text_format`` to ``false``. -- area: ext_proc - change: | - Fixed a bug where unnecessary empty data chunks are processed by the filter chain. This - change can be reverted by setting the runtime guard - ``envoy.reloadable_features.ext_proc_return_stop_iteration`` to ``false``. -- area: ratelimit - change: | - Fixed a bug in the gRPC rate limit client where the client could get into a bad state if the - callbacks were not properly released after a request completion, leading to potential use-after-free - issues. The fix ensures that callbacks and request references are cleared after completion, and adds - assertions to enforce correct usage patterns. -- area: ext_authz - change: | - Fixed a bug where headers from a denied authorization response (non-``200``) were not properly propagated - to the client. -- area: formatter - change: | - Added support for the ``UPSTREAM_LOCAL_CLOSE_REASON`` log formatter. -- area: formatter - change: | - Fixed the log formatter in HTTP router upstream logs by correctly setting the downstream connection's - ``ConnectionInfoProvider`` in the ``StreamInfo``. -- area: spiffe - change: | - Reduced the number of file watches needed by :ref:`trust_bundles - ` when the validator - is used in multiple places. Added support for :ref:`watched_directory - ` to support Kubernetes environments that rely on - atomic symbolic file updates. Added content hashing during file watching to handle excessive watch notifications. -- area: odcds - change: | - Fixed a crash (SIGABRT) when destroying OdCDS handles on worker threads. The handle no longer holds - a direct reference to the subscription, preventing thread-safety issues during destruction. The - subscription now persists in ``ClusterManagerImpl`` and is looked up by a config source key. -- area: ext_authz - change: | - Fixed the HTTP ext_authz client to respect ``status_on_error`` configuration when the authorization - server returns a 5xx error or when HTTP call failures occur. Previously, these error scenarios always - returned ``403 Forbidden`` regardless of the configured error status. -- area: release - change: | - Published contrib binaries now include the ``-contrib`` suffix in their version string. -- area: header_mutation - change: | - Fixed an issue where query parameter values added via ``query_parameter_mutations`` were not URL-encoded, - allowing query parameter injection attacks. Values from formatters like ``%REQ(header)%`` are now properly - URL-encoded when added to the query string. This behavior is controlled by the runtime guard - ``envoy.reloadable_features.header_mutation_url_encode_query_params``. -- area: mcp_router - change: | - Fixed MCP router to support session-less backends that do not return ``mcp-session-id`` - headers. Previously, this caused a spurious ``500`` error. -- area: health_check - change: | - Fixed a race condition where active health checks could start before required upstream TLS SDS secrets - were fetched, causing intermittent health check failures `#43116 `_. - This fix can be disabled by setting runtime guard ``envoy.reloadable_features.health_check_after_cluster_warming`` - to ``false``. -- area: upstream - change: | - Fixed an out-of-bounds issue in ``ThreadAwareLoadBalancerBase`` that could occur during mid-batch EDS host updates - due to eagerly calling ``refresh()`` before the deferred priority state resize. -- area: upstream - change: | - Fixed ``ProdClusterInfoFactory`` of HDS to honor ``alt_stat_name`` when creating cluster stats scopes, so - stats emitted through that path now use the alternate cluster name consistently with other upstream - cluster implementations. -- area: access_log - change: | - Fixed a crash on listener removal with a process-level access log rate limiter - :ref:`ProcessRateLimitFilter `. -- area: json - change: | - Fixed an off-by-one write in ``JsonEscaper::escapeString()`` that could corrupt the string null terminator - when the input string ends with a control character. -- area: network - change: | - Fixed a crash in ``Utility::getAddressWithPort`` when called with a scoped IPv6 address (e.g., ``fe80::1%eth0``). -- area: rbac - change: | - Fixed RBAC header matcher to validate each header value individually instead of concatenating multiple header values - into a single string. This prevents potential bypasses when requests contain multiple values for the same header. - The new behavior is enabled by the runtime guard ``envoy.reloadable_features.rbac_match_headers_individually``. -- area: contrib - change: | - Fixed a segfault from a timer thread-safety violation, a ring buffer overflow, and incorrect alpha - calculation in the ``peak_ewma`` load balancer. -- area: http - change: | - Fixed an issue where filter chain execution could continue on HTTP streams that had been reset but not yet - destroyed. This could cause use-after-free conditions when filter callbacks were invoked on filters that - had already received ``onDestroy()``. The fix ensures that ``decodeHeaders()``, ``decodeData()``, - ``decodeTrailers()``, and ``decodeMetadata()`` are blocked after a downstream reset. -- area: dynamic_modules - change: | - Fixed a bug where dynamic module filter may result in an incomplete body being sent to upstream - or downstream when some filters before or after the dynamic module filter in the chain - buffered the body and the dynamic module filter did not. -- area: io_socket - change: | - Fixed an issue where a userspace IO socket could fail to drain the write buffer of a connection - when the peer had closed for reads, causing the connection to remain open indefinitely. -- area: config_validation - change: | - Fixed ``--mode validate`` to create bootstrap extensions during config validation. Previously, - bootstrap extensions were completely skipped in validation mode, which meant their configs were - not validated and any providers or singletons they register were unavailable to downstream config - elements that depend on them. -- area: http2 - change: | - Apply nghttp2 CVE-2026-27135 patch. -- area: shared_pool - change: | - Fixed a leak in ``ObjectSharedPool`` when the last reference to a pooled object is released - on a non-main thread after the main dispatcher has started teardown. The queued cross-thread - deletion now carries ownership, so the object is freed whether or not the dispatcher runs - the queued callback. Most visibly this fixes a LeakSanitizer leak in - ``StrictDnsClusterImpl`` involving the ``Locality`` shared pool during server shutdown. +# *Changes expected to improve the state of the world and are unlikely to have negative effects* removed_config_or_runtime: -- area: oauth2 - change: | - Removed the runtime guard ``envoy.reloadable_features.oauth2_encrypt_tokens``. Token cookies (access, - ID, refresh) remain encrypted by default; use the ``disable_token_encryption`` filter configuration - to turn encryption off if needed. -- area: tcp_proxy - change: | - Removed runtime guard ``envoy.reloadable_features.tcp_proxy_set_idle_timer_immediately_on_new_connection`` - and legacy code path. +# *Normally occurs at the end of the* :ref:`deprecation period ` new_features: -- area: ext_authz - change: | - Added :ref:`shadow_mode - ` to the HTTP - ext_authz filter. When enabled, the filter calls the authorization service as normal but never - terminates the request. The authorization decision is written to FilterState as a - :ref:`ShadowDecision ` - under the filter's configured name with a ``.shadow`` suffix - (``envoy.filters.http.ext_authz.shadow`` by default) so that a subsequent filter can read - and optionally enforce it. -- area: lua - change: | - Added stats API support to the :ref:`Lua filter `, allowing Lua - scripts to create and increment counters, gauges, and histograms via - ``handle:streamInfo():stats()``. -- area: dynamic_modules - change: | - Added ``envoy_dynamic_module_callback_is_validation_mode`` ABI callback that allows dynamic - modules to check if the server is running in config validation mode. -- area: ratelimit - change: | - Added ``is_negative_hits`` boolean to the ``hits_addend`` - configuration in the rate limit API. This allows rate limit - requests to add to the rate limit budget (refill previously - consumed tokens) via - :ref:`HitsAddend.is_negative_hits - `, - or per descriptor in - :ref:`RateLimitDescriptor.is_negative_hits - `. -- area: redis - change: | - Added zone-aware routing support for Redis Cluster proxy. New read policies - ``LOCAL_ZONE_AFFINITY`` and ``LOCAL_ZONE_AFFINITY_REPLICAS_AND_PRIMARY`` route read requests - to replicas in the same availability zone. Zone discovery is enabled via - :ref:`enable_zone_discovery - `. - Note: This feature currently works with Valkey only. -- area: overload_manager - change: | - Added :ref:`ShrinkHeapConfig ` typed - configuration for the ``envoy.overload_actions.shrink_heap`` overload action. This allows - operators to configure the timer interval (``timer_interval``, minimum ``1s``, default ``10s``) and - the memory release threshold (``max_unfreed_memory_bytes``, default ``100MB``) passed to - ``tcmalloc::MallocExtension::ReleaseMemoryToSystem()``. -- area: dns_resolver - change: | - Added :ref:`HickoryDnsResolverConfig - `, a new DNS - resolver using the `Hickory DNS `_ library. -- area: dynamic_modules - change: | - Added :ref:`tracer ` - support for dynamic modules, enabling custom distributed tracing backends to be implemented - in dynamic modules. -- area: dynamic_modules - change: | - Rust SDK: a single dynamic module can now host multiple access loggers via the new - ``access_logger:`` arm of ``declare_all_init_functions!``, which registers a factory that - dispatches to different ``AccessLoggerConfig`` implementations by ``logger_name``. The - legacy ``declare_access_logger!`` macro is preserved as a single-config shim. -- area: dynamic_modules - change: | - Added upstream HTTP TCP bridge extension for dynamic modules. This enables modules to transform - HTTP requests into raw TCP data for upstream connections and convert TCP responses back into HTTP - responses via explicit send callbacks. See :ref:`envoy.upstreams.http.dynamic_modules - `. -- area: filters - change: | - Added filters to update the filter state in :ref:`a listener filter `. -- area: tls - change: | - Added a per-connection filter state object to select a workload trust domain in the SPIFFE validator in - multi-tenant deployments. -- area: tls - change: | - Extended TLS certificate compression (RFC 8879): added brotli to QUIC (which already supported zlib), - and added brotli and zlib to TCP TLS. Controlled by runtime flag - ``envoy.reloadable_features.tls_certificate_compression_brotli`` (defaults to ``true``). - When disabled, QUIC retains zlib-only compression, while TCP TLS has no compression. -- area: tls - change: | - Exposed the SHA-256 fingerprint and serial number of the verified issuer (CA) certificate from - the validated downstream mTLS peer certificate chain. Accessible via the - ``%DOWNSTREAM_PEER_ISSUER_FINGERPRINT_256%`` and ``%DOWNSTREAM_PEER_ISSUER_SERIAL%`` - :ref:`access log formatters `, and via the - ``sha256PeerCertificateIssuerDigest()`` and ``serialNumberPeerCertificateIssuer()`` methods on - :ref:`downstreamSslConnection() ` in the Lua filter. -- area: dynamic_modules - change: | - Added custom metrics (counters, gauges, histograms) support to load balancer dynamic modules. - Modules can now define metrics during configuration and record them during host selection. -- area: dynamic_modules - change: | - Rust SDK now provides an opt-in ``CatchUnwind`` wrapper for filter callbacks. When a - wrapped callback panics, Envoy logs the panic and returns a fail-closed error (e.g. - HTTP ``500``, stream reset, connection close) instead of aborting the process. -- area: http_11_proxy - change: | - Added ability to configure a default proxy address that is used when the proxy address is not - configured via metadata. -- area: compressor - change: | - Added :ref:`weaken_etag_on_compress - ` - to the :ref:`compressor filter `. When enabled in - ``response_direction_config``, strong ``ETag`` response headers are weakened (``W/`` prefix) - instead of removed when compression is applied, allowing caches and conditional requests to - work while indicating the body was modified by compression. When both ``weaken_etag_on_compress`` - and ``disable_on_etag_header`` are ``true``, the new field takes precedence. -- area: golang - change: | - Added ``DownstreamSslConnection()`` method to the Golang HTTP filter's ``StreamInfo`` interface, - providing access to SSL/TLS connection information for the downstream connection. This includes - peer certificate details (subject, issuer, serial number, SANs, validity), TLS version, cipher - suite, and PEM-encoded certificates. This achieves feature parity with the Lua filter's - ``downstreamSslConnection()`` functionality. -- area: outlier_detection - change: | - Added :ref:`detect_degraded_hosts ` - to enable passive degraded host detection. When enabled, outlier detection marks hosts as degraded when they return - the ``x-envoy-degraded`` header. Degraded hosts are deprioritized in load balancing but remain in rotation (not - ejected). The degraded state is cleared using the same backoff algorithm as ejection. Defaults to ``false``. -- area: http2 - change: | - Added :ref:`max_header_field_size_kb - ` to configure the - maximum wire-encoded size in KiB of an individual HPACK-encoded header field that the HTTP/2 - codec will accept. This allows increasing the default nghttp2 per-header limit of 64 KiB on the - wire when larger single headers need to be supported. -- area: dynamic_modules - change: | - Added dynamic module input matcher extension that allows implementing custom matching logic - in external languages (Rust, Go, C) via dynamic modules. -- area: dynamic_modules - change: | - Added listener lifecycle event callbacks to the bootstrap dynamic module extension. Modules can - opt in via ``enable_listener_lifecycle`` to receive ``on_listener_add_or_update`` and - ``on_listener_removal`` notifications when listeners change in the ``ListenerManager``. -- area: memory - change: | - Added ``soft_memory_limit_bytes``, ``max_per_cpu_cache_size_bytes``, and ``max_unfreed_memory_bytes`` - fields to :ref:`MemoryAllocatorManager ` - for fine-grained control of tcmalloc memory management. -- area: dynamic_modules - change: | - Added :ref:`TLS certificate validator - ` - support for dynamic modules, enabling custom TLS certificate validation to be implemented in dynamic modules. -- area: dynamic_modules - change: | - Added filter state read/write support for dynamic module cert validators, allowing modules to set and - get string values in the connection's filter state during certificate chain verification. -- area: dynamic_modules - change: | - Added ``write_to_socket`` and ``close_socket`` ABI callbacks for the dynamic module listener - filter, enabling protocol negotiation use cases such as Postgres SSL and MySQL handshake at the listener - filter level. -- area: dynamic_modules - change: | - Added HTTP callout support for dynamic module listener filters, enabling listener filters to initiate - asynchronous HTTP requests to upstream clusters and receive responses via the ``send_http_callout`` ABI - callback and ``on_listener_filter_http_callout_done`` event hook. -- area: ext_authz - change: | - Added :ref:`path_override ` - to the HTTP ext_authz filter. When set, the request path sent to the authorization service is replaced - entirely by this value. Only one of ``path_prefix`` or ``path_override`` may be set; validation fails - at config load if both are specified. -- area: stats - change: | - Added support to limit the number of metrics stored in each scope within the stats library. -- area: stats - change: | - The admin prometheus stats endpoint now supports the protobuf exposition format, and will automatically - use it if the request contains the correct Accept header, or if query parameter ``prom_protobuf=1`` is - set. In a prometheus scrape configuration, add ``PrometheusProto`` to ``scrape_protocols`` to use - the protobuf format. Additionally, when using the protobuf exposition format, the admin prometheus stats - endpoint now supports `native histograms `_ - when using the prometheus protobuf exposition format, using query - ``/stats/prometheus?histogram_buckets=prometheusnative``. -- area: stats - change: | - Added support for cluster-level stats matcher, allowing more granular control over which stats - are enabled and reported at the cluster level. The stats matcher could be configured via - the xDS API dynamically on a per-cluster basis. - See :ref:`envoy.stats_matcher ` for more details. -- area: stats - change: | - Added support for listener-level stats matcher via ``typed_filter_metadata`` key - ``envoy.stats_matcher`` in the listener's - :ref:`metadata `. When present, - the provided :ref:`StatsMatcher ` replaces - the global stats matcher for that listener's stats scope, allowing fine-grained control over - which listener stats are created. - If the value of ``envoy.stats_matcher`` is set but does not contain a valid ``StatsMatcher`` - message, this will be treated as an invalid configuration and the listener will fail to start. - This strict validation can be disabled temporarily by setting the runtime guard - ``envoy.reloadable_features.strict_stats_matcher_unpacked`` to ``false``. -- area: tls - change: | - Added support for fetching certificates on-demand via SDS in the upstream TLS transport socket - using the extension :ref:`on-demand certificate selector - `. -- area: access_log - change: | - Added stats customization support for the :ref:`access logger `. -- area: dynamic_modules - change: | - Introduced the extended ABI forward compatibility mechanism for dynamic modules - where modules built with an SDK version can be loaded by Envoy - binaries of the next Envoy version. For example, a module built with the v1.38 SDK - can now be loaded by an Envoy binary of v1.39. -- area: dynamic_modules - change: | - Added drain and shutdown lifecycle hooks for bootstrap dynamic modules. -- area: dynamic_modules - change: | - Added support for dynamic modules authors to register any combination of HTTP, network, listener, - UDP listener, and bootstrap filters in the Rust SDK. -- area: dynamic_modules - change: | - Added connection state and flow control ABI callbacks for the dynamic module network filter, - including ``read_disable``, ``read_enabled``, ``get_connection_state``, ``enable_half_close``, - ``is_half_close_enabled``, ``get_buffer_limit``, ``set_buffer_limits``, and - ``above_high_watermark``. -- area: dynamic_modules - change: | - Added socket property getter and SSL/TLS information ABI callbacks for the dynamic module listener - filter, including ``get_requested_server_name``, ``get_detected_transport_protocol``, - ``get_requested_application_protocols``, ``get_ja3_hash``, ``get_ja4_hash``, ``is_ssl``, - ``get_ssl_uri_sans``, ``get_ssl_dns_sans``, and ``get_ssl_subject``. -- area: mcp_router - change: | - Added support for MCP resource methods ``resources/list``, ``resources/read``, - ``resources/subscribe``, and ``resources/unsubscribe``. -- area: mcp_router - change: | - Added support for MCP prompt methods ``prompts/list`` and ``prompts/get``. -- area: matching - change: | - Added an optional ``field`` parameter to - :ref:`FilterStateInput `. - When set, ``FilterStateInput`` calls ``getField()`` on the filter state object instead of - ``serializeAsString()``, enabling direct matching on individual fields within composite filter - state objects such as proxy protocol TLVs stored via ``tlv_location: FILTER_STATE``. -- area: ratelimit - change: | - Added per-descriptor ``x-ratelimit-*`` headers support. See the - :ref:`x_ratelimit_option ` - field documentation for more details. -- area: ratelimit - change: | - Added ``RemoteAddressMatch`` action to the rate limit filter. This action will generate a descriptor based on the remote address of the - downstream connection by matching it against specified CIDR ranges with support for inversion and formatter substitution. -- area: mcp_router - change: | - Added support for MCP client-to-server notification methods ``notifications/cancelled`` - and ``notifications/roots/list_changed``. -- area: dynamic_modules - change: | - Added typed filter state support for dynamic module HTTP and network filters. This allows modules - to set and get filter state objects using registered ``StreamInfo::FilterState::ObjectFactory`` - instances, enabling interoperability with built-in Envoy filters that expect specific typed objects. -- area: mcp_router - change: | - Added support for MCP completion method ``completion/complete`` with routing based on - ``ref/prompt`` or ``ref/resource``. -- area: mcp_router - change: | - Added support for MCP logging method ``logging/setLevel``. -- area: upstream - change: | - Coalesced load balancer rebuilds during EDS batch host updates. When multiple priorities change in a - single batch, each LB level (LoadBalancerBase, ZoneAwareLoadBalancerBase, EdfLoadBalancerBase, - ThreadAwareLoadBalancerBase) now defers expensive per-priority recalculations to a single pass after - the batch completes, reducing CPU spikes on large clusters. This behavior can be reverted by setting - the runtime guard ``envoy.reloadable_features.coalesce_lb_rebuilds_on_batch_update`` to ``false``. -- area: cel - change: | - Added functionality to reevaluate CEL expressions that attempt to read response path data on the - request path once the data is available. Allows CEL matching based on both request and response - headers. This may cause a behavior change for matchers that previously would silently fail to - match due to attempting to match response headers in the request path. This behavior can be - reverted by setting the runtime guard - ``envoy.reloadable_features.enable_cel_response_path_matching`` to ``false``. -- area: access_log - change: | - Added support for gauges in the :ref:`stats access logger `. -- area: network - change: | - Added access logging support for network filters, similar to HTTP filters, by allowing network filters to - register as access logger instances. -- area: formatter - change: | - Extended ``*_WITHOUT_PORT`` address formatters to accept an optional ``MASK_PREFIX_LEN`` parameter - that masks IP addresses and returns them in CIDR notation (e.g., ``%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(16)%`` - returns ``10.1.0.0/16`` for client IP ``10.1.10.23``). -- area: dynamic_modules - change: | - Added :ref:`cluster - ` - support for dynamic modules, enabling custom service discovery and host management to be implemented - in dynamic modules. -- area: dynamic_modules - change: | - Added server lifecycle callbacks (``on_server_initialized``, ``on_drain_started``, ``on_shutdown``) - for dynamic module custom clusters, enabling modules to react to server readiness, drain, and - shutdown events. -- area: dynamic_modules - change: | - Changed the ``details`` parameter in ``cluster_lb_async_host_selection_complete`` ABI callback - from raw ``const char*`` and ``size_t`` to ``envoy_dynamic_module_type_module_buffer`` for - consistency with the ABI style guide. -- area: stat_sinks - change: | - Added support for exporting OpenTelemetry metrics via HTTP. The new ``http_service`` field - in :ref:`SinkConfig ` - enables direct OTLP metrics export to backends that only accept HTTP (Dynatrace, Datadog, Elastic), - without requiring an intermediate collector sidecar. -- area: ratelimit - change: | - Added support for shadow mode in the local rate limit filter. -- area: config - change: | - Added support for :ref:`set_node_on_first_message_only - ` to Delta-xDS. - Guarded by runtime flag ``envoy.reloadable_features.xds_legacy_delta_skip_subsequent_node``. -- area: formatter - change: | - Added the new access log formatter ``DOWNSTREAM_LOCAL_CLOSE_REASON``. -- area: formatter - change: | - Added ``%UPSTREAM_DETECTED_CLOSE_TYPE%`` and ``%DOWNSTREAM_DETECTED_CLOSE_TYPE%`` to expose the detected - close type of downstream and upstream connections. The possible values are ``Normal``, ``LocalReset``, - and ``RemoteReset``. -- area: formatter - change: | - Added extensions for :ref:`%FILE_CONTENT(/path/to/file)% ` - and :ref:`%SECRET(name)% `. -- area: http_service - change: | - Added the ability for :ref:`request_headers_to_add ` - to use a formatter extension that can retrieve secrets, for including authentication tokens. This support is added for - all uses of this message, including the open telemetry, ``ext_proc``, and ``zipkin`` tracers. -- area: reverse_tunnel - change: | - Added optional tenant isolation support to the reverse tunnel network filter. When - ``enable_tenant_isolation`` is set, Envoy scopes cached reverse tunnel sockets with composite - ``@`` and ``@`` identifiers and rejects handshake headers that already - contain the ``@`` delimiter to prevent ambiguous lookups. -- area: tcp_proxy - change: | - Added an option to emit a log entry when the connection is accepted. -- area: mcp_router - change: | - Added SSE (Server-Sent Events) streaming support for MCP backend responses. The router now handles - SSE responses from backends for ``tools/call`` with direct pass-through streaming, and supports SSE - aggregation for fanout operations (``tools/list``, ``initialize``) with incremental event parsing. -- area: http - change: | - Added an optional :ref:`JSON format ` - for the :ref:`x-forwarded-client-cert ` - (XFCC) header. The format can be configured via :ref:`format - `. -- area: dynamic_modules - change: | - Added :ref:`load balancing policies - ` - support for dynamic modules, enabling custom load balancing algorithms to be implemented in dynamic modules. -- area: dynamic_modules - change: | - Added ``get_host_health_by_address`` ABI callback for dynamic module load balancers, providing O(1) - host health lookup by address string using the cross-priority host map. -- area: sse_parser - change: | - Extended the SSE parser utility to support all standard SSE fields: ``id``, ``event`` (as ``event_type``), - and ``retry``, in addition to the existing ``data`` field. The ``retry`` field is parsed as a ``uint32_t`` - and only accepts values consisting of ASCII digits per the SSE specification. -- area: http - change: | - Added :ref:`envoy.filters.http.sse_to_metadata ` filter for extracting - values from Server-Sent Events (SSE) streams and writing them to dynamic metadata. Useful for capturing - token usage metrics from LLM API responses. Supports pluggable content parsers for different SSE data formats. -- area: content_parsers - change: | - Added :ref:`envoy.content_parsers.json ` content parser for extracting - values from JSON content using JSON path selectors. Can be used by filters that need to parse structured - JSON data and extract specific fields into metadata. -- area: resource_monitors - change: | - Added cgroup v2 support to the CPU utilization resource monitor. The monitor now automatically - detects and selects between cgroup v1 and v2 at runtime by checking available cgroup files on - the system. This enables the resource monitor to work correctly in both cgroup v1 and v2 - environments without configuration changes. -- area: connection - change: | - Add support for closing connections when they stay above the buffer high watermark for a configured time. - This can be enabled by setting the :ref:`per_connection_buffer_high_watermark_timeout - ` - field on the listener and :ref:`per_connection_buffer_high_watermark_timeout - ` - field on the cluster. By default, the timeout is disabled. -- area: dynamic_modules - change: | - Added configurable :ref:`metrics_namespace - ` field - to ``DynamicModuleConfig``. This allows users to customize the prefix used for all metrics - created by dynamic modules. Metrics now appear with the standard ``envoy_`` prefix followed by - the namespace in prometheus output (e.g. ``envoy_myapp_requests_total``). The legacy behavior - (stripping the namespace prefix from prometheus output) can be restored by setting the runtime - guard ``envoy.reloadable_features.dynamic_modules_strip_custom_stat_prefix`` to ``true``. -- area: oauth2 - change: | - Added ``TLS_CLIENT_AUTH`` for the OAuth2 HTTP filter to support RFC 8705 mutual TLS client - authentication. In this mode ``token_secret`` is optional and ignored, and the token endpoint - cluster must be configured with mTLS. -- area: dynamic_modules - change: | - Added a process-wide function registry to the dynamic modules ABI. Modules can register - functions by name via ``envoy_dynamic_module_callback_register_function`` and other modules - can resolve them via ``envoy_dynamic_module_callback_get_function``, enabling zero-copy - cross-module interactions analogous to ``dlsym``. -- area: dynamic_modules - change: | - Added a process-wide shared data registry to the dynamic modules ABI. -- area: dynamic_modules - change: | - Added support for loading dynamic module binaries from local file paths via the new - :ref:`module ` - field in ``DynamicModuleConfig``. This allows specifying an absolute path to a ``.so`` file - via ``module.local.filename`` as an alternative to the name-based search path. -- area: dynamic_modules - change: | - Added support for fetching dynamic module binaries from remote HTTP sources via - ``module.remote`` in ``DynamicModuleConfig``. The module is downloaded asynchronously during - listener initialization with SHA256 verification, written to a temporary file, and loaded - via ``dlopen``. If the remote fetch fails, the filter is not installed and requests pass - through (fail-open). -- area: dynamic_modules - change: | - Added caching for remotely fetched dynamic modules. Since ``newDynamicModuleFromBytes`` - writes modules to a deterministic path based on SHA256, subsequent config updates - referencing the same SHA256 load from the existing file, avoiding redundant HTTP fetches. -- area: dynamic_modules - change: | - Added ``nack_on_cache_miss`` option for remote dynamic module sources. When enabled, - uncached remote modules cause configuration rejection (NACK) with a background fetch, - instead of blocking listener warming. This enables remote modules in ECDS and per-route - configurations where an init manager is not available. -- area: dynamic_modules - change: | - Network filter read and write buffers now persist after ``on_read``/``on_write`` callbacks, allowing - modules to access buffered data from ``on_scheduled`` and other callbacks. Added - ``envoy_dynamic_module_callback_network_filter_get_cluster_host_count`` to query cluster host counts - by name, enabling scale-to-zero and custom load balancing decisions in network filters. -- area: dynamic_modules - change: | - Added metrics definition and update support for bootstrap dynamic modules. -- area: dynamic_modules - change: | - Added timer API to the bootstrap extension dynamic modules ABI. -- area: dynamic_modules - change: | - Added admin handler API to the bootstrap extension dynamic modules ABI, enabling modules to - register custom admin HTTP endpoints. -- area: formatter - change: | - Added ``SPAN_ID`` :ref:`access log formatter ` to log the span ID of - the active (downstream) span for a request, complementing the existing ``TRACE_ID`` formatter. -- area: formatter - change: | - Added ``QUERY_PARAMS`` support for substitution formatter to log all query params. - They can either be logged in their original form or decoded. -- area: formatter - change: | - Added new access log formatters for tracking upstream hosts and connection IDs attempted during - request processing: ``%UPSTREAM_HOSTS_ATTEMPTED%``, ``%UPSTREAM_HOSTS_ATTEMPTED_WITHOUT_PORT%``, - ``%UPSTREAM_HOST_NAMES_ATTEMPTED%``, ``%UPSTREAM_HOST_NAMES_ATTEMPTED_WITHOUT_PORT%``, and - ``%UPSTREAM_CONNECTION_IDS_ATTEMPTED%``. These are useful for debugging retry behavior and - understanding which hosts were tried before a successful connection or final failure. -- area: dynamic_modules - change: | - Added init manager integration to the dynamic modules bootstrap extension ABI. An init target - is automatically registered for every bootstrap extension, blocking traffic until the module - signals readiness via ``signal_init_complete``. -- area: dynamic_modules - change: | - Added ``on_host_membership_update`` event hook and ``get_member_update_host_address`` callback - for dynamic module load balancers, enabling modules to receive notifications when hosts are - added or removed from the cluster and inspect the affected host addresses. -- area: geoip - change: | - Added ``asn_org`` field to :ref:`geo_field_keys - ` - to populate a header with the autonomous system organization name from the MaxMind ASN database. -- area: tracers - change: | - Added log events to spans created by the OpenTelemetry tracer. -- area: mcp_router - change: | - Added SSE response support for MCP ``resources/list`` fanout aggregation. -- area: mcp_router - change: | - Added support for MCP ``resources/templates/list`` method with fanout aggregation. -- area: tcp_proxy - change: | - Added :ref:`proxy_protocol_tlv_merge_policy - ` - to control how TLVs in ``proxy_protocol_tlvs`` are merged with existing PROXY protocol state. - Supports ``ADD_IF_ABSENT`` (default), ``OVERWRITE_BY_TYPE_IF_EXISTS_OR_ADD``, and - ``APPEND_IF_EXISTS_OR_ADD``. -- area: admin - change: | - Added ``filter`` query parameter support to the ``/clusters`` endpoint. The parameter accepts a RE2 - regular expression to filter clusters by name. Compatible with the ``format`` parameter for both - text and JSON output (e.g., ``/clusters?filter=service&format=json``). -- area: mcp_router - change: | - Added :ref:`statistics ` to the MCP router filter for - observability into request routing, fanout operations, and error conditions. -- area: mcp - change: | - Added HTTP DELETE session termination support to the MCP filter. DELETE requests with an - ``MCP-Session-Id`` header are now recognized as valid MCP traffic in ``REJECT_NO_MCP`` mode. -- area: a2a - change: | - Added parsing support for the A2A (Agent2Agent) protocol, enabling parsing of A2A JSON-RPC messages. -- area: mcp - change: | - Added options ``propagate_trace_context`` and ``propagate_baggage`` for extracting ``traceparent``, - ``tracestate``, and baggage from MCP parameters, respectively. -- area: mcp_json_rest_bridge - change: | - Added the MCP JSON REST Bridge HTTP filter configuration to transcode MCP JSON-RPC requests into - standard JSON-REST HTTP requests. -- area: http - change: | - Added :ref:`file_server http filter ` to allow responding with - file contents from the filesystem. -- area: mcp_router - change: | - Added SSE response support for MCP ``prompts/list`` fanout aggregation. -- area: mcp_json_rest_bridge - change: | - Added support for MCP session negotiation, including ``initialize`` and - ``notifications/initialized`` methods. This filter is currently a work-in-progress and not - recommended for production use. -- area: listener_manager - change: | - Added ``ListenerUpdateCallbacks`` interface to ``ListenerManager``, similar to the existing - ``ClusterUpdateCallbacks`` on ``ClusterManager``. -- area: matching - change: | - Added :ref:`local reply matcher input ` - to distinguish Envoy generated local replies from upstream responses. This matcher input returns ``true`` - for local replies and ``false`` for upstream responses, enabling ``custom_response`` filter policies to - selectively apply only to locally generated error responses. -- area: mcp_json_rest_bridge - change: | - Added support for MCP ``tools/call`` request transcoding. Support for ``tools/list`` and - ``tools/call`` response transcoding is planned. This filter is currently a work-in-progress and - not recommended for production use. -- area: ext_proc - change: | - Added :ref:`allow_content_length_header - ` to allow - the ext_proc filter to preserve the original ``Content-Length`` header or let ext_proc server modify it as needed. -- area: cluster - change: | - Added :ref:`MCP multi cluster ` extension that combines multiple MCP services - into a single one. This cluster type is primarily intended to support aggregation of multiple MCP services into one. -- area: tls - change: | - Enhanced TLS certificate validation failure messages for CRL-related errors in access logs. The - ``%DOWNSTREAM_TRANSPORT_FAILURE_REASON%`` and ``%UPSTREAM_TRANSPORT_FAILURE_REASON%`` access log - formatters now include the certificate's CRL Distribution Point (CRLDP) information when CRL - validation fails. For errors such as ``CRL for certificate was not provided``, ``CRL has expired``, ``CRL - is not yet valid``, or ``certificate revoked``, the error message now includes the certificate's - CRL distribution points (e.g., ``X509_verify_cert: certificate verification error at depth 0: certificate revocation - check against provided CRLs failed: unable to get certificate CRL, certificate CRL distribution points: - [http://crl.example.com/ca.crl, http://backup-crl.example.com/ca.crl]``). This provides better visibility into CRL - validation failures and helps operators identify connectivity or CRL server issues without requiring debug-level logging. -- area: lua - change: | - Added :ref:`set() ` to the Lua filter - state API, allowing Lua scripts to create and store filter state objects dynamically using - registered object factories. -- area: http - change: | - Fixed an issue where filter chain execution could continue on HTTP streams that had been reset but not yet - destroyed. This could cause use-after-free conditions when filter callbacks were invoked on filters that - had already received ``onDestroy()``. The fix ensures that ``decodeHeaders()``, ``decodeData()``, - ``decodeTrailers()``, and ``decodeMetadata()`` are blocked after a downstream reset. -- area: formatter - change: | - Added support for ``%TRACE_ID%`` in the custom response policy filter's local response policy to allow - trace ID to be substituted into the response body. -- area: redis - change: | - Added support for ``BITFIELD_RO`` in ``redis_proxy``. -- area: network_filter - change: | - Added support for - ``on_downstream_data`` (see - :ref:`envoy_v3_api_field_extensions.filters.network.set_filter_state.v3.Config.on_downstream_data`) - to the :ref:`set_filter_state network filter `, allowing - connection filter state to be populated after first receiving data from the downstream connection. -- area: oauth2 - change: | - Added :ref:`allow_failed_matcher ` - to allow requests to proceed to upstream as unauthenticated when OAuth validation fails (missing, invalid, or expired - credentials) and the request matches the configured matchers. This enables graceful degradation patterns where services - can handle both authenticated and unauthenticated requests. When triggered, all OAuth cookies are stripped and context - headers ``x-envoy-oauth-status: failed`` and ``x-envoy-oauth-failure-reason`` are added. The matcher evaluation follows - priority order: ``pass_through_matcher`` > ``allow_failed_matcher`` > ``deny_redirect_matcher`` > default OAuth behavior. -- area: mcp_json_rest_bridge - change: | - Added support for MCP ``tools/call`` response transcoding. Support for ``tools/list`` is planned. - This filter is currently a work-in-progress and not recommended for production use. -- area: http - change: | - Added support for clear route cache in the :ref:`set_filter_state http filter `. When - ``clear_route_cache`` is set, the filter will clear the route cache for the current request after applying filter state updates. - This is necessary if the route configuration may depend on the filter state values set. -- area: stateful_session - change: | - Added :ref:`status_on_strict_destination_not_found - ` - to the :ref:`stateful session filter `. When - :ref:`strict ` - mode is enabled and the requested destination is not found in the set of available endpoints, Envoy - will return the configured HTTP status code instead of the default ``503``. This does not apply when - the destination exists but is unhealthy. -- area: tcp_proxy - change: | - Propagate upstream TCP RST to downstream when detected close type is RemoteReset. - This behavioral change can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.propagate_upstream_rst_through_tunneled_tcp_proxy`` to ``false``. -- area: tcp_proxy - change: | - HttpUpstream now maps remote-originated HTTP stream reset reasons (RemoteReset, - RemoteRefusedStreamReset, RemoteConnectionFailure) to RemoteClose with - DetectedCloseType::RemoteReset, enabling RST propagation through tunneled tcp_proxy. - This behavioral change can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.map_http_stream_reset_to_tcp_rst`` to ``false``. -- area: ratelimit - change: | - Support populating rate limit descriptors from cluster metadata. -- area: ratelimit - change: | - Support populating rate limit descriptors from cluster locality metadata. -- area: http - change: | - Added a new overload action :ref:`envoy.overload_actions.close_idle_http_connections ` - that closes idle downstream HTTP connections when the overload action is active. Currently only HTTP/3 idle connections - trimming is supported, and HTTP/1 and HTTP/2 support are to be implemented. -- area: contrib - change: | - Added a new :ref:`Kafka stats sink ` contrib extension - (``envoy.stat_sinks.kafka``) that produces metrics directly to an Apache Kafka topic - using librdkafka. Supports both JSON and Protobuf serialization formats; the Protobuf - format uses the same ``StreamMetricsMessage`` wire format as the gRPC - :ref:`metrics_service ` sink, - allowing consumers to reuse existing deserializers. Configurable batching, delta counters, - tag-as-label emission, and full librdkafka producer tuning (including TLS and SASL - authentication) are supported via - :ref:`KafkaStatsSinkConfig `. -- area: tls - change: | - Added support for building Envoy with OpenSSL as an alternative to the default BoringSSL. - This is achieved through a BoringSSL compatibility layer (``bssl-compat``) that translates BoringSSL - API calls to OpenSSL, allowing Envoy's TLS code to remain unchanged. To build with OpenSSL, use - ``--config=openssl``. HTTP/3 (QUIC) is disabled for OpenSSL builds. Note that OpenSSL builds are - not currently covered by the `Envoy security policy `_. - See :repo:`bazel/SSL.md ` for details. -- area: dynamic_modules - change: | - Promoted the dynamic modules HTTP filter (``envoy.filters.http.dynamic_modules``) from alpha - to stable. -- area: attributes - change: | - Added ``connection.peer_certificate`` and ``upstream.peer_certificate`` attributes that provide the - PEM-encoded peer certificate for downstream and upstream TLS connections respectively. -- area: quic - change: | - Added SCONE (Standardized Communication with Network Elements) support for QUIC. This includes - a new :ref:`enable_scone ` - field in ``QuicProtocolOptions``, and support in Envoy Mobile via a new ``enableScone()`` API in - ``EngineBuilder``. SCONE bandwidth and timestamp data are now propagated to the application via - new ``scone_max_kbps`` and ``scone_timestamp_ms`` fields in the ``envoy_stream_intel`` struct. + +deprecated: diff --git a/changelogs/summary.md b/changelogs/summary.md index 105d52e03b25b..e69de29bb2d1d 100644 --- a/changelogs/summary.md +++ b/changelogs/summary.md @@ -1,102 +0,0 @@ -## Summary of changes - -## Breaking changes - -- **tcp_proxy**: `max_early_data_bytes` must be set explicitly for `upstream_connect_mode` values other than `IMMEDIATE`; missing configurations now fail validation at startup. -- **on_demand**: the on-demand filter no longer performs internal redirects after a successful CDS fetch, so earlier filters are not invoked twice (revertible via `envoy.reloadable_features.on_demand_cluster_no_recreate_stream`). -- **BoringSSL/FIPS**: the `--define=boringssl=fips` flag has been removed; use `--config=boringssl-fips`. -- **TLS**: `enforce_rsa_key_usage` now defaults to `true`; the option will be removed in the next release. -- **ext_proc**: the `processing_effect_lib` has moved from `extensions/filters/http/ext_proc` to `extensions/filters/common/processing_effect`. - -### Dynamic modules -- New extension points: tracers, TLS certificate validators, custom clusters, load balancing policies, input matchers, upstream HTTP-to-TCP bridge, and listener filters with HTTP callouts. -- Bootstrap extensions gained init-manager integration, drain/shutdown lifecycle hooks, listener-lifecycle callbacks, timer and admin-handler APIs, and metrics support. -- Network filter callbacks for flow-control and connection state (`read_disable`, watermarks, half-close, buffer limits, etc.) and persistent read/write buffers across callbacks. -- Listener-filter socket and TLS introspection (SNI, ALPN, JA3/JA4, SSL SANs/subject) plus `write_to_socket`/`close_socket` callbacks enabling Postgres SSL, MySQL, and similar protocol negotiation. -- Module loading from local file paths and remote HTTP sources (SHA256-verified, cached, with optional NACK-on-cache-miss). -- Process-wide function and shared-data registries for zero-copy cross-module interactions. -- Rust SDK: unified `declare_all_init_functions!` macro for registering any combination of HTTP/network/listener/UDP/bootstrap/access-logger filters, opt-in `CatchUnwind` panic wrapper, multi-logger support. -- Custom metrics on load balancers with configurable `metrics_namespace`, `get_host_health_by_address` fast path, host-membership update callbacks. -- ABI forward-compatibility: modules built against the v1.38 SDK can be loaded by a v1.39 Envoy binary. -- New `envoy_dynamic_module_callback_is_validation_mode` callback and typed filter-state support. - -### MCP (Model Context Protocol) and A2A -- MCP router: full method coverage — `resources/list|read|subscribe|unsubscribe`, `resources/templates/list`, `prompts/list|get`, `completion/complete`, `logging/setLevel`, plus `notifications/cancelled` and `notifications/roots/list_changed`. -- SSE streaming support: pass-through for `tools/call` and fan-out aggregation for `tools/list`, `initialize`, `resources/list`, and `prompts/list`. -- MCP filter: HTTP DELETE session termination, relaxed `application/json` Content-Type matching, optional `traceparent`/`tracestate`/baggage propagation from MCP parameters, statistics added to the MCP router, and default metadata namespace changed to `envoy.filters.http.mcp`. -- New **MCP JSON REST Bridge** HTTP filter (work-in-progress) transcoding JSON-RPC to REST, with `tools/call` request transcoding and session negotiation. -- Added parsing support for the **A2A (Agent2Agent)** JSON-RPC protocol. - -### HTTP, routing and protocol -- HTTP/2: new `max_header_field_size_kb` to raise the nghttp2 64 KiB per-header limit; applied the nghttp2 **CVE-2026-27135** patch. -- HTTP/1: optional strict chunked-encoding parsing behind a runtime guard. -- Optional **JSON format for the `x-forwarded-client-cert` (XFCC)** header. -- New `envoy.filters.http.sse_to_metadata` filter (extract SSE event values into dynamic metadata, useful for LLM token-usage metrics), with a pluggable `envoy.content_parsers.json` parser. -- New `envoy.filters.http.file_server` filter for serving files directly from disk. -- Refactored `route()`, `clusterInfo()`, and `virtualHost()` to return `OptRef`, with new `*SharedPtr()` companions. -- Happy Eyeballs now handles interleaving of non-IP addresses. - -### TLS, security and authorization -- TLS certificate compression (RFC 8879) extended: brotli added to QUIC, and both brotli and zlib added to TCP TLS. -- `enforce_rsa_key_usage` defaults to `true` on upstream TLS contexts; the option will be removed next release. -- On-demand upstream certificate fetching via SDS using the `envoy.tls.certificate_selectors.on_demand_secret` extension. -- Exposed verified issuer SHA-256 fingerprint and serial number via `%DOWNSTREAM_PEER_ISSUER_FINGERPRINT_256%` / `%DOWNSTREAM_PEER_ISSUER_SERIAL%` and corresponding Lua accessors. -- Per-connection SPIFFE trust-domain selection for multi-tenant deployments; reduced file-watch overhead and support for `watched_directory`. -- **ext_authz** — `shadow_mode` (decision written to filter state without terminating requests), `path_override`, honoring `status_on_error` on 5xx/HTTP-call failures, fix for propagating headers from denied responses. -- **OAuth2** — per-route configuration, `TLS_CLIENT_AUTH` (RFC 8705 mTLS client auth), `OauthExpires` cookie cleared on logout, `oauth2_encrypt_tokens` runtime guard removed (encryption now default, opt-out via `disable_token_encryption`). -- **RBAC** header matcher now validates each header value individually (guarded) to prevent concatenation-based bypasses. -- Query-parameter values added via `query_parameter_mutations` are now URL-encoded to prevent injection. -- **OpenSSL** can now be used as an alternative to the default BoringSSL (build with `--config=openssl` Bazel flag); HTTP/3 (QUIC) is disabled and OpenSSL builds are not covered by the Envoy security policy. - -### Observability -- New formatters: `SPAN_ID`, `QUERY_PARAMS`, `UPSTREAM_LOCAL_CLOSE_REASON`, `DOWNSTREAM_LOCAL_CLOSE_REASON`, `UPSTREAM_DETECTED_CLOSE_TYPE`, `DOWNSTREAM_DETECTED_CLOSE_TYPE`, `%UPSTREAM_HOSTS_ATTEMPTED%` and related attempt/connection-ID formatters, `%FILE_CONTENT(...)%`, `%SECRET(name)%`. -- `*_WITHOUT_PORT` address formatters accept an optional `MASK_PREFIX_LEN` to emit CIDR-masked addresses. -- Prometheus admin endpoint supports the **protobuf exposition format** and **Prometheus native histograms**. -- Cluster-level and listener-level stats matchers, plus stats-scope metric-count limits. -- OpenTelemetry stat sink can now export metrics over **HTTP** (OTLP/HTTP) without a collector sidecar. -- Access loggers: stats customization and gauge support in the stats access logger; network filters can register as access loggers; new `asn_org` geoip field; log events on OpenTelemetry spans. - -### Routing, load balancing and upstream -- Coalesced load-balancer rebuilds during EDS batch host updates — significant CPU-spike reduction on large clusters. -- Passive degraded-host detection (`detect_degraded_hosts`) via the `x-envoy-degraded` response header. -- Redis Cluster zone-aware routing (`LOCAL_ZONE_AFFINITY` / `LOCAL_ZONE_AFFINITY_REPLICAS_AND_PRIMARY`, Valkey only). -- New `upstream_rq_active_overflow` counter distinguishing active-RQ saturation from pending-queue saturation. -- ODCDS over ADS fix for tcp_proxy; SRDS late-listener init fix; drop_overload now uses cached EDS. -- EDS metadata comparison uses a cached hash for O(1) per-host comparison. -- ORCA weight manager prefers named metrics over application utilization by default. - -### Rate limiting -- `is_negative_hits` on `hits_addend` to refund tokens to the budget. -- New `RemoteAddressMatch` rate-limit action (CIDR-based, with inversion and formatter substitution). -- Per-descriptor `x-ratelimit-*` response headers and shadow mode in the local rate limit filter. -- `timeout: 0s` in HTTP ext_authz and HTTP rate-limit filters now means "no timeout", aligning with other Envoy timeouts. - -### Memory, resource and connection management -- Replaced the custom timer-based tcmalloc release with tcmalloc's native `ProcessBackgroundActions` / `SetBackgroundReleaseRate`. -- New `MemoryAllocatorManager` fields (`soft_memory_limit_bytes`, `max_per_cpu_cache_size_bytes`, `max_unfreed_memory_bytes`). -- Typed `ShrinkHeapConfig` for the `shrink_heap` overload action. -- **cgroup v2** support in the CPU utilization resource monitor, with automatic v1/v2 detection. -- New `per_connection_buffer_high_watermark_timeout` on listeners and clusters to close connections stuck above the watermark. -- Fixed a resource leak in global connection-limit tracking under load shedding. - -### xDS and configuration -- `set_node_on_first_message_only` now supported in Delta-xDS. -- Delta-xDS failover fix for `initial_resource_versions` on reconnect. -- `--mode validate` now creates bootstrap extensions, actually validating their configs. -- CEL expressions that attempt to read response-path data on the request path are automatically re-evaluated when the data becomes available. -- New `HttpResponseLocalReplyMatchInput` matcher input to distinguish local replies from upstream responses. -- New `HickoryDnsResolverConfig` — DNS resolver built on Hickory DNS. - -### TCP proxy and PROXY protocol -- New `proxy_protocol_tlv_merge_policy` (`ADD_IF_ABSENT`, `OVERWRITE_BY_TYPE_IF_EXISTS_OR_ADD`, `APPEND_IF_EXISTS_OR_ADD`). -- Option to emit an access-log entry when a connection is accepted. -- `max_early_data_bytes` is now **required** when using non-`IMMEDIATE` `upstream_connect_mode`. - -### Other notable changes and fixes -- Router returns `DEADLINE_EXCEEDED` (instead of `UNAVAILABLE`) on router-enforced gRPC timeouts (opt-in). -- Hot restart fixed for listeners with a network-namespace address. -- HTTP/3 client pool fix for early-data requests with async certificate validation. -- Fixes for HTTP/1 zombie-stream FD leaks, internal-redirect hang on buffer overflow, keep-alive header preservation, reset-stream filter-chain safety, idle-timer-before-connected behaviour, and a worker-thread watchdog configuration bug. -- Several ext_proc fixes: two ext_procs in the same chain, CEL message text-format serialization, empty-data-chunk handling. -- Geoip HTTP filter promoted to **stable**. -- Published contrib binaries now carry a `-contrib` version suffix. From f93c22322b88ff2a3884beed005e6306616f89bd Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Fri, 24 Apr 2026 03:13:42 +0200 Subject: [PATCH 129/750] dynamic_modules: defer HTTP watermark callback registration until in-module filter exists (#44621) --- changelogs/current.yaml | 5 + .../filters/http/dynamic_modules/filter.cc | 20 +++- .../filters/http/dynamic_modules/filter.h | 18 +++- .../dynamic_modules/http/abi_impl_test.cc | 93 ++++++++++++++++++- 4 files changed, 126 insertions(+), 10 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ecf0d6e48ce5..84bf69e3abd2c 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,11 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: dynamic_modules + change: | + Fixed a crashing bug in the HTTP filter when a stream was already above the downstream write-buffer + high watermark at filter-chain construction time. Downstream watermark callback registration is + now deferred until the in-module filter has been constructed. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/filters/http/dynamic_modules/filter.cc b/source/extensions/filters/http/dynamic_modules/filter.cc index 6dbcfd323b7e5..5d466d1ac1302 100644 --- a/source/extensions/filters/http/dynamic_modules/filter.cc +++ b/source/extensions/filters/http/dynamic_modules/filter.cc @@ -13,7 +13,18 @@ namespace HttpFilters { DynamicModuleHttpFilter::~DynamicModuleHttpFilter() { destroy(); } void DynamicModuleHttpFilter::initializeInModuleFilter() { + ASSERT(in_module_filter_ == nullptr); in_module_filter_ = config_->on_http_filter_new_(config_->in_module_config_, thisAsVoidPtr()); + maybeRegisterDownstreamWatermarkCallbacks(); +} + +void DynamicModuleHttpFilter::maybeRegisterDownstreamWatermarkCallbacks() { + if (downstream_watermark_callbacks_registered_ || decoder_callbacks_ == nullptr || + in_module_filter_ == nullptr) { + return; + } + decoder_callbacks_->addDownstreamWatermarkCallbacks(*this); + downstream_watermark_callbacks_registered_ = true; } void DynamicModuleHttpFilter::onStreamComplete() { @@ -22,9 +33,11 @@ void DynamicModuleHttpFilter::onStreamComplete() { void DynamicModuleHttpFilter::onDestroy() { destroyed_ = true; - // Remove watermark callbacks before destroying. - if (decoder_callbacks_ != nullptr) { + // Pair with the register in maybeRegisterDownstreamWatermarkCallbacks(); the underlying + // removeDownstreamWatermarkCallbacks() asserts the callback was previously added. + if (decoder_callbacks_ != nullptr && downstream_watermark_callbacks_registered_) { decoder_callbacks_->removeDownstreamWatermarkCallbacks(*this); + downstream_watermark_callbacks_registered_ = false; } destroy(); }; @@ -63,6 +76,7 @@ void DynamicModuleHttpFilter::destroy() { decoder_callbacks_ = nullptr; encoder_callbacks_ = nullptr; + downstream_watermark_callbacks_registered_ = false; } FilterHeadersStatus DynamicModuleHttpFilter::decodeHeaders(RequestHeaderMap&, bool end_of_stream) { @@ -279,11 +293,13 @@ void DynamicModuleHttpFilter::continueEncoding() { } void DynamicModuleHttpFilter::onAboveWriteBufferHighWatermark() { + ASSERT(in_module_filter_ != nullptr); config_->on_http_filter_downstream_above_write_buffer_high_watermark_(thisAsVoidPtr(), in_module_filter_); } void DynamicModuleHttpFilter::onBelowWriteBufferLowWatermark() { + ASSERT(in_module_filter_ != nullptr); config_->on_http_filter_downstream_below_write_buffer_low_watermark_(thisAsVoidPtr(), in_module_filter_); } diff --git a/source/extensions/filters/http/dynamic_modules/filter.h b/source/extensions/filters/http/dynamic_modules/filter.h index 154f5ca60010e..236c80a829c0d 100644 --- a/source/extensions/filters/http/dynamic_modules/filter.h +++ b/source/extensions/filters/http/dynamic_modules/filter.h @@ -42,9 +42,10 @@ class DynamicModuleHttpFilter : public Http::StreamFilter, FilterMetadataStatus decodeMetadata(MetadataMap&) override; void setDecoderFilterCallbacks(StreamDecoderFilterCallbacks& callbacks) override { decoder_callbacks_ = &callbacks; - // We always register for downstream watermark callbacks. This allows all filters - // including the terminal filter to receive flow control events. - decoder_callbacks_->addDownstreamWatermarkCallbacks(*this); + // Registration is deferred until the in-module filter exists: the factory wires callbacks + // before initializeInModuleFilter(), and addDownstreamWatermarkCallbacks() synchronously + // replays any pending onAboveWriteBufferHighWatermark() into the newly registered callback. + maybeRegisterDownstreamWatermarkCallbacks(); } void decodeComplete() override; @@ -242,6 +243,13 @@ class DynamicModuleHttpFilter : public Http::StreamFilter, */ void destroy(); + /** + * Registers this filter for downstream watermark callbacks once both decoder callbacks have been + * set and the in-module filter has been constructed. Idempotent; safe to call from either + * entry point regardless of which runs first. + */ + void maybeRegisterDownstreamWatermarkCallbacks(); + // True if the filter is in the continue state. This is to avoid prohibited calls to // continueDecoding() or continueEncoding() multiple times. bool in_continue_ = false; @@ -257,6 +265,10 @@ class DynamicModuleHttpFilter : public Http::StreamFilter, envoy_dynamic_module_type_http_filter_module_ptr in_module_filter_ = nullptr; Stats::StatNameDynamicPool stat_name_pool_; uint32_t worker_index_; + // Tracks whether addDownstreamWatermarkCallbacks() has been invoked on decoder_callbacks_. + // Also gates the paired remove in onDestroy(), because removeDownstreamWatermarkCallbacks() + // asserts that the callback was previously added. + bool downstream_watermark_callbacks_registered_ = false; /** * This implementation of the AsyncClient::Callbacks is used to handle the response from the HTTP diff --git a/test/extensions/dynamic_modules/http/abi_impl_test.cc b/test/extensions/dynamic_modules/http/abi_impl_test.cc index 675600a2ac000..9dd29a93a9afa 100644 --- a/test/extensions/dynamic_modules/http/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/http/abi_impl_test.cc @@ -2984,20 +2984,103 @@ TEST_F(DynamicModuleHttpFilterTest, SetBufferLimitNoCallbacks) { envoy_dynamic_module_callback_http_set_buffer_limit(filter_no_callbacks.get(), 2048); } -TEST_F(DynamicModuleHttpFilterTest, WatermarkCallbacksAutoRegisteredAndCleanedUp) { - // Create a new filter with callbacks. - auto filter = std::make_unique(nullptr, symbol_table_, 3); +// Lifecycle tests that exercise the ordering of setDecoderFilterCallbacks() relative to +// initializeInModuleFilter(). Each test body constructs and sequences the filter itself so it +// can pick the ordering under test. +class DynamicModuleHttpFilterLifecycleTest : public testing::Test { +public: + void SetUp() override { + auto dynamic_module = newDynamicModule(testSharedObjectPath("no_op", "c"), false); + ASSERT_TRUE(dynamic_module.ok()) << dynamic_module.status().message(); + auto filter_config_or_status = newDynamicModuleHttpFilterConfig( + "test_filter", "", DefaultMetricsNamespace, false, std::move(dynamic_module.value()), + *stats_scope_, context_); + ASSERT_TRUE(filter_config_or_status.ok()) << filter_config_or_status.status().message(); + filter_config_ = filter_config_or_status.value(); + } + + Stats::SymbolTableImpl symbol_table_; + Stats::IsolatedStoreImpl stats_store_; + Stats::ScopeSharedPtr stats_scope_{stats_store_.createScope("")}; + NiceMock context_; + DynamicModuleHttpFilterConfigSharedPtr filter_config_; +}; + +// Production ordering: addStreamFilter() wires callbacks before initializeInModuleFilter() +// runs. Registration must land on init, not on the setter, and the paired remove must fire on +// destroy. +TEST_F(DynamicModuleHttpFilterLifecycleTest, WatermarkRegistersAtInitAndCleanedUpAtDestroy) { + auto filter = std::make_shared(filter_config_, symbol_table_, 0); + + NiceMock decoder_callbacks; + NiceMock encoder_callbacks; + + EXPECT_CALL(decoder_callbacks, addDownstreamWatermarkCallbacks(testing::_)).Times(0); + filter->setDecoderFilterCallbacks(decoder_callbacks); + filter->setEncoderFilterCallbacks(encoder_callbacks); + testing::Mock::VerifyAndClearExpectations(&decoder_callbacks); + + EXPECT_CALL(decoder_callbacks, addDownstreamWatermarkCallbacks(testing::Ref(*filter))); + filter->initializeInModuleFilter(); + testing::Mock::VerifyAndClearExpectations(&decoder_callbacks); + + EXPECT_CALL(decoder_callbacks, removeDownstreamWatermarkCallbacks(testing::Ref(*filter))); + filter->onDestroy(); +} + +// Inverted ordering used by callers that run initializeInModuleFilter() before +// setDecoderFilterCallbacks(). Registration must still land, this time on the setter side. +TEST_F(DynamicModuleHttpFilterLifecycleTest, WatermarkRegistersAtSetCallbacksWhenInitRanFirst) { + auto filter = std::make_shared(filter_config_, symbol_table_, 0); + + filter->initializeInModuleFilter(); - // Watermark callbacks should be automatically registered when decoder callbacks are set. NiceMock decoder_callbacks; EXPECT_CALL(decoder_callbacks, addDownstreamWatermarkCallbacks(testing::Ref(*filter))); filter->setDecoderFilterCallbacks(decoder_callbacks); + testing::Mock::VerifyAndClearExpectations(&decoder_callbacks); - // Destroy should clean up watermark callbacks. EXPECT_CALL(decoder_callbacks, removeDownstreamWatermarkCallbacks(testing::Ref(*filter))); filter->onDestroy(); } +// Regression for the production crash: the FilterManager synchronously replays +// onAboveWriteBufferHighWatermark() into the callback from inside +// addDownstreamWatermarkCallbacks(). The replay must only reach the filter after +// in_module_filter_ is set, so the ASSERT in onAboveWriteBufferHighWatermark holds. +TEST_F(DynamicModuleHttpFilterLifecycleTest, WatermarkSyncReplayDoesNotFireBeforeInit) { + auto filter = std::make_shared(filter_config_, symbol_table_, 0); + + NiceMock decoder_callbacks; + NiceMock encoder_callbacks; + + EXPECT_CALL(decoder_callbacks, addDownstreamWatermarkCallbacks(testing::_)).Times(0); + filter->setDecoderFilterCallbacks(decoder_callbacks); + filter->setEncoderFilterCallbacks(encoder_callbacks); + testing::Mock::VerifyAndClearExpectations(&decoder_callbacks); + + EXPECT_CALL(decoder_callbacks, addDownstreamWatermarkCallbacks(testing::Ref(*filter))) + .WillOnce(testing::Invoke( + [](Http::DownstreamWatermarkCallbacks& cb) { cb.onAboveWriteBufferHighWatermark(); })); + filter->initializeInModuleFilter(); + testing::Mock::VerifyAndClearExpectations(&decoder_callbacks); + + EXPECT_CALL(decoder_callbacks, removeDownstreamWatermarkCallbacks(testing::Ref(*filter))); + filter->onDestroy(); +} + +// onDestroy() must skip removeDownstreamWatermarkCallbacks() when registration never happened; +// the underlying remove asserts the callback was previously added. +TEST_F(DynamicModuleHttpFilterLifecycleTest, + OnDestroySkipsRemoveWhenDecoderCallbacksSetButInitNeverRan) { + auto filter = std::make_shared(filter_config_, symbol_table_, 0); + + NiceMock decoder_callbacks; + filter->setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_CALL(decoder_callbacks, removeDownstreamWatermarkCallbacks(testing::_)).Times(0); + filter->onDestroy(); +} + // ============================================================================= // Tests for stream control callbacks. // ============================================================================= From 7dc1c03d9d054fabe2b009436d4faa2c11a98498 Mon Sep 17 00:00:00 2001 From: Alex Hansen Date: Thu, 23 Apr 2026 19:15:34 -0700 Subject: [PATCH 130/750] Support repeated string extraction in Proto Message Extraction filter (#44595) ## Summary Expands the functionality of the extraction library to support repeated string values, which currently is explicitly an error. The resulting dynamic metadata contains the extracted fields in a ListValue. ___ Commit Message: Support repeated string extraction in PME filter Additional Description: N/A Risk Level: low Testing: unit & integration filter tests Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A --------- Signed-off-by: Alex Hansen --- .../extraction_util/extraction_util.cc | 18 +++-- .../extractor_impl.cc | 8 +- .../extraction_util/extraction_util_test.cc | 12 +-- .../proto_message_extraction/filter_test.cc | 80 +++++++++++++++++++ .../integration_test.cc | 53 ++++++++++++ 5 files changed, 158 insertions(+), 13 deletions(-) diff --git a/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.cc b/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.cc index 324375635bdc9..6cffcc4067968 100644 --- a/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.cc +++ b/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.cc @@ -335,12 +335,20 @@ absl::StatusOr ExtractStringFieldValue( CodedInputStream* input_stream) -> absl::StatusOr { if (field->kind() != Field::TYPE_STRING) { return absl::InvalidArgumentError( - absl::Substitute("Field '$0' is not a singular string field.", field->name())); + absl::Substitute("Field '$0' is not a string field.", field->name())); } else if (field->cardinality() == Field::CARDINALITY_REPEATED) { - return absl::InvalidArgumentError( - absl::Substitute("Field '$0' is a repeated string field, only singular " - "string field is accepted.", - field->name())); + std::string result; + uint32_t tag = 0; + while ((tag = input_stream->ReadTag()) != 0) { + if (field->number() == WireFormatLite::GetTagFieldNumber(tag)) { + uint32_t length; + input_stream->ReadVarint32(&length); + input_stream->ReadString(&result, length); + } else { + WireFormatLite::SkipField(input_stream, tag); + } + } + return result; } else { // singular string field std::string result; if (FieldExtractor::SearchField(*field, input_stream)) { diff --git a/source/extensions/filters/http/proto_message_extraction/extractor_impl.cc b/source/extensions/filters/http/proto_message_extraction/extractor_impl.cc index c96d92bf98d3a..c3e5f2b98f7c5 100644 --- a/source/extensions/filters/http/proto_message_extraction/extractor_impl.cc +++ b/source/extensions/filters/http/proto_message_extraction/extractor_impl.cc @@ -82,7 +82,9 @@ ExtractedMessageDirective typeMapping(const MethodExtraction::ExtractDirective& absl::Status ExtractorImpl::init() { FieldValueExtractorFactory extractor_factory(type_finder_); for (const auto& it : method_extraction_.request_extraction_by_field()) { - // TODO(adh-goog): Allow repeated field extraction in field_value_extractor. + // Allow arbitrary types only for cardinality extraction. Otherwise, the extractor is + // constrained to supported types in the proto field extraction library: + // https://github.com/grpc-ecosystem/proto-field-extraction/blob/main/proto_field_extraction/field_value_extractor/field_value_extractor_factory.cc#L42-L61 if (it.second != MethodExtraction::EXTRACT_REPEATED_CARDINALITY) { auto extractor = extractor_factory.Create(request_type_url_, it.first); if (!extractor.ok()) { @@ -95,7 +97,9 @@ absl::Status ExtractorImpl::init() { } for (const auto& it : method_extraction_.response_extraction_by_field()) { - // TODO(adh-goog): Allow repeated field extraction in field_value_extractor. + // Allow arbitrary types only for cardinality extraction. Otherwise, the extractor is + // constrained to supported types in the proto field extraction library: + // https://github.com/grpc-ecosystem/proto-field-extraction/blob/main/proto_field_extraction/field_value_extractor/field_value_extractor_factory.cc#L42-L61 if (it.second != MethodExtraction::EXTRACT_REPEATED_CARDINALITY) { auto extractor = extractor_factory.Create(response_type_url_, it.first); if (!extractor.ok()) { diff --git a/test/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util_test.cc b/test/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util_test.cc index c48324ca69e41..aa2970e239bcc 100644 --- a/test/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util_test.cc +++ b/test/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util_test.cc @@ -671,6 +671,12 @@ TEST_F(ExtractionUtilTest, ExtractStringFieldValue_OK_DefaultValue) { IsOkAndHolds("")); } +TEST_F(ExtractionUtilTest, ExtractStringFieldValue_OK_RepeatedStringLeafNode) { + EXPECT_THAT(ExtractStringFieldValue(*request_type_, type_finder_, "repeated_strings", + test_request_raw_proto_), + IsOkAndHolds("repeated-string-0")); +} + TEST_F(ExtractionUtilTest, ExtractStringFieldValue_Error_EmptyPath) { EXPECT_THAT(ExtractStringFieldValue(*request_type_, type_finder_, "", test_request_raw_proto_), StatusIs(absl::StatusCode::kInvalidArgument)); @@ -690,12 +696,6 @@ TEST_F(ExtractionUtilTest, ExtractStringFieldValue_Error_UnknownField) { StatusIs(absl::StatusCode::kInvalidArgument)); } -TEST_F(ExtractionUtilTest, ExtractStringFieldValue_Error_RepeatedStringLeafNode) { - EXPECT_THAT(ExtractStringFieldValue(*request_type_, type_finder_, "repeated_strings", - test_request_raw_proto_), - StatusIs(absl::StatusCode::kInvalidArgument)); -} - TEST_F(ExtractionUtilTest, ExtractStringFieldValue_Error_NonStringLeafNode) { EXPECT_THAT(ExtractStringFieldValue(*request_type_, type_finder_, "bucket.ratio", test_request_raw_proto_), diff --git a/test/extensions/filters/http/proto_message_extraction/filter_test.cc b/test/extensions/filters/http/proto_message_extraction/filter_test.cc index 30a4323a69a24..167a2e1d5cdea 100644 --- a/test/extensions/filters/http/proto_message_extraction/filter_test.cc +++ b/test/extensions/filters/http/proto_message_extraction/filter_test.cc @@ -1094,6 +1094,86 @@ TEST_F(FilterTestExtractOk, ExtractCardinalityRepeatedComplexField) { EXPECT_EQ(Envoy::Http::FilterDataStatus::Continue, filter_->encodeData(*response_data, true)); } +TEST_F(FilterTestExtractOk, ExtractRepeatedStringField) { + setUp(R"pb( + mode: FIRST_AND_LAST + extraction_by_method: { + key: "apikeys.ApiKeys.CreateApiKey" + value: { + response_extraction_by_field: { key: "repeated_string_field" value: EXTRACT } + } + })pb"); + + TestRequestHeaderMapImpl req_headers = + TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/apikeys.ApiKeys/CreateApiKey"}, + {"content-type", "application/grpc"}}; + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(req_headers, true)); + + CreateApiKeyRequest request = makeCreateApiKeyRequest(); + Envoy::Buffer::InstancePtr request_data = Envoy::Grpc::Common::serializeToGrpcFrame(request); + EXPECT_EQ(Envoy::Http::FilterDataStatus::Continue, filter_->decodeData(*request_data, true)); + + Envoy::Http::TestResponseHeaderMapImpl resp_headers = TestResponseHeaderMapImpl{ + {":status", "200"}, + {"grpc-status", "1"}, + {"content-type", "application/grpc"}, + }; + EXPECT_EQ(Envoy::Http::FilterHeadersStatus::Continue, + filter_->encodeHeaders(resp_headers, false)); + + apikeys::ApiKey response = makeCreateApiKeyResponse(R"pb( + repeated_string_field: "one" + repeated_string_field: "two" + repeated_string_field: "three" + )pb"); + Envoy::Buffer::InstancePtr response_data = Envoy::Grpc::Common::serializeToGrpcFrame(response); + + EXPECT_CALL(mock_encoder_callbacks_.stream_info_, setDynamicMetadata(_, _)) + .WillOnce([](const std::string& ns, const Envoy::Protobuf::Struct& new_dynamic_metadata) { + EXPECT_EQ(ns, kFilterName); + checkProtoStruct(new_dynamic_metadata, R"pb( + fields { + key: "responses" + value { + struct_value { + fields { + key: "first" + value { + struct_value { + fields { + key: "@type" + value { + string_value: "type.googleapis.com/apikeys.ApiKey" + } + } + fields { + key: "repeatedStringField" + value { + list_value { + values { + string_value: "one" + } + values { + string_value: "two" + } + values { + string_value: "three" + } + } + } + } + } + } + } + } + } + } + )pb"); + }); + EXPECT_EQ(Envoy::Http::FilterDataStatus::Continue, filter_->encodeData(*response_data, true)); +} + TEST_F(FilterTestExtractOk, ExtractCardinalityRepeatedStringField) { setUp(R"pb( mode: FIRST_AND_LAST diff --git a/test/extensions/filters/http/proto_message_extraction/integration_test.cc b/test/extensions/filters/http/proto_message_extraction/integration_test.cc index 50996302d1d3d..d95741814ad6d 100644 --- a/test/extensions/filters/http/proto_message_extraction/integration_test.cc +++ b/test/extensions/filters/http/proto_message_extraction/integration_test.cc @@ -73,6 +73,7 @@ name: proto_message_extraction parent: EXTRACT response_extraction_by_field: name: EXTRACT + repeated_string_field: EXTRACT apikeys.ApiKeys.CreateApiKeyInStream: request_extraction_by_field: parent: EXTRACT @@ -313,6 +314,58 @@ TEST_P(IntegrationTest, ExtractRepeatedCardinality) { })"); } +TEST_P(IntegrationTest, ExtractRepeatedStringField) { + codec_client_ = makeHttpConnection(lookupPort("http")); + auto request = makeCreateApiKeyRequest(); + Envoy::Buffer::InstancePtr request_data = Envoy::Grpc::Common::serializeToGrpcFrame(request); + auto request_headers = Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/apikeys.ApiKeys/CreateApiKey"}, + {"content-type", "application/grpc"}, + {":authority", "host"}, + {":scheme", "http"}}; + + auto response = codec_client_->makeRequestWithBody(request_headers, request_data->toString()); + waitForNextUpstreamRequest(); + + // Make sure that the body was properly propagated (with no modification). + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(upstream_request_->receivedData()); + EXPECT_EQ(upstream_request_->body().toString(), request_data->toString()); + + // Send response. + ApiKey apikey_response = makeCreateApiKeyResponse(R"pb( + name: "apikey-name" + repeated_string_field: "one" + repeated_string_field: "two" + repeated_string_field: "three" + )pb"); + Envoy::Buffer::InstancePtr response_data = + Envoy::Grpc::Common::serializeToGrpcFrame(apikey_response); + sendResponse(response.get(), response_data.get()); + + compareJson(waitForAccessLog(access_log_name_), + R"( +{ + "requests": { + "first": { + "@type": "type.googleapis.com/apikeys.CreateApiKeyRequest", + "parent": "project-id" + } + }, + "responses": { + "first": { + "@type": "type.googleapis.com/apikeys.ApiKey", + "name": "apikey-name", + "repeatedStringField": [ + "one", + "two", + "three" + ] + } + } +})"); +} + INSTANTIATE_TEST_SUITE_P(Protocols, IntegrationTest, testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( /*downstream_protocols=*/{Http::CodecType::HTTP2}, From dd9be9a753133ac742c648779b0b87930a4a1f59 Mon Sep 17 00:00:00 2001 From: "publish-envoy[bot]" <140627008+publish-envoy[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:59:33 +0100 Subject: [PATCH 131/750] repo: Sync version histories (#44640) Signed-off-by: publish-envoy[bot] Co-authored-by: publish-envoy[bot] <140627008+publish-envoy[bot]@users.noreply.github.com> --- docs/inventories/v1.38/objects.inv | Bin 0 -> 216736 bytes docs/versions.yaml | 1 + 2 files changed, 1 insertion(+) create mode 100644 docs/inventories/v1.38/objects.inv diff --git a/docs/inventories/v1.38/objects.inv b/docs/inventories/v1.38/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..1958f517cc23a127b8027cf70ae1747310d7a0a1 GIT binary patch literal 216736 zcmV)2K+L}*AX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkqZgy{Z z3L_v^WpZ>BOp|0Wgv28ZDDC{WMy(7Z)PBLXlZjGW@&6? zAZc?TV{dJ6a%FRKWn>_Ab7^j8AbMyDb zTr}?7fh+Cm_KaQScDlOfoG*e>lG7S1Qz}X6Kebrkyv72%FL$0~8Nnb$Nt8%R%Ix!@ z$|;f&@e7LL-(bX<_gL4(kK3g7zW4GXDZ@I=_tjY#@6!EukM%D57nbMyp8tolFw63& zDD#IjOG-{x7I#+lOTyJcV;c>4&z-|J^khX z`oAYIManL_eYi^ktkr2u75XmgGO6-I870#gOX?A}f1X|$lFOgfYN?YwklOPT1VCOK zoWD(#hOvmtFx>}P%9`h1erpzNx^#V3f8Ca0lsp`=sXQA7Bh3F?J*7pEyr%VRF$yb< zz;wUK5BoUZXK#}wsXUm7WdEGMeOD#ra~dVzQ6KT$^AF#x_|J>9it^{Ad|UG(XY0C5 z!riLXFA@yi&<-5R8Bjf^$;;VmTm@l~s-Rq@`W>k~;ISY8Cg`6#g)Zo?^*9F7k9=1IJ7HJ)o7yUPc!i!G2e8558!3z~!2L55T|He*aV9+X(i% zf_w1G{7@&r;tqZc0LQ##P>;*=Tk?j%t|QhV#Jt!5X0^z&$j5~H8sU944s2}M@mk-2PQ*Obf4BP72A|oi5=(O9Xepunc5jyw5D{;)WvA>cGn)5i7;! zlKIwtj;_)miz=yuh|NH2mzjQt3>c}MzgdN**QTGrE|1fPx4%9mZ{PprIm`~p_kU); z-|^q?;P*%V`=dQdch(FL^yWz!r2B{5QX7>57}TNl#s)7dh|X9axJ&BCKUFzSrXb0SQ^GY=P<$@z0#hZ8Gqr>fCpDWzCh>wGBuiTeY4uN7 zwJr%ku+&00REe^6GeMwa&}BcZ8`n?|GuCKZ9d^60e48h7gzpHLI(daTbQrTWAA zyheo1d4D=KQq*E!soj^k(AQ}51upc}*?eh}-j4b%>aegL%b+YHdH$ZCD&ZRC440J8 z?OJRv?d5XhY@gIGdHEFZaSLVxDK6crEC>(vBc&^NpTFmaQn;k?r6>UL!Cz&Hl9S6>NA05gv*+*~A z92-jF#9HJObt9GjE!?^5)=(m+f_0j;Uq8Z->Dn+l#K4ah89p0@lRfN(x$@L7Q29RQ zwNJZk6B(-{u82>a*&JCJSh@0N$|A>p!sJ?9oqQ2kAM?{9Tnqj{FDNbYwu! zU8P?W|7kN_g31K=E3MEMyT)$JbsT_my4}P37TtCHwhwnxd;W1=AT7%yR&8QnON)uh z46@-ewmzSDJ9CwUZ9aZdMPZS|lle&og71`ZCX>_?1_DV2Ja&1UIMO2t&=z3{LtXWF zsAG7^lX|WJCcwW;{+UGeGVO7k7njDrh@{A)M`!LTEpVfwIWc#3v(XG&}F zLTNdyV5fY5Dy70{LRX+tA}8FMtwYxVx1j?rY-%SzuxT@V9S7h%ZHQ1t1jmv7k3f^T z(*$I|h`Rzo(|%$&x%=MJWFr&rf9-RxPU|fB-wliG5C&{ru_Lv+EShZ2f_*#x?VE2K za@a{21|V~X<=0U$!a(K@Gk8h&tY@!+>QEFp)0ZvuY5eRm)0XTp4mWIh9WdG!q9&CS zoWrwMKPDbaJM7rEhi~i;Y~7AzS-64486zyxGr5@mqj!FL1!a8TgbV$m%@|^sJH7d zpzh9l{sHB z9b4^%+ob9~>llwyh))|wSdnNcEqIM5NCYaaRPURAT&%xsWJ7Or7Df@vX2W{(ODl4y zFpVPk2(yE2t1>%ObPQo5fQok6dvg2DqY@@z6ND}x96keCMMGbj1y$=sh7FSXt>}-i z#IN~q)UU{dVvMTu-3`c%n!!KVg++m1TR&|Wf+d6ARKuf;5j<6ru?2eGVJK7WFd3Ni zamlO^WY1xi#$k_ZlWd30$fl33$?L-7o#L6gyPyZqF4*c)2Y5nCT1E#pIoXs+_{69T z4bwzPQhAm~Swr83%8Woeh*v3E%C?X4%(Cg+)n2qpz$Je!$N-nlctw|F7S2{KS;}S( zaZ0nKB*1NEd9{CHTpO%=02&oC~ zp~sY`Ya=$p2bM=6@TFfhrk!?VPH7E9!jL{IC$zB_WacbhGh_GJS@5 z0qclhSLr#82bGI6{AZR)1-YaPM!_=nG&pR+EM%*~AsJvP6Tk-GrcC2)(tS3|HZY+6 z;r{;CyIPRiXT5ylB37mA%H{^lJZ}I% zTCtbJ|2Ioj3e!dt(uvl5X==?+VFLfmf~a7B*LI-v!&At2VuC8&KlKu91R7Y-=nSbI z-`!cW)B!+Q9>xK0_vwBc6nU0LDI3BHHfar#mtENWGDCA<#zEe)NAL2tn{)4)k$UgP zzdx`%+m^tOewY#UNJy?=@Ikw37SvzD8vdEZP(jN^P$qCflLYmF^*mWnCb0R>`uBl! zvv@~_9OfO*^mwKxG6yhMX#xMtSg0Uv{axKc$Nl;?z_ktwY_e7J%Pc`dMEil~x8CQA zTMxe@IbQWdmpGp#3kvfDPg9(W`$)?i${Lx(aTc2}ddeRjY$dje(|!j}@}w1&Nnn-O zOA}>V8$4sXI)XGxGFvIKC<(c|W<9Z-)vj!otb!At9dh>p*5dm_>ZDRg7Cs5a5zw7e zZXOWWF04$n91mW1b`WSlDn12kcx{5tyx(qXfmm(JA`0LJKpvwn4qFI%I*tTS8F`)u zc{UKl)o~Z`EgD5MrJEWEzwaI3g>PHAeHyuFWa%EJo5aGT(ox+$jWQRtRTp?1%G61w zEZ>*seZIT$3^L_Ww57~;d72A|^KNd=P?<%(z6BEm_o$@!*Ee})ZV(P$jIw8&%@SRC zG4As;gD7q)6GXde8$9Bch4olDNJ^6e%=T6{>z_Qzw!^|-kRu9}s3o7QTMUv98@6t! z54e@X4I07C0Gr)f!!U3lOiC$FT5v)s1HePfK3uAAry*#7koM7F7R9THwuba5;-reo zw7~ru<@d1k0Rn27u-4DgU0QoeURUJ43PiJRJ&Xt)BB4!yk84k)bcBgJI`YS`kX}(5 zHR)-HF^sl?!0_gVY1*Q!#%CR~KwjslreNujw_#NlNN@qkS0XM5dSqn!0c*-TOdgQU zPbkjf!)BB7|Az(k%Tn_}GX3!@(eOV;*2>+tfrTj*+=4+%8i~I~#+~w9!cRmziW~-M z{Ld&umQ+UaQP#p*NB%I1=uxySe8$Dmp_KS*6xJ!cR%KPw6BU&B2! zG!h(g@go$!Mr8C0_Y5;+PFK=4$f0$mS1+iGO zu!Nyk1OrbLGA68{8UX**qMB^|0d-@z1;&2_58B(PnY zcAGHKlPttOxlUXXmf0Jvq5^qlJT6pAqYTTKPfIU(4y_gbT057G+$h zmPQdQ^1Z9u#=b=-91U+D4q0Fvp0&l*of*m!V|7Pbx`#VwtP@s-0Bzzh0&5n+dYtEs z3_5sW6{-ybzH$0Tf z9=2QbCd&485wUl!f13)g@tY%~P4d7+wb=1el8pLK;3l?OU%_`-GI))o z$68KEMUIrv%W;P7%)2ua7MZrXbQ2t)b`zTOMFc>py)T0eDsN{w>lT8Db>$^nspeH( zoh7@X)})W&abiTn2y{m)O!j_;WJ`38L5K zEVwb%YE{){4vTh0!how|eLksrwI@i9ldIU{fzJZ>4?K=I1aDZSiKp ztKE9#=k#LY*m)ciH9{stq6NT>b^==Si;;8<$WXtU!p$9Ox^QGuo#0$7IRnr3Y~Gjb z=-Z%SfbT+$F0t(BsT*Xa?nS9)Sh*0TETB~(Z0NcI5>z@EYs%9K$Nx>T@PDOI(XsKwc5n7AZQ0dyO9R$lg34QsdjuUS^ zy5MW*pWa@=?8(BMYLJ9_%BQ#Ac&eqQ{42B-s+86k=xUi7CvKYwsWa zUHHapyR@>LmfXIk1*<4);c9U%{@*dhSke0mof>gFvR=ZdMVIl+D<&D?Zh>e9H?Z6O%2mFMr-izh-2_^q8$^yk1=MQqQsP*1Y;QKEWL#c744dBR0F)NoM#wm6ch5saYk<%jcm*g z4|K)a_d%hAmZ|R6JU2WT*UgrG=!fDORZ)-Dyw(~j+FNPG zV;E`rY9*y9s4gg&Ad>_+q*{sTlfj+>5+5=CT z)Fi+-6K6izl&`8st<0F3Z>CvXo?oxOwa13>7@J)lyf?5lpTo~%s9TFGs#t^Pap;T( z_ihZ-ll;uFGos}LGQ{=76i7N264X8p%h=KO%oTM9fP~;0dqI4oVG)RGjPDqo1VA| zhcX|;wq}zp2peZHJqkCCkB_A5BC$$pI5nxjHr8txdc;umgF`zslHruP75fZYePrgb z>vdriolTQ@RVN+5hTCnKY(stkW^w}P#(*|BM7|fo07?4e+k|)S9$o)*g|l zEcX;MUk_zCoTI@I8~tc1Hr*_6?xG^t9kM!gaQ!)n51EFpZapkEy=LiR&JX;@r1m?; zLcQZ7>T%_cvD=4pY28#u)GkchewCC#Au=}(-a&@d+dhIAn7rLJkHL;xy;`An;zc6_ z;U7c-SxLD|QkldL5_zH}PJ33gM!*#59yTxCkjBY^SjU*P&|WA7E%9!@rbVYRAjuW! zC>y$Q%%0ur6B&0aU^2pCMheCWwfH5hdlkS9lzEOTSUY}nc>z=0YZf>$Ktn<*yIs^N z92?v$$sKrK#@Q6Kqio2iV>-TWOPh>vn300fy2+v~f#Ohlm+sTuVHe2R9bBpEq}joq z&Y7mAsJ_m)rK|9kDyBj+W_;2e^AEoc>Tg)E8yv#o5yxmP2j%}9_ z{K`BYLMmoEO)9^b_-2FKiIr+zP4{H@?R-4!{DqNrDA#}x?=lFjSCsDj z+5wZGyvMU0U{^^U@Fa8(jk{YIv0(ya11}NktM+-T@YfjbOM1Sd@GghM9zA$k!t23t zT19Xrls853FhK_^j$9MrY?vE*&ILJBXR`y>nH-nUxeL;)GrF4y-(f$={nP+DdKChKHK^1$p zO%PA%)S2HlwbURh_S^)Z*t_HG5fhs0+mEV8I(sm9$XF-Rb1QA_a3KSk6JSplLkfyQ z(kPJ4fY76nt)@j9K)9dqP(KO>BjVXzA0spAt>o{?n|IEVt#Q6oK^E>dG5_-AlRL&y zM$Jy+HKw}26ZfC1bi1eBHBIs)z%Hywg_nx%U$1$lttOjrAHSq={TRqyxK5I@2DU7P zuY1a(%{^<}6PUP(vd5x z8Q%MRaO!S_cea_J`W%LE{rb)tTD%XkJm-7A^xGZXAQKhf#Yg{|Ww_QISff{mo!o^I z3GOJzInla=(x!~6?ZbBQ;Er>IP50E^0a>&A)I3eXHq59UjV*VmI6P4d&;urgkTS>g zo54*6$AE$!SYa{=XpL!&m~V!`-w6P_#PZHZWeuP?OXQ1xdL#X0cX*DhMQk(aYxRn< zt#zr&2b946x})t86Q1?m2WtRrXO_sW@7e(V(6QLA~o!jo9)w}^4*a%@7tBA0UVX^49a6nxhECg5}x=A&O} z?}a}&0~%w~@#hNj+`LS`+HUT)e8UR%=qGjxNIjEbm$Pm!R+UglCeVtKS!lJqmI6I} z+kB9mHuiD1{7UYY+u{M^BDnn^BSecwcWee$&CR7XrmnP3EXTRORduopngvxqbs}tA z#)=Y{$=7eZ4Y(<^jSBp5ara9;IKv-fLq-FuTMrUp3KKbX1n99I`N9u(Xc}_q_C77F zQRG>O8z+2e!ynzj409n&m@;K?gtPI7G|6J{+*mdTL=$KER{WG}Rv(}!^i5RM2{-)u ze*VhD$V)xnL}+3@lK1N4$%<^UR;z<&LGDb(bY$A@xZ-~ z*usO@=S_#AJh}rkuu_AqWAcOf+SY*j7D|nIYnkQStIrM3G zMw?GMn%B~kqYanCc=kTxWEbvv%n9+}4rhc7q1t;FnCM*yi#cnIA_`Ph;)bI7?2gpP zjPQ!kQtL8u9QKL?X1U4`+e&aoY{QP6>Lf6S7Gw2%EM+u7iy=C+-7D< z@No>=w5D?N;F4i6SrS(Z;ns2;RDj%7d*{~X%8KY+QdMjKUS{*lmZhr74KF=m3#=KR zgD;1iH`i{OvkLZ1m7pu=Rm%GX@?5~T?pscry^Qkd#f7m2XA=j-o>++;V+%kIixbCI zJ%h0jzC)aJcKF>iXL$BZ2kiXa1_@xTcIxW$B<$nN8q5>dauw`;wpWgwBzM%7ED8Ji za>i1$8_`jC0c%BhbXLHcY2#%~jUDMYI#;jFc?xbmz%=I_S){x1YtQvF>`vWo^tiB-Q6h z8kiY&*3B2yEZj5K`VI`|toNmuBCqOgnN+QY%7@y1)+9wHNT4+x-ZyE{#B;V5i^v1IG>^gov!Kxao!AeFY_9K72h#-RqcvdGe%ajMH$F znch^UUMU&l;C8D-a+-Uw#DY=lZV8pQKSmJ(a05|UTahADy1Jtb)q(AXr8>Y}*lg07t-8}( zx>zF!N#f`$Na(x}2gq*X6dhbRlS!U9XcL*<{p!}eTiE6Mt+Jj~9w(_`;6~6P*?EZF zy;WZ_g?IBf38{e_A$?o_a_x+CIb%2%jyij|$)knbUKcgg*>#^>?+%PRTlubUVFA-7 zD^?S%nFx>~?OIRksW3-zF z*orBw#39Q+hb(g3)J#`yoTn>&(e0FF)Y0*52Q9hBoax(pC5FG+eNk5cYRV)%tayqQ z9OT^m;@+S%LMASPMp>yacV8A+1}%NY=gxQT5ucQp7Tr}k9~GuA{;gO}JoIRI*JUUf zv)}W8YMId~)_o0qH&sl-OI!<0PO4ZiMsW zm5h<){(Ui?gWjJ8Gp8yuRZ^p9Jkj5jKv73Upz%7YNuHvkD|h;Y+2XA6ZF=oEQ3U3) z9_7PS^h2B)q^_R?S%-4Q(sb|$m(mv$IRCQxD3~IALuct7G@9ha^mi3W3oM>}geq8Y z1?J9w#Y>A@?3x*TtA}!8yX0IcRR1@z-hWf57^?3>GeSR7h?BPx|ogSulvYr-f zzUKK;k`$~flZtkdrhv2MN|BiI{O_1$JtiOG0z)J6XAvloKe^yF^JzljTK8VabWt-K z7mj)po6i@wjfuLGChQ-*L~%FgcpgQUlh=wV!~@1mo&V`U@pGrJs95zj=TQ84zRG6q zbZLXVR`NuSdG=efZMjqW_%RL*I$q0|<$NioCqX{U!r9@{#X5p3FLz-L9Yw^qz#_}N zv_U#*rXpVn_6cSKMVanvFqc|gUb8F_-mbEOh<+j|2%Gd9NzZPe&78*7DA{Bfv;aDf z=Qvfk%5K5IT7@#r4;6(k z%O9Ep9vcP9qObdWZ`hWrnXeEJ+W11Sjp zO3tD%%Yw$!eRUQ_U?<8}mQM+pWH!%gK`V6@l%)71Jja6GJrq>j@K=*{#!ht;R!Q7i zWowd`t${gm8<)fWdnnpi53HD&MXF_+=UW_Yb_JV31j&Az?r}z>%~-Z-=E_!@%TGvw zQ958Ebcqy8R!|!56l5t}@I69RSV-wBeRY1bq^4!^z~(`Z%{Qw{KZ^DfCHHIOjuR}{ zdr(He`mg-3uLJb;hg}puXB*00ww{(lq14%$lR=LQWZX|7?|m1kxw?{>=yXCpE?hSb za#Zc_*OSzI4B_Zq*NQ2Iu+n}-RN-0rE+Vl1sa)`)OVvTTXLIyJiLXHXUR-ZGA&Rb7 zRA?zJ(N3{3SX^2^6MIE7ZWXzZT6I>5TeVB8G{#y{2X4DAq|9rK64{@{l^J8LDD!$@ zt4zz;aq^5;g46vIpFE1$q78HhMlJWr7$A-{CcHS7cxy&u2^SA^z$i=_jk)3+;-DIw z*<;~{2+eWA6f}Ast;2{BKux$sHIfKzFj5poeU25@x$L`POnzqh7-rEJ!$c8XrMrTO zXiK%fm+ity7<@o6iIoS2l)KM)2|fUe?7(LIRtiAS$>YAA=@JHC(68?tUJ=M&>@8U_ zTl__Jg3Zz80D26HJWF|qZf%CWf(wXg`N9!?18%VZZkBZ;;W=s!ogz9eXux!mCTnZU zy6KG3Il^?^lCaF)f^Nu#WuiJPAV7ox7vtA$(SJVl{sAHct z@aBUPq-?DhKFepWQRC2KD9^phmX%E1BP*lc)0dwOo^!ZY6E$MF!(W}jAr=H#W1TdI z)k?6gg=vh1A0(dPL5BEzBqvLip2IAS!x}0bZ|AAY+-?z9t=xH&rv)hdpa1dyR2~tC zXfVHu(sTrC(vioSWuH}RInPhIlyfs{y`1>9ul6b-Zl{3mcz1s{53-({;w=81177Jr zEiKp!uK8^bF#__}tVFQo3D)qZGj9dQrH?~!gJ?05oBcqe2lM)XF@)KlnOb!jaSi_Q z;lo_qJ+HJ;ok-eM;V2P!5t-=S`@6D?!sMzXqN3*X^ z;w*h9h!7LJhhzms)>vYq&QHl(fCeziIkDT~-G&4C1W4Gj4@#o_kT1T}aUI z3A1n?!ORy|jwx#IJSejL?F>*;Z;-k3<6!0Ou<4M?!@f>;N${YAPVJ`6 zQNf-+&DwdMg+`jtB7BOPoWc*BR>|@QzU=8~U4*&CvO)1a9 zqTurt{1K>mE}FRUdGSO&b2C%NYJA$u13EqqWhTO2>+{199| zE~AMPe=-bi%FI&jk*%_FetnaEE~AnaU+VXHzN;D)I@1gAT;w6-mo>zFg>IgU8t#1C zwqCr9C(BrW_4%RBQnrkg=ih9|->101Y&N9X+A=Q#ya;Tg)HI%4p`I`6i2x^Rj8`q{ z^f?KTWhLf$?bO+o;38&9y*Q~cTvbI_Kl0J^>`7J8KFGcbfhQRTXL`Bv2Vu9*&q1S>73 zu%vYjrrm5j;^8qKUSYn|trz(eCW_PdWIA>&BRV2Jzg!0F7u<~EGQQaDZ8n7;?tCaCviEMDt>K!PC}1(u z%vX}e!;TV95z+B#?&kKyF zi@d~Rw{BYC5bJF&cO|HVC^ROkzr=i!c{hVJ2EHLck-l&3;!T{A#&;>!&(Q+2(RrG=oCT zVIwlOx|ED4#V5_t2 zfTIHYGY4Xy51GRr!bzy61wD7?CvH{n1uplUDy}fC1^I{uuscnjv$kbeJf?NR_px!& z(n+#WVFE*H0+jXN(O@yQ!`zN+&$(ijBUA$)HKs7Pr!+VYQ&{IkvJYxDob7Pngb7S5 z#Sy6;JDLQs9PeJ_I3stBD>6!I6;gyd(-xaMj-R%HxcQ#fSkP>RSce6v1AhMDm+Zkr zf(B>!S=y{J^EI>bG2R3M7-#|ZIKn_vU0+T0swJjL< zlA2FTE)Tic1er!aC1ClWm+yjn5m8OuV=?ft!`Gk}RLr~+?SeSeufFt%XG&4((7v0#)x{Be%FU==ty07q%ja!OV>>I7=Tdi&95JnF4 zfnVRRouHss&hNihLNJMwL6Jin;L~tAvZooFICF$6N@w4H_q{QKUth3v%pwHxo##&C zG^Jd4i(PH<8;`_4P!ujZDDIO&DP1j2YoW=~UMj2A}? zIiAdM0$PSI!RGMrKv6v9(=@2#+=OYti}xN=oq$&FaE-?POoP^?AFzM65bT7y4$GSS zyo=ux@egv6)4H<0AN8o-XIk?^x7zOP&P-2Z%IGnjhD%g5=8^JYBBBvqO-yLMy>TzU zm^HuW@KS=$R=0KuuY`@oU}gfei`>m$N{S{PB``@J_~FoQ0d1>&y0~r{o=rgY#TDQy zr+v6v{w0m@Zmq?52*1TtYn(Ztb?z?Q$4dCYMKrV*LEDRapO@0e zNS(9MO`b$_>1jk{HP*}?>CWFYSV2{!Y--nhDZ$+C#)dCd@9oux50}ng7Y63V8zgky zL`3TqcTw86RBSYv#8Qa}9MWtA@3R;e;e*n)6)gPg=y6FE#+f!s{Ive$VL4}YX!%tw z>0M+Z(}A$&>LMQJlkeH=Yn#|B)%QZK-nS@nyY=owrX5%3&Hatrd~}uD5r_ZDDB-iw zo7s_NFw36~nym8vb0vvFr}5t{5mx;qwYVNQK1m$oO*`&cumZjQ_snGMGF%f zKS5qt8{#^#qSgvMgxo>;RG5ry8xh#L7Vaioc+T%7l-4(|wvCLMg}p;nKS!DTZrSfi zeirO_aCpD2uBZD`aJ8?PXjJh619qHmVdE5CWH%md+9`GzoR|rKoX9)pt*w3xYcI~( zs0(KKoDN)Kb(O#`k^HbLzcmO4N-EzgCa@LwKHMg7RxD(ollnaxfQerk{+d$4T_QaS zej=PAk4Ky3TYl^@{B?{IC{Y%90gsjNDq-<>e!c#-TWgZxp+=vnO+X_sGGy=43}u{> zj&5WOB+4}55HWx7dXqH<4Hd~}&v_~Cb5wd8P#N5ko5bN7Gd8~qU2$5EUaW^?#Ay}5 zrX8Leg%(ou1f<<|TP9n)+;t&}HHNO|7sIc;#Z^AIf$5O7mf|wcJHNg1AaZ%W2W#FT zJjp&Ta@ING%e1H!ZFw^F7_NNP_LY3@b&^D&Q!@ZZR>~)O`OK|Q43^1Kk_f=1wsRQ~ z%M8?loGPvpZVRwgMr^##Z}$EI`_^PZ-5}#JP-qR=G%pG8ay8Q2c}P>7w^=kUP3cy6 zSdH|bM}&*P01XW-#B0m92a-Wi;LBJZf@GzIFg=9`;TXc5CDBl&w%2#V+JZqs8dc=g z4}MyIQzlX}W9C_=rYU^Ns_kD%bN!5_u;X}zmt^^QDe>+MpT2T+`^zEc7n{oLi}|gN z6Dk+IukoVyFHoidb9epA@cRYU$eHuB!_p_{T{f>8=_A4+{Ba;geg5P&W3c3)Mq8F) z2$dVx7PASQOQH~qZ@yoc5DX7ylB9AHP<14%$3$>{m58gh5uz*Kc73VoJMZpCI?v{4 zsj>i|DXnh6>t;#oQQ^GMm_Xh?kq~_7&GMmpJ@BIoD}sWkYkd-gIXXRx{qHLw<8oLfNZS4Z)I15V3eyj zM8TZzb95T^=GAWt-ws980kkhhg)q zr75Lpn-rt&jmmOyL2jVveD*qVU`Z`p^Yg&9b!d2bBP6XrxA);zD-!y}uzEDbu^%i; zqbGzp(ubIll&tqPvzLXGQF(-6z&{r&zOAwfdz#$97gr6*Dv~l9yPK~qM_m+88sf}G z`)y+6tCW)Qv9`%DPg($hQ#$s0`(`0PPtsett$B7jTvrXN!#=GYBsawyseGw+4-@&% zEPF_11qNSW{|2|xjnlC~0x_%Rc^>5r39a*|WFKq}aL!^B*e^^|TuAqLx=v8V{~zE? zxx@;73R-z00`t8MSh^TaQ^pBKPE4NCEUUbbv1CfHq%sL(kBNYE_Lzv>z3w>*nbjba zEZiiS0)}S^<35o6FFeO5BDq0VwYdfIykd-RbEqMIX@vL|A!H* zY88&5#Tt}(zT`2iM8Mu0-~SF+%|=fFKh?mi>0@tih-NUO^@e#N9}D?d$mbGi#~m{~ z%)lK82DOYH(fa*4OtVMH{Z(tiE+@u)bui>zTDK`hl%KM@` zbpSs003<{G{CUzaE;biQ^B7FNj|fZ(xfMT}^WYI|c&p^7z_+grbly1js{j;qs;nNth^94i(oBX+bnB zr2$0u6>h?P{F273YozlJW5Z&Tw^EKCXC3;1)+I~%T*`a#>Lq)$S|p;7>?KzsMPSlEA`jrK!`YOqbGPX*JDrxoFQXdbQu!eetqHv2C*%gI zHF3uP>&kqchrDHVN->m&;pQ*!&AuHIXa3@&2s#a6bI_<=pE`IX!E%7^n}3c?rIbO0 z^H<)l7@KKT(LpvgX*=TF56X+Fo=@fFN@z_}K-^2G%z-r#1kigBGsQ)#lyN3N7==$? zc_Mt@!LE;xEv`*CBwk?+5boq-7e*PTd#g_~FA{DeUY!Xdt#HBcf_`)b(j_O|E}uzk ziA$D83+yt5aa~LC)%8Jq{*LvN}8o94_es|3_QVR};46 zL=^>cE=^#v6M+Fa*CsIKiNJuII}@1dRA^ApdKeE~a&#FdI@cpdFbL-2+9W;epyal5 zM{)0msw5wjpL4PK#ruvtFxdpty{`^4LP;|+Eo5bU&R3dtwyqoWVQ?$MI4LSOC=J~J z4lLj!V~K@5CHpwpNBuc6?HWaoY^bP~9))RZh?vu!5P#~x5Vz1ocx{J9DI;==A37w& z-=+<8f1mTe#|IA|x{%5u za_{khN(%tE!Td-gCD_1?e7mW&gE(KZ#d(={(g7gr*lJ+_k}U$Lq^)raz++exZ$a{w zu+GG7SJY}jjqH8N{%3gfF2y6ADs~~M2Y~WW!x~s;FGGt;Jv|f>G$)7pGoI<2C63TE z`y=B}*oe=%-57!h9ubo-rCf*~yo3SVLs-@9zcgbb-*EwgBwt86#}1buHXBV)#-lPO z;Fg;YvmkRBXwlY#s@s?7f=pw$X;Qx(7p9cb`dUP47h;Z1wk^+!o286Gr^S@lzF_-v zUVHSrE4;3YAIxHU}06cj1(hxY;3$-Dz1T7XCF{PK><;PtFM+JN- z#@C{;M`2SVhoaqX#j{2<-_qZOdk4wtK6~SLVF(W2(FX~1K}c?yt3Dh0z{DQLLtesy zhPwcEL5T!|sKovUvG+N9gh*K)WqFvTgJ`{h-+&rfgd;4$5`pM9<5fOEUdD-?0f{pP zU-G660r5PBr;r;B*4x(Zz?cxZWkG=v&xF3R#zR%@q) zP-6zYYrg4X2EIGgq1|D~RJc+Cy?k=BLa920Y@!n5Nq)hWQ#EXcRLAR7xG>hO6uuND zW^OUsDHNW_(1Dc;xZ^==L?AHvsDp#C5d$8^`iPKFj4#C8@wk~hc6MviS__di0LPAo zNQU7MOXLl<@=GzJ zZHRa8Rm9d}N5O-AJ7-U0Cj2%ds>4loL`0lDM})sSh|+ypr=bk;?Xr{wQXPs8%d6I7 zPduIHfb$9H5RTJF3o(8pMh5JVNnL0KMVWx$H_@B(@kfVV3#;&3BCc-989#p>2gGKm zDeJ0O-Bo3TE$ajjeIe9mLb1JhpD4v!^9Wiv=6uj?>x?QOPSZ}VgcRIWMEV!fD)Y}5 zekkf2@V@!mZ};Egp=#rHrGu$^BDwTwrYIv9Q?iQm z3#zt%Kt~{>2r2+*K$pMOji6p;P)Eh`a(&=>zq%cidWer96}W!B=t%{CRhuw_W8gBV z4x1{eUCucadN@8N23jPnq%S1-LK3>fdQYj|nx_t9bURS!fD;7_=s?CN{bpvH;gs`) z4kW?B3`ujWAe{D929#++nl2Md_|I?5r{5r^!*C;#8s$apu0<#o$ zDSlYiFBvJEsA|d7fk`712bT(k+ofQL5WFPm_Hk~5=y0=h zRDyK8D+^Za{2UDgH#B+ZZ&OB zq#K~payiJ7=OmjkJPSrPh^eSgGUbw;KaT^hvi!yN7VO|uLh`Uc3x&m{SoYK7Kf!Wl z9BAWxVqy=d%${u80hiZY7gu^Z%{)|tHK1GX6@1#V7iSCgJ^a5(_or6I=YV_w)O70z z$mf7oO@XG1#{wP}u+ch8z&fI&9kx@eh|+x_{Wm#GL9$25Hd^e$E&F;-4xhx{;2#^D za#bBO#n=zWs~z2uNOH3ph&X1szE&Z~<+YMHRUgrq=xF4>fO?{V(fNs1TO5x?lcV#L zg=(O6_x-+9x0xi4>1DP_2i^WDimM-qF7f2?rg0S5RWJ2_N5-vG&XFk69N zR)BxU;Yb~MesdmezUKQR2w%c7u|HEAQlQ~w0UiqbSXiFC{X0wYFlpeJ#E6|u920rj zw))*P@!K$bof*%96qam`}vv=1YM-2~-XfyepCE zU18=Ba{H5n5wwEIC}b%~RwzMT@%ujMesG(57Zt(okkzTXQ-dz47GhNRE%rC7TMrWk zTpH~1{dU4NG$hjkKz=vyfA0>O2o-RoQwm})4=F^i>fkRZ&quzG*o9SM@(2)O(wp_EBRbV*s}Xq;5I4Y*DS zQHU>uT*4Fk=kkwzz-au zt$pDr1_E7TEj}tE{o#C~gaL;*U7mi;78Bt3(zjgt%*5A~*PRc6nWk{P)4J?qYqaTd zg8|Gng%h9Fy&n>G3wDpfzfgj{KGgdcdTY*tEmF7Tlp9-uQVUgZy`>)=so-^HIVUy9 z?Jmy}?>*Xpk{3Dj7n)}p(e_4NE)b+W7lkh93dV>k&ee=$T9L>PN zSPLu2aL7>~1+{VNW{E}%f!K+FIAgZ5wB8GpNH5lXX^&P1h6?#{FD{NuF+t)!T@PWF zy+I4AaJE_>9jM=k^%b$7Xl%y}PsQV`m+Mi>L_NAEEIpuJ%^iu`zzulNffJ$jNgBm8 z(66$FkXkveSq3Kht|8k9ZRo&@-nPjQYBps|VLv3^pA0>T1ou+jHUAC1r7Qu@JQ|sW zeCnCVkS;K++Bi^N20ckz;~#5;Wg69|Y#m%8HjXv^)U}Pu#rFd%tp&iC++S;0qj{(G zrD_W?XbUQCP`8ag^>?QMM3j^@do5XFvt?%k0vP(la0QoP9{>ZJ24Iq=(E;trrBrNUEt=u=-r4V^W zwH7IKKV-lYR2>zzcZA(GY8hj?9elsK<(Gl=eHstkVpT6ZO%iZtKU(3E=e_);bJjD_ zX^=`bDBi+X5XI1PZIeW%l}sG(`Cq*}5xyQ?d=YMKSHX@Le3Ig;QUDjO1R=*$aym#4 z@sXM(V@%ER^TlYhJQQflZF^-&6xYfWtnk1~JoV&}j|c06lb$E;QN5{*JUEe_x#>h@ z;N;{btac6^78KWsM>fB)|1|8uS3#0)_rb$qZ|f}HN}x}S{f6-ep5n=rKWtzFra;fl z5iZucCkU-=i?ep|Gbql*LxwLieB>kcesD*A|D3;hVdaHv;SQPHKSD=5nsB~z0DuGm zdxKgO#7>Wc+lzXPR$w`#S-&GjgUvIM$Hn~7dr!}Q(1ikO1?|xV1Ns*-O_Wq4!GR)- zJ=xx~)AyVY;g_>^Wwm-K&ZxaTCp*la^A|79y(?MUiAPy~9Ynxpp{N-@1*-aBI>s7( zl5t?XurK*bj4&I{5Fjri+(A{=Sn=*|dVI{jkaNfX>hYOud-^Z7PcZ^ZW5J_&-Cuh~ zJy@IQhCeU;p($h=SI=+6S;L}rp72L?!l*kwYV*}N$Ngh_YuPK4hXigwCVyy!hYNZk z>Dru!qWGMlLn2YDN?v_l{c!BJCC(4Wp%tIPE|xc>B0aQ|<&mwy_@D5JE9 z(7Q(iDdWxvNq<^he_r2T-UT<8_vaVq_vhbyJiobI-=427fBgI9zx}swfBg3`;CaX@ zKRRNqN(p~jeLBCsT(2&_fg91m>eI&$S3jB1;SdsQ&+nE6iGzt0#IWAnFpw9Y@6PY9 zK7IUVeSdy;|KsJyiyv?AE>~A;miFx^aOu$?jgJXi#@k?G80e25FIRv!xVo@I$7Jk; zDSFJBRGL*Mfvd>%goBd?MpcW8>^KOxsgxzxX1eERL<`u^_n{08cn)%@i}u)6-VzBF}ribC9! zmTSWjs$+hoiay*lmn%bJRdudEtK8#`@^1neqc+x!d#4*|fK#4`@^q#lNx1%GGMx+$DjC?tL?s zB@u%NA4UA>^5*J3xWBr&v_ZtX#S`G-9if7bcYPxmhi{lI39xlh>`x!Au3?7CKK%R3 zZ`h0d@xzZFt}d@H{yP4G%jC=P5CZc*-Ch3j`Eq?9{Cs(Sae4R6KmPh}fXF+;9Pu~? zkbAvjGpaZ_{`^UZ^FdrQZMtJ$%%_iRq+5NuxD>OcZ~pPE4e*C7+@1n4m(xJ~aDD#M znkrgNV$JvCPb0u_WI6Ijk22`1$Fa)v**Bn${oOav#E0JiG=BLsrXF)aLL(L=yWrp2k{Ow(%j1<3t>5HPO=ltZ`Ic(Wc15QLUV*u3vMcK3EGV)i<|Jye&^R^`KIQ)q*HFdN!@hY zZ&D+t%{QsX$+q6461|N#se%2rn->0&H{G-f(3YE41=EI`RH96Fd7T7d9K$*G2{hmW zeIc-ce|LHF>Hab}zqnxQ!?g|e({B|@ErvG=r9Lj(gqr(&mTMxU974_6_G}RXQ3*k- z60A=1o$8Ou`f6F_M8CN$M5p}?z@o^=#u~JAshT$BRxuvon3{YaIiEKPU5kfJp4aSs zheaUG(krL4{I&}c9lOzEeM#Uq1f~Oifn6R?>u!IyVKzaW+BDtg7B+b(P5i~pOmX*H z52GIt-4;q*jfv+0&p2WR=pd)60FuA-E;u=0O412Pi;ir z@3Du#gOh;pEA`^w5kRzOv}O|_xzO&o>h_Xb5Yn5p2?}p@c~=; zf3Z{s4H~t^k0;Or4J0+r%{tzm4XT{7-49J$MzIC#Yi2XTBsnEWp9%F*=y`@gNg!4W zJWl*Lxh62bKWSb2T;l!p+5-#A)wz1(%@*{M!>2R;(UFs*aeD`1Ue=G!a%5G+{0;?Q z%xOm4|7_3I{E_iS*C8H+K%StH>WL>|ai>DT16pNsGxBF}0$ z1mQ#EPDFZJv*Zc-^#X?-T^%K&m@8W0{p zy0a3!=s!n>PjP|VakAMJY%XsO@isZ}fK9S}A^V;`d;Hm5mF&duGd|lG#(B*7rtoXk z7U?8S%UEtfG}`b#&taCvMgu^_@lubjpV9`v|0r_!^&#(9nWAD6_5$>!2Jv`@(b>mh zrMtp~6#y~xf#JpVdq5!~TZ>>*0!Dn;Y;yg5Se^97L05~g{nUbmIr2vA1w6@XusRGp zs$)4F3a4*f1k*wBNeS+PcnQB=WiGuAse0E)`AsPi)5gNqA9$rShR z!KP08zMz~B<$NKWM=tgo;SgdOFB;alCkpX+#$Hnh!Y%g|DwAghCQ&gQMMsUCQC+FL z-;`faeRc28wy*8bbe~&ls5^WZEMD2&Q<_zWh0c+qB^bp|pdLcYspEk7`mszO>PhlB z=_ml=e$W3E5Pg! zPC&<~Y@h-2Yxd6-l-7~16)>W-0oU=XYXZsUk1(;f8v;e?_LBw`%YlUnHLhlk+Bv*z zv9YoB*Fe$U7Z`>lP5k8^%!<)@7d&+o7vahDcNu=(LwfPBV!^TW7w@>$r9t#!02621)Ev9c=R(2$blmqnFX zjjN#CLlkZ@wIN0pRAWTstDk@~p^+la+U%h#JE1BlZ$uBsA6J6AT{&L;3;y(X98ARq z%-!)Skl6po{~DmM8a!gP2=@5LfaK+W9uEfae)*r{0Kkz{QiNsCTlj)NR2j-|3t+}O zpYw5X5zWtWREG+TTDHfmEk(qSMOZ(M1A6tt-B<1^Eb-I$XsEh!I?pDMn;(4R#m$-c zq=@FH1uEVYD~Jgl9~WFpQZK~ETMD>$QX!%SP zjJ->UH(+roV&qA2jlp8~++?_C}@v42d+ zEL4DX9P_b2YUAtrM(ObeMI=_*7&r`hh zWBLFIXKRr)L5rYzG?-N#Qx~607aCQhk+(_emn7L^Ob%hzRZY?ke*J#hp>(O8e1lkk8=8G8q&3Qd-jYZ50g%}aIq%D0M={CzZ zVRm$+TucvXTlzTCJq$is`ZWpQ@a1Sor5GQ@NH%2JE_@cW(TpR966i80NUA6-z&}P7 zR$yWmMiWLwdtE>fa$zANr64OKf1B8&xW05WWlD2al)d_(4s<>72dl03tldaP2-g?TTLhLZG!Ya1) zgixqW;d5Zk(`*3;$t#-HRKE*yUrP6_0hh@(b-mEJ6kL*hhkx{qV#DU3FKNua&n(@g zj&}|_#72y=yw8Qb7tbC(+W>UiKUfIp=;lHLv`V81on%920gDyo9proX>75UCkDJ58 zgYCRn0zn!x2+sOF{?Q7zyT#THs0k!S(VQ{fQWNo=2j$z`=F>pnxAM#=;DR3l`Ssl> zlo&SK^TH7dQJJB{u)WUMuP#3N*YJa%tmv`84bbYabHqYaW-NmJihk%pECI->HqVBx5aQYq&AAn<$w z(mdYSfy+6X{NlY9K=NASEbFTq21#ZF!AkmEz$Mww;`}Tx)35#G>>eto;Czd~><8)d z4G_h~l@g%*=pe;zm?F~zvr3`^7>NVa3cSm3M@YxVDg^r-!D~u~(Mw4;sbRnKKMQo* zD^?E-`=ldx6hLJK0@=$C=9WFN&TnWM!lS(NAdyXOVDIJX{!EY=p|O%a z7x2^S76)gTB$GAT$V?DDnJwfMtqlknU){ERqKyHub8yo}f;46*s$(0y@TqG*B!raj zZC8HkiVs(M(xJy@c*%!i9?~R>VcaUi2}6XIg&kU?@)f~|`vauH@K%Py!-rBo!gt%u zYHqW!QvfNYGdOMKQ>O7H47i~?u0y-NMg!jMP$?g;q6meWOoLMMm6@c37Q7AU3BzMU zX9tjrz=mQN9g*`(vYCK~QQ4sZ_E<%~B^xaWa@Vsv)Dspk9bzLS^7kW!kvpOrdL9E< z=Mz_geR^A%b>1umFX6Tv;q~ux|3+ktj83YnLTAj9(gv+`ci8gxs0GDF;X1_dPHvDI zIwPR8@P!CAl;4M~U{WnGya$;2X1mlzV7%|>z16=#vgJGd=Sw|hPA19B7+-3dll|?t z`_7z9i|8rMGI2WU21=zg;)25BK-K#@dk{y~Mo`n2f0F^WmVlRN``E(VUHWNtfQjEB zbe(kwDkEOW{+fR1LGUXj;1hSkfpnMJ42s`P0av$N9NkF)*EYjv(mgSa@7wCcfPGc; zXLNqofcMl)xFec%v>WQDdax$#0?b#=W=9}9ICi6qz>I|Z(i{>f*1|7kSQM^bxutws zJo`chUpRwhlqD)O+=8$Wo44B4U}hRdwBdq~5F|0F-@}h@z8yrY*mfVaE)`$(LXP62 zKHtDD&uG;~`2nsf9~&hKQ6S>86~5PiqNAq!hy2JeUw!hZ2eNb(%CkMlwgJBl4{QKA zDrOWRq5cFP`xY{2jbH;K@d+^vM3G{HQqES73Q|as80F;F z0%8%CRT5*+_v4`B0x+VxcZWXaQl4cUbW<4W*jEpc< zl?7EEJtZ~vjK_h;g!o(tc8T74eXaVJwy4Yr8#UJ8Mh^I-=%9-7;>hicqDFxr+1l1z zZ%DJNCLH6?Rw;oA&asf<@W#c!tOhN&vV?sYWCCoD5DY_;|MOqzrrL?{`dld8uk-lR zB|eOOD?RK{mV_mJ=@ZO4r{hP#^x1R4n1Y=X+Fd%S!E*yAryKKi;JMNP2Y+1gCnxw= z4W~lQh2j5BAYp;VzVKZEdCuEy)A35VKFO5{YkYQ6;+bEx3he#z%vXS9au+@H02jT? zz;UYIIJgetv;-SVzV3E%hBQ$Noc}8xwmAS=#|r>W{sm^^7Rx!$oUCJ+>~hv8{f5_7wWk8C1tsSFJx=>_)Ysu~e9InA5&`Ji zVJ-*21kHW=6X5a{X$Dh;-2EOOl9+Fgan7IMp!1zxG(>jVg|1;ZC0TGNSTjh>)*Cs5 zbV5O&_eAP=(iC5c7#1ymfvkBmGL8 z$J4|Ekf=OtHm>cyPjAG?LuKL#h!BZ=3EpL2Q_=`nwrHu~9CfPg+7K5@;6$ieuyOb| zJi}zfHucEX;doFvro9_KpjDXCeZc*eTmw}nfago0z7V<+Dc*TBZ2YfC0}mf%3W3>X z-3yt9UkJCoBBDt7a>!?3L~0kLTpTusYit?6V8S=Q<>ixqwf9*Y-okR_TN7?g)t{oK zHI=Sv^NG?1(UbkbDccCy9ay%EP3Sl<0EHO`u)WK(1TMlSG5e_C+K9OQ;C*xV{(SZA zm|YV}nOSYNK>KxNHD{YLU$dMOX+x1~gg0UKSdVqsCGio_G%@ysEcLF6_l-<`DD$1N zD>3wGw-s=Xr#%fjhQ|ij!g5RoBxD(ENEOa8y(r8E+_aG3GO5eApvbc{dZX9{^5Z-1 zdqBzlpyXc4s21dEZx@26_dP6wz~OLmRQ%+2Pm$@POi}sYb=b&9iMJ?YrC}wj1Uu%FsMVD@lwqU5E_}tpX>yXLri#;~*&vp?4eqCa(=fLMYEzwX z+dE)h2saAUrdr{a@uZGDP3ud<8CyTu2KI@jV>)q~G%LjL4WN5>vN?q5&Pl$?0mJsH%NM-h*1?DxJ90WR$K9z6}h;(ea8QL-(hhQ0m}tbQ!h zhkAN{(5E-ULK4m8|9hTc**&0-5%?Az0ShuSPATAC{L%su!y6qZZstSUFY@P#A$A+uzuKEunQ(*t!R5iYh<*@>rhm_k&~Y{pwcd;-_I@>xW8_ z&nd=aw>?lbNIFh_-`GG=Md4^CpVyBtiAvbIn@zOOBu!hqLIi zEuk{rvE+OuV#8lkN)Lo=zWA+}BJoaxBinobLtEsiEvamuccilmzG?BbW7}9;~ zgm4(G!G_ZY?SL`oFxnA=&K}SruWB&nn*Gi78CiiAWXdOaOGy@=A$PM~2=nrZ$XiSd zaG|U(1>T~=ZeiX&2-9WVKLWJN#;StNdjYaW|7%aZyh+nJ2#3v#7k)joIYQSY3Vqb(o1W;)jt0qi*04Z1KfRJAzNiEBVSe` z^5wM>__7haPwJPve8MY(RS|-jUsy+vhNVh_XPABfCK33Oz?T9)@+|M8$ntI|tB>2l z%~}Ie7l61yG~hZ7eMJeYLDu)Pt_K_=kolu&LEoc<>wj$k;2%oZ3W`!9Mc50fcl`8# z9Q`g$#bxVyNwf#2WoU<}``m&j+K&(*8ck;j+F>E$*n^K7(}lHRTpcF5nOX;)2y72- zZ&2OCXU~6X;0-%$Y)jf0{E?~Tso>SnslQ2 z;2t)c+#F771Zu;;3MLd6QKaB^J9g9vyg4mkI!NpHoeV2|iA&#QK#yEw8wMl3bf*VV7k8%%bZ+w-fnl@<6IbH!P}ZD)&I z$S$cK=YRxydx$8Ff73Vc)8>mt3B1HUH%#XRUmET1;?J&NJ z(B?e5i_`ij-UNBhH?;)tu0!K4-d(=m&RWST3~caWahj9e!0+)HnaUl#06#ho zDBlg&BF$%G{@sor77O% zm)H@rOzWPDeR>=3NS@J5X6sk-S6L$fK4s{hY?qY z4b!bYaL=T0$*-=b?rBcNtzz&S48!Or4<17<0(;o3+!+d^knsa^XU~Sqz@OfSp_G{i zQ%*>y9Y>W(oX#5I6b4pMp*Z2ZgWux=o60?Wse&}zof@Xk zB5S%C8tB(_SEeL}SE9f(0YpG!iea6n%KCjETMvDT2zf7Vy6M{t z#Eq{OkS;HY@-Nl4;XkuKNl+j5`y`vHfy%>938e5z>K%Xd_~Rf{3~smkGOPb<@vwqQ z6rvZz$D3aa9bsF{oc25uuyw1RcIT!Gchu)b7peKGjooac+NU}(YgQzjy&X3iAwD959m@fMHQtXXIOlE(Gp zoTZh9k&P;Nhg*=*Vhit(i?s1o*UYJ(B)5giWdT5At+qD_X0`KaFl7@Fvc;rYVo9>` zsYxTZeRnXZv*+rhsNz?O2jZ(&xvwdZ1BA|3E?T> z%LE}+wv5n#w@l(g1XWH>Q%?j~i1nq|J4*3H$_Qkb+fl%sQ^AFaoe+5kPZZsB7-q%h zAp0k{JzSgyBp2wTz%~AcOT@(>z`9JgTf@DF<^_(UzqcEt9{Lzzz96r&H zF6$s1zx*%Rzs42Z{MB;zfyw!HTPE8O{N7(k2LNmC0H7yzljk*SGlussWXsZe=?$~@ z@>isr>qzl&l8w(~?9}FLF7W?k9~XJLuX`^KE=VPNbXjVB6HSVjw0ZzCyK}|tQluN| zp@F9OY*-Rj!}g-<6ru-)e-3E1Sz*ZU`7fwp=@ZM>i#1029DZH9?GD0G?t`?d4vF2z zfizsg01#FH-jPpCZzM2}hd1{=?^&N2J%*b!OY65Ds=ChRR|hbG3b?<&76QJ6_1E#6 zXC2RgoS?;@c>KcuKnP6;<=FUCf_x^xQ3xyDlLB0Rp}&1Oy#STM{fNEbhDA$uH$sttyYsg(Qog1zcL9n7Ubf#00wAlDu&efhRPayPKTZs2f6OfWSTK;wbd{Zu{@U)OSdVL7p zzF=!58hJjK=Qr{$8q7S;lX(N^Q>p$jur}OhSMzbcbHXW^w-3+dM?Qe5y*F|cWbM6_ zF1~%stIz;S?kFTa62ON0o&42##qzvl^aV@nkCS{wP4tZJHzW_C2frZm9S>#2r*rBx za9cyfRz-*S^VxI9`s7d_#(_NRi>mWo8kvS9OL3Cdx%kx;XE)H;iEDV_PGaQAgsxP& zP*p7;RI*ypkFKmZVN=b>ylk@*r*iv@x5Gr!5wXQ9KL4O{5KNiAg)T4{?Skr!^=G>U zfB`q3xP4&1J^G!6Z~39_%7$Z8qg!!1!IITA{piWU3#n{1`Lu*Iys3)J_~lys`Oqjl zEInx6(2_X(hOG!CUyXpEIez1i@34u}a$!g!xRP|oM?zL@_k5AZGtfV6+J}vX1i?S6 zMZO$l<|8vSM%Wx4@c!%H)}KClC44Rw`+Qq_9Rf#L$4gWYvY{Tnzt81;b9b+Jx)idj zx7`NfbhGKsp1r-7MS4oX)CZcr@(W7$Jfn68M6^C!s&l z^M7CSXZYi-Sy2sObZ{^UCU>M@1jhw%hZJK#So_@vb&@4JQy}C)L~RykXi(m7M1EqA z_ws2BtI5(~5Fr{m7;!ab4m}WL#9`s8ls9okJ}x$`y*+h{&M#!>{- zj2;s*qH1=vVe4mi;1C!fKrAXYks#dvGjh> zA#l~64Fn$2!3pAhWwDW@N7m39f%&Yg*iE|lxbAc$dSv1&&ErFsOsKdH0}G7eqa=ya zfQvtmqJ=~U)4%QuMp83ENTe%=7ahCvaa7Zc&*!3aH#;|IQIc`)Y0wL4WD;zNu^s=j zIuTZqDm#KT1r@dF0~GA`7JBm$4-6{?h?6a>X9L#RR>No5*jM*#t3ghuJMI-+KyjjpQ{hSZC~qB>;Bd@WRNM(`M+Pl(3&d@Fir zpSe7zWqk-Uet0=|mz9|L3bS8%l5X!27nnAO673BTAZtm51bH-pOVP4@KEYSA&c+EU zSzn;whXUIs34&i3KSSi`SpGoaaUS(W_llxG@0^~77|Uf^E5EXkGeF73%GFvsa|p$V zD~)jwdP0t2t>k$hMBWgDmn6%Ar+xmi*RRdSag0!NFu$<^-ru3XkWKy=&)$t%Yc#!E zv?>xl1Li@$y-ye_ru&LE5Y410!45cLExP?U3}uvz<7j*Jg%X}DF%BXT0fTKsCn8R; z6bPdv-(CnSFWh^`P90tbIAzImk_GikZY^6^T?LoPLsBOD zUKhiI4ux#xSka$hA^oyk5{?c$VT6149L^|0A>eZGylu@n{0vi{6%-s689cvp(Wxo! zpcW9hYSy7~R<0K!s+VGdVdkJ#fmkVtJUdW|(|H#pN_cG%Kzky9><~Ij7s0l?Yeqa# zF2+ln@D7NMMo?m6g+Z=B{(th`wK;JmX&3!IzrqpweAp*q7oK*nS?~KsoH!8};&CXylv9GeKH zHGqwjLv~346PT0@J*f;PlQqrICL2AnN=B-ar>r;CzsxN-tH_lM7^vz=;+O7s>MOGG zMbq^C@40cTOX^;324KmtygIagv~iCUjn^yTxkx#UuDq&>60f0D(nCw=#a; zI<}eWd~Y^^-w0Q®BdgP1aI>^c8`Enw@$N>Ah{0!ni6A&Z-Sy(Zh*!lqiNjH_K3 z;%4;*s>{WQ;S+WPbf&}&3g9+wBsQ~w*ULu8ie(5`{HpM)S&wP!ny=1#Iy$Eooc-(^ z$!v5*FJP?HdJyzU@T|>~-Kd)6Gjjmn7gu9UR#ML+?wFDL;=3}el`f8ADG8uCyMDD` z*K3g#d#chwRpIOE>bB5%Rh6uWz7iVNl)cOX15HL}g7xZm&(Ct&MN4%$BUg(_wJNb& z-aunIAOFHYN_K?2F8kX}W6d&aO8K`NW%agidO@&lqCT6Is~=9tm?rW*PQ89utq|=t zP4s88a{WB9UU9J51{kcSf#zIZG1kUYv#ScK#ju^34KZXgCm=GrJ*y;O_s@I@w=450 zbnTaBT$D+XBam>Nyv%2Vt6RLTfGms}K?$R*?&*9{uS~WFxuf1@#Zf9H-|D;)*Xxl* z;d2uxNj9!~F~6%^0ScXTS4hBF1oV#3ME_py^i%|?q#*bI_5V(lh4cN zA_lXZGi=p2SvtnA7LI&u;9Ov0W&P<0zFGkGLjPui`&Q(H7QwUYcsBS?37H$ICI~9z zFhJP0xPv?3(dBoj;e(V<&09ZZGE%tYyvLJT<0mCjZ0KzRUZJcanEXa9xUkbK+0h(Y zeguQgVN=A_;u6q=?`Pw?!sU$`pj!f43sM&x4k$N;l+K(gVug|EjF_L!Q&R*<))W!n zou^vGe7;VEbZ(%(SOTB+ViRuv?jhR3Aq{b_V4uyIYe7JBWoI?|g66XFxoe(S zuedlqv=)Gh5YuHbuPdbOnpDyxms}T9t7)@p(%emQ@f9%ii-}`MF>U%Kj`jcd!(jIR zDnZC>=F&y7zZ2|=D$Z{BDwhzz1Jq<(HKR2ff)!-f467x7nT} z!-GJiMWmph3+JnUpx46x{T;kfjeJ5GLq~Y^FQHT@KP_Cj2!QqB6ct+qjbDfbFGIx_ zVPQNdMXiNVp!TCXVXIM*0T6jWi>)BL1XZ@4zd#D~Y!7NV7E9nz7Dkg6BQpHF^Kx5| zv``g`NR~#y#VdZX9?-8Q4f`Y)SNq48T1~Olq`8vh;!Cfjx0s4NY`6Jgg!gV&~vYgdAH~>m2J8!Jh1a;-gYi`tK zXso@d7OJ!cM_GjGTAXr$FOcl64z4{#a(L65UD2#TYKuYC@)s=%Q3s;7DnwC0#p;l9 z3CyYHQy9ED;Q>Gj#PEqC^uu|J7m61}*9qM?(fVLyDIsZG4>Va9)dB5wMNyi~^}N(g z;J+OH%HEeEri5s^<^#8RkN)i)j%ZH`NA$k<$5<0l?z{<{+&RNC$;3oZZ&b;q{h=fk z3d;2l^r&j3rt3~NR7g@8&{xW?IIv$B`By=aW4nhJ=e`uNf03r^7GyT$#UY>y1PNT} z5FzT&(9!kg--q*6BuErATzdG2CTr@mC+adv`koDITI9RZ#eQ?<_NC;3TG9XKC;Detee29h=^Sh1-@J+eNdce3(*YfolL0Y&NhiA;yUxI zQ`M2IcQLYmZ>49E?*;Twzq?A+mq=B=@>8ZNq3}UVA=`I2RVumug+tlIV!llI)6{~M>^TA3t;gp!On$$z?)|J>eR4qwL*`6Xd*+N6Z#wBO zhlU?g1)?0vs5VfMZVaLZ2#cZ^l|(CQ9Dk?*tjbVEHIa&Hg(rs%kR^x7^L&Npe`yp1 zsR4w7C`!fQlFB8qH;|5QmZR<+miY1(zQ8TdwbKr1Qz;edwdACo+g^Q>$ra#|LnRE! zp{##ltqS^S!N^D4nc`l8n&vtw$#)D=OFTF3yI5CJuF&JC3|6D0q$I{obr72p@9Hw@ zDJ*Sw{0J4OH)QmMsdOH4Vr}n983MhRYZ%|AC-!eG58Qf()tc7tl;vnuRA-W1mdJ$Pk^j!j*OKV+0S7sC6U##!kA@pDBVtXp<5^+GQqJ35bwouk1!)+9W z)=-F?p`|DjZlfx+Mm`W6TP2iTGgLbsp&=x_=BRi`qFR{Z-W?4g75jMdQgY|&>T~Dm zSE5Y(OFQ8*)j??m;MCls-#9>KWB4^OH&r8PHVxp|7`N`MyiVlC=AarrAeHfE8>1-* zTU#8UR?6ewrldJrp?mLM?lMzm&D>w6$@yh%w$&}|{HkU=) zu{Ki!g)C#Hk;P3Ouv^Lyhyw+k;vl!WZIvov_#$9Pf*0Bl{%O6N&z9@a?rk>gB(_1W zt#GwA<#$nPp$%5gO$EdjA_*5sgvdd1?dl7=c6GgKXmw6f`4bU$i6ygf=E*Uv7$!v;*YED$2Yl=T&XJ z;2j#L7)ZplgWZ$o6h#$g^)`1uRg=oV9R7LYvqa_= z;JZ!^CB2=zede>N*Qp#Z+$^$`r8AhQnhGHxiw82sF(sjzNY!JQos@E}M0ysz-XMn=+D~v#INw%G8@M8hNfom(reYOLEbgJj&e~#fpLJ0 zATYwxxwT9Zi;N8dhyX^?z=#B9P&rq67P2up_T}lxz3ZjC!QxgZgI;Pi4xhZxrH93Q zx)u^m*((IP>;fG-8!Kv=uenVd#ZXd_>d49*P|jAFE% zK~d&_6q?4$Oryxe0klqb(Fv`~0V%ZZJ3m<9JsQ@*=j{w^Ll~8oE$JOacCr37EWdJB zI>z7&7j>~NI@pjpt+oPhAt|K{I?MTh$<=s6XB&iKE}$Eu&pOZCvlY zBinLH>#XymG2VWMw`kGuM}|plH5QGZ_yDM$DbemJa$+|1Kk=grGQ8|pc0nEii2c#k?64Afv7$c#Xe=_y>KCyj}p;zmcoWv z;Jmge!pE@q;1AEJ#0ww8$|)s6X~u94u)lR#WndnZ;k>3Tl31ov!fhB+Sa1n`<-%Yc zk~hoS@evE2Vr{pMZ(H?`F*oMPiu7#QQ%njJ7g@)8LEj)FtBgFQj~V-Q4{l<&7q9F4 z!NR`u>K|P@6GbKV&>QM{xe|9AZ$-aByk{73(N8yC?|e9dn%cxrEzfEwvWbUTe84*tAP2jc zw!pTb8^{&#t%J_#D1^=Knelt-fk(Lik+-F)*G}Z{XYweTkkOV~6p;hQHfHjcS=D-o zLW6i4fSpuN_~guDSxhcK-#Fd~$ZIY)k$?QzN!27!y`kk(D#tW3HPKRUVA-@wE@vEC z&(H3PIoChdw{A~Kkqt|B(@@-0j$Ll=r@8pLsI}$#`0nf_ofy_mu6wf!VPKdHS^?86 zf$#XS+EXX;!L>XfQV1wbK-wI>Ity~+d%Tx)q*&jg`o*3WC695&w+Ri}w#(y0WtpME z7XzDMuC_|d_(5wHR#m=)*2dYmap{CCw~6OVO&ZD-qAHg0JlJ;KQ=7Ich3{V6XwVx= z=4g2?;d%vMF)Pb+V7+!2iDcFU8JZN>WrU1#sBNjxl+7VPi!d z0`S_Jx^Me0dB_T(Q>*us*Vy}qJY=P=VyVMgAH0mksfZY~s`-?(WpV}uDMV3hJHL~c zv^bsfjDyNAO^Sp#d}lKPuo{qdxp3$NB4=80C+182WM6-Y;WP`IW`Na#PRvGo?l6mr z$#B36osU!dLHr!-e7oziPmqu*B*iqq0WW+$6EO+mJIJhC2U3IxhgsQb*)bO-AZlq4 zZoRUPh5`d`ZW!+@ZyiK&FZLoBqM!m`qflK7t0n7G07wCVLd(CgJof;4=|%G-Pjyo& zW(#7dnC&h`|Bm%fd(4*au%+Tkrp0X4PNIbrJkuP?6mJ5zlD)d87(&IP^?$nCSJ#&v zMvu)x3YQEDgAOPOjAD}1jDkFHA+4R1YR{M|fd3P-@|N#FWHor!+98Ny0x*>x$IdT> zuXTH-7wD}wdI_lmmZ|h>jm@phsy=yA`b0?IU7Nk7;YOGbEMQ)O``+Yxe9}FHvvjTRh~SW7A8t zqjhx4GXO{$0O0^ij2EBMJ4F7NMn1=kO7_!NIt9F?-DKF=NY6Sd@QhxP(pzHfa=e%& zIO$nUA*ZJKD;f%DP-;yA_Z48;PRxUh;@V9G<5DXW(rtn~meTKj{zi_(`4_`c{bFg` zf+FRG=1kd*2EVcDWs{wn=6)t>%qp0jRyQXzL!H)_vLrJ}%^h248o|9sbZbdkX{9;b ze?^H+$-b{%;yeEZe4Mb&TDWz9t$Pwfh4dBa#&w&>vcj(A1~P@MV~G_0z93u*-&BU% z3fMX*0f`_aq(Q+Tu@Fic^hk48b0Zzl@J6QXqa>9DxhYbF6tSz3X2)%ezB(g91Ysgz zbuQdmxJb7ifBjNQ<(>5eljKl{geVQdqfl% z@og=~XCgiIpFW}2MD=w2n@A2pd#M2=T1mtK#nN3K8j5*3ONfKWbYXwGrfuuol&lrTXN?Yaj z)$?F3yW_Pvc@ON4SK>KU=$&s)YUh;2$O&0EGTTvDp5 z6;^Zi`91x*x-YTUn7@7Ut{aoK&lpi>s=%m!s?#HUBa4Qf1M4j5H|!nTtI1h%*uWh} zk{R2tiBwgxxIEd4l@hQqo1EoP=4q=mc9@(chxOf{oXlry=V4n24{XP?-cyGSxWmjq z8Ga&bFpfSGcQ>;7LIJuA8_0T zzOXeMJu%l`Hx>@yQ4zZAF^^w27T!mH(h$n1NyF~bp9RZP-zYA#n=jKlrQK-=s&?~* zgSvMAj7-nkJlR%iOva_znR_sO5~MujB9Y#VlAjS_`BTF)Ids+hWX$6uG$SdX0a<)0 zw;8!F=yn|{&CiIZibVcvc`vXmdcD!6FsDUunOEMJR4#~cL6|RZ1=+Krmg)j>`adJS zwtc}9`EOr{)hK{mTy^a6izJrf_z>GnyV~Xiv*o>6U5&VwLWmqZ$pR)Rz?JkCn0_mg z*m58PC23Y^nr_1U&Y!sca&6hcPup3!*kZ&1VcJ@xA8#!(rOaz~6IalPK$=c4JFd#i z;3H{#mE2{FT%eGlFcJr|=O{#zHfj&9!Gn8X1qPh6rZp~CKu@C;GelMFfha*9s%gv} zwG;}24K8aY4aNm21CUiZ4W%NOgQG78cpiFBf&CuQ2DbLooTf05X`;}B znaqMD8IZ~p&ZH1{UVFDx$bt%K@bH#!#|LK5oxL2)ru?yCm2l zoGLs&L2@c}7kWG2iB{IuiMGy4i_#)BKyONXBW6J^0l6fRB)msu;gdZ4N~+6f0Vh*` zr^(AC^)d7wx!eVntHV8vc$JMl_aLht$1wh!ja}4WNm>R z)iJ1!xG5caQesAL>&QL*%!*^)bpwuamPKG#X+*5J?YJ)Ay;x( zY`EA93{u#1P^(;OyP-Wbla)hMiae?hQlUV1YsMCCz5qe)R-E7wcc#aEH0ZYm6XED5 zH&NAyKl<$Q{BXO3N>Wfs25KdfS;I%OB|jN$=@Iw9$9+ubwhGHQJe$Q-GwB6mG-={SiY~jmDHyWHcR9ZjlA$G3Au`&Bm0e z0FwoH#kE|P=h`^h%C}rKA%V-|TnOonDPj3>aaRrXNPKTHT~S0#Xm1XRgQV$A?E^Jdk8W?r~)GhWz{@Oey+ zwZCI6^O6QcBp^jzrhPc#yuh*MhY7LcZP6;+Jw35nh^aJDq-EmY@WB=>%J$VHuGgCJ zhnes;A(4*<6#yq$;FZ)C6M_z0`dfFw(>URGLaydh%9PM2AflfExQjQC`S$!Zm6oLO zL;eYVYeM|KrgrG(a!DeW@BYtNK=v$9D1l;fm_s8YN#ln@A-bhPz-#o#U%#fg z-1ef)`I>8-6^PwBA>R?JzfQhuVg**EW%LTf&YTcuoAj60XN0pTwE2$Xv+H=a7K8h8 za9n?1dLv4ioP0-_p{(St*t|Q)s6!GA^TQTqoM;d1cbR|<-sF0Nfi0O=&V!($W;BWH zIKRG!ljLz$Q9Z;;Th~ux0lCtV z70QwR^Y+|0k~Hcvaw{mvICShSy|*zJrbaH01=zD`OE2KTV?~pI3e@Q?a8cJOQMeQ} zC0Un5E|Q=O&ZPLx3v^|il~^XIQtvvH2#RQ1nD$@)v?_Oko|LD3h?-P)^!W~ou+H6b@ETN0d8*_31< zMs`ye(HZ1KAg`migoYVUMsj{;Q@;N#tdqwcHSj$DL)Bn!!*#;(14yG5bv*iuI+{#3~CEJqJl-gu+;aCfhCHHJj zhS4vlAqp2J=Z#fb?A?G=-qOvjc+^=A-dagEd39fCIV!#H4=%+9Uw|T&Tk9P{P~>&; zY@2+S za~ja)QBG-77Nwko$TIMRgRi5ufR8Z~xfCL&f{*F8{*#*CWW>gu*qZ_(J%n$Lxq`4^ z_$adkIG6W{b(`WGDhK%1r~3G^q`JaL@4`ydUuV>_r2%O?s^yX1g_TGrLKp#I>@`qo z9H}xcUs$SS&UnIrp5KZ4#F|fW4$sQeH$Wd>lcipge4Them9spyBRw6-ki6^;;n-F; zx@<`yCYrA^np4doc}t^mOcW57hbzcL8%~?15U;iYi!wejyF+->)r)G|TIuaV7bYNl zs5@fufQSio$PCPxmu!XEdJ{5-mHK1o!i3Bu4MRk>YCcoLw%+;RQrxl4Rp{7`AG?+p z?QJ$t-e$ly7Z!-j?>V^9D?lNVq(tuRKq%jPpkgW8h@iLy0UNnbmS_1Z@CAWmGvF?t z`z3=mM6jYo6~B8={0aJ#zS=EtW+_v|El*bD)_%uIGzyPxih)^DykdVq3M4|H@Yn_| ziJS>!&jgY+Ay0cfOJkmY{KRYeKdFj7sE{e*mRnHKtW-S5isJOPK1>kjk>l~Hx$=O= z3I&l-g7GplS`RH2l$PGzn6`oBZBQ+VJ!Zy`Aa*=1gw)b7%nB-CcZs>SaKa2v@EHC* z?|h|iL>c2!G7fM{HHR_{k_bq`L2eG1WB{wuo3VXPPwwFu{@rm=%{YyAPmzP=#gDuv zcJOR@-jlU@RU6YTBbLd+I08)kp_DQ)xO|ZvmEcw7HL-uej>b>y;Eu~v8ROO_SBz*} z8p$QN4<=g-<{XuD_GY6EzI?)^m4d)cY79BJSQdh@e1c9niP3=6G82Zu>Dk_soRo)S zy@C*fBKv}`sW7=P0R*cSg%}J;Ex+L+2^Aw(BfxPD*NRpT>9G!(w1*gBuREdgqaLlZG<0TX(+z;#0y8wW2b zQw>=N3gb(e@_PVER&y5Fl3Dt%vN%%?%9>HIdZn){h+Br06~co(yDR3qnQ!>#F#u_< zT@+)e`!}i(?^0;x?diIvx*GIrq#VNKK+H3&j}@3n3FbVjrGyL;;fV(X9^bIH1mOb^ z{w*cEA@z3f(`WP3G(k`vtfDO~@vW!0nxT|zP;xNSMG(mzL}hM=Qryyu1gBFfZh_)d z-oT}@=~om+1uQT2t&Kw;h}9aFpjt}1e~w;d3(*mQPMN?36VvzN2^I6eja*m?+kZ-B zLRdj7q~e4>_O0i_hw{Ms*A{abcw`TrGIg}Z%@Q*QQqDn@F3TMY(1;U#XGU8ZSwqI) zjEkry>!%fMSo{Z!qo-DlAQPC6nN^L4n^{PQw5Gg$0uPVtS6bdM&EnGbVS29^D-98E zR7O69Px7vKppQ$*Z-Ya(!e-(~xza=~BeVN0i-hEmpv^@F3^1FBdnyB^e78B=`JV~ zab70aoJwm{jZCYMX&q8`=UR3V2|D13dg&3f3KRd^!UE!Yr`O^rf|L1l+1=K=Ml316=aT5%;ktqx1`suHz$Gk_ug)BDE1S-lJ-6ZK2zYvPk+@5 zGepH=TpsR}%?T4lJ!`x5`1m4yEBKC=*z5YjT(E#PK@^)j;pBFvfDr&D60Yv2i={a+ zmfxrIuZ_&4-x1%oLi?;tl%N*N{$$vn50y=0wIF#7J)OfdnFU1{sQX);CoyN2e}-o1 zzh1-vZ}+f4q|?7tvapDNb#L^?%ljWqt=su$YF`5EanjUAY<;f6!2eh9bT;;3Q(1r- z%%_vTPj8!9W; zE`V$A&|R^oDcJcGpkCA&K)@D3VAz@DR0TiYND>G{Kp0FPCre|l0sh6>y*Q_Y&H#Mr zKYlSZV6Um#;3c#+r-Yt>xF0Q+(=YS>L!AjQ$ri@awUhZ`#m-Oby>lxB4(@!32pkJz z{>_-1U;0bqd;f>BH9H?72fEL$W1r5HG>FH^VrC3RcO#?XN&aQSbbZK%r}z>pbXFVC z@>#8&jRSq=+|xlbT|*}3N`$Y=61fyW)BFVTGru7jrGzOZsPt|$HV%Rg1a}S{l|n!L zAn5d#6JTdr{}?VR&|CJ#^^<7{A&Ivft<`JE1AzIG-~gt*+icBfZH11lf*!Lc6BJps zgsJThzE)KBYT$O@M5Z6art^zFgWX#I`f0Z$CQScuYAnpjbZHuY&=+e%oBR`%2G=_| zq&z}r<+yK-ZGwyZuzMSXe!}$6je#-xW(-YZKA+AT+k{(bPEjO@>bdccM`N*UTrjb_ zaf00mo)DpUIeIXrk4>M6o&V|wKmG3E3&oF&`0rP5zc&Joqr)z24Dh}8&XD8quhcrC2UB{`+yI2uEqbOB zUBWiU%@@#j3|(sFOY9X4U=R=iyp$dG{&D@6;~-DeVL#0wQr&RgA5BJ+FAX3=eZzel z-NDYNq_~%({&@7yX7D}tSXF!vHmVxaMq2}z+t4vjz%*5xWqUR^Oijk!&DL|+5KXUk za88=QGDLdtQ>uv8AY297^3k}Kv8s%^iMX08SexkuFZ?aIwL&+7OSsM;*!G-%7b0fa zuVr>zG9YCsz!zaOMIQb>%;+bDp8j}l z^oKv#Akv~0i&wDW$vcPglt>2s$-o%5RHCq`-}Hj$3Kk`Xi&C;f3t_F=!!w8EEN=^BWR?LG^cmwyg&KUP7`qJgc~=4F+S6FeD@qaI0+=eA?D= zHsAx>TDg|b9CmD|+Dsxh?kNaxQ`;8D8tC&5d+?yK%{b$|`-dET2x?@4; zCRt1*gNY!fucPt!2=;EA7Ymw@=Vr6S^@6S9?I?2!M8ZDG=Jc5fGnY`YN03oE=9aR#_7Tt3Fdt6R@= zu*GBSunZg7MIswuOPm}%(%1W7I=LHtd7R_W;dD72Ovi20lKAsGKUxxOSz3m)dMuW6 zqyKOSEpmemwg4PE>eB9vastYY;o@K;XitOoggw*tPiPDWK>);f`h|AI$L4TyxN$+~ zzPRB2$&TXFStn2sKrx+Neb60rSEsX#>t8JU%h8|(YhY8+X=@_GfEAIx zE=Gf8(|YLBK78=H+dk8#nD^eZyiVVdIrRk8_s8Ql4B5rONi6~W{m13B1zX;JGfS6? zc4W@mZv-KH`^}tA#y{FIY8yJwP8e1X$#q~^f`ghjn;Un=d|p2kr()ypr!TnQdBOeO zwBWoa=4(_WKx697wYx1f0YWB75Q8zy0?eVg^ldb3RBY11OkA0hn3su{b2T>li>1kj z%MLQEAR5Cv&v+U;3*t}+k3+O+)gPL-{c(RX7)`#Iv*~y=_`!P&hp@1*UOeaRB=&OE z5B)D=7k8$^7e)0iOwC0)&-cHQ?E_XimR3kb|Gcyj&I zm;?F=(?5qL4CZ`#JDM~yi3Mj7ao!4p9rY~#or<%!f$MkticF&`3B^PxIwH~aCqGPM zGMr6EljYI);874wcJcfe5y2ukzM*l~e;hB(#pCV5I36p0V2YIUqr?wnM4hY2)V!lT zj@$m=YkOoAhpv(#hfL3n&Y`9h!<5_*gellD$D_sa=oE=$^Smlh2~#rue7Z(G7|T8` zgFQ;sQVcA1Zm(&@Fk)T2zf~DLyBE7EhaJ7~Re5}M-lNMS5l{;TzZyRlC!_DYUWBUj z9sLulPG1axs$IY9@rncK1DE9tHLOjh&+ur{Xlz47f|cQ$(-C2Nn_Wo%urFrbbjeR1 zRwhs_VX#;<+ncM0$2tAOj+%MXIt9Oeb>0=NHvZQy);s;<58b}$Zp)?=lYl8=_xrOO zMzpD7hfWl+fkELvfqyKzFuxQYDTOD#W#S`tWHMCtoSc#k?VTtG^09Td&be}d*k+@d zabylkJfoZe0~Fb}-;Qnl?`LcM?^oY`ueF~0uA^6Zm=)Yl0O#BVtw;Nsuzb1FOzz8$ zJ6qy1G%V%5>~bgwM?nw;E~2LIxiRfIewoh(hnoi5^kDCRY5BI9T3&D30s4>IMb2rh z7656l&rYxZCw71>AOJWtU}qt0c`*Bvp_$qlU=F9~ckXdv9Lm+HD*)S=!Sshib986+Zx;$1Wc7^3 z6R?~4NHBxYq`FUTKAr#r3>KliS%B4_&5Q|h!F)KF)455>X_XRK|MW9-V<%zd)O1Kl z2_sk2Z^r!le6%zYHjYihnN*GfHbng>AVt55c=1}gh5)yYj*`VJqq$l;tJh|hOT9$u z9U5QeeUeW-GsR7ojj0Hoe>p3)6@({XgQ1hF3+Kk)je+{| zLe9yn^N#84yPrGlxV@fLPKvO!(Og$Dx%1Y#lJUoBtg8^xZChQ1jFy|~4M{kUigDD2 zItGapZzg_Cjz)Y^d@KHXHxGC>7C<0!eB#s9w@0vLCZ9u-P$?^PjZp2V;3Zg_)}QsAiQ`@g{v|rU-r08TjyAGy}bb zw^L`3nYLMfIk;~KH;B4>9#PqBx@h=BZg(9*rMhO14S~j?wd(Gr%(N}%{ek)`IoQRn z|FR9;fHr~OmDWHV2sx`!ds$Nn@U{E0?hrMqyB~c~e%PX5_3DV_8%dP=c(qkQoyn&K zJfk-WUJrVggV}-TclK$}gWkuXCVD=J#7>f^N$lKowXhV>ZA`E*J7MRUp)*@q zW9v3_p4_u2O@V~jIhz}|BlQxcEuZIScV)XFcY5Xe%C)p%iy*I(2$iFWR%P6SPFotC zW0yLokITc*chmFSGe9?pw%9_0j~81L%>4DP)|Ttzz)~++g3FeI;Xs(>S57($fnb|B zHX&9|;5&Y-(*zQ^;f_2sQUnb)48lIa_HBkR=yiJmPHDKrXZ)eI7eJqa$l5s2A_zj$ z3xaLk7PM2c0Z^fN09&gB)J6ZnfJVV|ZjPtZnacjNG)M9n7tQcB)wLbPA`%)}E{`Pu z-F{<@Ih(+CpWka;6dl(j27);$dYBGJcR!RI6^YP#FiTH`1}`D40UI~QIG-}5$}BD+ z6{pXHFo&+z@>iY{r{nW5k`&r$UM#0y=KY6*%!+oKjTOG1)+hmD@%Zr2pZ{pXAl#sE zCS}GU;g*C15;qpNASjn5%&~%>BW#hbEYE>eU|}SZ*-R|I7cb#M39CBNu1k~Go$1=u zff|HlphqfK=S5_}c55|WBJtuXl(ak31TR6n_siuh?beh}_+aOCSRdb^brLt$uS?<( z;8sxh(Ep8DEP1&XZ8ouyY#HraO=zTc<$0aC*T@M=(Ltc=|;TYQ z$}=imz=R!3S}G#jnM ztVo4`CEiKWKOX-+36~EHmhjwcOqTWI2>P~r0d1?e@%y=wAn@hb7*7ZN@o4#@ZCX@X zP@VFi+rm<|l%YSfQfGHEP}o3gT)w~LA)l4BL+d$~)AoP;d;+8Mm)ym#-uf=G+*L<_e=aT7*Q7V+QfMdJ}EuJWy1Gp;{P*IbGh@@1E7NI(Dc9Axr|D zWi^oSl4`It9%j=y%my{62K4hU;s!X86SM8Q&jGD#aSv4b)v?-IwqK$lduWJ2voHqJ zNqqn}MH_d%`^$tu!YH;j+cPm0K#>I0<7D)Q37#4j%l^Ym-EcVIRh_r-{UFAVNv6gN zCyqFC34OFM3_5(~6O^O^rF1n(7)ruW3cr))!kB-9uG@S*owv>qIH&8lS)j-Yc%@^= z!1J7D6G72>KB1lV*mrh3aExiEeOFK=Mgt@5qOTD*qUPnk9&5|DJ;#(!PxqgT6gR~f z3B#B?I}+o4e=-~!rhI(#zZ~@h!3I&2?-)IAngA)`NDIiC^%o0sc~5)EU+xc}?9C11 zo#m~ADDK5x1iOZ){0+5>t!b>@)Y~d2DJWzgifYtDRm;T6z}kgfUD_x>v4vh@&7#s$ zFIBs1rz$!$S9!i7X5p==*zGQkT>rJlTT1_m0^fWJ?03_O%%}I*scaa=ES@#BX`{7H zJ!WC76ZM3*i9kI8^~8z@Q~&qHbYk94hd<2z!o2;lG#2d`2eC}6_Z=M9I<7xs9t2gb z-)~TAA3`*rT)$?yUh9UT2*MR(1ZnejG*MWmr(}xhIsQw$Hf=UaR6R&_RNDwcLJ}lt zW6iNK`Let>hsJpHFk04+Sk+h`)3?_NOhvWKt|fhA)*1mjVZn?)-R-OE%S{U&3o#+W z>Li~34|EWX`oM0cPseV^376AINg2{EJwk5&!uZ z%;2EK_^dQD8B^&oJab|&K61>b&s`e24+3g7p&)40HE%z+Y?=vf;>@3)S5Tx|>&1d= zXJIb^WP~CkX=IE9^ZSUt+P{AuKylCdU;3~Rwv-v17_?R^CyKn_1zlr{6Sk#8E`4-L z#v4GwwakQDyQMe<0&o&g=D-`$07w?VP@WmS-GWhDCv*e5WZJhg%Or}#ycthw!Nw@I z!uYfXG6;!v3pj7Vw0P(Hju-9l^7GE~-eHXL$#bK%bK=@s4K1N!iT5p1_Zi83jVYoZ z{jzv+M`nO)o&@p2X>;JnPx*39 ztEW~S{5qxba#47kQ&q}+9Mr%^o;VPRjV;~TCoQxdV0FoNEQG!X~E78 zd4W)w?E7*Sb+Q5tHJ5$hTCAVjleS0G>V;7U73KEyEzvy;!mz`iJ|Q^>Jo;~kB2sk(*i&d4HHkU9<^(fCw+WWJ6HgXEN(mWp{C>|)P#Leq9) z?p(xqqhCd3GgA?e`N}A!8{_im!ru9(1TM5cPV~TYB$%1X!9LXrml^J2_suc?_Ul*I z78O7uDM$n$4W^TYF?d{#z8U7<{`jLcb6>33`DwkI&z9@$Lg(B}=kSPtm!{!Me`$R0 z|1j_Rqw(Y1XpIuwCTY?p#f$5oD=&zVFpP!d;HFMM!%zpec435jhcdR1Kp|O|ioo{OCmBnQmC-zi0}-EU?7!!PkYBr~|$Z-RJmJ z*qQR>{d{z{)V6?s6xg1M6*PysHY0_Aa0LAUG`kM6#dqSLLHK$~V+4eWvHU)re{I7Y zvnl{~w`R$p*xWp`E;zP|nXt9!#8ps0fNe}wl@P{<{fN0QVqbl5AmoG*Mkzp#xqJbq z^g|2VXX9S^W{^bWbynmxio}2hC|kkQL>3>8AzT)QE3O!)^ZEPROpg&#^yzaKo7OigYr5ij`<_k%D=gu`10oH>+J==vK!UPd20W#zu z)o%1`VC1f3E5<3U1Wf(Mik%qxc52k=uFchTlB93^+i&Gtc~5Bg+iz?$@3-HoxA8VI zcpEy;P8gnFaZ{@kY}EAG+_*F5^L7jT_wy|9-+6)mu3q5G&cx*zY~x)WrK4Z0g=eMmVz3m&tT)v}$ric=c1;tQcOsfz{wOJao^~ zY>939jXu$%=7uMZ^q8NaD12VYZMQ1k8apJS0_y=@91kF<%&X&>a8l>(F>CoYWQ*Z@ zPZX5r%A^3N0C=L~gHxRlL*i70n94FBL|-#0*NJG1F~>w}b0MS>CV(VTAuCTz0agx4 zbs#U#fqy}yAlkd>m>Ba4Z@&lfQtGMUEJ;DMm98C1=y>_l z-R8*1B++|3=gskYPfiq@sZ*lO$jhfSpdgx-}LMOXY#Kwik=4 z{obt*Mmuy09gj1jiUCHFXa`iWFVcjbRDy{qC5uzfkEe7dmh$N;RiVtW@y!@lEN*R| zxtoh+?xujbn+}=#e6h@ZE@19+hs^zUvCREez}#;gGWWNOW$teU%>AuH=6=6e=6)|= z?)MIv`>%^-?!O9{`>zg}`|pco?!OC|`|l2!`=5(t?tcoH`=6sR$HzS`uye(cXZlxs z^ybK2tSE+owU>8Eo=7-*<^)hloz5zLr0v=DVkf+SuBoI;BEB1qm&W`k zJ<}9Dzb=cLUr5X!$7N-`dw$+nzS@l9*^t9gvkAxh$Gf|S{zPZiu@P0F=Ufg$0Cqk$ zbouzV74I=(dE2!muB@%l{%M8I6D(-;TJ$GD3l{$bOTKnQ+UJ`K=L`&>lzqPGim~4=oUq@Juiv_&>u;Yf zy@sI$lrs9;6=}a;dL{dv)w16^t7ez}>Sr(hX0Lr}co|=CNjA8iV$b^X{YIB4dV3jD zApS6_sDv`E;F4^A3GBDaasQt`ZjMV+u#GR2BtU+o1N<(8cnDz>m$&OXxWE`$h7@ zp?{eh#_-26If6#UMKXlIp=oq%c5W_~o|~&iO~-w9dIvYF_er@vkc@pMNUIHK1r{`0 z%_rfYar;=oe(gr_Nf>e)wb3 ze;5tS2V>bE_Lu!5634Qc;N8M8U!C`hB93K4*BlN3@N89%xmbeZzOTlQBNE4UQeG5Y z9P+)<9~$!p)L=^nSiu9^@vQgMA+JsfOO|q^pa|fD$v=n+wwR6PNN|xXMBQadISwKx zHq*}x(!p+lQ_S9r)>i1)5c5-nvMCSdUd~Hi6;QFaJ!|u1TPbgXE)}xEfa_?n6}|f_%BUagB0amM za5O(rbqEVTBf_MlJPT$4lMLYg;>Tn_tH5OXxHt+um&Q&S@1fj;&cAo)F2Q~AU7tnA z{2B37k;s27?&+I;2o3F7iF4rGvvBU~Kj&Ra$DR$)$NRsZ|KT#UYa z`|T8*S|5{CD`8c?HTOi@K0jSMUYpYYluTkNj&D7Ayp88ZaBOSwnA3nhWO7Om;7^Jo z(m_=gKS}TtiGMg+^l!&{eRh4icnMxATLS*v_{XEMSa!k{_8LUk#0w`5yA{$F&LZ|C z=Eh7~;sscT3VO*bLt=AS@Rw6^Tvr|qmHKJX1g{rD1_e6(dNdj| zTuq+|9|w4#-eZWuT>(KNdNG`{*;Lx8j-8c774wLbE6=g?pC1q?7(!(S5(F|J%N}5o z2TT~?!Pw}}kIjwZVw-bfr@_jbLl_F3*mC_#B8VfN8_WJ^au7XS3GM4(E9NP%-y_Gj z*$HmKYVht6%p}51Hd5CPL0QZsjhRTyw~u#ss$L!rV#nNCVeDGoC9*^WcEOf8>(7^? z{`hFDJ?}ivw0B$2`DL=XqRXmZfS)k@MB=}D9FNW6<7{mF0nS&9{=*ULo%U${i5Y7? z-ml8YCDtQ}`55(uy9yWl9NGfthMp|wR@(s8^uWPoaf65##^jilCVF+Z7sd)8UVI(R zv{`{btLDF=z&CN-0(!+p?>sael2fU>(A)V=w6eAi^mHJ!let=3A#IpZaax2GX$A69 z+793iq!W-%5_#yvXU`^*vk78Dk(5n+`f(fF_vih=(wHw!M=sT{a<(xHSUTaG=Ij1K z$R^B2B5ll)ZyJ9LjM)-~G>!SU1|N;q3nTKZX#J5(f*Dv`42yApp+BFGj=4&mIf4Lm z{jNtUcOD*N6fT+ijC63I)TPG*vpZ{m@2<0s~48|4(H<1E=iQU&(ge zU16Qte#mApJ?m8+YM#dYM$R+!2;goO&(nSZj=O(s-+BdqkEgL*!?$C`V%9h}<3O%a zO|4wQtStOY@5zds-}P4D?91kHwDTGdAcjdJagZ!rO;$3c@Dq;zw!bib|Lu4*X%G9H z6-mtpf$9ljLFIRwCnvl>f><1ShJNz+aBIwuMG>0uw0maKE|n@_1xvb`&8N%h?c<&K ztv`M|TJ555L)U+~RLVH|#d1EHe9@-J-I}rW^1@dDx?6FAM+^a{kx!>tTiCj0y3bUk z8^w_sgr;TdHEa^4z*-8z0c_C^9G3!_q#-0_2uT}4m?6Y{Cd?2Lgai!f&t}GCXpZh^ zKX9~IF3joN><@>>XEw5YH+KJYE{{rye9@nbmZN_ft&zo-f8k^L6vXSZu;I1rM-4~6f+XdTrc+)%@Uy4$ z0G8MQUiPz=#ot=vNsFojnows&S-XuWEeabnrOt}F!x~esuIGsj4L}o|i&NVT9kC!u ze<^!fG7yx}bO1RZN3OA^*;0s!M11h-byZ#(tg${dF^4Xz-mGxs2!6ySmA3XmQ<=aB zh(3S_njP+jpM8Kw*b=Qr$YDF9Pr*Fi_JZrkz+b&&A0{kh1@_%IzB z<{_)DQ-rj#!Mb%8jq+4N~6W3@dWyBRFv2XKXNw>BqJ^Is0!JP9%Fzdi%lA#=k9kb_s(;ls$0wQ(XhZulfIp+F5D9l{C|#fFFzd_>@D;o+%c zz_WgxtMLGB-2bC>leOpVI<&d@tX!Sd&`Z{IPST;>93AxG8@MV-Y-dN@b~t9E&{jnI zA8>kl`_l-CKu8cFp2D;>?O6058w^F)@eq5Fx$<1ckI$M63X?FHjFIJdVGhP4W3oIB zExXV&*{ry2!j^z@<%QxT94BsFxO$wAO=B{kU&*0)J01SeW(L9H&Cx2{Jw34|1*XzO zk;eIAImP1i;LQm`*VcQJ?&|~Mxcmc{!>o9-kjodR7uZ2Eb`WXD^qVoCkA_A=66uR9 zqWwU)OH2+z55|etR7;Y^5BYG^n? z=-3~SRKm^T+?sP^cuI=5juq2a=E@3f*QYJ|`27PC&Wt2!BNO^(Mt@1)y@URIIGXgw zqvem&6oyP_+~>Q1!blR*MjGtmP%>~xf3d1C@EX6AT;bgC1U43t9A98@RGCRqre%%` z3giLIKY3X^yCHX~?rt>a4wlCK1~Y!JyjZ$bN=|;sL%zE&d|tgl zBv|ld5n9i&$? z@EANlUm$gWdU|))5k6ApazWip)`oiOFE zyq$fKRB_~^$zc3A?1ZdFo!IkW>+pVSybfLZ_RcL6qTg`O7OSwIJ?lkp3-!KG+)28x z>HxNwwEX%Y1TGd}GvF*Zhj};de_6Cjk7yOZu*^l#BN`kC{J~f(`dlj8e7lt>)jMDcRsN4>s= z6Y>d4lV2)6j*=KdQpU8q1hx^KO%`;XC6%o#{`8&rXAr*j?A_B-V4^ZP%tsZnsFZL` zl`1aqIwcT;1g7LS}cI|2i|63p-azfYg+&__S9xXA7T+6>XS2Fex z6N&hqA2i{m!c!VN!;So;32IvO9xF(xaFdpX+ssLtHYb`68P9>2L;Nuce&Ew;t^(g@ zn?KBr<DYJI5_UooZ9quzPL2?+G*mOvw5-t^l6xv6meFW|6 zKQ5;o^9t8LePm*pf%nVh?9&OTz4_?W-XOI%0GU8$zbByf^GB!lGgAAx6KZK!=BE|f zY@OfA31b^PsC!Y*@_t(Hk;$){f$zP4Jl@N!C(L?rouKvo@%R3Zg?TrffA7zS=5#Xt zapEFk{zUlo|D@7k{zru2-0cW2nA4WYf9Ug)c~H*h3DNZn^8rfYAcr!?D*-cd;{U)> zfH>i@B+P}eJWXk^@k{hQ@gGnK%v$tRarO7bbaGP8W2)pohjX-cY5dVmJ>OdK`X?-j zLSNN3dZczDKA+%jww|-0@NoRgarA?J2O9YMIZoLKPyWo^%SY`hR+f;UfccN%6g*Ch#h^bkn9GFm z6*_FM#eZ^fNe(jQH8+N%xiRSG@xp*3AzRUyTf>lebhVx$`f>euB~@;G$_p$vK5_rr zzg^I##HmXM*5>{U-Prk$Dwf1*Sd#pGK3W>5E{G<6l0HOPwBm|Ml*p^~{A$5Ys5LDF z%iFFkmV_x3RI_nwu)7VS_$72Ax#EK{ERtDj6nV(4<$1wRT`sd4B%e9OhjA)|$yFeX*Nhc} zTS1r={qcDE{pgHw7djWunXrR<0y;Wx(5-O8%jZql0sW%Uyb*8VlZ6Xx@Rxhpe4A15q8lc+UdkFm!k*6oK8%(oA&lcvsMgrACd=+ow76dj&Fm& zi=3Di%S%MBSvL3GX8GD5%9J7`E~WQC{yE(V)M;QG(fg^w6JoSV9{siq!)KoGJg@h==}SsTVciFz&XY^X!D? zhoF$zBAA@bjXPs*o`_KX=zI)Dg)y8`{Bk^bSaB5k@DS$|Q-^Hlh~rt8@Nr6D zldJ`VSuixl{U2SC2EGTvizba(V9e*!MlZj3wax5AO7=g+-W%>u6peQKfPr|a1ve5( zm4~rKXaQ8knT*#J6v;@9s z2S0sw!PMCxd|G~BGqA*s8_=$%6LUEIK4II=Ox@kU4H*O1%xl`ojMmnxbBWY})8@p0 zEQjF0`JQ%+7x(?I&8i=B(7?=+oBazT#q#N}T%fZ?k*y`YDeZ|Hfa6A!m8HO(Nw9*D z4g(6K%;qg`Cnk2=MWEw-|05MZBhUV=k>4P!tuyB;^(%&pp<;N+8)F}&y=4Dk6ida;UasLnVNYy0%>BjMa-^>n4 z5pb8^N6Tdk-#JQ$bflQPW3@d(o`9ANZ1$E-VB>4HkzYtPmY%~VE}%}v4u^X;Z|eN1 znhn-8z8E$*T;|fceKvOfh1CuT_;J#_hWr@>u)~q|&Ll0KMekm{Q{_&y4m?}i~ z9KW(``==GJw6_!FQ~_6c?T67Nr6r7&*+Q5tMB37~?eB2V5r?a_vwHmq1TsSg@V`sy zu+W_7!!Ri9K_b7ildkymg2?$0M4}-B_}>+EY%b_SkjG6Ku-~o-44nq^$Y;qPvt)oj zow9fHap=3@{M~d)AnXpYTwlK>e7-~`CNV+>;=EI0Nz-}L9iL5>fu1RqXbNR%9?9URcPi`O58J}?FNqrcq|_)!{%!jwg) zjYF#S1HBJZ)=(Nde-m=uHy?}2n-rH_b^4-rfAt|)1UnY=p9*gnb(XL4XD%4oDr_nn zJT#gsD{|NpK9&II@;=s(>dA9h+!FYxm5GdcijLzkFzp_U>kJGQD>RKuk6QFm$+sFL zI*1TSKqOoKS86J$Fjabm*YN&+Z1yQZP!S>9e?kc`b5RUz(U}=@bI@NHx+Os`y7aRc zPy$&_Ub4w%9C-&X0}cSZGw*yjS_PqWPp#zlGEIM?|2m^TwIN5|azF_rY6#28)g*gw za%Pe$Gj$zf9+7aHRs&OQiR9(e4#tWFmP1V3HC{m^jGIj|6Oju<;Ze;Lz4Z|8aSLERJ@eXEL8Jw)qs; z?-#`s1y4A5g5V$LW7C)nrbAt@VOmykyIuUGc-q1%Ih7rNl zvKzm9Py9UrUY9NUo^oW0xCP1%y?S`@`rTXy;L(n1tz)$W!!^SIoN_dJoT6(1DV7Ma zgbBlu#hhtm&oq)Tjd0Tlng$LbV2|H;dH=(_pMo#D$>YOWkp9zgU)J$Q<(*rXrT)7y z`f|TKgniGkBH6ygLVyHu9yuP5=;Ore!0tWoJkO->c)uyj8FfH*R)0I3qfF6GMCwyE zk(^BgY$9mW-Q##{(r?wIJ3@VPwGP}>{Uq515$Y3R)5ILyPe%h|aR{N{`ovsY7~2U? z@EHC*(+(XQ3uH|iysIgZQVseAmO9%Z5abb%hrWTi4J2)YY?h%7d~m_M?=S9;%-v=e z(m%Y(yt!2Jm=Wxzo2!S%IsHTO%$p zvSSx7ltJ;f<3@=0-Qw|fVRXWxADANLLMikEA}yFq&Aa}1eA^#Ppb&s9gQSUS6} z@0#k6QFlR~9=T%4Y$^;n7fh%y8EO}fbL^EEVnFghCf~B;<8rDJV&DuJWDx@Bzhkqr z{Rs7~(2Pm2ZB*AnCkl4q$}u;A@8bOSYP&Ocu`4EM;f+a?QYm7BJDsxG?qVm3Lo0S( zFrJ^9XPrIm*E#JtZIT0|$FopDgbG6Ck(4~hUSuFCH;|`W)iK7CovPewM(;(Sz| z(KeWoQeiiWE!yUH{nx1XYCXTAttyScQveW}0gk#j7=ag5M|*ybG7wuU5`BHg`T=uW#n*e4H%jkBg;R+pbk|@#Njat0rE)V0xJ`>?&c#d^6^wyB{Xf zYYy(|H{l?PllWpZK~Nj4qAh)#ww?%n=sCXY__2wJ3T_HYDFv-9@mYkNly=|R5b{9GH9u$ z(S9{7Gqb$dw>HiO_nd~k$nshIWj{KTw_G7k!f_J7Ih-tx$OKJry^G+2AvGTQqwx{> zaJLYemNuF%qk_0wBH)f@5Mg$1ERI=Sf|#C}aOEQCLAV6rPM7z_+?@0uj3d%Re_5>K zozM3bolR@Cnkf29co2gG_@X~yVT>Oqd>7P#?bB-VcA=FMMkZvOhWGod5{nB)Ny08BXGyU}dH!MDf}obd=ga12ZSgR|r& z1r%>Upb&2ug6SNOkhf3fUoU}=w@>7+KnLJGDjT0JgpE&}jZa5qjF z7sAG8&cU;^{n@<@?K?dJ@Ive)~^~A7! zS4fMswVqtqktL&B;*QHo<5_mx#cM}8wz7<9snjdORN`jItNZ10b|8M(mm8hJ0Hzo= z051+SAzq!N1VHU_P|rVKGGL}3Fkbmx|3yAz$>IRNU z@@C1|a8!VtNaPRGp<0XP0Gf9Gs~`OIFOMYw{BiPiGW|X|E=QMJnNrvvC(0EHR{D7E z#>J(%@+9>_0vD^Vm2AjdxYyCs`ZCMBxRo)lo}f!qs($*l9Yfxn(8To98bo0b24nio zn49C#!)V!xfhy1Vv8~g6Tx;w~F7JH$&$ixAWnFR^kPtvJ?Eh%lzICt*&%{84azA~X zw`}5uwhUtDOpN4U9%v(FX|!!9Z3V5*#ZJWXK$~OtEn68uNDli<%;aDe25oo_TQ>B> zFV~!<$9>KIbez{SF%+SEH;k_>oA~Zn=klgSC_jvT{hOAzQ(*wbg9UtS4Y%x2<7z}} z^;*+@W%uJeN2E8B!rPiKlCr0tClRK9Fvj(S{lQm-R;Y?u{;BQqGlostvn+I0j{D}= zrW1xictQ&W5W;|POXtSG7=1H_rZJyS=Pg-`E{0B8oCyYLCwd%hsZdqE7RNPEAmHYy zekfALrOoQ7Zl>*EDz$4}8a3=1jT}Gj7C3C^M6Ix!b74%DP2tlY&*>IBQ@L37$3|n+ z@BXTbM>_+X>0JDn4D=r0H1N)z*)($1h8!-QL&UbTVvjf`!30wAA{e-gvnAk6Q~@Y1 zdqa2`4TNccIjT>M(A^vz-3_9$aq6~}vPerueASpBtvbfd5+1er;g~7y7ZVT<_>C?&~v>lsin(fzM>v&erj5 z2n%~;^n2{b& z*3Fc)0lZxEB=Gy`Yop|vo^df7-?N@P=ji67b-}A&o@qkH{lE6{pM#Vp3fJJrnqa%= zPf9furgpEAW|pzfyWljfjN%<7r<$lMx8Af0S!$wqANa8j*pdw~?P09m@=}0kYlRSD zwVO9^PT7o|Hh33rt#@!x5C?&0g-7o{=C#e(`7#K`aIOg)s(42q@VvGeJ8$xlmaWFh z$kr`F-_s|XsdJv91wX`zO1HD|-U5ZQRFeLPZ(3ki)u%{h+KNUMDGYwSpQJfkrFjO| z4sXhamjB|wa2}NqJ-Zz=riwJqAq@r(lQbGjvNPFYaSV^RI_lLDIDlw#MQMs-+H4j z;|?Cx0)-2f^q3^+SP(m#Z4g@Fdq>~oeSJ%j(&U>=A8qw%LU)e2jw<5ArQyP82=PNAQuShgwJ@2itnb z#wpZ1Mh>_JwO-PnT@QR)qIK69k!)3dT1T1H2#>e{L3B8?P9SFz zXqHDTO3)FGZVzu$!)=#x{N{x3Y=*1%WWBtU&1|2_{2WFiF!JBFOvn38)`*YJngD)3 zN1DOi78$!Oi*sQzL-7GV?^+=c6NZ8?#D#XWMH0*-VeCzL;j9@FaFYvH`xZ3jMutW? zS2iTTl>J4hTVQ~K-C8f@b8Kh8K^kH)@}@lCb^bc)MX&BQaS^uiAUfEYyf>BehQe6Y@n1qP4+K#~9~ zK;4u9`I$b?Z-;DKB=GhTA8h-%el?`^X@Etj4`qN`1x>WS&UgkG9DxUZD7;-DexT=LZ_Oie(rL% zAuS#rDrXZvJtLb0BL1sl6MVD8I-!$)YFT_h?h=rJNg-15ypTk)Ab4r^RHwmh&(xfkim1eWZGZ^0in@ z00ta%&!r60(1?Uqj9AO)mE{LMCCxKW-P(``5)zB!o<-QuxX^iXqZ2pN3k;O7_L1rt z%I4in*FFye85~04B*?T3I)~=3JZRMD-Rqm2g((t@IM6cXY)S-yXqUuUbUOBXA9`Jl zlr^wD_?$k;43YR6XYlD+cKWVHzR#i4$;jJbR{0w|kv(Dv>?((`k$s!McFxK;1e8Xn zXV%%6gJ$T$7oA9JwF-8Ad={37UarKH#n{BY=$s6TghhId?+TwKof{&c?E5pRF(M9W z3(vHJjpd$4^BMt>|4ZH5Xt~N_ZM&zE6QFv(-QV|)eKrm&QYpKP+G=X8^wDqTWP@~S7g-wD}iVu+j!iRS|& z#@ih$_#I{yzO$KH%7{}HYN?dN8L02TruFt;ms`|{YKj}P4DC{Vu1prJ#y&|@C zee3co|9Vte!7Gk~9T+`VT>NC76%tcOOe1a6+WM85JzXYM`t)jxP5X0n$+$IuI30_!SUs>L*9LXF0$6jr1A*(T38(%vOzmDM0cTd>DAc-jMEPDy$DX? zR$BCG!CFHc-8S#?d;cL{UV-AbGxc=`Ohbl|jC|QeRC#CT4%A<>r&2$0xXrZ6{ylXB z$o zH|a4jMAKb!umG*eji8-xG$8fZxHNrVx{* z04Br+oR`sS`$%U{h);~oV7v_RwvTH91jYYeuy=k>m-<3{lKL|kFGEmV6xnJw|Jim> zTKDizK)kF_0A^8&^g~=Hq|8v+#PIVG`-T9)&hv&4zstV5{3=78M(1XR_7ug-O|o0t z6t=mgG43zU2;m*}fbmMbD|mnH%68oYhWw>(VqcgFNb-^#BmUB_up>;XF;mSr_5tUA z+b&y~X>$rUh|$EFQP-NfXda&x$MFUADZa_*UBB-@jeNLzSW3&CI+&K8k{tG=-iW%3 z<;4kYO}!DdDoLPFf_cL?eL82PFJG)(mG$tiefP1#P|@V~IyxBTV%hRW8* zjS^;4VKxfuqvLMub6r?72Qu51729kU*2vM^mSx zK>p6d#6py5QFUN#KpoO+2It;K*JhpB)uDPtJyYD9eCRljNsfA^xGy<|!NIS)mqo$s zU<4c!;y}T)>#v0k$gYcxG$I=uDfH1Hi}poVSW{Zpx%66dq0{0cuM1Tm#=21C!Kfae#^z1YdPfCTdZXd(@*q1o??@5N`o_ue1)#avr63PJd zO#-M-rpn)-9X=PMBRTyHKN0`7u}#UjaQF!W`5De3Yq4)bG!)=wLwGzEDe#ftq9|>= zB1f+RtvyMW!)O`In8oHm&hA9(t8U_z0kEvO85@2|+5;PpKIA}g9CDfJq$pp!xqmos zrS-DD#l#0;riCXM-ew{;0F(_S=~~(E_>KQn?{7^5^~jF@)1uEDe&_*i3Nk6kKycLd zW5+j{rn6VsoHGP-D+w`4s7|xk} zpW$iN{CfVW2c}it2>r36{U4J?>iOfWOk>#~J z8JR?8qOQpD+Ta;Z;*|=>d@6b@x0clW1s|190SHNZD{^l@U)@xx}=Rl37<9!#wBfV?Vh+hAJ+kovqx8L zL&=qX^mThSLqvWCB1+8(ai>5Hz_$|+FfZ<+kt?Y57Nv6CWAV7R<7cbUvGAu9!vr=j z#h<)}#Gk-ND{A9s7k%VRT;=_cJ=!84co78}1ep>v)nB4tc{h|_G(4MU@OFn*e51GJ zG|nzsWBB$$D)v<3SX6VjqL&8%FZ_Wh=C8m^2Ss&zO(w`5%yaE^h zJY$*=g?w3LU){WK?MxG>$xw5YcQw)G!}qz!r~v=CqV>EhVUxAT{c|INXD~)7#gvpO z>`eXm{5QW2SuWa7xR27w0Ir1GqYfm6s3Y-+uDu9MFF9=3>5ijVZU7>-8>p zaubtXO!YHcQu@B$-A>EQb**TPs$B6D8K=BvImVF6)Hkeaby@$Ncf4>lV$|)3fJp-V zjmrj^ufacgcZVRa2^|u`(&C)W(y9E4W@Y`= zq;p3U3@J<`S9eeji5#8aE|bd4P@a(Z8A*3bP4ti6QByOCZm6lFVE0p^8|c^_gHLOS z*3FCTx6I+I=hPTbh@lV@Lh#kgMAiZKT6Wh*Ib@$5&wuuB?nA_+#kAI3+<}`HeWrH( z+uWxm5Vz3MI}W1OT-<>hcdag{;HqZFK zDN6M4-SPgrsfsh*AP18ij5ru@FvY<}4i{*Lv^YDPy^Q;}!PI<0KFmK$*n9c(8)2d* zd6xhV^a(T6ULHD>ZJPT%uL}s;-e4Fdcq)j$WqN0Y-Ci3x6Aqs}*8Uc%^f$e|1E?-R zoUK$<^Cx@MKXvn`&ZtI)KkK7-0(nOAs)9)JFVI|Fe&#*T$|8m%kb+pMBSEmIM$ z%uc4bE4cWm#jOIes<~wq|J~SjJTSkO6%Umgld>x)?r!Sqqw9Emea3My${EKV)KGnn zm3Js+EXojEJN9G7r_;j18$&E(wG0`k=?+T zP<92y)eSA&P^}L&4iv9tM5{7i&b3g8vqVXvyR zhmFRQcva6J3GfQf*=uwC;X@Yd=`ZDECBTsOq>fd&B5El&N;^}-1&Je?K`$Yz>mq6ZYz1YHWOr+a$<~OU3*G1cu^{_VY zTm3C_z3p9{Eg5v7rT(VNg)KN=m+DXI)|;L-f#oUCJOM5>AIZ>k88hzj1z|jEgV58;+m-jm_N|@3n(&H;HtFzsZI8Ia6;&6FT{56}KPxys6 z6J73t-?|*mj6r5jb}6M*-c!;t*v;h?*Aa)5CF1*|+_~n?Wb*Q+lSkaARVi8-1t0j; zVsUXGi~8D;J{$FS3B3%fvqZd%%G%@tPB`40wBi8)3*B@FB!>xg*dfk)g<3{(kBWp`PQecQi(Nz1ibU+{u11Cb8m<5wp2IP!Fxz| z!IaP;C|byqDe31`^L5t@=}za!bL_3zKyooQ`RTb{CI+T6`(23lD@aE{?W#a`es8)b zE}g&b7d-TBOCb4eHlAA_*&u!MytP<)S`i(a%S!;}n@GHcrQ%yNx#cFnKf z{4`=I!lcMj_cbjnT2=pBkV zNwR*WUr_p+ey=iaiz#dox9L1V3AgF^Pyx5;@NKgAdBq#Qc6@lT1qFQ}`0~7k@OHAI{wfZqu6lgKwF$W67zE31!8yFxf$uFVW$ZEWR8v z;8l|eWy7<^vam#r^LRHrbrFn$roI<^n!DT74t8@-3`M%PCq{g4?Fo3aJ9~oea%00f ze_ZBI>-RIP3)2ez;6VaLJrys2VZB`GtwGotFav3nbvcxIRsQ#kV339hGqlGeRbUiopZ| zMq&$jM{YG7c}^g(CAu0Cm3nIswgw{HPfEh-zA5i*#qWIR%B#hbPJYpPWYavm?K8~; zg3Yqe1H!YzKlVT?dxD1~vfU7zw(!g$21YIJdPvoeq7J#u<(dQoiDK`exW-ntcC)&U zBbKe6)HRB_13$X*Y*LUA|G#7#^0wpud58z0+LB-|c#`;qE!B8(wE=-Rxt6iz5{Fg; zWPV`cP>p+yLNC!CHB7L1ikibs)KZ~-+N;kp`VgzTJ2?^RO-|L~6-vv|S4_|ws$ASJj{#Cyr(e1EkubDSXsq=_m7EiBfp?+Ardcd7W zjnew*XK*;XkeBKMIyX|}7^%si!-eEW5??O`6$B{_DF)?g#^h^F@6wafn}*P#5NA}r z?rvRsrKRC1{Q(A3acd;TlFZlRx2RBv_IpBml0Z3|@c5d7EdihuN@+_Ki^|xH$=8}_ ze&^yroTs#=7?iacldrY9875J)Qd$%&li*&;>IS2)SNo~J?tBl z-%@%~APhj1y)`;-r;i%DR8xAC2-@9*$k!J=O|DIm)SKEKU0^VcF(>o&2s@2&+D|Q+ zLk*$)&8e*2)dl%_6;3<29i^%xYQG4ky@n0@%jR9#!=Gd!*hT@L>oTF+wBFR){p+WL z@ASQ|;j8*S!H=hm;$W(77`lvywoY{AUey|6wb5t7?k)8>dGz-B+`-{iJ2XRN>8#s> z7{oS;o4L3Ou&W~JdYgC|i0pJ1hWd6)vNa#F{U*^-MF$bAX&&_vlc z15NSauulLlb@5+YaMf;8P0M_^erM%Z_QRhUG<3e2M_oj*G9_V1!pbiuNU$j;N!9_6 zxQ%p+tl5KhvwXER&~yo^drH)|wx^n2(DG9F;xG>UQk+R~{MXuoY+n(5%eyOXK9$34 zh&Z>$gn$tNQv@?2KU=C1DQ>L#Z3b=Q@ME zoeUUez-)8c`j%A$Xc(ud*U$BxYxbW_GxVbU+hThb2Sud-Y}xn9g8m^`7|&v5L4O_p ziesTt6rQM;QQwz)dzZGe;pK*HGZIG|FXld-gufp}{RM7SHGj}%RAf+PQq>@EL#Iv1 zZ&mY|SMa?$tIB&hMD#(sq)P<&Ud<%J7Mq(xfrWd?N4_6OR^ALIgBv~Vd-!UK|2h1$ z`Jxt{aM*en-{483;E!*lCjRRI+_qJDmBZ6ei0$`~oZ(=Ez><|9*4Yku6sJ)kO7w>D z>C9>xV@G>qC;M zBU>Gh41_fiWzonZH3ei`u`L z*Eo!-6|NnZkLvC1V$OGO05SeWGhOBN6rbUe|;*Q~-z9w{dtK9bQ8^tlq|9 zH9D*gI8o3xbx$b=e&Q`*X&xA&|gk<+%&fpPRd3cCUI01T-{rHynZQwCzR)-)6sA4`Cl2S zyAYM*p?hX!0r~&cx~Ov#XNy#%(_a~8ykJPmfK*Qry1X{xo@(Ez3f}Lroc%sf&eDYV zw&P#r-`E!9)O^yOkhmM6@PYCyv5QiQYpBGhEbd;a{K=t$PGfzM&5*hpX}rv6-0jGn z05aMvqe<17G2OkR8GALh_}ratMqD}1)yp6(uW6fK-c?OrB)T3-USEefkGrFmcV8Jy zc!mglaR-OSHvqv1f*C_at8DHbRrK;iZBi9c-H@O1_&PG0YqUjd5@$wRO<|~m9`me`<^UA?g9eF{U0f*BO6 zbiOvf#0~nL)*~X!4Nkd^xf98hVyNA8*VIfQEV+E$QG_oQF%;?mT^YR=C2dL;9m>>T zc|q!JysUrvG&V_DQ@C58dQ4<|rAn_J*tiNsj}H8%>fVt{WAxa?@7S-iN$74CCV&pt z76#VwgjLi0Jlfb<4rZtd3K;4~!-`^q&fVr8=z%So*q2btssn%WJ`sAa**%i|ukLih zM$M1NsCu45WIum(R~RFdaURCy6{oFhY5-8PbU6-8vm(@ZY83lZrsdEt&D_vTd(fU~ zf$jC^Rpyy_>_wPVo)=i&_FzX%l~#L;LC^CCn|-QFiWEyQPlwH8s7i5)f)D(fD)$lu z8zB~0$L#6TeDGfwEQvage%-7_Vu&cQE|}BJpuo|cBFPfY)+vrX%c19wh*{N;)sr%u z@*<6l(7=d%8>cciRc53tUu@~+u!kP?P$dU$ADs=2Xn<5a@8jgKbr!k5H5(eHSmtmC zy~1d43zeYuAxDLA=kyo5cSrIKgV-(9-r9#8%*MUlX}j-N&;iopQN#A^MMk4yZ;=+n zq=xXubXbEung;Szl)>WmDsCl#v`t83FXIO4!35}=uR$d!F3U=v2LMT>0wPfs*g&;S z099~*#wzAid5waf4&|33Rh^Dm|M)D*PE;U;^{D8IfK3Qixm?X9^E4Gc;^27~b)cWD z6Mu~BKngP{Y*08lz#4>H)0g3KOcy9fs}!ZU0pew(DEpsOK0upnj2PZ7=)$zHnjc3Z z$}3mR|1{p{Y9SFLBGvl4tb-nOpQ?PAdk$4xEc_)ZJr@3g7ADc!;}bVF;nTL6yP@b) zl^dW<<;IBN?IPth(E0(chH+gY_7H5?!AnFK29lRKnm&~%v(aoWr9PtAdQl@tA2HW3 zC{ew5!ExDwhW;t5DudK$9z$GmP-aTm#BlZi&0zBc*}uynR;57OfkwJ%i4nvGoV!VW z%kO31r@2^4U0<*{j=P_9UM4;cd3aPqDKGAK$NTT5Dk57^0Bi(UfjVL=$rT!HsmnA6 za<-%n;&-xZTT=wT4)-lTIAoU8jwHZ;UCs2O2p6qf$gg>kMV-!~5f~sai7-xWS>tkd z_owM#)%|oln_7eVVyzt-Sop)$h@rh(3x;IEqSJl zPGx6Jjr_w<+>``I34nwdBr9}&Hj}$E0K$2Ziy51`mc#E^+cZ^rgL2`brecz64K=X; zx3fNX9mX*9N5RS)+E4RMToV=3RBNa~{J)p)iQm9JhpyyrV^hKyV24e%UDE!FH_W@sQ}|VGKs}0 z`&p?a=WI6oDIG=FEprQ`HGUJz9V^aze5TP zLz^MWwW(~P3Wjuo2n-OILeN14U-5Z4tv)S=vBJBnKF5ByDTj}X>QjGXD?9h1x^Vr@ z>l=@1RFS>7BLx{FG8_D3=_AgG>+Hq-o%i3+wo8)%#&pp{jEE+AV;eV6I?#FbU*LWA znOAvz<)ZL|*%YZ0W`>CA#0ZfQ;tKXj2?Wa}AxlDi1=mc{7v3$EV7aH_#5JFabIZV? z^pa1@XV&I*d9@pcIxkTOh9QhGB((u~$T;nwQ1d^SsC@>5`jt3K2X%;RngpQz9t0F- zfu120LntPocAyU>2(E`KhiCSOm$&bM7pY5F1;iAPQNX@{J(LJ~7Os4F;r2Oc-0NAw z@>wfM*6NlD@XSLd1dN1-Z+mL6s*5Y2;AT~tV>Wqeqvq@Ui9X} z6@-^8&R*z{zQe87frK?RA&@Q&cOhJ<<#G|9{|jGgC7)z4fZV)UY$6`eh5hc~E2uQ& zcKznppIE>&9!Q!K0!R2^X#bgN%nZesqh;$%?!HpmT6cBO6m@{oq1S?jN5x__LiTUXGhd=wIS9Ojy zrURg|up@wxK-w3 z=~9Yw<$addsq(o1V0q5~z<@bsYC0M!2ayuln-1E_L9WXx;33O*KIBC{ zXl-F0@-C-A+?#@@%f@nw1~6bwo+2G`*Nd#evssQwV-iv3S^BaHc+9e_tg7NL(UXYk z&<4c}%h4mxvEhR&lcEo}qqr;Q0c^}j342MY^odSrn zf*8Q~^}E3cRn4OwQaY9#Omdh|0X+6q^9SbJbVGt{JU0q33}N(6M{_(!%Mj~5@B8vr z-+8=?2r=gk)ft6{1v!rtrUt~V41f`>t@}{ch#XCIFm};4;)_zvtiER<3g7h18x=(aW>IZpP{~-H8i5QIvQ6N z%XrDLXO@@?on<)Zk>CZLqfM%#$MU4))PWmq#E~t}c`Qv=-pV?!vLZpfPl>Z)Qtex3 z^7B;~^#UByu-rD`1eXSi9CI=EO_;MCcdJ{oKm3`{scvlphRJl?q25;mA8R&S>7DFV z@BV?$U=6g+8NffRF&XnsO5?B$X3Wx;8JVM1mo0ii8auQr&x(=hb1wUQe0@QvqVo0) z@t9!KnC>U?)Yj=^nMouDNK7N?d9N<~AyURN`u^$3VTRn_C3Q63aW$JbYO80|RV|(c ziLVj)YrLdmK{uYfgYDL$yOdCPeaAw1Oalq^68UIybi62K56;^phDKURifvrigrFHC zvLiBH>8&TQjKWJ6H!n0j6lRCwo*A*DM;Q9Jg~k|0;?r{flO0pgDof}I8jR!SE(VLM zqrQdQ3X?~NA)XOb#R;sSI-_v30G&>_x;l0f43yXkPg}tdAGbj?inUr#9pL@MFNpmRW3rCqaC$c)^B1}f|yq4XpUB!M_puH^I67^ zSAyCMwaxo*jmX0ko+0_@$Py*|*a-X_f31f3ZZ}ab_wfpy82~c?#=~QtU|A{6%y0~% z7uXvtc%_LW3S`N4%&U*k#u@*Pm$9@3br<**V8C7ug*`fk*2toV#I4ev2!JyFRVE ze#@P24wNUsb9~^xvHckU(&GjH4PYnYK+LKe&vCO=FR4om(^u$XBwmn3lyVQMC|A3i zD@fzkZ{^Z^b3N;H9sUZLk3s%-;B}8d8PY5llRh=Za^=__r<-ftK`R;#k;5zKo@0_mUrpmMYJCocy)!=Jpn!%EMxE@M5PK^t9xHmpHK?NuT>Q*zxmZEKYk zWq*Z91-4^}^lMi)@@MJ~qv)K%$$~@e?-;gYHIKwRV)`7zJQ%+Rrm^Z-tZF0@Udq#( zL@UxvORIx}E}n}zP?SgLrnTpyGBmkzb4)w;H0eaoIr+$|Kl!uI_`hLz3BnG-5<5%F ziy17==I1y};xNQv5E>D2Rv|qNQnSlkccgqJZGKk{_1t(i2sBxQuZQuY&fNRh)IeYHC(gDe6Uf*hBtf70R zh&&QbZe3o8G7=*rr%MKT#1xJUj^P0 z7OYGJO`!KGD%MkJwa~D)b2zu^vKxe-)nVLA1xGu7Cqo_$v zl57t>T#Beg=f6$$NZI;YNfNb2p3rvT!zGIAek*a(W2Ab8?3oIVlY>uitO?xa1}~hz z^kUs+cbhkLf-9K$tUDF->YdA^Ms!vDKG!zT$adwRbB z4v_@HGyiqlU+?S$KqR%F-X`oZ^IwlDqv%0qsO9WY%kKHK;|C+83VUQmhh_vRM-?0l z*(DiH3#Ios&lT4#>WC;YCgLs-aFwDC*dh^@RKEVyT?Oltpic%@M#T51ZhVEoB*r?O zuh+N}ke4PGlaPI;AFBx+e;q~Xz2ypY@EM1CP0cQ&{r;wrW%A# zlyy;TYlyxD#1b&qDY7L6>TVQI@%DgxM>geofO2TZdAI{sn5|8X; z$pmxBMv4j3D}%Vf9mJTK?AXq^T4f+>SHUh}TJem2EZPGZNX7Ba$xg*!SMcvB~KF-O0FudKny zARICv$Hf3U?vg$jL@ShRmv!Dfv;EZ-QGCzwn8c&C7bfcoNg-}1=xl%2q>y+_;f<`v zlcLR`S7xVZkK$UBr~_wnGW1d}jnSLvzDx+FAn4V}kvgQ;45G0|b)yc|D*~7_hwO+) z7*A>k0Zf`Bb|5)E@F(gIqM{MMIGK6GVE!f*Q(wO*ys)NaP@TOI)#$EAEPZs?Hy!uM zooWzuaM+qoq>sEVtX9v)@TsYopwf$K4Z?uF>t=XE--1f}HV6ayZn`(L-RM7DM!tZi z_0jb6H%fsC;VGrrm>RUH%{wLoZNp=r%~*pc!}wlqJpCq*^m&qu?VH1y*eT4~pht@>l0p6}r4sXaZ9d%}rj(k2#o<6`QJeg zmMYZO{$>e6RBLH50S=Z7LzkD;`=x`zT1$v2a)6Ye8CGw3pU|YR?HgqiK3u}Q+pR)r zvU;zqA%{zex4T;?Db{b7Rp=v$fp2?0d~}bB_bKfw5++IT-y_a+KY2=1y5oNAwm;%Y z^@69gq&nqwm%N3c$B^T45WJ{3Xpc9u`urnrTV5A7t|n(fbJKG>d8D7p9EZdB6D&qp z@+E-Hj^CSK{&qGAOTGlNnYoOEC-6Ith|hR~rNG!Ge!2{J{@D-w{^sdH zkTd9)Y0=??d|CkVA-{=>8eka-NCy8LPXWC`C&OhmHVZ4yv^_5PK^(QdXPxr6dyGvX zl5{Ub@R^n|a0W3LJjcx?y_2nCPTTnqHK(>|AC>q*k^E|zF#58s5!(1}OZiFPJi04~ z_u1}VBV>*ukJB&R98P8Nizut~JWCeAo*obAlWB zXG4EM0SS{MQ2%y5|QThm^GBiv{ave*I~_zWM4De7~x+3GBi8S3=Y ziW2}=cr)w8fj94g%M`!{{!vy1J-a{de8}M$bj~Brlww*+9k{YO)PxSADkb|G&Q;`m zJ?5ke%R2w8pbM>|T?($bUCo?3_MxnZGbyeDn2`7yQ7CgR_0&@gDu6ArAV$9)_0_tV zI>G0tzrkhWvMf4Lk-u>@=90;^L8ld7T5uc~l7drxCv;lQRPj0jHZ7(Cu~F9schcQh zoZWLW?Pe^X-q$-*if>I2qB4tbO`dX>1&*s9*y*wqRg^Cd>)zt@k4!uf07C%XMY4+J z>aER(@0eCl5B~81O_!@ceU`13K(3Zxdu>_qiq=On>vJ>i8Ku7lWoK;YnyRhxTHG)N zuZL26uREurWr{IFY&^6eHo9oUm&>lH@8Z|lZ{C&gR6K{==&LbAnHF6mar5$3isy>u zFybM8Eu;?IILo>!Yo1}0#M4=&m<-Q&F_pcTWqm0YG^tIr-LU`Q6yPE&$_|^>&(qOK zk&TKA1{(`?52aD{7{;nJ?5K?&ilWJ-xvzfXXlY4+dwr|3>E{0$mZw8?n#i_BS$*VS zKzv=#VQ8{~f8~z~#?|spR85NJo|pCcc18$T;ZG1uLNJ2ZqVx0Wvjcr-s{!|+C!_cc zH7{_SyGY^v%kw%p*M!Hd286Hy;p+p&u_**5EY8eqpOxVZNdl$_0()UQdH*yW6$Nz3yt7SV?S6FY0AV7X97XAg!gCG}{S{V>=H zccg*H75t+Ek}%8f&PAC~V6a4*@An};vY#c2lLhHV6m`+T*AzlcL1gkHnl5S*`TMec z#-HNH<>9~1pX^%n7l)aka0->+^4su0I;yuR5DZiMy+v%!c)&+{ajZBTFT z9Ur=9);3jnO_DQFpOL-^K_!T$Sb#ZtyfqC3y^dQ7N>N!1 z8USACviyKB3fGR~a7$kGv8~8L1fn~b5Ob3x_^m`5!otl@Z>1-`#Yrp z;EZI!-KsS-?{lYsVDD=NK>XlBwyt#%EBb%Od?FZnNY?V!gz4Zp0r>7wA$e9C|Dhrw`()-c6G| zTzb)0GEoJBjfk^<{~0n_9eRuOQC}fECh(ZT+awR3IwTwpPnkV!81~&ij1-`13o8J9!1IP>-(jK;!+2v zxTp30qK@#s`9d?Ad1DRyq!h9rf{&Xj#H9{Sao=B?_sb9j1^=F7`tkX1(O7-s2M7_G zkB}vFgtU3b;$oUMl5hciO4`WN^S{?`SbC=XW{<9Xw=|Mzqeyjw2}rk197r>9u9{a_ z-rW*a+a&-)0F&mpr{v=?WNk7evt&DL6jJLW^#ufdc@cwLJoJRo5c^WDT4oZ+C?zJ? z0yStt^!-~VgTR_3!VL335+v-umePlNp~qy%zOZlLnWY=EFvrLx49% z(u`oiytZBW3t=-;jTp?#5)KXs7-+%lc1_cPI3j z*4Hflh6YiG<3qAmu9TNeS54yRMuqfn38@vvhzc>HmFFmqPSQ+D4K)?jA;wD>a3yFT z#f5e%$=eTyDYEWM?X--Z18YC=Ren>p^DLhjzJRjj1|oLlzlql6f9%_z#{h*CwJWu# zBWxs4DdPbs*Be>w1{r`;syS@(_?kD%F#uXzY(}CJ1TPcvlUVemCSSaGe>iV5es70o zr2CJ^L6@N}eJ6v3Xem^0H)pV%wt;(v|s% z%k;|EPF&|rp5^GsghO$0}Jtn{b3 z{jse%x%n3`9%d$my2~=xaHb~3xz}RXSEhVg*5`N&(b9h9R1K8JEx+e3CoA1eBu97h z_?hwI=C(6M7+WOIKz`idVHL{L*zgT&c$%t+Z)%iYz_^+jhBi@o2-4H?Z~^0CW>TSO z3VzM5RSAy^+lfZu%uVVPfWjn-oKduZJ7|{wZTS^uGeVc_qmS8mnVVH8OrtE|4w{8s zNz?g5b@=C?3mKy$oe$ENF9*HEtYsucURGTNM?sAfDJH5dv%iyUH{J?UqMi~0Jy4xfU!vw^;7hkBk^%#CA0}z4VP@bbWI)Rw|X}Z#27lBDn z+8o8*$>i?nn!nEs>8?!`eLQYT!;pq48Z|1n%6QCn^v-~p(PMo9Gc$492s0Rqql%IuLTz>(J2#fIGU{8YQpVTHi=?gsn;%I0TUjeeV zB)heGK77RNf77bWP_^%08;16OR2id-!t0f{Lx$s$|MUOYm{4bMG_WGhq_`X=72;DC zZ%0+t{K@i9+(Qhvd5G-_0GI-R9VaeJrep4|-;er-7Y<>>gZm={6+3*5%h#jYZ3M<2 zaf`4txl`?d0vNc1naZbIe7=5Zt84LeEGx^!5&><}2HaYGM&oNrj`P5hH%Owp-PZ5{ zZApD-u`|+N5FN87&Ld9)u&|u-nv16OpQbDNBvv&KlROM~nB-XyhO!QiAO7{IC`Bzh zo=^0yfS3t1XY_Sn-fHn@y50XLd$hMss7H1zA*e9=AEqGI*?gTgzr5?pf~z&k;+-hx znp#x;M9r@(qz-Ko5jg=An_69sY^aRQgQl%i(|pk*_tXv3x(izV3OQ@%q{nmU|)wG)VO*qF|b0gU;9N zcbi{-vO!L+u$=1>&?U;VK~DDo`8kiTFEw0mVBD1wMtXkDdNc*- zL6zP;Jzv+HFByGZ!=_W>d)G(T!8Boc;y$ut*c5AoU$mx z9qbl}d!o`pPZ~8%lL(5B5g(I$xUnm(OjUf&j*cW9HQ(fqAjf4tp9xZk+LiK0fWZXD z7T*!SDlyxxl)Hmas*aI^61Z1+LP4p_F$O!j*s+I587h4qZ2`e`3<+4NB;RMf<>aB0 z(uvAWiD)JkAjuk{Wn+~>WJp$g9LiTc-iPn3d~bxF0fI-I-iv}y^wuT*PMLVHJxrrIk-5IP!T)0{&ijl z*T3HQ+4ryad-g4o>pze3I?j`Hn64HuS%6`j!KoNl$t-QI55-X{Thy<_F?_vu2o&b; zcYPR+ItdII6+mnU)-wsp0tTw>XHbUo3(9&brHSF`gnCj`*;qWfJpM(b4j@&SYlscF z16EabzzyNzn^d_~$}fNp7^79LvbpXy<3FrL1%jOo2FMG@L$+y!t4hbSyXMv{)7o2g zd8uP=1@3@VdT^z?L+Lp?xjSc2miAO+SUoSoZW1Frp}m628k)?|91k72k;F2jau{l? z%YdSib|gL!tsn@0@aP2M+#(e|uGOQrU35w*=Was$h$$g@bwG4kXmJS9V}S)Bx@2W< zJP@(VxUh|_yf<}!1WetRbIFF%u7k0EeD*^*JYOwF(b=jAkoGoK~y+vE-l-9sDjU+TWJb9(b-X~VsQ z`r%LP$dqxQKh6wAJ(bb{sBLSgGt!QWKNL|&NlLyrklr6detKN;xtMcU@&&DZq@e(` zI);#Cz#w&kHZQyL!uy*l4okx)a3=;ApWwNTM&Pbu zZTR!!7Eo!X>;W7#*;f+WKB*x>uKcKP9v$?RT1M@aZ!`5P7Xx!mr&zrU;6E62vB-*2 zG$mnY)Y$X{z(#-tl8rCk!TMN3=2s5>Iq(3hfGcybdc7zxs(NS;WpY+beKhl??v2gXD z2k)NaoZQno;iftc-EcF&6FcH&h;ChRFlB6Q5JaxSJAW8wRbMX=G=_AhK;F*eLYOqJ z%DJ4Vh(I{bvoX$j*O`!`B6NhoJjn1oyC=cN)T8A6lio|;IUeey4e z=A%#Yb}6~;LOwy&+-N3Uu+hb9lb&f#0XF)p=3}7Y}(KBq^FC1=AF|c-q4AbXz~}>I^s&dgw~ng*n5x)rlU+f=SuM zN!!H4FxK|SWQT5%F7Sk}%6mEJ>d9AGRT+GI@@T|D8D?En`0+(Ecsamoar49Ns-;0K zV2xc$j}$g0M9v;K444v!%Zdm0eyX{g*K&siGwcQT6j`t;ug^%_D9K3D)c4`|o z-~Ww7uEg-gidLAD-f+j*tmhR~R-|vpZ?L%L-VXg)9yEdsm8=oyIzaB%2)mGurlH8g z-3ECCdr%B3F+myt_NV<$QNg{) zNvQ;2()f5&*EZ&*(Lv-6T@_89;DTrehpxq=hDv+UJU%O~H$OOWJaQtayN{*dMBW*m z-esqa_D<319S-^$()QRi2l}hZZxIvTPz{s%rj1yaz%+6EI&^Y@S6KC(UM2D(weepscH0@?C!ouD-*$7p<*((S<;Uma%f7fFaQdkSviv0zqEzH)c~|a& z_t&m$Z3lTnk(;u{u`7L95AjpItW`m;<(;`(mqmubz+1f*Wdv|VcP&^BQ~Q)ABx?Ii z3We&@vpWHmZ=l@yfZ@?wXV>fs#XZV1Ew2s^Sg~(Eo?t|a+qM`dCI-uqzF^=am=Df* z$MI=-7}fU?*{f*S9}|*3B6~lE@5WE{l=~@>jWHw8pKQ`om_G>d4wLJwuS%eZfm=X~ zfaGN}>5jN`i=leDN8j+OoBkmFqQgecryB$wwXw6o7RaL0ofy%ozCBl1k63T+ZrSmZTQGhij`>yN#hWotBS! zb-tLo<08Cq>F5&0-;4nJ~F7Pp8254rN3a}=0*`@0^qT8Z-9GHSmYgr9GaZ=Pw$*I9Sr#ycG)SULnv?u(! zjz~v;v`uLJJ4gN%O}8XpgLX~#knl&Yz@uoXzgK>an`QjXR6mDx=f5~lrh1+!bdHO! z8HU`;_8FvVsvw2u-JO0JH?AA32j7f0@;vbQFKjdc-DOF#Ey!P1p14J z#3a%hX9)k0)&l>xgxWnsnf}V*IhHsH=2VQRHfTd6#2;SXzG145T3rLxjp}u^Awo(h zEWIj@5!M#3TUx+KXYWWQi(Hx^Kol{S`CbxV-hP7 z@=W6}$r_rE#i0>T;w=5mh#bpXavjgdsA|1uYLABuKacNJff#!vA;<=xrB{8Ni&nf` z!DhGF$J)~$7cn%oc!2lrX7%w%Vr&GmhUah5VU!0UCf-nS8ef%qMe*W+bW6=I|S8IV2=K7ibin7PxjIc2FmuF@gzKdrbVy6VyiT5J2fFD>w2uUNdongUMsr zP&Akhx|89eWYtgnx-NUzQ0Gr6?qm72q@HD9D{6o!ItFx<9G6}GgXtqH$&}tsx9TYg zBCGP06(r7*(9&}Cso$xCsCVqBuRqqEnC=$n;RDI^=u`Qirz1g3klY<%2Kp2=-}(w2 znX)@Cl{0K8YZ&TuysRR+JGI&B?@V(p`v6WX@S@j!v$A1H--5r0UsNidVD!h~CK~>Q zjcD?Lvc?-K5u&$KcD*B!L1*ifPB!M8UgYA;d%XIX$t8@)(#F`eNp{2_CKGi`>+X{j zS9Q%WUQg;u*L^;d3%pK#Btg;_%WEkSzIUDtGRhXU8l$$JlR<|Ber#sH?v zzJJ6X0Da|J^2}||Cs6fT>ruT=78sf4 z*{JGVSwPZ+L{a8zdc-0R*^UYiykTeDK96fUtgE>cUO8Ub6t!uB@wSx`@ z*clQM0iw{t61_sT+0w9)WNB}rthG)#wDeV#h&c()a!FM*`XgMQB1|KqjA zz~B7i_n-2>&YU^_i^H>9{F43tlMX=tb>6MYe&Dqr7uU>duBkc!mQLzqR_N;6_{RhD zN?%*B51DtC6W@q=uYC>IHq$X{QrHpzV+@_k1jp7?Tk5y)MfJwkjrki7N25A^QLCG$ zwR9rNlR+HOW}xEEyTZD+r|V^L9_%swNCq}e6D5W@l$)i!i4teounD%Zf@>RM^!f3#tHb$-D~= zh8%JSkjsqA-5D00lTis3Zzox?D7TO@hb2a&RZw5o1@DKlrjzY#2Xww+gGTNZG{^I{ ziJ^ulTq(7tGjWUDc;ZVe!6>vUHToHWy@2*jhc|LQ^%}$-1PP!p2#L)}5=JBjmbW#^ z1Q5_Mu{Fx7lElC|YVCcp=IY%T@;0P%?L~f#=2U`=P`?gtDh^%UE=lA)H-6JT>Gcq}Co(o8?*u z?%5-Ez~ab2t1!6gmD{BAw@K$wgTQ}tQE{fvl31SbFyv9GaGB7Bvn>8zyp?AFGEAF% z9?Nc8<;-X@vY5(-B{NB18SeiF2j*tK%FhZ87PUq&jeAE^l?y@$6PL;=zS|=%OgU zmoaY=iNGWRK5dyq*kW_`2=V-&{gt3aqO`|m$%4k!&iqju(apRnaGA3l^^%~fvst|f zRi%OLLdH5|4^2Ag;b;d4TCGN}IP47+(KT#rVn9KoQ$j&xZ8hd5q3rpcQ^s>(`-pGP zp=^EZGmd?L*LCE2l*F#au^IaKf=yn&%iDMMCl}fIo_9Y9?zVQ;ZbmEySWIK-JJ7{L znDoKtOZNF3qV^lECknDK$+Ao9YClPJ*r*5lSAb(rw$#TV=2(s{hJ3J?q8-|TP-R+G z6S%^|M9k0p`cpNx-Ho{dJAfIXBrP}NnzQ(L`O15^ki5?R^WYDjRlL3p-;rGb1Oo_$ z5R4$U`Fxy4oXtBicWe^xXrar$zRGJRCl2ha+zV|(YO_&$LE~Yk?%TYJTlF(ljbm4M z*pSW`)QsHsF4QNzm7u;tRkQ zNS!tmnYc*lkX|&(49(A9^r^EmQ@(AG)br(MHjd7lpPp`$1)sENSp<&xeohQAM8Rwx1*l zj!8KDH<txQ2#;|`M|Ws%+BtND8+NLetAwUQny)JKw|d$vQ9eTzqH z|DFGE*G|>&34uUN1C8@FHb{?~rzj4obAU8Xp(fOIUm-kTSdQNp$vi53dOhHzEUNT* z6(Dr}G>HxvgYl}K_dO0)LB~0cO=VTnw8?zJaGPSn?tWxq$n=8uKq|C3bWh`Goyhq( zk<=&xaSK0D^%HNBr3n$j&?6ASt5Jp{A(MpGUXG+!*EC;hDN^Es3|Z9D1!7s@JKz?P z)rYQQDZ%~1XLzx32 zB^}H7lT2etKPlkd%Jro=0G0Iv_421H2j?NFll;OkmBdmnlFl5mdO`mpRVb)FBudfT zFl=7Ym-Ua$S~2=9_?8g^t(?-Y;BON}hqYpx&`<})c`R4~hIv_{9K1*L02Oouz}mS6 zSm*L_xIfn;up<)Oo#9~+e~Zl9fWglPR=D{vl=ZEL)o|zLrww6-!k@3FV%zY`w`nYp zHoYD*vfJ5unhmME*O_Wsc#3CO`4{s(?06F88jPB_4~xh9z)@h-2bOs)Hk>H($!3UR#>8O7I#9DK!V z7#?jE#%(YiYb_b9FsWJXI=VMoFC;ZgtyWv1qV62T!3o{yAN((zprkeZ=Q|-?rggDP zKRK2QKCCUk_OE=KQjK!YkfT zsZP=_s8u{Az?Q26zw+zQbZTGrds&zF#~oJg;&A!UWTHlX{mI(qk9bxzU)k{I`Rl8@?gGHdS%HrwIca8JNMa0_Nusc0H_b3GGP$)7u=$ z#Td-S?)vHDT=nnjzeD39!?%&#Zc`b5hjxX0d(nL7Shtu&pTxZ4G8n@r0RH<{)+G`C z8^|vHN#-D`81?BLOSAYbrl3|pZIj@;E9;+cp0}#QApe^^Y6vR`dtu(8F1Q;ODUk6Q z3eL?U6edw9D$*9CuVU>GxVUFsp+>l>V)6LFlH zn&Nb=p_$KmUc}>6didhV8nHg2a7Rg-nun-;aSUgMbzt?zZ|08vIT-0G%U)RhE35~v z5_rwov2o@+hvPhpVPAKIA_-^4%2J|>dL_X07^$o7hw z!(29{i(+bdcQ0`vAchjz4WZx|HdFMiE6@6rnI9`taR7g|D zICcQ-%KBQid37@9C^H_~P^vFuyQeJ9qEs^2=m8m$b3}Zek7wQ78L=M2H?IebfL5Fi zI1!t?^M@WkKeKwU?!ovRwWlafp01C6XzpjzlZt$C;-g)+JAQ8leqO^^lhO|0QV(0k zGXN)gil__ID^JfM6gXCDnZi!Qr#2lmt8>dy^POBf)P!D5GEE8Xqw@4}4Vq&@MT$A` ziFQ$h=ay)Ofr=D!;)`~>kN3IX4GwLq2q_5-E0A$bN1BUtemc@LC=gPT9-yZ;{${)f z+kTq<zP9u`06^KA!VQ*UY^x2fv;LZ9aykYR!})XKXS5DJ1u9bY&=k1 zm@|wcPUIgV8zIpd#48)Kxs{Qfhq+V5O`G@A(mZ!yxu{v?pO#6L!fe~^Un zk!3>6CWX=(Bsc-1bFvvW&Kkil0Jbq-Yjt7 z94;Jx1%Fv*U?YP*I^d}6DT>+aIYTFVDto3W3?eIEP4iRoapCMQ?<`{32lHd9GpSDT zhD;Q9BfEFrDF&&WOm6vad0(s^(92?RWvbD|=mz!K^5K zhWgCZUl4gYl1=M0Zj|SZ&E+E$MktI>UU52F=6~C!=ZWq#DXwp|DT|{K>MXR_E#L$r)NQH6 zr!1~Un(j~D6`9^U268h*NvX=)_YnIM5zV-#4cv7G%?_=jcW5o-Hy+s|v|B)x)E$}o z@4OIqW15U7#dXG;ve+0YM1upjW%+1Yz8}0hlm#uzMp=F2#w~=~{I)kX5uAIJ)kjv` zI1(codqThGG$r=4DV|a2SHu~L!#{`cGf@zT`#-4*Mhpgzw6}dNSX6Q)w@65>KE8&eV)s+zg#oY+(!Xxf9m~JalL;ekdcJq5XJmWh< z97Z_wH;67SabCjOc`t69`VsIrg9TIKBt!SK333t6wC0e<4Ayg@|ECUWu?1*j6j#)X z8XjyI49%U{z(v#dSuYa&4)?SCMhx5QRC+i=P%%lxG}Q~@1L*)gPwG$le0{f`b z(`PQUrdcX6tc_tUsO!&M2d+3)*!(;`MV>CNcGGH4Gbka(xwdnvBi8iXMz!0-ZUCXP zed_s+YEukJG~o;6xW9H~J2YMP!=Gq=xU^(JN{QM1ym=Pv;=`!(|ykMCa4PBUS z=YLxsbJ%m=xFphJp(9=x!x3j&?t4`*4%03d4)1yYgNtNv`KiUWccfmBsmI~Ou7>yNV>qdEK^yFU$~G&%viuV|htpaF7FK>S#WF!VXmfv+ z^ZE9OVRxU%3FwHY*&b?p_;k|zc!~NW4?{959$2*j*Go%xW*tzL> zeu*^*xNCi$$q{UgHb^qOL3un+fbhlgRN<}qYPQgV*Q8#g!JFOvr}p94B*R?__wKhsxzCx4o+8V~&(e{KEEuKF3zZi>#Ir5u$Y20a)t zV>;rCPVpZ3*(5q8FHbDfz;v<(>C7(;ogL;nY;LrXQj%v7PEO&>t7mPH24G z3`5%|wF3Z#p9znLAqa<`(hum5b|y4FZgA(%gl=#{Qtig1@o^*Pigo-?SH=l8H@73_ zuPJ~jkB1={IU}7P8OlTHPiVZ{7%P~QxG`2%UnVpjZW07TnE9CpKJW;3F!wZcWt-su zQwl$z&Xl?)vGXx>PZgyD9$7uFIG#1bKV=v=bb#F}BQvMKCFgipPwzVp>t*Re(XqSXl$}HJ8}e+>)mosPtx7>%|HD zaHh}v`ja(ZU$LD53L_M{(Ex=3$_q>%$8;wA7|tV3GX{zIjT5nPZheJmj1vP)CSWMj z5c(nR(3FL1gfhme3`A$&@MHPgm>qzo$&tXL4{*auv>~SrBTk`ErY6J#1v{csB`}m#-3K6!gyIx zm8Q?U=jp17W=#o5#tPzu>yX2wb>VX=z;Fx&j?T`BDp7NcV|A&SrQz9f^`keihB#^q z0F%wjs=9a6T{uN>@S15O$uE~SbnHsuf!@0%iW58-CH0X5?=&=LMCxg*(8~A3(vtK{2S2?%HhBE zjL_Fck$&Qlf+*9X8o-`<=<;h>-{KJ~L3(u{#o9PLQfc4Qi0uVQj%@>YIP~$l_1aTJZ*(86(bY*47TL zulv5`*HV1F%i8L3E9+FYH3uV(EkI|ZP1i>^1)A`WD=g2u>Y3pbcG=Sun;ZccPN*k< z1(K_$FL5EiqfYoS{Ghm=?lVDg^>ORktNJaBC-ywL8$eKI7$K}+xjOsKMXIF9Zy>F5 znt|ctfI5lA*J_$g2%BH?ZYX)5!WMNEk37uaS)~l(dOV$4lv}oMVSfZuOLYNEAs+JH z{FFSYM+A^BhmCyX!_bwVk0Eik1r}zojEBc9M(r!!H0R!jylDuQqog;46!6b+L&H#| z_X7~(|HE!{)_sqRr*Q)2z|ms7s4#XOC69dwn01%bO9I_hDa}zQj>IsT4qL;se8gH< z`^p`X;CfoyWwh(Q()1A+b_l?)dthNCdc>MnrVhocdFe9#%Db07k1oVp_d1Itn9$}d z$l*N3;ZcwJM5&*AQ<@+TKG?Xjtn-xh6pf$`obBMg)rs2PxS6(Ud>bInCKC3{Pdep~ z^v9dOP1L<@deINBCDtKlmH4MiaL<_56vGN5ITP{Z0} z4XE?lPt!zwtsVu>e!sJEMPl?m4x(Vc-^QBB{<03|Zm}wB9ds`F;EKfA9%I?Tb$2iK zIM)mDGx>-3wk5~7wLEcb0UkG+<_W$uN*r5&u0~i^S{<03|V^M`$d~|t=8xZ`dFpvsi1IExU&d%b88Z%8Djx#o~5^-_u#02R&EvPlX6E>gBHq(KIQqo@LfPKnp zjlsR_x~7x2RD$^?Y7vCQU>ais&(ozmiyC%JGk;K&7w!^u1immCX71~nWfkPS z|IUl%kDS}wKfa)RmD9N2Ar(|qb_q!V<`9zRpkH=vgh;D*=u%xvp3}Nn*AijAzUuxM z@_73u?4-neS=b1-i5(p*Vk%NNG7J4=8z$b!^#|S)8+TJ;%`w>AP`eP z6Fhgn82uzkEuI3gNNp3u)kLBmJSFSdU-xKlow!ZuJJCxPl4nYOhU4jmzCu%DV=qaY zTBENJi&~vFMGIn+-sM##p2}^4HEOGd=^aurq#6N3+7{S@Y zdP!ZD8p#s{&8u$bN9b|PC z%hMu^r_AC)$=EG@`CslD?dP~*81k8gt&%}z0EdG9SRnf*hI0(50FJ$*NX^)>^G240 zS3O*++Uwb$$ffojdz%PHkLoHMhBy|;o{8Z$f%mvu_bIwl-lHLpjypIf=+UlA*DyM# z7!4K!Y2B{(ct<$1p~pJrMh`}Q3Ssn|*iZXLQ{>9A8`CMO-JK2mg5UHo63k%iZW_!o`H}^-*@iL)6gWOmQ!O6lPL* zf?;i?D*Hhm#p}xeO-7pX#TpkIc^_AZN#66iFJWikXxu2yK%9-@Qx;bv)Sry+B95n% z6YAVn>N6T=FR;G3kM2RL(yJqLl6{It_%Qnuk9LwQ2A5WjF94SoVB%|Jbd-dD98qI# z$TQL^RoAW{$s9EvhXi&HzA>W8qA2zd^T^HU(3mA^68SjlIrP~=hkfMmriLrcprW0w zlDWD|Vu$#BfM-j}W8cWL74H3fiM#8qootGYv=eSNjkMF}Up(@fRcQo-X_e~8;0Qld zuuoV$@V`T3dZEmevO(d_Pd~)>v}+UG;-aQIL>#5ordDv+h@+3f_*5;-6FNIPdfyBT&q&twsOXAq^$}_nn`JCwgjc0h^048_I8qoqaB zN%E3XH!{@{$i@zg9DT?SGa2sqw6PPAQXi7kMA!;f4zJJ{UChqEsj^Q8;EX+1$h#T1 zQ-zCu3xiqFwkj|*-L@()VZ~jYmvwYrB}=ca)Uo63ndpb&?U^Q2tg3tv4eAzFX*_D2 z9~yHzPScr+sYO5xfnYYP2HHjTO@p%6;X`O&^k^$~*HWPDb)Xa4yItb!;QG2(bDk#2 zn##MHm^H}-mzSunz9Wtwn+}z%!+`OGI^wA71lwmCt_dFb0gBMxPtWw*yP7sm-{jS_ z9yuO}>GME5kcoqo`@c^7oU;q;n%DSyimn+AHaU97V!X{ocU*>;8NKE;K?s;Nrvax$ z&s~-s&xR_AGa(J5Mi&Fa-9fUwe5Y=L&+6WDnd;U%X-fChZrsoeQ-cRWGfYfGo0dAk zcb%AOA}8Ee*Yt40JbKfmnra+#6|`wjO?)1w(9eKu45@|Fs#h$gnVI%zB!B_=ae_!) z8>ZyP$K!Opwwo&Q)N9j0<4(O!fcTM4Ut2#M`}DQlSWWQPCY>7PuMMSZ{k*mqyglTc z4DqUI&M6V1&9s*u$!h0&sS&)s?w%6wlQmAN)4xuVgtg7r>2X5+^L0wdm-NPoL%#Uf zioBWL+|7aQ^X5*}{O=$BjJVWcZb(f_hv|z%u>|w6H#CE2aHPw3sIT&y3FBkt0p(NN z-OlHJ9#DE3yPnUGUbq_a7CgzWXI|vRy{!AP{=s-R7~t|{n@;@vANcNm$JpluW5yEnN%@>GvvWEpFhcJE{2{vL+* z#%tb{R~WC5s{n9pX@6M7pMFhM4tUWRib3G%fj-b{2R=T_I`5wQt--*q<{V6NP%F@c z3T2dL*(Wzj6aN**2<2(Cg&6@|-ADAKCDnl)I@N5gSH&KqHq{&^!x1n3tc6XauCT9k zUKeNPoT(ZUYLq{;t_SfmD)CbjKRt=zqUk|>%5!qGIGzt+&<%KEkYm}BVTbXUXB|;J zja?n;ed%;UcFd%x>xgG`I+sd+&E}NN*C(~()8=66XV}ido0JD_C(FdMo|QGTL9#KO zD_uS&qw5)YmCo5JzU}!S8t2tnt)iJZCCQ{D{_D(LH$%khA=D`GwXQR9(?tYsI^tET z`?#W&*FIDfc4(`rMq$?d-JcogPj2dBb6M%K?ynK^bwP8)(U8c|u9uI2`jqc3RB$8bS9@UJT&dfZtr7K+l)q9a>c?1<614EuJ@1jc?C zozRNV=jmQ%v<>D0806SWLoCtm1~!hsC&r_+H!3`bKy%v5$mZaVRBI3HHZ$QLI- zo#i9tUaiNJ-b8<&UA*Y$&1uhiEuv;p*gfE9Y!*X~TWj>;dXq*Zlg;ahrgMCZPI&p_>kCtB2xI2QG zC_1J{G&53FmAMlJcMHyUNeBla?h)x*ZG&WAA8QRm~ z2j`lvj?3^KS-}S^DElPd6b2hHmSnyj<<_lT5@T9hzIt15Ex?2OI8vC8buLv&kYTMH z9;x2dwz;+nv88q^HA%d@IG5hxtQ;!0<-{xbeQzuByN*7V%Ez5RYC<`0g^dpceAjuP zLmdF6;2&4M59>QP45Zcai*-^t5!vA$4MZ#_Mo(r#+uxplyIhr-MJ{S@btanmZBxDu zZK5r}&mGxEMJ5%QR-6$y@~gOK8H@*J_q;B@cpt-t62{?3HVUuuxSHY^6IFh(k6}g% zG9@_C_k_2;hr^)LGk4VX!PR=(I6_;1;YNA#i2 z9eD~z2c~YQuAL1pPDN@+Gg^4b62MOO%e#IjROG3)m;%^lJ7CkPKQ5%bgS~MZc3kSI zA>An51&Onl{yT?(;J(dko1G8>x{=BZl=V&sD>UwoL88@ibKy z+5i0W|NSh7sD8?Vk?M?M%>{Haf8d$!mOwks45Sd+6L+)iQqqG-%$jglgQD=G_ahQR_6wYf} z&lK1R&3>v~`}pkpMvCT7O>wRD4)i!sl@~=Pz8T%!2*f0i8b?z=d&Gy+p=z#q^$z|K zWX<=4@UY+ax!~JZI`AbHMSI=1O*dp;q6~O+5TG{iH=dl4%xfk_OpKT;xDO}D9|)@_ z*Jp}bikH6|yFx`u3&5eJqZ;QmZO9aWS-x-L9#OU_Jzc-r@a9yHO1OBjrOiISx$$b& zeEwTJ1L7B3=2oywL9CJadBhnBcV3hjRaey3#MSPe(OsGtSc#Mv>vVojU&hBi)0grXo%&swHd&vek<5ETFXyq{2b|Yc{K`trYC*b6u_M5 zps%#Oe%jr%&u^XXpwq*IVy8qRx?{>?St2(m?J=XV_38=r6=D#>01j|gdXjX_^i3L`N^I^b<5 zD8}i&jL;abqhT!lz}#T{0rnVv$jlM5uC#<;ltT@O1(vr##Je`t%b95>`%?^ha@riq z+uf_#K_{D1(l4e0Sz36pYwGD9Nc+`3sOv7swox$TkKw!N%HbKs$wHrv`ugaI9iy{> zLUc!daId>G`ba2>`1Ua6D)*5z`D(g9c~^+1h&W1=0Ga`gE)$|7*5vGL(Z+#5$BC>d z1`ND>nGhYZme3JOALbYJ@KpRD4gVbB?>K~ON_D>|b$UH!#N2~4N)>R+letYQ)or@sY9NoF7@1%A`5L2cj?)vLD zzw<<{lsLW|aiMz`CY7-$Js|7ObU(&XUE4)U=;=Pu>jaAg&=Zj+wSom+7`xY|;K_|5!~`jvxoC?iY)tB< z6sw3~OvkP2ZTlT7dsDEWF5l!%o!9u*Aj9}p5#5tp)*uVCLq^vh-I4pM7OvOp8q3k9 z107XL6{nki6uioxSy{ux=LohZ;ta)^7N4?sxv3#F`lfo|f-xG3jP0t%+)EH@#PqVx z=H+s%o2;*ti*0zUNUbmIyX+DlOa?tX!n-a)KkKG_J#Hg8HVF^frTG@(?|evSmS%%U ziVZq1qqNi{d$rl%i4I9#Z$3EZ@$m$AJ1=NbkSM&X;afD6uRoE!Ner5@WMXXb`FVX* zL#js#ijR+!DT|Mrvi{AhvdH95V|Q~-%@GTeKb?1Wz9jXrE^|YQQbuM_sccaAn5Y{y z-7x!V9(C;eST}0=Ba`xT9#2n`eX3JAn{1*z`deZ1bSX=sr=H^*-y#v{iuq-Q%F~=& zE|uug$z@ymk{K*MZkk`-RZR|iN7ng`$F6}Xd8Xv&JU*T}Tqy}#->P6kNotoJuHys@ z9S>GwZ1ElQ8vTJi`V-P7hnEk%ma=Q^6scL3MzLM#HJ$>5B1jX+HlvSYz0$AZiR#OG z)nUciNU)&tG54rxV$y$k@eyeb^#8}+xvsa$V_W)Gd;`krUA4P!hld!&<_iWFhh$}4 zeQO>h#K|^7VBu9i65H5_FGi8GT|iTYt(GX&6s%C>nn~oGQDn+mqdhzG5YA_^I5EhLVdi8EHinDr~7(O%H z3MGnz;rCQ}_pcI$bl;jX4T%8D5+Fz8!beRbWZ6%imi%U3_09v}=c88*5JQW6zLl7r`OUO|Ikx3zW*ON6A1cDes0s#kKTcuUdW@JB$o&Xp36w=)-n6VyweIfCnk`Q^+$u5e7TCJEHpy0w zK*Y^eN$&bEjh;=JJ>8FKp9L|tvKjvHg5~>9d;58Vx^Sm3-rnrEGOV_D?jMJN*17h1 zU)a^u7h_eAhWl^;3@mx(CoHcE;+A$FOu9Gce{_HBxs=tp$dZ8Ybn6%Y=$>Vx^g%~U zpoMY;*?1ORJ4J~LX<5Uq<0Rq1g>MwY_|714NCH;@#+~TbGyJK&aW2xB0FJTD!^2xK zbL=3lCO4M2Qhw*w<=azNg?Vy76}F^V&gj8DNz7o-dk+KNUC}drl9(Z?q+pvhDb-62V9$*8`3Jwc|sMV+U8rNXFQX+pOg#f5wyL;?B~y(w)qx2 zTJ`n%9G|p-%0yF+kd$ea%N(oBzjbjru&S;`)G(sU!Q3;hK|HA26^30%H)Hgzq}d!; zznQQ7{z5$~X@7j7o=NR_5oHB*Qbui2`HZZS7lL$H1v%)L&^O;G*A+h+J@L+#zPa62 zO}DgTHMg|RxBf`e%sMt_HN_0&a{>RX8m+$dc&W-7FHd>kFjgF&HtOuF(uEARi6znF zrJmZ^{CbXl`=AIvX-mAWhDrTNUV(h|QpfL^eB;>pSz6U^cjNr4y+A%h6rjBy&<;@v z)K+~!VI9x}^uHItzb{(7k$G}0(%guSAi;H&TSwK(1s>Iw;N;=42Or-a=0EY=@}TE} zZF?T{)HFJ(C1H*S-p@qfUR}OcZ4Rj4xTw~yH_1F&xqhXCS_)ho)Ny%|b+wszeLQ}3 zl3Z_>OW;`Q&p{@Z>im8)yjZfg2jw>o>h(qOwq?#X(>w5>jybwj-dx^q4U}F>I~|r@ z9Q4zLvF&ZCb7(*Jrmh=oAb)}tY^ulO8U^kqZb#T&MgRTC+V@qL>7Dfk)={nxtKYag z_?GGEyft|~D806K6?riPQWo&Z}=|HrwEq)j6=B1LOUx)!^~c4rwhAENaUd3G8daZJX;9zOHpJCCa7<0JjOcFhNm zCCst9hkjTwpS6fi+scypU_u|PckoHkb$kWU5Tf@jz8jiU{Th_=@?sCjR6D>{j0zm< zSfla1=%PJQRb8g}X+2e(zB$rEy6UpYVMau7q!$Kr_gQ>s#TYVYWZOKX<`*W9j+UtPglEG~T1iO#oh;roOq4cHu%i{HQ2*kN4w1 z`?h6{csI`Tr1-n5-1*Y{=$dvnl-^s+&Y#prm#&)>I}$u*ecdAwZN{S5JmQ9S;)E-C zx_2I|IoQ_pl7+a?em}O!VnKzdO{X-Uk%}&EBtVC7PMzGRIbC!XFl>h=~Z$3ly&!B zG~*Uz)+V1Je&mq$4e0Uv&X{%_Fi~x$n-51XE_&5DRMp6bpPhZ_hQnHq)Lj)WJ|52L zz8rYT>EC5q%O~K+<68WCIv%s4N0b-Us>=1)aXbvY%+j|-VZs{S3lY)^m%-{P@gVST zuClk=Bd9yt%BdP#s6$ap-t+Bt9@I(v!$Jqaa6wVu7%_NIDAs zpDk{i+(-qbQ+6bkGEbs=0q(tORR`p-sE790Gjvz$S7GP?E_IjDO712l{Ag`=L4iN& zofZq3U`Sv04 z!B6Nz(pQHO8S`yuJhXqrfHtwC56x#oANq!@S@!7kZ+xEUp#3v7D#gXjbIulSr&9Tn}CoMP~!Rg=#g;68DU-G&?hf#ZLv7|kA%#ilb>^gQT0&@wX zv{g%>55MX!IJ?$~ZRPnedyVUJI5trY{PSUrX3+(iduhI(orecvXA`L5^(Zp{A%DZd z(C|z80=B!|?>OOKDghjSrP5!30+JFc4cMix5x29(R<2(R?S^nMSQ^{6ZWxqA6G8}A zg0ODzb@3_l_Xeu6Z@kK4RW#*MI+7V23pfo0ETzUN3X`IB)3+??^(5I+$KbjwVR>#i zic3`~P?p*fRi8QrJ+kDDW*c9!x~{mW^m-=HabKqH+Is+UD)q8PJ$WX02c);=PD9DP z(SvxTzOBwtnyce~#o1hzlr-KE`GC&6Xn(vfjtb`X1c8Eu5j<*RC014XN8T6}9~LJN z%VX>Z@_qB+8GIpA(GvO9Hvy`k)l*}40)b$uY~T7uQOaXcCLv>Bh4kXzKFnt-uG4{^ zgP{Pkv9!>R>{Xa>+Hddi@w52&*bO4qDOk55*7LC{GgwNJd-)=`YA@G%Kj2`oOcpPU z_%TPm5mt3vN9u9uBmEsRERZRTOe5JhjXFMsOabJnzj>}t<@HFj$6yW^d|IQL3+duY zFqwP1G^9b3&nA?vSHFE2l5_obQ$YSIw)eptRb{cbL@Hl~iG=hC)Msh+je(v|BPg6? zRN(S;Ra8Ty!c&k~_<0)j3f3!Xh`4w<-0=0lL2@E2O3t#%y}_A0d3B1?}y z0aLLT)Mc?#E_~C1?i`1v!foh4Uxn^7b!hLq@7@e^!t`#f)%I)@Mk%Ie)^;1y?*<8m z0gWMwlO%<@HHOdV%z}GmO>B`;s@)UUAxG+uCB_sH66-9XgGpgD}o^M|3h$~>xNCNEH%z(s1*7PDb+ zrPXzrzBluq&$`C}I_jG$>I6o0zy!$(lPs=hb3?wcG8{#p#fAiGJ6;#|hAV-5mAx_mIgOkfMzW5Yn^sl9e4HkypKV{A0j zjt$suR>kJniW>&9g{{0ZQJL|hJAcA8qhc)|Bi>eJ=|1Qo42E7tL%@y|hn1x#iUuD4 zaQjRum=wUEG+im+-W+|Fo)A!xU`2B$cT5>n54eqtAU1UpmQNHWNSGzkt814TJ=@qO zWwuN&#;c&rsz)|{v&`HnZceyzSyVOeFq1lvbBelfX=J0z7U{$EHBL*Js8n^Lhym`w zlS=_U3By1sg3%M%4z}!dz^FGn0i)iuU~Jhwz*I7*s{`f8+LN=l5Kpku)t*kTr!ttz z2^;lOH6e4sRZPg^D%FyXn4{Yh~OC~GP;zH%lQDgzlRW(U2Q(%9(9ZtO&zRpTbmE#-*JVu;k7mGG|&c;z_^N#q^nuj z8GhT*(+-Ro%yBN+bx2kNylp#JCo|&2zEms9s?*Y!fD>^mt^~n zE%ZHY!|##0LeTWkK-bU1+TG$RJyx19%fWZm{=sEy2*2y*$Yo{C|W%32dwh}8P9yp`?V3n>F4dQWsXC| z5?RAMhXhA8Rgcr{>iTs<7v|FTYrSKuZ0N+{oN%&&^w=w9azI~`I_B?(ISJw<7vxy^wT0mW1VVplBXE^QZ)=FsqJ+D1*_RKF0Zpbps zbFQaFJ`$nzzh|3=WYJtxL}v4Q{u!Rp^z|2>Ot*Hp$j%ON{C%gA2#J=^1i9y)&TE>Vq7Rk3no^~F zE@gEtvgE&K)BF|y2?iuuK;zt=doD`}!80CSjN>mliJFXVo9dBz?rHN;n|Zxc=2na0 zAhp$7d^(l#6xVnvcxyN2?%mpl{@pK=7DlV5t&elk$q17S_DR{2%x`p%-Re=B$KrA= zs`Q3UnZw+NN18>AUz6xEzb2VUzo_lu4%Hc^tMI(eKetIvZQpsNb;Vg0sV8n&#z0o; znvE)T7gALUnJ3pG&FhC^Vt+Ne(M>s9qTL!d8F7 z*(wavTs=QQ^?|>9bX|a8Flqz(OS3@0ZGWC&;w2Uz&vN!9`ZRoS{i0+uV5#T9Ga{IJ zr}xc~Yw2wbkX(+% z8W%a*RAz=TcQkuPF{@4VIUrD?t=Ag_rvcngqTz3lw&8O*UZYEs)v1;}0Hl5a>r}}+ z!5=@kP@f^xKfp3XDm7uJHe}Sw#UE?Amh@H7%({l2$YgdO!$TDGcRR=&rf8kDnxh+z zb5Ye%DZk6KmQkkul`|T)p2!At3)U^P?lCi~PO7 zWA0V)6e3#Wgm@oZcnIB}B9{?-S*ZN?6BZR7{Ra|7D{)k~4VY> zG~j~{-nWykJuaJzzTZ+5M86kkT>@BqMXuz$$bC%}4`0 ztXM!MMOzxMJ94;hgK1Q723_P?B3??BkMf~ZgK!xFuCO#cQsTe2GwJ5T{t&>uLN-fp? zVL+;y3q~v61sG7R8Ifw+!h@8)nNHPQWLaeLsP3XNuhTeF(!bIKS^&+aNN;L<@vc#1iX|0BnxpG7r-}Y!;wS%k*r# zlGVe3S)NFMWl~4MK)639I@P2pADTD$93+}XtFkBC5y*F1pTFgMipQ&e!oFDUiuHEw zJcuQ*OX)#At6W+4iK%ZR{dH6qpHgMsxQ-)QQ)U8S5bxcGy9mQ<$T$0~Hr6&ldZb|H=8#5aR^`KpfNZ*rfG)DdSI^Z4&>Et};&`Tqy_ft{&tvIWgKq zo0!dKa$@v^e4KpG@`aH<=E)a6o*kQ36nNPRb)`-ek5vzuAJ3R2{hiodYIm*wwmr`% z+t4I*ZX-UqJD}KxSt02{OP@65pPf@s!ywRk4SP5r+`Nc^K@eLQQ*b_`OPcR6V=2|c zOx3sIAC|h&ls^wo#S4NIN;VVo1}6M7+Fxc`6xz}SP*r42{rFOUp-|GY?T}*wGk$sD z#E?fq`8*vF&S(LpTC!gd@U%#a)$S$Fz|$_Rwabe87W@K&CjvlUg--^aXh^CN?U%)j z7R4uGcsL--Jo8qlF+d9k*g>AKf~K{q$E^5;>BLruV>^#s18Z*_ukgF5t*GxH zuQjkx$MFikvz|Gs!-S#7s9oavcuw;ZfHL30Fj4%1X93Mw;Fvk-bbs03Mmo{Ch;(Z3 zyD^oE*D!vR2ET?0L5t&8PnLftRTQ*sCD2&6#QL#+m1U`(w`A}bw9sWZ;L#~dFKG=t zvf>zLW=kTgW0_v-0!CBTW%}^#*`$CIugwAG0x)~R52vIFEsbA&x~|S~QhYN^5L$>2fgA$ zZjf$C8UFX=_?*@itmh>2BrwOIOt3Obr6=EM73Hv!TMEx+X&u_7oK`~1LlB3)UQZbC zb70;{6aM$)(rkm6ZDpEBy_k3vsoGN1@VK2QABnp(+s`X@F4}#&C&cL=-$>8HorXG=lg`x-duyDb` zg%N(wnJ0i!WmTA6`QW4Mb6~MZdZ4uxdxDVW*QSo%)x+dx^!`{?@Hrq*s6e3th4z(u z0`i|GR{8EHSPohrzj|{0o#xO~5_AqAjdb!Yz4Hkq(*OO$PSX@8t% z>U;C?fq}4LLA&FllNp|eM&DULydd$SBmSPd85mE0(rfQpqD@Y@2zKpVMwprtB-pce z3DKs7IcL&x`cP0%pZTXV-IVzHzz|XyT2muvW}LmtJ^4h%!^M}^ym+%&RD|Y`K+i317uq(T8w$|$x2n{ zWPI6Kt|HN$JT>$&lag{hg$ciMF0j?lrvO31&V;w}p^<`A|T&#jfeT?Aqz7VO80#)~1_(h~sjXvTmgOy+1?`}`Rr8x=h zz`D`haOW4JOVi5}nOSmYH@pl{ye+ty4GH{mcRsW&yxg`XEibuOZR6bQU1^F#lHzhp zarqM*083i?)`R5*ci&BX`@7Fh5pXH;HnG0AaO<}(EiK;q%}Wa{_nVje6>+1+&1Li0 z`0MShcLdrW?%KyOOkZ)9CUIRr;GZ;4FU=(~3+5>XyiIR1zkb7kRJ=9HA1MOhaKGBa z{pueM01YI}fQ4hA+Vlmd)h?T-cAc_=0V2!~!A{@beVR2y#lFU61-k#KXmvq&g4d|~bRe0zX@YoMs=;tF#bA+{Iv z6vrR19yCoMs`kSOY>3u|KpUd9al@ons(V0IVP?ALThDvF6iLI24)P}2q61|VQC`&Q ziY=+(Aw&Wsj9&bB&7oBTiYyGRc1f!$K;Iw| zg6UCYI21Y5?Tg1n21($t*c~5fhSfeDT0uh4MwuTeoFPrqk6G-!W6d5842d8em;9Xh+oJ50yu?de}aN5{6{v|wE;b#1}2Ff>L0hADqX zSz6U$Q5w@t%iPd_2h=T4_o$mynBhV27;p%@Yeh|&?Sx!cJ>J#?sMj{;A^V7%3@5ze zn?z>ukNU}P2r~umXEMP=00$-ON&@%Bi_C};C`ynhOW<<1XBor1sle=#D)wtjTyFR( z%cIIo@kxeb*V5F!U1GJJVNIFkq51q<_-!X}Q;sHKmPn7T=`H?`KHu+0aC?G(dQ1ON zVyo5<2)=d#JpX)&lQLVTALC(N=B;fHmwF}Zh&Dwqd0e*jNBoFkU=)sH4f8f02+-35 z&`%y%P@1kRREoR_=f2Cf0@(@{EEpsASmM_}Qu|5fQt8j z-2L&^mx7p?LU zJ8Mw;Os`S;5y{K7-br=>$s+v_EZH{jAfR(yU*F8eov%NzCLhXq^R4*_EjMWHGL&H=?H9GpGPSpqF(5&W@yLMScN0XzkO5Lng zE#{$cA*ORmv?~&$Cu{2<1r^StCBz z<_dF3)FFp?o?<4v0_G`;94X>JFHZwaJ-iRCps;J&)T>QCEzeKm{xWynThNWkP41+( zaQ~Cm^^2D$Y>{4`2Abo;>vUe4I0wczUVGl3b`Es6$gybh`lZi+?iM1a9p9_cuj0!! zlTmy+m9WT*jCjaMn(hBCO5TL=CTwrhzUT3V2|K*TBOG0?>{0P*47d%Cf?H-X?Ilh? z`$u%8$?52}6}=>gtz1~j+qeD!xwZ2>oz9}Ry+^oE*D#DXfnO>s@9}&3NQC^*NKwx> z8xgjVNsInzme=a>piZF?RN}38P8}=$Nnz;-^U^6m>)u#t`9jO@+F5~Dx!;Ucl~%s8 zjjVvFIYPjL=vczyGHJX!RHR?D`g?{}L#7X)DstE3-uY?y0?f29JJz-#*~O~Qrc;-0 zkZ`-Cb@pX{Bc9i%4!gcg{>5F5nU1w>NOrNlR{pj9Q98Z+JK}j3b$FlSqaV0B7Fk6x z7~0XaW+c?aFQeiLwbq%ar&2@YlcF5a>-2<4k7G}m@waA7)zmMw$-5Y4(O19tls{42 z)Mw1@*HnGPPsdUw@FX5*Gr?L(B1`)aj`-9sR1yM`;5}HSuvJ>`Ju&zw%I~;LX3QRW z0s>29`_>Zx*jyD&d6Xe%0@$ooT8jNJxXQ9Dnp(c$Tx6L<#|)ae0_LF=3sx+&;+_M~ ze0EV_7&faG9EQmr3J`;2#e&2D*+qe3h^$bsw0Z4~F2m3;v;w)@IM9nH)3b+cZ|WG- z%^j`X^V(iIJSRIeyRAT;n$454eVI-Z83lYVuoe&(NswF?-a(w%&!-IHG$oi*r%cQ7ONn$tySr zAWmp;w$jD6Q?);>|Liaib_3_FKAIxi)4SOJHU7aU$k({4>a(i-pU%NCV2#4oIHG$2 zi}reYi>W~u?nh}75DKdpAc=UtQxZFZ{V=Gc`cy}4%3*RKnHG-2K)5d+)DKY?pE3`X z1$A+-L|d>s1Kl2Am6e~;Xsuc)IYvEEj_-IQ^FnlPVDvo<7C9$Dijx0Kf=K0t&;K(FpM7x6QQ#PCPLE_ zUA0qmXhvu=LYo=)xFXIpe<7UOOb=(@<0?4ud3$C6y1*c!1s#yi_Ol)^O8-s%zNRJB z@$2*=_sowRiPn)oM?HtD5H00t_K5dj;IVjmSp`oe%dhk(qd5y$1O|yM&@vacpLKxI zYZPavLfzHRm%UQsSIbA=*(&^+N=8i%pCmlCm9NIvzbBR|sLJEsXwgmGeA&7t=m=gD`P)Mttg0p!dk+QRGE&Tc#H zr3|S02Xn(@QsFQ%YNxJby(BM6w&btErIgiG?Jc9ajMHrH6UFl(Y5l_JpEBkbpZa;` z7EtGj%s_NIRlj4wFP-@j|DTO!$pQcmLh0v7mqPrBg!)&SF1aUcd1lw-@EjJoOoV+V zGm>p4!uD+VW#n2hcp@J!HGnETno%G42|vI;Xcy+bNQZGRZOVki9LJT0O2CZ&FhWIF zs2%__D&Gee9%U7&r-2e4MFS?oQiYbP*-TjQK?jnoLS#Y0@V^J2=3ydKzvdai)i=i4 zLeKQB6GZ-JZj@$91PxPij%IOZG)nI}0q;@OK<5(I<}^D5SzF1nIkC<`(I!nMY2{Ek zSlOl#rQgi4YNp%a8#CLE=8Am9<&RT0keN)kWa6BeBxgx8(^^29>C_q1yBLDgxn9q> z|FfK)@}iWcD4=LQrZS+!r$mgtE}_^H;akE@{@_n*pKzlpd>xe!`3(3 zZ1nXna-O;wK z3%^miNS+_7P`%a_lH$lw@@75^Y~mfB40LtM7CK>I79Dke`mCCZq7!G=b8I%~?4ZAg zWpW}MlQXxJ;1quG5q`Bvx?$qo1*a#vireo&vCxWb`K$1$&hfwg@BiBe8Lbsx)}T@C zf_E4HtG7MbF5Evl5(SZqH)vs|K=NIah7bY>73lM)uP zCRs-ydViX-p+BHTz33uq9MSDeBl%IOrN?&l2lW!EI`I)IGCzUf=!g_;4W32zReORl zdra3>08rT65n3Ii^0z!TwWJ@!7`0QIcdA^jW_(1bhM}vPUEN-h*q2zqKE6g4nG|*7>=fGgm!d$MdJRXx+xGpPpvi#4#)->jZ$i8jCP1b zrnLIQ0@22cQrElq)A3vsl~j9|)%=Tv($FY4MRyjIfUb1$ClV+~psm)AeOygDNlS{C z-PXNoSPBnlhY4j&wqV(qV-B{56;Zhiuz4<}1wrO#vf))f@HAvHKh@_QVd5oBFhjBU zp&wuH_-&M>?`7=ptg{g&{Z`8yD)$6r2YIHGGgiqJ!I!~nmbTm%78Q9U>-glr$w2b(ww8&(GcEXT$45qyGLV<50k3B(72ZA7py9m z%lf-0KRI}CFhE$IV0j$v_;5@6LM-z46-Et&bLEpDbLn^WEi}uctK1$*)-cNhz_g&Q zzNKb)W`ADPbOFJY1Ti79^mML6IMq5Pfl?^6LSZSq<6t2loc(c<0dT4NslsFwCAG2h3)(l?qJVCH9@F+i6o<)unkW-Hw#bRQu5vVM5DXM7#p`v+?|SQ{2R&eTxfhrg=7`e{wgFxvrBGFtC;n7 zV`{3pxR_oE^o^U$mfkPiW`cIpKpwpVJEFl<+Gb3k)R8~Um2xF!f_hH1RH^BA> zr|z4rA$C4!!DBAGA@VYeZfi`d4yStrSO1!g6IG|G(`wMVy$6i_Yu1?QH{>5CZLZDN zeQa4EOg#-TyTi^$KkW9vR_%s;h}fR9+p0qv_4H@mrl%bI{;}pETJiWI{aajC>!VZ^ zOnwf5@m2F4n@10eI7jt3a^;!cx;+!qvgR#X0sYKxas4pxrXRV;+amz{V^8v`jg~b# zqlSZ6`Di19;iUVXG5_eQ_{$2S6(`B;!C?XM12b)`Fk@!S;tC|2cO8Um=h;v>(Sg=< zMjV>+0+KyoM{g^8o{0Qx5%%ls>OOZ60tgI+Mw1s}$``MCWk`H8K)W=>1kW;b3 z0oe|EKkEm_>G)cKpAN6GKW;iy3@PvUSdY`}@Hv4{vP0?24WjcPA@z4jp(BVs?~0Pv z&O0A^Ui{N)M=I~V{A!0YKeHS&Ja=238IGHlYkh8eE8qIm)-UG_G4&?GcYJd8%TB!! z^v;e`IO=Z>PqiJ_Zux^NPO3LWnf}2jRoswk7MpMxYiUydML*(0xdBt#ag~@q`128| zCR%cT-3fa3Sf;h>VW`{_5*>ucWV#~xiRi@49htNh9vx1|TscazXeHlvn1_=oS3N6r9>j?mSA2a~`UJJC=@E#0)3NGJ} zSkozNYX2nmrWFsQG8vm@OBm)9932p-TU$}!K=N`5FpLr6t#R13OHd0|gtRS&Bt@t37|3oniYRB47cx>_?5!brzcIe6V;9n&a_HC$D*tQf5L@x&+FKOKw zQyOOB3Nkz6W4TzJkne#D&dZ7hjWep6q-sJcmEZ`W;9*-+EBLM@AJm}Kipuyq)XRz# z3Z<|OEHV)71H?yB9D(bx{KZPD2F->hhCr};5!5?Bin#>v4P+K==&cUIg62w0~8ox;{R zWxyYrI;#}#stT~WuBj-ALQ5Qxy_^Mf5vs@MRF$-*{ziQq0d?&eEYQVCYTL>vb=a8x z&S^6qVCiQf-GK&U|Egu#W@P&D+~BB27_D7~=)1vT znOa^)?sESazvx<&HKy>TejQ*X#)!lew8&BE9*FF%?7UxUZctu+#aWs_ySVN_3J#N2 zC|ZR>(aS-EIsn6P*kbI3nZ$#`J|aEtBQO+Zf)1(_MrBvDKNwD{rfi5|(Sj)*>#&6z zAiAThbv6qGfu*xMrdnShhf(#baG}S{9e`n>6j%MH*v$`dTl*6PJPyvj0a$}~XhBu=0>qtn?oJ+~(En8Gi) znxM*%CJW`(&{fD)1XgIv^@X6<>0O11q1SU?9s06;;ot||V>sna3G?rihEY;1)cq<#m+F{G^InhY(x(C>jVJ(VYkE&b7y`V7+D-2^J(+htaxW z#y2q>3E*6L*pL(sXfQ%Y#Cs+q6O%F?rwSO3Obi)8TE{K;LzKu5l`zhChUK4J!iH^z zNNW|S)r15ax8M;GPhna<2z(n#JRxg1Bz)3{e^%+`B9j!_n4F*RHic#MwhwyKR{Y`e zFf?xyOsfpgyMbllNTY3-_#JO_g9b-kbem?e4GRwDND@1O{c%_{{%R8p-bP_fLs&E# zYZD0G#$-)HfN?4uqaf@W3yim6p{BYrBHkmJT~O&0S&s&H{S{ZYzPXKXa2KXl*_Z7R z$g(MW+u-QV)LS++ZyH7M4#iUZo@QzN<2K-B6C53#H2P&I-6NJf;t#L}Y}l1|l}B2w zZN+`uf3dU$S`eEU zm95U9be~wbNj-cnI*gkQW+2`pnYN`SijPjSW48g*wlxXXIh5`XOC|G!hNtW0m%rl_ z#;(9R1$sJM*mp3nKm+jg64Pt}o~ZSFzt+MW!yY*=7$3V0ML(ltZxdKDT6QxE9>>gD zG~lh>VIN={L{H)>jS%owcf(ZSHr;k)uHJIng@SEfurv!rkqoN&w#x;Z>R4&phC8a? zFP-qNn;sO;?U!8yfwstWF|64j)5Q_yqhy!HdQXy*ECPLyoaD06b0oZ7KB{^>HvUFb zPnkCKLv-DWGw)n|Q(d0>(Nr$Nw$H_p9ams5D%5w3$JPgG>fTWl3OMFKjd)MZ10EOASHUS)Zdc|CVMuGMT> zdfK;6CgBr(cy@j@N;sdoXV9Lf$hx+pQay^K7a5LEyA%>y-K1_WJgf^NKpxg5+~YB# z+Y1lAgaTy2%6c4ndKWTCWETGf;hB`+e;=Nrq$%~N-*8SWN)uX|IkV%6y`A{%4^h-v#AqC>^uKho2ijwu73b2CfKHf4lh9mo-dNEeQXcD~{E6P>IsQtEGVttU1dCSih>2Ag>C{c; zf}idLm)35w8EUtU_(XPz^Y{b{jo`}9R+xECK%n)NbLys zO@z~>-@3;9dF!C?VLdD_gK^dhrhBqsg)#{(5(4P&{uu*iKf(6vcTG#o2fK zQ$=b1kGjkDB0KJyqfqVyxfA5B7t``Ei-q?yF?y6J{fKf2gCgScXLtBVF^lv=w3hC> z_70k#S-;ORkFG^krSDEv-shs_Cvh+#EE7X#%kkko4>!^_+QnD3cw>yW%=oT1&jRVP zfI@f#wF`#@6`Ytk#6AcS8ryIQZQy{dQcuqij*RbgP&M5-L3X5n&W;8ymvf~OmO1Le zIDB}vY}3kg&~H3rxyTDPToHF%S<|JrZL(e38S$MxFd)KGUa@ym% zFxCC3k3GGp7;*p}H%Thds83qedkt6fu9^Izv~$u)5$TbSf?%8GY*H| z%B@MTM29ZR`~Gnh=-hRFHU@w{1C3g&Uj)u8J#CD^%aTv`ak#`O-k5}z=n$Rw0sqCP z%w3kZ*%|`q)T`RqqMdPIB3RUR?iE$~M+O3-uaNH&sIV2g3;;oVgXzQ}x#n3%C=331 zG5Hl|szT+^YqgD8l&AZpY^}4*%Obtv)sAnD>LyxT^i)NcdJrUowlO_N%djtA+W3Y< z>6vA^(lerTv2jXG-~1`*`B{B61hzb3XL(EN0tQOyS30{~U%{;rqTehq_50%^lzgnD z=-k_?T4s&2DkPUTm#8kG5}ZWm;#!4Zm$NDuSMmhQ6DV&?^DG!e7Uw5LBhIep_>D=N zWG>4T;s731C2%GLOz{_|$RAx<;Ld{6;?pizozUufYM0y3()Th}Ro^rT%sC4aN?5DW zpWoSS-rG4wX|~SguCG~hN^_@TZFSqEO_y^WrQVFsza?*F8)b)Qn^JX`8Jg5Qa zOU(@pOfWY;;Z&)+uj|4LuIvA}U9(}}&kIiYq9?|T$FGiMdaa96-H+>#VNQAwtp@=f zx|UYvXPv3&{Rid|o{MCk810Pu{v+7Rs6j7KIm_c$_#~gC)wQTn{66_ncM?@ebecT6 zG16N`1RH5*oc5i~W(n{9Cwec_8O@q`m!pqlHcj1y=#Qik4}kEkv%5=g+LShCQPMP1Co*Lhoyvog~wq50XV?;rqXW*#Y+&>ah7&emZF3+S8+ zl@t}|P!LqRW2jB={ew)5Ia`I{vPkp+@beR{9*z7{LEsnmp>E)Lbu1DZWCP#B*Zi&Z zC8Q18AZU#1RXlNEfM^|Q0Z2H2;n*gr>wBZlFl~0GFYTe(Z6I4!_V+ZOJ4+y7#@3?= z8yi^Nobdc+!(H%2TRofGu!UF~o#4vzyT`*N3?W83v82)5sO2qb+o5+~54Rm_*y7zc zy$$edndY^Q49~O4a9bqv=2As5OfIXdS2+W=T|Lzfw+XZ>VwibX!*VH8B%Xa*)c;FL zp?~1mf5DkCjs-iu$>RyS6l)x5~lW1R6JbGOHt9h@8GRv5Q@NOC*LVViTJZ&|oA zWe%4m?gBm!`c{oQ)5W13PMddwKMvh~bZY8C$ER@QyBgPVp7zs`60bvT9Bd~m!8*p* z8rJ)4^3S~Z&ONl_J{cez5^QML%zMxAYM|BinyPwKUeQef?%ip8b6V39ko%-( zTDy`&SYEo;Hi7B8-8U4l+coem0Pd+pH$>)JG~Lc4e=R9GCJn0c&{!J?cI}Hsk#(A7 zr!8**TrW8yzq2%#Q6e#Nipx-ykZ|jjRmhdFU5(q_5OoEkx9Xql)nHdJR1JI$agSJD z4LDpw?MQ?jl7;fYGSGLl5jYqivwL_j?&mP(Y$b?RV~=`(R!5Als#wo<*a&O2X9?{@ zpp!|DD>2ni&R=nsCbWLsUZ2Ty(?Dxw&_6hIxh~fBc=|3?N{pwgE|LV=*YGKuj(RMz zEUorTe&AM+TfuIrsez7axqSr5lTMRvw|AZU6!>}3-4<+b)bx|d@4gG;(Q`29;22uRs=yreHgWLV#*mm%WGB~iRQRHr_xInKH zPBqQsm&~5)tyLpP%|M?Qr|RaeDmN07jcG)E;Eq;X9 z(3eMa`uSy7$n>+Ag{tqvKcbyxFJ?8Gzb2hI(1Uri9O%#r!S^YDup#^!r{$Z>q50f} znw7W(&OYQKb}#{$6S89Q+1dqbr}}u$XzTsJi39BN?u6>BTP&hcKK^$HAKC z2i3ff!^G;0E@?G8h{@B*nCPQI%O4n8ix*CNn%S`!5HBr%V8}18QeKL$os~YUnO?l% zfKFhSr4gTr12hYx+326iB&*L6EV22?t=sA|eWWEeVb#^SL`;&*qCJE&1(k%rB)BPC ztK>C7QDrOG8vv_>SmtdWUFs!&?yP^>KwAq z>~W-9yN4GPX$kg5f!Cgg{Hc6A27Po7x(?lrL(XHC?Htg`+(-6G~r+5 zZ*^~j`Nih=^IP!)0&61PXkXTz=H-4m)O9o$(5|zdV;9l`j4(DPb~3!- zOsB|R%dXlHuhs}n>gue}2XVj#<~Ai6VHfht@T6Yy!=%x**~-hc{)wDdkPHkhy6G+{ zIc0M_u=D#%gQ3WMrgOVWAK>HO&;DFEo_g9JBIMlP{%k~^dfXp@#65*D0>0c6Bkm$3 z-P*uPa9sME!@y&8H9Pb%&p_nT#T*7At9RL9uoo7Be-JP`q zY1vL~K;Lx~H}yGhr-WmK5X>sGo5)RSi>H9Almw>K&Hy*Ms8xJ!bNEfCaQ9Ekx%cL6 z+!}5AjkD{2zCWwD`pJ(UQq>FJK47*%q8>)@W0W*yJi9kwB?eukf|LqYDvZ(z2mS!5 z3L$hp$$VxH!B$vQ_0{O>s9k`dhWF~`lOAa{%=-^^Iv-Vei$htm^mMMnpwW{mNU8v- zqLn&oz#||#vXV;`e=9n6Oen6=52btLqD*|Hvxk{%hNG2d-gz|7h4lp*B`)zbOazul zSb6c#6YUd6xR-@%%gAlRt|J#Wv+ZG+;#~w~dX3;74U10d#)D94D6N9E3QOylg(Zw| zG$cEL?`U-7>`+7Nfw2VQcQld{1$OoNqyTF?)y1dGLnZ<1Q=!O~MpySDB3YbX>VgNQ z;HM~*=dZpzfuHCxtX1mIJ&)54);_!5b%9~YsmHuU?|!Pi?phm@QOcH zMK4}_w#_J{I5fv?s}Jkz3-&@mRj=YKf*!5V{G&4LYdf?ey?BP^qAdN8vuAH_;2K)D zj5b@S7w36VUg9kMBTaoF;3Ch6HFX}l^t)-rFDyR58_q{*mr;%IakUB-o2G`c+l66; zmObjm3%RR08~dnLE2UF4>uD377zKX;j$L+ZU2G7sq?SS6h)5dUi7c6(Y-3li+VPr( zH15ZCj|zpQa46afi>krJMS?s3tuB7E#I4o0&l5zP1{1I5+8&EQQ$Do5j@nfaL`4}< zt5)bRM4?;e4igT10aE*v@v)AIJo|~h)B0RA_DaP8f?~x26bn{tExUZUERH2ubx zXM4&{;j{jjsKn;So3v8|jX%O!-IgthN+xn5V zFzwDSMGi(lhf8)tj>9Tw*O|>f~C>LJ*lGtT~QKw~S zy(XZqbF|0mz}LA`Bj~!AMX15R!SJ%MvA)45!H|%n=>}r~!;A0c_|~ItQ{)QfaqfDV zK3(cIP_7T7mJxl9PSZR~^Cvqb4dl$yE42KspKg!aBDI{Zx`kF}4{TY(pi{)Q5ilgw{dbxBzM>g6613 zN59v&tmrzP8&SijPg0@dW^^>U`YMZSe1gjAdwe?0eT4VwO)C*v$w1p;k1#<`I^8PP zaf*7(y<$SAqidb>tGXx^yZMXJ`?RbtP8LIp&5tQ`G^>Dpe5A+cnq(1V&Q|1Rs`yg3 zr!iZESp}u0y)WgcQiorN!DIDTxZWq3xNcw zzRsPrcArL3?JrxPX!z$BMYBT^O0)X&!9i_@&9Id>s(aQK2EzR)_5x;LLXZhTCR#)5 z5UYzKb8MswhZva9ZG4s?AH?Vc0ROj-ILqGS<7b3+j_NS#n`-W`+b4o+y$J0^XfHy0 znRMxoCY&O!~Gp z+Fax{?^twZcaMev#;HRUBw{aK2@rS$yl$4FF3dNFHZ)FCL*OI0Tc-rz_oEVBmK5(g z=OL|R*eL_7=Gd0z<0PjEEGSw3aGP6E>eKEPXL(ic1FdHAhSAy=?-Pte^n;CYu)_H< z4!YLWC;p(`K|M$23I2p5gG_DgO{D!BcYJ7T-A< zrBvXzwkTnXvfg{KJM&(?vQLNG_Jp%a)sv$to!Pm(sy&b-NRk$}+-UXoF5^;`9CC@i za_QYz(|RRHB+n3}uXOLlw0<9SV2D06=cj7-GFUvIQfQSuJ~tfr0F)+yXPq<|%{Jq@ zeGtLzw7!$9g2jT{1*`0_x#7Scpz|d@22KYgj-nZA_s1nKavYZ*G8oi2N@hg84>YCx zP+9RDcu~VDN6VWV4%`6oy8s~YJ9EET>PZh)$Hl>DMX(4XdCzQQT*s##td2{XkvgH( zjY;-F0m4_5p(ep+xcVD65KbSF+|Ok$|v}P(eaD<0zqgb2;2G;t`c; zAqp9jY26w@G-ALbp6-X9L6MjAaRYMI6s#~b2^82w+3~B z)tQ_1m}DO$RAB_8QKIgHw!?tab&;^lUNZs9ylKJ%BvVI%4&U(di>cUwJdIu1pWUo2vJy5sq~wt^1gC7>Hd2plug@J7y`YMVlre zxNmg{(xo1#4@RLk%hV8_6qh)RMmpOzwb`?_j#AI|Vd_a0rhAfR>-6g#lY9&98E?P= zWp*F-yglSiFzNJ_1q`Lr<=5@uTLX)3x(k?yZh15#>DJ)T8HpA!jLvAf?)bU&u)rv^ z;F+MvR_}T=aO7F=I7p?Xcnu)IRWc`#bZeZe)HZRXW+m08IxrA8xRjMyDt#H(eHj5d z*ZnXk&DOcZyNUT9%z#J}`QLqDWF95*66Xop%=vwh9A}2OH0hv4lc=N3T+sgLltl>= zh5r{jRB5#Bdl4*yR2OAnhLy&)!B1$71JzzwP?`Y((8BP=i&d8y(%X1lFe|#_R+kFg zCKS@e+?D2hNtfv<&EqVp$|Ig${tO>4arrsVtd$u0wG(%=%t;891fJwppFn-^Z;oM< zfxoMcW%K^d;gZ^gf@S@NfxI2A<6+uB!SoZl_{Y8QcREQ7on*@u7>bp&cLCAhLFc8f~(V6Dqa7blzlB zI}F{d0!=TZv7anAfTvdo8OeFm?pA`yt03>1Ca>^3igVK6a-O(VZPeS$FkXFUf{}S) zVjP!l-Be-J;i2nfHDI)7nxEz`X7g-dMmh!M=sH;qSooLe1g`$Ix&Fn7AEPG!%!}`w z65baw{3zIf&;~|aEk|gb5WPH!!zi`QfDPoDQELw4)b0|gbiH>6H_2X24P|$)y)iG| zarLW%`=HgY*H$AHTL)H}c)K`MTP3r$>H43whSd8H9l(L!@SvU;>P%&=ePt?V_Ig-c z(X}?c|Ih&rH=M3Wm#c%Xf|qN)<9w&R`T~sK@mK9Px*oi!329@tx19KEU2>qjy!G%{(vH@HFuH3awBK<*=M0oF z#w@<1b@ZR6sBtlP*oK>M%X@SDl(l;HuGKST^+q3In&f+vW^?D(^bZG&2NK+9Lox!B z5ts~Ct8O?E^bv++X(t138<0#BKGw^THO_(~{S{}8)E|ybrLM#Xx4jH;+o2$bwC!Nq zeI9gpipfu}44oVb?Xb1GDs+y7z_o6@dAEc)+yg&h7(3%GR|l()Xf~e8>oJ09KFz)n z_l*fKCfJx|a(lwKBGv4*dxPhTNue!c@JVc6G52CpY|JvbJz-Ns@rqLsG$tm=Ytc5v zGn*99yqQ>SKV9>yo&}mckVJ6>lT9x$A?;nBnWbpkG!G-rpL7mN33G?DxT>uT@`7gp zeG2etT`Y%D_p6?Xym+abL|%;EAI4p-4EDuKm@$Jv6fUK_7A3rm(p)9;HFGh&kQf^) z!dMx)^9z%H&h7o&Jjr7?cJz2;yU@+9#zoK7iu@dcHiUzX#m^7tFcT7eh-3}{1CyP? zq7Oow=z7`$-bYa$_fL;q^jxjTPmg@*KAcD2XPHNNQSTkB9`w^~lMrA}-N5UX_@f-w zJQdl0s)$z7+$(M6fY$^)SZ@3>D^!YQn#ld$oV9~(Z_I|<-fqeU*PHg2M08d6MwbS>?STj_b<%_n zK+7I=)~Dk0Ddv`c$4d2f0Q@|Nj3BV84NyBwk1{+8R-Hs^9Ln~}0{ep^9RWq9bB>#; z4l#$cY_Olp#BmX==KHpMvvVB1uq`YWIU?LEj-WBv_PyBV$M)F^9&Y2CTOUxJE!B&2 zKVwQZfUxSgkP$TMqB;eb8?-F--;YVBu;*J@T^j!UhV7<`U>%vVEXpvgHxo;p-gjg2dOk1EVW@7LWHB z!@uFHOW*_85gkJUj$YNSXcJx+L^J4kDVuC&C~DtMNCEy|<|1;w1FzwzbITp*b9P#t zvx7P9Y|M&L}BIZ@4qM zqj#)`&tXU3BB!PN2UdAI7I_Y@CM-!@WxV#AoWe2= zf^2o(Y(~X(e)PL7>oD#dlytaFO4*uhhbdt;!f%+GL$Q)I7h_nOOW?^vfsdK0Do3a>I>xHbFbh^c zVZb&xm>WP?-+;%6U^1{55B^2Xf*0*U%>x&n=%~!w8?LqoZAZ2$f)8eDo_$UK6FIu5P?H+*452v&p1JX7-0#e_pc^_w4 z@g2b!@$WLNgL7@(qfXK4?CJKxBK!-%ykGrNFz+cQVvv`Fc~5a2Nu#CLi~q2lyA}tP zj4)VdYglc9NSduosL`eQ340WvWUOKC`!z1BnH^cZVRhr&aM?;@^?{f`e7*AGuR}%7j1@0!?&1dBtbOw$1NXd>$?{P>;LFJ051?fvWFE zlSLKKrzpO9o8H8)RNI~hsFykt>L1Y&5ho)!8D!KIZoXpSwhix$9Q$maO=$VxzenlN zz&dVPPt=1{n#UNn8>Z}j6iuG&4IB*}Uyx-~}{oiqRJ;(U|_+FGzQ_gD>RXhITMBu$E zgWu|zoKnH}AVq)_t!a@jS=Q-CdYpZaU&orI#(KZVV3tA!@)+k4G$CJAUGn$IW%Bq+ zj!~Xa@&w7ltt{`B_9aY`ri^E=AG(fzBSpVi;)}>|ZN{m?bRCnVi)&n_yL43T68o1l zD&?^#6Ng8@ReWDkL4x>kuA^^>(`%W2!MN>D`4fOmRwF=-rEH18>RdGN^PGkfgj8aY zG(pnr(rQZc5)NN+mL_Ta6UePs%fZ{Pt!<9SI=(_x3|@}K<)R)59F_=IdqXt~5^=Mp z_Jkk83#n)q{=h~}egR0ewPm^Ovf93I&{PMOtSsID5~1 zq|2#2VGu@i*Mlc8yhQarZZ=4T7urU}mU>S-)Z@-2#ct@G;80I%T5zk~9}Jk1?!yb zkX5VQyz@_}4Rx^Z3$cxqu;m45(cAZhY2ovAfkK}BD9Q_PgY{U=2JAn6iyc~*bqWZ` zr0>W@?+-s8>JzLlp5Lr|$2e??=wrCYcAgAv)*K4 zVgmWLlq;1Lnu(s#m$>mvh5qb}x9`i?6Egs%=`J77QXkNdk>hh5>9_-Y1r z%ox#;rK(`btn0dCnii{W*qUrpI#NtG^1Jm zrme3^94204B*^Bfj$KvpNzR&65H@d^WzOydyKCp0kJ(<8wLaj?_^4qc)_7Y}9g`k` za>{Kl^Yxm+q%oXo>r%@LfM?H>usl(XuWxcip78HIu3mzp@uJ_X$gP9=%aZ& zdP&QwEPnq)x`!WXIJm`ZprI1nCRIh>X?<2DL)6`EKEclJPz~zoSO5(YGGiBDzm6du z6rv3YHq_hg@Q`iatYP{%92deCsU7EZ0E9rcl`o9^5lg27@7N2v#g z%jbCYhhbjhs*37!Sv03}RA*nix*xvJ#bqgO3A zs{n)a=Zv`w1{H8Z;o89(c&fc&nYT>V!z`AxVLp+Fr}oQxbRY*pqo7*@)bsGbC1iBl zCguXV9r&3dmCOdSPRwTm=XTs@)@;I zG(XY$qw@=)&e5^sJoEFIsM6^?Aqv};KwH-AX#mOLY|xfqTY_!P#NhWFa6{@p@xN74 z`AL@7GR^B5Gk%^;n;VIh0nsxsX>BEf|GRgB|2s+We^(`#=4qWmN0p3|=)L*)fO5^H zRsBf-S-$}Ng7j0n`IIrg_@H&ZQ(o+e9rQVL3P;o7FtGF>wOrQUMfs@%oK;m8#Z7(2 zn=x)uTW|yX!=!k$;G@X+0Ka+%0A~GT&Zfp`Ya}Wm0SVz<*%nV*At^1s@5VQ+^csDi zQ(P0cs&|Ddy$V#0S@O{F;Tw{wSF4BAbCk#|{@E2IRxU_+$A@o74o^`*AX@eqW$?81 zJTnZW-A0?-vQIBzgwsEmJDs*VYestQaC*wm!A?UNlRM#mIi!Vtr&XKTr#~B84RT8jDwPFN2|BZ7Dl9gGuM9e z{$5bOBMzD^lZyRERne5k`R>~UacHcSzPo)$gj=>?M6432{jQ{XZp0+Cpq;`aR z!?B;t(#a#MWQ|IeX`H2hXrN~J`Jmrz*MfP5zG6;}dQS7RQ8kf!YOWw|uM*t&H=N+$l8F z&d@9$o}pEHpi?mhTk%2;TuIWu?js=@0L6A-kl@ z!==Howcvk%Hc1!$_h6Hz3%)sa{;qGA^qKx$w{G1V#?8>}Kyx+wZ`a+XOx{uLkH)u5 zPv=@Dy8FBe7Km0VjM9O24_LZ)f|J^I4Ub5-=qHi*-#dUbS37*fM;SVVR;YRB9Y%1q zr1fe?Q?;ThbpNc9Uc58lnPs6v&@%be0|t6};^2GX*{A0wEstNl*`{Z+P+hY+dYS9_ zPOv2YosU>v2FnUTlMJLgF_SuWRtV(fhSe#IS9my=$M9N&&*|nI8a&7FU2fo@`3>U% z(dYfb(?&R%L(kYui0UgGQTYe(sSou2y(sFcF5_z(dk0xnbJm>I=*%I0(eL`kTIh&)FHlSwj*ASbpP0B5RhV$hEr|sSOYjXd zUSL%+Pw))HnaZ0m?KF{j8fRgW0=1ZjP%TWV)oLef_+jKe7-J(WUIt-|n>WkgU?Pr< zKHFe0QZW%Nb<}_#G(T7CroWrw24&wYbnY5+z^6w3a@8ZOqB%v)ElWI{xp>u)I&iqeB^M#XSe>K;qjc zU-i47#B~BKx$j{OPW%q;op43(08^_Ni={))!5WYjK7wov$lvpb2Gb%2!ulfyRSAbj zSET-kAz==-V*ebz%e0okgCLb1%`;J)Aeu~ij+$}(Ei|Ncxc*FmrQz|IQDN?Dgb z(Y46ZDytZ4)ana(qa5CLS%>?dhNk<*4p}M_T;we*g`>!HpdL0z$wz1iTyP zL`TD=68{Hi(P2HoqQr3##s7gM`c^QyjUD^0XKh`MP~FI6^Z~P}oSq~?B1vWhnxTh` zfxSgEak!ksID$I7J8jBUS>WNW$ms((eJD@pI62sRsNB>2@iv{RF7Z*%Wy(Ytl*ob5 z4q7|DsHqL=_FMnprZm4cwb>NyBy$Zdm`#++I);3K z806=3Su#5LQ8w4OKJSkVs)m1SQK4%!o!_$<0Gn2O`*sm0tm>Ago?gDar+E@JS9K?l z@x_WqxC|C0ZpQn2Lq(~XI(sQVzR>cmfOv-~UyJA|Y`j4~^0XIoMb=9j!d6UBaT_D8mG6~{WAv=&AkkvoB4^zOp}%#AqxKva`dbie ziT4U+Nu-76Q1KOK;Wn2AU0=_^;-~lPHWXOtc*gP)&jq5z@~bBhi(|5G4a0AI)>~K> z$7y{A8vij{2Z%h)V;|62IG=Su#p7J|M;N%xW`72SB{SL|A@P-#brWY1G+yii2n!Y@ zxFdfl9!?C>dJB_>rH}!5Hy&j1VEgt~F4(REEf8$q%SwVGz0xfc-tg)n>2ed~;{5c+YtQTD z7t$(PI9Hw0{l)3>#|7STf5FM=`deBJW}*+3djXQA?`2&6L}?Nv2$m<5yjH1K<4!I9 zTUtkfwpuUtuUT_SbJzBgE&P*0GaSj*>yv;}6=g&J)LGhUo#`EhM%W9ZUgA7HQSSvN zbSd7`Ov10)AL-w?WsZp>>Oml5Vw}lAg7&gLoFLBEyW?)rk{&mx@?18KfR8y1f1Svx zR)shI9HvuNJLNj`N58iG3Gz2?dv(&)tD~+aSXXPp9xox>JMRKPJYF>T=;K?!GWy7x z>KtM8$*=jb1a^W}pi5y~PWs;vyyIN6y%5*-hK^TfoFvq+5t*f7tli-$PrerPBoV!Z zajf0R?*mM>I4w;j%Se67>z6wNU`>#c9m%^qRzD%vD}=boiesEb#g{CzV*b>}uulRy6zEVGhoinXg8e(< z3am`9Hu{RVe+%OgYvP9q?`tEcL02xzfCWvN29}|%*5UdrFBkPY+|S&ZU$JILMrl7& zP0>bgQCRy7;QHsd7(-Dxhsg*UJv$WW&`g*c^}RamGFCU@mxK(?TDZ2)wV}$m%zh$@ z9v@wrn%ZRABfJ!6G|M6Lg*Iy);c$<}+Gm5>6YQtnJj&&_u8Y3Z$)Hvh`=LKqiGAyu z`QZ+W)lUUgiz4ouhls9GBI`KKUhA4*pL#R`_!Qdb4F=D^&ogCS9>4Z^gTYq#5k$1> z{hur#pu|SCF=Q0PZ_kUF@w?5c8Ag0 zrw+#%#y5jdNt{qNW3{qJ$!|9%SZ z$T3Jk@RUtJ1KZ^$L|;AwkLTabvoF?@VRlKJBqh|xUa)yedlc+Zw@^)Z-wHV&mD@ay zX;MaIoS$U%lJGlj1-KRL)^h$F^i7`rj;iALDeJ$1J=hi6uI+to`ZOM?lvnjs@Pfq* z4Z=cux`Y`zUYjx0@^$kRFJm5kzP}V_c-GOaa%@p3i{s8$=Pk>VjH)bF&$q8tc%U<( zoppS!4K**yOPr;Dq|-djps5;bG{dv&bZxFhnVxVeaSBtkYToTd$ERqsB86#cY}Ov1 za*xK^X9Lc7^qs1IvO31FcpzV1QG9&4wkj|yz^q`ix@6&K@`rmgRzDl6h9t``8R?aI zUbSaS8x`89-Nki_!un?bX2tx^|M@?-c4nG4^2NBr#2{r)7<)s<+c1V%M$Jn;>bJoJ z8}pby;&~IybVL()LZzrKClcclejt4Awqe1B`HZ09A%pGDLn$xCm$Vb%_$t&X@`3|Q zhNp%Y*ojyS%m9;zNImTY{#9qHcS@ekUQgePKC<}qmz)CaS&-*Z65F2;npz2yc4Q8d z&WxU;!%{vwJ25oz)Oc^t5P82vX8RM>s3YgF^zzF+O!QBSaw@14jO|^bJGH!gQgD8M{j4(@uA<~{i~geDYJM| z4)E`(#Lsb>|2@djE&hKd^dztT8sy+L$)8%P&`tZVA%RuP@k_7I zMBkL@UxOXIs=w87f2P;y_s2&>VFOxa3s~6*p%VDEonElL#DWTTzeJUHB8Jd0liVfU+CTK2tYjEg# zBhqP-$yWvq{`#D4&l_M?cZxXw!G#!KFLW^a6Ura1x+vJ+!1FekG4NTMpI(}^HaP%3 zbiWPtx>U7HbKInOMd;ciK_`!+-q%L%xY&2lh?lJi?ut;a^;yN2Yxbf@b@R6I*z>#@ z>ZYoTOH{wwxJrW={R#AEdEE*x%n`+jM(UtZCiBya%`ViKVTvryxKcg^`yBDU74{tC zf7U8#82f*XPp{YgtI6jW{}ikW+v+%ht@syLS-vjv)}48`8Q3qF(2~9Ot&vT(_r|{K zt!TS0$-N88OBeWdrdq~%qHe=4orTF`n}d6vw?bWA;4aCbYMn*NxEf zBrTc8@7asR_w6_o+9AJgi}w|SWOngthZ_aT?Bdmz&dV(a*k!Cs476!`UY&LY^L;Nx zEp@!e*P>iWLH2Z*%)`*}+Kj)__@$tNAN993HW*)%HGLEjI9E0=X0Hy7`pI3rHL6ql_}Z(3|?l1ZuGQW+nPUi7|Rz!`ErH(y-0qd zUC#*UTM}SNkNUUltqAb~1pX~&17I>Ffl@;HD zB6ut*)7?fj8)3&cT49`x*tn~C)^{`ice_p zqzJC@@ad(AKo|OJcA+r}bn%E)k7O4_hTR^ZpK!0F^CGLWf)Quea}3wxcU&fEe!{zc z9hWCq!y$tiG^HCCNdFh7Z*5u%2GS6mtmw)XBwJ|NT^If#71#8*DC;}}5s21V$ab(C z4{l=EiApk0;j!oKy(sERJt$ryT8*U&m({ofVH^nUVBl#rdc4JHl=9QT@fK(@h%dwy2qc8$v#->CyNdA&PsM!z8aJwNMlD~qH#?l-?_{W`z( z{CMJqTjE3&lyzF+jNB^pEQVWJ$<*xBv#=5!_a>L8(Lu>Q3qI+XVi4DrjxG)Zgr7mk zl;$W~pzIMVK1sFJy=Xx*)wY;g&D>1YH{lbI34O(d;VuZ75MD;~z{X5QI72Zi<636v zC9T^zm}X0sdIx~#<91U-hj504DG8H!JLS@BVf7}mQO}KcTGcr&alqux zIH7Lr1UC-+vvF@?c{Uv{?Jf5S)y1dGqxa_cDeIjfLg|8~+dgJOx)k}TpqnB9!jJSR zJ|3a{FDqtOudOrzi+u=ZBA5u@(A7SzPC+^a=xil>qi`%PmpGq4PI_AgMx}n^&3_`Z z_$O@hBoY66vn|y%79~GWo2FpYWL`1&O`HWnf9|> zjrJYYPQo;PIw?=rhwYfRg&ILE2BKjgfI*wk5|UASA;3=|EUO8Q(Dxu4u@LwzGK)U3 zCFGlt9qmCkPnnd9QYywZ$)L)9&`;A4z%R2!X9>L;kMYNptMP)b@Y`fYiq&dXHYc&H zqB1G!e0&F2t10QD5!q@!HYYYj>(~?j{2C_NZmAPG`td{f946_ll-QvR^R>SA%AJ8? zZ@569J?MTEX*l30eKLOO`&yE~k>DODM%oAON0b=_@>**6DRl4fpSV-+THH0fX`Abo zZ()RV;TQWMrsV`GI`RZF(;2GRNDh7x41Mhlt~u;vhI1})@#7TF_6u;dAdwgPF2*^0 zac|o3Q}Gs{sgdyS98!};C>NkKeR6z@PIU=YgEZLBqar}&iM8>0V7iy*$2=L1h0Pbo zU{0~QEZ=r9ri`tPvtCNcDoxq6n)>#fbN+ zQU^4>R|a&8!cr35DtJy;c_@ht=oW<~B>GkbDz7|@INvG(a2fUjf;2Uzmx)`~ZKaJ> z|I^5)ZACX$rXyxVwpgp?NnPQNmOXWaTP>$*5sf7Fh2n$CR7TaPtf zQpi$OMWP)GhZ7}xbZlQi)`$@LkymsclJzQck>_b8>pIEatCzRi`K;17r?=`7OdUO0 zU0R!hAPNF0m=TsCUdyRC`0x%U?2cBUYR!Vs^;RlJ1Nr4HZUC(H-jhlVzRKBvX?gIh|tlT@ZLnR7DODStE#=KquoJukIv$HRmT*px~tf7uh;=P^#&9(sPED7rk5T( z{V>Gj;MQk01{_y$9&RhTi3}lwx&s0awrT zTn;6eLJnT5>HRu&m@Fd~{=7_d1G}MEyki{Wp5_2{lti#oBJI$9M4@2k#|C|i%aWo^ zoK(EES`6FA#N)^MV5?d8YagKxU_dPF1<)P#c5gtN*U~a_YLwVvGqfI)HM6)2rFZyGxyNPQkGU_U{H_v? zVH~T;o~s{1SZ3BLjH=Th-?c^8eksqeliKb_Lmlq_8hy)lz{CP&caBVGT99do_JoE|$?2Jz4WrrOaR>*5w>l4^G_T~msYYGdI_Y{;t%V=P z2y=n>TXTzsX?fPJfIR||ti~OQk0Tkt76x{UglB)JU)&>rptkFw`nY-Q4!Xc z<0*uwZ3+vhlc*9Un6nkxwlV?y_GV;*q&y3!KISBzxGH`twJgld;cQ(RQ)WNAB<3-u zJ!{k8Hg8W7^GMmL$Hb>hT}SEoepW>o$%E+C_5Us!0{6N3RM3m#{Eft0&V&9#$ujW2 zd9)7`KwDpBn}bhvRID(o52p{owiN2}7^_hZo4Z_pwwlEpqW9_elxbc}+M}&)8zpL| zHeDjNW3`cO)Phmd7Hu{K&+1@T@6?9zX6o>C8bWPT@T|_9UX{(n@zlfeM4Gw`RZQ?C zn!o)+wWWhJIm2G)VRjs)3Oi+I=%EE6-`|7s1STwgbx3E;yZ16 z<63+P-K2?&!+dF2aq?_{m3R}%x#Ro~6)=_RUBB=wglQb```^OuM;0olZY9@nr*vJc zq)7`Y^-@gflyH$@;z0Q(Ld+T?9~J{&ua}Dq;|bfpuB8Hk6dEM|`s82dMH20sJwe3( zBbisS4k!#ALP%?CusjQ_HD6PD1-iyThF*e`6;YNr46ByX+A0kCRho5d2))l{DW&SP z3NA1u;}U+zgG#12h=#)heqKe)`2ln zWUG~l(JE=vugbL9(Fu~7E=^Jh({M4nIJM9Ujh25G&|^{twT&1EbxIaAC_^k$d7Rgx zg?5qGqOHe-jV(9Q0+5d1&vICUQr2Yxy`2YnD>`@=c`v$}?D@^;>aonbQ57nd+;`5| zMe8~9tZr|xq2Aj;)ab$WEQdN~=j2=~o8xtTHC4K(y}4X>6ltP^rtS2Zw|knaD$rG3 zwG;SZAXte+{gvCe28r8ffC6R%Ef9t(*@b*Tf2MZk?% zZ5|7Wr3=*q%B7(j!Ma=)lFXF62h_}9H=^~}ETr0$l^`1mg>WaV|8SM#R>a55(FwSD zecC*{i^Vl@Q*O}tA>`s(xhVk|dWXtVikZUWc#4@~kfC>|ETx!wF4ZAwrA8=5Oh3!X zQXfkbtOqQphOS~9@58bxObot9XDQw6TE_8&vujNneUH#m(jBJa#9}R1b7DbEmE(9X zlBql~^bQp-ijmEuboaN=F4EcG)??Rwx@|G$_vf17QP?d7_Nxn&c5%n&K=_M+elgezdJEW+(G@FWd5^`|Xml3HGOEemW=Kt~9*jeNcw=wv22s^6+5l5N7oWmJSVAb2M9x8B4tS z63m>?-lsoziPt^8la_d`#&`M>uRDC_E%925x0fYecNCbvmT4^&{H$eil406frfr0n zvzFlPlKht_^;HPn^s?hNl>zB77i@^b z$V5ERoy6(_AXx21!ba{x5JVj1XP*LHPZ*qssefReDgq#sjigQmSV0m&VvdP9-i_J{L&~(As9L%gs zbumqG<=SVM-sF#?Ca%#VP7S@q6s*Ee8QcoJ6M7t%s{a4uhzazBrY8)&A<`oyuA?G& z3$yrJ64#d?O|D4|;;LX1N`We3I(e&tfmA@#p~NSvsf-C@OrSA{+hYO|(`NUA7O$nm z!#&#YF!*>Yszb|%yU@`ga_MkeOhVp+ZQ@o>`;w41nA2Q3+?JAXMy+O7^7*OqILr2} zOjM)h2l;xDjQ#m`*-cQ<6v^s@j>AZ`ldB-j-=~OW%G}2IEMZKUs__ZJCjegq)id+p zoZhO-tRizwAq2v0%Eh}xe9OdLxVzU!8P{edf|`DZ0L0g!n&5%qTXqCiR*(1sW3=~;Cs`>atPbFISpc-!L?tN zEGM@OZeeggr?!%U!9S83+}0MdjAT}a@A9ac3SuHGCR6Bp(_MyT`h^~#gSW2K&Y}TDMBig zETtjvfZQTtvQsbEL)zFnhKkx9Sz_5b4bfRzw`q%3Hy4dz_lPVlm+2Lou)EFXqM05( z0Dp(nA_Cqj=mn|jV<@K3V!?XSu)Szvrg!QgqbVEHuOo<=pQ?wv5b7WSFVcLI$@`^- zWwO2dsNmV7; z`>#drJQJF6Lw*CeEY>AEL zE1Alu&dZnRlBBU0(FXv4>O;8v%FZctb?-43E#`ull`NOt&}*WIf!NUb$scV63*Qg zNU2A1K|vd~+AaR^6~*ll*l{Uy>eF0M(`u5JqTR`;X-x1rG(xPYZq;@tm)Ry^n&UNF zGTb+)M4jj;O7lwEo}yI!z%28SWjV8@Mya4^l%5~K$QY_bM@Yl=lBH@qW(^C`c+PZ= zuv*b+TmQB+(_(qjN{cjO+uvcPlQ#Wbh3WJSe+QY)+wOM_#$LAib>JR&m^ihH1bWU> z8@ES6X0A6{PY|!miXI5k6pGc3)o3bkl~r@AeCoixtk(pJ6!QF)$X#o~y~KoA7{qs& z_}7XOARlQ5$YlpmFPQTVX{7gs_XT%Szzh)kTT=|fL-Li?Dc(Vq4YByYFGdas`f7dQdUV0>a=!DhCm#LnhR!{{28dpyf{W{%eo@Af0toKWt@zIYz+P=n+7v;Xdh;Lhk zy;mzphDAo7z-I!U(_LOl%2op2l(1oXD=J&uVJ2BT)d%PL8X#QbjMKvIMHB7ZS^Ia@ z=X(~WgV{K(KIwx+l)+t(VL#O)Nz;HfN4~_Fx7Xadn)upx?tvD(((6NhRWxIx{8Ux9 zu#)?ihS!h*bfKXOgnpNlN8w-#5AJrTV0RO1^OOBc2b+>;@UWkYwcX3<0WpvA>B%!i zSXC-KzrA1fFPol^V}l+b;!~z$lxx-&4BksKJ)V4ks*g2ntZP0tbdQS6*@|LyK|F0s z@jwo~#!>dK2w&59+wgtra*)a&NU~^^uP);wycU2G{>^2sOuSH@6IA)FjP_caE6v_d zf^oJ``;fuTfP)?PAAl5g$w8e6J@hjr@#gY%*?uMOzFysA@Z;rm6KR%k?>b7f9rjKx zWZSk@3aa#L6flR7}|B~k)QbIe}Jn=MC zcSm1JRasr~G|oR|SthZ3$%`qTXFDkX_imz-WiW=Chkp;fVD$8}ph_m>s6sUa%+Y+g zOb~^{@*LjMI$#jNQABIg5KKdBX-RjNpm!-{7QUsjpAf9tDwC)>mZHR_FR0LLp3z-O zU$}+xjv$usQ__6*L(2@e5Glc=Xr6yp?Pugy5M58C#Lc9C6w&ppQ6%+1^g;5gh|b$j z{a~u|hFi~NAENJF_kLcc%i@Dc-OsffA`eJ<7Zk>LiZJ?+{KXtgMHsyz^z?$Bx^%qj zHKV(ITdC%Cz2D;ECC)1DmFj@&^O~mFLZtozRHGuw{ zf0QONt49t^tt|n8G_7D8vS%UGNv)!D7{pL{S)Sft@c0lSwFqF0hJY4ukk+C?V*Vzx z72rX~*1^Cl`zKia!!1X7FAg6vs{_S*%9$}CLr3DnTO*tbIM*Gi+u zsD!>xXi@!=EKM?bO!Wx6rCpbdKjp^P-&YrCeog%=3$IB8Zg;@^wIh;N}Mm>|_E2sE6n4+y}8_FbY z;g35gC{?328*e%^TA3vA6q;w7_G&uor4`VIp%7Jr9Mz9@ROR*h3~145=2jnETa0a= z2TXf;FT?Av^O-d4hecW8*+6wE3Zu)Edd0Dx161sgsog_lQ(r;ti8b6~2|d4#+{kMj zjE!^!(^hTz+^n_^#;Mpe7cV_(>I9ExbEH;voa99 zd>9@Flol7Z1A2kcp%Gg79u(S0z2!%E=4v`~U}sOdE;35vq_19mSBK@hR5gPUEzJ+mo5Y-qo>?8-E+Z>kDs>mz#IKujJ5Sak4K)eR|g5s!5 zA$$i{$z~bVNDs$btECAv3&u2o``lL5vo6CVJ+z>P!2@=SO8WgPxTtU8I0}MiNC4bm zUs|67m7eUY4*)mVm(!<|b@_F$Rs>=;jffV5g@w}9-M29M$j|2^>ZYr?OrdFuwJEJ6 zB-E<8u^fbTEw4p=NHw9+z&wQEF+^@TUAby~prU{q2#DB1znnxjRU{Y3(MQ3>`tC{N z_N9#8?Xt>oMfQHXht1ZVgj9DD4-@dEjuJaBllKIc2vudY)7c2{a%h-B!Gy^O1JFH+ zg3IA)4L|x*ehbqui`477S9x&RJ$WHOr_c@HhjgAvmX}w^&C8c}SXq!nK_w$q7=UUV z!U6QdZ|C!3P1}8K+XbOIDY>hqZ3?y^$O1%YVb$RVYYX}lMc!qe;Dh{3p#As~<<~20 zmh?M5!t44nzfdZS4S^5%XWDd9cqO}_45#I+2wD=xlA!o-`(3OMhYcatHuQzG8P|uk zy*{1pt3HKgbV~CGx4A-l@Hk$=?74gY&E@LB76=>ut*H}Mw;VF?o$4hte4ERv`eBK4 zRP_ja!(&zD>gOBE?^QQ|t~so#*=KRooJ|09&0#goiUNG0mB}ad$)@2UKUH8Mn1vt~ z1I$(vwL0WGs8FlF6-83*u=isMB>*`ySWB=dsgL+=B5e4#nojvGtII=5#mM=0Ey0qi zIG|X7TK%mhQYqh)LpGWiIQ=ZE7B^<=Ts38n)yA}uuhq=#sdc!PW>2*Z8k>C;kJaAn zYq)Ncv#;NgTAh9M)@yk7l{-?~v#;EG&Cj-S2X2A3MchLpv@LCQ)c0|Xrf65HL$yY` za;?`OZOL}1Hfc+qVb{jHw5*-_|0k2Lsu5I1iEPZlZ9aeaDNLR)mY;DG-wdn0 zT_my7d9~7kDUlL{!StL}A#(!F2{b3r9L$Q85bGXnz8O}sBkVSd5n{o+Qpz|%Z_#uR zeyf5cypE9q-VTh6-Wk!#Bn{xr!070m5f2m^0caT2JZcV}*jA$D=5eh{hjtaljn?Vt zD(g!gJ2~*~5+e)E_UhBA@m|NUr8*rEDsqftA)HGV0Y-VcBc=wDKA6j~F2k&HS>3iG z$p(`-dPcLZix+gqiy3fGeO0x*1`teMdfAz-qze#^!MeCcN4`$^v5NVMx4#Z2r1Rtf z2<`%@IP7g0^JOw+2b0&_VK#gVmFb4qvI1ViEPQva2OdF0W~8f&ZGuaS`JG)$ORSJ# z#q7t>^gG{%#Ha>(8l=0H!d3E%baUZyEP;WS<+=*^S9#^{`~EXJXNK(j<}z{ z9jC8mH8XFWuIAJXcK?d{$!O;&J1e(6g24WBT_18!$$F2ZwvR0MR@M2{cmsKXKJK(} z4#GaKmEw3ohjZ{-9)Gzh+fL z_ycOl%Q{aM%f%J#Itg-In0PRCyIzv?a>hIC7grse!rm&CyT|eeQ<|6wW@cDUQ51T} zO7OAKXsLrZLKN2R5o`UTUT=j)9BDzO1t)RnvuKiLc;btX>!pal1mBdue{oYqKg+y&o!M}8tp*G zRg0ZlnH*IofUf>rbdh11e)0PkTyHf_<9X?x)0nCXG9miFt^GKd%4_#rMeHU#PAN== zKQf_h^v-Fd)gmJrGUmuhyYJxhKaT(7`2u!E2ld zrhT;Onz^K}a1?FJTz3?kVZAs6JIwsYTK*H=3>HFip+yi3009gKuF8 z4ePLiwK$hDbo?%MuRQP)#7i)*LAE1Q{tVM34r|DVC=zdp)%1*-J38A~eM{;~aLeF0 z$`$T0<7>UdUt5V=9j4}%X<|1i@O!Fv0W*E29eQRY1Lz5ho*s95LVsE5RqWpcdXm2D zHXplJ99Wq(2xjMxVY18Xt5Y=lv8HMMEr>3+>_f&uDzkUT$)|glyhBtVQNct76CGl| z=rd5CQe7rdO>^yN-Ob@&aZQTMXi|ez7Zn%MD}r+wzF%Rb-MP%K?vtr^tS|BsnwJ1x zf_V)SUR32@1Mc*+us*}w1#{PRnrGr{_SCA61Tx0*9a@`==|_H2z*jI|VfYS+u7QdDhK zSXRx(Jp0zK}Vc)9iZQX90E}bMm@k}vQT+=t1rI= zo%kq96R7Q3_e@ubOoB*k+=E7B2_o_l0k-cs^*Q*~$G!!^vB&NZLfN-KIQB)J42LC! z;>VK2hZBn5F@@Z4SV1V-vF)g0G5Qu4f1=4OF7hOU?c&Suy7xRvqZN#{gWkU^Xjj6H zhE9%Q_MwgeO@3os9LCI12FuUGNkgddzdv!RC@*x~DX6(v5w)_sE4+7^sd+@4uYpgy*?Ay@`NT@=N&Rj8o z&b4b!90f4;U^!1EYgp1#%Bl*&DyT~pOS;vH264Q$aG;CV7O-(jn=Ob{)7>PY7o6z! z$nQj-DvyE~d$6KOQ58&8gq6@#okdyp*>FqBxs2Sk`g{?|Sj3i9u$1swQU@rlzB;~; z#EE)+nNy$UbXmWj&mgAy(IZRH8KzDHSk;it$p@4XkPTAsA(pZO(F{`R&Vx{mw2j=O zCSWwXU3E4PT=JsYu`m#z;OhKMAlxAio+Z+zqa-f(u8-D+u=ddVP=*bny_el;Vxwd= zoE5<3Gv`~Xip#3POghg$5=oBa;pi=_WXu=wW)61}qHFz?;*!&NOlO=D+MZy0f~=71 z_(8NBexjdAil{g;Rc!>&L?5>V8PONs6GR&XNlZdBMbUO3+7RIJed1F!aqlhIlQ84D z3wqi#^&2|se?ik#X{s`l>M%V;ntIC}tj=0s<0nIpF|wrF3zL z{)0a&5=AIcC=C{6SgXyR94zb#ZgpbjFBS@5`h4fTAN}EhF2d49XkG3qw$&rNN|;+s z)f-sSqv~GQ;s(1T+bDnK&yz%=dLb+@5=JEZA)nl|-0Dd~o970w)S&T(aM4x{?Hmxa4e zW%915cNMmFN4-Npz&XcNEomY!Kx%0T$ur~urDNb0i3O#)ffJa#s?lc|)ZqArW9fDS z2RF zSjvMb2*3pbx1<|lvy5h#hzU7|NeV85&S%v=gQ`ZDCDCDIY;h65MKBj|EJ9oc8LlF2 zUaCTjb|iqSM^zPQ@FoaIqswb$EpgvyavBW-P&+My#tDlXW7GJY=Jb&9YLer9aP8%q zWVlVxk;OJ_#1)#%z(&(rLs9cE-$GSCIjE54eI*iYPHU(LPb^Ad@=$r?7ZTn;rqR5K zHm^0b=R`gjM|an+pO{{;o>pcNL{=Fqz)@b+>C zNFh~Lo7G3q=Gye-Xw-co-nFuNX1^s_oPRrlR8|MG2HmAGQKs{f5TMV&8o8hiy8G=w+Hnv%4v`Rn4^g20DD_ z_E|nwu#zdQA7LMd$JEcT5Sm4Y)-u}pnkcccc#F+%xQ6ek{G>gq@TeI+(xX80zg#j+ z8%|*&v*jW?v~v_5HL&%lmi3`N;^a%W1xB`3kZs-Zte$8!&RU(*kF|nE2$FiK8oUz~ z$UkLS!qg5JZbfek9DwKNFiqdW=ws%nd@B+3w$Ms-wnEw*4kn}L1&f5xB=iVSaRQTHgrZUrSztus^f%lScgB1iiVIrSVT#x zB;vw(_1=HTgXW2v23I9FMsNR~CP%D${e7WEZC%b`S%wYAJ?y!y?=U=WX-?H;AABc6nWzn29GaJZc zAh!s3m1cov)4D>f+kzSv@fvOudCKtlbkziNURkj9&2Fjr6FEdfpivpSj#&Wt(4tW&0A0HHli)pLtEY6Dv2#4 zJ|fR3rHobLVTO%|$$cGU5;vzLzSTfh!R15C>oS@NN|Wo$go}y{!fV zZR7T(hNnKo735f^OrF)9Np2R74a2GHO_;4R0F`z!a0wy*ygF#~6bWD`lunNL#*WX2CVqIGMGE+{>izaq*=UE|P=UfpX`c6#|7> zhZ4W6Hf~>PRO+p?)?(4ixnoffZ}$^B4YKei5&i^b4(FcL(S?`(n7Y_KYobfXYX6#8 zP&?QsYVxft%^L^GsB?4L?EjNQA12o1H^)v2v}z?iTQOy=`Jhx}i-@(c5fRbv)g6LQ zq`Zc81if-Ev~_CrQkP*?o%8aFPWM4Y6M_M8nHPBJnR43lD_eeTTL#fOX*tWLP4kAZ zu*U@(LifE8uLW8FXuNbX@E6gg4&M(a3t-mZeT%~0X$~d}JK6!%4S{8(QHEbK`3({X zoHobWpH2$8P!vEe-YKg^$W=F53l=0TYvpYDI0s1ri++tobbPYId=Y@^u!qXUSOmEPbF+;)0^Ej=(g+y7aiu#!n9L) z?We+S;=5^&7m7sCDGg0!8**SKm=FCdtX(B#{NpG`Y^01AR;|92vbyBy4J{(FM@T=slV%4f@-`9_aR`R7W^zdw++`j;8C39h8(sOHZBv_w8OxrsVn zz6$jdg)rACOp8lsg_QDLe%Cw0UW@u`s({AiD<{%~%va8s5^UTGK?ZumS7czZSxm7g zgkIUII;dO*zCKt^sQmhtWKP>O>{Tm^fpxr?VsTyv>J>hyS_(aUj3;KXm`X`}P=OTi zBwkFG>n(-7JTiGubsGHUXfdsRz9q6gs7{0(|1YKw{y(TbgdP7crq3-Z4yq0ySAWYX zQ=k9)QKbRs@NX$`uKcEtf z%0f7oEcChtMJdn8_Z^YNfl7nQAe=~64?v(x6@8p!pR$}>ylOM(Qbn7b&EHJr1Gg&r zn1RxdPX)$}TyG};;T$zA-JxIJvO0nFY$XknCYC5EedTB z*IBh0_3k-!VJxWTobiGEDCjyapvT^#Jj>Wy8?TdpiPJYz(!%k`dBuACt#A2NSu+JK zSaw!KkGwTD%}Re|$CR&dJa65{2w9#tmp4|zrCB_JFYNAIN~ayEHzp2yQfs6ZYDX#J ztWq(mJXM9)OIQ>%9|#ZBC4GhG2PIjQ-eMWtGVsohPQ=dPRW-8RMY7X-#bhA&z=NQL zWwcZ(w$QN!8l)&gOawC#4HIFQJYYFR)F>=z>;Vt@qS`kHd4Y4pCaH<`akvof%Li=V-VTjif`oYuvKLdF4i|`BQ6}$LE*qMSz0zYLYK^_u@xh z5gGij?{i#2QFv;%xCo+4sPgbMh;OCSD8|n8gm+TW5KKcD8u$1uq2at!i@oWDX`7@i5=4sy%8R zcNs1tC!JP@lTYI|M~ldl!;DItnNi8|h&mwwVH4bj`(K4q8-2e&evCLLP`+4#_>WNZ?rGWM_w!Y&AV!^4kAb=sEb81fj@!l0gB%svuD z9R;kp%e>f~iKCA6+k@ykY-WnZhy8pi3Zsjh-v6|kUiSzHBtU&n4VR_Vf(Y=~0{rF= zsu)Db@n$5^=CrtSb$WQ+fP&3uDa~jjS)f_T=%Atz2f-X#jFytkTlIE(^%kwpx@g(z z>^rE4t!~VA_vON}O|Jd#Ml>H?m@b0N>v!8;E>Hb$E>c0aLG@MD^6I#lZl{t##|ZJz zOXzyc93`ys=tI^)lGU<2hY`*PV4uEpskxHmsyzlikwy+%cay-?a@uWhH~G=pkp*Ea zU;|;0fJkzI1k9jym(dK(k{m6uAzl5bZg1BFcW;O8zMF)JI;n$K`t77^^Uzh0hKE2N zLi2dS)1RQ`QH^PCZ7!dA;}CU=gFk7Y5)-`L&S!AbdL(rKxFFypP?w26jlPu$c%b5e z*K2hcjS#pQM^?bQOrB^2{#|J5=!>!&sPdsm!x|;Jfh9V{8@P;w80NlbI>F5}bM)0V z4>H;3G@I02ATUdFL*e@o+6>%3jGAH?)GZD!3Y8LdeWVfJGNe+#*PPVidmrni@r+Z9 zV?ENW#iFg6j(!^C_Dsbx`GmVaXUD0q=S_rq~Gp^+5)TS?fY`1~iwA?96c2|V-=v&CsW6@UMOsQ7zJ z#ov2U@tk~@@n%I9rXJk8BQRYSzO;VIc8!8;n&e)~uw&@HSjqPM31B|;r`-T0z{lfl!)fe{nKlBSbp;}QwuX37PlRAiH6_rT=n?*wJ zW=WI(beMJMGU707_Z95E;+@5anUP>jpfOcz{;oiYcEA~_4jXe0KfL#<-%ls-a(f6vIih0%*~7e$GvZ{k5e!1NeR04iEI59ITz>8*Gh7rtn46EmUyBfQ% zU=P1huD48Y(AM8prU4@>|sfxrX<>p}IS0D08Vw+3fbfucmJ@LIU6Sy)REatdN@ zJwrK&`WT>3sXSGT`t*Wpo+VfghAxW-z79&wc-}sU{{V`LhYDX1z90lIfuc>0kzuJQLj#d za}+CX!ZL-L4Pd7MNryqmzB+OKE#qRe84IP}adXAGV7+DF4*63C!v@9+Icx{}%!eV) zkhQUB4FFSD`hC*)Y7*%?22M|98U1*uxqp#iVY(XN#Ci;Hipx}{e(y~ZP5%NWI748x zBEX92Lug>5ka!JXTc3G{%4o5Y>>o6LNMFJwI2@?M8sDSR#m7iruU;y&<#;#g8R}_c z%_UfoGsr_29{S*^YPHJ5FL_nJ9HdaACNmq`r=@-xYARlp$$`9sseO=x>+=oza8 z*>gwlB%@C5bs#y=av*r3m^vk)O^U`OM7>8$&lzoqb(ucZ71mqJpuVAp$WlU+4jQ~> zxd}E$s+ae&h`4Qn$8@MY;iR{!c4$%DNvv6FLW#XviP^_vRK=78r6f#B&Ds-6Nq}|T zChO7?nil`ABPEqt=pyWk78k0v;SZ-QvE;YkupB18itc7rr_ACXR(2*SfMlckAoW#5 zhq?!^_l@#fR{v;vLw$=EI1|4v;nh@j^rv`4fyZx$;YZdHdvzU6)@d7lO_G%(QJj}Z zwcDUFA1;#>aJ#S_w^~@;7DK@dg<{x{U0&EEhCbE$>qnK<@Y4s&OX}8$J*J$<&8bm4 zK}O=&O0eSoT9`hkz6cKdanyZ4Ss-PhDYpbzTmADbO@rcAU49+P16_f14e_-V&z$SUtn$?K@F z!Be1F7r%-k*E|Ee>}@Hqg#L6hZ0nW4dTHI<)k82pRE~3ps~(qu{HCG3rr1AR-hNN% z6|(hv8Y`<|R{ttgCaR#bAw5^=CPdwt)%w(o*CM#t$~GaGin+t1j!nn_5v}rxf0tFw zXNUjh$}QBz--Ph3%^WNPfWCV#-KVR{_=J(aPE&Ug>#qW)j61DxJC>NpOs{UUX+9G- zzX4=jFQ^I`CF-W~CotF&*&=|H41VX?s}nJQ=c(*1sQ4z6EkZ9 zz%HSKo&(@IlV=}-==ls9b%%vtiPGe)3}ISId=PaQFw`p=?8|5ZE~5{o3PQH=PgJIQ zNpCoRQ~mh;n5tK066P5~0<-0)gfauM49kNhxcX}JGQhin=mmeU(!s_%b{3?IAbiL-HJOZ#<+?&`JBjWvFtId) z4x=aHVMV-3vJYq?hs6c9dO$C+xNJ)YQ3%}T>ddFH``WRK8{~G9a#jl45NK9t8|2g$ z7jO09j`YSeS`D0C2MZX$Yi~7JvfmfW< zud0Lae#J7a!@YhTJaDY>!Z*Tr?=tZvRh<_}v{P9K8~%5vn-?+*YMII_u2EFHEw<_h z`Z-k`ZvDYcNffyBep@26)4+9{?Np~|0Vaeo0WmBoy1)eir%rB*#8NszM3n@mG`gtS zfndZy>t-1AY59^+wKVgqHr>G4xon79t7N0T%g2*mt=d3Xt5n`@4(s?gRjXC2u`u7^ z;z=nSI0sjRNwn`w3kTaZP#d);Aue@Y96|^HYJ{*+dlF)FcftA+Ozb0gNZUs>Z+#^e zt?aqzby3aq9D1z|rdWf!uh|eMZ>sV5Lo90KG@({KAA8^40l&vAs}A3bTyM!$$KE_wfcWI?nRaz{zd=}l%e zj@$9!(13;O!WS(En?lEPoWN-M91$JO*~r?169(BJUQZ&pyWv|?0L#l>59eIsj)jPg~8PMZ||l%}fZ?t&iRP=AhepjO|s z3;InOY?@$qE}7SN=o_>*bLXB#twAf{*M7nv4)-Hd8bR-#@uIa79ca^GtL7;$I?}@@cadGt^I$=Z?CEMb*a9Yp_fMKA$U_Hj9(!I+%rpxjCFI4OBSr zuC_q#%K@JYWRh4yreXY(miSE;YfQK9KINL)-#b~dGSBOvD#DDWrCpQkRv%P5pdye8 zPP}6(g9MjSla~D*Q$EH{KeGiY!ctv=ZLs6EMBmD=fJHzYI$tYF33uF%myqio6TwXM z&8H5t*%IO%NnC=P`T-&JCJ69aN6JR{?%}(omoA9|Om#U?A9FKXKnR;#pYL;tvkE); zt33LUb@dXaHA;)WmoST~O9=B1-OG%&!B;Q@x}m`_`j8#Lb_B%7+u^qX))WOjtn;`R zvau9|5e^(xJE0HQG;v|D4I&;b0v4&i`HgB1<==5k0s?;X8$8v!A(>ca{SRJt?(*%a z)Jn9q3zWXXt0(73h&6}b{6=6R5bqH)vto$uHv78{@m*NsZu}oY-fsL;7xidSv7apq z(?BOiJ2OyVUsdgs|5IJm`zd@RSv+3dUc$6lLl?~qX|=Iti!$7-;SM!1KusVu__u|w zj=k4M)YWw*{A^u|u&U}yncvJ@shJ5~}~9nvZJ7FIWFFG=+%$VhvFltyWcsWvvYGxDg(k z-g@;Ez$o7wOf4a_T^=H$^k!(KZSd%ig;^NPe6uMBhPn8Z3!cJpgo4nvL_Peo#L+M3 z5#Om{?5n<8cjVjoJ9bC7CEm4~w!oy*+H8SYkwy02g@YvMHf+33HEn=dkJG@ z$&@E#sscWi*D#A!SX9c|`ZVFr_vZLC({4R=IBygPZ-Q zi;CcObGI(aC$3ey_W+E2-wToMJqVNF(^X0X^!+Y*TjmLw^IzS=X<{?;t`yrpl9@G= z!#)t?-Iig}!@xBUrVM9;>V6)3cnQ-q|EA0oNePben_H)ej%5IJkjxbm*FD_FEfBY0 z-1e=fyNnjmFDlcWD+LJE-!kmUvyz`2bUj7LQ&cbayTfJ?<*wv+_4f7;Rm%y&bXLu5 zHRZ14IF?YuWLd6p-C?t!a9b%J7V3O0fToA8>2A05?@o0QX%+i|ALEDqjtvOPFMoDBS0^;Brg( z5Vk4^}o3)gyijU-Y z$|34IY!*@O8+vz&gIF~xpjYZ^=bABI(ctYdn5+{Lpa8!>K);zH+=MJLGt+f?4Q%F| z7OhGLod9&ALDvTBO#>D1mSjra=21+askp2l8mefZn(2uTXn2?E;rC{Zd>us2ho7T0 ztk;$NKGSs-@24MBaizP^XSVCN)V{B|P{j0zLA&GgA$xz%gZdeoCAGWLl{!c)fIY~* z#lZQ{c1g~|KRcC1_xG9#iH7@xjyKspq-wh5yZa&)ECH|>o;SKZJ-Z+0?&=-AH>8{A zRHx^pmeOzUYW1}7t3(lkyX8GY*}GkfSzVwD3dOL6hAkMjuwXk- zTTaSUrFBFt0ryie?~wDVrg~uNiA7P-APazuwFI0K2GeB? ze*aJCQJt1dY0t_-3y0e5IoQe0$N?NJ&6DhR01 zKnLM_6{wUy-;z?ABs@&ESx_ZLYh<#}-Y4Q!FP#n639dm>ZOUD~PC82`n1W#Tp)W1X z?y9e@Vhz+`o#-tjDDHH-Z|N9f8}d{+-eo4sB;uLl^fQ@0TbZvw3zg8Q@W&_7YM)Fi zraC#(D9YqhWyk8nQ2;-aSe7s6FiqdW=wthl%RV?^!07{z#Pz|9xx@>zHwTEYh1a7F zG_hPR znK+^395mQV*~M8o!_tLU8|6&OJ9(3vRaf&~U|l5ptMB{fxNs$(;dVwncEi3iJkuSW znaSGY(1dT|i;hWDMRGn5u79E_rf;__PUnr)1(Zf_O@j}G?xE+Ivl@Mi%TiLs`87tpzuPz62I1)qa8LZ}mwTSXd*uz#@+9`lw+b^{2)0+!3ZEc+ z40I}e=-+HAk}dBlxWZ<80on*#bGis&53?sNtl_%dsbGyR+QW*Tn1h3Qm7&JFqiulc z17CzcqUtxl-5*_oML)i%QokRxmiNb(utDqoWa0GBJkWifdzQd-OP_|2dlF@b3#&Sc zEVz_P{U}M({QV`(qcGJQ9tGg(C((iG2=R2!>h26R?sU zYG5D=191ShA3bCgUxMgXe(p~PfF>B41=A1jDtXVs`c|q>EPc<*q`q8tCxF2yY>WfI z{YU~`JJ;}ga=l&6`do0YQ0>A~L|YmNWUSW4JdE`b5sy+O*>R*ItaqfkLu7_jJT6DW zaxg0QGHWQ&r!qZgG?F-H=+NvuW3R{u7igu-HQD;67!!)p>Bq zs~YBp?@$m0PXIia+J0(*^(_L-jfbAQW-N23a=;{i=xelq-XY*oqTMZM8c=7^q(XAUl|)KA;PDPSaCZ5MdXx zM>Tetxgw~i8L8O}3Ac;?HAuN2@w2Uf%OG%-)quVOm^5tyP-5m0|f6JRjuk z2&7EDlF3&#$)}TbfvsT3gkXzW7X~R5$Of-(1zfs7cc1~nv@!eAp-6Av>Qd9J9bGU-)N7vZ<*RL~};3ZQNqKuB#A zMrfh+#va1?bqG`3ha5Jc(%$QWD2y&rbK9{*4Gm#vG(PzR(h!=)Q=&_#3x~h$X>klm z5qs0Qrm+uGT1vd%9ay~I4U6}?)x*4326 zu1iu4m2P6FL-@T4bJP-`kAH(w;EssDJGhAJmIil3Jh+BymI~l4-O&%vMj`=1?Sscc z>xVrMpe|sSk|-w`%YD0oT9-k$SSgs3xC8?P4u z^j(K)Z|J-3l){fW`H`)XLF2xtNn_Wo4+Yxtxzy;!pdWMcC0moiQJW=qNNmtHc_ioW zegrw!ZKXVt^G8tAcY6g!bzm-H)KfBvQ|antt`Q1GbYONwxWFzjNBho9;cgt!zK=#Q z8qq>C1ksVXx+^L3>?*VRGb|JE=&Qadn7^m4IL!*0$8{)unoX##(43zZnmM#!4%KIv z-sBdXU|JU^-r#U#LnD;-`v{(bFEWf(b2O;xya*E2GIg2}Iggr+gr~wnKQSQ@fJ8J% zo*)UXwrFo>@;1CuLvm@^#l#^)V&u#T5+)lT3HigDHOrQXeYf^wT1*U0I zp9&q;kGQogJ>!SfESzu~#}p;8E)+ksri=}pf_Pw5P!kNkXI7Py_F&?)k-Bk0d-t4uFS2}>4} zR4)9g=ybURx6|z*8A~O~W4RJ~ObY&u$GBQ?YJr_wlp6R}{W35W%#~H@29vE->;nED-5W671b44R}Xkf5T z(!vxSVZv`E%g-c|FKd)gR(hs_Kc;B1@m5Lpp2~XZT*roNC8>{|0oa4OPvnm6E zkcQ;YKR1Sib_WTp$>DH^kCjZnwoL=mPGTcClS#n_1Q{S_-95Cpq{yJPq5Bm@-xWKT zGFuCy?=pzH6qXUlW$D0v#3A=87W5%e^v51Xk*q3kr2-D~uoKe+Raf;JzDw$hhJZm4 z*wm|u2b{yFtIhPcZ=#_~ykP{?lZ*%q7B8$!sHzo|iL74KVF1Tpn^Nt<0_uWb8Gy6- zC$X}VsEFleTrCLAp+{o)IPZ2QtjiFeA!5;>d=ky>HEC8Ri8!)5?VVBNU_@rqJO_qy z5P(MFgVGK&oB>p$^P5|J0pek;qQBj0*{t`cZBm<3?J-Sd2N`NRL9+v%6JHe{uXJ#j zJzXsveQ1*y#v$3wD0_J3)5}{Pe{I9aLLm?Z{F}|^T!!z^_}`wB zL81?4bECnl;2IX&vm-15ur#ndF|77NuVQZ1d}4XEgI5w!z`xmi?sUqm%0^)r?x`8Y zQA9|t73^x7%W8>i4=?W{|=5Pf1s>riSzQi(1WE|-8wKz~}f>IYI z^$wZ|bhM8}&MemEqa8o$iQg)nR?B zRz`0Ug&!Bp^c_*Vnt?la#>|1;Pj}qF&H(N)0|K6*PZ$CiAh6OKGk|~eYmN%IZ^<3+WKu9H1&v ztTL$NanG5Fi7v^FVK=Jge#-d=fN<4~f!L^K5=^qBPSk@@@?k?(Z#GnI%9c?HjY=3) zck#~D;ps%Yr71hjz;hsCgn_Aws!4x)t0L8$+?JeqjtELLpzfjdE5a??r3^N#`-taD zLHS~qXVK>LDeEREZDccP!j9{~t=u+~1`q;3Xf%^aahbd2GiyDod5_4{AfS#F2km!# zTY8MC^J$)jq52kJXWaX=Juu!0(}tlZX4O|!%d4(_1W9(zx8+EHbWKSRpgzdCyf@Bh zne2?5b`Mqj1#0(OR%kucWgpkzwj$Ci6{eL2`>2yGR2|#8Cg!mc)}^vV=H8{#%Fm}| zhWOeltX6TYipRdL8J|dwI2^&!dp%>Q8@eT-EV46eEG}EN2U-tE! z+u&M{-1p5h{I1@uJ#g_z<8E(yVmz9|?5Hr=y0@BAendSzcgeEpUGlEF{2ed(D)CSalP_4@7L%7ov z*x=bAQKtt7esxqr5uk=SzSmogPHCRcHr6p)lf587R#jd)qQ{kWDh^z=lXshRd>4F_ z%CHPJrhp)bNP}k*9r6d|y2xW$RvL@2uyoLk;n{WAEKSUL zd4o;ky(}V%u?dAH`DyU!;hC(Rrr!iEiyFLBX+z<;L1v*t8`(s9m8^HcP2n)aEKK8A zRT~#p9Td~cE0|pmg~0}uJe~CT3Z4NivpC4l0aZITLa#80{LGc1Qwz~NKEb!7zNpeG zV4t6R;3pW9(3l<~9?c(eR2?~hjY7aHjf^*94rnw{VWGK$atvGkez$&s^(q=q@33aI zO*U`w8L>jI%K_T=DUi?y>$A`)3j;wPxDrXOsu7li3Xk)OVed?O7{=%GeHt_`vf7%7 zhQYtn`CwMEMcuW}33EG!V!@iuZIfJ}&ud~{(C4#{$26&>!4Q({eWa%o?}U0t#``{b zOl*|%qZR1Q@*nt^YW@cI=?y`z&!&8@K(>WQAfi6V>4ZBQ&2P=tY?94zI`F3RPu5h-Xd0@Dkx3*Ki z=>qgXbw4kzp2^+3xgCg8*iP|`vnX31u5zA`H5JN@1`r468c0nqW*_AY%24MBU|hUR z;_cZ28e!11u+8A8O_**ea3PxC>donDV5T2U1G|TKh6wwpHklrIkM*Vw!Oe@H4o8nQ zrw-HYOP~%_uXU%cZ>62QxL#{dcl<}~ho;Z^Q=?lF~pm;{omw zYG9kcweNbXB-*B9rVpxBB2nK=59VCfR$j3q+N~K-#JdZ09`OoB%+Qt|0R0vwwR#JK zb6#?OvfURkpa}pbJ@j}k0~QLfo@CSj9z)MnN&W4$X8x^T01m+zKTtJDX2Y@FK~^3H zd7ty#cGALg-pfp27}RAKlNkmTS!iN|PHTAdd7}*QE-1?X45${w`HZz6(!G(^Gb9YI zF3|bZb%k6W13#!^N&fm17r>vhY5H~P~l`- zAg0i;7S2(ruws!sjKmaHy%PBz#96$z=iB2sjh~hM=s6Qjg>^wz{dBF0@~JtT%;Krr zSnv3qoW(xEoR?4DHh!{htx@0n?x8bpqK@5c%XSC0cYRt7D3_gRSJS4|;?u~6;EvX$ zDrA(X`%04suT{Ao0%5Y>b~Sqt+6<~nV3>iMuFcM*s}I~b=*qasnqSsvDD+abng=6W z1$_cKWL5koQGGBGeWxP4Lg9S}g0Is19>Xo3Qm1Rg=~<04+E(Xg#f(EX2mCf^rvoyT-rr<7ij%&7-L1Fv07k zgf0Xa0Cp-{mLi7N4VI?7xr1>IZL(&XL&19)X16pfpJf+IQ71f2Ez-vG0P7qHa27zA zropB!GtOL4D4MsrhwBl^tE|f;s+s{T4&Xu07mCV(4+-);z>W`1B5q7MRx@PPh9W4c zhSCByhhY-Sti}r3w)89<&7mJ+&&!Gx*|`-UnXJF%5%MqURhd*%!P>0LHZ z=d=E%N?5sT+K7;NH(=)wZ+4W)WCSw{T>9uY5un+1hLP%S{T_A?df@WzCVcOrU>?|n zYh{L6_>gDJg1b$HnX?ejQH&kum8PxXHM3;id(JJH_~`7igcSs7a!u;s=Uc8SJmA^% z2IK5W%m)w2cH;xtGX2@L?F7*UMi&?zy_$0Jd~l+lu_Pc2gD|9-)``Y~FnT)WZ2@M( zn3$(vOf7_7S(C|^@GZ<@Rd(!91A-(Np;n{~cnL{Z^CaG*8vJ@Fa0ccQ8sMcMRQbt! zc2!Nm9lsxRa|2bsD0e6tcS z{Mpy?{p&cYTGkBs0YdZCyg@Gxb);50L!%D@uTJ%yXt8Q6?9MM-{h|4QF!%Si%Mv`LnKxcEtXkwAk7%=0EHzL!vaL6VNvZ{ zDl7msP8+mGapL?MCfSZuU_kWqj40Y@G1MK4v8oakAOyK0`Zk z>v#qmx2NQvh@XdXNgykX)t4Qw&jAt_oVW#K3LE24*E~|%NFjPQcIKIw@u=IzArMFO zWY}ypxm#I6wHt5S1xo@%dCI8;&nNGg7ybvgtWHvxQz~~sU*i&pOAxLhus+40?7x(d z4j>d1*x{an0mrov?F^Ht_%nXrYI*ueErho{vkLUwn!T{3;z8EX@%ID?Z z28<0beK4P~Uac3< z>?)BeAEx2glR3qFC-SMCSI=-PM9+tm6#gFx@_Clx3!_urnOmgjS|L#_e{xj7CqWhE z#bdM-AHYVHGgxkOqBn&mQ9corsEuF(fp?k7Qnm2D#}zwYs8-{_X$zIM&}k3gna)ZX z={%?BU-JS44Gi<-ctWEM$_q>U*`6i+G}AU4P(E0y>=kZRFNxkceLJcNZ78c=VuPg* z&j;x=RIQC$5v!VPyd7OYAv6jbk`IbcbNofA%_#+XRO+un2DdWZjt(FYfIvg>LF-dB z`=Glw$K}Y-9Yr&8q>M+nrAQA zTWL;HybqfliEh{Vt7pj|y3CP(ig*onIgAa3BW)%Rhn+ z3G!l~A3+Pn1R7>J_R2iW5^+wqgJS}dG*L=q>pu~lu4dA~F@Z^$-)gxw4NO#Xun|lk zfbH`**n|hA-KH~uX9}xzv#Y0_UUgKMRlM?hbP2O}T_W_u!#GZ$b2Ll?1V79Jdfzgr zVpgx`Bn7VpdR5b_*&kM$Z*4A zwSQFzv1kx?Da_VHd#&ET76z$4804lWQy_xHncz? z2G*G{hWifELkREo5BS0MumfNSPZ8ent>p&~numX`ctxnD1T^ih6 z2PUc>0$b4NkR(&(F8eIM&x7v2gvVXR6C)~Zr3+ijs)9EOi*I@e>_rTxE4}M4F9_z<@%dyUl_Rjm`1$HWCXiW zh5FrW+(MWRYU-?ypWq7p+Vqk!w>k%A6A}~-85)0Kx zlDvV_){n3UWBxOwj_l3f&|w(9=xzV_?%tv!K2|?V0Ci*I|=K{s&5D< z>eTKXME%e!=K_G8_WFAccbo6g6jR>5NW%w8yVYr6qVR7bXtUzyVa0YK@C^%FLvB6jw7J{)hRGlrWruAJuJ zIvxi$yIo;j$HGvXRLs#Mn3u4+6nUCNUoZB=?S~K{PQa1eU^c1nY&t=y*h!8;Nwdoz zX6k-`VnR=QC)yCn_f(J+%YmaL1SFvodkC(si)VWm7+f&TmTz{%WvoBmkwBwKvif(w z^`EshW?gbN3xwZ{&4S^Wuc;Wn|EERtV6%>TXb|4@&@deJ&{^PXg65_$(sG6PAw2!4k`Mg0)H8=c*XXu6XjQ!Y5*s%KZo@%NnwI=m>HlNpO=uY z5Z-6rdd6EER3JrfJOEnIl_m*cB_YU=Xp#_|guX(c{{v4>KbhN3{|8?9Z*#;_O5Ee7 zE=`lm#pZ0ml%;!$!KFqrbv9Eo?Yv1PcU2U@fm51?apOh`z6x{Vrr8vmqc!711SKML zB8@{V?CCNm1l+F-OUZ5S_b7MA<}z}d`@P8Bm0^b5=sQMzL>cDs>UW&7ccM=g5q;5s z9gGIQ1m_Qb`pf@1{rlhl{ul0@n@u^o$mpZGT}?iv119i)Vr!ll+Lp=dOaKA>iBG+% zgB$(j_+{hK1W|@f%+rD_VfIBw1nXb$WH#ydJPmO~g&rcEPGJ7P&OG{L$-D@BN&x>2 zPrSYaNu}y!*y)P?I<30a-S_Av)IKGse}oY96k2)J2t>by=*W?twhove z(Gy$q6wwjza84HA1oTPt9KEG`D;X#)mZbLrXv1o~% z^}Wd~)bRv~1})6fM42(K3LF!tza{UPjEVifvX9Zz|ClXEys_a3MRfUXd zs~yq5G)|~FX-t;Vc)r$_g|TeZ9k|4A9A7y7QD2y&vGzf$y}DPa*`urzG)~vCb|-7e z2^lB#Rcbaz>jrVn2t!YQ|I^=25GtYPj5-}W$f!4_!Y$ceVQ?|3UO&R~2WC-tFil5i z!?T6d`)y%Lf>IKg0NA4gqVPZA7)MYe-qJvK-<;J;`EO+B^cS$>McermF$Yug^?P0& z<6AwH6q*n|mf)?-KWP1neLhX1=N4xxMeh=sY~C$CcWe-k5Wt(vS6N-myVC^C(fOfC zPeOx1^IBR#_Q(8Z72rdtF)qzlcb7g!I#b;U8%?QKplSi`%i!I_?2J3 zs-IIX0)TUKI#J0d_U5Yx7P%w=-KY<4qf3dcKyr=B(elPKDZj|4e1t+bdY+Ff4cdx%sTxA=C78vpQQkZM6AhJ0S%A zuNe5}|L|A08{&7Xr9ZK5wOmjm^pIsqbN!j6`75rt`>9nq_8?c46ig1&ZknoeI!!hw zqqF6!Xb4j&M`q3U$){Szo2N{zVO5u*p`?!IiZp1>qddx{(FTqQ?%)6V2fwPc9`amG z28?-Br1hqluBT$HPtA0t=@4IPbqp?`SQFA!ZGuAVj`;l*T@o9&i?5fjLj6QxW{8;( zH>D;U9<7*8Nsw1%osuwijdD#0QTTU#e&R?|6f~V1QWc4X_!UwHBR3QOLgof z+*o^~^)j7oCIP_-2xZiW1;;%0mcE^un@@am+36;lAYD-=pJ6RwWg2`lpGl0v;^kLS z=hud+4sg?NlF`v3aw-DdN$6CBHrfp2>HzMp#M3{&oj5wI?KtV^{uKNOrPco+=jb+Q z%9C2kp4Ek+3xWUnhkspIo}a_op1L$gouxH713D&tFoQWklMMZd{FXiwnR_p*orI1d5XG5bEQI=T{Wr+Ce7sCV)#Q%(o6P(U( z+1v`sV0rRR9nG40IL_K=fsA>Y76h9QFY^I?MyWxpj)MyOS82`AIgA_|DsEOrtDsC7 zzHm%tg)z(Q|DT`!bfQZqbIJLAAw|OYn&4z=)rfvL(Q@duB0gJ#|8j1A*7Q!Ldok*uneSEcHc2k;!?J;uv9rHO;Lm=m38!g{R}1)o&-3D)pduB6`b9GCm!KHlc! zIjKkSoJg5Ca7%MS3IDS`ccsC_HkM9F8QoMq|E6k>kLM;j#&x2jn|n~ckrBa0^mC%n z6Ngq)n9<=lMs}RhO!%->7aCcw>_ei##8E8lC}T;mi!E+UajT*;w`t1LP|f^QHS2>| z*3w#>?#Sp)?6Kt7j4)=H=l@ONCll^Wkd}YKfC9P%YU+Cq^q$0ZBS))QVbp>4;A?7( z+(96}UbQczyrloIeyB)q@5xx6_Fj@^BLg>@_o9wrY41sCHZ{QaG=n~)`1EHU0{+8a z{y3J|t;P=&y(3jz@vuVNc{&*y#hT-z7pIErHg*V)l#s7YeeHzT+*K&?{o{@5UDysp zaAZ~DTgovR{r}<&hC|0Ae+#3J{Csvt-ij~^Imgt6FP)vV7ohRLD>(*rB_~0yWV;45 zs@kJdk7Vi&)!aM^sfwk6{_CGk|N33Km2jR&Gz^^D;NG;C1OP)^`p;yjBziX9D#u$gwLlb6LM%$(UcxQlokWgPB~lZSuR=M(E{3<(DZ zRpa=Q#{u*&xRV)I` zJoFsb7Mt%l>4`XvO|^^ zjd2VuV}gw7o$#2hy?r{}z`Ns(<&Got{Hl$tPu__-iYX4`^E+zSB>(eY{-VcfDcqHp z%;F+XvKniPeEgi!{a8+HbQG*+>SL~D^69in<`Av`JnZEe%nO@FtY=_4$Hr2JZZ(A= zx>BP_QRA5PB93N+KHy9wx9%varr$g+r*mr^*C&n~c(ir6KZHc`had$EAzp0x~ zKbe~=@RuV`{|vV_>&P7yvD*&nnJJ@5rJfueJQuM~8!+hA^#+7{WEGjBqkA zonx{f9fFoa11B>CMxz4 zG^;LPx(p%bDr~l@R zU-D*B*E~zWS>HMNu4-+n3R37gs}k?w9>|!D_XMt6+Qiv!cRKfUBJ5hdd*>DmPlm;=A!4tL9dF6&j;-*wp3ciy>KJRD;ji*^$jUK;7JV*C#9kFRfG z`r$Nsb~n;U_$Ib)34eV3ZwiA#oZn0LLoeH=oFr+Zuf_uo$c zulQ^DMej&CZF-(T^h;8xXEcI>9bp^^ipezoKRg-vYUs7LuW25IX~0ufgGznq?56%T zmgn%6*1_-{&v|-E!busOmOx8pUaq!;Wgw+>?+z~7>88`4;HJ|b=q8nUq|bcflep;3 zb@Za4r?DPOfa5<87YyOdvu3$a<;LUD(uDx$O+Mq~YQcfu!vzpDiSynv2*vmv@=m$bnK}FP=$N&19fe*phP;i6p+9JDmvjgZSt+9A4 zR;ZpkB5iJU$4R^8H0@p$JRYMe6SL~c42%c&yQ4J`n@T60Ps9DK42$AnTkG!K?wC26 z^(r7v4t}1KSgtUrYzZJM0*64lv3kv8y@03_xqS?n1Sgtn6zF_;NmCVNMhO-W` zHi1i`)1N6+4?6q_?fn1bz1?!$xUw$zDs?$KCRmR4I&to0$?o=Aa$8HK?mn?^DySk= zVw**>NtR^!YQM&bn0dMLB!kQZKoA5;03@rb_gvU2;wLk|k0glS%n7_fA+ck`i=nOn zUoNna*lQxz4_K3?96FwalLAl1>G+8pcQF$%Q*_NZZJ*+4PVkJzpR$F{1Ymk=`L2^V zi53?XQz_$O+e?bwMES*sbXaG+h2&|3rbpKA!6jV&(~q zW=ah=XYTb~8to}Ig*1PfWb{jiAet5;d})y3GP^rG(y3ofKFffH!Y@y-CL#WCaWVq& z;x7p}i^4KuKhV#axYfMc1P)%q3XO-qUiiBLe6fQB>|VlOGUX5U)3d>AoQ}-ZsdSfU z%|zqV#JelmxIt-7bb8T+PT{gJ-KzWPI*G27xk&Y(O-O2O-RmZbT^W?#Y{B#*s_p3X zU6j9O+%2x|&<`JwCRjS2Y{&H5E@#+HH(h4h-*V}eXXZOAyg9xL z_xm#5hqN1G+>er*2x~s2b4e+y>t*RmWUY2y{+3Lpb5AbK6sIHfp8D>%`Fx`?W!g-Z zcH_J5Y@KiR*j(gQGUbl&r9l3I|+qm(j&xe?I*4Tsd z%6ipJWHwLyR^mL^;2l3Mw+Zp4al?CpyciE2I6qrTZ+99`eS-WMQhLi~ZbUCx_@(pV z;jk;VhhthNPHy(RB)K!eL|7deS z=l#S(a6JQly&FY1^i*d?T*Uft)ah&?VV%@=G!3Prwext7sACXTZ`lru;5~0Hw+A}$ zV#$@0G<%Au(}8lZ2tG>9Wx&OX4j5#>@`gCAc}ieo_7$=+A=A!W_@R4+d1beURkN!k{284xqaB9AFaPlFfe3@EoAo`puibK))z| z4)o_hXTkqDz)j>YVVXo?ox^ZD$&$nIu$AL0aGju~*%IR~+kq+EncgS~xWHxr2W}hm zG(O6wWcy%5ZFDvG#w|iN+G9}utZ3i~f4A*SYr5P7x^PtEwq}wXfJCT)f7_Xyn161* z{)(sjS(2XISwV2q_$4|#(y2-s$O9s+Sf{jT&R)z3;7T_+Ms8W; zqgg43n37j=2WO2(BSiX&hpOB;KQ}* z)=uR6?G`~HLOH`%eM^dM=rCjGF4315F=`LxbRmcqmo1*K=!$Zt@KeS0mXa_67Ot#e zFb^+oMx^=j!o&*Sf+!X$Q!Ek@%Bl|N?Flv*&WZ~2*j5yq#DgLXqZz)(){^~kW5ih5 znE*Uz!K!2F_u^2_)%z=dJW*KG$qUYK?~Y|D8aY$C4`OHWo}6%bfg+D$ei+g@Ydj0S z#v*G1jfVvL*%Ze#Pv7zIXRSmp0__F+Qpbe_Tp01DCkoPddgHXom$4K#gw?V1_RXh; zX^V4DrVoP?6nQc#gi1f;Vs;ugUH4^jhDE+SWyiXWknhhlD+V#2!ejj`ykUxKA3jIL zd9uceK~yP_BdW=3>2dR4@!RT(rTDY)T5Z$t@EB23EU$c+7!>8n!fO?f6s*)-HETY% zm@tU|-s9d@$v)%j4~Aq%Kd9>XkpO``gFE0->` zBoEeXh5E52MFnz&L{AcdrFbe|CGqE|Vnb`uJ~UTe%nXV0<%3C)&5Uv-SYQd$+L*`V z(-ZA5pl2dqAVhQA!;=KhmEtYcN@ zaBQb4SRTEy?Ia(tmO?!#Kv97Zg=}a^oES`n@M4%c^<$~8Rjbz?B#n#nqeoxm@%~daw?lsmw~;6PGik^ff&266JJJ_j$`B>d|N49 zcyKf%$d4D6jzjGqe0wUtXtt%|5n&j6=VkK4d!18S4^B;l@$lP|1hW`_3P6Tn0Do%) zaGxtMPc_4{AgnK>RCvVWiCh?^O{b(03vWui95YiE!FvVx;WsXTf5MgT&vND48z&S2 z4<BWj)HqNXzm~d?u@@Lrox2ld0+h&HXo9a!Ct>`I4p)_Su z&ycFnhUqlbM{mqT3irDLzXQ0{L0)(eHYCW89wr1oD7aOkx#0U;ukTiH4TGNDv@gp( z&J&!^!XG}+4FWtNg8+n!KR8tQ?T-#-DV~xzjkc^v+3aiap8v3t=!qX@K2UVfi0;gR z;H1YVCyQ@rw}&O!JD266wdbU-5IN>8hF)esrrwqW5& zq$x53k&6(xEOFL^%|(>-6gTa)G>Vx(hIBgDeeobGscHy~I0^Y{{D>c2{@WM1QOpG7 zOa^|z#cu`qAMv9<fz= z1tcmEqPVyCR+4lT53jdK75i4bk8+wY*12>DufSAN{pqt5J&D9RZ(PzhJgTWGmBB+E zNVoJ8Ck)gW0eZ{;Kh8$PdGpl7A<*Lz7$Oq*(vcL~I>cZG=v7c&c~CJd%8v%t*l;Q6 zm9V+u?g+t0J%wWMC4l+{?MGfu^B}`stRFGd>1e-2oiu)Y3zBDigZ_N2n-RZv@1s|W zCm*kMGilK3d;If{2b_wYVaDKFlBQw};YOQNB^It4!#rINr z(?Jo0TK*rJB=236)>8e*p-@CEM-xp_Yab=WBT^zWd-HU}*Ps3Ip+l?eiTa2w1pMLP zj{aMZ4?FBqvOgYp^vZsCz-Jen?WH#2CA(m8l6+x*uXk@&26fK|FiUn<)@u zdczuyO7A~z9;Qx&03HmTS_*V@_g zyk6ZT)h>UDOaGk>d6}>-DEmTHC9bKe*FH>)iu2@y%L$kqq(r^utnj4Orrb^KCSo-# zTi_XZl?!%Cr&M`HoVBpr0^5M{Iqd38#_k1bJEm4+FbSX z^@V#VyYfHUtl&JvRTb{zxfZr;g-&6POCMU^BHF%C!8umAnhx{!uVYSQP(96qE_<iWmH$5XrU&WW5jBhOgeNGa^&`XN{GpT9 z;|F45#{^)g({k#yCsV`X{25Yb_gdEtGjq-ARj{_)p0CuBe7La`>d6o+RJW%<^(4;I z=Hr|bp!je;9a)E*J9^{sy3{luvaH2=(#2&3T$Tt8)=F!lghBF(CyOO4U&chJ*U)Fi zNU!XNUIdK|3nlofH=gG*d8+kCyPN8R)=9J{7bs1CcMPeVDax4f4Z=+jPkDKeTQT6m zhphoYp4{h3mZgzm_#oR%V|FTcM@Z)bDxHUHFxhiHG^&NIa!A=YWt{(`jdzPx z*8V8A6L=r|b~DQ-F^lWhy!`e~iQ_51TzZ-tTkevbDdQ^CfK&3L*hy=75P@-u_iV91 z79}NkLTR}_@K}AZ6z9zj$!+^lP*+@TFn=ZTCoyt-e9ZX){%P~h_7eSh0nxk_pM6%^ zWIqf*n#<{YnB>jE28?5rOa7QZ3je;%M+nkA3l)l_xkx~f2Ke|`_&dJu0GW9N>?+rv zE?>_Ozmk9%zFYC`IY(~>?5`h7T?yWNfCzMj0Dt*mfA(_{z8U;q*vRt7JmdvK+~*!| z7*mOv<`H9D(j-z&;uk|;*h9~ez1P$lHXAp5Vgb0w^k#?)=WT7X7o-&r;Abk-YISMv z%zTY+8zs)%R`h9+-`6?l8xTAbhopgj!qhLg{CHsrL+JB1&2w{!XOhL-;ZHMiX%ajy zmvo$(PfPx!%tronYA!`!j%GXC97Og>ory)UFHYGa*Z*FNy>Sb@*z=4=Y-G+v-tdS7 z9yMOx-yxY96?*be66RpaEOREN&|!$0V0y^rq-VmIWk}Y1bg1JN)4Mj~JLc~^W=O^a zD&}+{BTU3qFJ;b($MBX5V%;o1Dtz^L)1^AzrO?V0oLsEL8M2fmIVGGs&z(Z@?C0p5 z1$)!zdYZ1Bv{_Z)I^@}2ng38O{dgLa=S>evYFa1tlAk()l9N_86BwqcTjg zZTK3N@%DLl@a4ry>;!tamnD>sLspOB4NM%tf9_ z>WtiJDdGVmzf=#HIODYI{EO1=lD)}0&yutRm?a{xGh%BlJqa6;=TB3Xh~Uu3kaEe% z`Sr616Fn0zy{^D}GyOU9)isIFUJvB`nE<6gTE7j~(OjO$1r7DC*c!rh8It{Ic9ri< zEt6=93}#B^+b6E?ln7zBQ@dP%NIpOE!k@H0S(6Bo&suZQkTJOv!uRHr<#O}!B5t~7 zROwGBodzHpdOZ(2U5T@i)Vels zHGvoZBSifYym8NXkVshfBhSCZalyXw@>q#n#i{Hiqk_$+(Ti~SN3WeY4(=5nnwgi!Obcpzvc&O*<;OP17q@pQ~7PMeqE>`852p-I!yqao*PV^e(Jhoy1bZ3 zfhK1<4b6p4g$wMwJcKoTtadabzq6j)@2zPMO^66~G+)1ylk}fN)=HMg{XIpQWHoFL zz5aO1vM`RLv)eFfIp(uRhGo?&?PqgkQj9n6i=~yxL(5a$T|y8Wqy!5%1DAIgX&z5EVDEGj<;+0oenPDv*-Phv=FZjy5BoTascKP8ToE5?^I}+`rSd%7q zosqbJgz(Vr2f45P8ZTg98f+87&-Wd#<3A6gLpg-s4+%Mv>67D43YS9Lg6SvU z1{8k(!miA`xG;esed6*Z*~Ra6VVZ86(Baj)a0kaOwly1J^WzN7wOY`Utr>OMGm0^W zNNE5{mDuQ2KuQ8sLPsi;YqcN&%**0A%%<~-bsobij#|_GhUB?fo}zgs&an>aYJF}@ z$;;h=eV*^JBX%g*_<+^kCm9acj2Uy^g{j(Sx)Y==;MxMxhTS7$#^QG)2@eVh33#4b zgm())^D@j-D73=W;#`*T6B`6QH|?n@eIb55bMe$^ z!OshA)9TBe`<~2a$4vJ}WN?)fh=;zrxVfx1cfosAS27cQXv)jk?}me>x%jRd&e4Hq zb2%TQtWp*m8-pm80n^(lQ>mRrr6wf65!7is#>8i;}<4oTP4{y8l{S1sU0y6qO56XMbJc<0m`J}V#wMG zt$B^1HE(nDQxUH=thmc;jEVxcroo{XdCl$)s7T1%}DmfIaNE@q*XE_&HK0J|7jiQ!Mxeb<(bgb}y zxg#PkA+}GE9`{M+$I$OzuXpin(W?`X?-q{6M9)bvcyVdso|#$lPhZTTUY!?DJ-w|Dw=bB;zN zIj}lSg&Vi1OPsqAf;!jD(Hxn?H&W8%#tOx(sLV=~QP#DcSr(4w3hDYy1iFTOqRCT2 z>#ao*N87Nj%j6LjOdgL(8VN5=<~*1Cu&@!1cS>=N$E}eux}`M%X$+*sfNP{BiE|8}!%)lZ^q-*C6m40n#VHFB>@^$vE~#U-O=7ZOjc1cw{rC)$m7 z-h4GW0wFR0k>sRaO%OinDQyL5qJ5J>d`Wx>(Uyb)AV#vOn2(o;9_t1lQ@oO0%fj=pqs0k-Kb0G^ycw9$qL9B=!;rt9LW_(Jk`(M6smV`@=+n|8 zjAJh~!GR@z@ouEaKqN^KNIjy{EZ)R_XbF6G9d(9zyCwv!KHsgRpb4EgGXdY9o^Xo# z>1i^Tx~MsLW5RH5oBbj4{@15Zvoq6)+_=m0ZwVji!!HMFEM>WI(+TrPqG-whe~Imx zaIwODTdPVB0y!f=tXQ1p1;P@)r;yYyZz54b82Ymozvbr-FBc`MX;_>aEK6Av32IuT zR#U`#0&_k|Nk+Zit(YBn7p7_cy2bD@(H4&e;nIj9dFwKPH=81BYK6vT;sZrfWSof3 zLg7MBO_((h+N(R>)U|-aHJ`LFeM;00jLRt zngG=Vs0MG7Dt|K3-hM0=dp^@RtZK3Nfy<|A!ZccKWKX6PWyiAfZ zR}1NWS* zXCAHbGIz=D8}K;;y|AS%j3RJjrO zR*D^|o?@-(6!Lkj$SRQ1kV@R$-cI4W+(qIFT=}nfZ7;%TsJKnC;#lK`grv-~15CYu z!f=;Hf>}HKelv#-~D=aVrd&18qgT;UHoJq`$ zNS(@>@ZHJkil02EHGRPIRzeDqHpeqpRe0Wx{h5QU9%)lqV)F2_%l??y*=%l?V1~Wi zbPODROrymhYBDDviq0&Dk`QQ+sud5C>BRxAb}_1U{E z!n?BN%)=xjD*!H2YaHc33Hs`5sg68O(&El5wcSEp&O5rnY<8RtYnM@Cb;gm5rKi|#y5c&HUbeA}pi2?tE(wpFn z+?2uJ-tvez(~J1#a5VsWlRPva9FlRD&KvJ>n&~@dJ4&1lW-%X=ehCbF4>tk5)()<*yG~I zGdg+glYErDlG*;g)AM7UCUM#9*Ji!SEWt^`W`japmU+2FpTTx4`bo8Bkc(F~gbVS? z_6HOnpm=AeCxiB*i=#SuVf8>oCB{1K)ES6!Q8W*CQZ}(yiE)*d>Plx3*3Z=YdUq_7 zZ5DyrBp~1#)Q>^CK>JJZ@83RO^G%1VyAZz$e_%Ox24xg#)*3w?_uH4Gf_4FOw{q&R zxpE=VPqrhEQb~YH0eC4#}%Tl{pde81CN51%_im4I>Rae=Lys_^>dA+1(d zYVPVzNOU4J-pAj0B$KKY48qdm$fy8&T0jzyW>U36LI|HCKV!n`{^NT$h~yJ2iQXNL z3Eo2ZYYT6nvI8_a$HMxTP#a$!_{(QQeq)_VCqVRtRb~wLWqvHShxov!C|U`&NPVO_ zP{6wd&7>nr3rHGvHCqXM3kRmHzF^u!l8WQ@SSM-nLwe}blDvtslstv3;ut9MkS;BW z6L$C8N=cnYS(LwK{A1D#fv5**%<(1APtOE88?nmpk2HH~BHI6lW= z(R3r>%9qPwc@sFJ&L)Uzx}JC?ocBW45)!8IA-z|LEZQclCHk`CB7Fi^(o>eq!i~s! zBHvNO(^n$hjlh~U&Uj=lhtBgZ%);^wkIdl<)Je2u&P%KVWlTPuxdTxFiNXYY4ZDf_ zXdZOZuQ44@=T+dj!`)AW4iA2{Lhf}_ora53<$LppztnwNhSb)u5DA6+W(ya}^z*-= z;FgYEGmL!vv=q%p%;X1;b^h}b&OAv3JSEYb;xx$~kKa@+j#LVD z4S9{+7N<(G*#}QkfIo|s$C)5>`|(KsKVL3stc4$K%lK z%dmJ(>X_CbahRti*>U9|(wZzh07fBhgdkeY#POt_#7E3L_5a3m8IdPDc@byZn!SSu zY**m()k<;Z#!jFQGax6Ba_|G#PT^lYmbiKliTu=xYdjzY&}&%X`V0JZW}{o#2RYQn z1FrCwA8chm-2maKc=1}hwv!{(9YEpvdGX#GTf_~P@i=-=m*H;C(;KextXbfK{ZD5+ zpa36kp3-BS)!1b5pKe_A)py3uYvXv6|Kx*Bx?0uV7@=8cS|B-4q|I@B%N_8H<+q)D zH$rAn=FDD8YcuGSTE(M;<-7UHV}&KaX*~^3Z+Hoh^&%HW)HA~;xB(XG7P$rLx#hl_ zpq^Vk^=*J}3~kV0N=|VjtQ0PCD@d~#mGs?=4VDu76_>jmRR{r}O75EWHGHeKdA8-L zQ)sskJds|ZfqdM8h9J*Gs&s7&>8r2VYD4LZ5Vwodxhd<2oRKUhTchd!{MY|;{qgqy z{MY}xC{4}RXJ)c`1kXrW^9gGc*L=8^B(4Zc_+X&V*15UsmpvnWq=b8>Z8La>S$wFy zB#HEZB+(X+u`EO8xQ-b~rROn;0#s%s7rW=H&k^iCN%A>J&MMsQK}Uot*0R^Ug|F?$ zGNUqFNFrhUNK+Mn5c(jMzwpTG_6_qW#e7m>qP>U=41R4{=bbRzN7 zPXTIbZLkqJT$9*0AokXP|A==#t$zYfxZmHV@hP2&3+1#xrX~84=^}jslhQ}IWDu9% z^hB}z#$As~J|_`~cW;Xlc6r)*VA@%}96BnPKqEbfLR{;K)Uvb(lAgmmo;!_C+<7VX z;LB`Z@`j{MU+tE15WrjWT@QJ|3T*Xx6>C^5tV#-ULRNsDMO~?@)Oq zwp_lhK48K$KC1UP$;kfrGN4PCfSJj_QRMs3`Jkl1t~xC1Iu#>g{OyggSJo7?To4~P z({DLKE^FAKIO*Qjw1uU%-3v1=VFKEkw!PGHzsrN$Mi+x|yP`P&CojCA4oI5o!Sx+qI(d+-+lCIPzm^cLbmGemPb}+b!wzN;Hj}H&x%5n7mAxsTFyIO;9cap6 z->~Tb>L*XM0pYL&*;SS-K&?UQoJ2D5-N5O;em((PG-;WIhb11!0qL`{NAsVcrpqGD zdRFnSWbgN4d^WCY@kr|-%-~cF=$w&OdnI$Q+xW@FbF-i=AZ;Czzls7To_%(~43Hqh zzs|W9I-jyRm~SH7dak0bt`+{9Fu0R%0L~)&k77C^CX$5{&vLvnrPnGfa|n}TC1eJG zhM+hPw;O*4I=yz&t2+_ADr7o%-rVq(951Q`kWUz*=n|%5ZiXXpo15`BccC;B-fdMR zY#7@#aNFtMC>zQ1@Ixw;cC!7+zx({@)5qy;;4vz;1-dBV&QfsFDk?n$s3O0d{k@qFO7;_n64<#kH?s)uP@#&r8>^*y)5Z`2ouB zr|jp=*A*6-P9C3utmbti?J3rtJRyIgBk9(P01nSTuz}=0oJT?+P(NH3v0g;(UpLnJzxzWv1m}HzzBsofOUS9IT9q^JN8^bKa_(S@|uR zlNIsEi&ds=UO1dGbrR;wh=DS`$nzq+C)x_%DpRg|;78XVN8Di9xIlq&GqlkDPm;cgdKRhsWp; zt@%-zyt=yk%-(@Rk!Nvc5!_0c8D~+ECmAlQ#8&shSi=sHsEUuj>z#;S=-da)l<_b| zTGEymwE^KM%o5)V9TDLo5T=$Sin>AnUPI4j*J^hlfwk^~=Q~^kSVE+xch_pcq$F_| zhG(pJ2E=u8ZOr5+y;<5V^{Bq;aB}g$|7kxBRD-^~Wb~r>;JYMSBU|>vJx#K1+t!y>dv9uN<+{(Q; zsr1H=foWwE(C~hH@4!23VNZ4y4;lqO9h$qb%69m8RUTXlf3};mFO=&1u8XjlYBQyz zxwm&d0_`{u5kk~V2svUmCXD7ZtP;9d-U2vKx5!xBqr{+CdF!AscPcb*tE;B2WmY>bf1R8XPV#V%FSl#gKun zTn8MMGAw~YbvXo~E7L&a9&%P9XQg<#^ck3-7ZJ4H>VwfOZV3+RAuiWXh16I%T3Gp- zSh?FsLFbT{+RjQ-4NE(D;5#6*mX?aRcZNR1{ew#CFlNkdHyXK>dW+& z9ySiD$)y0Qrq2CS3XYz~m~0sUfpxYr_WPJ~QoE`SKrpUPTZTLm$~yTEt(?jpp5icy zyBr@w)mOs;jN(E{I7H{uVBFiet@jw+5OsIBgZFRmeQbz8DLa(SMb;%;U`Juz}%KmE-;9v%k| zo()xSG#P6Oqw&JqY-rmYjBbj%nT$=S(q{Uai(b(k>{ZIU8BFfiMgN@r_BNUN6x$tp zxTuA2Fu$j+8x-E*0UCFODFb|0Dw&SoFw z5%mpTc$@tarbm}1Q7P|cG*;v`{|>($O?HdoK2pcV^!`Zf@-t**IXJ4|D-^q9Rp*Co z9gaTU1C=an=e4oJjI~*pUOeX4n6P>X_jcw(MNySFjBO{FaihVBZh7Em?O zy=N(_J4~@Rm34Y-Y?Q?;jgvc&sA+RCXG?9RrB$uc4#x2YS+S9iJE4iLtPL*Wa>oX( z7RI(p+8N5Lh;KGsS)0a2MElsnFr}QGSy(|%7-klGc^g%H))B(NvETbQEBnlqY>gEY zouYPk;&wwhiNk$wj5=mX?S!JK;EDihD%R6H?E}nD{l;>vZLi=sADrEMiOb4j7~fmP z&VErh;C1DZA4*>FWNY*5{9BxDACIst-@^26aXYiIUO@H41~^DsQPQZna=RDI6FkCs zn$gW-!&QdOmb<_p3+h^I`udinX|)QOEesM%D&sI(vBHqfE@QEpJ+u|&hDZa+FSU zc`J)$%>ZkftT!8Div;(mZHurB57ju1ZfQFde1{y?Gh#59VTV9&h8QzmD`;bPv(ch! zvaZ+8+M!r9{pDx2%hN4562N{qgoc#3;UUL=feNFv)B|sHI(SP(9g0!x+75Zb#v4yb z9Le>n;pH}yA-$v1k-{!EcL%C|9QRImj}&&Y)nl&Pq`rSPc&xOGwSE)9(gKSX*{LOV zYk};w3giJdqG@3 zhWkA{dKUvV5EfZLUVosne$eF(9+vT(M;AnaZkY`gH=@oLtm8)s=0C?_1RcQ3;iF}i z4H=&y{}-y@#{{#u2=};K=i(lZu5C7)+%>hPJR;&mF2{@eXfB5ulXW)a2sa?KJqR54 zc{VDrtsF*!%J#sEA*XAb4K;u8TC#yL5rZ-?T@XRINN$)G+4|)@`|}+B#B)48u^@&H zjqDc8}_xhM+j0{U!$cf!MA!zG(47A}&NC>*7=*C_(B_6QEIKIa`uVDqSkPv18 zI6muZ0BEp9H}``m@=+8ShseG?@oV_Tcb1;-{%LmTX8ugse9P-NESQr`lltv^fTRVw zX}3IM90W|FRYkaqBM9G&Q)*{}W3WXx_g7iu7hc2yBAxj<`{SqC(Sb(U){h@KXgn7f z)UKO!#}e%fhvTH@H6AT60k~yp;~>P3^MQ~S=%$H&n*jtlx~C(f9{n&>)EvrHE(Z;# zgSaiN+!8vb+S`v(&5h!xBui?C@8eu1E1Fwo-^lb{(~I=DPqI@p-O|b}xx=(xroTtk z8rJOAFaKD}T?TifkzoK~*j7;X4Ut*wD~tp3q>l@|H5)j9o`b36IK4yu8^#qiYDj z`bHG9gwpL=yKlefGo8B1CTo8+_mI`1xOO7HL#A()+ioJ=NTd9g1rJa{jwc7dQ8+&Z zJ1?Lf{DqDV?Z237SBS8`vnfPT)wSg-`wQMh?y*3R^+3g1taL*|7N=rya?VX|TO zhmdd7e;ucMq1@8oC_g@?F@89$4If^L>@(e<$!O6HmcwuMR4D87;0k;|u00S#$5MLl ztHiT`yHe)a{?dneNU_mUaHK5#30C4M#ceW{Sd*;&$NN*<&#oe)J)fbN$35$aU!Mh3 z;DK=M2ls#ej}sx(6!n4npZ{YH9P6`y3Oo>c-L}FS*}!JBURcF2f;&%R*)O-dGR5{l zQ#K5R{qndCFr`HspsXQ{?6c~F)zxG)2&U3#6P6ngYxBo;4ffeTXtkHE;%|3ia{;>p zRoM^*@waDA(-P16DT&kQR5wKh7qm3nB3*xiT9a=h0}w|%oZKlfH)^c`--#-7plyDg z{9$ge2W=zVnP`4ZeE1t)z)N5npZlo@bTd4-*`SDtPfcCMr{e&X*&uyj1^w~R??YIe z6j&g&+9IUiU&6u>YM$Y*<4;yG zohOGCwDsD7T;E_NN604`CRhv+tcM3QT^cusC%lelv@E3=QblA-+jX2aM>T9L17|IK zR5YV;s?@H1m`favn5axV`W_q6?NddzbSd+dBUUvtO>-HgNNj0p=BLI@;gT@?*CcA zK^w6GB$zZv^>Q;Jtr^|0%mHIt@xcd;WCbfb^HiBqkrD*f&YUQ&!z8Viuu6zF8Fxc@ zPByOdT~#b^Z=k9fuzmfNm2<+eh}C8r2Q8woJ!97JQkDh@nT;_##ljGeH+Wk;hinMx zW}N9g7G~PL#vMUx9`TjuoXY+{Q#$}>&7;rq95-~Zf$Q|(+FFOOZLp+0G;(LQz;jQ6 zbyGK(JY-jXID}aQyN`HW!C~X5+rCY*r`*!Mu7bu$%DZKGJaXZnr3-y!jW4Cm%NXyX z#aj+dht&O%rF$5Om!n6KX0|%$DD8oPm&3=48eBYx*YbD3ps2B5WZK<}t|-xbi>yWO zN>_bgRzHMKH0L7A=T7lv{am1p+6OUHGj9zUj&M-$tN9!Jj%=wFPK8i-tqM)8AE1?;uQSE?w@aMO) zTc61y*3`l_Fc%QQhvdmnXbMd>i70-M5pWF{(A$_iN6Vxvrt03$pxl zFG)W1f_!y)Iar^p$g?VbO2gB2N(@R(w%zk$pI9*`OvkKF4ySwJ+xm=1sDuj$YR(}x z;mL4xRGRFfFUU13sxmOlDzA$ZS$znza6h=Aa4Fn_h75B6Bdsf_dW}`TP*uNX zfG>ZZ<<{%Ehf(vzSGOC_H-T|>Pk>HSXtIg#Z|1kn2gf=;*6h}oQCyQJ#*+gJIu?v0 zU4NxIH=>Rubc~lyOPw@)KLy&ZQX@btMyshbb6|(*i4AMh@Km=>)n>Bh6Xahx+>$hW zi`gfK4@^#gKvQV4>6VJ{p^zO8+SU}N+=b-nT8SRTCL3w1)s-mf)M}jW1`XXAr(41W z;SE2X-K1{7czn9t5zuJv9AE-P^B67~@%ciGlIq)85H^ZO0p75pzWB;Is?LJ1Q9O$8 zo)vY#Ct|yw0FhK>vO@%sce7glIN1kOC=4{sLzv_bmk+CuU!4$CH3*}>pUpdjK#OJh73^AYffq*=|9Qe&+fmvN?qgK(pYFFVeXNd;Ra~DCnWnNI z*X`p53rxi@Tin%r(w51ncmOgK=ew_cTA)L? zPve)9BGgp&WBY;yeNg3PvQM&*^)yd=^GtgC@qOi`Z~SguQNnqmj6K>#o+i7ulOXIU z?T2?Ki{4>T7i?wx1Y?D=Z(sV{uvC5hi0-M>{m@Qz6ByQ&gih&6AH|N+0a&ak^mu(9 z!ZJT*QJFs`rycAV)DGZ-!uL?v2Vs@wuT>qEr~K0EI{UG0Sji2PbO%X-ucI+iQ zK|5~J*R={FZha7?>|1Yx@b!5x1uoFm_6S`T_7z;b+E&(cMA)~QuUKZic(2rfO_JXf zFK^YNx2dbGYY}Y8{H8dVvzjYC7SLl354^X*z*}q$n+&I4YuCcxc-d=Nc5Ew{4ZQVp zSQKyZTg)E*v^v4(sn?RgWQhX}>tCS$M_zyIQ-%{T>u)j34AUdJ)nym>yJEw<*n$a) z8ZAXMCmgL9!P)_<9bj!JVlrUB-hah!zr*hh6t}~xMV#A%qnTv)m`88pUv7fy7>1yY zR*O5=QQvDU1fBbtN`GLbM?T+HSl2uOg2r9~_@HBt6B}!u@W873&_f%bs&g>WQauQo zwqI4Q_>8Xf7lkD>_L|h|RDTRae=Mt00BQxd8Ur^R;K~K)K#;3bqi#pNa-m=(@|6=G zJ#mgsPDW#g6B7f$jr?dB3X63)~OLUA-7zh4}{$s<-izv>m;y+=24`*@wkS;o$U>`eTBW>(WWiK z%POwV1~HHpSYoBiO+4P7A8S1Jwk?loTs6)VM7j;+mI&^UQx)n!0IwKe#m3iyv}VkB z>cD^D=~Kb4%lMGL#11d->8J?Wwz(0rzQ@Y8ulxv2n0#66kV$T&+(YsJ7iCOGt0M>t zHVn7K$r@hN4pte{fohVjDZQfPapU&;GTuWZ9qz;tkGa{{%3Wts1t*KmDs=#|6wM=T zu+%sn;Xw`!+U04r3t59_GjqyuiHc5;hPCR@MCm(G`Wi})&YfC8?8}a`x_mni{$S7~ zCvy6-KZ2-n=8EKWPr0T`hgl?U&ovyp1-CTzray+2o5h+6-D1eTVcC`zKZd(+`O_0@ z?XTngo1Mj2#l>h{i)O?J%HHtoaifzgvYBW1Zmtytx~VK@bjqAtc}&viB`&Mc?v~$^ z(2}p433EBb52f%h(P8 zq{GN19`i=HZmP(zx9?@Mg&&u$qAq}vH#D}73iysW#PxF?jW21C zF5tWv3rc>gAfR8}y&?xLjK&>YvIAPi6`96(L2}aR4faYf`H380g7pCd1j3YKmPr|f1 zJ-k4-zzXjAn$@~)YRv?nXC>j`qBseRW_c0Cvp!#aS%5v^cUMV!D)U2;9`{Lh0W=QV z=f|gTB&?);VlwYg=3K<`BH?bAAG7+;*P2{`Ue-fuVWc0$yvP*^{^uVLB_Z`Yom@KO4J6xCI-FsrVMlJ6bN) zbZr)K99*pBJ~>Wrf6!IjkBIx_@Wlv?z!?zgY6tK^i{p!qA51tkI$iAmI({I+3tbVu z!GXt9p%Ur_(A-fGCls)ot~$d_>gXN7bq{5IZ&STH$1|vD9RT;R^L_&Bv~pO4ngv#) zaR^dc*)B+taEf>`y8Cnv9E~a?01B%zys4C1&YF&&#P*GnezvB$`oeuH4% z)%g~`jfc3H8&sg{wgAaHMgNN5MnYM*fCeB%T<%y0?lmu?>YOA5x-A~0tnX09HIwlN z$gm>m{RtoM_ZE-$d!NT!7rbWgew}3dstC*Q5RZALm>E{k)ouZFz2UX)L@g`m|2X0O z`-jE-`-ji{J0S)O2tEiLreUGD&-8+-#$dJW)G3HLmWj2ctgNe*Al_i%Z)GxS-JcM+ z6^EWEza$Dg57#8Yh-XmoQGs)^v(x)KTNIJX&PR6Xj~Uer0K+1{EP2 z=$&fUT1S~?%pJ>j+N;L;x6gT1+wUu7#(sa>`n0KSRl4DE`3uXm+)-L2#ZvpVQM7Jc zQdlqv#0Xwa!lyx@Za5SX-7i~iRk1}_RrPb3ANS97`cj<`kgjaVDYipuu(DeTsa_xsDe;t$E$$CC!WgP&?4thXWPCXzHeWH&V0) zcHQ_BJ0$xO`zfcmNBS6f$c%C1>&C0u7g2r6eH~(DO(}5_rEyd2!Z-_tjhx2lno{D= zOn6hwn(q=FyBw0*g){FiM^~F>&ON$_VFl~~#D~0&mpyya(V<(7`3~oBFkFm$7pi(F3n2le}DLAk{%8m5qVA;|0g@ zP@uIaKR%|*+?|#J-EPXf!Ax6QpW?7Smhn>>?iVyz)nc~#6P5VHN?4m-Qf##_g{6Tw ztCs}O(bWS(mb;^JwqTZrFl@M^xyK}{|M7l7Yqk`EF&futPU8bb=Kv0VF&xlo7!n@* zW_|F(Ij{v%xgAJ6Ee_?q(URxtvtZ?_Es<`pv=h!Me~HU7iB9*JHW~*|RbP3baWKki z8@1`Q)^@s;x?T%(d`dZ5xZ7a4QHb@fkaJ24eScaiH1!X=Lax`1+nT<(cv144>y%n_ zQ_~kaFGpG&k|jD;x(eOYc4(7vi`fevX~*tu%$8N0Y&R*>0YDk;i2o>xV^r|If;9kE zWh=TT4u1Lxh^|jF=eMZfJrhgwc@%>a?h+YNsiWaVeRhpt$muwWk`m+PEixm}(V^M! z2j$<0{9dEuEGlv~U{OLE!7k*ms*-(n7#Y@y%J^~!K^7=PStXp2rqo~^k7ojrXRc&H ze_R$JbryrC#nl-_ITl*S7VJ7I^8)$|V_V;sMjo^bain2=!zzE}m0dU)4bG#&2I1Q> zhR=#8@>X;n1GwSHJbm`#J!GE?9^CTOcmiGh2tK@7tUM2{X@^(*c6~&dtV5A!>`|#) zF-nyNb3XFlPPT&|)`aHMEap2FLKAe27{CW!TzToS84@F6<*dPYpJXY-T_c7ND+eD& z`Yk5J@J=0KHuBh=s%h!dJb)3}&@WijzQIW!&+rN=N)0C6K03XIX%oO&Y(qQwGudy0 z312M3Xp8-E4o{?z-*9-!=EkJm1d$hOZl&GRc+9f!07s_J_129TU@-YNsxjpq6r&YM z>8ZdP^_np9f&!#FH1XV=8d_WL05tK$4nyNnPwVG0d8#3nF9zy+AEw1~_!GY#LPz!A zQT=yR{++1LuWg``oPgv6R4)*6`1Q)l=6k&E1&n?a8rKIrOl%B;*hsoI8f|SgDLj5` zyhSlvA;$(ddFhancu-lRJ^ctiu^JzshIFJf-2AF188Oy&lvIpC`Z0vNUGW{3XSbVv z*uW(L0T)1raoK&QAFC1b@HlQSC~zDIVf4@k_^wXaC@XwDMukWpzpJ98m~Rr-4l_=o z3TE{iVKDg$OW%jDzeU9@PanCBEyONeb`_`;;)<@Xd)@+N-79ZlDH3sMivpy1BT>|u zFcgWnA6C|nN1Zm&@7RS=?QaL6R!td+Lfy4`_Z$v8nOnqk(%NhPHDtSj7>z2k9FkVh zm>xL8v0>Cou5W_xTQKwrI6eRJceCY_VspoCE$~zzL7)BsfXaqb3K1SVG{IxQ*YS7u zN#IH6W+7-9<3Y*#BM9J0Zu|)6+aXPBd7BwL3K*ok(|1#Egf%Q^h%+Q&Fjas-9i$fg>xbaW2PJ z)OzO}O;`;g?8gp#5(q;}tJAsmY%L1H~AhXE20g{ zw=w*gLCs-L*W(dZ?`=+UOoY&%rgZIUZl0oC8Z+nZX)Z+1aoH zcY`aFY7Oz5PTMREK+|ThN9?4z+6`{aH$garS-2m59p=O!)D3|6LPe}lK!7edaCykH zggt{xqtMkEEV}1KzEBY>6xA+FQ}&n5dDKgVHlqPwr_GeN{Mzpye6_qTp#ulsYJ zp6bQy=rh>#Aj-JBo~N>Z!0ZugrCR{aBZ1{8O4h(okEvCEP1e!e5*Wr$E$b8q1vX=- z@-f_rh1lVq0+dAip@t0rR!*$0HoXF&Z=@$6iWPBrNUCZf(-i5N-aDD&VB&gY8gaUr zfifr9HFZ5S=O@h_-IDHYW^2Q_G;erYxipJ4fV-p!(?u+k!gb?BhP{33@K`^?jt-b= zjTW^wXshb8eSM`Cb3n4x<0|bpJ*`r^&BF?RmoO3KYP#+}TwPlk#GZgCsndMFPqMwm zl=&1R1CuU;;p`b;__0ePc|M*Sg*am|(wY}QZ7_c(&1 z?}gYeU2;{x442W+1p$)bDMk(FeB#yi$3 zY(HpLvcL6_4^;mv8Hw^{%$NFbm7KE%^Vz zJ#UamX_#cS&WhsRb;Q9&y{--`2ARA7ss>awz^brsuh+bBWM5h0HKu|4qH25rmSiy% zFG+r^8bKn(m7umHU`LZ}O^vA6@X}+`2$DOVVoL~YbyW{piRsnxV4+wQTVD9#xaf>^ z)YDa(Ufn!#s7e$YiR;l6PlK~4hYO0TOv?wXS9s>iDxog>grluR%2ad zQ8r@j9UJrIC!o4fr5kZ0axCn{6T+(LO#^6Qe5Y+RgH8 z8yT_r4nA)1j+_cWk6zt8vC;3I7qf-c_Emhc2g-4w+gArp;*_T|_;T=F9^lw>sNOo3p z2@65jEEva46=3q=W^nSM7xW(X?|}#Lw=>{Xl#fFFnachve)B-7#GD;Htg#T*jtQfP zl{mP=M>zU%AHwPzOfi7#WQ(&}3qdzoF^c{RRwA#@V+Xnh2sq!%Z2uNdczxR+<#XIz z-GWiH?H)ok8T47->RwYEX_)kupZooGe6y#%(=B!9y72}h+<^PvZ1fOh;c0g<2L!?lTMvk+1}zD+;K(Ev9MKKOA0EU2bq zH~g@)@nl~nMVZlF>u*vcX#E^k&nNplHF^U@?*NwdFRYmLN6QFHaZJH%E#k6H;_6&j z1EVo^1Smc!?quBwtia0t8F@hU*2tmsM66+5m&xPl+LS)fjU7(tuS7L(8fXa_4mqBW z0@Vgf__VkUXFvU+pR5CCM%~wqGXu8n%$c#;$EaNtvpJCM7jf z+p339{zfHjcjENo6P=c`qYc`ISYsER6(=Sr(fQ?dN7`0o1a++zv9uqY(wyjCp(>-{ z;0njm-cv1EG`fry+b%#zG2-nw*^Z+|b)c#42PEDA`<+J(KJa$iT-qFqnH#3rf&sKY zvkJ}S=&2b4=sE>=Fm?RBJ{#&D69!;Eq~N6nYPjKMdLSr=U#mxzx9*K!uU6f>4Hooi z2>Vp5Z8!GJ4lXe5>gfZxw%XP+1akdUhWoKVOv|DTqzNvt*PnQ;ar|`|?ndGa&hu^x zMY;hGls<0h%X3sM)RREby1w68qJ>pSSs%mnWGl&k4&){^0r6ylVo*2`EZFIi8@cegvI34@JqnSZg?_DZF6CQ5YSP zY|P_W4JS?E>Ts|N^;p9}QsMVXRnj)%1JA#L{6V*4yaL29fO*-lSjuEgLt8xrM7WE~ zhp@0Q7J@4BZLg1c^fo#c@HN?}r)>%ZZRERZKd>Uhd|GBM)le$ZO(xnjkTjXgdYOxUgbZ5RW^6(5Ya)q z-dcCLz?D9X`cr_uJU_Udu)c?diBJItN#EXH!}ObFY;vqd2elPFx)Ij^kU($0m}zS= zY*mZ$y@SoXhLgcX{gGbdj2N11#PEbr5 zb*sLzG7qAREtKXVUjE7N=+ceZe0%$Y3fP*DKdha!H2~}#TubW>s&s2${2$vnWoscA znY55*LqBMXSEaM*%6vu3-l6#l-AL^0ox)P=@%5RD{k?IUzn7uQy9vdeOjYFTs{hx0 zX#D~HC5F^M*5kpi! zy+9TvKa_24U3;$Z;uiWVP{Ia+^+vt72n3%X z-`V)}F$2$?d71p^@esNYlAxt{1lIaDrLYagdUx7}p{uWJ)5En#vrwni$zGhcE(3tu zjoWL|61d*M-zS`GI}fD5Y{3Z7^>tHgcKGY+_y}Gd!?cC~>E~x7&uEHRxxeWj-st6{YPBZ|P`he_%QN z?jo>glu=CAtg6Gubl`bsC2(3WtF~Bj+&)$mKLJ9ts_!PN9w5=qsx_bW>pdkfkA;D% z#$@f9&n}t|3K%VCKjLg4fZt4(t7?pvvmYOM0ej2&O47oH3oU(i^TjG>vzLBaG)*Js zyFD{pu!v248W>XB2ow;wgwsuI|xH*7|;axk|sieyz`vj#D27|bq!+G14EXYW1Bw72%POzL=Hb4f>^y?;QiwZYZ0wpM*+ zRyG!TwPwEdjy7s!y?e8U0$*TG9LR~k z!yW@DNg3`8fV6i^8g^lsP;VYUP{1TWg^pR)fIiBaTS0#LQ&{KU;_UGV zjg2Q)9qGVaSLHMwxx98rV*>B%pUURE-mRen43iLS{xgcGC$Ak^9UchZC1T zSJ1L@0(azyv)5HczhrOt%#?jCQRq$Uj;TaRc z@O0<}5z}YSI1)UwIu9xJ?xHy9F=rgd!)_`gaJp#Q98A(rI89@yEgZOFIk3rm{0L4z zX&^L^LOUm<>`ClGZylqWKq`2XtoWR)J8M`Qk1C-UKbEjApS&HWK^i+5C;M~E+P%q)t}lTAw8I`d_&=Tk zyAChm!x(@W_Mjg^#}6c)u|wYtQCyb3(|`O}AwoxqzS!~LAUb(yn_~lEgLS;70$V)f z$NG|ZXqjWl1FPf3!)sWf2>9#E0o2760+8j0AF87Ntd<$$X8*zd+Tni8b67|Ti_vui zSyI*><3rY%aV2**lUZswUqEdl>8`aTM{ZlXm4SA6nJb><9UlTdk#C|fW@v3 zXC8%!K!{Moe@ip%5sHxDw>-^~tulULPjI}gk69L{>nMNC=uO_zYTp)>JqZ^jGMm?H zw!TntS=yf9wgvA7EWqct$rcqG8@e_--xe-3AZvm~9bf$nq-=ch?fIC2{TrEmPxYD| zMzrSoS(HBW%_q-iVHQ=-;kVdskpKU;2O=3L?Du874{PoT3{&l1X(8gB6oD(#{4|** z*+NJlg^;H()s9iKxhqqPXI$1tm#bAW1-2A11-2Ao2fJnVtgyvZ|KayDa1gYNaw9}s z^_-kH32U}y5BVfE@t|zY_GO{jt1Xldn0S*_y{qU47#|$00r7L2))oe8i!t4)$vgjx>%~Z zHabDv93MGjl@DdgFn8KBrHtjLQlt#w?VlqBh{qFj*cIEuF|8BjOt~K)x}+(DFj;{r z%Z?EfmL({G5)ga0FS`5L*$ET_y@*J)^exgs#+0bX)iZiD#Jh-K3I878C0rfCEZkf3h_c=(vT)OoyxekW*7Tvg00rP?ZhX>u zY#YUN1(&eJ`wP{ZnVfa~42Nopyyvj(h42(8W_nV#en1g-x#<9}LFOxGtvVj!GEd_j zYXsORF9}nI?bchmujd`iW_(!q;aKJ*;F><{2DXyclYRZR&;cv8gtR zk|aDpt6qiK+v{^&Ud|(9VOTGy&sSgID^p$Zy3-iSQubb64ljM?^s|9ACUQ{KVTMj! zE~;G_w`+hEs(MFN{pjhPSA>b~5Ilz^quAc^l6`+ z4p3o+9zlcgydc$(NGf~7PM!md1Mx<%(%-qE##tF7zsCA?jLtOC5BAH>y zP3t(X^D$mdO6$0;*_sgu7=(k*U&*7neniNWC}u;nr09!jaxyMV2HDUddAO1FoEfVY zI(1X*z0q~~;BIeJrM;G6oJ#wChV-WgS2Fw5$js_{t)(;L^0R4&R`@SjKQar`L?%)E zCrY$HEX2H{8t)o0KmwO-Pl6B^P|ctbhn^A1DiPa6vWN4+F~LIX;w7&Gu7)GI`F*8P~rt$B7`uK_5db%atNlypN;G-mQyH z606$B64>aD?%$MUO;lIT+LfIyHSc^W>GBZ_@nA6iKb^trFQONGy)BtUfZmpjQlLYy zAOV-jU&1trLe|-=)~r{S9FB*r+{Xbi?R zQnQ(bRqVChnT0QuCN^aLE=rFP%zoF~A}lMA^Jhi-VBjy$qbv(9RYC7S0I&ytx9v;^ z0-;vGMFt=Wigs}SmT*UF<55{jGRE;C{_JQ64U$;MmH(rL$CAcGtp$U&lcPl zUin_BJTFyic$1T?IM!jB=C8fcST2aYz+mzeLf6o6eFk|~AkQ2Ere6UzfcUz?k0?$< z*R~5QqJvEo<;NS=#rLu?!@}rK@Z$zbPs9vS%_3+IE(d?0n&iX<1Vf9Ug_^{R_2S_2 zd>B^-2YIkt7PQ09ihYlxOQRxCta4_BeZyGg*$AR1_2L*(&2p!RULt3X#7==w9=)i( zC54?gZH*i*>Rl+$nSiKm=ybqDuHmlW zuXy@(lRTYK2oRgV{_s4j%REi}9;AUQ2o7cK`@SQuA8)0-U#e!T{o)kP}PFBkuNrs^?$n?ID~ArTgTUB?jIf|-Lyl3 z3u#*pLhPQZ_(mT{bG#dxM7kBa8o&F%r374x*mc!1)(PsBNaXa+%-ovCOW#5KyD|y? zR!j!E@spTc^SJQWT-w4+V_1s+7PJQ~B(vUwH3aN9)eV8F|2=L<3(34U1l&Y=+>jLg z(bn-+#P=3QYvlOer*GOPl>*K5sr0%JLRuiD1t_f<{f~7|hbW*40Ox)w%l{U?ZNp>z zj8AxxuuHgdqfrrq%&B6%$YS|SKmY=Gd103=l&d27SNwLxuI8Y__K459Xfd7hv82c9 zoX?=F=Jexb(WBU93#B=Y=}!kgdzWPU%c4WE%NEKfS;e0Y_PffDzcy{(dc#t)#fWS+_soypOJ3YK(TzI1Up0hhyl?r1hU zg1&mK?kROciSyIAW*6mKRhNHLN(0sYsr0V@4Z=1kY<{Z!x`>xS^LY2sl4AGHUEY03YDWZG6_lfY+OM3MNKhs~$!?-lF5v6M1!|Uv5-j6{La0cu{~= z5k6TkZt4g82dH+(*iE%wfyI>2wYM+t zCE}O&zL=SmSzy1H$fYed)+7Ai-#Zf!W!p5*>1=$I!A~CoxD+HnNc7?$$^xj29JI`_ z0dO5(Q6FfhxE3D{A4PTXd4u#K`V;NoyKfT6TLF3D1Q2Sb~?Z#Ad z9J{%4CZ{=3u88!dr#VrsSY!v`T3$^jfp1lgl9I<1@M~rkrzSAl=}H)P2XrIE_%ujH z8u^sV<@4BMs56m#HfaeatAdq|{|;1|E%o4%1kFySaCuf;9(#>xu5<|(Y}IsryyhiN zLeJ)N6h zpo3Qns@u3>PqIJRBf(oxnJiM>(08S3KHsG>FK(oqK*(utB#=fs!frMeeCDYROjhL+ zQpf)KJTR;pas*<$NMRL`g+~9Fsn_hU^8<6N>KuY@Qxl7bF-fGjp>9h<0yG6)T%Zj3 zAwgQ&MmoT|#w77gv1dy66Q*7}k)*pjYYFzDM|XMFvdk9FjdDE?DKiXJPfC^JkadW_ zsl(PmT4xx#o|ad~A?zTrQ-`r5%uXN5o|;;>A?*ma6Nk0EF*3Zn+5G`PjH9eCl|Eq<&vmqTVw?Sx8_VF zf?eS6i{L;SV`cis?=`$0KPDpg`s6On!01=i$b|bxv?T^osr4 zMexP8Nj5+}@>+MImNy^pbIMDy1R=UjmW%)y-mi;BiQh8wl7x&eD;wzWSV}gK;r%&T ziVLexiAIIrheQY=hpo*?cnI!r0ndinEE_=NL$*1m578z(pOQmdhu}IqFQ7+%0j%f- z*?{=M>wKj;voImA*#Mc1bHZJGdE^8J8$I}v*S|_JeiZWWAbTPYdF0>XG-6tI32>bJ0ol6_lNXl{dAQf;&*nao~VA zu)ci26<(Z;g2sYTV}E&gxI^R)K$A4yLjR5qH^L=dd8}I~vJkr>+KT~JKcM=2ovdw| zC8SU6a)Vc{>k%W|tZ6kgFW(;H=kO(Q9L1cLt)OeHCx7c3A?X8`G#{fy2!Zd5!qwTu ziMdqG^kwmm$@oGtT={#e>iDqL4^vNg2vva%e=RlsG@_W!fosdBP=`trw7w7CFmdb0 zw<4^nzZZW2H+l+<>klYnW;UOWY5L~j%;5Z#s+LdU^%Dqkb&}vpZkR=3`Wn7fv?Z0t zHB94d@90|OqKF?vHP=nK%AzY&Li=`k*vDH`GV={Hz(}657zQR``Sv%vPa|L^-5$fO z`26&%kUjHVsp>Kg53kR0R^)7i_?X6jvpZn`Fp2#Q%KL-KyW{evGLD@mOr$jLJ$h## zd(d__8nFL|iNv_yzCE*SdI_sEO|cY9BT7XZYa@ zj-Oc~N`0RCx$y=DgX}2aXRYXFJ3sYpR=z(yT?8489hMyYucs$x=y@8V8&K|m$^$n_ zklv=aKri@Mwed7Hg1v3|EWj(T)%ArLs76=VjZ{?bi|m>lR9Y74ai3(DK}gFYOBBdK zd53izsDR_`Pf46cP^4@Enqpd$;U zW|&W#b4nijO?E~Paf~!lo!hNG*?eC-W?}T%D5=m_mg`2tlaf1VwKE5Nhd)w`A#`sV zX^s@bub&hL7rsxaDrXK7QJ&(o!{$-cv3NamB&3XAU_<>JeocC%&PNlM>+0iV5U=M@ z)0RTN9>}?=9PgsoQtHub0nvX}c_t;ZCLybKc|>zYkPq2Bx5;#;xJL`HtmyP+8pKPY z8-d~@WCr0&3w(?IS;46v%zcoVCt#jYlw`Hf9>q(&4M7Xh?Du4S2jZRgLpJ)%H)is? zun_rX(w!c9GqKa2@-Y(Uc&yLFsO$4PM(^SetmyBuPGb_`@x{L<@4YZ)xCgvD{JWGr z+x<9Z-?IEQ!@xcsp|DR(yTMvlz`v0Q3_j!>Cqae4jhJ-L-aG}wVKf)f0%b|hW<3ht%tl~(+Yy2oa80@DzepU%5@r5jJ z{H(HQ^1%=rqoE1^BYt#1{DZGs{BT{V`oY&Ke)uj`{NO7UKZaJRxOqSSa9X2MKuPzk ztXRrA#p(G_1|3suU`zMAtk^Bovgd(T!M2BE9g6uaN)jnfTximO7F~f4LnI>*GMaO6 z(ntq*w0if2r_9Kd*QCn17#p2S8D-9W4#gl+{H`vPw8lOMP=cY#nVLG^iqmidF40GWyU{1RH zKmBLq&hFzsPNZiSz=G97Snrr;?BC1mUeEn91)myfEidlnr zekwG3tT6nO)wzN?pP-I65$MYjqVDW28v&MxU(c%5A8 z5Rrz-q0T+VH;YlxvdEeYsP&LL4`(zwE{F$50;VEoCch+?f?XP8i~f#Rn2vv)<>i5W zh<~uL&0Cs>(M1swbgXk_X#J~@a7PmSDT6&ZNf{qlgM1MrsrJ~?a^KXMg@`bV(xkdL zK7?5wv|MG;ZBos{PF`LjIov7_ZoZZisF@HzXc1B@BLlTC_(lV26TzD%7)Xhe{?!HW zVKmE*7G4zwcETG$<;f0v((*cn;lueUQEaiY{T|E1Fvc6aeAg~xM5`NQNrSB9HlqEO zr0JG#eFZ^q?GtfEl>=qwND6bl33+7+!+Bwz?oLN1Fcpiq%t%?LEK13yX z_Ew7ZAwyJ_=WnG=)+layf&&rxE@X6ub(|)L1PR3?nc_xBTN*qgP12S$cu@mF0x1`& zXjVGlyM?5CmU0Uz4pbd+dXoKdFWKa3ugG9w%SE%Hc{xhpO z7j1Q(r+!V@ImuJ3_M`BD37D(#`@2y%fjnQ@$RU^-?3!=Vsq$mFb8q}eo?weh_vMJH zt8uN{#@PWA!K``R4c7H#iPl~(i=38Cb}(p7cp4R~8=uul{U$a%m6yUt*DyOkpLxBH zqMk1^?20Zfg^|V}I{>UNKwkeQGXTFH!`-(yi!P3gK(Wn{7x?vos?I{uEoq3%!X=-M#h(uW;1D#r^)VONa@&R2dCNDdWY5QX~|&` z?LD3F%fBT)i{&H&sK8%dERYO3y{DKlZuK=5ckQ#8QCAlNyK=-``2eS1XGF{m7`OB* zY)XCakMZn}bju7|y#9|gS69Z^Muus1e&~uV*7%z`vyi~wbPQ>GNrVKdT^{7{mH=by z`V-}~*0}^TqsyK;74D0lY}g;ghcJr}en!9DfC~wTU5fLShVLf9yiE2<7N*;(#OT!Gnq9hap~yixRJmS^ zZ~*}mfRBb^m@SyE{CZRF>={90u~fAykB^UhPA_IxNm1wEYSoFEuDpqR<**Kx5(05v zuVtVcxEon_6W3FjIzVW=eGIdzW#SQ-;6!|x%ya|#g(Tc75~gzpE0&i}UB^mS=0L!l z#9=B+6lor<`H9HuvD6Lj9i(0r$%d!7w)$qNu=DsO#$X1tk6;G=@W6dsZh6>_8Hwy# z>xT9o>tlR|tDW0P$3FVx^L)9FU)M-i)@1#;9=gkJ>?SmGQyMt_dD4Q4P|Mq-c@WkW zJPU1HtQJzGW3LZr`dT!*o93Ys^LW;z06=G;b4w=qTnQeodF3qYotbbT@*Ws{eoFz1@z^W-it;8zQE9 z?Xzvpy4hBhRn=Y7Ro%HVvwLlu8;k@+Bs+%sv-Y z83@V!^G_i3<8u9}Y9(lybgC+%aO|hAuY?_*`4_+7lGxjBrUIiWi`CCZF8xYKKSP7Y zo{F?&NmNzo?f`rIg2-=uJ$We+uS>06(90H%}Z!BWw8qTB|q(}=Y7P;3s!*DF(lJW`yEX(O{v%BuNS0q za=X|xmG0ZfTHA&Fc0#xIK+oL$+}ig%z2&)u-eaG$*?O;~^V731xsYyV!+kluyp3?d z4gR`uzzd-HAi&FTUJz{t%~SBMKD@v1Gs>@mmo#p=E;S=PYEbzRdiYy6j*v5)AR7nB z2?y&2s;g&p*U0s;sOpkM`zVfCRb~0k=it7RSRV{Rv!!MIn~?AxC49|y&PVR?o>E8& zcpjGT;nQ6)Kvhq)4{2P8NWde$Y!0C|ndI7$02wn6V9`ZLI%dTi1}YqmTj`oGmjbWA z^d~1_G7Np!4ENyk6&#$~4$;h$JR|jYgdry#`ChcZ*}?5l)NPCo#$8v;Cr1AiO<)(a zQhem(9#H;;XbZZSeQAW{7a_jxR zoOG2bnBc@nTNcpLk?zBd6cL~ZDA5LJZ7gpHt1biYsRU<6+w$;G-L?i_KIzO*DMrTJYk;~L z31RoX$OJdW7K>7?I2(;r?X^g$Sn~>2J!Yy$z-lERrjI*6U2OP4bq1 zPHo@IhNsV#Z%ws$mm^9W)BDMoCD(SW)cc?l_(B4JU+ye0`>oW9BF zLR&7~Cl9TOb(u)LFX~g#`aE zbxRsfZe&Y*Hn^1b7^4N!$-JA)@#A}7^n>#_Dgcs5#+xkR}w`611WI4#Tqfk zp}kU4j<>gYh+hkDXHCURBWt$zqM`<5iqD~qE9baf4xXJRK`<*<+Q+mq@d7h%n#2WI zfeE+L9N22WhL5FXSgRop;L>d-@_bZi*c=#vONVL2F=FuQb@D>c9BILR;~Z8fS_4LG znMn)2$O{poqq4V!f<}CUQVUT62joQjnw5A7J*jk?&VI8_L{%_^){8k#t3kWgL5t# zhNIn#k>L=AYK)TC`Yz3ShfeVBytWUW2Fj5akI&r1GPcO)*>_xg5nC}fv4-7-?Ph;# z1^XAu=h4$-@m&t$;IAujPzKmS8J-kDS;&g|EpUF~f87jxqEom3Kr5eW{OxS16tbaw zLZYwDPjq_tA85FQWNX8xry3@B{%~di_J8P`pBy(2>Rf;*Nd=0X!331ocR!7695h)Y z7m!?b3tF(Et})7a5ykAa5)~Mb`f;bOe^HXW=@e;@yF5gh*n5IddN=So^@Lu`a@RK`BD=|nCLe%-KLPFQ{Pg}DlNk!i56W=IlZI<_oe{n zoX{=QV>a6;8oc7(NsqsGOS6kYrT^c%McGA((gX0_9Cj52;1Bq2QFia6?LBQ@f*(t> zi$Z~2QnMtxC=vHEb%A?R@H6~ahFuZh8Xw!ds_`;C6vuiVtwO98LVFobg)n?r+i-m> z{)vmg!RHqcu?&mS1%;9hb@}}2nKVH5v0VDhAful^%rcB>)534#$?O64v09tHUhwHG zqWbO)G1-CZWAP;yu{eumUa+!$eoIXDA$zeY#4OEd$-W<%XwThe?9R?+2dGA)QMw*5#6?oJ*9IkgLfg^0-nppi1p#HlXn25qC9f{@_9!F04)&<;T@05c>_q!lY;8 zIR1_DELA7lynKwxMC`Qjd0<~fEf~!3K9Zi(RwJA%njZd4Z<(}c$?CZwnWwFaW3elvg#GE7 zy|!ITR?BZBdHLqf&&U4N)WNi`{RGt>KK3ILDVP{5vgbGaZ1;@t$5j0T=dBQM}Oxwqt@X!zAPz z3R#L>KCCEM@>ZLI2ECv4*I0hRWprO!y^;qe1;R0)fgqPSY`qB#@xIMsC z7jXk<-AtxUs3hd060!)9kn*azLx3K1ed8%k3@vszilk}uY?OInc)iX>88Ou3XcQ@J zpC5L5tUkE(+E$?n!hJiMTu+uzgchN%fo5;dH@I})O0j>gzGoUMaOuZNBM4d1&>2gnlb;Eio9Oza`g`7ZB1qcX4_}t!IzN>RC@lbN<>Z z=B5ME#lGg8ICrk(nI`fPPz*7}ZFeZpF1_ihK{@M5&|H2PY<@#A4VnsV~#s6!6KBfW_Tp#wdQ)N862cwJ@d7f3QuG7P= zdhK+!uzGWO$)#KiDGL!G1z{M0y+x z;6f+A8zphR-$#d}$g?zlep>_wef$ei&hJLa1xda{$)~IY3R;wwWnR9~YUo?ATt!eY zCX~HsRp8HUbj)6LZ$U%pMJfLL4CNhZd*GKWtdG2pk9rG{6>&1aTJrNrNEabAw>`4q{DQw@Jgh3 zj42Il`E)Z4EX|1AWFlv(J82-rx#wsm!!IiSc_^6Tc~xR541 zT??bLi5FO0j1w26jP?n>a?S)uZ_=pz0<0x0W3T-|Yh!da@go*r?m?4qUiE|40qITJ zVh>u$>N17l(yM;dx+tB^yb=PId)yjGulj-O!JI+eVh>$)yshFgy?y0RT@R+Sq1RHv za?hOx>Qz5@eVlXHTkO%}zeOB=&(Xno*z3V`HuR>9#8CmWh6-lxUiW+3$2o(&<=$K! z)~kMX4M1lRxY)~kERt70{Ay!#Ht{zszT876;k@bxt^?AWv{?_D8n8z3-3+26NsZvy zRH;!aB6Bk40*cHfiR)kvDnNpbdlW z2MzY6;^s5)J$jYgwE#MqhMVZbXL2S>UMp>VdS)#G^r@L#Y3m!}zS1^8hTlrtc_^7) zX&WHMW2J3?o>?nx=V8furEP#L|CP1@&b(II&coH@O4|TO9xH7LSy=SeCT6(~`G+GO2F?r!}}TzrtJ?)$4isHzx#;q`S;LL_Um9zftqJ#Y$= ztqq(G_s@Zmg>`;N7;LV1g>(c8n=3EtUk@L#E<|V6{wW0b^hB&!TZ_Mx{02A- z+JNUWx)y&a*^S@3GTqifg}h0DOv5^FMcJX$I+tnAJZq}d&AuuO@Y)C9TOFuW)QG%E z;)6PG1t99#w+w9*ll3P5CeqGTJ?ne+jIn5oC@uf&brrOXJx#@}e`e1ZTKAIcI9o0H zgz$WGwKo+3bWW`~2M9GPE28HtkCLu7k-TuDXi$c6*9;hztSlkFTU&h_7b5SHl9y~6 zZL5hFU|CYJjQPZ-e~XnCv@JeW6|O&nnrorvj1{{0*Y35~=d3TDb#*TL&9kn}#lCv4 zy%xQ`eAczM%(riu2#?0Q|+*uxw)=IpxR$& zv9)f%G>?sSElIvx#9QRd-MiW%#b$eW*93Q$6(b3L4lAdpV%k~7NQj54$_On>#PvBB zRjv_yM(CRzu*XR0EZ?8gvuN%2V&M8Im(S1XIhyqMF>>SX?b8)Q*M9CkT~=DM z7b2&H&%v%6y!P<;F%vY?<>!1%IXL~8DVyWDh~?r+H0x?QtOPS!kI! z>bA#6naq1k<}Rw8vT=41>?78H;9*QME@B`CnJApbT(? zGCV1QvPcc+x4`*N{@2OCKl1#Zv4WKidl?_`9^z+CDy#1DEXf~OS*D5e$gCq8sOW0* zWJQT-o}EF^Znl3VF*j1ouR_dRPF0fE7wxP}cA&c1`M>h~XH2sH#vPMlog69=)(8V) z9x5NtLoR632*SXZjaWy1xU3*i*BI-v1Io&hkB7?HZ*YQ1gqL7k|YZ1ty?A zyzvGc1YMAzMY)8{T6po=xikUw;_?~=d2%VdWA}M-%vgnlQWB-`JXy*=wFg$ZjSG>> zws0i0qtAy`;T0@IAhqL=?QQ=^N#8K#x1{(g1UZZyK{Ed*TF{DBaC^O|M>cS)RBVKb z&7g1gB7WJxJp2<*|D@C3L)(!huQu1S7S^}N?Ut2}qx&~jCqdiDWou~t0foEF8kn`w z+1gQ2@wai=mqnM{97`@VEm941GC4`~ez{aOhgFm*qQooc z-t9RLS8aG6bX}7G@2*XsF@EX05#xNknLv85$1lA$VpwqKxf|oG6k2S@uwc+Z>Z`~FW#`v)YAQnh0_g@M2$hpROlNXG}cc$@?L z!u#^JUGJwOazlc!VI<*}{Ggmb$qA@5o#8M9&C0S1{PiQ;muD?o^maU$f-(@Utj19R z%aVKcT)krY0wdmeobvWb$;90ZKXaL^^U_;sM4es8`gjNb_%(vwh89KRJUM0Qqui=MvmpB8U9uZpa`3*$Q$->=yN?`VWo zdbr2lClgx=c?P_Xw;>&JOmU+N-e`icOC`Hjw)Bv&0EGo8EI?t@!=gQbW|S_b{)8DW z;QlAL4ZW{xp2M0zbo7TK9M$2KE-c}W1}LPCsq~=-_blK6mGWrHl?_@vdD6*Ha*%2K z>Re5wQjOP`ud%}8M>)`~tT2<;+_=GYT~N4Q>X%o~Rn7LHm=A|!G0LDDcw8kU+qE5# zeS`ToX6b79(bsFs6wUwh67-&sAU#=(Nz4YgQh~1v-oV2)A=#z@YD8D*zTm>!QYq1& z+@4VS+Tpjuokx<^q0kJ`!NzM$^kXf2_`&V*eE7kYL$2Z+&*;MsZjY%l#Df>Zv-Wzk zS+iy*qES^KMNrh{=8`0$Q=!2YZ(=aQvkXjcvG8P+4t;=<#kD3^Ce7fwBUSckbVYOc zm9jKuJD@$xz7k2cOtPrVo`nK&iNT@Nxv+{#WS9*p zp?9rfZYTr1iJ&3cH<4n&@DsXV$6ysKb)|=0D7L?W(^Sew?Ktu&3YLUXU6<+Yv1Z}z zG0hTkRik_AmbhbA1w#2|O!;O=IgJ7bfH_st6Z*5qK?6f>0Spw3fK&xaRUlN+5YouO z@??L*d++6~4|K<&nwvI`Amm(O6+NhHf+XHx_}KD$%`Mw=NZPe)(774XiGfCk@OV{f z^NCQ}1JKZ}ZIK7Q*=CCuWAvKeyHvNV4m~+V6=JgGD&C*3Is@JM6IU%J+pHC$a?z%% z8F`qaVCYB8JA|Z0Ccw4I=g(4TuZ5LU{e~7Y_x8G2~R3u^B=$O?_-$LbZC%O^7X$jXl7b=Sn*NyyuiU|2lzoHA? zg9YkX{4R5xnqMsa&T$*=t0p}#TC`M6tYLWg&Sf+2yy`iYX%W?T2YBDI^bhQ9{wZo{ z%&^5O>$ft!?m}E&m1kEMEiFzqB z&}1s#gvKdheY9TW$=zyAmZj)qhos2ULtR8=v}d*D(D9q^w>aIW2Uq z6x~qa3n)BIR8>bc+a9w}ZeEJ>{61ymO9Q`d`5-J2H!-snU0tyo5=Lt>sKA9UDNXIu zA{pd8V+QG9=LP=y03yWpifhR1!5uHb_&@-q%HQ*;lahcurC}C*QVJ9@;&ig~llXmN z@uP8ZyO) zmiLg1d+rn5_*q$eqx-^(l0BsPvC;&~q$ltNBHC`CE~4I;C!3_O3ZNtLn5 zuc5pJRTwk-Hp-$y3=1phHIVX0n@0Fyl_o5d(Z=AhKfwRDDLHau9>^kdUQg@0ObcLAN#apwlBml&5m62EHX^n6=b-A`<|7G z(cPqXbwZVhX)?*b-rAz6o;vDg*9%3vlk%t@+VDMD5OME>7k=W$uC~L0XN3SaHEg`+ znX+5EMrM#*&m9eeOlmtDhM+oGe4)hrL}I?)&S`v*v=?U=N{_eP2cC6Vas- zWE1mtcnk)i&mveQ$R_nv%>R)eSo9c`>Q;V&;jWCZ2#E+>j* z0$lsD(-MlS&n+!8xst*H5QZz!I%FT4W&G!#a?i(Vl*QIXl^^*h>j%O-GlZ#F>PW6gs91aXHy?g}?j=fh!mkBPaXy2r0Cwj37M#}W%;34r*LCci0GL2$zc~?C9d9d& z`$NBc$)eIYr%w`RQbba)-%PliY`Ml?ix9HTZs~{ZakMYRP|ySFLD~B2E&Z@MBX&sY zV_AlBc!?)*l%>4%WltB02XyEvW-`fTsnIYV+@Znsu=by2DF?q3?7^cM#$T= ze?uwOe9~lMm>!B_jXPD+GC%BL>GvTj)95yH_{JzITS7M`p+GC%c4kvdos#T8lO0Ie z=Ff|x#V#M0wnTZR5_o$R1@8^uMjpAv3p05VDG!n2nZ-}3a~_HE2q`QyfY{0I_l*>} z`Rog1nn`EEsyaQO*tB@%L>jO<+4`|5|K)OF@`}8y-wu5ZT3-S$xr9Y1L?=t?i#Zrt2uKU^vG@*54P^=LN38$iP4O>$yw%c38+@`W7NwWw74@1sZ{*?BRI@w8QfS`Z-5>Kc2$*YD{MHIhw?#ytVEdGUl{vt&z!6Kax z2&i5;e_fzXR?^{xK-?mny^W9ZjL-3sA7X;>2D*V=pdO4Z)%hbR^>e}A1ZNFU56*6Q z;Uzf3xeBPgH!}Ji!0Tl5jg&EawHvnVAj_BR;IHa72c~wS3UT#J!TnnR7Bnx z1c6kf2*uB22V~$=V}|HpB=q4wa|zI)`UZKI;jNB#xSi{DQr1T2$kt7?!<}djMB&z9 zgEJ_kxBN^!9y8o0y_|=vq7QdW8~kF&FuT|eA;wzHu&z%ZstoaxP9KpwbwaV@$GBt3 zn%B1YO<%lN?CvUc<(Vclq#kYGQXnK6cQdWL8R_k4@?v|+`&(r6W%~rrbBE_Ck8f#! z9~b=xdm@O!knB6&qb{pB0_UtKEAFBu#g|(pU?W5}DRW6xf&wABNm+Rr`;$yrn!I(% zcI@eGQ5X>Bc|@+SK!_KeX#tORTKKY-7Pxv_%%vzTpa92VjrmLAWw6gVghR&9IhAmT zh^aFQZS1l-v2aLGc|6qVo`u_zybRD&meORBFJE2J=rh|@HwnHUAeQAr&X{ILvqC<;5n@MAghP#kuC%~-8%y?`GJ zkhg=TGAYx;HmAtA>T($(HGvc@S6wbgswR}8=Z@Im$j6VYe$Owz@XH0rzDwi#oDPiT z>EON#1IcOzy+R_!4x^vT(JBOr+08Cm2U|IFu5$JKJw)!Ex2uEFX~+fB9vY2xQH zFZG$o8^r8j_fPcG@8zxX_@31vhQ8WHF(cP8j9gB))E!$IyAp8{fFy=JZH_cj`9PH? z*D)hnOItru>1(|8Mc(nIF3pdym(4N={!3BTL1}5*m;5&(wJkT3k0I{YwFzFQUpS2Q~?VXffOPZFia@zOyo4(h)bM9vUnX) zTFD+k?HFYuivww*#5iqIvkP&e%oufTw6^AI^j%RF8v@Jp0W5)lblTC@<#SQz`)24O z6GV2Nn-8k*a?6vbjY>9BrW&Uyt+sTMSPaIeo%SuHsKMF16IdqS}MT@5eNj5e%`xC`10ys%2-IzNB=YZcmX10yjp1`lq>-!Inx8 zEh9Qo$v}wKIx{V-)`p6{425a@C*HQ8t(ogd*zIu_KBQF&2kpS!P_3ZaJ#@s|fW%EB z5m?mZJ5krnXrc5#NT<7Mxw!xExmauc%nga-GH#$oLR7L0f4qzi*~Qck zP|kz1Gzsx&pGegJ&N6yFY`V0Kkft6q`>E3|OKB1)v2+tBP%WOYA@k-kg>4#tx%cTI zt!k_>kuObDt_3)xxc7LJm8LBmg0%#{*aTXd=0Mj+UFL<#_a*sJ=CZC6>@$TB@+!iJ zE|fs7(Uj`Kyei`x;Yi+2MfmZAc!e6po!=)1?C| zx)h4Nu1R&_1PclWS&FdUg)?QqC0vt}tStw`IPc%EEh|en!is6R@byWF6?{;X{UQrCc&QMC3$5PGW*q; ztR&~Rri1}=mhRJhzd|tpot1VgxJh2 zG1(jPQKgO3aT~>J<-&0+sMiALn!z3noGle;X#pvg4vipoqnuc}VsXB-lP3L0tU2Qv z8W$ER4VT2Bw1itC3JiomTYt_e zbL_l0+fu;6BF}djE7=cjEP(3-mJE4OKfA#*LMb1E_#@`J`t-wuWf+v0k0#g0V~P(6 z{1wTmW2XjRueki89Uh8Eq5U604YOr8*Aw zyNu#5eP_i0a2MNwQQ%T;ws3ng)#;q7hGdHOL)jMDI+xVo(QXgX`&ZUo5ky~|C*&O+_U|m%O7s&qHl_hLEO>L3z<*SP_r+kEvf+I$nz^v@_ z5fN{triRR$OB>b87b*9PkUOt_niQV%P|M4OzWPlHN>@1+`mSB{s&JkBq0}|7;EBy!9(mI<5c0g3M&W<<==6~jqRBgBjJc;rc z&84OaxDY&d@)+6EqHOtkjAl_H7k!(9>g~cM3{xW{(y^0nE6Rfu2|?O<^!=y^RyK-b z=-*EVL$DGjeu_*g&#NVG2(PQ%Vj*6zc`fsd5(>WPVU-^4;jF+rh$lt3={xK3#RX>H(-BIz1au6Z_Fw(g_tJ-+Ms10})@ zyCzIy35x?a_=BL~Ee~=~1Y|Q}ny}+%_<=kC4HnIpCL}M|A*@-(_Bf>kQT}|mXp6!5 z+qPhLNm3=)urJFpl;!{j~T(%EHj@HO-z{z=u6qbULSaKq$YsE$_Oq(o(hQ ztCCL-<_~-KT`6p-YaAJrI{9dgS>{RDM=4f%ID8a$7p@rXY`fD}jed~cSt@7RI#1d@ zHG)rELGa;~8*EQsL!?bCVxOmuEDZFd?iwXieJoZ>RkSZMntEJE*<=WX!UKIph+TXRim-$;gXOUzg-eeRvjT zuK9j0&ffrJC$rUuaY-(VsN}b9qlzU9vq_Re6ums5gSefnzeFh?K+2M&VvaN^!D0Ip z)Y3zmCLwA#6~z-)I5C3bhcC$$$a1HfjtmmP-75a^sl`XAt}MuGm)<}r`qtDg11ypv z0>o7Tl%~&0U9GIpzUO}(4TbQU;k$Pb{paaf9;NZ_9bU=}ZK{+z6D{$rU3>D5SfTW% z3D4Jf_bxyc@?ecY3*5-~2}NDPrQFGM8&$t<9jGDfnz1%8y3n_-b7@Q zr)Qb4!28hF&}w=*%DroaS{x}y#4{N)!jTeecjPH{b6*SGDbIz%-)0}u41zI~fQ;tU zyieY2Vz2<7c?h7Db^qXho4r&^aJ9grcuJ-mA2OY%xldFXJ%+c(?Ut6*`4wHb^V0~z z!zmiE-VAXW0m;ClC=@gYd#<9#4|w6oN>xW?U1xH734dF6$~(l!@?!Fci?2}W|j?FF> z8~hZ_`^IXk=AaP%=S2+%{WdtcnXU%Lg==6>gQZdk+N&h`TOAi;(K{!s@T+M4=I~KI zgmNhgTsliLbmFQZR1F+z0PfneNa8Jpu3=-;AR>G^Y&A2`$6FE56bMZLX+la_AT?QY zYt76u#8-zD+nqzY0LNU)Nf_mFu|eV>y{xMQY^uJ}c1R%!x_>?@GW030R)4b@g1;Y+vSi4cV&K zOkdE&I+xD%H441q0wG)FNiSMXW2IS7;H*KM-a@#!D63%x!7@DbshemsC@ZF@B?moR7o5UYg2}JdFj?L2 z2u~Yj(f#cR5kqxt3ZV%lROESv<~Xt>1Yvj!`l%mX2U0y*64JCO1`}8-W5t9+6XEER zmq#Bw7xwT!;Y>ZBH~bIIy$AQ?Wf;T%gs-$4SJE_ajh@L;9T$GVpE~?-IrG9kb;tqF zhsl6X)3&(3bRYzJKk)(&4Ui%nL6&)BfEDNl#f}e*FzP0r`?&D0fcUJ-W68Xp|kBodM3+Q<0@{TEBAM3^1L!`y^y6%3pr0vy_#z zYuY0G&Wo_u1L@4&uO#84l&}DE$BH#eXRrM68^Y?$<8Kvfmi?R`MC^Yo+h@ma)d{-n z*uC=av3D$;-C=J!UEbeLt*8c{4VoZ8B}D)U3!LkYUwfH zxWxMoHBquGkCM{^Z|O1MxWfAmz^e#in7bT;HwV5!n*r0cmdnAEhyCddP&y3yf8RTR zg}-o=aXQ@q&%vv!F`$6JgV%I>0(BkVB{|NqY}1T|6uwbx<%N{Usf8tJo3=DM2dE|A z+FBAAh~fbi7n^i@@azn`q?Grgjk4f&9mO%Lsx05p#sK(QtE6hr|3*7=JqrOFm779I zjCUnOp+FTPm;M2*CWv&%#CuHo$}@kX%<~My9nhf@xNCp=ABTCk}G>NF;j-YMHI*=Q>?WYh~*oprVOS6Sn)@)8E>+NW&1`FbrJ zWTwrG?^t~AQgP6;!_YP;!NsPwst%3|xM^KELX=ILz7*_kL*B-1+i>%be997PXwHJ( zq_BhOBW9uY%+`+l{){$sm36TEdT)n4Nvjxkc0W@^WObxcY+eAB8NE@IEE_{qZmJQZ zW)sMdkoBWXnS47&4Ya7KQ(4%lw^0+_D)B|?&#vk`Ywcw!%(AY0tBD+_l-n!+rtyw) zlpgBpJm!t*HyTO>l-KP=Q((Ty>*jZsp6i(%h7)w*I1;jMet%8vpYsuZs`RkSSd;SN z4D|uIa&(Onnv@f;v}RA7{_53{6-ZojIa_~1L6+2&W3eZf=A%(&f+C~2J0j2W7`6qS z)c~b)%#N>=$O5wRxk0K^;8dzO=tn^;R|lgZGlodV1%d+dpn?alf8f;jotxEZbSwl9%e%ne`ihUW<(G-8`w{MO&B=5F^HVwm7nBehEYSx(xv3 zr(xPG{<+M=nvL?r95sguptE^mw*pTk+P8_ZI0P__tEST}5^TJyMOLs!{8VCX zY!hXgu69>-A0l7&1W1Xinqp7gkuEgN1nmWG`9(exBOOmDgv<|-?^E$$nyd!S!Oh!%b7Q_?5vhQ{u94H)M;Q$NcI-b%x z0__84`jWUA1ZkLrDONSRxOqbuMqpp@w6Bo#4@mK=OS4*0#Zf#tb$KQpf;J#$QpAJt zJ|3Uo^kirq-A0EbKfF@Df)2cS?5*FEv{P}3w<6v=?QoOvYvptR=1gw!)_Wy&KF3Gs zs+A1FH&!o6VjG7wOTR&}D=M}W4dQWY(q9WNK_8eGQR0zm(#M%Yxh=|UdUq-_5n|5= z(V-L3?YFf+DKjVgYeTkJe$5v%#_;7fS!46H9yVv4uqrOo0uGa!cHB9EeL)|9mBH(e zs@#-w&jbT1DI>pzmp)lRy$X-O8auA^+gCi)IcHVs3TSTqI;n$mgKhisX8yPI+aE6?z> zwYBs>T(WZHS%T1Hdp`1kDMj`a-$jQV6A^xU=~Dq>=)BuXPJ?Y^`A`ZF)4lD)QEMU4 zS{H^0^xI2cjoI=|q<<3W1}2^hrh@;)d48Xo4TgLwcp>E*H-YC@(FujzmaYp-vjU{H zaGwfUqrd=BZ3>^jT1537o-DA<%l)v6TnjFg3EPG2u^SY&0A2F?H?8~57OZprn!SuI zL8E%Ys`alj=M4M!D^%`Sl||LvK$MsB7#p;3SX<8DbYAS5ip`1{P# z6z}Qw;6Y8@{7HVi&6o@5!*PGhN+-;1C0z&~@vb#u@#%z<;$2|tC(Jv6H|Xm^`ixni z(_?Rj7VSoCoPm4kp*Yr7_Fybv%b&kpEXc<95~a8)?)% zV(330KkkV2G*)=oTBW^1);l9Qk*68J= zf(v$8#tD5_Q5T4u#qdRrBc6)wU;+AURs_1z35#$Hb4_`ttvkqu#I~2u^E+9hE9ff! zMK(${^^ljReNh|vUCqCi)*zvtTwp?eCzQH^u5ze&YosOHu_wHa4oP*qt?INMofXqi zV_7HIL)yBit{@-U@cxiot9&lzSTaAy1_SpEa5wV#^)AnM{IkV=I0NpWQ8s45`bW%m z_34LcTh8)L8vhr*ru?)wyGNPXj$4Eirj7?Y3pNf$OHZ~CNzhUCNj(YlP{6pzMT z$Nc%r_jR-z1}}2sN;BpTdG=}aq8KhXD*9tL3Jxz;K!Bz4NSg-Nk zmtq8HT^A!h^a>x$_6v_WD}0Su1&b#QIlap~>7oAH4@(WY@Xkb#cRU|8_`6>Z4-&=uoHe18<;4J?+qSmhy%ovz0Yn1yb>~ zNdKMEhY{BKJv-1|u2FQDFQklC$aKL{*$t%8hOa&%bp#@KY2D^!J$aO)I=>N|w4j8| zagFMXMo8o1X4*roKo&UDhX`itebqI?{F4?%|W5E%Q6P}HqWtzD#oy> zOn^;YSYd1AkAM)fJEQ!FJUD8gpH}*n=rQ#e9ejPy_&WzvtD`V5??&a_s9Sb$uy_D- zi==8C7Pv%U8!dki96?~%OMrg0O)JhQDyyNFV~E76Vqu+!`b=3T$rdj_2qc6N zv_d_O9Gsugyh>{(A5pxpf+ytQu)-GG`dCopBPWnJM@TEN_{$kypwwF61YaE3%r^EA z$~+jeO#BX!z60#cassIISzr@;l&09D*mNO=69g9PyE5J4eq;LOgiq?UfOkxa_1MXf zh6?6I;9rQFSOKBBa01Co1yZve(D2;#3=pmA8ogqM)^{`t+Z~=wUhuvJfZmXQ-$o^a`z9_t>>$0Mxcn8?2&4fF%L}!y5KjpYWWxEjo!imZ$hi zJfw;1mk7%ePX5CZAI}*y>c)_jMI6Q@!?1f%X7$;|GA_>~6bN)BV}OoPnec&~7$R0D z>Q+lNfmAaBNpFhR>;bwIReHF$;;L2F2AGImiUk5x7LYPafUG<{u8C&~dkJyGv%2+T zpak||;D;S3=opYDD?H)D5AKk7AzxGh*Eb1_mT*S5oDbq_m(^B;R6rAZz*sO)IHqv3 zsLY;2TEYuQ4@eqmjB);(Kbi-IuVJ{44iLIXO^x?&*&Boa#TsLv08W}EV;VN`4?{8W z81C}~LzB8S;GH84lyplgWJtyakDLe%8a&ok$?9zaM81eID(Z~}W6Fl~@SprqV9Dg? zYVd7QeuT{$!Vo~2RdhF3-^BHbmMtm^oHX+(wuZIg zJ|$Iu|BUo{wxCa;6SJ#AhYfq8W$x{6I~{|f*#(V1-P~NRaLd~2^YLKKBo+<@L8qb; zl>|zO4VR*2KQEm!^fM{_MNAaAlUh~Td4Y3<0`&FuR}a7#?gBzvhErDsL2vs|`3P9tZ8p4ZVUcev=%A-#5=qG(%d}=E#v-+hp!t4GV-YqjEFySZLo8D+ z-rWODB4k=XngXF|5?=EbB<%LM3m?)d9Xg2I@GgLm&|!U&kT&lEDh;Gk2efGt;xuD) zMzCoTG&-lSas~M|966VMDS)U*xZ_Qtf@w+K48-MgQRm^vLe)~}NMS$CDRe&&n(Q$cuoIR4<|B7@s%dl75l9`QU}(y0;6RV_&uhpzGY)7JHnlS$ z9|omUk2-V-VdM$Wk$L`7#YzlIBxX)9v zN_b4|E$3@_i2$@V{%1BA`5Odxcug0D1-E9PXl%tYT%M>lIGo|1zl5j7yj^tkqLhXR zXn}<0V2#4!O?;hIUSJ!|6L7)D)Y|8Qe^wsAu2!K9b?f)Hs!fs!*2r-eyp1OYAeKIE z;VXV_Bu1YH$Y|erpCluEyGN>w;)yW^W>Gy=Dqx39$evaYae@!ZMXc$t(lVU|i|~1( zFQ38PtexTlJ!s)-y0{H4XWOc1Uu1Sqek{G`8f#=}d_W)vOsQZ<;_x=A_?TN{m|>&> zIBr_|p#0cT`JfGe2qyW<7{awrQwN&g-aF%?4?h7nAOGo)xUnNfj1wCf|9Lk(T7^ zxR^2QX|#?&dyK06=%G0pI>@`hjc>e+4Fbrhj?2+O823db&xdHA#^8^1%vj~^?%WwRBNzdLprAo9i5e$R9Km9liFq0$I8tVTiI3k(XHf7} z5i>W?9dLn#`fPCJ$NH?eK;I-T{y{$98v$oE*}V|(opppX{Vg|QLQQ=<&2o$5N4#0I znfLuxax6aG4Or!PIJ)D?2)9=h0QVB@AS+Kq<0h;w=#oLS1s2xF!-01Znz4@<)`Tc- z8^z4tnzqc%h)k@P=C&-TEnjs@*l{2P1%yBw(KVh#(kcgX+Ls|69t`u-qPZU*vK-)e zu8mO!ukV3$M?-s;0--#YZgoW&R+M2S)!1{O#zKod9)l8bBwC#K(NF^~rw8L9p6rhE zENYm=e|w?@MPop0+G^XzxzRe%F3T_n>u9$t*={HdmZn;k*r*bd1LJl?g`iP2f=6xW zw#L)m5AJ}cQ8j`mIaJn2l0zlk^IUKwhiVjo_}Fdp@-ZqCT4l1wWGd~-l>BrGA+&B~ zgW2+9-$Sc?>wRzzf8rHFmQ>Teud+pv$A^YVTE%epc>)MX|8z)-oJ(D)Lh{Ve)n7h+ zy#DJjHr}U7VIHmb>@A8>Xu-$tEB#)5{N;KW)}Hrq_s6VG$AbBqYPDO?wOaRZHmR<7 zq+Zotqhuhst>ffg2a4J$1mZ_v;PKVg1Yz#xh;0`@q-YdL_iO6_t6P*Xc2g~Z(#KlE zF@hR~scC$|r(YcHyvsP^ZT3b&c1j=2zP37T>TPM|r=8K`;oA=uUUG;J3*&u$NV2I@ZKjSEx{oW9R6 zBJDoI&R8d(;ZDe}^*%kM`{Q1IuPb}+EYS&RWNo^#a3fwPZQPnpq_9AgZay<+FD~6U z-b;Jpm5|jDPG-Vo$OpK*h+)8yB>CrqQ9UR{3^S z-?7!x$7^k4*rkv@2D!@X$CDri!Ak>%bFI+RA0GQCspjI;CI3Qiw}8lFO?N77vXXJs*JZkO3W8Tg39->`ji;ybQK&oNesoKNSlXjqEczmsLTSSlSBW`m?ez{Jwg=73B0gs||>Zm2eH_sf+=#(ZKI*ASjh?0Q>Glf1Y+yXa20rD;>4)(A>%On!? zOQ@OpAXlM#0K`Elgn8hmgCRmfpd<`7YHjO^6t~4h$NJ9AAg$nr&RS5{_OQ7Q*hGWc zr;t8YVHWLg6USyk6+@Kj${yom6$rf+2<6`BD%~A==SWyU?Ua|8JIPMqvobga#b!Ea%%0MuK2gg`6+IS5mtHS0FxK7dVX(@+bwg8 z_D@9+8UC_IMY<*sohFQ{0ghEmbjl#ELzucTTurc&Lzv~cW7vLC2w{oe_Q;Y$fEuTe zLAc)P=oYJA7HT#S@#Y?Tsx-ewc9dH zcCJkJ%G(V%I(ShE&Atf~lfWJ0&O!l#Ha_j&0ajL%Ncz!V&l*(L1Y4C26r?FDs&eZF zn-@Tw{I5MQ&@zSO1;SaIppHbdHlRM^VLJw}6ANu^q(0tnZOB6&aK;<o}g#Cv5G;HoLgMC*ExmNtHssAbDX8la~lc=@dFR#AWf5VoZoo z(G(ioP3;E9em}Me6)Bv81s&II%CaQn`O3&Cl0Km{N$}_dW9Jv;1R4?>`0(qUPw%}~ z+7~v;ua-`a}15PV&4pr%H&43^geneck83iJnO+5`Z zYrAIKv!ET0_^mbp_fe~Tz&;Pr&|cs^x<_{Fj*S;m$7k{1ZrDKM1TxRFco1NxtT-Y{ zPqKzPcH+)3YVBlAAK;y{I{T@v@asHKc&05-XKA@&R|V}3U? zkF`giSC#T;S$SEz4)1-MWpWYA37uXL0P5G)FrkoK2Ae_(J=0_4*yWLhREJn5SVL55 zr{H1GDeJH_ZItb@6!k+UIWEspG^(+fGi}&MxA%zWM|em3w*^#8pxDQ5gD##QUDIRx z0T-y4Mj<;cxtjnky%)c1}e(v`%hIic|yx zCOWMI3sqBqWROlbOtKWhk{9;Sz$+P*PN9<5-lK)zL`scUztFK(c>I0pPQ%)g=j<-+eqE!;7PW1$9 znxT|+p8|!#;YVQ=m&e;%$5*{y+@qI)`OsHLVX#8IXH~^NslKyELqfbQ5TYFBkhCK@ zz8`bP$E8~soi{sa))r`ocMuW_$T1w{g#q-lfPt7X*-O2p3XDxkG(Rx6lC^c-V$ z$|Bx&l09UYd$b>5zR=04Cg5WStD2BMxy_33{LN7nPxcKx=$U|0M{a|Vn$Gg6Bl|%J zsF*@Qr#SdNgZ<*#(GWgQqBtbN;bFu%XE+wv4Y(|&=Dgv;9;Q+{fv$;$pC`=iQ`0Wm ziYA6B)?k}(_=sTwu}w&>7EasvTrErkA-W_I6*AD%c|^q_T*iS!igR!xQ5N7npY#Cd z>^S(HDFfQY)wu&I#RU3yiG3=DrdXDrZ=!Kk0By{_MzQix*3q$r4-|?5mxv$p~ zfL;mRL>D7u;XpyMFe5+gJiC5cQH0)HI)R2_*3I?)x%!@Y!flEo+~(2=E)=td>$T`E z0ncA|m!NHz-W6;)^WjBwyL5j##hG(8lVVG|cdIgEAy|TSX+vC=gB3)_6=8*Id{J0I z!szH;^{y3d;;cJYHWst5>DJ*W?uc7Qt7w7O#qljpxb=floS}zF8Ks9XdW_1&MqqvO zT1w)*zkLBp3Q*FFepZR6yzIYwK5(tl>0EdZAWkOo` z5w8k1i`6p4336K4(|t%o%QG>0Xq%!)a&zef_K*g{XJeS)&e~S5gCE>oKX}vCFxQRt z(o8s#&ogk7G|(UdC(ZlGE=IxeefFW8baT9FNfngPoofLATeD974t_|rvYPLNJ6>{} zu!Zch$}ge1F7!)i6Knkv(jPe=S+jz-wO&MaieohVc9IKbRdkY3-e`J8)@)jB<;h2l zx4k+3eHq5Q6ym{8|5^ms@UJmfTb+NaZ7Q9H#pq8Pq$^m80xPSZ{Y@bDlZ6(F;s zcJ(%@STf$X7u!8M1E(qSs1~2YD%gT3QK@4SK51u&*OSKE7I#vrV%6gf%QGv*)lIi^?(76$KPE zNtzk>ilU5y;BMF}i!fNN*r;nQ#;H8Xfseq_ZAxF^qTt^r-q9=5+hfhYSI0E72fwXRlv*x`b@emxmiXPN8S!(Cr-KNSQ zc{3y#S9b>P*)!C<-AZEsQrpxGx`|WtEDRUUYfA4SiSD13{U!mj`>74>CasckS{)?` z#09)vaF${0ok%S|_#q#R+tQAR!h668tHl}cq6wvDz{&uloJ~uP780EjChbw%wD3@B zAyhWT0-TdwPuuMbYj|m}f&cMD4(bQ+s-5DZ0ars~=w&gCY8=LqWlPYLv1!Sm-CA10 zq&aFky7(|Ok^?CyKy*{q$@$MX&+k(^V|x4QsVr?-fnWCM%&f@~ zN>AR#AcW2!>m`TNTb`tNt;eU}1}Ksya+5&7b+nlOd`RvZ~)vxlpUEibu(p;Dx*!-*zLE2 zljE?YHcevyH4yzvZPH8=7Cy3ccPF0w1N?uRP7XYVqKZw))wgpFRa0iZTL)&)zi0^|&F+=B>>Q*)_d9H)K}E zqN=uyusUQ^O9;+z=m|asp@5Olt3&4Oqg}=xeC?ppG`NLdKF9?Ed&n^RR>J#ym;2X1 zO9+l^^aLL}q<{cn*_(6T0E69R#K6_jvw7fo*@ z8$>MyAaHr%g&nRCiUa)lpVH1(bLI!q?^{?MZ!6~gLQ`q%kl`;M+ya3;{0r7LtvW zr+z>XQZr(iBOuj*QXP=$098*yu7uY>Sk+}O2>*$&R$`_h5IQYDst9JdvtjD#P|!s2 zO}lt2h#IDhJsLgmswfVS$I!?5e$P|WLvgGtm~cqT{IG{E^g~pp(e3mytFojeYGV?G zhYY}ND`QArK=J}jUZ9|j8AcO+cnybD6TQ3!82t;wT_A+gV8zUu#HqT z@E%Ba6rNf9lrNjL4OO7w8H-lcH6w;ycd*wQuTT8w2&ge^or5jfq;nc%!Ur(vfw-Oy zRB=j{H6(#ChQ%0k_^!Pc@QB3%KFyg``LT@IOLI@tVuZz#{X-R24$Vs&byZ~);5YL@ zdw^*>o0de)U0C{Go|I!b2{P<(H3%Q)O$Hm25jz$*b#~AWEb7+UutQShyzLvPkW;ma zQmqU3E_-iJ`PfYCx$VN1&O1eB9wOEJJG>EJWf`jaSYZU-(7tmhut^^uP-Y2@50lPZ z`BJS*<;&`VAyQRD@rynIat~hvX4(LBrVfj!jP{k)b0DJdx?)2;fzUGzOS&NI=brlnxM#A0%-fyjBn9h^0;Ia!=a zJ2b!6mn6JHI;mDG@FUrNxy-qUi^d1s9;EBO&^}-l#nI)tFOP>h-Lr6OdE(p<{H`2a{_Lc=nIlRE+u^h44DsJ- zC6BF>Z1PZaL-co1dc#Yr*>V1R47efYyO0t9hGEb!&&IpEdBz()oEc}ffc26aEQt=0 z@FQ$VwSWy%ZZdyBC@dkzQ90e97$IXxY!usLOdq z=(sFRxl7~we0q1c(F{=*hiUY49(;+V550)$J8EmNt5UOlkwJJm{ld241X&xvUqp?&}{Yym3w>4EpiIWq$c&SIZ7m*mf( zm8wY_TT+JVX~^0l!r?Ks8Qk`5bj)fy>&R`KN`~7Ajw7OgO(Z02Wtg;rfCQ8~kU)k| zmwCPwtFUs>C%%iOo%;~EURs=n)kvGf37O@rj~LRE>H(}`3bQELmX#&!ZaiIOSX)iE zEfj}Bu%bnR2Psyh6n7`MyHmVKaVG?q0>#}Oio3hJ1a~jiLV-*Be$V|kPjZs1nZ0JM zJvnD)@2_wyQ1i1@i>t%ujL>JZh2_B07Ym1QVm{az- z3aW18xBk`-sV<&Wo<7PSbFd%f9%HroFt_;OS8B}|GGfK5l1bAm?8R3wv){~V{V9TE zV(87$8Fet-UAs_P+4w*MkL`Ed4=4_kkN_S{f6T@H&-h5fFemO>x`5VnzA(G~;zl&I z#G&M7uYNtb`?Ceo_U+bkXfJ=b$F?z7BVUQe+dEko+yUmzZU>Wu_9=WZ1Zp6KoWi_e zPC2>WLYlgfnIhv{jtZAC0c{k|ujDr>It6;iMS4{~9+qoeXI+mkOE&q#)39um-+9Hq zuyOFYl}*a>`!uzVhJs37wN#Gj~}PT4;ux};paDWOO$3Jo!@jTe)f4|obZTmelxm77P~(XZzq!x2;5Ck zR!58r8pq=_AEMoG$Pb}AFyrsMLB2#rr%Y7A)lYj1e-}Se;I(@7Dy>e&-qdQ2XDEmP zoq|af*G|{JO4y5>O|Ysp%hjC2IS@tLcG5c&R%tJB_s;DIxA}bA^ZMbST~hQ%x%$fq z)Wv6iNQNmm(OWAenr@>TwBNI}bvHLup{1Fyw?`OXBkkrI3K$X|KN+ew=?-2+ZBbSG zJ!vzSL-buThLbJjubd0JLDTY9ZU$=A1w7v{-!q z5qM3{G9)B>5yk^ZCx?S227{Ifsu*nFnMF@I<14(+{C=9~L?2ZQz=x{3J>)g|nIiU! z*P(_pjpwG+Bi?=;ZRqy$m_vB_ei~yZK65|X(ASm^ev1!Awe?ezo3_gRVuijazJ(;3 ztdJ~mQXs9_9e1uwo07+32!bI!zry3Qa&me8Luhs+QOWamhBg`Y>Dzy7H<0po9HDXdht_x{<-kawo`%X^%Z4)n`8%W z=O+rl6ROYn_FoN#_>6gBZv>Ej`P|uEM0B^m0|nF~Y}m;vB9w2r;jCeRr!w)GNK886 zCf^Buq?J*$bXj@x-n!mjQzClv)UhR9fB)#U?XdEN;@4fIZ%OC~@FbnB+-mb)bw`*Q ziFl6!e@>Cq4(s8x75OlTLauihRHIu^Zer&o)+t^WHCC2X{BMv&e5ryKD6y+f#$z0tjP8jot5KZ!%XLx;~r;h)knj~&Ehp~FQ(Pcw6%VaaZv!Ae8@0_WO=`x^e0aQDrfv;dN znng~{NIEfW`1GkXu*ugru|2$KaDb#1b8pk6_M_$lorwZ`-}7wPJ`8|7KEk=7QvNlW zKwY8nT_=u>Mg*1S4L=HXg!FZr$_KjYpExzq8;a$>^Tq_YWF>iDi&+pf33MA(HexD0 z?K+5|6!wNIeiIDKa>XF|xqn|k<%ZsNFqqxvi7B-C^`~!mUFh`rJNTlDiLEyx?}@Qh zj9(s*jBgXwxg%+;3{cD3K;6Z9-li^TT14%Oo@)OTW{vl$xOKTF8W#@GEl=U@tITA& z-sC)apJ~4SWNw(92>u4jF6_^crHU6xq@Kqt(gX4TrK$CeSk5? zs5h%FT@>W4FHWQOW)5GocTP1@GEm@b70C57yy)mpfv= z?X0Qt869vb{`~fB1?v@>DW-OD;#bE?=MNRF@l7*cvBrz}@QkaIXB8>sGp)PL;ZbVk zDHZjmhlV^wDHeovzwVRXmojVk(@Fh&QZbu;m0A3U$1HpFLy@dGYsp6WkL>U4=EUkk zck(}I^OwK;@L{4c4cqze-ct}-X z9iNZ7^V54U+-kVN3tMLT#$?_SAu^zaKHs0&*AHF;O&osNGS0ESy@zY%(>z5Je*Y1E zw=gwzWDJXO*dkb(uOy-SrJU(8I$;+bN2Tf>T+D;T$NLYTc3c{RMZ0K?Zo=#QPBJfV zwjR^c8e$w;O7E?7g0RY|^dpb7moV!05Q7roY}`| zST)k)hi@57C^!TXY5004*vsMyyf~d(e4T%FJU=|omX?;{CukM3GddhZ$hb$_FQEzO zN)SqGcm0+#N=v+CEAP<}Kx^mb>`3hMz~GnQNdx@W06xp?aT^cY^#DJ1mK?nEjXM9y z^j&D?J%z&sL9T#teCnkmk}t6_0&3gx>yDO4W!Ur3Z>j)3tLId&RKv&0I0JM)@=Hn{ z->gRPgIZnq^lOMaSHSxvObI9jaX}1SjGL32fU)O6Ee>rE6dn%Kcfc^3;@e&({zqH+ zlQNmbilDZ!62a-IuyAQYD%2pd5ubQYt*pTK`#Q#N_B2xh_V)`!*lVY zQ!|&1j_BU8%-V}4ZMzz%Ltz# zgIBD`bwi&*Q1P{zO&#*sFyH?+k*^mfJ-yhpmsrr--kI^;EQT9>4sj$#-lie&A`y}>}f z4%M3o&3|>{MXt`xZsZb)3Mt{U;K*s>vk4r&`59HqvuR0Qg4ml+AYMf;N#TSbQ)fcJ zsOl_B)myYgqZ&!GTJU|x5xJbjMRrrEkod!JRY*;pfh6C_m|bM@YU(We`R>i70nNY5U)dpAiK{%lewVEDMYt-IyO^$9bfrriccrRCNAgzjuBN}HXw;yeM}SejSWCRcLWnhUIg zrW2dhQe_JE;PbG1nq|>2sqkfDza1f|CplZ9VCP^G#^%fSe6AaM9P_AJK$Sj10-v90 zE|FK7OpJ0;HxEG)c?g{Be$9*CPYFUPudIjlE<;a&g& z_E-~q)#9cTfxB_#NYj7I9TOEGQY~-jF$4GXepCn<8w@}U5BIe?lK)co1C%=uRY>lr zhNHjd>u*(BbS&(Li%sGf&iSOFzv*6#6jNils69^-PsswWwozlc$X$V07d=@C(l``Y zu9g`Zz|!}yVbJ3q3swH#qcXO}A*%=BDZT`@zy@+Ve(`kKF{1_i-1 zTE;Rp`yMpDrsva|fhIzJ)_p5!@sGKHdnV-U1$zzj+6Y!4J7X4irRE6sB6|Y3(pcm` zS2Zb0-$rN$vruIW{&=|X=VXBzaM9eOgb|hQx9*Rws^GWDepX<{GE5SD$2iI>i{7dn zw3|*L&px+Zl%7$Ee*|vROLu%O?Ew@T7rDv3bkjTI~9G z=}{C^NoR(&Wem{f-aewr|ANHh8_N$1h*nHS?6%zva?sHN?l>U>x{D`;Ce}i#oW6`0aRk;2bkJd{1J}I59|obYKe3?PSP13VY^Gv(Wx*RR10t<5E*D zkee#z%#Ts9<6?7FX3~EtsW(z{kI```%xy{2FjRHkxH2b0^mrv_~d0EIo zvx!H)U#8>JTMN>F1bU^x5hDsS>?=jZWfK28rHcqSGt!_*Q?1hqmiOw{D|t83U!Y$y zOq8$^o?@RZV*X@euUxj5pW(0;1^!g{uzadp`W%)-CEq}qps&GZPBbmK>)ZeQo8S?q zX^ZD-KT*PmE)Tauq87>15maZb{L5^q9#1RO$!wubE4v5M{THaXgr7ih;Cz@IAc6n6=y z5%MdYVBt@GQ`oS9s(CRJX_)xq$lt0WzIup@$gB(&(%M>)J*@i_XpIa5Mj8%m-JBl> zPG)LG3ssHrtcH1uv_=AJ@jr_bOm-U19wvMbX~gfq$|b%~kAjAgy)wh3;2@pl3CfSR zb%|VHY~0K1Ec=u<&9lF7*Q)bp!OD=*o&Zhr!zaY#;!_@lA_={ij<50m73$T~>BRZzPIp+w z+*)x^p3tcw)0$$xQ}PhyKD49KC+}*J@Jpu8{q2*nI3<&9?AF5O7bWRW*njTk#D@Tf z(0bg;xVqlcrin&E8nJR?SoPqYv5@`}NsTO4A?MEX@##q-Nkg@_?|Gq;T7iO#b#(Ka z3zcFO3yp&KQB&HX>oC4}J?B7WojRSF`W-1JH&27~1)fT`j2%FxE_GM1psj_SX?U-8 z2O`=lVL+!r3pdeF$8=y&=5<|go6gJy8y-Qtoy4f^)pH9{`D1EPY2`A>!;Oe-1dHLiD($uodxp@+#6bd@^-!9(xj zj&&lUcN`I(mos+;3fuBscoW#Y(;mdDn*y9zZpWux3K+I6Q|I2PElcZ1++AaK&JFAR z>!wqH-P&M!(NMTD%G{eXQTJ3sv@%;?D;=||lD+$?cD>fi)Wz0cyQ$pOREfw2T+Yqlj*bO?R!sPwZ zLVcG7XUa!?$rZy$f?hU0OwB)k77W;*@$n7p@UAZ&8}Vs*m!4i=v~LOPXjxA`d&qdS zq2tK0*!!8f?VY9b&iK}NTB34GB39|o3LQtXg>uTa$Dpua!V+JqKPh749thc zJp8LAc~73;ib<`{_hTMc;ED2;Am97i_pe^_@on4OF~jj5^1eCU72HZT+!EU4i)LRo z9k{ch%KTZu>usHqo~ebM- zVXzHk!R^RsHV7W&RI>cXldZtpjz*Jv=EGzkXZ%Y3y15nCmxSd>H35$+Rt7B6Z5Ks! z&Kk&Xo<5>MM989->(B!G5iMe*_!WVOn{H;m`6~R(3rJnnM@$4@S|E0DU1LH?z0I3?7m2W(YR2B2Vne-^=^s&IF5Y4=3JN z$4fdO3nkCqBD^OTo-s#pd{&5FDqpv~X}Q+8rST_w(}rYCY;kM{e;b^`hlDWsz!kmz zV1DUa82YLioWzq+pEU6GV0`@MXSdrc;-#C{Tuy5ApS&bSoA)EfvQL#M@~Xp*KQ!A* zE7>FE`o?kv64y3R9z-VJ+ugJ<-}=atcC2T5RMu5FK)x9uc>a8!tK56*F(!Pt)84k8 zQGXh^>RU;t<7K??$oG2w@z`2+f5-Rwy%Wwl8OOv^eWp_~Zg1yo@K#iF)C48I--@Cg z(t^Fcg@YiUGsTQYxvpl^vPZ<9?rte*i(Ay@1Kk4_|BUdv|~gZvgY%B%dn>1I-A*y{!B!;U(=&0Dqo+P@{Cq0?gORcazFkaj+9&cc z+h@9QF-07&&0me9WO36?D*ow-F~?ld)#qj$SMUsv`x6CNgQDF#g=EVUH{Uq=%=ImN znG;yIs>gQFBv;RXVql> z_7e;3k^h@W+BEEJGIuU659t*ulamL-+!H!))wI*i!jL60q}dMySq{Piac*k>IAtn>-OpvpsFTCUF{#MpG{PVJ8uj~d{@&4?@o8ZO9v(DKxuZ*>) znOh^dp2E+b_3g0J8zDT>;kxjOvg9_iZgsW#-=x$bTVF;-Z|WsH5Jx0zZ!=vL>ZN6e z?@W))5u)$Y$FYy#M)sccj>YU39Xd!JQqy@~vTB<7Ga~k%b(t7$kBdy>&zom%Gpmo0WP!+ z8nCPvJP@eMg13uj63^O#1zX*1v97%A3>LIP?9yzjnl$0h-o6VksZ?>rJgp$-K3o2} zxL&{O{Qwyes5wC*95d=DVCy?C-WhGapi&_hcHcjXx}yB-Dp{*FH^%=n%A|WVlI-!z zl&kL_HZq&LWi_8ly=1;B=Sq%ylDnTfn|mJSB&*k)RS9h^2r!cLZ{d740u6n8s9o-! z>rQ<3(dZ#wh%f>Zecc%{S9co5j6&EseAGT3#e6uqt6ktdu2u;DK)Q~i-NF|^fszao zcH;3$f&tWro?8zavYe>ssJMFvlk_tP40<;E6mN$d!m@(!V(A06Vi$W1KFvu^{O9XL z-zJu@t-B=i@8Er9+`FU+QnBM^L(L_FHue3{ZVZwMG_L;XE#Nf7;qMIVVK01^Pxc(UU^8JakaDmYIy(~ZS$#Rm8>3jdJu(uwh+?uSa>;uRNmQ|Wu}3~t?N2tEa{#J z77)SoI8&ygcM=WJJigbQ&k=+Voo0mfJAnU{m3H9 zihb3MHolI;BX~rAeuLVi5*TQB_{ z{Uens%4tIyy> zc>j3j!A19!+Nrs6YU3`$GQQZdkEE=ky97o+ds)NRP=_h`f!#+7dGuHCqX_vt-*d$y z?R2axKf6ncUh1Ax2NCmBMkq%M+OU~}TI;opfd|9GZg(aGA)8>UzHqHRLlI4+vX`c! zCdvHNDMnDUu`gFIuGa1}xMqdqz;4HZd+o_*C%d?#t^G!wD{FjdnoM8Y!nFUw)>%81 z8^oWRS)CGGO<&{0oK8}7KfMSHaBY-D2neY^6}HPX*H~sxfo$) z_R+E2@%gA&Nz9}Y*pn%ougLy7%nBaIv>0L-SiClV(#V*kg%&zYB(C{$_-H#Aqp|+r z`oPhQV8=e?aPP~_;cfj=`*4lZQt;-X7dN!anjKpp?bGWd3##5l=vi^!^2W5y+mW*Q z3~dO%h4_aNh4NnW1&!!dm*eX~_r006qv@B`73sW&h6iV1xm%!r>!GUA2Z%tU19F&# z*-;wW>E_Gg>PNo87s;f(gPK~TlqE{=A=k|>&a>%z%^JeHja$_?x1OHQYHUC>#hs-w z>Asn?|AyAxTaMoR zsq5-lS$M0!MexdExUItIUyn zgze845eJW<)W6PnQ>b%E;_YH!4SJJ3x3%mZGSG;2e*e#UERiGm&O=Dq8Wa4I89&G= zCgASm?y3|u{?EqwD}bxyB~pgSK!7^ zMzl(8XufLP(y}c0-2#a@f{u)rjOsfUhk!@&%dOARhl8$k5f8FBtm`8udNaJQdFM^` zGjtgj-(rtyuugZtf)WS(Cn^!Kmo+Sj^N8FyS^ZRSY|HLUe!!L5k#S^BDb_fmC~zp& zRf6>C_QWr|Y=J}hFA_k>E3J`@1C2W@nt~L%j}=02FuPB)F-6R=Y)D6LJOP9xUzDnm zhYt!&V93DVK6;Cl z)cd9h>}+0#X!iDaGwb-%uiJ1tnmcfRy*+8`n+__(rM%| zO5lrP4?jkmKeku)o>LS#4=58LEVIW{tjKX{B{NE!uVT`sSzGPqx!PtenDQ=Qo!gsx z9hX~`y=Fhuu7Ni(yw1v}WxYPy`u2IMB|MB;wQYL&%WE5x`1UmJSofLm;KW>)qG%@o z(dQ>nLP=@Qlk3z>U109Xmpw9EV~g~tO4(G5@!7QzUO26AOTb=+7m-m^kStxCX63UE zdA&5S-Bnv!(Vod`=bh_e*+S;Y^2DuYua)O(ohVoo$56Ly!q89q^N-buREba)VBd?TOj;Dwm3a`;`{bQ+U!AVIm%o|}98HFd#`%P*7w~{u`{@jh9X?!-G*S-QHC~$0p(5KFh(EoBqHFxr^o|!M4Yx^7 zN~;|0Mmn0c!hsn(Fqz3P>)Vn_PXvQr&3G$L!M9e@jEqJaOq|At073VDStF-2V9s|@ z`OzX2n*G|Do|9vJg;FirHkXtnY%N*2tT+?axWravGm(`jizu@T31zn|YtuLmO3v^4 zn0VMWrbY@M|Lqx6!L8^{`v;%TncwWvzpT5r#X4A`p#G&tB;uV}+90`_XDZ;ZVJxS9)$VpA* z5$c!ImalQtzzi}l-+1acO2=U->o1#kM-tR;(tM`<@=}jkwb`6nfuoqW$KwQbM(Of< zV;RPNILZXZjhXQ>TNz^|vx&IrAdf+B$;U(vr48_ak6$7w8jkA z-DT87x}1xH(_%?{ont&-|0sfcIiRKG)+nF%!@ze6R)GR;wv1!c6=O}^H_l> zb_+=l+xy?YSa1TL7h&s9K9>fVMo7v9;;^k;*(foEsDacXi@7(Z>`z1Juvp)Rj3kmR z(knv8#jl$0pRL=eEn}Og6@`|GXw0Wg8}d6f&;wF1=zfwNf&DcFa{;59D5NcWk8GVN zD@~|@f#}W#4NmC|4_yT#+w_BW8_l8x&BC^{^j|djaO}JQ-g_5roXAg=nKf>IxOcjr zPm^=$lOf2W*R`VWFs3;iai-H^|G3W%R7`%3>hRndI7_m!S_{^9J(kSYw0|mpIU&~g z%}i&NVWwsXIsQ+okqhu?o%+!^d12Q97H!HvD0}HLRrVr*#`FCAATSAUDVh?;PP6$w z!86A3CstcT;c>Wre}Prx@B6mzN3cpZFb1qjyqn_uD5P@TL$0G|DE{glY49866YqtH zLOT1la?cuxTL#B3=eWq{D4wk_J`tn^Pg%ljM8RqT2sr0ZudZY8vT1T;gSBkGp>(;Y zJ^H?Bg>DPjl0M1cqpI4k^}yNI*e1AEdD`=g{z!Hr+jif&q<4DrGh`&ST;Ex)Kqe)L z&lG)DtabRS`*>B-_`d12og(*28>xkV+aRvUU^JTggGcV2^fISwASQp9OsF+YXmt3^ zP-NNR%Y(?Lxq}j=){BkreeMOY_ni%N%4JV*X)NV&ZFx{o zOVUa}1Dai1e9l|9yY@X;P>2+Up<;xU*sj#yOKPuwdecrF)qeJLYO6^WonmN_&u@0Fy7(g2`q5As9dK_TlZFFPc0kvihWlUvpSLdXz+XI^!4I$V1~*31Np((hvp;ArSeD2bQQrS9ZVMqi#Z#7DG$4; zbiP_y;V;jZU%pyo__k;zUPl{v2Q$Rj>1f_%;klx&?{hCf2Q=xvi*j_Vnhy?$PXq z-flcvbezG+r>YNztnBiueO{rSb7bY*es#w9s7h>W-i&B5GXDMhI!xRrSh&!UY1g87x2D|0N}U+3@JKZV=+y>$nwHeiP_#4a9|2%5O7IvV72iRKrOKZrc}t&75oGMjAivNM6XP)aI}L4DHAp4a z{B5)K2bp5j{4?t?)<;5yt%Oyd0zwuojauh%oo4gUAj-W@*CvalTW=6$06`R93l$Oo z9C4`tKkFadqe>zYD4ONBi5Krvam57oljxO1gi$zN-gysJkmjM$UTJjbSP83+D$!tFbaJ3gr--= zw2mwu6{a+jQrg>X4t0y@X}LN#+RNJ zg#=OVKn2n+2>MCT6M!iB%%$re&M|L^iiB8k`lJG4H5q;u5DgEaP>IUKa*u*V6i|3I zq1F%IMP35KP{M0}sEmgb5_4>a5Z7@5Q_aE@MpML=Z-~*z#P# z2sTBC8*BS@7=VX^BX_XU#KqS|uOuN%=ws;A2N=~fED>1|%lW}R_L^Bi0RS&=93|R+#NXG5SApamt_F3O-sIXK;#igwhO;cE{L zqBNX3mXxK~OLCYEP(skfZT%s}7H25JEv5c4W-kL^DkXmr!Yea~+O_Z@mI?wdj)wsL zWvopB!4XeD08bD6N$FW^QU8hTHq^XK@iLyv0GyDl-Gi@w(L8ztA|l-YDLAwc2QGxM zP!ytSGkV#V`;ga8)BMXQ2(O=e1pPH)v=}|F%n&M#f_O z0YN2%3jt(|KU{>88*q;StC7I|AUvDh^J;DYe^?W{-}-{*Q`g|B1#~0MWz2T`t1Nc| zr4fXu(1a}D6yL+dUm4*=b&lB~@D>u2s}Sd+bhLVRbB`wMot$(kiZCbYE~z@~%YGu6 zA(VV>CT%Lh#??K9c#}v7H}hW(iQ6lM-PFcujozs%rHnq%$mfU871RMBpL!xTwm$%_Ip8%asg!HDT{tlHiYC z{<&rsBk93`lTLV4C>hS7C9rFLGgUpK#Zh#_+F7pR<(C{cx0&d|D zL}Ka1aiC(J#4sLF2BK_JONO8Q|H7N80-aXlqo#&bP$^MG;Fu)@Z5r5!Hu>3zilbzc zbeiYXXEON7&Z{+~TqRc6z1Tq9+bicf-V7&q!-hSaF>)HFBh84D<= zNZ_cGv5#_;?0B)*Vn{!rFm_37Uf~_h(gS()1Ta9tzYYqk5<{h-`~~w5vNjC{f&z-N zLKbx9>2qiy%?lJF9lWd56(|;n+^u_aDkTTYV%bw;1@CBdbTdbhQV&%fgD8cL$Apge!X>x{p{`ajUYgAWcl4bx#=j$j zcxxBn_%F0m74lX3*~$1_Ws^SgwErpZY4%2I{z}h_^2)o$Ys3A||1U&HMmJkgQodiz zuj@Sp6iVR%QAM}ZdQvUu>8R2$%3tCx$-tYvlQ#+!=UV$592BHHT26GR&PYw2jMGBt zF@t8N8U!|S6SddQjYwg&UMVaAbfO`ap>%dcXgeaj@c2ourXn`(B}`QKI3ezy4;85O z;Tw^NGT1@DNn(PC%~6yT5{~yU`8Ww5xUBv8{JgZM3{n8n2!j&5R+on!9 zV^9~u;9(5d%jaGK1q!*_c`RY)>KLO@8^z>hUAD~Qp|GgsnQ@!<6n8NwVFo&(<#@yJ z;R7m3+Z=Fgu-h2CrVc)g_`RK4=^_Z%FD13YZZq=7K@*|RdOM|)_?Oa73gOA^&Ot#4 zV1NFP_By#SDMHqbeEa1Z^ycEMZBrKYRzs)%9FsdX?Ii&V3ksq<@TxOUjOOu1M@2;y zef5KRv8xHdNz(R!jMQB_JrXXYvtr2d51zn`v@%jWyA0Zl>$?Iu!;Ju0JM#Wff)6OK z4Y)k!sHe|7M1MI=m3|XGt!M~(kKd-uJA39a^3SXMUS47bOG#7Ete$xsqxGpGsEpaV z&A7$J#28xFg&sv}(3^?VGuC-(zo=vn&G8u};y$xC)(fD~HQHkK*YFDKA|fII0K>0u z>i?2ky5S0bUE-@rm@>8hb-!1ZY~tS>ksVr$Ef;0%6fh+1KZcE_Yl<$yFd52VubYz) zrzBH{yBW!tpp1>SwnW-&{4Z|{X~BYd1RJX()OO!v=0D0|zgY4Q(2IZ0gQzjE@9Kwk z(aX5s(u)J2LNATPf135u7*~>!peNEcZ2K?G+JrwYN5@kE$g!1nwg-iOexZpgLGj5y z$N84C)tb*JE=34DvmZ=_V=H`VOVJxKO))!4u~HAm1W{Jo82p!OS9p(ol{I*H$?fFo ztG}&&<4*5W?W+kBsry}*Yg#1m9cva7f0AEjrx)gMWg4nF@@^*pKCr+ zdsT1BtKyF>CVB8SO93rQ+?1{m1$$~JVKDDVm9nsp3As5&lyW3rGvob)MUi-k0xUN0 z7J-aP$tWx9Bo~Ml$y8T4McA`E3Y?CF7BSUKwvQWQ9I>Mhz6a&Bpa>(r#;>Bt1@BnqMu$ytaXbRb3e!fX5WHL?2^0`fQEx9PKG-oRnW$$}=&Pjomv@ww<3i4I7hvps zNr)EfsBsiR9&kAt&}2HeOA3|}pQWI5>Wg92rB%lEmV)STtP&GMsfumK?%t}AnXInX zzqPuQ6jvpuEHF%lPY6^ML&gb^K?1S)ONl2?QkwU(awxGYW0y;zq;O1<;>N0q@#7f3 z#i&iDVl0MaP>Zn;IfbFigK}6=9T`J(vZv#rMPfCx?Fyz5+KbEDLO*^8S=$)!vXA3A zBhcW3Hh%wnK9ynlTE9=BZmmUzGgQT#asEch90CUAh@kq@yiP3oln-o90DLehBBIqS zhA7kYjuM$;%4&jgxQ1Th8_)LHRu&KVA256jwXE(0D7~_X>W~r*NUazmK_fOsXdM!! z0m{)vWpfVs-s6-AJAgw7>B!XLB0Qgzw1L5PDs@}5^ zi7snsC;!X?#>E5FSvA>2!4SjaA(02*H?|^gatvz?5KtS{5wQoK zW0na&wmPUY4o+5eT^ZX*7Lvj?i=oo0Dt3fpyobSo{DBo8yCWzI@inuMnw-DnkTN`=3a_6QLv1Wu;`-241Aip&dvKU0`~yssk4T@E9KoD~2AQUR*0 zN^p#hAW&DBW78_sSt(5gtfmk#Vy>Cxkq$+3F*Y3XrvYsiLtN>4cdKv+ZdAp_aJS4c zxO>b5@TcyA8hLxh95)rk@dPRDx*WoZl(V3gej*!ja7jfxMZl6Qzz^dhe0qWshyp!A zIgw=~8U&O>iU!uea0!-yf^wdQlppRh*-FxjUJnO=a*)yf4sxSXQ4U%*Ve&{dkK_+g zeEc_kou3o?FnGb@U&JWuOb2lY2-U>2@cyDmrQ|Jv%+n)`6FEddV?a3qXkhdZZNHj4 z=t1m14JlG82BPKvLjlq&ftWLhl@nP*9YXXmd*-KA_$*BJ($BrJ$T5G+_;l zFNiYbpd9E6isqsh6q+bQ`JkM5G%%tlevw}ZaJU$t#;zJBT>{F3Zu{^2#CoPIP%nx1csa;3ogG61(pBN5AL3B;JOcZ|pmQ>F=& zv-w}2wkn`S&Hwt8yjlwUSov3-@#$ZcLev@2PfLIFXSu2~(QlPNJQ#cNF*G)6Dxe2V zBDyb3roS$MbTj^Ca)?X^DCalYzlmsE1ug3QZz6sw12$Lvort@nU7F$v#(%}RlozK4 zTGUpv(+;zV_`DAn$X*3K=n&EEazJFkC@lgqA?-Oajayjufj~In)l0hJyg#dFeV@}f1A#OLjH@`IOA=Epxv%g%vvKqEiz zn`_u|(kHNn#y~k7=>G!usyt`7mN0RooG0{y6+V7$-{9vcL?)F|hye3TZBh;6?jKP4 zd6CfwBlE0;av*U%fYTz2m)@=vQo@YDOlZj_d8Uq?t^?6!-*Jk1RRJ7s05lm7;9C-xlMuh8@Lp1Jn{GqSPhSN6MxvuI9kT znPlV`b+iovMxZ;=1b%bIbHD9d~%6C*JE!X*cfRtgEJh&8r8|VO=#28Lt4s zw*VSVJ84QVqlU!b2bQtk7>b()Qksvk+9)Y%V!y06n~k#=m5em8*$m$s!vD)Npma?KU*8W6NopAuEx~dhSi3KTNYPN7$ z(J$8iab8z}my#LS{KhC_dX8ae_>4J97~m$Gbm8efsO4KxsWY7Y6+&di^T_vhIBF_wWd(UmqBva(9dj$ z2RdEF-%`7jLHgOmUW`>}YXDk8W+KN@`ht_JCP!rO1!q+_7NS54 z(occG3HWMC{e!%c<3_e-;$q(@S>2jw3hK)sF9q{J!Q#byE7B;h${_6=f6bRm^p_TPp9M-9#|CLq zfV$Xg;;pwB4V``2_~77>Mw%XG$0kehFFBNUeGZJeLfY87mjAg|UL8<-`aky?tOI&X zGX1DTd6&EV%_>W&4v01d&|uZ|mz8A9A$s#qy^`yIpy~hA>wP(-g7dF>35YsqW9M6; z=(29Wf6G#T`77fub;QJr@u#BAi@gyK3Fj(S;Dht=!3iOM6G~NUZR|)Z2#BaetgjG% zD(vN&`~$){mLL8Id1{5fMuk07Cb09~$@nK^snM;CVta0lVdtFI8pn;7%oy&)@Z;~H zOxHg{xN47j6L<|36?oz6To3_T>glcQowzff;D%XB73jXdzD3=y zfMoYCr(Q@ubNzAu!?9yQ_b9ts7SlcBzo4Gea6^rl74Qw%#?hbm@H1YHXwW! zpY;u6M3KLLANkCF)X{xoO8cnM?~8w9u&qrV|75e>5p{2{*^Cwcq|yHj+B3)@#!?xV zUy6R~%_}-?G~HrFY+7d(!@=dBd2OG9L}W|66Y(yM?r~)Vw?lx#bwBO@U-gF3KBg83 zOQa(4nmlJoKNncVOce-^FUFfpcl_^55@!|fx#a^RxZeC<01pQ6`Lf2>|4#ZhO@oX! z4RXdhvPu7@X^_#TAwyS-O*uDZGg>sW8H=SRy_=>Xi#83}jD-f1{!P=6MVp3f#`MWa z|E6ikqD@0KV}i(}e|!1k*MG*luu1=>X~?2YLpEbHchbLU8nS59kj)qene=a(hAi4N zWEDOJFzMek4Oz5l$ZGs)f6~8c8nS59kfYD_Q_jtA8~teu^Z|a#wfR}2ziNp-s86{z zzi9Lat$|NwG*``` z*w>psn5?horz8}Y04=Ht5cvw6kXy8zk*OOs&f5-bDY*Y@1vZ~ENg8+&f?qdP|f zyV~BInu9u?)~9A}P}Fu5kUhDyj!7`znd6HKbNuh`{?%Nbjx?ZISI8$;z4OOWFukj? zw1LE~M)23{KJFh(&W`F<|DJ8gzehkz12R|Xxbz@%>}3yr_UZkD+8%ve&2F>ZU90z} z)17RK@Ub}^`{7-!e|xoQ-rZkcx9MIbja_{2AC}vr56LE5nq*7I`1Sq6VtZVCt5Ymo zip5{vzJ2pbE^Lzjj7PfNwLki9eWQ7I?FDJ($2)exMC}tVJ6<$)pKcM-4Ki?l)a=h! zcWU33^!BH?ZMevF!jDInFcP0}w`@}__if#^3G&KhqBpU z>Oh!e2F@i|yfA;h?6EGln*)#OQj7`2I_i9XbJMn1`o7cbPKQK(;;AFhxYu|9ZHfJS zQg`da`u3Q@kJHBB5sxu+o2c38dn!*IBpU;VeqyY)I_~cemkp2Vry7$6UO14t*M?Bq@U?{wP=9bzbccmusC^|7~MjZ`wxnZhtt%`G83m2jZW` zb9%UIUb(xRDUQueJc`MLmi>Bvd)qdd-+sPpuYfLHiyzjz>-+U}vv~3P+aJDMIMX9b z-|TPK+Z`4FO>1MgXbx>FywF$P<5-%$x3>4SyZh4-Lyio3dDl1M#rNhci{0K~G@s5w zzJCMd$^DBFdWkLOm)-Vi+iVs;I@Z^3zGr!d_E&B&8cAwdKkEZft`5z0^BJUcNgYKW zfGEe)p}iYp7@F3VK67Lml&oBjs8lgX>KBRq5`;JKGsKqZyrbErOBu`t!FJZ_TwZ?)|BrbG6_Ne+e+8wXtVfcd%WDY7x%%m#cj8^kJ&Ug!zW7=WKF-{wU_+1?WCCdh_9sB6!V;j?b`*76$ddF@1jIg_Wu>Wrw_YC^D zaJ=Isal9Nj{??^gkTi>antrsQkMh2K{(59!-pA7V_{;uNTg02i<;}JYW^uf0F7a|5BTf*X17?kKjLrqjXB|e z&c!$THn^YKr>AeV$wYAXcbm~L&Fr|G6BCMVMcXIgwstP;;R$g240@~Me;V0)37K{W z`p~vjZr1v+x<1@p`s#G|gfbjFF15#H-{XZpX`83s$$ns`)8iV!r8a!~_J_W=``r^8 j3hLmO@W0N;)nR`-)ll5`Qa%x^UG&l4_0j(y232~`{c5l` literal 0 HcmV?d00001 diff --git a/docs/versions.yaml b/docs/versions.yaml index 7d4f21e62992b..f5069d63539f8 100644 --- a/docs/versions.yaml +++ b/docs/versions.yaml @@ -31,3 +31,4 @@ "1.35": 1.35.10 "1.36": 1.36.6 "1.37": 1.37.2 +"1.38": 1.38.0 From 282f056b1fc801743040308dcff192b776a63984 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 24 Apr 2026 12:34:10 +0100 Subject: [PATCH 132/750] repo: Update/cleanup RELEASES.md (#44642) Signed-off-by: Ryan Northey --- RELEASES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 6e1790e9c3bc3..454d384fca80f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -84,6 +84,7 @@ actual mechanics of the release itself. | 2025 Q4 | Ryan Northey ([phlax](https://github.com/phlax)) | Boteng Yao ([botengyao](https://github.com/botengyao)) | | 2026 Q1 | Ryan Northey ([phlax](https://github.com/phlax)) | Boteng Yao ([botengyao](https://github.com/botengyao)) | | 2026 Q2 | Ryan Northey ([phlax](https://github.com/phlax)) | Boteng Yao ([botengyao](https://github.com/botengyao)) | +| 2026 Q3 | Ryan Northey ([phlax](https://github.com/phlax)) | Boteng Yao ([botengyao](https://github.com/botengyao)) | ## Major release schedule @@ -119,7 +120,8 @@ deadline of 3 weeks. | 1.35.0 | 2025/07/15 | 2025/07/23 | 8 days | 2026/07/23 | | 1.36.0 | 2025/10/14 | 2025/10/14 | 0 days | 2026/10/14 | | 1.37.0 | 2026/01/13 | 2026/01/13 | 0 days | 2027/01/13 | -| 1.38.0 | 2026/04/14 | | | | +| 1.38.0 | 2026/04/14 | 2026/04/23 | +9 days | 2027/04/23 | +| 1.39.0 | 2026/07/14 | | | | ### Cutting a major release @@ -168,7 +170,7 @@ Security releases are published on a 3-monthly cycle, around the mid point betwe | 2025 Q2 | 2025/06/03 | -- | -- | | 2025 Q3 | 2025/09/02 | 2025/09/03 | 1 day | | 2025 Q4 | 2025/12/02 | 2025/12/03 | 1 day | -| 2026 Q1 | 2026/03/03 | 2026/01/10 | 7 days | +| 2026 Q1 | 2026/03/03 | 2026/03/10 | 7 days | | 2026 Q2 | 2026/06/02 | | | NOTE: Zero-day vulnerabilities, and upstream vulnerabilities disclosed to us under embargo, may necessitate an emergency release with little or no warning. From b3966ccedf8f6941a80632c9cc499df1e496baab Mon Sep 17 00:00:00 2001 From: code Date: Fri, 24 Apr 2026 21:53:13 +0800 Subject: [PATCH 133/750] make target in the root directory as ignored dir (#44638) --- .bazelignore | 1 + .gitignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.bazelignore b/.bazelignore index 096b3dab21a68..778192d108514 100644 --- a/.bazelignore +++ b/.bazelignore @@ -6,3 +6,4 @@ mobile tools/dev/src .project envoy-filter-example +target diff --git a/.gitignore b/.gitignore index fb40cbde2890d..06dcce55c4b87 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ node_modules # Rust **/rust/**/target +target ############################# # OS/editor cruft From bba106b4222e4e5f1231c25d498045cf38554381 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 24 Apr 2026 10:52:22 -0400 Subject: [PATCH 134/750] cache_v2: guard against nullptr dereference on upstream reset (#44636) Commit Message: cache_v2: guard against nullptr dereference on upstream reset Additional Description: There is a potential issue when an upstream connection resets when using cache_v2. The code https://github.com/envoyproxy/envoy/blob/ac8ea19f77a183dac1ee31dcc4b6f2f345765377/source/extensions/filters/http/cache_v2/upstream_request_impl.cc#L197 calls with a nullptr which causes a segfault. Risk Level: Low Testing: Manually verified that the issue no longer occurs by simulating upstream disconnects, as well as simulating the load-test scenario that had produced the crash in the past. Docs Changes: None Release Notes: None Platform Specific Features: None Signed-off-by: Ryan Daniels --- source/extensions/filters/http/cache_v2/cache_sessions_impl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/filters/http/cache_v2/cache_sessions_impl.cc b/source/extensions/filters/http/cache_v2/cache_sessions_impl.cc index 2923e8daae5a3..314865f265a8a 100644 --- a/source/extensions/filters/http/cache_v2/cache_sessions_impl.cc +++ b/source/extensions/filters/http/cache_v2/cache_sessions_impl.cc @@ -29,7 +29,7 @@ class UpstreamRequestWithCacheabilityReset : public HttpSource { [entry = std::move(entry_), cb = std::move(cb), cacheable_response_checker = std::move(cacheable_response_checker_)]( Http::ResponseHeaderMapPtr headers, EndStream end_stream) mutable { - if (cacheable_response_checker->isCacheableResponse(*headers)) { + if (headers != nullptr && cacheable_response_checker->isCacheableResponse(*headers)) { entry->clearUncacheableState(); } cb(std::move(headers), end_stream); From bbed9913191ae8ccf168228f2fe9621b9ec79112 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 24 Apr 2026 10:54:24 -0400 Subject: [PATCH 135/750] cache_v2: update the scope to use server lifetime (#44612) Commit Message: Updates the scope for cache_v2 stats to use the server lifetime for singletons Additional Description: The current implementation uses the default filterchain lifetime for stats when creating singleton objects for cache_v2 that persist across filterchain & listener updates. This leads to a segfault when incrementing stats after an XDS/other update causes the filterchain to be drained. Risk Level: low Testing: Tested manually with a minimal local setup using a golang-based XDS server which can trigger listener updates. The issue can be replicated by leveraging cache_v2 with a consistent stream of requests (cacheability is irrelevant), followed by an update which causes the filterchain to be drained. Docs Changes: None Release Notes: None Platform Specific Features: None --------- Signed-off-by: Ryan Daniels --- source/extensions/filters/http/cache_v2/cache_sessions_impl.h | 2 +- .../extensions/http/cache_v2/file_system_http_cache/config.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/extensions/filters/http/cache_v2/cache_sessions_impl.h b/source/extensions/filters/http/cache_v2/cache_sessions_impl.h index 4289a0652d31f..22f8904be76e7 100644 --- a/source/extensions/filters/http/cache_v2/cache_sessions_impl.h +++ b/source/extensions/filters/http/cache_v2/cache_sessions_impl.h @@ -275,7 +275,7 @@ class CacheSessionsImpl : public CacheSessions, CacheSessionsImpl(Server::Configuration::FactoryContext& context, std::unique_ptr cache) : time_source_(context.serverFactoryContext().timeSource()), cache_(std::move(cache)), - stats_(generateStats(context.scope(), cache_->cacheInfo().name_)) {} + stats_(generateStats(context.serverFactoryContext().scope(), cache_->cacheInfo().name_)) {} void lookup(ActiveLookupRequestPtr request, ActiveLookupResultCallback&& cb) override; CacheFilterStats& stats() const override { return *stats_; } diff --git a/source/extensions/http/cache_v2/file_system_http_cache/config.cc b/source/extensions/http/cache_v2/file_system_http_cache/config.cc index e45ef4fa5dcc5..8af134f7d6a64 100644 --- a/source/extensions/http/cache_v2/file_system_http_cache/config.cc +++ b/source/extensions/http/cache_v2/file_system_http_cache/config.cc @@ -64,7 +64,7 @@ class CacheSingleton : public Envoy::Singleton::Instance { async_file_manager_factory_->getAsyncFileManager(config.manager_config()); std::unique_ptr fs_cache = std::make_unique( singleton, cache_eviction_thread_, std::move(config), std::move(async_file_manager), - context.scope()); + context.serverFactoryContext().scope()); cache = CacheSessions::create(context, std::move(fs_cache)); caches_[key] = cache; } else { From 19c558dd62cb20fbf4ab6d0880f58be274944c99 Mon Sep 17 00:00:00 2001 From: Biren Roy Date: Fri, 24 Apr 2026 13:23:31 -0400 Subject: [PATCH 136/750] Makes `HttpMatchingData` a concrete class rather than an interface (#44596) Removes the separation of interface and implementation for `HttpMatchingData`. There are no interesting implementations of this interface other than `HttpMatchingDataImpl`. I will follow up with a PR to migrate all uses of `HttpMatchingDataImpl` to `HttpMatchingData`. Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: --------- Signed-off-by: Biren Roy --- envoy/http/filter.h | 48 +++++++++++++--- source/common/http/matching/data_impl.h | 56 ++----------------- .../cel_matcher/cel_matcher_test.cc | 14 ----- .../metadata/dyn_meta_matcher_test.cc | 6 -- 4 files changed, 44 insertions(+), 80 deletions(-) diff --git a/envoy/http/filter.h b/envoy/http/filter.h index ef4d2aa7da8e5..4c54d39b95b78 100644 --- a/envoy/http/filter.h +++ b/envoy/http/filter.h @@ -1226,18 +1226,43 @@ class StreamFilter : public virtual StreamDecoderFilter, public virtual StreamEn using StreamFilterSharedPtr = std::shared_ptr; -class HttpMatchingData { +class HttpMatchingData final { public: static absl::string_view name() { return "http"; } - virtual ~HttpMatchingData() = default; + explicit HttpMatchingData(const StreamInfo::StreamInfo& stream_info) + : stream_info_(stream_info) {} - virtual RequestHeaderMapOptConstRef requestHeaders() const PURE; - virtual RequestTrailerMapOptConstRef requestTrailers() const PURE; - virtual ResponseHeaderMapOptConstRef responseHeaders() const PURE; - virtual ResponseTrailerMapOptConstRef responseTrailers() const PURE; - virtual const StreamInfo::StreamInfo& streamInfo() const PURE; - virtual const Network::ConnectionInfoProvider& connectionInfoProvider() const PURE; + void onRequestHeaders(const RequestHeaderMap& request_headers) { + request_headers_ = &request_headers; + } + + void onRequestTrailers(const RequestTrailerMap& request_trailers) { + request_trailers_ = &request_trailers; + } + + void onResponseHeaders(const ResponseHeaderMap& response_headers) { + response_headers_ = &response_headers; + } + + void onResponseTrailers(const ResponseTrailerMap& response_trailers) { + response_trailers_ = &response_trailers; + } + + RequestHeaderMapOptConstRef requestHeaders() const { return makeOptRefFromPtr(request_headers_); } + RequestTrailerMapOptConstRef requestTrailers() const { + return makeOptRefFromPtr(request_trailers_); + } + ResponseHeaderMapOptConstRef responseHeaders() const { + return makeOptRefFromPtr(response_headers_); + } + ResponseTrailerMapOptConstRef responseTrailers() const { + return makeOptRefFromPtr(response_trailers_); + } + const StreamInfo::StreamInfo& streamInfo() const { return stream_info_; } + const Network::ConnectionInfoProvider& connectionInfoProvider() const { + return stream_info_.downstreamAddressProvider(); + } const StreamInfo::FilterState& filterState() const { return streamInfo().filterState(); } const envoy::config::core::v3::Metadata& metadata() const { @@ -1253,6 +1278,13 @@ class HttpMatchingData { } Ssl::ConnectionInfoConstSharedPtr ssl() const { return connectionInfoProvider().sslConnection(); } + +private: + const StreamInfo::StreamInfo& stream_info_; + const RequestHeaderMap* request_headers_{}; + const ResponseHeaderMap* response_headers_{}; + const RequestTrailerMap* request_trailers_{}; + const ResponseTrailerMap* response_trailers_{}; }; /** diff --git a/source/common/http/matching/data_impl.h b/source/common/http/matching/data_impl.h index dff568c6dd8ca..a9cfeed3718ea 100644 --- a/source/common/http/matching/data_impl.h +++ b/source/common/http/matching/data_impl.h @@ -16,59 +16,11 @@ namespace Matching { /** * Implementation of HttpMatchingData, providing HTTP specific data to * the match tree. + * + * This using declaration will be removed, once all references have been migrated to + * HttpMatchingData. */ -class HttpMatchingDataImpl : public HttpMatchingData { -public: - explicit HttpMatchingDataImpl(const StreamInfo::StreamInfo& stream_info) - : stream_info_(stream_info) {} - - static absl::string_view name() { return "http"; } - - void onRequestHeaders(const RequestHeaderMap& request_headers) { - request_headers_ = &request_headers; - } - - void onRequestTrailers(const RequestTrailerMap& request_trailers) { - request_trailers_ = &request_trailers; - } - - void onResponseHeaders(const ResponseHeaderMap& response_headers) { - response_headers_ = &response_headers; - } - - void onResponseTrailers(const ResponseTrailerMap& response_trailers) { - response_trailers_ = &response_trailers; - } - - RequestHeaderMapOptConstRef requestHeaders() const override { - return makeOptRefFromPtr(request_headers_); - } - - RequestTrailerMapOptConstRef requestTrailers() const override { - return makeOptRefFromPtr(request_trailers_); - } - - ResponseHeaderMapOptConstRef responseHeaders() const override { - return makeOptRefFromPtr(response_headers_); - } - - ResponseTrailerMapOptConstRef responseTrailers() const override { - return makeOptRefFromPtr(response_trailers_); - } - - const StreamInfo::StreamInfo& streamInfo() const override { return stream_info_; } - - const Network::ConnectionInfoProvider& connectionInfoProvider() const override { - return stream_info_.downstreamAddressProvider(); - } - -private: - const StreamInfo::StreamInfo& stream_info_; - const RequestHeaderMap* request_headers_{}; - const ResponseHeaderMap* response_headers_{}; - const RequestTrailerMap* request_trailers_{}; - const ResponseTrailerMap* response_trailers_{}; -}; +using HttpMatchingDataImpl = HttpMatchingData; struct HttpFilterActionContext { // Identify whether the filter is in downstream filter chain or upstream filter chain. diff --git a/test/extensions/matching/input_matchers/cel_matcher/cel_matcher_test.cc b/test/extensions/matching/input_matchers/cel_matcher/cel_matcher_test.cc index 74577d3822a65..5f6b5c10f9807 100644 --- a/test/extensions/matching/input_matchers/cel_matcher/cel_matcher_test.cc +++ b/test/extensions/matching/input_matchers/cel_matcher/cel_matcher_test.cc @@ -208,8 +208,6 @@ TEST_F(CelMatcherTest, CelMatcherRequestHeaderNotMatched) { TEST_F(CelMatcherTest, CelMatcherClusterMetadataMatched) { setUpstreamClusterMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), std::string(kMetadataValue)); - Envoy::Http::Matching::HttpMatchingDataImpl data = - Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); auto matcher_tree = buildMatcherTree(absl::StrFormat( UpstreamClusterMetadataCelString, kFilterNamespace, kMetadataKey, kMetadataValue)); EXPECT_THAT(matcher_tree->match(data_), HasStringAction("match!!")); @@ -218,8 +216,6 @@ TEST_F(CelMatcherTest, CelMatcherClusterMetadataMatched) { TEST_F(CelMatcherTest, CelMatcherClusterMetadataNotMatched) { setUpstreamClusterMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), "wrong_service"); - Envoy::Http::Matching::HttpMatchingDataImpl data = - Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); auto matcher_tree = buildMatcherTree(absl::StrFormat( UpstreamClusterMetadataCelString, kFilterNamespace, kMetadataKey, kMetadataValue)); @@ -229,8 +225,6 @@ TEST_F(CelMatcherTest, CelMatcherClusterMetadataNotMatched) { TEST_F(CelMatcherTest, CelMatcherRouteMetadataMatched) { setUpstreamRouteMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), std::string(kMetadataValue)); - Envoy::Http::Matching::HttpMatchingDataImpl data = - Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); auto matcher_tree = buildMatcherTree(absl::StrFormat( UpstreamRouteMetadataCelString, kFilterNamespace, kMetadataKey, kMetadataValue)); EXPECT_THAT(matcher_tree->match(data_), HasStringAction("match!!")); @@ -239,8 +233,6 @@ TEST_F(CelMatcherTest, CelMatcherRouteMetadataMatched) { TEST_F(CelMatcherTest, CelMatcherRouteMetadataNotMatched) { setUpstreamRouteMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), "wrong_service"); - Envoy::Http::Matching::HttpMatchingDataImpl data = - Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); auto matcher_tree = buildMatcherTree(absl::StrFormat( UpstreamClusterMetadataCelString, kFilterNamespace, kMetadataKey, kMetadataValue)); @@ -250,8 +242,6 @@ TEST_F(CelMatcherTest, CelMatcherRouteMetadataNotMatched) { TEST_F(CelMatcherTest, CelMatcherDynamicMetadataMatched) { setDynamicMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), std::string(kMetadataValue)); - Envoy::Http::Matching::HttpMatchingDataImpl data = - Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); auto matcher_tree = buildMatcherTree( absl::StrFormat(DynamicMetadataCelString, kFilterNamespace, kMetadataKey, kMetadataValue)); EXPECT_THAT(matcher_tree->match(data_), HasStringAction("match!!")); @@ -259,8 +249,6 @@ TEST_F(CelMatcherTest, CelMatcherDynamicMetadataMatched) { TEST_F(CelMatcherTest, CelMatcherDynamicMetadataNotMatched) { setDynamicMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), "wrong_service"); - Envoy::Http::Matching::HttpMatchingDataImpl data = - Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); auto matcher_tree = buildMatcherTree( absl::StrFormat(DynamicMetadataCelString, kFilterNamespace, kMetadataKey, kMetadataValue)); @@ -274,8 +262,6 @@ TEST_F(CelMatcherTest, CelMatcherTypedDynamicMetadataMatched) { typed_metadata.PackFrom(pipe); stream_info_.metadata_.mutable_typed_filter_metadata()->insert( {std::string(kFilterNamespace), typed_metadata}); - Envoy::Http::Matching::HttpMatchingDataImpl data = - Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); auto matcher_tree = buildMatcherTree(absl::StrFormat( TypedDynamicMetadataCelString, kFilterNamespace, "path", "/foo/bar/baz.fads")); EXPECT_THAT(matcher_tree->match(data_), HasStringAction("match!!")); diff --git a/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc b/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc index eaefc8ee58f96..a7a30744b72d1 100644 --- a/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc +++ b/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc @@ -108,8 +108,6 @@ class MetadataMatcherTest : public ::testing::Test { TEST_F(MetadataMatcherTest, DynamicMetadataMatched) { setDynamicMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), std::string(kMetadataValue)); - Envoy::Http::Matching::HttpMatchingDataImpl data = - Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); auto matcher_tree = buildMatcherTree(); EXPECT_THAT(matcher_tree->match(data_), HasStringAction("match!!")); @@ -117,8 +115,6 @@ TEST_F(MetadataMatcherTest, DynamicMetadataMatched) { TEST_F(MetadataMatcherTest, DynamicMetadataNotMatched) { setDynamicMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), "wrong_service"); - Envoy::Http::Matching::HttpMatchingDataImpl data = - Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); auto matcher_tree = buildMatcherTree(); EXPECT_THAT(matcher_tree->match(data_), HasNoMatch()); @@ -126,8 +122,6 @@ TEST_F(MetadataMatcherTest, DynamicMetadataNotMatched) { TEST_F(MetadataMatcherTest, DynamicMetadataNotMatchedWithInvert) { setDynamicMetadata(std::string(kFilterNamespace), std::string(kMetadataKey), "wrong_service"); - Envoy::Http::Matching::HttpMatchingDataImpl data = - Envoy::Http::Matching::HttpMatchingDataImpl(stream_info_); auto matcher_tree = buildMatcherTree(true); // The match was completed, no match found but the invert flag was set. From 6ca32d3975607ebdb59697b69a8792b23ead7b36 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 24 Apr 2026 20:37:40 +0100 Subject: [PATCH 137/750] repo: Add @jwendell to security team (#44653) Signed-off-by: Ryan Northey --- OWNERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/OWNERS.md b/OWNERS.md index b0a0ef8cf9e0f..d2df8fd6b85ce 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -104,6 +104,7 @@ without further review. * Reuben Tanner ([reubent-goog](https://github.com/reubent-goog)) (reubent@google.com) * Paul Ogilby ([paul-r-gall](https://github.com/paul-r-gall)) (pgal@google.com) * Peng Gao ([penguingao](https://github.com/penguingao)) (pengg@google.com) +* Jonh Wendell ([jwendell](https://github.com/jwendell)) (jwendell@redhat.com) # Emeritus maintainers From b218c0b16ec06263c8a6fb1d21c7ce3a2a2606cb Mon Sep 17 00:00:00 2001 From: ovadiagal <102482702+ovadiagal@users.noreply.github.com> Date: Fri, 24 Apr 2026 17:16:03 -0400 Subject: [PATCH 138/750] dym: use fn_addr_eq for function pointer comparison (#44654) Small fix to rust dynamic modules SDK. Came across this issue when trying to bump [dynamic-modules-examples to 1.38.0](https://github.com/envoyproxy/dynamic-modules-examples/pull/61): ``` error: function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique --> src/lib.rs:66:1 | 66 | / declare_all_init_functions!(init, 67 | | http: new_http_filter_config_fn, 68 | | network: new_network_filter_config_fn, 69 | | udp_listener: new_udp_listener_filter_config_fn 70 | | ); | |_^ | = note: the address of the same function can vary between different codegen units = note: furthermore, different functions could have the same address after being merged together = note: for more information visit = note: this error originates in the macro `envoy_proxy_dynamic_modules_rust_sdk::set_factory_once` which comes from the expansion of the macro `declare_all_init_functions` (in Nightly builds, run with -Z macro-backtrace for more info) ``` Commit Message: dym: use fn_addr_eq for function pointer comparison Additional Description: Risk Level: Low Testing: N/A Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Gal Ovadia --- source/extensions/dynamic_modules/sdk/rust/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs index 22955d0fec261..8cc9bb8b25355 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs @@ -378,7 +378,7 @@ macro_rules! envoy_log { macro_rules! set_factory_once { ($static:expr, $fn:expr, $name:literal) => { if let Err(new_val) = $static.set($fn) { - if *$static.get().unwrap() != new_val { + if !::std::ptr::fn_addr_eq(*$static.get().unwrap(), new_val) { $crate::envoy_log_critical!( "Duplicate factory registration for {}. A different module already registered this \ factory. Check dynamic_module_config for conflicting standalone and consolidated \ From fe746515871f06ba5b811714c9eeca8eaf582654 Mon Sep 17 00:00:00 2001 From: Rito Takeuchi Date: Sat, 25 Apr 2026 08:11:41 +0900 Subject: [PATCH 139/750] mysql_proxy: add SSL termination (#44091) Commit Message: add ability to use StartTLS transport socket to terminate SSL at Envoy and pass unencrypted traffic upstream to MySQL server Additional Description: see #44089 Risk Level: Medium Testing: Unit/Integ in this PR and E2E in [another repo](https://github.com/Licht-T/envoy-mysql-e2e) Docs Changes: add docs on Proto Release Notes: added to `changelogs/current.yaml` Platform Specific Features: N/A [Optional Fixes #44089 ] > You may use generative AI only if you fully understand the code. You need to disclose this usage in the PR description to ensure transparency. I hereby disclose the usage of generative AI for this PR. --------- Signed-off-by: Rito Takeuchi --- .../network/mysql_proxy/v3/mysql_proxy.proto | 29 + changelogs/current.yaml | 9 + .../mysql_proxy/filters/network/source/BUILD | 3 + .../filters/network/source/mysql_codec.h | 5 + .../network/source/mysql_codec_clogin.cc | 2 +- .../filters/network/source/mysql_config.cc | 4 +- .../filters/network/source/mysql_decoder.h | 25 +- .../network/source/mysql_decoder_impl.cc | 85 +- .../network/source/mysql_decoder_impl.h | 6 +- .../filters/network/source/mysql_filter.cc | 352 +++++- .../filters/network/source/mysql_filter.h | 44 +- .../filters/network/source/mysql_session.h | 17 +- .../filters/network/source/mysql_utils.cc | 25 + .../filters/network/source/mysql_utils.h | 39 + .../mysql_proxy/filters/network/test/BUILD | 28 + .../filters/network/test/mysql_clogin_test.cc | 77 -- .../filters/network/test/mysql_filter_test.cc | 1048 +++++++++++++++-- .../test/mysql_ssl_allow_test_config.yaml | 53 + .../test/mysql_ssl_disable_test_config.yaml | 40 + .../test/mysql_ssl_integration_test.cc | 908 ++++++++++++++ .../test/mysql_ssl_require_test_config.yaml | 53 + 21 files changed, 2646 insertions(+), 206 deletions(-) create mode 100644 contrib/mysql_proxy/filters/network/test/mysql_ssl_allow_test_config.yaml create mode 100644 contrib/mysql_proxy/filters/network/test/mysql_ssl_disable_test_config.yaml create mode 100644 contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc create mode 100644 contrib/mysql_proxy/filters/network/test/mysql_ssl_require_test_config.yaml diff --git a/api/contrib/envoy/extensions/filters/network/mysql_proxy/v3/mysql_proxy.proto b/api/contrib/envoy/extensions/filters/network/mysql_proxy/v3/mysql_proxy.proto index f3f2cefdc372d..8f89b70d48f98 100644 --- a/api/contrib/envoy/extensions/filters/network/mysql_proxy/v3/mysql_proxy.proto +++ b/api/contrib/envoy/extensions/filters/network/mysql_proxy/v3/mysql_proxy.proto @@ -20,6 +20,29 @@ message MySQLProxy { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.mysql_proxy.v1alpha1.MySQLProxy"; + // Downstream SSL operational modes. + enum SSLMode { + // Do not terminate SSL session initiated by a client. + // The MySQL proxy filter will pass all encrypted and unencrypted packets to the upstream server. + DISABLE = 0; + + // The MySQL proxy filter will terminate SSL session initiated by a client + // and close downstream connections that do not initiate SSL. + // The filter will mediate ``caching_sha2_password`` RSA authentication when + // the upstream MySQL server requires full authentication over the plaintext connection. + // The filter chain must use :ref:`starttls transport socket + // `. + REQUIRE = 1; + + // The MySQL proxy filter will accept downstream client's encryption settings. + // If the client wants to use clear-text, Envoy will not enforce SSL encryption. + // If the client wants to use encryption, Envoy will terminate SSL and mediate + // ``caching_sha2_password`` RSA authentication when needed. + // The filter chain must use :ref:`starttls transport socket + // `. + ALLOW = 2; + } + // The human readable prefix to use when emitting :ref:`statistics // `. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; @@ -27,4 +50,10 @@ message MySQLProxy { // [#not-implemented-hide:] The optional path to use for writing MySQL access logs. // If the access log field is empty, access logs will not be written. string access_log = 2; + + // Controls whether to terminate SSL sessions initiated by downstream clients. + // If enabled, the filter chain must use + // :ref:`starttls transport socket `. + // Defaults to ``DISABLE``. + SSLMode downstream_ssl = 3; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 84bf69e3abd2c..70519737897b3 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -18,5 +18,14 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: mysql_proxy + change: | + Added SSL termination support to the MySQL proxy filter with RSA-mediated ``caching_sha2_password`` + authentication. The filter can now terminate downstream TLS connections using the + :ref:`starttls transport socket ` + and transparently mediate MySQL 8.0+ ``caching_sha2_password`` full authentication by performing + RSA public key exchange on behalf of the client. Added a new + :ref:`downstream_ssl ` + config option with ``DISABLE``, ``REQUIRE``, and ``ALLOW`` modes. deprecated: diff --git a/contrib/mysql_proxy/filters/network/source/BUILD b/contrib/mysql_proxy/filters/network/source/BUILD index 5bb9bf73dc62a..90a93dd087164 100644 --- a/contrib/mysql_proxy/filters/network/source/BUILD +++ b/contrib/mysql_proxy/filters/network/source/BUILD @@ -27,8 +27,10 @@ envoy_cc_library( "//envoy/server:filter_config_interface", "//envoy/stats:stats_interface", "//envoy/stats:stats_macros", + "//source/common/crypto:utility_lib", "//source/common/network:filter_lib", "//source/extensions/filters/network:well_known_names", + "@envoy_api//contrib/envoy/extensions/filters/network/mysql_proxy/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) @@ -78,6 +80,7 @@ envoy_cc_library( name = "util_lib", srcs = ["mysql_utils.cc"], hdrs = ["mysql_utils.h"], + external_deps = ["ssl"], deps = [ ":codec_interface", "//source/common/buffer:buffer_lib", diff --git a/contrib/mysql_proxy/filters/network/source/mysql_codec.h b/contrib/mysql_proxy/filters/network/source/mysql_codec.h index d040ab3fb6dbd..af6b3cc425455 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_codec.h +++ b/contrib/mysql_proxy/filters/network/source/mysql_codec.h @@ -33,6 +33,10 @@ constexpr uint8_t MYSQL_RESP_MORE = 0x01; constexpr uint8_t MYSQL_RESP_AUTH_SWITCH = 0xfe; constexpr uint8_t MYSQL_RESP_ERR = 0xff; +constexpr uint8_t MYSQL_CACHINGSHA2_FAST_AUTH_SUCCESS = 0x03; +constexpr uint8_t MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED = 0x04; +constexpr uint8_t MYSQL_REQUEST_PUBLIC_KEY = 0x02; + constexpr uint8_t EOF_MARKER = 0xfe; constexpr uint8_t ERR_MARKER = 0xff; @@ -102,6 +106,7 @@ constexpr uint8_t LENENCODINT_3BYTES = 0xfd; constexpr uint8_t LENENCODINT_8BYTES = 0xfe; constexpr uint32_t DEFAULT_MAX_PACKET_SIZE = (1 << 24) - 1; // 16M-1 +constexpr uint32_t SSL_CONNECTION_REQUEST_PACKET_SIZE = 32; constexpr uint8_t MIN_PROTOCOL_VERSION = 10; constexpr char MYSQL_STR_END = '\0'; diff --git a/contrib/mysql_proxy/filters/network/source/mysql_codec_clogin.cc b/contrib/mysql_proxy/filters/network/source/mysql_codec_clogin.cc index a2b6e3664cb07..602575b6e9cf5 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_codec_clogin.cc +++ b/contrib/mysql_proxy/filters/network/source/mysql_codec_clogin.cc @@ -71,7 +71,7 @@ DecodeStatus ClientLogin::parseMessage(Buffer::Instance& buffer, uint32_t len) { return DecodeStatus::Failure; } setBaseClientCap(base_cap); - if (base_cap & CLIENT_SSL) { + if (len == SSL_CONNECTION_REQUEST_PACKET_SIZE && (base_cap & CLIENT_SSL)) { return parseResponseSsl(buffer); } if (base_cap & CLIENT_PROTOCOL_41) { diff --git a/contrib/mysql_proxy/filters/network/source/mysql_config.cc b/contrib/mysql_proxy/filters/network/source/mysql_config.cc index c5e3b32d66ec1..1292a14a81ce7 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_config.cc +++ b/contrib/mysql_proxy/filters/network/source/mysql_config.cc @@ -28,8 +28,8 @@ NetworkFilters::MySQLProxy::MySQLConfigFactory::createFilterFactoryFromProtoType const std::string stat_prefix = fmt::format("mysql.{}", proto_config.stat_prefix()); - MySQLFilterConfigSharedPtr filter_config( - std::make_shared(stat_prefix, context.scope())); + MySQLFilterConfigSharedPtr filter_config(std::make_shared( + stat_prefix, context.scope(), proto_config.downstream_ssl())); return [filter_config](Network::FilterManager& filter_manager) -> void { filter_manager.addFilter(std::make_shared(filter_config)); }; diff --git a/contrib/mysql_proxy/filters/network/source/mysql_decoder.h b/contrib/mysql_proxy/filters/network/source/mysql_decoder.h index 491cb93a49d26..60ad85c33c54d 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_decoder.h +++ b/contrib/mysql_proxy/filters/network/source/mysql_decoder.h @@ -7,6 +7,7 @@ #include "contrib/mysql_proxy/filters/network/source/mysql_codec_greeting.h" #include "contrib/mysql_proxy/filters/network/source/mysql_codec_switch_resp.h" #include "contrib/mysql_proxy/filters/network/source/mysql_session.h" +#include "contrib/mysql_proxy/filters/network/source/mysql_utils.h" namespace Envoy { namespace Extensions { @@ -21,14 +22,15 @@ class DecoderCallbacks { virtual ~DecoderCallbacks() = default; virtual void onProtocolError() PURE; - virtual void onNewMessage(MySQLSession::State) PURE; virtual void onServerGreeting(ServerGreeting&) PURE; - virtual void onClientLogin(ClientLogin&) PURE; + virtual void onClientLogin(ClientLogin&, MySQLSession::State) PURE; virtual void onClientLoginResponse(ClientLoginResponse&) PURE; virtual void onClientSwitchResponse(ClientSwitchResponse&) PURE; virtual void onMoreClientLoginResponse(ClientLoginResponse&) PURE; virtual void onCommand(Command&) PURE; virtual void onCommandResponse(CommandResponse&) PURE; + virtual void onAuthSwitchMoreClientData(std::unique_ptr data) PURE; + virtual bool onSSLRequest() PURE; }; /** @@ -38,16 +40,33 @@ class Decoder { public: virtual ~Decoder() = default; - virtual void onData(Buffer::Instance& data) PURE; + enum class Result { + ReadyForNext, // Decoder processed previous message and is ready for the next message. + Stopped // Received and processed message disrupts the current flow. Decoder stopped accepting + // data. This happens when decoder wants filter to perform some action, for example to + // call starttls transport socket to enable TLS. + }; + + struct PayloadMetadata { + uint8_t seq; + uint32_t len; + }; + + virtual Result onData(Buffer::Instance& data, bool is_upstream) PURE; virtual MySQLSession& getSession() PURE; const Extensions::Common::SQLUtils::SQLUtils::DecoderAttributes& getAttributes() const { return attributes_; } + const std::vector& getPayloadMetadataList() const { + return payload_metadata_list_; + } + protected: // Decoder attributes. Extensions::Common::SQLUtils::SQLUtils::DecoderAttributes attributes_; + std::vector payload_metadata_list_{}; }; using DecoderPtr = std::unique_ptr; diff --git a/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.cc b/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.cc index 36d28b7688093..7175985c33ffa 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.cc +++ b/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.cc @@ -11,8 +11,10 @@ namespace Extensions { namespace NetworkFilters { namespace MySQLProxy { -void DecoderImpl::parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t len) { - ENVOY_LOG(trace, "mysql_proxy: parsing message, seq {}, len {}", seq, len); +void DecoderImpl::parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t len, + bool is_upstream) { + ENVOY_LOG(trace, "mysql_proxy: parsing message, seq {}, len {}, is_upstream {}", seq, len, + is_upstream); // Run the MySQL state machine switch (session_.getState()) { case MySQLSession::State::Init: { @@ -27,14 +29,15 @@ void DecoderImpl::parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t // Process Client Handshake Response ClientLogin client_login{}; client_login.decode(message, seq, len); - if (client_login.isSSLRequest()) { + + if (len == SSL_CONNECTION_REQUEST_PACKET_SIZE && client_login.isSSLRequest()) { session_.setState(MySQLSession::State::SslPt); } else if (client_login.isResponse41()) { session_.setState(MySQLSession::State::ChallengeResp41); } else { session_.setState(MySQLSession::State::ChallengeResp320); } - callbacks_.onClientLogin(client_login); + callbacks_.onClientLogin(client_login, session_.getState()); break; } case MySQLSession::State::SslPt: @@ -48,6 +51,7 @@ void DecoderImpl::parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t session_.setState(MySQLSession::State::NotHandled); break; } + ENVOY_LOG(trace, "mysql_proxy: ChallengeResp resp_code is {}", resp_code); std::unique_ptr msg; MySQLSession::State state = MySQLSession::State::NotHandled; switch (resp_code) { @@ -55,7 +59,7 @@ void DecoderImpl::parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t msg = std::make_unique(); state = MySQLSession::State::Req; // reset seq# when entering the REQ state - session_.setExpectedSeq(MYSQL_REQUEST_PKT_NUM); + session_.resetSeq(); break; } case MYSQL_RESP_AUTH_SWITCH: { @@ -70,6 +74,7 @@ void DecoderImpl::parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t } case MYSQL_RESP_MORE: { msg = std::make_unique(); + state = MySQLSession::State::AuthSwitchMore; break; } default: @@ -92,7 +97,12 @@ void DecoderImpl::parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t case MySQLSession::State::AuthSwitchMore: { uint8_t resp_code; - if (BufferHelper::peekUint8(message, resp_code) != DecodeStatus::Success) { + if (is_upstream) { + std::unique_ptr secure_data; + BufferHelper::readSecureBytes(message, len, secure_data); + callbacks_.onAuthSwitchMoreClientData(std::move(secure_data)); + break; + } else if (BufferHelper::peekUint8(message, resp_code) != DecodeStatus::Success) { session_.setState(MySQLSession::State::NotHandled); break; } @@ -102,19 +112,19 @@ void DecoderImpl::parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t case MYSQL_RESP_OK: { msg = std::make_unique(); state = MySQLSession::State::Req; - session_.setExpectedSeq(MYSQL_REQUEST_PKT_NUM); + session_.resetSeq(); break; } case MYSQL_RESP_MORE: { msg = std::make_unique(); - state = MySQLSession::State::AuthSwitchResp; + state = MySQLSession::State::AuthSwitchMore; break; } case MYSQL_RESP_ERR: { msg = std::make_unique(); // stop parsing auth req/response, attempt to resync in command state state = MySQLSession::State::Resync; - session_.setExpectedSeq(MYSQL_REQUEST_PKT_NUM); + session_.resetSeq(); break; } case MYSQL_RESP_AUTH_SWITCH: { @@ -165,59 +175,84 @@ void DecoderImpl::parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t static_cast(session_.getState())); } -bool DecoderImpl::decode(Buffer::Instance& data) { +bool DecoderImpl::decode(Buffer::Instance& data, bool is_upstream) { ENVOY_LOG(trace, "mysql_proxy: decoding {} bytes", data.length()); uint32_t len = 0; uint8_t seq = 0; + bool return_without_parse = false; + bool result = true; + + auto current_state = session_.getState(); // ignore ssl message - if (session_.getState() == MySQLSession::State::SslPt) { + if (current_state == MySQLSession::State::SslPt) { data.drain(data.length()); - return true; + return result; } if (BufferHelper::peekHdr(data, len, seq) != DecodeStatus::Success) { throw EnvoyException("error parsing mysql packet header"); } ENVOY_LOG(trace, "mysql_proxy: seq {}, len {}", seq, len); + // If message is split over multiple packets, hold off until the entire message is available. // Consider the size of the header here as it's not consumed yet. if (sizeof(uint32_t) + len > data.length()) { - return false; + return_without_parse = true; + result = false; } - BufferHelper::consumeHdr(data); // Consume the header once the message is fully available. - callbacks_.onNewMessage(session_.getState()); - // Ignore duplicate and out-of-sync packets. - if (seq != session_.getExpectedSeq()) { + if (seq != session_.getExpectedSeq(is_upstream)) { // case when server response is over, and client send req if (session_.getState() == MySQLSession::State::ReqResp && seq == MYSQL_REQUEST_PKT_NUM) { - session_.setExpectedSeq(MYSQL_REQUEST_PKT_NUM); + session_.resetSeq(); session_.setState(MySQLSession::State::Req); + current_state = session_.getState(); } else { ENVOY_LOG(info, "mysql_proxy: ignoring out-of-sync packet"); callbacks_.onProtocolError(); - data.drain(len); // Ensure that the whole message was consumed - return true; + data.drain(sizeof(uint32_t) + len); // Ensure that the whole message was consumed + return_without_parse = true; } } - session_.setExpectedSeq(seq + 1); + + payload_metadata_list_.push_back( + {.seq = session_.convertToSeqOnReciever(seq, is_upstream), .len = len}); + + if (return_without_parse) { + return result; + } + + BufferHelper::consumeHdr(data); // Consume the header once the message is fully available. + session_.incSeq(); const ssize_t data_len = data.length(); - parseMessage(data, seq, len); + parseMessage(data, seq, len, is_upstream); const ssize_t consumed_len = data_len - data.length(); data.drain(len - consumed_len); // Ensure that the whole message was consumed ENVOY_LOG(trace, "mysql_proxy: {} bytes remaining in buffer", data.length()); - return true; + return result; } -void DecoderImpl::onData(Buffer::Instance& data) { +Decoder::Result DecoderImpl::onData(Buffer::Instance& data, bool is_upstream) { + payload_metadata_list_.clear(); + // TODO(venilnoronha): handle messages over 16 mb. See // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_packets.html#sect_protocol_basic_packets_sending_mt_16mb. - while (!BufferHelper::endOfBuffer(data) && decode(data)) { + while (!BufferHelper::endOfBuffer(data) && decode(data, is_upstream)) { } + + if (is_upstream && session_.getState() == MySQLSession::State::SslPt) { + if (!callbacks_.onSSLRequest()) { + session_.adjustSeqOffset(1); + session_.setState(MySQLSession::State::ChallengeReq); + return Decoder::Result::Stopped; + } + } + + return Decoder::Result::ReadyForNext; } DecoderFactoryImpl DecoderFactoryImpl::instance_; diff --git a/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.h b/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.h index 6c126edaeba1f..85c8678e64e71 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.h +++ b/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.h @@ -11,12 +11,12 @@ class DecoderImpl : public Decoder, public Logger::Loggable DecoderImpl(DecoderCallbacks& callbacks) : callbacks_(callbacks) {} // MySQLProxy::Decoder - void onData(Buffer::Instance& data) override; + Decoder::Result onData(Buffer::Instance& data, bool is_upstream) override; MySQLSession& getSession() override { return session_; } private: - bool decode(Buffer::Instance& data); - void parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t len); + bool decode(Buffer::Instance& data, bool is_upstream); + void parseMessage(Buffer::Instance& message, uint8_t seq, uint32_t len, bool is_upstream); DecoderCallbacks& callbacks_; MySQLSession session_; diff --git a/contrib/mysql_proxy/filters/network/source/mysql_filter.cc b/contrib/mysql_proxy/filters/network/source/mysql_filter.cc index 1574bd2a23e80..d09b830913c8b 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_filter.cc +++ b/contrib/mysql_proxy/filters/network/source/mysql_filter.cc @@ -1,23 +1,30 @@ #include "contrib/mysql_proxy/filters/network/source/mysql_filter.h" +#include + #include "envoy/config/core/v3/base.pb.h" #include "source/common/buffer/buffer_impl.h" #include "source/common/common/assert.h" #include "source/common/common/logger.h" +#include "source/common/crypto/utility.h" #include "source/extensions/filters/network/well_known_names.h" #include "contrib/mysql_proxy/filters/network/source/mysql_codec.h" #include "contrib/mysql_proxy/filters/network/source/mysql_codec_clogin_resp.h" #include "contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.h" +#include "contrib/mysql_proxy/filters/network/source/mysql_utils.h" +#include "openssl/evp.h" +#include "openssl/rsa.h" namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace MySQLProxy { -MySQLFilterConfig::MySQLFilterConfig(const std::string& stat_prefix, Stats::Scope& scope) - : scope_(scope), stats_(generateStats(stat_prefix, scope)) {} +MySQLFilterConfig::MySQLFilterConfig(const std::string& stat_prefix, Stats::Scope& scope, + SSLMode downstream_ssl) + : scope_(scope), stats_(generateStats(stat_prefix, scope)), downstream_ssl_(downstream_ssl) {} MySQLFilter::MySQLFilter(MySQLFilterConfigSharedPtr config) : config_(std::move(config)) {} @@ -25,27 +32,145 @@ void MySQLFilter::initializeReadFilterCallbacks(Network::ReadFilterCallbacks& ca read_callbacks_ = &callbacks; } +void MySQLFilter::initializeWriteFilterCallbacks(Network::WriteFilterCallbacks& callbacks) { + write_callbacks_ = &callbacks; +} + Network::FilterStatus MySQLFilter::onData(Buffer::Instance& data, bool) { + Network::FilterStatus status = Network::FilterStatus::Continue; + uint64_t remaining = read_buffer_.length(); + // Safety measure just to make sure that if we have a decoding error we keep going and lose stats. // This can be removed once we are more confident of this code. - if (sniffing_) { - read_buffer_.add(data); - doDecode(read_buffer_); + if (!sniffing_) { + return status; } - return Network::FilterStatus::Continue; + + read_buffer_.add(data); + status = doDecode(read_buffer_, true); + + if (status == Network::FilterStatus::StopIteration) { + data.drain(data.length()); + return status; + } + + // RSA mediation: intercept client cleartext password. + if (rsa_auth_state_ == RsaAuthState::WaitingClientPassword && cleartext_password_) { + data.drain(data.length()); + + uint8_t inject_seq = getSession().getExpectedSeq(false) - 1; + ENVOY_CONN_LOG(trace, + "mysql_proxy: intercepted client password, sending request-public-key (seq={})", + read_callbacks_->connection(), inject_seq); + + Buffer::OwnedImpl buf; + BufferHelper::addUint24(buf, 1); + BufferHelper::addUint8(buf, inject_seq); + BufferHelper::addUint8(buf, MYSQL_REQUEST_PUBLIC_KEY); + read_callbacks_->injectReadDataToFilterChain(buf, false); + + rsa_auth_state_ = RsaAuthState::WaitingServerKey; + return Network::FilterStatus::StopIteration; + } + + if (config_->terminateSsl()) { + doRewrite(data, remaining, true); + } + + return status; } Network::FilterStatus MySQLFilter::onWrite(Buffer::Instance& data, bool) { + Network::FilterStatus status = Network::FilterStatus::Continue; + // Safety measure just to make sure that if we have a decoding error we keep going and lose stats. // This can be removed once we are more confident of this code. - if (sniffing_) { + if (!sniffing_) { + return status; + } + + // RSA mediation: intercept server's public key response. + if (rsa_auth_state_ == RsaAuthState::WaitingServerKey) { write_buffer_.add(data); - doDecode(write_buffer_); + data.drain(data.length()); + + // Check if we have a complete packet. + uint32_t len = 0; + uint8_t seq = 0; + if (BufferHelper::peekHdr(write_buffer_, len, seq) != DecodeStatus::Success || + sizeof(uint32_t) + len > write_buffer_.length()) { + ENVOY_CONN_LOG(trace, + "mysql_proxy: waiting for complete public key packet ({} bytes buffered)", + read_callbacks_->connection(), write_buffer_.length()); + return Network::FilterStatus::StopIteration; + } + + ENVOY_CONN_LOG(trace, "mysql_proxy: received server public key packet (seq={}, len={})", + read_callbacks_->connection(), seq, len); + + // Full packet available. Parse it: [hdr][0x01 marker][PEM key bytes]. + BufferHelper::consumeHdr(write_buffer_); + uint8_t marker; + BufferHelper::readUint8(write_buffer_, marker); + if (marker != MYSQL_RESP_MORE) { + ENVOY_CONN_LOG(error, "mysql_proxy: unexpected marker 0x{:02x} in public key response", + read_callbacks_->connection(), marker); + read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + return Network::FilterStatus::StopIteration; + } + + std::string pem_key; + BufferHelper::readStringBySize(write_buffer_, len - 1, pem_key); + write_buffer_.drain(write_buffer_.length()); + + ENVOY_CONN_LOG(trace, "mysql_proxy: extracted PEM key ({} bytes), encrypting password", + read_callbacks_->connection(), pem_key.size()); + + sendEncryptedPassword(pem_key, seq); + getSession().adjustSeqOffset(-2); + rsa_auth_state_ = RsaAuthState::WaitingServerResult; + + ENVOY_CONN_LOG(trace, "mysql_proxy: RSA encrypted password sent, waiting for server result", + read_callbacks_->connection()); + + // Nothing to forward to client; data was already drained. + return Network::FilterStatus::Continue; } - return Network::FilterStatus::Continue; + + uint64_t remaining = write_buffer_.length(); + + write_buffer_.add(data); + status = doDecode(write_buffer_, false); + + if (status == Network::FilterStatus::StopIteration) { + data.drain(data.length()); + return status; + } + + if (config_->terminateSsl()) { + doRewrite(data, remaining, false); + } + + return status; +} + +bool MySQLFilter::onSSLRequest() { + if (!config_->terminateSsl()) { + return true; + } + + if (!read_callbacks_->connection().startSecureTransport()) { + ENVOY_CONN_LOG(info, "mysql_proxy: cannot enable secure transport. Check configuration.", + read_callbacks_->connection()); + read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + } else { + ENVOY_CONN_LOG(trace, "mysql_proxy: enabled SSL termination.", read_callbacks_->connection()); + } + + return false; } -void MySQLFilter::doDecode(Buffer::Instance& buffer) { +Network::FilterStatus MySQLFilter::doDecode(Buffer::Instance& buffer, bool is_upstream) { // Clear dynamic metadata. envoy::config::core::v3::Metadata& dynamic_metadata = read_callbacks_->connection().streamInfo().dynamicMetadata(); @@ -58,7 +183,12 @@ void MySQLFilter::doDecode(Buffer::Instance& buffer) { } try { - decoder_->onData(buffer); + switch (decoder_->onData(buffer, is_upstream)) { + case Decoder::Result::ReadyForNext: + return Network::FilterStatus::Continue; + case Decoder::Result::Stopped: + return Network::FilterStatus::StopIteration; + } } catch (EnvoyException& e) { ENVOY_LOG(info, "mysql_proxy: decoding error: {}", e.what()); config_->stats_.decoder_errors_.inc(); @@ -66,38 +196,230 @@ void MySQLFilter::doDecode(Buffer::Instance& buffer) { read_buffer_.drain(read_buffer_.length()); write_buffer_.drain(write_buffer_.length()); } + + return Network::FilterStatus::Continue; } DecoderPtr MySQLFilter::createDecoder(DecoderCallbacks& callbacks) { return std::make_unique(callbacks); } +void MySQLFilter::rewritePacketHeader(Buffer::Instance& data, uint8_t seq, uint32_t len) { + BufferHelper::consumeHdr(data); + BufferHelper::addUint24(data, len); + BufferHelper::addUint8(data, seq); +} + +void MySQLFilter::stripSslCapability(Buffer::Instance& data) { + uint32_t client_cap = 0; + BufferHelper::readUint32(data, client_cap); + BufferHelper::addUint32(data, client_cap & ~CLIENT_SSL); +} + +void MySQLFilter::doRewrite(Buffer::Instance& data, uint64_t remaining, bool is_upstream) { + MySQLSession::State state = getSession().getState(); + auto& payload_metadata_list = decoder_->getPayloadMetadataList(); + const uint64_t original_data_size = data.length(); + uint64_t max_data_size = original_data_size; + + for (size_t i = 0; i < payload_metadata_list.size(); ++i) { + uint8_t seq = payload_metadata_list[i].seq; + uint32_t len = payload_metadata_list[i].len; + + if (i == 0 && remaining > 0) { + // First packet spans old internal buffer and new data. The header and first + // (remaining - 4) payload bytes are in the internal buffer, not in data. + ASSERT(remaining >= 4, "partial header should not appear in payload metadata"); + len -= remaining - 4; + } else { + rewritePacketHeader(data, seq, len); + max_data_size -= 4; + + if (is_upstream && (state == MySQLSession::State::ChallengeResp41 || + state == MySQLSession::State::ChallengeResp320)) { + stripSslCapability(data); + len -= 4; + } + } + + uint64_t copy_size = std::min(static_cast(len), max_data_size); + std::string payload; + payload.reserve(copy_size); + BufferHelper::readStringBySize(data, copy_size, payload); + BufferHelper::addBytes(data, payload.c_str(), payload.size()); + max_data_size -= copy_size; + } + + ASSERT(data.length() == original_data_size, "doRewrite must not change overall buffer size"); +} + void MySQLFilter::onProtocolError() { config_->stats_.protocol_errors_.inc(); } -void MySQLFilter::onNewMessage(MySQLSession::State state) { - if (state == MySQLSession::State::ChallengeReq) { - config_->stats_.login_attempts_.inc(); +void MySQLFilter::onServerGreeting(ServerGreeting& greeting) { + config_->stats_.login_attempts_.inc(); + ENVOY_CONN_LOG(trace, "mysql_proxy: server greeting: version={}, auth_plugin={}, scramble_len={}", + read_callbacks_->connection(), greeting.getVersion(), greeting.getAuthPluginName(), + greeting.getAuthPluginData().size()); + if (config_->terminateSsl()) { + server_scramble_ = greeting.getAuthPluginData(); + // The MySQL greeting protocol may include a trailing null filler byte in + // auth_plugin_data, making it 21 bytes. The actual nonce used by + // caching_sha2_password is always 20 bytes. Truncate to avoid corrupting + // the XOR for passwords longer than 20 characters. + if (server_scramble_.size() > NATIVE_PSSWORD_HASH_LENGTH) { + server_scramble_.resize(NATIVE_PSSWORD_HASH_LENGTH); + } + auth_plugin_name_ = greeting.getAuthPluginName(); + ENVOY_CONN_LOG(trace, + "mysql_proxy: captured scramble ({} bytes) for SSL termination, plugin={}", + read_callbacks_->connection(), server_scramble_.size(), auth_plugin_name_); } } -void MySQLFilter::onClientLogin(ClientLogin& client_login) { +void MySQLFilter::onClientLogin(ClientLogin& client_login, MySQLSession::State state) { + ENVOY_CONN_LOG(trace, "mysql_proxy: client login: ssl_request={}, state={}, user={}", + read_callbacks_->connection(), client_login.isSSLRequest(), + static_cast(state), client_login.getUsername()); if (client_login.isSSLRequest()) { config_->stats_.upgraded_to_ssl_.inc(); } + + if (state == MySQLSession::State::ChallengeResp41 || + state == MySQLSession::State::ChallengeResp320) { + // REQUIRE mode: reject clients that did not initiate SSL. + using MySQLProto = envoy::extensions::filters::network::mysql_proxy::v3::MySQLProxy; + if (config_->downstream_ssl_ == MySQLProto::REQUIRE && getSession().getSeqOffset() == 0) { + ENVOY_CONN_LOG(info, + "mysql_proxy: downstream_ssl=REQUIRE but client did not initiate SSL, closing", + read_callbacks_->connection()); + read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + } + } } void MySQLFilter::onClientLoginResponse(ClientLoginResponse& client_login_resp) { + ENVOY_CONN_LOG(trace, "mysql_proxy: server login response: resp_code=0x{:02x}", + read_callbacks_->connection(), client_login_resp.getRespCode()); if (client_login_resp.getRespCode() == MYSQL_RESP_AUTH_SWITCH) { config_->stats_.auth_switch_request_.inc(); } else if (client_login_resp.getRespCode() == MYSQL_RESP_ERR) { config_->stats_.login_failures_.inc(); + } else if (config_->terminateSsl() && getSession().getSeqOffset() != 0 && + client_login_resp.getRespCode() == MYSQL_RESP_MORE) { + auto* auth_more = dynamic_cast(&client_login_resp); + if (auth_more && !auth_more->getAuthMoreData().empty()) { + ENVOY_CONN_LOG(trace, "mysql_proxy: AuthMoreData[0]=0x{:02x}, plugin={}", + read_callbacks_->connection(), auth_more->getAuthMoreData()[0], + auth_plugin_name_); + if (auth_more->getAuthMoreData()[0] == MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED && + auth_plugin_name_ == "caching_sha2_password") { + rsa_auth_state_ = RsaAuthState::WaitingClientPassword; + ENVOY_CONN_LOG(trace, "mysql_proxy: full auth required, entering RSA mediation", + read_callbacks_->connection()); + } else if (auth_more->getAuthMoreData()[0] == MYSQL_CACHINGSHA2_FAST_AUTH_SUCCESS) { + ENVOY_CONN_LOG(trace, "mysql_proxy: fast auth success (cache hit), no RSA needed", + read_callbacks_->connection()); + } + } } } void MySQLFilter::onMoreClientLoginResponse(ClientLoginResponse& client_login_resp) { + ENVOY_CONN_LOG(trace, "mysql_proxy: more login response: resp_code=0x{:02x}, rsa_state={}", + read_callbacks_->connection(), client_login_resp.getRespCode(), + static_cast(rsa_auth_state_)); if (client_login_resp.getRespCode() == MYSQL_RESP_ERR) { config_->stats_.login_failures_.inc(); } + if (rsa_auth_state_ == RsaAuthState::WaitingServerResult) { + ENVOY_CONN_LOG(trace, "mysql_proxy: RSA mediation complete, result=0x{:02x}", + read_callbacks_->connection(), client_login_resp.getRespCode()); + rsa_auth_state_ = RsaAuthState::Inactive; + } +} + +void MySQLFilter::onAuthSwitchMoreClientData(std::unique_ptr data) { + ENVOY_CONN_LOG(trace, "mysql_proxy: client auth data received, len={}, rsa_state={}", + read_callbacks_->connection(), data ? data->size() : 0, + static_cast(rsa_auth_state_)); + if (rsa_auth_state_ == RsaAuthState::WaitingClientPassword && data) { + // Password arrives in SecureBytes (guarded memory, zeroed on free). + // The decoder already read it via readSecureBytes which zeroed the source buffer. + cleartext_password_ = std::move(data); + ENVOY_CONN_LOG(trace, "mysql_proxy: captured cleartext password ({} bytes) in secure memory", + read_callbacks_->connection(), cleartext_password_->size()); + } +} + +void MySQLFilter::sendEncryptedPassword(const std::string& pem_key, uint8_t last_server_seq) { + if (!cleartext_password_) { + ENVOY_CONN_LOG(error, "mysql_proxy: no cleartext password captured", + read_callbacks_->connection()); + read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + return; + } + + // XOR password with scramble in secure memory. The cleartext_password_ includes the + // trailing null sent by the client (password\0 format for caching_sha2_password). + SecureBytes xored(cleartext_password_->size()); + for (size_t i = 0; i < cleartext_password_->size(); i++) { + xored[i] = (*cleartext_password_)[i] ^ server_scramble_[i % server_scramble_.size()]; + } + + // Import the server's public key. + auto pkey = Envoy::Common::Crypto::UtilitySingleton::get().importPublicKeyPEM(pem_key); + if (!pkey || !pkey->getEVP_PKEY()) { + ENVOY_CONN_LOG(error, "mysql_proxy: failed to import server public key", + read_callbacks_->connection()); + read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + return; + } + + // RSA-encrypt with OAEP/SHA-1 padding (MySQL requirement). + bssl::UniquePtr ctx(EVP_PKEY_CTX_new(pkey->getEVP_PKEY(), nullptr)); + if (!ctx || EVP_PKEY_encrypt_init(ctx.get()) <= 0 || + EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0 || + EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), EVP_sha1()) <= 0) { + ENVOY_CONN_LOG(error, "mysql_proxy: failed to initialize RSA encryption context", + read_callbacks_->connection()); + read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + return; + } + + size_t out_len = 0; + if (EVP_PKEY_encrypt(ctx.get(), nullptr, &out_len, xored.data(), xored.size()) <= 0) { + ENVOY_CONN_LOG(error, "mysql_proxy: failed to determine RSA ciphertext length", + read_callbacks_->connection()); + read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + cleartext_password_.reset(); + return; + } + + std::vector encrypted(out_len); + if (EVP_PKEY_encrypt(ctx.get(), encrypted.data(), &out_len, xored.data(), xored.size()) <= 0) { + ENVOY_CONN_LOG(error, "mysql_proxy: RSA encryption failed", read_callbacks_->connection()); + read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + cleartext_password_.reset(); + return; + } + encrypted.resize(out_len); + + // Build the encrypted password packet: [3-byte len][seq][encrypted_data]. + uint8_t enc_seq = static_cast(last_server_seq + 1); + Buffer::OwnedImpl buf; + BufferHelper::addUint24(buf, encrypted.size()); + BufferHelper::addUint8(buf, enc_seq); + BufferHelper::addVector(buf, encrypted); + + ENVOY_CONN_LOG(trace, + "mysql_proxy: injecting RSA-encrypted password (seq={}, {} bytes ciphertext)", + read_callbacks_->connection(), enc_seq, encrypted.size()); + + // Inject toward server (bypasses our filter). + read_callbacks_->injectReadDataToFilterChain(buf, false); + + // Securely destroy the cleartext password (OPENSSL_cleanse zeroes before freeing). + cleartext_password_.reset(); } void MySQLFilter::onCommand(Command& command) { diff --git a/contrib/mysql_proxy/filters/network/source/mysql_filter.h b/contrib/mysql_proxy/filters/network/source/mysql_filter.h index bf41c53762269..33928f9181ee9 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_filter.h +++ b/contrib/mysql_proxy/filters/network/source/mysql_filter.h @@ -9,6 +9,7 @@ #include "source/common/common/logger.h" +#include "contrib/envoy/extensions/filters/network/mysql_proxy/v3/mysql_proxy.pb.h" #include "contrib/mysql_proxy/filters/network/source/mysql_codec.h" #include "contrib/mysql_proxy/filters/network/source/mysql_codec_clogin.h" #include "contrib/mysql_proxy/filters/network/source/mysql_codec_clogin_resp.h" @@ -17,6 +18,7 @@ #include "contrib/mysql_proxy/filters/network/source/mysql_codec_switch_resp.h" #include "contrib/mysql_proxy/filters/network/source/mysql_decoder.h" #include "contrib/mysql_proxy/filters/network/source/mysql_session.h" +#include "contrib/mysql_proxy/filters/network/source/mysql_utils.h" namespace Envoy { namespace Extensions { @@ -49,12 +51,19 @@ struct MySQLProxyStats { */ class MySQLFilterConfig { public: - MySQLFilterConfig(const std::string& stat_prefix, Stats::Scope& scope); + using SSLMode = envoy::extensions::filters::network::mysql_proxy::v3::MySQLProxy::SSLMode; + + MySQLFilterConfig(const std::string& stat_prefix, Stats::Scope& scope, SSLMode downstream_ssl); const MySQLProxyStats& stats() { return stats_; } + bool terminateSsl() const { + return downstream_ssl_ != + envoy::extensions::filters::network::mysql_proxy::v3::MySQLProxy::DISABLE; + } Stats::Scope& scope_; MySQLProxyStats stats_; + SSLMode downstream_ssl_; private: MySQLProxyStats generateStats(const std::string& prefix, Stats::Scope& scope) { @@ -64,6 +73,13 @@ class MySQLFilterConfig { using MySQLFilterConfigSharedPtr = std::shared_ptr; +enum class RsaAuthState { + Inactive, // Normal operation + WaitingClientPassword, // Server sent AuthMoreData(0x04), forwarded to client, waiting for pw + WaitingServerKey, // Sent 0x02 to server, waiting for PEM public key + WaitingServerResult, // Sent RSA-encrypted pw, waiting for OK/ERR +}; + /** * Implementation of MySQL proxy filter. */ @@ -79,29 +95,47 @@ class MySQLFilter : public Network::Filter, DecoderCallbacks, Logger::Loggable data) override; + bool onSSLRequest() override; - void doDecode(Buffer::Instance& buffer); + Network::FilterStatus doDecode(Buffer::Instance& buffer, bool is_upstream); DecoderPtr createDecoder(DecoderCallbacks& callbacks); + void doRewrite(Buffer::Instance& buffer, uint64_t remaining, bool is_upstream); MySQLSession& getSession() { return decoder_->getSession(); } + // Helpers for doRewrite. + static void rewritePacketHeader(Buffer::Instance& data, uint8_t seq, uint32_t len); + static void stripSslCapability(Buffer::Instance& data); + + RsaAuthState getRsaAuthState() const { return rsa_auth_state_; } + private: + void sendEncryptedPassword(const std::string& pem_key, uint8_t last_server_seq); + Network::ReadFilterCallbacks* read_callbacks_{}; + Network::WriteFilterCallbacks* write_callbacks_{}; MySQLFilterConfigSharedPtr config_; Buffer::OwnedImpl read_buffer_; Buffer::OwnedImpl write_buffer_; std::unique_ptr decoder_; bool sniffing_{true}; + + // RSA mediation state for caching_sha2_password full authentication. + RsaAuthState rsa_auth_state_{RsaAuthState::Inactive}; + std::unique_ptr cleartext_password_; + std::vector server_scramble_; + std::string auth_plugin_name_; }; } // namespace MySQLProxy diff --git a/contrib/mysql_proxy/filters/network/source/mysql_session.h b/contrib/mysql_proxy/filters/network/source/mysql_session.h index 691d582633b11..c659c50b4554a 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_session.h +++ b/contrib/mysql_proxy/filters/network/source/mysql_session.h @@ -27,12 +27,23 @@ class MySQLSession : Logger::Loggable { void setState(MySQLSession::State state) { state_ = state; } MySQLSession::State getState() { return state_; } - uint8_t getExpectedSeq() { return expected_seq_; } - void setExpectedSeq(uint8_t seq) { expected_seq_ = seq; } + uint8_t getExpectedSeq(bool is_upstream) { return seq_ - (is_upstream ? 0 : seq_offset_); } + uint8_t convertToSeqOnReciever(uint8_t seq, bool is_upstream) { + return seq - (is_upstream ? 1 : -1) * seq_offset_; + } + void resetSeq() { + seq_ = MYSQL_REQUEST_PKT_NUM; + seq_offset_ = 0; + } + void incSeq() { seq_++; } + int8_t getSeqOffset() const { return seq_offset_; } + void setSeqOffset(int8_t offset) { seq_offset_ = offset; } + void adjustSeqOffset(int8_t delta) { seq_offset_ += delta; } private: MySQLSession::State state_{State::Init}; - uint8_t expected_seq_{0}; + uint8_t seq_{0}; + int8_t seq_offset_{0}; }; } // namespace MySQLProxy diff --git a/contrib/mysql_proxy/filters/network/source/mysql_utils.cc b/contrib/mysql_proxy/filters/network/source/mysql_utils.cc index c51fffd2076ce..7c56ba6b0631e 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_utils.cc +++ b/contrib/mysql_proxy/filters/network/source/mysql_utils.cc @@ -216,6 +216,31 @@ DecodeStatus BufferHelper::peekHdr(Buffer::Instance& buffer, uint32_t& len, uint return DecodeStatus::Success; } +DecodeStatus BufferHelper::readSecureBytes(Buffer::Instance& buffer, size_t len, + std::unique_ptr& out) { + if (buffer.length() < len) { + return DecodeStatus::Failure; + } + + out = std::make_unique(len); + + // Copy data into secure memory, then zero the source buffer slices. + buffer.copyOut(0, len, out->data()); + + uint64_t zeroed = 0; + for (const auto& slice : buffer.getRawSlices()) { + if (zeroed >= len) { + break; + } + const uint64_t chunk = std::min(static_cast(slice.len_), len - zeroed); + OPENSSL_cleanse(slice.mem_, chunk); + zeroed += chunk; + } + + buffer.drain(len); + return DecodeStatus::Success; +} + } // namespace MySQLProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/contrib/mysql_proxy/filters/network/source/mysql_utils.h b/contrib/mysql_proxy/filters/network/source/mysql_utils.h index 254ce0f8edc81..d22bcdc8bab5b 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_utils.h +++ b/contrib/mysql_proxy/filters/network/source/mysql_utils.h @@ -8,12 +8,46 @@ #include "source/common/common/logger.h" #include "contrib/mysql_proxy/filters/network/source/mysql_codec.h" +#include "openssl/crypto.h" namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace MySQLProxy { +// Secure memory buffer that is guaranteed to be zeroed on destruction +// via OPENSSL_cleanse, preventing password leakage in memory. +class SecureBytes { +public: + explicit SecureBytes(size_t len) : data_(new uint8_t[len]), len_(len) {} + + ~SecureBytes() { + if (data_ != nullptr) { + OPENSSL_cleanse(data_, len_); + delete[] data_; + } + } + + SecureBytes(const SecureBytes&) = delete; + SecureBytes& operator=(const SecureBytes&) = delete; + + SecureBytes(SecureBytes&& other) noexcept : data_(other.data_), len_(other.len_) { + other.data_ = nullptr; + other.len_ = 0; + } + + uint8_t* data() { return data_; } + const uint8_t* data() const { return data_; } + size_t size() const { return len_; } + + uint8_t operator[](size_t i) const { return data_[i]; } + uint8_t& operator[](size_t i) { return data_[i]; } + +private: + uint8_t* data_{nullptr}; + size_t len_{0}; +}; + /** * IO helpers for reading/writing MySQL data from/to a buffer. * MySQL uses unsigned integer values in Little Endian format only. @@ -50,6 +84,11 @@ class BufferHelper : public Logger::Loggable { static DecodeStatus peekUint8(Buffer::Instance& buffer, uint8_t& val); static void consumeHdr(Buffer::Instance& buffer); static DecodeStatus peekHdr(Buffer::Instance& buffer, uint32_t& len, uint8_t& seq); + + // Read `len` bytes from buffer into a SecureBytes object backed by guarded memory, + // then zero the original data in the buffer to prevent password leakage. + static DecodeStatus readSecureBytes(Buffer::Instance& buffer, size_t len, + std::unique_ptr& out); }; } // namespace MySQLProxy diff --git a/contrib/mysql_proxy/filters/network/test/BUILD b/contrib/mysql_proxy/filters/network/test/BUILD index a29546b61dbd9..7a2d00086e919 100644 --- a/contrib/mysql_proxy/filters/network/test/BUILD +++ b/contrib/mysql_proxy/filters/network/test/BUILD @@ -78,9 +78,11 @@ envoy_cc_test( srcs = [ "mysql_filter_test.cc", ], + external_deps = ["ssl"], deps = [ ":mysql_test_utils_lib", "//contrib/mysql_proxy/filters/network/source:config", + "//source/common/crypto:utility_lib", "//test/mocks/network:network_mocks", ], ) @@ -104,6 +106,32 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "mysql_ssl_integration_test", + srcs = [ + "mysql_ssl_integration_test.cc", + ], + data = [ + "mysql_ssl_allow_test_config.yaml", + "mysql_ssl_disable_test_config.yaml", + "mysql_ssl_require_test_config.yaml", + "//test/config/integration/certs", + ], + external_deps = ["ssl"], + rbe_pool = "6gig", + deps = [ + ":mysql_test_utils_lib", + "//contrib/mysql_proxy/filters/network/source:config", + "//contrib/mysql_proxy/filters/network/source:filter_lib", + "//source/common/tcp_proxy", + "//source/extensions/filters/network/tcp_proxy:config", + "//source/extensions/transport_sockets/raw_buffer:config", + "//source/extensions/transport_sockets/starttls:config", + "//test/integration:integration_lib", + "@envoy_api//envoy/extensions/transport_sockets/raw_buffer/v3:pkg_cc_proto", + ], +) + envoy_cc_test( name = "mysql_command_tests", srcs = [ diff --git a/contrib/mysql_proxy/filters/network/test/mysql_clogin_test.cc b/contrib/mysql_proxy/filters/network/test/mysql_clogin_test.cc index 3772e220eb265..1dd3d12df9eae 100644 --- a/contrib/mysql_proxy/filters/network/test/mysql_clogin_test.cc +++ b/contrib/mysql_proxy/filters/network/test/mysql_clogin_test.cc @@ -804,83 +804,6 @@ TEST_F(MySQLCLoginTest, MySQLClientLoginSSLEncDec) { EXPECT_EQ(mysql_clogin_decode.getMaxPacket(), mysql_clogin_encode.getMaxPacket()); } -/* - * Negative Test the MYSQL Client login SSL message parser: - * Incomplete cap flag - */ -TEST_F(MySQLCLoginTest, MySQLClientLoginSslIncompleteCap) { - ClientLogin& mysql_clogin_encode = MySQLCLoginTest::getClientLogin(CLIENT_SSL); - Buffer::OwnedImpl buffer; - mysql_clogin_encode.encode(buffer); - - int incomplete_len = sizeof(mysql_clogin_encode.getClientCap()) - 1; - Buffer::OwnedImpl decode_data(buffer.toString().data(), incomplete_len); - - ClientLogin mysql_clogin_decode{}; - mysql_clogin_decode.decode(decode_data, CHALLENGE_SEQ_NUM, decode_data.length()); - EXPECT_EQ(mysql_clogin_decode.getExtendedClientCap(), 0); -} - -/* - * Negative Test the MYSQL Client login SSL message parser: - * Incomplete max packet - */ -TEST_F(MySQLCLoginTest, MySQLClientLoginSslIncompleteMaxPacket) { - ClientLogin& mysql_clogin_encode = MySQLCLoginTest::getClientLogin(CLIENT_SSL); - Buffer::OwnedImpl buffer; - mysql_clogin_encode.encode(buffer); - - int incomplete_len = sizeof(mysql_clogin_encode.getClientCap()); - Buffer::OwnedImpl decode_data(buffer.toString().data(), incomplete_len); - - ClientLogin mysql_clogin_decode{}; - mysql_clogin_decode.decode(decode_data, CHALLENGE_SEQ_NUM, decode_data.length()); - EXPECT_TRUE(mysql_clogin_decode.isSSLRequest()); - EXPECT_EQ(mysql_clogin_decode.getExtendedClientCap(), mysql_clogin_encode.getExtendedClientCap()); - EXPECT_EQ(mysql_clogin_decode.getMaxPacket(), 0); -} - -/* - * Negative Test the MYSQL Client login SSL message parser: - * Incomplete character set - */ -TEST_F(MySQLCLoginTest, MySQLClientLoginSslIncompleteCharset) { - ClientLogin& mysql_clogin_encode = MySQLCLoginTest::getClientLogin(CLIENT_SSL); - Buffer::OwnedImpl buffer; - mysql_clogin_encode.encode(buffer); - - int incomplete_len = - sizeof(mysql_clogin_encode.getClientCap()) + sizeof(mysql_clogin_encode.getMaxPacket()); - Buffer::OwnedImpl decode_data(buffer.toString().data(), incomplete_len); - - ClientLogin mysql_clogin_decode{}; - mysql_clogin_decode.decode(decode_data, CHALLENGE_SEQ_NUM, decode_data.length()); - EXPECT_EQ(mysql_clogin_decode.getClientCap(), mysql_clogin_encode.getClientCap()); - EXPECT_EQ(mysql_clogin_decode.getMaxPacket(), mysql_clogin_encode.getMaxPacket()); - EXPECT_EQ(mysql_clogin_decode.getCharset(), 0); -} - -/* - * Negative Test the MYSQL Client login SSL message parser: - * Incomplete reserved - */ -TEST_F(MySQLCLoginTest, MySQLClientLoginSslIncompleteReserved) { - ClientLogin& mysql_clogin_encode = MySQLCLoginTest::getClientLogin(CLIENT_SSL); - Buffer::OwnedImpl buffer; - mysql_clogin_encode.encode(buffer); - - int incomplete_len = sizeof(mysql_clogin_encode.getClientCap()) + - sizeof(mysql_clogin_encode.getMaxPacket()) + - sizeof(mysql_clogin_encode.getCharset()); - Buffer::OwnedImpl decode_data(buffer.toString().data(), incomplete_len); - - ClientLogin mysql_clogin_decode{}; - mysql_clogin_decode.decode(decode_data, CHALLENGE_SEQ_NUM, decode_data.length()); - EXPECT_EQ(mysql_clogin_decode.getClientCap(), mysql_clogin_encode.getClientCap()); - EXPECT_EQ(mysql_clogin_decode.getMaxPacket(), mysql_clogin_encode.getMaxPacket()); - EXPECT_EQ(mysql_clogin_decode.getCharset(), mysql_clogin_encode.getCharset()); -} - } // namespace MySQLProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/contrib/mysql_proxy/filters/network/test/mysql_filter_test.cc b/contrib/mysql_proxy/filters/network/test/mysql_filter_test.cc index 1b816e86bd7b9..1a8d4d6178128 100644 --- a/contrib/mysql_proxy/filters/network/test/mysql_filter_test.cc +++ b/contrib/mysql_proxy/filters/network/test/mysql_filter_test.cc @@ -1,15 +1,23 @@ #include "source/common/buffer/buffer_impl.h" +#include "source/common/crypto/utility.h" #include "test/mocks/network/mocks.h" #include "contrib/mysql_proxy/filters/network/source/mysql_codec.h" +#include "contrib/mysql_proxy/filters/network/source/mysql_codec_clogin_resp.h" +#include "contrib/mysql_proxy/filters/network/source/mysql_codec_greeting.h" #include "contrib/mysql_proxy/filters/network/source/mysql_filter.h" #include "contrib/mysql_proxy/filters/network/source/mysql_utils.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "mysql_test_utils.h" +#include "openssl/evp.h" +#include "openssl/pem.h" +#include "openssl/rsa.h" +using testing::_; using testing::NiceMock; +using testing::ReturnRef; namespace Envoy { namespace Extensions { @@ -22,10 +30,48 @@ class MySQLFilterTest : public testing::Test, public MySQLTestUtils { public: MySQLFilterTest() { ENVOY_LOG_MISC(info, "test"); } - void initialize() { - config_ = std::make_shared(stat_prefix_, scope_); + using MySQLProxyProto = envoy::extensions::filters::network::mysql_proxy::v3::MySQLProxy; + + void initialize(MySQLProxyProto::SSLMode downstream_ssl = MySQLProxyProto::DISABLE) { + config_ = std::make_shared(stat_prefix_, scope_, downstream_ssl); filter_ = std::make_unique(config_); filter_->initializeReadFilterCallbacks(filter_callbacks_); + filter_->initializeWriteFilterCallbacks(write_filter_callbacks_); + } + + // Encode a server greeting for caching_sha2_password with 20-byte scramble. + std::string encodeServerGreetingCachingSha2() { + ServerGreeting greeting; + greeting.setProtocol(MYSQL_PROTOCOL_10); + greeting.setVersion(getVersion()); + greeting.setThreadId(MYSQL_THREAD_ID); + greeting.setAuthPluginData(getAuthPluginData20()); + greeting.setServerCap(CLIENT_PLUGIN_AUTH | CLIENT_SECURE_CONNECTION); + greeting.setServerCharset(MYSQL_SERVER_LANGUAGE); + greeting.setServerStatus(MYSQL_SERVER_STATUS); + greeting.setAuthPluginName("caching_sha2_password"); + Buffer::OwnedImpl buffer; + greeting.encode(buffer); + BufferHelper::encodeHdr(buffer, GREETING_SEQ_NUM); + return buffer.toString(); + } + + // Encode an AuthMoreData packet with specific data bytes. + std::string encodeAuthMoreDataPacket(const std::vector& data, uint8_t seq) { + AuthMoreMessage auth_more; + auth_more.setAuthMoreData(data); + Buffer::OwnedImpl buffer; + auth_more.encode(buffer); + BufferHelper::encodeHdr(buffer, seq); + return buffer.toString(); + } + + // Encode a raw client-to-server packet (e.g., cleartext password). + std::string encodeRawPacket(const std::string& payload, uint8_t seq) { + Buffer::OwnedImpl buffer; + BufferHelper::addString(buffer, payload); + BufferHelper::encodeHdr(buffer, seq); + return buffer.toString(); } MySQLFilterConfigSharedPtr config_; @@ -34,6 +80,8 @@ class MySQLFilterTest : public testing::Test, public MySQLTestUtils { Stats::Scope& scope_{*store_.rootScope()}; std::string stat_prefix_{"test."}; NiceMock filter_callbacks_; + NiceMock write_filter_callbacks_; + NiceMock connection_; }; // Test New Session counter increment @@ -54,11 +102,11 @@ TEST_F(MySQLFilterTest, MySqlFallbackToTcpProxy) { EXPECT_EQ(1UL, config_->stats().sessions_.value()); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(" ")); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(1UL, config_->stats().decoder_errors_.value()); Buffer::InstancePtr more_data(new Buffer::OwnedImpl("scooby doo - part 2!")); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*more_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*more_data, false)); } /** @@ -74,7 +122,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake41OkTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); @@ -85,7 +133,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake41OkTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); } @@ -210,7 +258,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake41ErrTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); @@ -221,14 +269,14 @@ TEST_F(MySQLFilterTest, MySqlHandshake41ErrTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_ERR); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(1UL, config_->stats().login_failures_.value()); EXPECT_EQ(MySQLSession::State::Error, filter_->getSession().getState()); } /** * Test MySQL Handshake with protocol version 41 - * Server responds with Error + * Server responds with Auth More Data * SM: greeting(p=10) -> challenge-req(v41) -> serv-resp-more */ TEST_F(MySQLFilterTest, MySqlHandshake41AuthMoreTest) { @@ -240,7 +288,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake41AuthMoreTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); @@ -251,8 +299,8 @@ TEST_F(MySQLFilterTest, MySqlHandshake41AuthMoreTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_MORE); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); - EXPECT_EQ(MySQLSession::State::NotHandled, filter_->getSession().getState()); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); + EXPECT_EQ(MySQLSession::State::AuthSwitchMore, filter_->getSession().getState()); } /** @@ -268,7 +316,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320OkTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -279,7 +327,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320OkTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); } @@ -296,7 +344,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320OkTestIncomplete) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -307,7 +355,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320OkTestIncomplete) { std::string srv_resp_data = encodeMessage(0); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(MySQLSession::State::NotHandled, filter_->getSession().getState()); } @@ -325,7 +373,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320ErrTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -336,7 +384,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320ErrTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_ERR); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(1UL, config_->stats().login_failures_.value()); EXPECT_EQ(MySQLSession::State::Error, filter_->getSession().getState()); } @@ -346,7 +394,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320ErrTest) { * State-machine moves to SSL-Pass-Through * SM: greeting(p=10) -> challenge-req(v320) -> SSL_PT */ -TEST_F(MySQLFilterTest, MySqlHandshakeSSLTest) { +TEST_F(MySQLFilterTest, MySqlHandshakeSSLPassThroughTest) { initialize(); EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); @@ -355,23 +403,86 @@ TEST_F(MySQLFilterTest, MySqlHandshakeSSLTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); - std::string clogin_data = - encodeClientLogin(CLIENT_SSL | CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); + // Send SSL Connection Request packet. + // https://dev.mysql.com/doc/internals/en/ssl-handshake.html + std::string clogin_data = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); EXPECT_EQ(1UL, config_->stats().upgraded_to_ssl_.value()); EXPECT_EQ(MySQLSession::State::SslPt, filter_->getSession().getState()); + // After SSL handshaking, attempt to login. + // Since the SSL-Pass-Through, # of login attempts is unknown. + clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM + 1); + client_login_data = Buffer::InstancePtr(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); + EXPECT_EQ(1UL, config_->stats().upgraded_to_ssl_.value()); + EXPECT_EQ(MySQLSession::State::SslPt, filter_->getSession().getState()); + + std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK, 0, CHALLENGE_RESP_SEQ_NUM + 1); + Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); + EXPECT_EQ(MySQLSession::State::SslPt, filter_->getSession().getState()); + Buffer::OwnedImpl query_create_index("!@#$encr$#@!"); - BufferHelper::encodeHdr(query_create_index, 2); + BufferHelper::encodeHdr(query_create_index, 0); EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(query_create_index, false)); EXPECT_EQ(MySQLSession::State::SslPt, filter_->getSession().getState()); } +/** + * Test MySQL Handshake with SSL Request + * State-machine moves to SSL-Terminate + * SM: greeting(p=10) -> challenge-req(v320) -> SSL_PT -> ChallengeReq -> Req -> ReqResp + */ +TEST_F(MySQLFilterTest, MySqlHandshakeSSLTerminateTest) { + initialize(MySQLProxyProto::REQUIRE); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + EXPECT_EQ(1UL, config_->stats().sessions_.value()); + + std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); + + std::string clogin_data = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + + EXPECT_CALL(connection_, startSecureTransport()).WillOnce(testing::Return(true)); + EXPECT_CALL(connection_, close(_)).Times(0); + + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, + filter_->onData(*client_login_data, false)); + EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); + EXPECT_EQ(1UL, config_->stats().upgraded_to_ssl_.value()); + EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); + + clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM + 1); + client_login_data = Buffer::InstancePtr(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); + EXPECT_EQ(MySQLSession::State::ChallengeResp41, filter_->getSession().getState()); + + std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK, 0, CHALLENGE_RESP_SEQ_NUM); + Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); + EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); + + Buffer::OwnedImpl query_create_index("!@#$encr$#@!"); + BufferHelper::encodeHdr(query_create_index, 0); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(query_create_index, false)); + EXPECT_EQ(MySQLSession::State::ReqResp, filter_->getSession().getState()); +} + /** * Test MySQL Handshake with protocol version 320 * Server responds with Auth Switch @@ -387,7 +498,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -398,7 +509,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_AUTH_SWITCH); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); std::string auth_switch_resp = encodeAuthSwitchResp(); Buffer::InstancePtr client_switch_resp(new Buffer::OwnedImpl(auth_switch_resp)); @@ -407,7 +518,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchTest) { std::string srv_resp_ok_data = encodeClientLoginResp(MYSQL_RESP_OK, 1); Buffer::InstancePtr server_resp_ok_data(new Buffer::OwnedImpl(srv_resp_ok_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_ok_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_ok_data, false)); EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); } @@ -426,7 +537,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchAuthSwitchTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -437,7 +548,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchAuthSwitchTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_AUTH_SWITCH); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); std::string auth_switch_resp = encodeAuthSwitchResp(); Buffer::InstancePtr client_switch_resp(new Buffer::OwnedImpl(auth_switch_resp)); @@ -446,7 +557,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchAuthSwitchTest) { std::string srv_resp_ok_data = encodeClientLoginResp(MYSQL_RESP_AUTH_SWITCH, 1); Buffer::InstancePtr server_resp_ok_data(new Buffer::OwnedImpl(srv_resp_ok_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_ok_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_ok_data, false)); EXPECT_EQ(MySQLSession::State::NotHandled, filter_->getSession().getState()); } @@ -465,7 +576,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchErrTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -476,7 +587,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchErrTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_AUTH_SWITCH); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); std::string auth_switch_resp = encodeAuthSwitchResp(); Buffer::InstancePtr client_switch_resp(new Buffer::OwnedImpl(auth_switch_resp)); @@ -485,7 +596,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchErrTest) { std::string srv_resp_ok_data = encodeClientLoginResp(MYSQL_RESP_ERR, 1); Buffer::InstancePtr server_resp_ok_data(new Buffer::OwnedImpl(srv_resp_ok_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_ok_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_ok_data, false)); EXPECT_EQ(MySQLSession::State::Resync, filter_->getSession().getState()); Command mysql_cmd_encode{}; @@ -516,7 +627,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchIncompleteRespcode) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -527,7 +638,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchIncompleteRespcode) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_AUTH_SWITCH); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); std::string auth_switch_resp = encodeAuthSwitchResp(); Buffer::InstancePtr client_switch_resp(new Buffer::OwnedImpl(auth_switch_resp)); @@ -536,7 +647,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchIncompleteRespcode) { std::string srv_resp_ok_data = encodeMessage(0, 1); Buffer::InstancePtr server_resp_ok_data(new Buffer::OwnedImpl(srv_resp_ok_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_ok_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_ok_data, false)); EXPECT_EQ(MySQLSession::State::NotHandled, filter_->getSession().getState()); } @@ -555,7 +666,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchErrFailResync) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -566,7 +677,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchErrFailResync) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_AUTH_SWITCH); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); std::string auth_switch_resp = encodeAuthSwitchResp(); Buffer::InstancePtr client_switch_resp(new Buffer::OwnedImpl(auth_switch_resp)); @@ -575,7 +686,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchErrFailResync) { std::string srv_resp_ok_data = encodeClientLoginResp(MYSQL_RESP_ERR, 1); Buffer::InstancePtr server_resp_ok_data(new Buffer::OwnedImpl(srv_resp_ok_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_ok_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_ok_data, false)); EXPECT_EQ(MySQLSession::State::Resync, filter_->getSession().getState()); Command mysql_cmd_encode{}; @@ -590,7 +701,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchErrFailResync) { } /** - * Negative Testing MySQL Handshake with protocol version 320 + * MySQL Handshake with protocol version 320 * Server responds with Auth Switch More * SM: greeting(p=10) -> challenge-req(v320) -> serv-resp-auth-switch -> * -> auth_switch_resp -> serv-resp-auth-switch-more @@ -604,7 +715,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchMoreandMore) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -615,7 +726,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchMoreandMore) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_AUTH_SWITCH); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); std::string auth_switch_resp = encodeAuthSwitchResp(); Buffer::InstancePtr client_switch_resp(new Buffer::OwnedImpl(auth_switch_resp)); @@ -624,8 +735,8 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchMoreandMore) { std::string srv_resp_ok_data = encodeClientLoginResp(MYSQL_RESP_MORE, 1); Buffer::InstancePtr server_resp_ok_data(new Buffer::OwnedImpl(srv_resp_ok_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_ok_data, false)); - EXPECT_EQ(MySQLSession::State::AuthSwitchResp, filter_->getSession().getState()); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_ok_data, false)); + EXPECT_EQ(MySQLSession::State::AuthSwitchMore, filter_->getSession().getState()); } /** @@ -643,7 +754,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchMoreandUnhandled) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -654,7 +765,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchMoreandUnhandled) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_AUTH_SWITCH); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); std::string auth_switch_resp = encodeAuthSwitchResp(); Buffer::InstancePtr client_switch_resp(new Buffer::OwnedImpl(auth_switch_resp)); @@ -663,7 +774,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchMoreandUnhandled) { std::string srv_resp_ok_data = encodeClientLoginResp(0x32, 1); Buffer::InstancePtr server_resp_ok_data(new Buffer::OwnedImpl(srv_resp_ok_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_ok_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_ok_data, false)); EXPECT_EQ(MySQLSession::State::NotHandled, filter_->getSession().getState()); } @@ -681,24 +792,25 @@ TEST_F(MySQLFilterTest, MySqlHandshake41Ok2GreetTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string greeting_data2 = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data2(new Buffer::OwnedImpl(greeting_data2)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data2, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data2, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); + EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); EXPECT_EQ(1UL, config_->stats().protocol_errors_.value()); std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); - EXPECT_EQ(2UL, config_->stats().login_attempts_.value()); + EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); EXPECT_EQ(MySQLSession::State::ChallengeResp41, filter_->getSession().getState()); std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); } @@ -717,7 +829,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake41Ok2CloginTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); @@ -735,7 +847,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake41Ok2CloginTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); } @@ -760,7 +872,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake41OkOOOLoginTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); } @@ -786,12 +898,12 @@ TEST_F(MySQLFilterTest, MySqlHandshake41OkOOOFullLoginTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); EXPECT_EQ(2UL, config_->stats().protocol_errors_.value()); } @@ -811,12 +923,12 @@ TEST_F(MySQLFilterTest, MySqlHandshake41OkGreetingLoginOKTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); EXPECT_EQ(1UL, config_->stats_.protocol_errors_.value()); } @@ -835,7 +947,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320WrongCloginSeqTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", 2); @@ -860,7 +972,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchWrongSeqTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -876,12 +988,12 @@ TEST_F(MySQLFilterTest, MySqlHandshake320AuthSwitchWrongSeqTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_AUTH_SWITCH); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(MySQLSession::State::AuthSwitchResp, filter_->getSession().getState()); std::string srv_resp_ok_data = encodeClientLoginResp(MYSQL_RESP_OK, 1); Buffer::InstancePtr server_resp_ok_data(new Buffer::OwnedImpl(srv_resp_ok_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_ok_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_ok_data, false)); EXPECT_EQ(MySQLSession::State::AuthSwitchResp, filter_->getSession().getState()); } @@ -900,7 +1012,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320WrongServerRespCode) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -911,7 +1023,7 @@ TEST_F(MySQLFilterTest, MySqlHandshake320WrongServerRespCode) { std::string srv_resp_ok_data = encodeClientLoginResp(0x53, 0); Buffer::InstancePtr server_resp_ok_data(new Buffer::OwnedImpl(srv_resp_ok_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_ok_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_ok_data, false)); EXPECT_EQ(MySQLSession::State::NotHandled, filter_->getSession().getState()); Buffer::OwnedImpl client_query_data; @@ -934,7 +1046,7 @@ TEST_F(MySQLFilterTest, MySqlWrongHdrPkt) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(0, "user1", CHALLENGE_SEQ_NUM); @@ -945,7 +1057,7 @@ TEST_F(MySQLFilterTest, MySqlWrongHdrPkt) { std::string srv_resp_ok_data = encodeClientLoginResp(0x53, 0); Buffer::InstancePtr server_resp_ok_data(new Buffer::OwnedImpl(srv_resp_ok_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_ok_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_ok_data, false)); EXPECT_EQ(MySQLSession::State::NotHandled, filter_->getSession().getState()); Buffer::OwnedImpl client_query_data("123"); @@ -968,7 +1080,7 @@ TEST_F(MySQLFilterTest, MySqlLoginAndQueryTest) { std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*greet_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); @@ -979,7 +1091,7 @@ TEST_F(MySQLFilterTest, MySqlLoginAndQueryTest) { std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK); Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*server_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); Command mysql_cmd_encode{}; @@ -996,7 +1108,7 @@ TEST_F(MySQLFilterTest, MySqlLoginAndQueryTest) { srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK, 0, 1); Buffer::InstancePtr request_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*request_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*request_resp_data, false)); EXPECT_EQ(MySQLSession::State::ReqResp, filter_->getSession().getState()); mysql_cmd_encode.setCmd(Command::Cmd::Query); @@ -1012,7 +1124,7 @@ TEST_F(MySQLFilterTest, MySqlLoginAndQueryTest) { srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK, 0, 1); Buffer::InstancePtr show_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*show_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*show_resp_data, false)); EXPECT_EQ(MySQLSession::State::ReqResp, filter_->getSession().getState()); mysql_cmd_encode.setCmd(Command::Cmd::Query); @@ -1029,7 +1141,7 @@ TEST_F(MySQLFilterTest, MySqlLoginAndQueryTest) { srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK, 0, 1); Buffer::InstancePtr create_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*create_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*create_resp_data, false)); EXPECT_EQ(MySQLSession::State::ReqResp, filter_->getSession().getState()); mysql_cmd_encode.setCmd(Command::Cmd::Query); @@ -1047,7 +1159,7 @@ TEST_F(MySQLFilterTest, MySqlLoginAndQueryTest) { srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK, 0, 1); Buffer::InstancePtr create_index_resp_data(new Buffer::OwnedImpl(srv_resp_data)); EXPECT_EQ(Envoy::Network::FilterStatus::Continue, - filter_->onData(*create_index_resp_data, false)); + filter_->onWrite(*create_index_resp_data, false)); EXPECT_EQ(MySQLSession::State::ReqResp, filter_->getSession().getState()); mysql_cmd_encode.setCmd(Command::Cmd::FieldList); @@ -1064,10 +1176,802 @@ TEST_F(MySQLFilterTest, MySqlLoginAndQueryTest) { srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK, 0, 1); Buffer::InstancePtr field_list_resp_data(new Buffer::OwnedImpl(srv_resp_data)); - EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*field_list_resp_data, false)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*field_list_resp_data, false)); EXPECT_EQ(MySQLSession::State::ReqResp, filter_->getSession().getState()); } +/** + * Test RSA mediation for caching_sha2_password full authentication flow. + * SSL termination + cache miss: client sends cleartext password over TLS, + * filter RSA-encrypts it for the plaintext upstream connection. + */ +TEST_F(MySQLFilterTest, MySqlCachingSha2FullAuthRsaMediation) { + initialize(MySQLProxyProto::REQUIRE); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Step 1: Server greeting with caching_sha2_password plugin. + std::string greeting_data = encodeServerGreetingCachingSha2(); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); + + // Step 2: Client SSL request (seq=1). + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr ssl_data(new Buffer::OwnedImpl(ssl_req)); + EXPECT_CALL(connection_, startSecureTransport()).WillOnce(testing::Return(true)); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*ssl_data, false)); + EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); + + // Step 3: Client login (seq=2 from client, should be rewritten to seq=1 for server). + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM + 1); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + EXPECT_EQ(MySQLSession::State::ChallengeResp41, filter_->getSession().getState()); + + // Step 4: Server responds with AuthMoreData(0x04) = full auth required (raw seq=2). + std::string auth_more_data = + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM); + Buffer::InstancePtr auth_more(new Buffer::OwnedImpl(auth_more_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*auth_more, false)); + EXPECT_EQ(MySQLSession::State::AuthSwitchMore, filter_->getSession().getState()); + EXPECT_EQ(RsaAuthState::WaitingClientPassword, filter_->getRsaAuthState()); + + // Step 5: Client sends cleartext password (seq=4 from client perspective). + // The filter should intercept this and inject a request-public-key packet. + std::string password = "secret"; + std::string pw_payload = password + '\0'; + std::string pw_data = encodeRawPacket(pw_payload, 4); + + Buffer::OwnedImpl captured_request_key; + EXPECT_CALL(filter_callbacks_, injectReadDataToFilterChain(_, false)) + .WillOnce([&](Buffer::Instance& buf, bool) { captured_request_key.add(buf); }); + + Buffer::InstancePtr pw_buf(new Buffer::OwnedImpl(pw_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*pw_buf, false)); + EXPECT_EQ(RsaAuthState::WaitingServerKey, filter_->getRsaAuthState()); + + // Verify the request-public-key packet: [len=1][seq=3][0x02] + ASSERT_EQ(5u, captured_request_key.length()); + uint32_t req_len = 0; + uint8_t req_seq = 0; + BufferHelper::peekHdr(captured_request_key, req_len, req_seq); + EXPECT_EQ(1u, req_len); + EXPECT_EQ(3u, req_seq); + BufferHelper::consumeHdr(captured_request_key); + uint8_t req_code; + BufferHelper::readUint8(captured_request_key, req_code); + EXPECT_EQ(MYSQL_REQUEST_PUBLIC_KEY, req_code); + + // Step 6: Generate an RSA-2048 key pair for the test. + bssl::UniquePtr gen_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)); + ASSERT_TRUE(gen_ctx); + ASSERT_GT(EVP_PKEY_keygen_init(gen_ctx.get()), 0); + ASSERT_GT(EVP_PKEY_CTX_set_rsa_keygen_bits(gen_ctx.get(), 2048), 0); + EVP_PKEY* raw_pkey = nullptr; + ASSERT_GT(EVP_PKEY_keygen(gen_ctx.get(), &raw_pkey), 0); + bssl::UniquePtr pkey(raw_pkey); + + // Extract public key PEM. + bssl::UniquePtr bio(BIO_new(BIO_s_mem())); + PEM_write_bio_PUBKEY(bio.get(), pkey.get()); + char* pem_data; + long pem_len = BIO_get_mem_data(bio.get(), &pem_data); + std::string pem_key(pem_data, pem_len); + + // Step 7: Server sends PEM key as AuthMoreData (raw seq=4). + // The filter should intercept this and inject the RSA-encrypted password. + Buffer::OwnedImpl captured_encrypted_pw; + EXPECT_CALL(filter_callbacks_, injectReadDataToFilterChain(_, false)) + .WillOnce([&](Buffer::Instance& buf, bool) { captured_encrypted_pw.add(buf); }); + + std::string key_packet = + encodeAuthMoreDataPacket(std::vector(pem_key.begin(), pem_key.end()), 4); + Buffer::InstancePtr key_buf(new Buffer::OwnedImpl(key_packet)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*key_buf, false)); + EXPECT_EQ(RsaAuthState::WaitingServerResult, filter_->getRsaAuthState()); + + // Verify the encrypted password packet header: [len=256][seq=5][encrypted_data] + ASSERT_GT(captured_encrypted_pw.length(), 4u); + uint32_t enc_len = 0; + uint8_t enc_seq = 0; + BufferHelper::peekHdr(captured_encrypted_pw, enc_len, enc_seq); + EXPECT_EQ(256u, enc_len); // RSA-2048 produces 256-byte ciphertext + EXPECT_EQ(5u, enc_seq); + + // Decrypt and verify the password XOR scramble. + BufferHelper::consumeHdr(captured_encrypted_pw); + std::string ciphertext; + BufferHelper::readStringBySize(captured_encrypted_pw, enc_len, ciphertext); + + bssl::UniquePtr dec_ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); + ASSERT_TRUE(dec_ctx); + ASSERT_GT(EVP_PKEY_decrypt_init(dec_ctx.get()), 0); + ASSERT_GT(EVP_PKEY_CTX_set_rsa_padding(dec_ctx.get(), RSA_PKCS1_OAEP_PADDING), 0); + ASSERT_GT(EVP_PKEY_CTX_set_rsa_oaep_md(dec_ctx.get(), EVP_sha1()), 0); + + size_t plain_len = 0; + ASSERT_GT(EVP_PKEY_decrypt(dec_ctx.get(), nullptr, &plain_len, + reinterpret_cast(ciphertext.data()), + ciphertext.size()), + 0); + std::vector plaintext(plain_len); + ASSERT_GT(EVP_PKEY_decrypt(dec_ctx.get(), plaintext.data(), &plain_len, + reinterpret_cast(ciphertext.data()), + ciphertext.size()), + 0); + plaintext.resize(plain_len); + + // plaintext = (password + \0) XOR scramble (cyclic 20-byte) + std::vector scramble = getAuthPluginData20(); // 20 bytes of 0xff + ASSERT_EQ(pw_payload.size(), plaintext.size()); + for (size_t i = 0; i < plaintext.size(); i++) { + uint8_t expected = static_cast(pw_payload[i]) ^ scramble[i % scramble.size()]; + EXPECT_EQ(expected, plaintext[i]) << "mismatch at byte " << i; + } + + // Step 8: Server sends OK (raw seq=6, rewritten to seq=5 for client). + std::string srv_ok = encodeClientLoginResp(MYSQL_RESP_OK, 0, 6); + Buffer::InstancePtr ok_buf(new Buffer::OwnedImpl(srv_ok)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*ok_buf, false)); + EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); +} + +/** + * Test that fast auth success (AuthMoreData 0x03) passes through without RSA mediation. + */ +TEST_F(MySQLFilterTest, MySqlCachingSha2FastAuthPassthrough) { + initialize(MySQLProxyProto::REQUIRE); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Server greeting with caching_sha2_password. + std::string greeting_data = encodeServerGreetingCachingSha2(); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // SSL request. + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr ssl_data(new Buffer::OwnedImpl(ssl_req)); + EXPECT_CALL(connection_, startSecureTransport()).WillOnce(testing::Return(true)); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*ssl_data, false)); + + // Client login. + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM + 1); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + EXPECT_EQ(MySQLSession::State::ChallengeResp41, filter_->getSession().getState()); + + // Server responds with AuthMoreData(0x03) = fast auth success. + std::string auth_more_data = + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FAST_AUTH_SUCCESS}, CHALLENGE_RESP_SEQ_NUM); + Buffer::InstancePtr auth_more(new Buffer::OwnedImpl(auth_more_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*auth_more, false)); + EXPECT_EQ(MySQLSession::State::AuthSwitchMore, filter_->getSession().getState()); + // RSA mediation should NOT be triggered. + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); + + // Server sends OK (raw seq=3: next seq after server's AuthMoreData at seq=2). + std::string srv_ok = encodeClientLoginResp(MYSQL_RESP_OK, 0, 3); + Buffer::InstancePtr ok_buf(new Buffer::OwnedImpl(srv_ok)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*ok_buf, false)); + EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); +} + +/** + * Test RSA mediation when server returns ERR after encrypted password. + */ +TEST_F(MySQLFilterTest, MySqlCachingSha2FullAuthRsaErr) { + initialize(MySQLProxyProto::REQUIRE); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Server greeting. + std::string greeting_data = encodeServerGreetingCachingSha2(); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // SSL request. + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr ssl_data(new Buffer::OwnedImpl(ssl_req)); + EXPECT_CALL(connection_, startSecureTransport()).WillOnce(testing::Return(true)); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*ssl_data, false)); + + // Client login. + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM + 1); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + + // AuthMoreData(0x04) = full auth required. + std::string auth_more_data = + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM); + Buffer::InstancePtr auth_more(new Buffer::OwnedImpl(auth_more_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*auth_more, false)); + EXPECT_EQ(RsaAuthState::WaitingClientPassword, filter_->getRsaAuthState()); + + // Client password. + std::string pw_data = encodeRawPacket(std::string("secret") + '\0', 4); + + EXPECT_CALL(filter_callbacks_, injectReadDataToFilterChain(_, false)); + Buffer::InstancePtr pw_buf(new Buffer::OwnedImpl(pw_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*pw_buf, false)); + EXPECT_EQ(RsaAuthState::WaitingServerKey, filter_->getRsaAuthState()); + + // Generate RSA key pair and send PEM key. + bssl::UniquePtr gen_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)); + ASSERT_TRUE(gen_ctx); + ASSERT_GT(EVP_PKEY_keygen_init(gen_ctx.get()), 0); + ASSERT_GT(EVP_PKEY_CTX_set_rsa_keygen_bits(gen_ctx.get(), 2048), 0); + EVP_PKEY* raw_pkey = nullptr; + ASSERT_GT(EVP_PKEY_keygen(gen_ctx.get(), &raw_pkey), 0); + bssl::UniquePtr pkey(raw_pkey); + bssl::UniquePtr bio(BIO_new(BIO_s_mem())); + PEM_write_bio_PUBKEY(bio.get(), pkey.get()); + char* pem_data; + long pem_len = BIO_get_mem_data(bio.get(), &pem_data); + std::string pem_key(pem_data, pem_len); + + EXPECT_CALL(filter_callbacks_, injectReadDataToFilterChain(_, false)); + std::string key_packet = + encodeAuthMoreDataPacket(std::vector(pem_key.begin(), pem_key.end()), 4); + Buffer::InstancePtr key_buf(new Buffer::OwnedImpl(key_packet)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*key_buf, false)); + EXPECT_EQ(RsaAuthState::WaitingServerResult, filter_->getRsaAuthState()); + + // Server responds with ERR (raw seq=6). + std::string srv_err = encodeClientLoginResp(MYSQL_RESP_ERR, 0, 6); + Buffer::InstancePtr err_buf(new Buffer::OwnedImpl(srv_err)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*err_buf, false)); + EXPECT_EQ(1UL, config_->stats().login_failures_.value()); + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); +} + +/** + * Test that RSA mediation is NOT triggered when downstream_ssl is DISABLE. + */ +TEST_F(MySQLFilterTest, MySqlCachingSha2NoTerminateSsl) { + initialize(); // downstream_ssl = DISABLE + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Server greeting with caching_sha2_password. + std::string greeting_data = encodeServerGreetingCachingSha2(); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + EXPECT_EQ(MySQLSession::State::ChallengeReq, filter_->getSession().getState()); + + // Client login (no SSL in this path). + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + EXPECT_EQ(MySQLSession::State::ChallengeResp41, filter_->getSession().getState()); + + // AuthMoreData(0x04) from server. + std::string auth_more_data = + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM); + Buffer::InstancePtr auth_more(new Buffer::OwnedImpl(auth_more_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*auth_more, false)); + // RSA mediation should NOT be triggered because downstream_ssl is DISABLE. + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); +} + +// ============================================================================= +// Extended coverage: SSL Terminated, SSL Passthrough, No-SSL with queries +// ============================================================================= + +/** + * SSL Terminated: basic login with mysql_native_password (no caching_sha2). + * Verifies seq rewriting without RSA mediation. + */ +TEST_F(MySQLFilterTest, MySqlSslTerminateNativePasswordLoginOk) { + initialize(MySQLProxyProto::REQUIRE); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Server greeting (standard, no caching_sha2_password). + std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // SSL request. + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr ssl_data(new Buffer::OwnedImpl(ssl_req)); + EXPECT_CALL(connection_, startSecureTransport()).WillOnce(testing::Return(true)); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*ssl_data, false)); + EXPECT_EQ(1UL, config_->stats().upgraded_to_ssl_.value()); + + // Client login (seq=2, rewritten to seq=1). + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM + 1); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); + + // Server OK (seq=2, rewritten to seq=3). + std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK, 0, CHALLENGE_RESP_SEQ_NUM); + Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); + EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); +} + +/** + * SSL Terminated: login followed by query execution. + * Verifies the filter works correctly after auth completes. + */ +TEST_F(MySQLFilterTest, MySqlSslTerminateLoginThenQuery) { + initialize(MySQLProxyProto::REQUIRE); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Greeting + SSL + Login + OK (same as native password test above). + std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr ssl_data(new Buffer::OwnedImpl(ssl_req)); + EXPECT_CALL(connection_, startSecureTransport()).WillOnce(testing::Return(true)); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*ssl_data, false)); + + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM + 1); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + + std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK, 0, CHALLENGE_RESP_SEQ_NUM); + Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); + EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); + + // Now send a query — should be processed normally after auth. + Command mysql_cmd{}; + mysql_cmd.setCmd(Command::Cmd::Query); + mysql_cmd.setData("SELECT 1"); + Buffer::OwnedImpl query_data; + mysql_cmd.encode(query_data); + BufferHelper::encodeHdr(query_data, 0); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(query_data, false)); + EXPECT_EQ(MySQLSession::State::ReqResp, filter_->getSession().getState()); + EXPECT_EQ(1UL, config_->stats().queries_parsed_.value()); +} + +/** + * SSL Terminated: full auth RSA followed by query execution. + * End-to-end: greeting → SSL → login → AuthMore(0x04) → pw → RSA → OK → query. + */ +TEST_F(MySQLFilterTest, MySqlSslTerminateRsaThenQuery) { + initialize(MySQLProxyProto::REQUIRE); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Greeting with caching_sha2_password. + std::string greeting_data = encodeServerGreetingCachingSha2(); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // SSL + login. + EXPECT_CALL(connection_, startSecureTransport()).WillOnce(testing::Return(true)); + Buffer::InstancePtr ssl_data( + new Buffer::OwnedImpl(encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM))); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*ssl_data, false)); + + Buffer::InstancePtr login_data( + new Buffer::OwnedImpl(encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM + 1))); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*login_data, false)); + + // AuthMoreData(0x04). + Buffer::InstancePtr auth_more(new Buffer::OwnedImpl( + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM))); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*auth_more, false)); + EXPECT_EQ(RsaAuthState::WaitingClientPassword, filter_->getRsaAuthState()); + + // Client password → intercepted, request-public-key injected. + EXPECT_CALL(filter_callbacks_, injectReadDataToFilterChain(_, false)); + Buffer::InstancePtr pw_buf( + new Buffer::OwnedImpl(encodeRawPacket(std::string("secret") + '\0', 4))); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*pw_buf, false)); + + // PEM key → intercepted, encrypted password injected. + bssl::UniquePtr gen_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)); + EVP_PKEY_keygen_init(gen_ctx.get()); + EVP_PKEY_CTX_set_rsa_keygen_bits(gen_ctx.get(), 2048); + EVP_PKEY* raw_pkey = nullptr; + EVP_PKEY_keygen(gen_ctx.get(), &raw_pkey); + bssl::UniquePtr pkey(raw_pkey); + bssl::UniquePtr bio(BIO_new(BIO_s_mem())); + PEM_write_bio_PUBKEY(bio.get(), pkey.get()); + char* pem_data; + long pem_len = BIO_get_mem_data(bio.get(), &pem_data); + std::string pem_key(pem_data, pem_len); + + EXPECT_CALL(filter_callbacks_, injectReadDataToFilterChain(_, false)); + Buffer::InstancePtr key_buf(new Buffer::OwnedImpl( + encodeAuthMoreDataPacket(std::vector(pem_key.begin(), pem_key.end()), 4))); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*key_buf, false)); + EXPECT_EQ(RsaAuthState::WaitingServerResult, filter_->getRsaAuthState()); + + // Server OK (raw seq=6). + Buffer::InstancePtr ok_buf(new Buffer::OwnedImpl(encodeClientLoginResp(MYSQL_RESP_OK, 0, 6))); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*ok_buf, false)); + EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); + + // Query after RSA auth — verify seq numbers are correct and filter works. + Command mysql_cmd{}; + mysql_cmd.setCmd(Command::Cmd::Query); + mysql_cmd.setData("SELECT 1"); + Buffer::OwnedImpl query_data; + mysql_cmd.encode(query_data); + BufferHelper::encodeHdr(query_data, 0); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(query_data, false)); + EXPECT_EQ(MySQLSession::State::ReqResp, filter_->getSession().getState()); + EXPECT_EQ(1UL, config_->stats().queries_parsed_.value()); +} + +/** + * SSL Passthrough: server greeting with SSL → SSL pass-through → auth more data. + * Verifies that caching_sha2 auth more goes through unmodified in passthrough mode. + */ +TEST_F(MySQLFilterTest, MySqlSslPassthroughCachingSha2) { + initialize(); // downstream_ssl = DISABLE + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Server greeting (passes through). + std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // Client SSL request → enters SslPt (passthrough) state. + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr ssl_data(new Buffer::OwnedImpl(ssl_req)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*ssl_data, false)); + EXPECT_EQ(MySQLSession::State::SslPt, filter_->getSession().getState()); + EXPECT_EQ(1UL, config_->stats().upgraded_to_ssl_.value()); + + // All further data is opaque (encrypted), filter just passes through. + Buffer::OwnedImpl encrypted_data("encrypted-login-packet-bytes"); + BufferHelper::encodeHdr(encrypted_data, CHALLENGE_SEQ_NUM + 1); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(encrypted_data, false)); + EXPECT_EQ(MySQLSession::State::SslPt, filter_->getSession().getState()); + + // RSA mediation never triggers. + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); +} + +/** + * SSL Terminated: startSecureTransport() fails → connection closed. + */ +TEST_F(MySQLFilterTest, MySqlSslTerminateStartTlsFails) { + initialize(MySQLProxyProto::REQUIRE); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // SSL request, but startSecureTransport fails. + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr ssl_data(new Buffer::OwnedImpl(ssl_req)); + EXPECT_CALL(connection_, startSecureTransport()).WillOnce(testing::Return(false)); + EXPECT_CALL(connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*ssl_data, false)); +} + +/** + * No-SSL: caching_sha2 fast auth (0x03) without any SSL involved. + * Verifies the filter handles auth more data correctly without SSL termination. + */ +TEST_F(MySQLFilterTest, MySqlNoSslCachingSha2FastAuth) { + initialize(); // downstream_ssl = DISABLE + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Server greeting with caching_sha2_password. + std::string greeting_data = encodeServerGreetingCachingSha2(); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // Client login (no SSL). + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); + + // Server responds with fast auth success (0x03). + std::string auth_more_data = + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FAST_AUTH_SUCCESS}, CHALLENGE_RESP_SEQ_NUM); + Buffer::InstancePtr auth_more(new Buffer::OwnedImpl(auth_more_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*auth_more, false)); + EXPECT_EQ(MySQLSession::State::AuthSwitchMore, filter_->getSession().getState()); + // No RSA mediation without SSL termination. + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); + + // Server OK. + std::string ok_data = encodeClientLoginResp(MYSQL_RESP_OK, 0, 3); + Buffer::InstancePtr ok_buf(new Buffer::OwnedImpl(ok_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*ok_buf, false)); + EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); +} + +/** + * No-SSL: caching_sha2 full auth (0x04) without SSL termination. + * The filter should NOT intercept — full auth is handled by client/server directly. + */ +TEST_F(MySQLFilterTest, MySqlNoSslCachingSha2FullAuth) { + initialize(); // downstream_ssl = DISABLE + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Server greeting with caching_sha2_password. + std::string greeting_data = encodeServerGreetingCachingSha2(); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // Client login (no SSL). + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + + // Server responds with full auth required (0x04). + std::string auth_more_data = + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM); + Buffer::InstancePtr auth_more(new Buffer::OwnedImpl(auth_more_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*auth_more, false)); + // RSA mediation NOT triggered (downstream_ssl is DISABLE). + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); + + // Client sends request-public-key (0x02) — passes through to server. + std::string req_key = encodeRawPacket(std::string(1, MYSQL_REQUEST_PUBLIC_KEY), 3); + Buffer::InstancePtr req_key_buf(new Buffer::OwnedImpl(req_key)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*req_key_buf, false)); + + // No interception — filter stays inactive. + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); +} + +/** + * No-SSL: plain login followed by query. + * Basic end-to-end without any SSL. + */ +TEST_F(MySQLFilterTest, MySqlNoSslLoginThenQuery) { + initialize(); // downstream_ssl = DISABLE + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); + + std::string srv_resp_data = encodeClientLoginResp(MYSQL_RESP_OK); + Buffer::InstancePtr server_resp_data(new Buffer::OwnedImpl(srv_resp_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*server_resp_data, false)); + EXPECT_EQ(MySQLSession::State::Req, filter_->getSession().getState()); + + // Query. + Command mysql_cmd{}; + mysql_cmd.setCmd(Command::Cmd::Query); + mysql_cmd.setData("CREATE TABLE t (id INT)"); + Buffer::OwnedImpl query_data; + mysql_cmd.encode(query_data); + BufferHelper::encodeHdr(query_data, 0); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(query_data, false)); + EXPECT_EQ(MySQLSession::State::ReqResp, filter_->getSession().getState()); + EXPECT_EQ(1UL, config_->stats().queries_parsed_.value()); +} + +/** + * SSL REQUIRE mode: client that does NOT send SSL request gets rejected. + */ +TEST_F(MySQLFilterTest, MySqlSslRequireRejectsNonSslClient) { + initialize(MySQLProxyProto::REQUIRE); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Server greeting. + std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // Client sends login directly (no SSL request) — should be rejected. + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_CALL(connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); +} + +/** + * SSL REQUIRE mode: client that sends SSL request is accepted. + */ +TEST_F(MySQLFilterTest, MySqlSslRequireAcceptsSslClient) { + initialize(MySQLProxyProto::REQUIRE); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // Client sends SSL request — accepted. + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr ssl_data(new Buffer::OwnedImpl(ssl_req)); + EXPECT_CALL(connection_, startSecureTransport()).WillOnce(testing::Return(true)); + EXPECT_CALL(connection_, close(_)).Times(0); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*ssl_data, false)); + + // Client login after SSL — no rejection. + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM + 1); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); +} + +/** + * SSL ALLOW mode: client without SSL is accepted. + */ +TEST_F(MySQLFilterTest, MySqlSslAllowAcceptsNonSslClient) { + initialize(MySQLProxyProto::ALLOW); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // Client sends login directly (no SSL) — should be accepted in ALLOW mode. + std::string clogin_data = encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr client_login_data(new Buffer::OwnedImpl(clogin_data)); + EXPECT_CALL(connection_, close(_)).Times(0); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*client_login_data, false)); + EXPECT_EQ(1UL, config_->stats().login_attempts_.value()); +} + +/** + * SSL ALLOW mode: client with SSL initiates TLS, RSA mediation works. + */ +TEST_F(MySQLFilterTest, MySqlSslAllowWithSslClientRsaMediation) { + initialize(MySQLProxyProto::ALLOW); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Greeting with caching_sha2_password. + std::string greeting_data = encodeServerGreetingCachingSha2(); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // SSL request — accepted in ALLOW mode. + EXPECT_CALL(connection_, startSecureTransport()).WillOnce(testing::Return(true)); + Buffer::InstancePtr ssl_data( + new Buffer::OwnedImpl(encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM))); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*ssl_data, false)); + EXPECT_EQ(1UL, config_->stats().upgraded_to_ssl_.value()); + + // Client login. + Buffer::InstancePtr login_data( + new Buffer::OwnedImpl(encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM + 1))); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*login_data, false)); + + // AuthMoreData(0x04) — full auth required, should trigger RSA mediation. + Buffer::InstancePtr auth_more(new Buffer::OwnedImpl( + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM))); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*auth_more, false)); + EXPECT_EQ(RsaAuthState::WaitingClientPassword, filter_->getRsaAuthState()); + + // Client password — intercepted, request-public-key injected. + EXPECT_CALL(filter_callbacks_, injectReadDataToFilterChain(_, false)); + Buffer::InstancePtr pw_buf( + new Buffer::OwnedImpl(encodeRawPacket(std::string("secret") + '\0', 4))); + EXPECT_EQ(Envoy::Network::FilterStatus::StopIteration, filter_->onData(*pw_buf, false)); + EXPECT_EQ(RsaAuthState::WaitingServerKey, filter_->getRsaAuthState()); +} + +/** + * SSL ALLOW mode: non-SSL client with caching_sha2 — NO RSA mediation. + * Client handles RSA directly with the server. + */ +TEST_F(MySQLFilterTest, MySqlSslAllowNonSslCachingSha2NoMediation) { + initialize(MySQLProxyProto::ALLOW); + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + std::string greeting_data = encodeServerGreetingCachingSha2(); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // Client sends login directly (no SSL). + Buffer::InstancePtr login_data( + new Buffer::OwnedImpl(encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM))); + EXPECT_CALL(connection_, close(_)).Times(0); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*login_data, false)); + + // AuthMoreData(0x04) — should NOT trigger RSA mediation (no SSL was terminated). + Buffer::InstancePtr auth_more(new Buffer::OwnedImpl( + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM))); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*auth_more, false)); + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); +} + +/** + * SSL DISABLE mode: caching_sha2 full auth passes through without mediation. + */ +TEST_F(MySQLFilterTest, MySqlSslDisableCachingSha2FullAuthNoMediation) { + initialize(); // DISABLE + + EXPECT_CALL(filter_callbacks_, connection()).WillRepeatedly(ReturnRef(connection_)); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + std::string greeting_data = encodeServerGreetingCachingSha2(); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + Buffer::InstancePtr login_data( + new Buffer::OwnedImpl(encodeClientLogin(CLIENT_PROTOCOL_41, "user1", CHALLENGE_SEQ_NUM))); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*login_data, false)); + + // AuthMoreData(0x04) — no RSA mediation in DISABLE mode. + Buffer::InstancePtr auth_more(new Buffer::OwnedImpl( + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM))); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*auth_more, false)); + EXPECT_EQ(RsaAuthState::Inactive, filter_->getRsaAuthState()); +} + +/** + * SSL DISABLE mode: SSL request passes through to server (passthrough behavior). + */ +TEST_F(MySQLFilterTest, MySqlSslDisableSslPassthrough) { + initialize(); // DISABLE + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onNewConnection()); + + std::string greeting_data = encodeServerGreeting(MYSQL_PROTOCOL_10); + Buffer::InstancePtr greet_data(new Buffer::OwnedImpl(greeting_data)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onWrite(*greet_data, false)); + + // Client sends SSL request — in DISABLE mode, it passes through (SslPt state). + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + Buffer::InstancePtr ssl_data(new Buffer::OwnedImpl(ssl_req)); + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(*ssl_data, false)); + EXPECT_EQ(MySQLSession::State::SslPt, filter_->getSession().getState()); + EXPECT_EQ(1UL, config_->stats().upgraded_to_ssl_.value()); +} + } // namespace MySQLProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/contrib/mysql_proxy/filters/network/test/mysql_ssl_allow_test_config.yaml b/contrib/mysql_proxy/filters/network/test/mysql_ssl_allow_test_config.yaml new file mode 100644 index 0000000000000..b6f69f62f054d --- /dev/null +++ b/contrib/mysql_proxy/filters/network/test/mysql_ssl_allow_test_config.yaml @@ -0,0 +1,53 @@ +admin: + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "{}" + address: + socket_address: + address: "{}" + port_value: 0 +static_resources: + clusters: + name: cluster_0 + connect_timeout: 2s + load_assignment: + cluster_name: cluster_0 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: "{}" + port_value: 0 + listeners: + name: listener_0 + address: + socket_address: + address: "{}" + port_value: 0 + filter_chains: + - filters: + - name: mysql + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.mysql_proxy.v3.MySQLProxy + stat_prefix: mysql_stats + downstream_ssl: ALLOW + - name: tcp + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: tcp_stats + cluster: cluster_0 + transport_socket: + name: starttls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.starttls.v3.StartTlsConfig + cleartext_socket_config: + tls_socket_config: + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{}" + private_key: + filename: "{}" diff --git a/contrib/mysql_proxy/filters/network/test/mysql_ssl_disable_test_config.yaml b/contrib/mysql_proxy/filters/network/test/mysql_ssl_disable_test_config.yaml new file mode 100644 index 0000000000000..cf291957162bd --- /dev/null +++ b/contrib/mysql_proxy/filters/network/test/mysql_ssl_disable_test_config.yaml @@ -0,0 +1,40 @@ +admin: + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "{}" + address: + socket_address: + address: "{}" + port_value: 0 +static_resources: + clusters: + name: cluster_0 + connect_timeout: 2s + load_assignment: + cluster_name: cluster_0 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: "{}" + port_value: 0 + listeners: + name: listener_0 + address: + socket_address: + address: "{}" + port_value: 0 + filter_chains: + - filters: + - name: mysql + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.mysql_proxy.v3.MySQLProxy + stat_prefix: mysql_stats + - name: tcp + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: tcp_stats + cluster: cluster_0 diff --git a/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc b/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc new file mode 100644 index 0000000000000..69bbda2378b9c --- /dev/null +++ b/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc @@ -0,0 +1,908 @@ +#include "envoy/extensions/transport_sockets/raw_buffer/v3/raw_buffer.pb.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/network/connection_impl.h" +#include "source/extensions/transport_sockets/raw_buffer/config.h" + +#include "test/integration/fake_upstream.h" +#include "test/integration/integration.h" +#include "test/integration/ssl_utility.h" +#include "test/integration/utility.h" +#include "test/test_common/network_utility.h" + +#include "contrib/mysql_proxy/filters/network/source/mysql_codec.h" +#include "contrib/mysql_proxy/filters/network/source/mysql_codec_clogin.h" +#include "contrib/mysql_proxy/filters/network/source/mysql_codec_clogin_resp.h" +#include "contrib/mysql_proxy/filters/network/source/mysql_codec_command.h" +#include "contrib/mysql_proxy/filters/network/source/mysql_codec_greeting.h" +#include "contrib/mysql_proxy/filters/network/source/mysql_utils.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "mysql_test_utils.h" +#include "openssl/evp.h" +#include "openssl/pem.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace MySQLProxy { + +// Client connection that supports mid-stream TLS upgrade (startTLS). +// Adapted from test/extensions/transport_sockets/starttls/starttls_integration_test.cc. +class StartTlsClientConnection : public Network::ClientConnectionImpl { +public: + StartTlsClientConnection(Event::Dispatcher& dispatcher, + const Network::Address::InstanceConstSharedPtr& remote_address, + const Network::Address::InstanceConstSharedPtr& source_address, + Network::TransportSocketPtr&& transport_socket, + const Network::ConnectionSocket::OptionsSharedPtr& options) + : ClientConnectionImpl(dispatcher, remote_address, source_address, + std::move(transport_socket), options, nullptr) {} + + // Swap the raw transport socket for a TLS one and trigger the TLS handshake. + void upgradeToTls(Network::TransportSocketPtr&& tls_socket) { + transport_socket_ = std::move(tls_socket); + transport_socket_->setTransportSocketCallbacks(*this); + connecting_ = true; + ioHandle().activateFileEvents(Event::FileReadyType::Write); + } +}; + +class MySQLSSLIntegrationTest : public testing::TestWithParam, + public MySQLTestUtils, + public BaseIntegrationTest { + std::string mysqlSslConfig() { + return fmt::format( + fmt::runtime(TestEnvironment::readFileToStringForTest(TestEnvironment::runfilesPath( + "contrib/mysql_proxy/filters/network/test/mysql_ssl_require_test_config.yaml"))), + Platform::null_device_path, Network::Test::getLoopbackAddressString(GetParam()), + Network::Test::getLoopbackAddressString(GetParam()), + Network::Test::getAnyAddressString(GetParam()), + TestEnvironment::runfilesPath("test/config/integration/certs/servercert.pem"), + TestEnvironment::runfilesPath("test/config/integration/certs/serverkey.pem")); + } + +public: + MySQLSSLIntegrationTest() : BaseIntegrationTest(GetParam(), mysqlSslConfig()) { + skip_tag_extraction_rule_check_ = true; + } + + void initialize() override { + EXPECT_CALL(*mock_buffer_factory_, createBuffer_(_, _, _)) + .WillOnce(Invoke([&](std::function below_low, std::function above_high, + std::function above_overflow) -> Buffer::Instance* { + client_write_buffer_ = + new NiceMock(below_low, above_high, above_overflow); + ON_CALL(*client_write_buffer_, move(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); + ON_CALL(*client_write_buffer_, drain(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); + return client_write_buffer_; + })) + .WillOnce(Invoke([&](std::function below_low, std::function above_high, + std::function above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); + })); + + // Create raw buffer and TLS transport socket factories. + auto raw_config = + std::make_unique(); + auto raw_factory = + std::make_unique(); + cleartext_context_ = Network::UpstreamTransportSocketFactoryPtr{ + raw_factory->createTransportSocketFactory(*raw_config, factory_context_).value()}; + + tls_context_manager_ = std::make_unique( + server_factory_context_); + tls_context_ = Ssl::createClientSslTransportSocketFactory({}, *tls_context_manager_, *api_); + + payload_reader_ = std::make_shared(*dispatcher_); + + BaseIntegrationTest::initialize(); + + // Create client connection with raw cleartext transport socket. + Network::Address::InstanceConstSharedPtr address = + Ssl::getSslAddress(version_, lookupPort("listener_0")); + conn_ = std::make_unique( + *dispatcher_, address, Network::Address::InstanceConstSharedPtr(), + cleartext_context_->createTransportSocket(nullptr, nullptr), nullptr); + conn_->enableHalfClose(true); + conn_->addConnectionCallbacks(connect_callbacks_); + conn_->addReadFilter(payload_reader_); + } + + // Upgrade the client connection to TLS. + // Wait for the filter to process the SSL request and call startSecureTransport() + // before initiating the client-side TLS handshake. + void upgradeClientToTls() { + test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); + conn_->upgradeToTls(tls_context_->createTransportSocket( + std::make_shared( + absl::string_view(""), std::vector(), std::vector()), + nullptr)); + connect_callbacks_.reset(); + while (!connect_callbacks_.connected() && !connect_callbacks_.closed()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + ASSERT_TRUE(connect_callbacks_.connected()); + } + + // Write data to client connection and wait for it to be sent. + void clientWrite(const std::string& data) { + uint64_t prev_drained = client_write_buffer_->bytesDrained(); + Buffer::OwnedImpl buf(data); + conn_->write(buf, false); + while (client_write_buffer_->bytesDrained() < prev_drained + data.length()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + } + + // Wait for specific payload from server (via Envoy) on the client side. + void clientWaitForData(const std::string& expected) { + payload_reader_->setDataToWaitFor(expected); + dispatcher_->run(Event::Dispatcher::RunType::Block); + } + + // Server greeting with caching_sha2_password and CLIENT_SSL. + std::string encodeServerGreetingCachingSha2() { + ServerGreeting greeting; + greeting.setProtocol(MYSQL_PROTOCOL_10); + greeting.setVersion(getVersion()); + greeting.setThreadId(MYSQL_THREAD_ID); + greeting.setAuthPluginData(getAuthPluginData20()); + greeting.setServerCap(CLIENT_PLUGIN_AUTH | CLIENT_SECURE_CONNECTION | CLIENT_SSL); + greeting.setServerCharset(MYSQL_SERVER_LANGUAGE); + greeting.setServerStatus(MYSQL_SERVER_STATUS); + greeting.setAuthPluginName("caching_sha2_password"); + Buffer::OwnedImpl buffer; + greeting.encode(buffer); + BufferHelper::encodeHdr(buffer, GREETING_SEQ_NUM); + return buffer.toString(); + } + + std::string encodeAuthMoreDataPacket(const std::vector& data, uint8_t seq) { + AuthMoreMessage auth_more; + auth_more.setAuthMoreData(data); + Buffer::OwnedImpl buffer; + auth_more.encode(buffer); + BufferHelper::encodeHdr(buffer, seq); + return buffer.toString(); + } + + std::string generateTestPemKey() { + bssl::UniquePtr gen_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)); + EVP_PKEY_keygen_init(gen_ctx.get()); + EVP_PKEY_CTX_set_rsa_keygen_bits(gen_ctx.get(), 2048); + EVP_PKEY* raw_pkey = nullptr; + EVP_PKEY_keygen(gen_ctx.get(), &raw_pkey); + bssl::UniquePtr pkey(raw_pkey); + bssl::UniquePtr bio(BIO_new(BIO_s_mem())); + PEM_write_bio_PUBKEY(bio.get(), pkey.get()); + char* pem_data; + long pem_len = BIO_get_mem_data(bio.get(), &pem_data); + return std::string(pem_data, pem_len); + } + + std::unique_ptr tls_context_manager_; + Network::UpstreamTransportSocketFactoryPtr tls_context_; + Network::UpstreamTransportSocketFactoryPtr cleartext_context_; + MockWatermarkBuffer* client_write_buffer_{nullptr}; + ConnectionStatusCallbacks connect_callbacks_; + std::unique_ptr conn_; + std::shared_ptr payload_reader_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, MySQLSSLIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); + +/** + * caching_sha2_password fast auth (cache hit) with SSL termination. + * + * Client ←TLS→ Envoy ←plaintext→ FakeUpstream + * greeting → SSL req → TLS upgrade → login → AuthMoreData(0x03) → OK + */ +TEST_P(MySQLSSLIntegrationTest, CachingSha2FastAuth) { + initialize(); + conn_->connect(); + + FakeRawConnectionPtr fake_upstream; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream)); + + // 1. Server greeting (cleartext, before TLS). + std::string greeting = encodeServerGreetingCachingSha2(); + ASSERT_TRUE(fake_upstream->write(greeting)); + clientWaitForData(greeting); + + // 2. Client sends SSL request (cleartext). + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + clientWrite(ssl_req); + // SSL request consumed by filter; filter calls startSecureTransport. + + // 3. Client upgrades to TLS. + upgradeClientToTls(); + + // 4. Client sends login over TLS (seq=2, rewritten to seq=1 for upstream). + std::string login = encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1); + clientWrite(login); + + // Upstream receives the rewritten login in cleartext. + std::string upstream_data; + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + &upstream_data)); + + // 5. Server responds with fast auth success (seq=2, rewritten to seq=3 for client). + ASSERT_TRUE(fake_upstream->write( + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FAST_AUTH_SUCCESS}, CHALLENGE_RESP_SEQ_NUM))); + + // 6. Server sends OK (seq=3, rewritten to seq=4 for client). + ASSERT_TRUE(fake_upstream->write(encodeClientLoginResp(MYSQL_RESP_OK, 0, 3))); + + // Wait for client to receive both responses. + // Use a short sleep + non-block dispatch to let data flow through. + timeSystem().advanceTimeWait(std::chrono::milliseconds(500)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + conn_->close(Network::ConnectionCloseType::FlushWrite); + ASSERT_TRUE(fake_upstream->waitForDisconnect()); + + test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); + test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 0); +} + +/** + * caching_sha2_password full auth (cache miss) with RSA mediation. + * + * greeting → SSL req → TLS → login → AuthMoreData(0x04) → client pw → + * [filter: request-public-key → PEM key → RSA encrypt] → OK + */ +TEST_P(MySQLSSLIntegrationTest, CachingSha2FullAuthRsaMediation) { + initialize(); + conn_->connect(); + + FakeRawConnectionPtr fake_upstream; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream)); + + // 1. Server greeting. + std::string greeting = encodeServerGreetingCachingSha2(); + ASSERT_TRUE(fake_upstream->write(greeting)); + clientWaitForData(greeting); + + // 2. SSL request + TLS upgrade. + clientWrite(encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM)); + upgradeClientToTls(); + + // 3. Client login over TLS. + clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); + + std::string upstream_data; + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + &upstream_data)); + + // 4. Server: full auth required (seq=2). + ASSERT_TRUE(fake_upstream->write( + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM))); + + // Wait for client to receive AuthMoreData (filter forwards it). + timeSystem().advanceTimeWait(std::chrono::milliseconds(200)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + // 5. Client sends cleartext password (seq=4 from client perspective). + std::string pw_payload = std::string("testpass") + '\0'; + Buffer::OwnedImpl pw_pkt; + BufferHelper::addString(pw_pkt, pw_payload); + BufferHelper::encodeHdr(pw_pkt, 4); + clientWrite(pw_pkt.toString()); + + // 6. Filter intercepts password, sends request-public-key (0x02, seq=3) to upstream. + size_t prev_len = upstream_data.length(); + ASSERT_TRUE(fake_upstream->waitForData( + [prev_len](const std::string& data) { return data.length() >= prev_len + 5; }, + &upstream_data)); + + // Verify request-public-key packet. + std::string req_key = upstream_data.substr(prev_len); + EXPECT_EQ(static_cast(req_key[3]), 3u); // seq + EXPECT_EQ(static_cast(req_key[4]), MYSQL_REQUEST_PUBLIC_KEY); + + // 7. Server sends PEM public key (seq=4). + std::string pem_key = generateTestPemKey(); + ASSERT_TRUE(fake_upstream->write( + encodeAuthMoreDataPacket(std::vector(pem_key.begin(), pem_key.end()), 4))); + + // 8. Filter RSA-encrypts password and sends to upstream (seq=5, 256 bytes). + prev_len = upstream_data.length(); + ASSERT_TRUE(fake_upstream->waitForData( + [prev_len](const std::string& data) { return data.length() >= prev_len + 4 + 256; }, + &upstream_data)); + + std::string enc_pkt = upstream_data.substr(prev_len); + uint32_t enc_len = static_cast(enc_pkt[0]) | (static_cast(enc_pkt[1]) << 8) | + (static_cast(enc_pkt[2]) << 16); + EXPECT_EQ(enc_len, 256u); // RSA-2048 ciphertext + EXPECT_EQ(static_cast(enc_pkt[3]), 5u); // seq + + // 9. Server sends OK (raw seq=6, rewritten to seq=5 for client). + ASSERT_TRUE(fake_upstream->write(encodeClientLoginResp(MYSQL_RESP_OK, 0, 6))); + + timeSystem().advanceTimeWait(std::chrono::milliseconds(500)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + conn_->close(Network::ConnectionCloseType::FlushWrite); + ASSERT_TRUE(fake_upstream->waitForDisconnect()); + + test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); + test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 0); +} + +/** + * Full auth — server rejects after RSA-encrypted password. + */ +TEST_P(MySQLSSLIntegrationTest, CachingSha2FullAuthRsaErr) { + initialize(); + conn_->connect(); + + FakeRawConnectionPtr fake_upstream; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream)); + + std::string greeting = encodeServerGreetingCachingSha2(); + ASSERT_TRUE(fake_upstream->write(greeting)); + clientWaitForData(greeting); + + clientWrite(encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM)); + upgradeClientToTls(); + clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); + + std::string upstream_data; + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + &upstream_data)); + + // Full auth required. + ASSERT_TRUE(fake_upstream->write( + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM))); + timeSystem().advanceTimeWait(std::chrono::milliseconds(200)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + // Client password. + Buffer::OwnedImpl pw_pkt; + BufferHelper::addString(pw_pkt, std::string("wrongpw") + '\0'); + BufferHelper::encodeHdr(pw_pkt, 4); + clientWrite(pw_pkt.toString()); + + // Wait for request-public-key. + size_t prev_len = upstream_data.length(); + ASSERT_TRUE(fake_upstream->waitForData( + [prev_len](const std::string& data) { return data.length() >= prev_len + 5; }, + &upstream_data)); + + // Send PEM key. + std::string pem_key = generateTestPemKey(); + ASSERT_TRUE(fake_upstream->write( + encodeAuthMoreDataPacket(std::vector(pem_key.begin(), pem_key.end()), 4))); + + // Wait for encrypted password. + prev_len = upstream_data.length(); + ASSERT_TRUE(fake_upstream->waitForData( + [prev_len](const std::string& data) { return data.length() >= prev_len + 4 + 256; }, + &upstream_data)); + + // Server sends ERR (raw seq=6). + ASSERT_TRUE(fake_upstream->write(encodeClientLoginResp(MYSQL_RESP_ERR, 0, 6))); + + timeSystem().advanceTimeWait(std::chrono::milliseconds(500)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + conn_->close(Network::ConnectionCloseType::FlushWrite); + ASSERT_TRUE(fake_upstream->waitForDisconnect()); + + test_server_->waitForCounterGe("mysql.mysql_stats.login_failures", 1); +} + +/** + * SSL Terminated: basic login with native password (no caching_sha2), then query. + * Verifies seq rewriting works for the full lifecycle. + */ +TEST_P(MySQLSSLIntegrationTest, SslTerminateLoginThenQuery) { + initialize(); + conn_->connect(); + + FakeRawConnectionPtr fake_upstream; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream)); + + // Greeting (standard, not caching_sha2). + std::string greeting = encodeServerGreeting(MYSQL_PROTOCOL_10); + ASSERT_TRUE(fake_upstream->write(greeting)); + clientWaitForData(greeting); + + // SSL request + TLS upgrade. + clientWrite(encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM)); + upgradeClientToTls(); + + // Client login over TLS. + clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); + + std::string upstream_data; + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + &upstream_data)); + + // Server OK (seq=2, rewritten to seq=3 for client). + ASSERT_TRUE(fake_upstream->write(encodeClientLoginResp(MYSQL_RESP_OK))); + + timeSystem().advanceTimeWait(std::chrono::milliseconds(200)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + // Send a query (seq=0 after resetSeq). + Command mysql_cmd{}; + mysql_cmd.setCmd(Command::Cmd::Query); + mysql_cmd.setData("SELECT 1"); + Buffer::OwnedImpl query_buf; + mysql_cmd.encode(query_buf); + BufferHelper::encodeHdr(query_buf, 0); + clientWrite(query_buf.toString()); + + // Upstream should receive the query. + size_t prev_len = upstream_data.length(); + ASSERT_TRUE(fake_upstream->waitForData( + [prev_len](const std::string& data) { return data.length() > prev_len; }, &upstream_data)); + + conn_->close(Network::ConnectionCloseType::FlushWrite); + ASSERT_TRUE(fake_upstream->waitForDisconnect()); + + test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); + test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + test_server_->waitForCounterGe("mysql.mysql_stats.queries_parsed", 1); +} + +/** + * SSL Terminated: RSA mediation followed by query execution. + * Full lifecycle: greeting → SSL → login → AuthMore(0x04) → RSA → OK → query. + */ +TEST_P(MySQLSSLIntegrationTest, CachingSha2FullAuthRsaThenQuery) { + initialize(); + conn_->connect(); + + FakeRawConnectionPtr fake_upstream; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream)); + + // Greeting + SSL + login. + std::string greeting = encodeServerGreetingCachingSha2(); + ASSERT_TRUE(fake_upstream->write(greeting)); + clientWaitForData(greeting); + + clientWrite(encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM)); + upgradeClientToTls(); + clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); + + std::string upstream_data; + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + &upstream_data)); + + // Full auth required → client password → RSA. + ASSERT_TRUE(fake_upstream->write( + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM))); + timeSystem().advanceTimeWait(std::chrono::milliseconds(200)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + Buffer::OwnedImpl pw_pkt; + BufferHelper::addString(pw_pkt, std::string("testpass") + '\0'); + BufferHelper::encodeHdr(pw_pkt, 4); + clientWrite(pw_pkt.toString()); + + // Wait for request-public-key. + size_t prev_len = upstream_data.length(); + ASSERT_TRUE(fake_upstream->waitForData( + [prev_len](const std::string& data) { return data.length() >= prev_len + 5; }, + &upstream_data)); + + // Send PEM key + wait for encrypted password. + std::string pem_key = generateTestPemKey(); + ASSERT_TRUE(fake_upstream->write( + encodeAuthMoreDataPacket(std::vector(pem_key.begin(), pem_key.end()), 4))); + prev_len = upstream_data.length(); + ASSERT_TRUE(fake_upstream->waitForData( + [prev_len](const std::string& data) { return data.length() >= prev_len + 4 + 256; }, + &upstream_data)); + + // Server OK (raw seq=6). + ASSERT_TRUE(fake_upstream->write(encodeClientLoginResp(MYSQL_RESP_OK, 0, 6))); + timeSystem().advanceTimeWait(std::chrono::milliseconds(200)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + // Now send a query — verifies seq numbers are correct after RSA mediation. + Command mysql_cmd{}; + mysql_cmd.setCmd(Command::Cmd::Query); + mysql_cmd.setData("SELECT 1"); + Buffer::OwnedImpl query_buf; + mysql_cmd.encode(query_buf); + BufferHelper::encodeHdr(query_buf, 0); + clientWrite(query_buf.toString()); + + prev_len = upstream_data.length(); + ASSERT_TRUE(fake_upstream->waitForData( + [prev_len](const std::string& data) { return data.length() > prev_len; }, &upstream_data)); + + conn_->close(Network::ConnectionCloseType::FlushWrite); + ASSERT_TRUE(fake_upstream->waitForDisconnect()); + + test_server_->waitForCounterGe("mysql.mysql_stats.queries_parsed", 1); +} + +// ============================================================================= +// DISABLE mode integration tests — plain TCP proxy, no SSL termination. +// ============================================================================= + +class MySQLDisableIntegrationTest : public testing::TestWithParam, + public MySQLTestUtils, + public BaseIntegrationTest { + std::string mysqlConfig() { + return fmt::format( + fmt::runtime(TestEnvironment::readFileToStringForTest(TestEnvironment::runfilesPath( + "contrib/mysql_proxy/filters/network/test/mysql_ssl_disable_test_config.yaml"))), + Platform::null_device_path, Network::Test::getLoopbackAddressString(GetParam()), + Network::Test::getLoopbackAddressString(GetParam()), + Network::Test::getAnyAddressString(GetParam())); + } + +public: + MySQLDisableIntegrationTest() : BaseIntegrationTest(GetParam(), mysqlConfig()) { + skip_tag_extraction_rule_check_ = true; + } + + void SetUp() override { BaseIntegrationTest::initialize(); } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, MySQLDisableIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); + +/** + * DISABLE mode: basic login, no SSL involved. + */ +TEST_P(MySQLDisableIntegrationTest, DisableBasicLogin) { + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + FakeRawConnectionPtr fake_upstream; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream)); + + // Greeting. + std::string greeting = encodeServerGreeting(MYSQL_PROTOCOL_10); + ASSERT_TRUE(fake_upstream->write(greeting)); + tcp_client->waitForData(greeting, true); + + // Client login (no SSL). + std::string login = encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM); + ASSERT_TRUE(tcp_client->write(login)); + + std::string upstream_data; + ASSERT_TRUE(fake_upstream->waitForData(login.length(), &upstream_data)); + EXPECT_EQ(login, upstream_data); + + // Server OK. + std::string ok_resp = encodeClientLoginResp(MYSQL_RESP_OK); + ASSERT_TRUE(fake_upstream->write(ok_resp)); + + std::string downstream(greeting); + downstream.append(ok_resp); + tcp_client->waitForData(downstream, true); + + tcp_client->close(); + ASSERT_TRUE(fake_upstream->waitForDisconnect()); + + test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 0); +} + +/** + * DISABLE mode: SSL request passes through to upstream (passthrough). + */ +TEST_P(MySQLDisableIntegrationTest, DisableSslPassthrough) { + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + FakeRawConnectionPtr fake_upstream; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream)); + + // Greeting. + std::string greeting = encodeServerGreeting(MYSQL_PROTOCOL_10); + ASSERT_TRUE(fake_upstream->write(greeting)); + tcp_client->waitForData(greeting, true); + + // Client sends SSL request — in DISABLE mode, it passes through. + std::string ssl_req = encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM); + ASSERT_TRUE(tcp_client->write(ssl_req)); + + std::string upstream_data; + ASSERT_TRUE(fake_upstream->waitForData(ssl_req.length(), &upstream_data)); + // The SSL request should be forwarded unmodified. + EXPECT_EQ(ssl_req, upstream_data); + + tcp_client->close(); + ASSERT_TRUE(fake_upstream->waitForDisconnect()); + + EXPECT_EQ(test_server_->counter("mysql.mysql_stats.upgraded_to_ssl")->value(), 1); +} + +// ============================================================================= +// ALLOW mode integration tests — terminate SSL if client requests, accept cleartext. +// Uses the same StartTlsClientConnection as the REQUIRE tests. +// ============================================================================= + +class MySQLAllowIntegrationTest : public testing::TestWithParam, + public MySQLTestUtils, + public BaseIntegrationTest { + std::string mysqlSslConfig() { + return fmt::format( + fmt::runtime(TestEnvironment::readFileToStringForTest(TestEnvironment::runfilesPath( + "contrib/mysql_proxy/filters/network/test/mysql_ssl_allow_test_config.yaml"))), + Platform::null_device_path, Network::Test::getLoopbackAddressString(GetParam()), + Network::Test::getLoopbackAddressString(GetParam()), + Network::Test::getAnyAddressString(GetParam()), + TestEnvironment::runfilesPath("test/config/integration/certs/servercert.pem"), + TestEnvironment::runfilesPath("test/config/integration/certs/serverkey.pem")); + } + +public: + MySQLAllowIntegrationTest() : BaseIntegrationTest(GetParam(), mysqlSslConfig()) { + skip_tag_extraction_rule_check_ = true; + } + + void initialize() override { + EXPECT_CALL(*mock_buffer_factory_, createBuffer_(_, _, _)) + .WillOnce(Invoke([&](std::function below_low, std::function above_high, + std::function above_overflow) -> Buffer::Instance* { + client_write_buffer_ = + new NiceMock(below_low, above_high, above_overflow); + ON_CALL(*client_write_buffer_, move(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); + ON_CALL(*client_write_buffer_, drain(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); + return client_write_buffer_; + })) + .WillRepeatedly(Invoke([](std::function below_low, std::function above_high, + std::function above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); + })); + + auto raw_config = + std::make_unique(); + auto raw_factory = + std::make_unique(); + cleartext_context_ = Network::UpstreamTransportSocketFactoryPtr{ + raw_factory->createTransportSocketFactory(*raw_config, factory_context_).value()}; + + tls_context_manager_ = std::make_unique( + server_factory_context_); + tls_context_ = Ssl::createClientSslTransportSocketFactory({}, *tls_context_manager_, *api_); + + payload_reader_ = std::make_shared(*dispatcher_); + + BaseIntegrationTest::initialize(); + + Network::Address::InstanceConstSharedPtr address = + Ssl::getSslAddress(version_, lookupPort("listener_0")); + conn_ = std::make_unique( + *dispatcher_, address, Network::Address::InstanceConstSharedPtr(), + cleartext_context_->createTransportSocket(nullptr, nullptr), nullptr); + conn_->enableHalfClose(true); + conn_->addConnectionCallbacks(connect_callbacks_); + conn_->addReadFilter(payload_reader_); + } + + void upgradeClientToTls() { + test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); + conn_->upgradeToTls(tls_context_->createTransportSocket( + std::make_shared( + absl::string_view(""), std::vector(), std::vector()), + nullptr)); + connect_callbacks_.reset(); + while (!connect_callbacks_.connected() && !connect_callbacks_.closed()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + ASSERT_TRUE(connect_callbacks_.connected()); + } + + void clientWrite(const std::string& data) { + uint64_t prev_drained = client_write_buffer_->bytesDrained(); + Buffer::OwnedImpl buf(data); + conn_->write(buf, false); + while (client_write_buffer_->bytesDrained() < prev_drained + data.length()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + } + + void clientWaitForData(const std::string& expected) { + payload_reader_->setDataToWaitFor(expected); + dispatcher_->run(Event::Dispatcher::RunType::Block); + } + + std::string encodeServerGreetingCachingSha2() { + ServerGreeting greeting; + greeting.setProtocol(MYSQL_PROTOCOL_10); + greeting.setVersion(getVersion()); + greeting.setThreadId(MYSQL_THREAD_ID); + greeting.setAuthPluginData(getAuthPluginData20()); + greeting.setServerCap(CLIENT_PLUGIN_AUTH | CLIENT_SECURE_CONNECTION | CLIENT_SSL); + greeting.setServerCharset(MYSQL_SERVER_LANGUAGE); + greeting.setServerStatus(MYSQL_SERVER_STATUS); + greeting.setAuthPluginName("caching_sha2_password"); + Buffer::OwnedImpl buffer; + greeting.encode(buffer); + BufferHelper::encodeHdr(buffer, GREETING_SEQ_NUM); + return buffer.toString(); + } + + std::string encodeAuthMoreDataPacket(const std::vector& data, uint8_t seq) { + AuthMoreMessage auth_more; + auth_more.setAuthMoreData(data); + Buffer::OwnedImpl buffer; + auth_more.encode(buffer); + BufferHelper::encodeHdr(buffer, seq); + return buffer.toString(); + } + + std::string generateTestPemKey() { + bssl::UniquePtr gen_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)); + EVP_PKEY_keygen_init(gen_ctx.get()); + EVP_PKEY_CTX_set_rsa_keygen_bits(gen_ctx.get(), 2048); + EVP_PKEY* raw_pkey = nullptr; + EVP_PKEY_keygen(gen_ctx.get(), &raw_pkey); + bssl::UniquePtr pkey(raw_pkey); + bssl::UniquePtr bio(BIO_new(BIO_s_mem())); + PEM_write_bio_PUBKEY(bio.get(), pkey.get()); + char* pem_data; + long pem_len = BIO_get_mem_data(bio.get(), &pem_data); + return std::string(pem_data, pem_len); + } + + std::unique_ptr tls_context_manager_; + Network::UpstreamTransportSocketFactoryPtr tls_context_; + Network::UpstreamTransportSocketFactoryPtr cleartext_context_; + MockWatermarkBuffer* client_write_buffer_{nullptr}; + ConnectionStatusCallbacks connect_callbacks_; + std::unique_ptr conn_; + std::shared_ptr payload_reader_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, MySQLAllowIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); + +/** + * ALLOW mode: SSL client connects, terminates SSL, basic login OK. + */ +TEST_P(MySQLAllowIntegrationTest, AllowSslClientLogin) { + initialize(); + conn_->connect(); + + FakeRawConnectionPtr fake_upstream; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream)); + + std::string greeting = encodeServerGreeting(MYSQL_PROTOCOL_10); + ASSERT_TRUE(fake_upstream->write(greeting)); + clientWaitForData(greeting); + + // SSL request + TLS upgrade — accepted in ALLOW mode. + clientWrite(encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM)); + upgradeClientToTls(); + + clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); + + std::string upstream_data; + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + &upstream_data)); + + ASSERT_TRUE(fake_upstream->write(encodeClientLoginResp(MYSQL_RESP_OK))); + + timeSystem().advanceTimeWait(std::chrono::milliseconds(500)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + conn_->close(Network::ConnectionCloseType::FlushWrite); + ASSERT_TRUE(fake_upstream->waitForDisconnect()); + + test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); + test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); +} + +/** + * ALLOW mode: non-SSL client connects in cleartext, login OK. + */ +TEST_P(MySQLAllowIntegrationTest, AllowNonSslClientLogin) { + initialize(); + conn_->connect(); + + FakeRawConnectionPtr fake_upstream; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream)); + + std::string greeting = encodeServerGreeting(MYSQL_PROTOCOL_10); + ASSERT_TRUE(fake_upstream->write(greeting)); + clientWaitForData(greeting); + + // Client sends login directly (no SSL) — accepted in ALLOW mode. + clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM)); + + std::string upstream_data; + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + &upstream_data)); + + ASSERT_TRUE(fake_upstream->write(encodeClientLoginResp(MYSQL_RESP_OK))); + + timeSystem().advanceTimeWait(std::chrono::milliseconds(500)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + conn_->close(Network::ConnectionCloseType::FlushWrite); + ASSERT_TRUE(fake_upstream->waitForDisconnect()); + + test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + EXPECT_EQ(test_server_->counter("mysql.mysql_stats.upgraded_to_ssl")->value(), 0); +} + +/** + * ALLOW mode: SSL client with caching_sha2 full auth (RSA mediation). + */ +TEST_P(MySQLAllowIntegrationTest, AllowSslFullAuthRsaMediation) { + initialize(); + conn_->connect(); + + FakeRawConnectionPtr fake_upstream; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream)); + + // Greeting with caching_sha2_password. + std::string greeting = encodeServerGreetingCachingSha2(); + ASSERT_TRUE(fake_upstream->write(greeting)); + clientWaitForData(greeting); + + // SSL + login. + clientWrite(encodeClientLogin(CLIENT_SSL, "", CHALLENGE_SEQ_NUM)); + upgradeClientToTls(); + clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); + + std::string upstream_data; + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + &upstream_data)); + + // Full auth required. + ASSERT_TRUE(fake_upstream->write( + encodeAuthMoreDataPacket({MYSQL_CACHINGSHA2_FULL_AUTH_REQUIRED}, CHALLENGE_RESP_SEQ_NUM))); + timeSystem().advanceTimeWait(std::chrono::milliseconds(200)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + // Client password. + Buffer::OwnedImpl pw_pkt; + BufferHelper::addString(pw_pkt, std::string("testpass") + '\0'); + BufferHelper::encodeHdr(pw_pkt, 4); + clientWrite(pw_pkt.toString()); + + // Filter should inject request-public-key (0x02, seq=3). + size_t prev_len = upstream_data.length(); + ASSERT_TRUE(fake_upstream->waitForData( + [prev_len](const std::string& data) { return data.length() >= prev_len + 5; }, + &upstream_data)); + + std::string req_key = upstream_data.substr(prev_len); + EXPECT_EQ(static_cast(req_key[3]), 3u); + EXPECT_EQ(static_cast(req_key[4]), MYSQL_REQUEST_PUBLIC_KEY); + + // Send PEM key. + std::string pem_key = generateTestPemKey(); + ASSERT_TRUE(fake_upstream->write( + encodeAuthMoreDataPacket(std::vector(pem_key.begin(), pem_key.end()), 4))); + + // Wait for encrypted password (256 bytes). + prev_len = upstream_data.length(); + ASSERT_TRUE(fake_upstream->waitForData( + [prev_len](const std::string& data) { return data.length() >= prev_len + 4 + 256; }, + &upstream_data)); + + // Server OK. + ASSERT_TRUE(fake_upstream->write(encodeClientLoginResp(MYSQL_RESP_OK, 0, 6))); + timeSystem().advanceTimeWait(std::chrono::milliseconds(500)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + conn_->close(Network::ConnectionCloseType::FlushWrite); + ASSERT_TRUE(fake_upstream->waitForDisconnect()); + + test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); + test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 0); +} + +} // namespace MySQLProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/mysql_proxy/filters/network/test/mysql_ssl_require_test_config.yaml b/contrib/mysql_proxy/filters/network/test/mysql_ssl_require_test_config.yaml new file mode 100644 index 0000000000000..e5536a29fc2fd --- /dev/null +++ b/contrib/mysql_proxy/filters/network/test/mysql_ssl_require_test_config.yaml @@ -0,0 +1,53 @@ +admin: + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "{}" + address: + socket_address: + address: "{}" + port_value: 0 +static_resources: + clusters: + name: cluster_0 + connect_timeout: 2s + load_assignment: + cluster_name: cluster_0 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: "{}" + port_value: 0 + listeners: + name: listener_0 + address: + socket_address: + address: "{}" + port_value: 0 + filter_chains: + - filters: + - name: mysql + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.mysql_proxy.v3.MySQLProxy + stat_prefix: mysql_stats + downstream_ssl: REQUIRE + - name: tcp + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: tcp_stats + cluster: cluster_0 + transport_socket: + name: starttls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.starttls.v3.StartTlsConfig + cleartext_socket_config: + tls_socket_config: + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{}" + private_key: + filename: "{}" From 89d0582ab833599da92ac9676dbd7698b763bfbe Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Fri, 24 Apr 2026 19:59:04 -0700 Subject: [PATCH 140/750] Stats sink filter (#44287) Commit Message: stat_sinks: add WASM filter stats sink for plugin-driven metric transformation Additional Description: Introduces a new stat sink extension (`envoy.stat_sinks.wasm_filter`) that acts as a programmable middleware layer between Envoy's stats collection and any downstream stats sink. A user-supplied WASM plugin (written in C++, Go, Rust, etc.) receives the metric snapshot on each flush via the existing `onStatsUpdate` Proxy-WASM callback and can perform arbitrary transformations before the metrics reach the inner sink. **Capabilities exposed to the WASM plugin via foreign functions:** | Foreign function | Purpose | |---|---| | `stats_filter_emit` | Select which counters, gauges, and histograms to keep | | `stats_filter_set_global_tags` | Attach key-value tags to every metric (e.g. node ID, datacenter) | | `stats_filter_set_name_overrides` | Rename individual metrics (e.g. prefix with `envoy.`) | | `stats_filter_inject_metrics` | Inject synthetic counters and gauges (e.g. plugin-level bookkeeping) | | `stats_filter_get_metric_tags` | Read tags for a single metric by type and index | | `stats_filter_get_all_metric_tags` | Bulk-read tags for all metrics in one call | | `stats_filter_get_histograms` | Read histogram names (histograms are not in the `onStatsUpdate` buffer) | All foreign functions use a compact binary wire format (little-endian u32/u64 + length-prefixed strings) for minimal serialization overhead at the WASM boundary. **Architecture:** 1. MetricSnapshot is passed to WasmFilterStatsSink 2. The sink invokes the WASM plugin (V8/Wasmtime) via `onStatsUpdate()` 3. The plugin can: - Filter metrics by name - Set global tags (e.g. node ID, datacenter) - Rename individual metrics (e.g. prefix with `envoy.`) - Inject synthetic counters/gauges - Call `stats_filter_emit()` with kept indices 4. The sink constructs an EnrichedMetricSnapshot (filtered + enriched view, zero-copy wrappers) 5. The enriched snapshot is flushed to the inner stats sink (Kafka, gRPC, StatsD, etc.) The `EnrichedMetricSnapshot` wraps the original snapshot without copying metric data. Wrapper classes (`EnrichedCounter`, `EnrichedGauge`, `EnrichedHistogram`, `SyntheticCounter`, `SyntheticGauge`) implement the `Stats::Metric` interfaces so the inner sink sees a standard `MetricSnapshot`. **Key design decisions:** - **Thread-local context passing**: Foreign functions communicate with the sink via thread-local `StatsFilterContext` and `Stats::TagVector*` pointers, set/cleared around each `onStatsUpdate()` invocation. This avoids any cross-thread coordination. - **Buffer-to-snapshot index translation**: The `onStatsUpdate` binary buffer only serializes `used()` metrics, while `MetricSnapshot` arrays contain all metrics. The sink builds explicit mapping vectors and translates plugin-emitted indices after each flush. - **Unused metric passthrough**: Metrics that are not `used()` are invisible to the WASM plugin and are automatically passed through to the inner sink. - **Startup global tags buffering**: Global tags set during `OnPluginStart` (before the sink object exists) are buffered in a thread-local and moved into the sink on construction. **Bug fix (pre-existing):** Fixed an exponential growth bug in `source/extensions/common/wasm/context.cc` where `gauge_block_size` used `+=` instead of `=` when computing aligned sizes, causing the `onStatsUpdate` buffer to grow exponentially with the number of gauges and trigger WASM VM OOM. **Files added/modified :** - `api/.../wasm_filter/v3/wasm_filter.proto` - New protobuf config - `source/.../wasm_filter/wasm_filter_stat_sink_impl.{h,cc}` - Core sink + 7 foreign functions - `source/.../wasm_filter/enriched_metric.h` - Metric wrapper classes - `source/.../wasm_filter/config.{h,cc}` - Extension factory - `source/.../wasm_filter/example/stats_filter_plugin.cc` - Example NullVM C++ plugin - `test/.../wasm_filter/wasm_filter_stat_sink_test.cc` - Unit tests - `docs/.../wasm_filter_stat_sink.rst` - Full documentation with wire format specs - `source/extensions/common/wasm/context.cc` - Bug fix (`+=` to `=`) - `changelogs/current.yaml`, `extensions_metadata.yaml`, `extensions_build_config.bzl` - Registration Risk Level: Medium (new extension, no impact on existing code paths except the one-line bug fix in `context.cc`) Testing: - Unit tests covering filtering, global tag injection, name overrides, synthetic metric injection, combined transformations, thread-local accessors, and all foreign function wire format parsing (`//test/extensions/stat_sinks/wasm_filter/...`, all passing) - End-to-end validation with a TinyGo WASM plugin producing to a Kafka inner sink, verifying filtered metrics, `envoy.` prefixed names, global tags from node metadata, and synthetic counter injection across multiple flush cycles - Format checker (`check_format fix`) passes clean Docs Changes: Added `docs/root/configuration/observability/stat_sinks/wasm_filter_stat_sink.rst` with full configuration guide, foreign function reference, wire format specifications, and example plugin walkthrough. Updated `stat_sinks.rst` toctree and API docs index. Release Notes: Added to `changelogs/current.yaml` under `new_features`. Platform Specific Features: N/A [Optional Runtime guard:] N/A [Optional API Considerations:] New proto `envoy.extensions.stat_sinks.wasm_filter.v3.WasmFilterStatsSinkConfig` with two fields: `wasm_config` (existing `WasmService`-style WASM plugin config) and `inner_sink` (the downstream `StatsSink` to receive transformed metrics). No changes to existing APIs. --------- Signed-off-by: Prashanth Josyula --- CODEOWNERS | 1 + api/BUILD | 1 + .../stat_sinks/wasm_filter/v3/BUILD | 13 + .../wasm_filter/v3/wasm_filter.proto | 46 + api/versioning/BUILD | 1 + changelogs/current.yaml | 654 ++++++++ contrib/contrib_build_config.bzl | 1 + contrib/extensions_metadata.yaml | 7 + contrib/stat_sinks/wasm_filter/source/BUILD | 41 + .../stat_sinks/wasm_filter/source/config.cc | 68 + .../stat_sinks/wasm_filter/source/config.h | 35 + .../wasm_filter/source/enriched_metric.h | 304 ++++ .../source/wasm_filter_stat_sink_impl.cc | 697 ++++++++ .../source/wasm_filter_stat_sink_impl.h | 178 ++ contrib/stat_sinks/wasm_filter/test/BUILD | 52 + .../wasm_filter/test/config_test.cc | 45 + .../wasm_filter/test/test_data/BUILD | 32 + .../test/test_data/stats_filter_plugin.cc | 206 +++ .../stats_filter_plugin_null_plugin.cc | 15 + .../wasm_filter_stat_sink_integration_test.cc | 108 ++ .../test/wasm_filter_stat_sink_test.cc | 1490 +++++++++++++++++ docs/root/api-v3/config/contrib/contrib.rst | 1 + .../wasm_filter_stats_sink.rst | 8 + .../observability/stat_sinks/stat_sinks.rst | 1 + .../stat_sinks/wasm_filter_stat_sink.rst | 224 +++ source/extensions/common/wasm/BUILD | 1 + tools/code_format/config.yaml | 3 + tools/spelling/spelling_dictionary.txt | 4 + 28 files changed, 4237 insertions(+) create mode 100644 api/contrib/envoy/extensions/stat_sinks/wasm_filter/v3/BUILD create mode 100644 api/contrib/envoy/extensions/stat_sinks/wasm_filter/v3/wasm_filter.proto create mode 100644 contrib/stat_sinks/wasm_filter/source/BUILD create mode 100644 contrib/stat_sinks/wasm_filter/source/config.cc create mode 100644 contrib/stat_sinks/wasm_filter/source/config.h create mode 100644 contrib/stat_sinks/wasm_filter/source/enriched_metric.h create mode 100644 contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.cc create mode 100644 contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.h create mode 100644 contrib/stat_sinks/wasm_filter/test/BUILD create mode 100644 contrib/stat_sinks/wasm_filter/test/config_test.cc create mode 100644 contrib/stat_sinks/wasm_filter/test/test_data/BUILD create mode 100644 contrib/stat_sinks/wasm_filter/test/test_data/stats_filter_plugin.cc create mode 100644 contrib/stat_sinks/wasm_filter/test/test_data/stats_filter_plugin_null_plugin.cc create mode 100644 contrib/stat_sinks/wasm_filter/test/wasm_filter_stat_sink_integration_test.cc create mode 100644 contrib/stat_sinks/wasm_filter/test/wasm_filter_stat_sink_test.cc create mode 100644 docs/root/api-v3/config/contrib/wasm_filter_stats_sink/wasm_filter_stats_sink.rst create mode 100644 docs/root/configuration/observability/stat_sinks/wasm_filter_stat_sink.rst diff --git a/CODEOWNERS b/CODEOWNERS index ddf611b5dff9a..5720954be9dcc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -477,6 +477,7 @@ extensions/upstreams/tcp @ggreenway @mattklein123 /contrib/dynamo/ @UNOWNED @UNOWNED /contrib/golang/ @doujiang24 @wangfakang @StarryVae @spacewander @antJack /contrib/kafka/ @mattklein123 @adamkotwasinski +/contrib/stat_sinks/wasm_filter/ @prashanthjos @UNOWNED /contrib/rocketmq_proxy/ @aaron-ai @lizhanhui /contrib/mysql_proxy/ @rshriram @venilnoronha /contrib/postgres/protocol/ @agrawroh @cpakulski @jarupatj diff --git a/api/BUILD b/api/BUILD index 1e362efbe52ca..f6c9e45d37457 100644 --- a/api/BUILD +++ b/api/BUILD @@ -108,6 +108,7 @@ proto_library( "//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters:pkg", "//contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha:pkg", "//contrib/envoy/extensions/stat_sinks/kafka/v3:pkg", + "//contrib/envoy/extensions/stat_sinks/wasm_filter/v3:pkg", "//contrib/envoy/extensions/tap_sinks/udp_sink/v3alpha:pkg", "//contrib/envoy/extensions/upstreams/http/tcp/golang/v3alpha:pkg", "//contrib/envoy/extensions/vcl/v3alpha:pkg", diff --git a/api/contrib/envoy/extensions/stat_sinks/wasm_filter/v3/BUILD b/api/contrib/envoy/extensions/stat_sinks/wasm_filter/v3/BUILD new file mode 100644 index 0000000000000..bf9f90a0d01dd --- /dev/null +++ b/api/contrib/envoy/extensions/stat_sinks/wasm_filter/v3/BUILD @@ -0,0 +1,13 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/config/metrics/v3:pkg", + "//envoy/extensions/wasm/v3:pkg", + "@xds//udpa/annotations:pkg", + ], +) diff --git a/api/contrib/envoy/extensions/stat_sinks/wasm_filter/v3/wasm_filter.proto b/api/contrib/envoy/extensions/stat_sinks/wasm_filter/v3/wasm_filter.proto new file mode 100644 index 0000000000000..d37c4155b097c --- /dev/null +++ b/api/contrib/envoy/extensions/stat_sinks/wasm_filter/v3/wasm_filter.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package envoy.extensions.stat_sinks.wasm_filter.v3; + +import "envoy/config/metrics/v3/stats.proto"; +import "envoy/extensions/wasm/v3/wasm.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.stat_sinks.wasm_filter.v3"; +option java_outer_classname = "WasmFilterProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/stat_sinks/wasm_filter/v3;wasm_filterv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Wasm Stats Filter] +// Wasm-based stats filter middleware that wraps an inner stats sink. +// [#extension: envoy.stat_sinks.wasm_filter] + +// A stats sink that runs a WASM plugin to filter, transform, and enrich metrics +// before delegating to an inner sink. The plugin can: +// +// - **Filter**: keep/drop counters, gauges, and histograms by index +// - **Enrich**: inject global tags (e.g. datacenter, pod) from node metadata +// - **Rename**: override metric names (e.g. prefix with ``envoy.``) +// - **Inject**: add synthetic counters/gauges (e.g. version metrics) +// +// Available foreign functions: +// ``stats_filter_emit`` -- declare kept metric indices +// ``stats_filter_set_global_tags`` -- set tags applied to all metrics (once at startup) +// ``stats_filter_set_name_overrides`` -- rename specific metrics (per flush) +// ``stats_filter_inject_metrics`` -- inject synthetic counters/gauges (per flush) +// ``stats_filter_get_histograms`` -- get histogram names +// ``stats_filter_get_metric_tags`` -- get tags for a single metric +// ``stats_filter_get_all_metric_tags`` -- get tags for all metrics (bulk) +// +// Metrics that the plugin does not emit are dropped. Text readouts pass through +// to the inner sink unfiltered. +message WasmFilterStatsSinkConfig { + // WASM plugin that implements the filter/transform/enrichment logic. + wasm.v3.PluginConfig wasm_config = 1 [(validate.rules).message = {required: true}]; + + // The inner sink to delegate filtered metrics to. + config.metrics.v3.StatsSink inner_sink = 2 [(validate.rules).message = {required: true}]; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 325d264642f2b..686bb76e45ba2 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -47,6 +47,7 @@ proto_library( "//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters:pkg", "//contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha:pkg", "//contrib/envoy/extensions/stat_sinks/kafka/v3:pkg", + "//contrib/envoy/extensions/stat_sinks/wasm_filter/v3:pkg", "//contrib/envoy/extensions/tap_sinks/udp_sink/v3alpha:pkg", "//contrib/envoy/extensions/upstreams/http/tcp/golang/v3alpha:pkg", "//contrib/envoy/extensions/vcl/v3alpha:pkg", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 70519737897b3..27855c0fd9d53 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -18,6 +18,660 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: dynamic_modules + change: | + Added ``envoy_dynamic_module_callback_is_validation_mode`` ABI callback that allows dynamic + modules to check if the server is running in config validation mode. +- area: ratelimit + change: | + Added ``is_negative_hits`` boolean to the ``hits_addend`` + configuration in the rate limit API. This allows rate limit + requests to add to the rate limit budget (refill previously + consumed tokens) via + :ref:`HitsAddend.is_negative_hits + `, + or per descriptor in + :ref:`RateLimitDescriptor.is_negative_hits + `. +- area: redis + change: | + Added zone-aware routing support for Redis Cluster proxy. New read policies + ``LOCAL_ZONE_AFFINITY`` and ``LOCAL_ZONE_AFFINITY_REPLICAS_AND_PRIMARY`` route read requests + to replicas in the same availability zone. Zone discovery is enabled via + :ref:`enable_zone_discovery + `. + Note: This feature currently works with Valkey only. +- area: overload_manager + change: | + Added :ref:`ShrinkHeapConfig ` typed + configuration for the ``envoy.overload_actions.shrink_heap`` overload action. This allows + operators to configure the timer interval (``timer_interval``, minimum 1s, default 10s) and + the memory release threshold (``max_unfreed_memory_bytes``, default 100MB) passed to + ``tcmalloc::MallocExtension::ReleaseMemoryToSystem()``. +- area: dns_resolver + change: | + Added :ref:`HickoryDnsResolverConfig + `, a new DNS + resolver using the `Hickory DNS `_ library. +- area: dynamic_modules + change: | + Added :ref:`tracer ` + support for dynamic modules, enabling custom distributed tracing backends to be implemented + in dynamic modules. +- area: dynamic_modules + change: | + Added upstream HTTP TCP bridge extension for dynamic modules. This enables modules to transform + HTTP requests into raw TCP data for upstream connections and convert TCP responses back into HTTP + responses via explicit send callbacks. See :ref:`envoy.upstreams.http.dynamic_modules + `. +- area: filters + change: | + Added filters to update the filter state in :ref:`a listener filter `. +- area: tls + change: | + Added a per-connection filter state object to select a workload trust domain in the SPIFFE validator in + the multi-tenant deployments. +- area: tls + change: | + Extended TLS certificate compression (RFC 8879): added brotli to QUIC (which already supported zlib), + and added brotli and zlib to TCP TLS. Controlled by runtime flag + ``envoy.reloadable_features.tls_certificate_compression_brotli`` (defaults to ``true``). + When disabled, QUIC retains zlib-only compression, while TCP TLS has no compression. +- area: tls + change: | + Exposed the SHA-256 fingerprint and serial number of the verified issuer (CA) certificate from + the validated downstream mTLS peer certificate chain. Accessible via the + ``%DOWNSTREAM_PEER_ISSUER_FINGERPRINT_256%`` and ``%DOWNSTREAM_PEER_ISSUER_SERIAL%`` + :ref:`access log formatters `, and via the + ``sha256PeerCertificateIssuerDigest()`` and ``serialNumberPeerCertificateIssuer()`` methods on + :ref:`downstreamSslConnection() ` in the Lua filter. +- area: dynamic_modules + change: | + Added custom metrics (counters, gauges, histograms) support to load balancer dynamic modules. + Modules can now define metrics during configuration and record them during host selection. +- area: dynamic_modules + change: | + Rust SDK now provides an opt-in ``CatchUnwind`` wrapper for filter callbacks. When a + wrapped callback panics, Envoy logs the panic and returns a fail-closed error (e.g. + HTTP 500, stream reset, connection close) instead of aborting the process. +- area: http_11_proxy + change: | + Added ability to configure a default proxy address that is used when the proxy address is not + configured via metadata. +- area: compressor + change: | + Added :ref:`weaken_etag_on_compress + ` + to the :ref:`compressor filter `. When enabled in + ``response_direction_config``, strong ``ETag`` response headers are weakened (``W/`` prefix) + instead of removed when compression is applied, allowing caches and conditional requests to + work while indicating the body was modified by compression. When both ``weaken_etag_on_compress`` + and ``disable_on_etag_header`` are true, the new field takes precedence. +- area: golang + change: | + Added ``DownstreamSslConnection()`` method to the Golang HTTP filter's ``StreamInfo`` interface, + providing access to SSL/TLS connection information for the downstream connection. This includes + peer certificate details (subject, issuer, serial number, SANs, validity), TLS version, cipher + suite, and PEM-encoded certificates. This achieves feature parity with the Lua filter's + ``downstreamSslConnection()`` functionality. +- area: outlier_detection + change: | + Added :ref:`detect_degraded_hosts ` + to enable passive degraded host detection. When enabled, outlier detection marks hosts as degraded when they return + the ``x-envoy-degraded`` header. Degraded hosts are deprioritized in load balancing but remain in rotation (not + ejected). The degraded state is cleared using the same backoff algorithm as ejection. Defaults to ``false``. +- area: http2 + change: | + Added :ref:`max_header_field_size_kb + ` to configure the + maximum wire-encoded size in KiB of an individual HPACK-encoded header field that the HTTP/2 + codec will accept. This allows increasing the default nghttp2 per-header limit of 64 KiB on the + wire when larger single headers need to be supported. +- area: dynamic_modules + change: | + Added dynamic module input matcher extension that allows implementing custom matching logic + in external languages (Rust, Go, C) via dynamic modules. +- area: dynamic_modules + change: | + Added listener lifecycle event callbacks to the bootstrap dynamic module extension. Modules can + opt in via ``enable_listener_lifecycle`` to receive ``on_listener_add_or_update`` and + ``on_listener_removal`` notifications when listeners change in the ``ListenerManager``. +- area: memory + change: | + Added ``soft_memory_limit_bytes``, ``max_per_cpu_cache_size_bytes``, and ``max_unfreed_memory_bytes`` + fields to :ref:`MemoryAllocatorManager ` + for fine-grained control of tcmalloc memory management. +- area: dynamic_modules + change: | + Added :ref:`TLS certificate validator + ` + support for dynamic modules, enabling custom TLS certificate validation to be implemented in dynamic modules. +- area: dynamic_modules + change: | + Added filter state read/write support for dynamic module cert validators, allowing modules to set and + get string values in the connection's filter state during certificate chain verification. +- area: dynamic_modules + change: | + Added ``write_to_socket`` and ``close_socket`` ABI callbacks for the dynamic module listener + filter, enabling protocol negotiation use cases such as Postgres SSL and MySQL handshake at the listener + filter level. +- area: dynamic_modules + change: | + Added HTTP callout support for dynamic module listener filters, enabling listener filters to initiate + asynchronous HTTP requests to upstream clusters and receive responses via the ``send_http_callout`` ABI + callback and ``on_listener_filter_http_callout_done`` event hook. +- area: ext_authz + change: | + Added :ref:`path_override ` + to the HTTP ext_authz filter. When set, the request path sent to the authorization service is replaced + entirely by this value. Only one of ``path_prefix`` or ``path_override`` may be set; validation fails + at config load if both are specified. +- area: stats + change: | + Added support to limit the number of metrics stored in each scope within the stats library. +- area: stats + change: | + The admin prometheus stats endpoint now supports the protobuf exposition format, and will automatically + use it if the request contains the correct Accept header, or if query parameter ``prom_protobuf=1`` is + set. In a prometheus scrape configuration, add ``PrometheusProto`` to ``scrape_protocols`` to use + the protobuf format. Additionally, when using the protobuf exposition format, the admin prometheus stats + endpoint now supports `native histograms `_ + when using the prometheus protobuf exposition format, using query + ``/stats/prometheus?histogram_buckets=prometheusnative``. +- area: stats + change: | + Added support for cluster-level stats matcher, allowing more granular control over which stats + are enabled and reported at the cluster level. This the stats matcher could be configured via + the xDS API dynamically on a per-cluster basis. + See :ref:`envoy.stats_matcher ` for more details. +- area: stats + change: | + Added support for listener-level stats matcher via ``typed_filter_metadata`` key + ``envoy.stats_matcher`` in the listener's + :ref:`metadata `. When present, + the provided :ref:`StatsMatcher ` replaces + the global stats matcher for that listener's stats scope, allowing fine-grained control over + which listener stats are created. + If the value of ``envoy.stats_matcher`` is set but does not contain a valid ``StatsMatcher`` + message, this will be treated as an invalid configuration and the listener will fail to start. + This strict validation can be disabled temporarily by setting the runtime guard + ``envoy.reloadable_features.strict_stats_matcher_unpacked`` to ``false``. +- area: tls + change: | + Added support for fetching certificates on-demand via SDS in the upstream TLS transport socket + using the extension :ref:`on-demand certificate selector + `. +- area: access_log + change: | + Added stats customization support for the :ref:`access logger `. +- area: dynamic modules + change: | + Introduced the extended ABI forward compatibility mechanism for dynamic modules + where modules built with an SDK version can be loaded by Envoy + binaries of the next Envoy version. For example, a module built with the v1.38 SDK + can now be loaded by an Envoy binary of v1.39. +- area: dynamic modules + change: | + Added drain and shutdown lifecycle hooks for bootstrap dynamic modules. +- area: dynamic modules + change: | + Added support for dynamic modules authors to register any combination of HTTP, network, listener, + UDP listener, and bootstrap filters in the Rust SDK. +- area: dynamic modules + change: | + Added connection state and flow control ABI callbacks for the dynamic module network filter, + including ``read_disable``, ``read_enabled``, ``get_connection_state``, ``enable_half_close``, + ``is_half_close_enabled``, ``get_buffer_limit``, ``set_buffer_limits``, and + ``above_high_watermark``. +- area: dynamic modules + change: | + Added socket property getter and SSL/TLS information ABI callbacks for the dynamic module listener + filter, including ``get_requested_server_name``, ``get_detected_transport_protocol``, + ``get_requested_application_protocols``, ``get_ja3_hash``, ``get_ja4_hash``, ``is_ssl``, + ``get_ssl_uri_sans``, ``get_ssl_dns_sans``, and ``get_ssl_subject``. +- area: mcp_router + change: | + Added support for MCP resource methods ``resources/list``, ``resources/read``, + ``resources/subscribe``, and ``resources/unsubscribe``. +- area: mcp_router + change: | + Added support for MCP prompt methods ``prompts/list`` and ``prompts/get``. +- area: matching + change: | + Added an optional ``field`` parameter to + :ref:`FilterStateInput `. + When set, ``FilterStateInput`` calls ``getField()`` on the filter state object instead of + ``serializeAsString()``, enabling direct matching on individual fields within composite filter + state objects such as proxy protocol TLVs stored via ``tlv_location: FILTER_STATE``. +- area: ratelimit + change: | + Added per-descriptor ``x-ratelimit-*`` headers support. See the + :ref:`x_ratelimit_option ` + field documentation for more details. +- area: ratelimit + change: | + Added ``RemoteAddressMatch`` action to the rate limit filter. This action will generate a descriptor based on the remote address of the + downstream connection by matching it against specified CIDR ranges with support for inversion and formatter substitution. +- area: mcp_router + change: | + Added support for MCP client-to-server notification methods ``notifications/cancelled`` + and ``notifications/roots/list_changed``. +- area: dynamic_modules + change: | + Added typed filter state support for dynamic module HTTP and network filters. This allows modules + to set and get filter state objects using registered ``StreamInfo::FilterState::ObjectFactory`` + instances, enabling interoperability with built-in Envoy filters that expect specific typed objects. +- area: mcp_router + change: | + Added support for MCP completion method ``completion/complete`` with routing based on + ``ref/prompt`` or ``ref/resource``. +- area: mcp_router + change: | + Added support for MCP logging method ``logging/setLevel``. +- area: upstream + change: | + Coalesced load balancer rebuilds during EDS batch host updates. When multiple priorities change in a + single batch, each LB level (LoadBalancerBase, ZoneAwareLoadBalancerBase, EdfLoadBalancerBase, + ThreadAwareLoadBalancerBase) now defers expensive per-priority recalculations to a single pass after + the batch completes, reducing CPU spikes on large clusters. This behavior can be reverted by setting + the runtime guard ``envoy.reloadable_features.coalesce_lb_rebuilds_on_batch_update`` to ``false``. +- area: cel + change: | + Added functionality to reevaluate CEL expressions that attempt to read response path data on the + request path once the data is available. Allows CEL matching based on both request and response + headers. This may cause a behavior change for matchers that previously would silently fail to + match due to attempting to match response headers in the request path. This behavior can be + reverted by setting the runtime guard + ``envoy.reloadable_features.enable_cel_response_path_matching`` to ``false``. +- area: access_log + change: | + Added support for gauges in the :ref:`stats access logger `. +- area: network + change: | + Added access logging support for network filters, similar to HTTP filters, by allowing network filters to + register as access logger instances. +- area: formatter + change: | + Extended ``*_WITHOUT_PORT`` address formatters to accept an optional ``MASK_PREFIX_LEN`` parameter + that masks IP addresses and returns them in CIDR notation (e.g., ``%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(16)%`` + returns ``10.1.0.0/16`` for client IP ``10.1.10.23``). +- area: dynamic_modules + change: | + Added :ref:`cluster + ` + support for dynamic modules, enabling custom service discovery and host management to be implemented + in dynamic modules. +- area: dynamic_modules + change: | + Added server lifecycle callbacks (``on_server_initialized``, ``on_drain_started``, ``on_shutdown``) + for dynamic module custom clusters, enabling modules to react to server readiness, drain, and + shutdown events. +- area: dynamic_modules + change: | + Changed the ``details`` parameter in ``cluster_lb_async_host_selection_complete`` ABI callback + from raw ``const char*`` and ``size_t`` to ``envoy_dynamic_module_type_module_buffer`` for + consistency with the ABI style guide. +- area: stat_sinks + change: | + Added support for exporting OpenTelemetry metrics via HTTP. The new ``http_service`` field + in :ref:`SinkConfig ` + enables direct OTLP metrics export to backends that only accept HTTP (Dynatrace, Datadog, Elastic), + without requiring an intermediate collector sidecar. +- area: ratelimit + change: | + Added support for shadow mode in the local rate limit filter. +- area: config + change: | + Added support for :ref:`set_node_on_first_message_only + ` to Delta-xDS. + Guarded by runtime flag ``envoy.reloadable_features.xds_legacy_delta_skip_subsequent_node``. +- area: formatter + change: | + Added the new access log formatter ``DOWNSTREAM_LOCAL_CLOSE_REASON``. +- area: formatter + change: | + Added ``%UPSTREAM_DETECTED_CLOSE_TYPE%`` and ``%DOWNSTREAM_DETECTED_CLOSE_TYPE%`` to expose the detected + close type of downstream and upstream connections. The possible values are ``Normal``, ``LocalReset``, + and ``RemoteReset``. +- area: formatter + change: | + Added extensions for :ref:`%FILE_CONTENT(/path/to/file)% ` + and :ref:`%SECRET(name)% `. +- area: http_service + change: | + Added the ability for :ref:`request_headers_to_add ` + to use a formatter extension that can retrieve secrets, for including authentication tokens. This support is added for + all uses of this message, including the open telemetry, ``ext_proc``, and ``zipkin`` tracers. +- area: reverse_tunnel + change: | + Added optional tenant isolation support to the reverse tunnel network filter. When + ``enable_tenant_isolation`` is set, Envoy scopes cached reverse tunnel sockets with composite + ``@`` and ``@`` identifiers and rejects handshake headers that already + contain the ``@`` delimiter to prevent ambiguous lookups. +- area: tcp_proxy + change: | + Added an option to emit a log entry when the connection is accepted. +- area: mcp_router + change: | + Added SSE (Server-Sent Events) streaming support for MCP backend responses. The router now handles + SSE responses from backends for ``tools/call`` with direct pass-through streaming, and supports SSE + aggregation for fanout operations (``tools/list``, ``initialize``) with incremental event parsing. +- area: http + change: | + Added an optional :ref:`JSON format ` + for the :ref:`x-forwarded-client-cert ` + (XFCC) header. The format can be configured via :ref:`format + `. +- area: dynamic_modules + change: | + Added :ref:`load balancing policies + ` + support for dynamic modules, enabling custom load balancing algorithms to be implemented in dynamic modules. +- area: dynamic_modules + change: | + Added ``get_host_health_by_address`` ABI callback for dynamic module load balancers, providing O(1) + host health lookup by address string using the cross-priority host map. +- area: sse_parser + change: | + Extended the SSE parser utility to support all standard SSE fields: ``id``, ``event`` (as ``event_type``), + and ``retry``, in addition to the existing ``data`` field. The ``retry`` field is parsed as a ``uint32_t`` + and only accepts values consisting of ASCII digits per the SSE specification. +- area: http + change: | + Added :ref:`envoy.filters.http.sse_to_metadata ` filter for extracting + values from Server-Sent Events (SSE) streams and writing them to dynamic metadata. Useful for capturing + token usage metrics from LLM API responses. Supports pluggable content parsers for different SSE data formats. +- area: content_parsers + change: | + Added :ref:`envoy.content_parsers.json ` content parser for extracting + values from JSON content using JSON path selectors. Can be used by filters that need to parse structured + JSON data and extract specific fields into metadata. +- area: resource_monitors + change: | + Added cgroup v2 support to the CPU utilization resource monitor. The monitor now automatically + detects and selects between cgroup v1 and v2 at runtime by checking available cgroup files on + the system. This enables the resource monitor to work correctly in both cgroup v1 and v2 + environments without configuration changes. +- area: connection + change: | + Add support for closing connections when they stay above the buffer high watermark for a configured time. + This can be enabled by setting the :ref:`per_connection_buffer_high_watermark_timeout + ` + field on the listener and :ref:`per_connection_buffer_high_watermark_timeout + ` + field on the cluster. By default, the timeout is disabled. +- area: dynamic_modules + change: | + Added configurable :ref:`metrics_namespace + ` field + to ``DynamicModuleConfig``. This allows users to customize the prefix used for all metrics + created by dynamic modules. Metrics now appear with the standard ``envoy_`` prefix followed by + the namespace in prometheus output (e.g. ``envoy_myapp_requests_total``). The legacy behavior + (stripping the namespace prefix from prometheus output) can be restored by setting the runtime + guard ``envoy.reloadable_features.dynamic_modules_strip_custom_stat_prefix`` to ``true``. +- area: oauth2 + change: | + Added ``TLS_CLIENT_AUTH`` for the OAuth2 HTTP filter to support RFC 8705 mutual TLS client + authentication. In this mode ``token_secret`` is optional and ignored, and the token endpoint + cluster must be configured with mTLS. +- area: dynamic_modules + change: | + Added a process-wide function registry to the dynamic modules ABI. Modules can register + functions by name via ``envoy_dynamic_module_callback_register_function`` and other modules + can resolve them via ``envoy_dynamic_module_callback_get_function``, enabling zero-copy + cross-module interactions analogous to ``dlsym``. +- area: dynamic_modules + change: | + Added a process-wide shared data registry to the dynamic modules ABI. +- area: dynamic_modules + change: | + Added support for loading dynamic module binaries from local file paths via the new + :ref:`module ` + field in ``DynamicModuleConfig``. This allows specifying an absolute path to a ``.so`` file + via ``module.local.filename`` as an alternative to the name-based search path. +- area: dynamic_modules + change: | + Added support for fetching dynamic module binaries from remote HTTP sources via + ``module.remote`` in ``DynamicModuleConfig``. The module is downloaded asynchronously during + listener initialization with SHA256 verification, written to a temporary file, and loaded + via ``dlopen``. If the remote fetch fails, the filter is not installed and requests pass + through (fail-open). +- area: dynamic_modules + change: | + Added caching for remotely fetched dynamic modules. Since ``newDynamicModuleFromBytes`` + writes modules to a deterministic path based on SHA256, subsequent config updates + referencing the same SHA256 load from the existing file, avoiding redundant HTTP fetches. +- area: dynamic_modules + change: | + Added ``nack_on_cache_miss`` option for remote dynamic module sources. When enabled, + uncached remote modules cause configuration rejection (NACK) with a background fetch, + instead of blocking listener warming. This enables remote modules in ECDS and per-route + configurations where an init manager is not available. +- area: dynamic_modules + change: | + Network filter read and write buffers now persist after ``on_read``/``on_write`` callbacks, allowing + modules to access buffered data from ``on_scheduled`` and other callbacks. Added + ``envoy_dynamic_module_callback_network_filter_get_cluster_host_count`` to query cluster host counts + by name, enabling scale-to-zero and custom load balancing decisions in network filters. +- area: dynamic_modules + change: | + Added metrics definition and update support for bootstrap dynamic modules. +- area: dynamic_modules + change: | + Added timer API to the bootstrap extension dynamic modules ABI. +- area: dynamic_modules + change: | + Added admin handler API to the bootstrap extension dynamic modules ABI, enabling modules to + register custom admin HTTP endpoints. +- area: formatter + change: | + Added ``SPAN_ID`` :ref:`access log formatter ` to log the span ID of + the active (downstream) span for a request, complementing the existing ``TRACE_ID`` formatter. +- area: formatter + change: | + Added ``QUERY_PARAMS`` support for substitution formatter to log all query params. + They can either be logged in their original form or decoded. +- area: formatter + change: | + Added new access log formatters for tracking upstream hosts and connection IDs attempted during + request processing: ``%UPSTREAM_HOSTS_ATTEMPTED%``, ``%UPSTREAM_HOSTS_ATTEMPTED_WITHOUT_PORT%``, + ``%UPSTREAM_HOST_NAMES_ATTEMPTED%``, ``%UPSTREAM_HOST_NAMES_ATTEMPTED_WITHOUT_PORT%``, and + ``%UPSTREAM_CONNECTION_IDS_ATTEMPTED%``. These are useful for debugging retry behavior and + understanding which hosts were tried before a successful connection or final failure. +- area: dynamic_modules + change: | + Added init manager integration to the dynamic modules bootstrap extension ABI. An init target + is automatically registered for every bootstrap extension, blocking traffic until the module + signals readiness via ``signal_init_complete``. +- area: dynamic_modules + change: | + Added ``on_host_membership_update`` event hook and ``get_member_update_host_address`` callback + for dynamic module load balancers, enabling modules to receive notifications when hosts are + added or removed from the cluster and inspect the affected host addresses. +- area: geoip + change: | + Added ``asn_org`` field to :ref:`geo_field_keys + ` + to populate a header with the autonomous system organization name from the MaxMind ASN database. +- area: tracers + change: | + Added log events to spans created by the OpenTelemetry tracer. +- area: mcp_router + change: | + Added SSE response support for MCP ``resources/list`` fanout aggregation. +- area: mcp_router + change: | + Added support for MCP ``resources/templates/list`` method with fanout aggregation. +- area: tcp_proxy + change: | + Added :ref:`proxy_protocol_tlv_merge_policy + ` + to control how TLVs in ``proxy_protocol_tlvs`` are merged with existing PROXY protocol state. + Supports ``ADD_IF_ABSENT`` (default), ``OVERWRITE_BY_TYPE_IF_EXISTS_OR_ADD``, and + ``APPEND_IF_EXISTS_OR_ADD``. +- area: admin + change: | + Added ``filter`` query parameter support to the ``/clusters`` endpoint. The parameter accepts a RE2 + regular expression to filter clusters by name. Compatible with the ``format`` parameter for both + text and JSON output (e.g., ``/clusters?filter=service&format=json``). +- area: mcp_router + change: | + Added :ref:`statistics ` to the MCP router filter for + observability into request routing, fanout operations, and error conditions. +- area: mcp + change: | + Added HTTP DELETE session termination support to the MCP filter. DELETE requests with an + ``MCP-Session-Id`` header are now recognized as valid MCP traffic in ``REJECT_NO_MCP`` mode. +- area: a2a + change: | + Added parsing support for the A2A (Agent2Agent) protocol, enabling parsing of A2A JSON-RPC messages. +- area: mcp + change: | + Added options ``propagate_trace_context`` and ``propagate_baggage`` for extracting ``traceparent``, + ``tracestate``, and baggage from MCP parameters, respectively. +- area: mcp_json_rest_bridge + change: | + Added the MCP JSON REST Bridge HTTP filter configuration to transcode MCP JSON-RPC requests into + standard JSON-REST HTTP requests. +- area: http + change: | + Added :ref:`file_server http filter ` to allow responding with + file contents from the filesystem. +- area: mcp_router + change: | + Added SSE response support for MCP ``prompts/list`` fanout aggregation. +- area: mcp_json_rest_bridge + change: | + Added support for MCP session negotiation, including ``initialize`` and + ``notifications/initialized`` methods. This filter is currently a work-in-progress and not + recommended for production use. +- area: listener_manager + change: | + Added ``ListenerUpdateCallbacks`` interface to ``ListenerManager``, similar to the existing + ``ClusterUpdateCallbacks`` on ``ClusterManager``. +- area: matching + change: | + Added :ref:`local reply matcher input ` + to distinguish Envoy generated local replies from upstream responses. This matcher input returns ``true`` + for local replies and ``false`` for upstream responses, enabling ``custom_response`` filter policies to + selectively apply only to locally generated error responses. +- area: mcp_json_rest_bridge + change: | + Added support for MCP ``tools/call`` request transcoding. Support for ``tools/list`` and + ``tools/call`` response transcoding is planned. This filter is currently a work-in-progress and + not recommended for production use. +- area: ext_proc + change: | + Added :ref:`allow_content_length_header + ` to allow + the ext_proc filter to preserve the original ``Content-Length`` header or let ext_proc server modify it as needed. +- area: cluster + change: | + Added :ref:`MCP multi cluster ` extension that combines multiple MCP services + into a single one. This cluster type is primarily intended to support aggregation of multiple MCP services into one. +- area: tls + change: | + Enhanced TLS certificate validation failure messages for CRL-related errors in access logs. The + ``%DOWNSTREAM_TRANSPORT_FAILURE_REASON%`` and ``%UPSTREAM_TRANSPORT_FAILURE_REASON%`` access log + formatters now include the certificate's CRL Distribution Point (CRLDP) information when CRL + validation fails. For errors such as ``CRL for certificate was not provided``, ``CRL has expired``, ``CRL + is not yet valid``, or ``certificate revoked``, the error message now includes the certificate's + CRL distribution points (e.g., ``X509_verify_cert: certificate verification error at depth 0: certificate revocation + check against provided CRLs failed: unable to get certificate CRL, certificate CRL distribution points: + [http://crl.example.com/ca.crl, http://backup-crl.example.com/ca.crl]``). This provides better visibility into CRL + validation failures and helps operators identify connectivity or CRL server issues without requiring debug-level logging. +- area: lua + change: | + Added :ref:`set() ` to the Lua filter + state API, allowing Lua scripts to create and store filter state objects dynamically using + registered object factories. +- area: http + change: | + Fixed an issue where filter chain execution could continue on HTTP streams that had been reset but not yet + destroyed. This could cause use-after-free conditions when filter callbacks were invoked on filters that + had already received ``onDestroy()``. The fix ensures that ``decodeHeaders()``, ``decodeData()``, + ``decodeTrailers()``, and ``decodeMetadata()`` are blocked after a downstream reset. +- area: formatter + change: | + Added support for ``%TRACE_ID%`` in the custom response policy filter's local response policy to allow + trace ID to be substituted into the response body. +- area: redis + change: | + Added support for ``BITFIELD_RO`` in ``redis_proxy``. +- area: network_filter + change: | + Added support for + ``on_downstream_data`` (see + :ref:`envoy_v3_api_field_extensions.filters.network.set_filter_state.v3.Config.on_downstream_data`) + to the :ref:`set_filter_state network filter `, allowing + connection filter state to be populated after first receiving data from the downstream connection. +- area: oauth2 + change: | + Added :ref:`allow_failed_matcher ` + to allow requests to proceed to upstream as unauthenticated when OAuth validation fails (missing, invalid, or expired + credentials) and the request matches the configured matchers. This enables graceful degradation patterns where services + can handle both authenticated and unauthenticated requests. When triggered, all OAuth cookies are stripped and context + headers ``x-envoy-oauth-status: failed`` and ``x-envoy-oauth-failure-reason`` are added. The matcher evaluation follows + priority order: ``pass_through_matcher`` > ``allow_failed_matcher`` > ``deny_redirect_matcher`` > default OAuth behavior. +- area: mcp_json_rest_bridge + change: | + Added support for MCP ``tools/call`` response transcoding. Support for ``tools/list`` is planned. + This filter is currently a work-in-progress and not recommended for production use. +- area: http_filter + change: | + Added support for clear route cache in the :ref:`set_filter_state http filter `. When + ``clear_route_cache`` is set, the filter will clear the route cache for the current request after applying filter state updates. + This is necessary if the route configuration may depend on the filter state values set. +- area: tcp_proxy + change: | + Propagate upstream TCP RST to downstream when detected close type is RemoteReset. + This behavioral change can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.propagate_upstream_rst_through_tunneled_tcp_proxy`` to false. +- area: tcp_proxy + change: | + HttpUpstream now maps remote-originated HTTP stream reset reasons (RemoteReset, + RemoteRefusedStreamReset, RemoteConnectionFailure) to RemoteClose with + DetectedCloseType::RemoteReset, enabling RST propagation through tunneled tcp_proxy. + This behavioral change can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.map_http_stream_reset_to_tcp_rst`` to false. +- area: ratelimit + change: | + Support populating rate limit descriptors from cluster metadata. +- area: contrib + change: | + Added a new :ref:`Kafka stats sink ` contrib extension + (``envoy.stat_sinks.kafka``) that produces metrics directly to an Apache Kafka topic + using librdkafka. Supports both JSON and Protobuf serialization formats; the Protobuf + format uses the same ``StreamMetricsMessage`` wire format as the gRPC + :ref:`metrics_service ` sink, + allowing consumers to reuse existing deserializers. Configurable batching, delta counters, + tag-as-label emission, and full librdkafka producer tuning (including TLS and SASL + authentication) are supported via + :ref:`KafkaStatsSinkConfig `. +- area: stat_sinks + change: | + Added a new :ref:`WASM stats filter ` contrib extension + (``envoy.stat_sinks.wasm_filter``) that acts as programmable middleware between + the metrics snapshot and any inner stats sink. A user-supplied WASM plugin can: + filter metrics by index, inject global tags from node metadata + (``stats_filter_set_global_tags``), rename metrics (``stats_filter_set_name_overrides``), + inject synthetic counters/gauges (``stats_filter_inject_metrics``), and filter + histograms (``stats_filter_get_histograms``). This enables moving centralized + metric processing logic (tag enrichment, name rewriting, custom metric injection) + into the proxy itself. Configured via + :ref:`WasmFilterStatsSinkConfig `. +- area: tls + change: | + Added support for building Envoy with OpenSSL as an alternative to the default BoringSSL. + This is achieved through a BoringSSL compatibility layer (``bssl-compat``) that translates BoringSSL + API calls to OpenSSL, allowing Envoy's TLS code to remain unchanged. To build with OpenSSL, use + ``--config=openssl``. HTTP/3 (QUIC) is disabled for OpenSSL builds. Note that OpenSSL builds are + not currently covered by the `Envoy security policy `_. + See :repo:`bazel/SSL.md ` for details. +- area: dynamic_modules + change: | + Promoted the dynamic modules HTTP filter (``envoy.filters.http.dynamic_modules``) from alpha + to stable. - area: mysql_proxy change: | Added SSL termination support to the MySQL proxy filter with RSA-mediated ``caching_sha2_password`` diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index 8c7bad41f4d92..64c225c39cd03 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -52,6 +52,7 @@ CONTRIB_EXTENSIONS = { # "envoy.stat_sinks.kafka": "//contrib/kafka/stat_sinks/source:config_lib", + "envoy.stat_sinks.wasm_filter": "//contrib/stat_sinks/wasm_filter/source:config_lib", # # Tap sinks diff --git a/contrib/extensions_metadata.yaml b/contrib/extensions_metadata.yaml index eca1671c20e35..6897c83357226 100644 --- a/contrib/extensions_metadata.yaml +++ b/contrib/extensions_metadata.yaml @@ -125,6 +125,13 @@ envoy.stat_sinks.kafka: status: alpha type_urls: - envoy.extensions.stat_sinks.kafka.v3.KafkaStatsSinkConfig +envoy.stat_sinks.wasm_filter: + categories: + - envoy.stats_sinks + security_posture: data_plane_agnostic + status: alpha + type_urls: + - envoy.extensions.stat_sinks.wasm_filter.v3.WasmFilterStatsSinkConfig envoy.tap_sinks.udp_sink: categories: - envoy.filters.http diff --git a/contrib/stat_sinks/wasm_filter/source/BUILD b/contrib/stat_sinks/wasm_filter/source/BUILD new file mode 100644 index 0000000000000..99fa56c27e667 --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/source/BUILD @@ -0,0 +1,41 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "wasm_filter_stat_sink_lib", + srcs = ["wasm_filter_stat_sink_impl.cc"], + hdrs = [ + "enriched_metric.h", + "wasm_filter_stat_sink_impl.h", + ], + deps = [ + "//envoy/stats:stats_interface", + "//source/common/common:logger_lib", + "//source/extensions/common/wasm:wasm_lib", + ], + alwayslink = 1, +) + +envoy_cc_contrib_extension( + name = "config_lib", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":wasm_filter_stat_sink_lib", + "//envoy/registry", + "//envoy/server:factory_context_interface", + "//source/common/config:utility_lib", + "//source/extensions/common/wasm:remote_async_datasource_lib", + "//source/extensions/common/wasm:wasm_lib", + "//source/server:configuration_lib", + "@envoy_api//contrib/envoy/extensions/stat_sinks/wasm_filter/v3:pkg_cc_proto", + ], +) diff --git a/contrib/stat_sinks/wasm_filter/source/config.cc b/contrib/stat_sinks/wasm_filter/source/config.cc new file mode 100644 index 0000000000000..2b462f62ba117 --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/source/config.cc @@ -0,0 +1,68 @@ +#include "contrib/stat_sinks/wasm_filter/source/config.h" + +#include + +#include "envoy/registry/registry.h" +#include "envoy/server/factory_context.h" + +#include "source/common/config/utility.h" +#include "source/extensions/common/wasm/wasm.h" + +#include "contrib/envoy/extensions/stat_sinks/wasm_filter/v3/wasm_filter.pb.validate.h" +#include "contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.h" + +namespace Envoy { +namespace Extensions { +namespace StatSinks { +namespace WasmFilter { + +absl::StatusOr +WasmFilterSinkFactory::createStatsSink(const Protobuf::Message& proto_config, + Server::Configuration::ServerFactoryContext& context) { + const auto& config = MessageUtil::downcastAndValidate< + const envoy::extensions::stat_sinks::wasm_filter::v3::WasmFilterStatsSinkConfig&>( + proto_config, context.messageValidationContext().staticValidationVisitor()); + + // Scope a TagVector for the plugin's onConfigure to write global tags into. + // This avoids shared thread-local state when multiple wasm_filter sinks exist. + Stats::TagVector startup_tags; + setGlobalTags(&startup_tags); + + auto plugin_config = std::make_unique( + config.wasm_config(), context, context.scope(), context.initManager(), + envoy::config::core::v3::TrafficDirection::UNSPECIFIED, nullptr, true); + + setGlobalTags(nullptr); + + context.api().customStatNamespaces().registerStatNamespace( + Extensions::Common::Wasm::CustomStatNamespace); + + const auto& inner_sink_config = config.inner_sink(); + auto& inner_factory = + Config::Utility::getAndCheckFactory( + inner_sink_config); + ProtobufTypes::MessagePtr inner_message = Config::Utility::translateToFactoryConfig( + inner_sink_config, context.messageValidationContext().staticValidationVisitor(), + inner_factory); + + auto inner_sink = inner_factory.createStatsSink(*inner_message, context); + RETURN_IF_NOT_OK_REF(inner_sink.status()); + + return std::make_unique( + std::move(plugin_config), std::move(inner_sink.value()), context.scope().symbolTable(), + std::move(startup_tags)); +} + +ProtobufTypes::MessagePtr WasmFilterSinkFactory::createEmptyConfigProto() { + return std::make_unique< + envoy::extensions::stat_sinks::wasm_filter::v3::WasmFilterStatsSinkConfig>(); +} + +std::string WasmFilterSinkFactory::name() const { return WasmFilterName; } + +REGISTER_FACTORY(WasmFilterSinkFactory, Server::Configuration::StatsSinkFactory); + +} // namespace WasmFilter +} // namespace StatSinks +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/stat_sinks/wasm_filter/source/config.h b/contrib/stat_sinks/wasm_filter/source/config.h new file mode 100644 index 0000000000000..d7633a430eac1 --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/source/config.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "envoy/server/factory_context.h" + +#include "source/extensions/common/wasm/remote_async_datasource.h" +#include "source/server/configuration_impl.h" + +namespace Envoy { +namespace Extensions { +namespace StatSinks { +namespace WasmFilter { + +constexpr char WasmFilterName[] = "envoy.stat_sinks.wasm_filter"; + +class WasmFilterSinkFactory : Logger::Loggable, + public Server::Configuration::StatsSinkFactory { +public: + absl::StatusOr + createStatsSink(const Protobuf::Message& config, + Server::Configuration::ServerFactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + + std::string name() const override; + +private: + RemoteAsyncDataProviderPtr remote_data_provider_; +}; + +} // namespace WasmFilter +} // namespace StatSinks +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/stat_sinks/wasm_filter/source/enriched_metric.h b/contrib/stat_sinks/wasm_filter/source/enriched_metric.h new file mode 100644 index 0000000000000..41cf1f73f8f68 --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/source/enriched_metric.h @@ -0,0 +1,304 @@ +#pragma once + +#include +#include +#include + +#include "envoy/stats/histogram.h" +#include "envoy/stats/stats.h" + +#include "source/common/stats/symbol_table.h" + +#include "absl/types/optional.h" + +namespace Envoy { +namespace Extensions { +namespace StatSinks { +namespace WasmFilter { + +// Merges original tags with extra tags. Extra tags are appended. +inline Stats::TagVector mergeTags(const Stats::TagVector& original, const Stats::TagVector& extra) { + if (extra.empty()) { + return original; + } + Stats::TagVector merged; + merged.reserve(original.size() + extra.size()); + merged.insert(merged.end(), original.begin(), original.end()); + merged.insert(merged.end(), extra.begin(), extra.end()); + return merged; +} + +// Wraps an existing Counter, overriding name() and tags() while forwarding +// everything else. Used to inject global tags and per-metric name overrides. +class EnrichedCounter : public Stats::Counter { +public: + EnrichedCounter(const Stats::Counter& original, const Stats::TagVector& extra_tags, + const std::string& name_override = "") + : original_(original), extra_tags_(extra_tags), name_override_(name_override) { + if (!name_override_.empty()) { + override_stat_name_.emplace(name_override_, + const_cast(original_).symbolTable()); + } + } + + std::string name() const override { + return name_override_.empty() ? original_.name() : name_override_; + } + Stats::StatName statName() const override { + return override_stat_name_.has_value() ? override_stat_name_->statName() : original_.statName(); + } + Stats::TagVector tags() const override { return mergeTags(original_.tags(), extra_tags_); } + std::string tagExtractedName() const override { + return name_override_.empty() ? original_.tagExtractedName() : name_override_; + } + Stats::StatName tagExtractedStatName() const override { + return override_stat_name_.has_value() ? override_stat_name_->statName() + : original_.tagExtractedStatName(); + } + void iterateTagStatNames(const Stats::Metric::TagStatNameIterFn& fn) const override { + original_.iterateTagStatNames(fn); + } + bool used() const override { return original_.used(); } + void markUnused() override {} + bool hidden() const override { return original_.hidden(); } + Stats::SymbolTable& symbolTable() override { + return const_cast(original_).symbolTable(); + } + const Stats::SymbolTable& constSymbolTable() const override { + return original_.constSymbolTable(); + } + + void incRefCount() override {} + bool decRefCount() override { return false; } + uint32_t use_count() const override { return 1; } + + void add(uint64_t) override {} + void inc() override {} + uint64_t latch() override { return 0; } + void reset() override {} + uint64_t value() const override { return original_.value(); } + +private: + const Stats::Counter& original_; + const Stats::TagVector& extra_tags_; + const std::string& name_override_; + absl::optional override_stat_name_; +}; + +// Wraps an existing Gauge with tag/name overrides. +class EnrichedGauge : public Stats::Gauge { +public: + EnrichedGauge(const Stats::Gauge& original, const Stats::TagVector& extra_tags, + const std::string& name_override = "") + : original_(original), extra_tags_(extra_tags), name_override_(name_override) { + if (!name_override_.empty()) { + override_stat_name_.emplace(name_override_, + const_cast(original_).symbolTable()); + } + } + + std::string name() const override { + return name_override_.empty() ? original_.name() : name_override_; + } + Stats::StatName statName() const override { + return override_stat_name_.has_value() ? override_stat_name_->statName() : original_.statName(); + } + Stats::TagVector tags() const override { return mergeTags(original_.tags(), extra_tags_); } + std::string tagExtractedName() const override { + return name_override_.empty() ? original_.tagExtractedName() : name_override_; + } + Stats::StatName tagExtractedStatName() const override { + return override_stat_name_.has_value() ? override_stat_name_->statName() + : original_.tagExtractedStatName(); + } + void iterateTagStatNames(const Stats::Metric::TagStatNameIterFn& fn) const override { + original_.iterateTagStatNames(fn); + } + bool used() const override { return original_.used(); } + void markUnused() override {} + bool hidden() const override { return original_.hidden(); } + Stats::SymbolTable& symbolTable() override { + return const_cast(original_).symbolTable(); + } + const Stats::SymbolTable& constSymbolTable() const override { + return original_.constSymbolTable(); + } + + void incRefCount() override {} + bool decRefCount() override { return false; } + uint32_t use_count() const override { return 1; } + + void add(uint64_t) override {} + void dec() override {} + void inc() override {} + void set(uint64_t) override {} + void sub(uint64_t) override {} + uint64_t value() const override { return original_.value(); } + void setParentValue(uint64_t) override {} + ImportMode importMode() const override { return original_.importMode(); } + void mergeImportMode(ImportMode) override {} + +private: + const Stats::Gauge& original_; + const Stats::TagVector& extra_tags_; + const std::string& name_override_; + absl::optional override_stat_name_; +}; + +// Wraps an existing ParentHistogram with tag/name overrides. +class EnrichedHistogram : public Stats::ParentHistogram { +public: + EnrichedHistogram(const Stats::ParentHistogram& original, const Stats::TagVector& extra_tags, + const std::string& name_override = "") + : original_(original), extra_tags_(extra_tags), name_override_(name_override) { + if (!name_override_.empty()) { + override_stat_name_.emplace(name_override_, + const_cast(original_).symbolTable()); + } + } + + std::string name() const override { + return name_override_.empty() ? original_.name() : name_override_; + } + Stats::StatName statName() const override { + return override_stat_name_.has_value() ? override_stat_name_->statName() : original_.statName(); + } + Stats::TagVector tags() const override { return mergeTags(original_.tags(), extra_tags_); } + std::string tagExtractedName() const override { + return name_override_.empty() ? original_.tagExtractedName() : name_override_; + } + Stats::StatName tagExtractedStatName() const override { + return override_stat_name_.has_value() ? override_stat_name_->statName() + : original_.tagExtractedStatName(); + } + void iterateTagStatNames(const Stats::Metric::TagStatNameIterFn& fn) const override { + original_.iterateTagStatNames(fn); + } + bool used() const override { return original_.used(); } + void markUnused() override {} + bool hidden() const override { return original_.hidden(); } + Stats::SymbolTable& symbolTable() override { + return const_cast(original_).symbolTable(); + } + const Stats::SymbolTable& constSymbolTable() const override { + return original_.constSymbolTable(); + } + + void incRefCount() override {} + bool decRefCount() override { return false; } + uint32_t use_count() const override { return 1; } + + Histogram::Unit unit() const override { return original_.unit(); } + void recordValue(uint64_t) override {} + + void merge() override {} + const Stats::HistogramStatistics& intervalStatistics() const override { + return original_.intervalStatistics(); + } + const Stats::HistogramStatistics& cumulativeStatistics() const override { + return original_.cumulativeStatistics(); + } + std::string quantileSummary() const override { return original_.quantileSummary(); } + std::string bucketSummary() const override { return original_.bucketSummary(); } + std::vector detailedTotalBuckets() const override { + return original_.detailedTotalBuckets(); + } + std::vector detailedIntervalBuckets() const override { + return original_.detailedIntervalBuckets(); + } + uint64_t cumulativeCountLessThanOrEqualToValue(double value) const override { + return original_.cumulativeCountLessThanOrEqualToValue(value); + } + +private: + const Stats::ParentHistogram& original_; + const Stats::TagVector& extra_tags_; + const std::string& name_override_; + absl::optional override_stat_name_; +}; + +// A standalone synthetic counter with stored name, value, and tags. +// Used to inject custom metrics that don't exist in Envoy's stats system. +class SyntheticCounter : public Stats::Counter { +public: + SyntheticCounter(Stats::SymbolTable& symbol_table, const std::string& name, uint64_t value, + Stats::TagVector tags) + : symbol_table_(symbol_table), name_(name), value_(value), tags_(std::move(tags)), + stat_name_storage_(name, symbol_table) {} + + std::string name() const override { return name_; } + Stats::StatName statName() const override { return stat_name_storage_.statName(); } + Stats::TagVector tags() const override { return tags_; } + std::string tagExtractedName() const override { return name_; } + Stats::StatName tagExtractedStatName() const override { return stat_name_storage_.statName(); } + void iterateTagStatNames(const Stats::Metric::TagStatNameIterFn&) const override {} + bool used() const override { return true; } + void markUnused() override {} + bool hidden() const override { return false; } + Stats::SymbolTable& symbolTable() override { return symbol_table_; } + const Stats::SymbolTable& constSymbolTable() const override { return symbol_table_; } + + void incRefCount() override {} + bool decRefCount() override { return false; } + uint32_t use_count() const override { return 1; } + + void add(uint64_t) override {} + void inc() override {} + uint64_t latch() override { return 0; } + void reset() override {} + uint64_t value() const override { return value_; } + +private: + Stats::SymbolTable& symbol_table_; + std::string name_; + uint64_t value_; + Stats::TagVector tags_; + Stats::StatNameManagedStorage stat_name_storage_; +}; + +// A standalone synthetic gauge with stored name, value, and tags. +class SyntheticGauge : public Stats::Gauge { +public: + SyntheticGauge(Stats::SymbolTable& symbol_table, const std::string& name, uint64_t value, + Stats::TagVector tags) + : symbol_table_(symbol_table), name_(name), value_(value), tags_(std::move(tags)), + stat_name_storage_(name, symbol_table) {} + + std::string name() const override { return name_; } + Stats::StatName statName() const override { return stat_name_storage_.statName(); } + Stats::TagVector tags() const override { return tags_; } + std::string tagExtractedName() const override { return name_; } + Stats::StatName tagExtractedStatName() const override { return stat_name_storage_.statName(); } + void iterateTagStatNames(const Stats::Metric::TagStatNameIterFn&) const override {} + bool used() const override { return true; } + void markUnused() override {} + bool hidden() const override { return false; } + Stats::SymbolTable& symbolTable() override { return symbol_table_; } + const Stats::SymbolTable& constSymbolTable() const override { return symbol_table_; } + + void incRefCount() override {} + bool decRefCount() override { return false; } + uint32_t use_count() const override { return 1; } + + void add(uint64_t) override {} + void dec() override {} + void inc() override {} + void set(uint64_t) override {} + void sub(uint64_t) override {} + uint64_t value() const override { return value_; } + void setParentValue(uint64_t) override {} + ImportMode importMode() const override { return ImportMode::NeverImport; } + void mergeImportMode(ImportMode) override {} + +private: + Stats::SymbolTable& symbol_table_; + std::string name_; + uint64_t value_; + Stats::TagVector tags_; + Stats::StatNameManagedStorage stat_name_storage_; +}; + +} // namespace WasmFilter +} // namespace StatSinks +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.cc b/contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.cc new file mode 100644 index 0000000000000..9ecbc0086121a --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.cc @@ -0,0 +1,697 @@ +#include "contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.h" + +#include +#include +#include + +using proxy_wasm::RegisterForeignFunction; +using proxy_wasm::WasmResult; + +namespace Envoy { +namespace Extensions { +namespace StatSinks { +namespace WasmFilter { + +namespace { + +thread_local StatsFilterContext* active_context = nullptr; +thread_local Stats::TagVector* active_global_tags = nullptr; + +constexpr size_t kU32 = sizeof(uint32_t); +constexpr size_t kU64 = sizeof(uint64_t); + +bool readU32(const char* data, size_t total, size_t& offset, uint32_t& out) { + if (offset + kU32 > total) { + return false; + } + memcpy(&out, data + offset, kU32); // NOLINT(safe-memcpy) + offset += kU32; + return true; +} + +bool readU64(const char* data, size_t total, size_t& offset, uint64_t& out) { + if (offset + kU64 > total) { + return false; + } + memcpy(&out, data + offset, kU64); // NOLINT(safe-memcpy) + offset += kU64; + return true; +} + +bool readStr(const char* data, size_t total, size_t& offset, std::string& out) { + uint32_t len = 0; + if (!readU32(data, total, offset, len)) { + return false; + } + if (offset + len > total) { + return false; + } + out.assign(data + offset, len); + offset += len; + return true; +} + +bool readIndexBlock(const char* data, size_t total, size_t& offset, + absl::flat_hash_set& dest) { + uint32_t count = 0; + if (!readU32(data, total, offset, count)) { + return false; + } + if (count > (total - offset) / kU32) { + return false; + } + dest.reserve(count); + for (uint32_t i = 0; i < count; ++i) { + uint32_t idx = 0; + if (!readU32(data, total, offset, idx)) { + return false; + } + dest.insert(idx); + } + return true; +} + +bool readTagVector(const char* data, size_t total, size_t& offset, Stats::TagVector& tags) { + uint32_t tag_count = 0; + if (!readU32(data, total, offset, tag_count)) { + return false; + } + if (tag_count > (total - offset) / (2 * kU32)) { + return false; + } + tags.reserve(tag_count); + for (uint32_t t = 0; t < tag_count; ++t) { + Stats::Tag tag; + if (!readStr(data, total, offset, tag.name_) || !readStr(data, total, offset, tag.value_)) { + return false; + } + tags.push_back(std::move(tag)); + } + return true; +} + +void writeU32(char* out, size_t& pos, uint32_t v) { + memcpy(out + pos, &v, kU32); // NOLINT(safe-memcpy) + pos += kU32; +} + +void writeStr(char* out, size_t& pos, const std::string& s) { + writeU32(out, pos, static_cast(s.size())); + if (!s.empty()) { + memcpy(out + pos, s.data(), s.size()); // NOLINT(safe-memcpy) + pos += s.size(); + } +} + +// --------------------------------------------------------------------------- +// Foreign function: stats_filter_emit +// +// Wire format (packed uint32_t array): +// [counter_count] [counter_idx_0] ... [counter_idx_N] +// [gauge_count] [gauge_idx_0] ... [gauge_idx_M] +// [hist_count] [hist_idx_0] ... [hist_idx_K] (optional) +// --------------------------------------------------------------------------- +RegisterForeignFunction registerStatsFilterEmit( + "stats_filter_emit", + [](proxy_wasm::WasmBase&, std::string_view arguments, + const std::function& /*alloc_result*/) -> WasmResult { + auto* ctx = active_context; + if (ctx == nullptr) { + return WasmResult::InternalFailure; + } + + const char* data = arguments.data(); + const size_t total = arguments.size(); + + if (total < kU32) { + return WasmResult::BadArgument; + } + + size_t offset = 0; + if (!readIndexBlock(data, total, offset, ctx->kept_counter_indices)) { + return WasmResult::BadArgument; + } + if (!readIndexBlock(data, total, offset, ctx->kept_gauge_indices)) { + return WasmResult::BadArgument; + } + if (offset < total) { + if (!readIndexBlock(data, total, offset, ctx->kept_histogram_indices)) { + return WasmResult::BadArgument; + } + ctx->histogram_block_present = true; + } + + ctx->emit_called = true; + return WasmResult::Ok; + }); + +// --------------------------------------------------------------------------- +// Foreign function: stats_filter_set_global_tags +// +// Called once during plugin startup (onConfigure) to set tags that will be +// applied to ALL metrics on every flush. +// +// Wire format: +// [tag_count: u32] +// For each tag: +// [name_len: u32] [name bytes] [value_len: u32] [value bytes] +// --------------------------------------------------------------------------- +RegisterForeignFunction registerStatsFilterSetGlobalTags( + "stats_filter_set_global_tags", + [](proxy_wasm::WasmBase&, std::string_view arguments, + const std::function& /*alloc_result*/) -> WasmResult { + const char* data = arguments.data(); + const size_t total = arguments.size(); + size_t offset = 0; + + Stats::TagVector* tags = active_global_tags; + if (tags == nullptr) { + return WasmResult::InternalFailure; + } + tags->clear(); + if (!readTagVector(data, total, offset, *tags)) { + return WasmResult::BadArgument; + } + + return WasmResult::Ok; + }); + +// --------------------------------------------------------------------------- +// Foreign function: stats_filter_set_name_overrides +// +// Called per flush to rename specific metrics. +// +// Wire format: +// [count: u32] +// For each override: +// [type: u32 (1=counter, 2=gauge, 3=histogram)] +// [index: u32] +// [new_name_len: u32] [new_name bytes] +// --------------------------------------------------------------------------- +RegisterForeignFunction registerStatsFilterSetNameOverrides( + "stats_filter_set_name_overrides", + [](proxy_wasm::WasmBase&, std::string_view arguments, + const std::function& /*alloc_result*/) -> WasmResult { + auto* ctx = active_context; + if (ctx == nullptr) { + return WasmResult::InternalFailure; + } + + const char* data = arguments.data(); + const size_t total = arguments.size(); + size_t offset = 0; + + uint32_t count = 0; + if (!readU32(data, total, offset, count)) { + return WasmResult::BadArgument; + } + if (count > (total - offset) / (3 * kU32)) { + return WasmResult::BadArgument; + } + + ctx->name_overrides.reserve(count); + for (uint32_t i = 0; i < count; ++i) { + NameOverride ovr; + if (!readU32(data, total, offset, ovr.type) || !readU32(data, total, offset, ovr.index)) { + return WasmResult::BadArgument; + } + if (!readStr(data, total, offset, ovr.new_name)) { + return WasmResult::BadArgument; + } + ctx->name_overrides.push_back(std::move(ovr)); + } + + return WasmResult::Ok; + }); + +// --------------------------------------------------------------------------- +// Foreign function: stats_filter_inject_metrics +// +// Called per flush to inject synthetic counters and gauges. +// +// Wire format: +// [counter_count: u32] +// For each counter: +// [name_len: u32] [name bytes] [value: u64] +// [tag_count: u32] for each tag: [name_len] [name] [value_len] [value] +// [gauge_count: u32] +// For each gauge: +// [name_len: u32] [name bytes] [value: u64] +// [tag_count: u32] for each tag: [name_len] [name] [value_len] [value] +// --------------------------------------------------------------------------- +RegisterForeignFunction registerStatsFilterInjectMetrics( + "stats_filter_inject_metrics", + [](proxy_wasm::WasmBase&, std::string_view arguments, + const std::function& /*alloc_result*/) -> WasmResult { + auto* ctx = active_context; + if (ctx == nullptr) { + return WasmResult::InternalFailure; + } + + const char* data = arguments.data(); + const size_t total = arguments.size(); + size_t offset = 0; + + auto readMetricBlock = [&](std::vector& dest) -> bool { + uint32_t count = 0; + if (!readU32(data, total, offset, count)) { + return false; + } + if (count > (total - offset) / (2 * kU32 + kU64)) { + return false; + } + dest.reserve(count); + for (uint32_t i = 0; i < count; ++i) { + SyntheticMetricDef def; + if (!readStr(data, total, offset, def.name)) { + return false; + } + if (!readU64(data, total, offset, def.value)) { + return false; + } + if (!readTagVector(data, total, offset, def.tags)) { + return false; + } + dest.push_back(std::move(def)); + } + return true; + }; + + if (!readMetricBlock(ctx->synthetic_counters)) { + return WasmResult::BadArgument; + } + if (!readMetricBlock(ctx->synthetic_gauges)) { + return WasmResult::BadArgument; + } + + return WasmResult::Ok; + }); + +// --------------------------------------------------------------------------- +// Foreign function: stats_filter_get_metric_tags +// +// Input: [type: u32] [index: u32] +// Output: [tag_count: u32] for each: [name_len][name][value_len][value] +// --------------------------------------------------------------------------- +RegisterForeignFunction registerStatsFilterGetMetricTags( + "stats_filter_get_metric_tags", + [](proxy_wasm::WasmBase&, std::string_view arguments, + const std::function& alloc_result) -> WasmResult { + auto* ctx = active_context; + if (ctx == nullptr || ctx->snapshot == nullptr) { + return WasmResult::InternalFailure; + } + + if (arguments.size() < 2 * kU32) { + return WasmResult::BadArgument; + } + + size_t offset = 0; + uint32_t type = 0; + uint32_t index = 0; + readU32(arguments.data(), arguments.size(), offset, type); + readU32(arguments.data(), arguments.size(), offset, index); + + Stats::TagVector tags; + if (type == 1) { + // Translate buffer-order index to snapshot-order index. + if (index >= ctx->counter_buffer_to_snapshot.size()) { + return WasmResult::BadArgument; + } + uint32_t snap_idx = ctx->counter_buffer_to_snapshot[index]; + const auto& counters = ctx->snapshot->counters(); + if (snap_idx >= counters.size()) { + return WasmResult::BadArgument; + } + tags = counters[snap_idx].counter_.get().tags(); + } else if (type == 2) { + if (index >= ctx->gauge_buffer_to_snapshot.size()) { + return WasmResult::BadArgument; + } + uint32_t snap_idx = ctx->gauge_buffer_to_snapshot[index]; + const auto& gauges = ctx->snapshot->gauges(); + if (snap_idx >= gauges.size()) { + return WasmResult::BadArgument; + } + tags = gauges[snap_idx].get().tags(); + } else if (type == 3) { + const auto& histograms = ctx->snapshot->histograms(); + if (index >= histograms.size()) { + return WasmResult::BadArgument; + } + tags = histograms[index].get().tags(); + } else { + return WasmResult::BadArgument; + } + + size_t out_size = kU32; + for (const auto& tag : tags) { + out_size += kU32 + tag.name_.size() + kU32 + tag.value_.size(); + } + + auto* out = static_cast(alloc_result(out_size)); + size_t pos = 0; + writeU32(out, pos, static_cast(tags.size())); + for (const auto& tag : tags) { + writeStr(out, pos, tag.name_); + writeStr(out, pos, tag.value_); + } + + return WasmResult::Ok; + }); + +// --------------------------------------------------------------------------- +// Foreign function: stats_filter_get_all_metric_tags +// +// Bulk: returns tags for used counters and gauges (in buffer order) and all histograms. +// Output: 3 blocks (counters, gauges, histograms), each: +// [metric_count: u32] +// For each metric: [tag_count: u32] for each tag: [name_len][name][value_len][value] +// --------------------------------------------------------------------------- +RegisterForeignFunction registerStatsFilterGetAllMetricTags( + "stats_filter_get_all_metric_tags", + [](proxy_wasm::WasmBase&, std::string_view /*arguments*/, + const std::function& alloc_result) -> WasmResult { + auto* ctx = active_context; + if (ctx == nullptr || ctx->snapshot == nullptr) { + return WasmResult::InternalFailure; + } + + const auto& counters = ctx->snapshot->counters(); + const auto& gauges = ctx->snapshot->gauges(); + const auto& histograms = ctx->snapshot->histograms(); + + auto collectBufferedTags = [](const auto& metrics, const auto& buffer_to_snapshot, + auto extractTags) { + std::vector all; + all.reserve(buffer_to_snapshot.size()); + for (uint32_t snapshot_index : buffer_to_snapshot) { + all.push_back(extractTags(metrics[snapshot_index])); + } + return all; + }; + + auto counter_tags = + collectBufferedTags(counters, ctx->counter_buffer_to_snapshot, + [](const auto& c) { return c.counter_.get().tags(); }); + auto gauge_tags = collectBufferedTags(gauges, ctx->gauge_buffer_to_snapshot, + [](const auto& g) { return g.get().tags(); }); + std::vector histogram_tags; + histogram_tags.reserve(histograms.size()); + for (const auto& h : histograms) { + histogram_tags.push_back(h.get().tags()); + } + + auto blockSize = [](const std::vector& tag_groups) -> size_t { + size_t sz = kU32; + for (const auto& tags : tag_groups) { + sz += kU32; + for (const auto& tag : tags) { + sz += kU32 + tag.name_.size() + kU32 + tag.value_.size(); + } + } + return sz; + }; + + size_t out_size = blockSize(counter_tags) + blockSize(gauge_tags) + blockSize(histogram_tags); + auto* out = static_cast(alloc_result(out_size)); + size_t pos = 0; + + auto writeBlock = [&](const std::vector& tag_groups) { + writeU32(out, pos, static_cast(tag_groups.size())); + for (const auto& tags : tag_groups) { + writeU32(out, pos, static_cast(tags.size())); + for (const auto& tag : tags) { + writeStr(out, pos, tag.name_); + writeStr(out, pos, tag.value_); + } + } + }; + + writeBlock(counter_tags); + writeBlock(gauge_tags); + writeBlock(histogram_tags); + + return WasmResult::Ok; + }); + +// --------------------------------------------------------------------------- +// Foreign function: stats_filter_get_histograms +// +// Returns histogram names so the plugin can filter them. +// Output: [count: u32] for each: [name_len: u32] [name bytes] +// --------------------------------------------------------------------------- +RegisterForeignFunction registerStatsFilterGetHistograms( + "stats_filter_get_histograms", + [](proxy_wasm::WasmBase&, std::string_view /*arguments*/, + const std::function& alloc_result) -> WasmResult { + auto* ctx = active_context; + if (ctx == nullptr || ctx->snapshot == nullptr) { + return WasmResult::InternalFailure; + } + + const auto& histograms = ctx->snapshot->histograms(); + + size_t out_size = kU32; + for (const auto& hist_ref : histograms) { + out_size += kU32 + hist_ref.get().name().size(); + } + + auto* out = static_cast(alloc_result(out_size)); + size_t pos = 0; + + writeU32(out, pos, static_cast(histograms.size())); + for (const auto& hist_ref : histograms) { + writeStr(out, pos, hist_ref.get().name()); + } + + return WasmResult::Ok; + }); + +} // namespace + +StatsFilterContext* getActiveContext() { return active_context; } +void setActiveContext(StatsFilterContext* ctx) { active_context = ctx; } + +Stats::TagVector* getGlobalTags() { return active_global_tags; } +void setGlobalTags(Stats::TagVector* tags) { active_global_tags = tags; } + +void buildBufferToSnapshotMaps(Stats::MetricSnapshot& snapshot, StatsFilterContext& ctx) { + const auto& counters = snapshot.counters(); + ctx.counter_buffer_to_snapshot.reserve(counters.size()); + for (uint32_t i = 0; i < counters.size(); ++i) { + if (counters[i].counter_.get().used()) { + ctx.counter_buffer_to_snapshot.push_back(i); + } + } + + const auto& gauges = snapshot.gauges(); + ctx.gauge_buffer_to_snapshot.reserve(gauges.size()); + for (uint32_t i = 0; i < gauges.size(); ++i) { + if (gauges[i].get().used()) { + ctx.gauge_buffer_to_snapshot.push_back(i); + } + } +} + +void processFilterDecisionsAndFlush(Stats::MetricSnapshot& snapshot, StatsFilterContext& context, + Stats::TagVector& global_tags, Stats::SymbolTable& symbol_table, + Stats::Sink& inner_sink) { + auto translateIndices = [](absl::flat_hash_set& indices, + const std::vector& mapping) { + absl::flat_hash_set translated; + translated.reserve(indices.size()); + for (uint32_t buf_idx : indices) { + if (buf_idx < mapping.size()) { + translated.insert(mapping[buf_idx]); + } + } + indices = std::move(translated); + }; + + translateIndices(context.kept_counter_indices, context.counter_buffer_to_snapshot); + translateIndices(context.kept_gauge_indices, context.gauge_buffer_to_snapshot); + + for (auto& ovr : context.name_overrides) { + if (ovr.type == 1 && ovr.index < context.counter_buffer_to_snapshot.size()) { + ovr.index = context.counter_buffer_to_snapshot[ovr.index]; + } else if (ovr.type == 2 && ovr.index < context.gauge_buffer_to_snapshot.size()) { + ovr.index = context.gauge_buffer_to_snapshot[ovr.index]; + } + } + + const auto& counters = snapshot.counters(); + const auto& gauges = snapshot.gauges(); + + bool has_enrichments = !global_tags.empty() || !context.name_overrides.empty() || + !context.synthetic_counters.empty() || !context.synthetic_gauges.empty(); + + if (!context.emit_called && !has_enrichments) { + inner_sink.flush(snapshot); + return; + } + + if (!context.emit_called && has_enrichments) { + // Plugin didn't filter, but has enrichments - keep all metrics. + for (uint32_t i = 0; i < counters.size(); ++i) { + context.kept_counter_indices.insert(i); + } + for (uint32_t i = 0; i < gauges.size(); ++i) { + context.kept_gauge_indices.insert(i); + } + } + + if (context.emit_called) { + // Unused metrics always pass through regardless of filtering. + for (uint32_t i = 0; i < counters.size(); ++i) { + if (!counters[i].counter_.get().used()) { + context.kept_counter_indices.insert(i); + } + } + for (uint32_t i = 0; i < gauges.size(); ++i) { + if (!gauges[i].get().used()) { + context.kept_gauge_indices.insert(i); + } + } + } + + EnrichedMetricSnapshot enriched(snapshot, context, global_tags, symbol_table); + inner_sink.flush(enriched); +} + +// --------------------------------------------------------------------------- +// EnrichedMetricSnapshot +// --------------------------------------------------------------------------- + +EnrichedMetricSnapshot::EnrichedMetricSnapshot(Stats::MetricSnapshot& original, + const StatsFilterContext& ctx, + const Stats::TagVector& global_tags, + Stats::SymbolTable& symbol_table) + : original_(original), symbol_table_(symbol_table) { + const auto& src_counters = original.counters(); + const auto& src_gauges = original.gauges(); + const auto& src_histograms = original.histograms(); + + // Build per-type name override lookup. + counter_name_overrides_.resize(src_counters.size()); + gauge_name_overrides_.resize(src_gauges.size()); + histogram_name_overrides_.resize(src_histograms.size()); + for (const auto& ovr : ctx.name_overrides) { + if (ovr.type == 1 && ovr.index < counter_name_overrides_.size()) { + counter_name_overrides_[ovr.index] = ovr.new_name; + } else if (ovr.type == 2 && ovr.index < gauge_name_overrides_.size()) { + gauge_name_overrides_[ovr.index] = ovr.new_name; + } else if (ovr.type == 3 && ovr.index < histogram_name_overrides_.size()) { + histogram_name_overrides_[ovr.index] = ovr.new_name; + } + } + + // Build enriched counters. + counter_wrappers_.reserve(ctx.kept_counter_indices.size()); + enriched_counters_.reserve(ctx.kept_counter_indices.size()); + for (uint32_t i = 0; i < src_counters.size(); ++i) { + if (ctx.kept_counter_indices.contains(i)) { + counter_wrappers_.emplace_back(src_counters[i].counter_.get(), global_tags, + counter_name_overrides_[i]); + enriched_counters_.push_back({src_counters[i].delta_, counter_wrappers_.back()}); + } + } + + // Build enriched gauges. + gauge_wrappers_.reserve(ctx.kept_gauge_indices.size()); + enriched_gauges_.reserve(ctx.kept_gauge_indices.size()); + for (uint32_t i = 0; i < src_gauges.size(); ++i) { + if (ctx.kept_gauge_indices.contains(i)) { + gauge_wrappers_.emplace_back(src_gauges[i].get(), global_tags, gauge_name_overrides_[i]); + enriched_gauges_.emplace_back(gauge_wrappers_.back()); + } + } + + // Build enriched histograms. + if (!ctx.histogram_block_present) { + // No histogram block in emit call -- pass all through with enrichment. + histogram_wrappers_.reserve(src_histograms.size()); + enriched_histograms_.reserve(src_histograms.size()); + for (uint32_t i = 0; i < src_histograms.size(); ++i) { + histogram_wrappers_.emplace_back( + src_histograms[i].get(), global_tags, + i < histogram_name_overrides_.size() ? histogram_name_overrides_[i] : std::string()); + enriched_histograms_.emplace_back(histogram_wrappers_.back()); + } + } else { + histogram_wrappers_.reserve(ctx.kept_histogram_indices.size()); + enriched_histograms_.reserve(ctx.kept_histogram_indices.size()); + for (uint32_t i = 0; i < src_histograms.size(); ++i) { + if (ctx.kept_histogram_indices.contains(i)) { + histogram_wrappers_.emplace_back(src_histograms[i].get(), global_tags, + histogram_name_overrides_[i]); + enriched_histograms_.emplace_back(histogram_wrappers_.back()); + } + } + } + + // Append synthetic counters. + if (!ctx.synthetic_counters.empty()) { + synthetic_counter_objs_.reserve(ctx.synthetic_counters.size()); + for (const auto& def : ctx.synthetic_counters) { + auto merged_tags = mergeTags(def.tags, global_tags); + synthetic_counter_objs_.emplace_back(symbol_table_, def.name, def.value, + std::move(merged_tags)); + } + for (auto& sc : synthetic_counter_objs_) { + enriched_counters_.push_back({sc.value(), sc}); + } + } + + // Append synthetic gauges. + if (!ctx.synthetic_gauges.empty()) { + synthetic_gauge_objs_.reserve(ctx.synthetic_gauges.size()); + for (const auto& def : ctx.synthetic_gauges) { + auto merged_tags = mergeTags(def.tags, global_tags); + synthetic_gauge_objs_.emplace_back(symbol_table_, def.name, def.value, + std::move(merged_tags)); + } + for (auto& sg : synthetic_gauge_objs_) { + enriched_gauges_.emplace_back(sg); + } + } +} + +// --------------------------------------------------------------------------- +// WasmFilterStatsSink +// --------------------------------------------------------------------------- + +WasmFilterStatsSink::WasmFilterStatsSink(Common::Wasm::PluginConfigPtr plugin_config, + Stats::SinkPtr inner_sink, + Stats::SymbolTable& symbol_table, + Stats::TagVector initial_global_tags) + : plugin_config_(std::move(plugin_config)), inner_sink_(std::move(inner_sink)), + symbol_table_(symbol_table), global_tags_(std::move(initial_global_tags)) {} + +void WasmFilterStatsSink::flush(Stats::MetricSnapshot& snapshot) { + context_.clear(); + context_.snapshot = &snapshot; + + Common::Wasm::Wasm* wasm = plugin_config_->wasm(); + if (wasm == nullptr) { + inner_sink_->flush(snapshot); + return; + } + + buildBufferToSnapshotMaps(snapshot, context_); + + setActiveContext(&context_); + setGlobalTags(&global_tags_); + + wasm->onStatsUpdate(plugin_config_->plugin(), snapshot); + + setActiveContext(nullptr); + setGlobalTags(nullptr); + + processFilterDecisionsAndFlush(snapshot, context_, global_tags_, symbol_table_, *inner_sink_); +} + +} // namespace WasmFilter +} // namespace StatSinks +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.h b/contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.h new file mode 100644 index 0000000000000..3925608decde5 --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.h @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include + +#include "envoy/stats/sink.h" + +#include "source/extensions/common/wasm/wasm.h" + +#include "absl/container/flat_hash_set.h" +#include "contrib/stat_sinks/wasm_filter/source/enriched_metric.h" + +namespace Envoy { +namespace Extensions { +namespace StatSinks { +namespace WasmFilter { + +// Per-metric name override: (type, index) -> new name. +// type: 1=counter, 2=gauge, 3=histogram. +struct NameOverride { + uint32_t type; + uint32_t index; + std::string new_name; +}; + +// Synthetic metric definition received from the WASM plugin. +struct SyntheticMetricDef { + std::string name; + uint64_t value; + Stats::TagVector tags; +}; + +// Thread-local context for the WASM filter, holding filter decisions, name +// overrides, synthetic metrics, and snapshot access for foreign functions. +struct StatsFilterContext { + absl::flat_hash_set kept_counter_indices; + absl::flat_hash_set kept_gauge_indices; + absl::flat_hash_set kept_histogram_indices; + + // True when stats_filter_emit was called by the plugin during this flush. + // Distinguishes "plugin didn't call emit" (passthrough) from "plugin + // explicitly emitted empty sets" (drop all). + bool emit_called{false}; + + // True when the stats_filter_emit call included a histogram index block. + // When false, histograms pass through unfiltered even if emit_called is true. + bool histogram_block_present{false}; + + // Per-flush name overrides set by stats_filter_set_name_overrides. + std::vector name_overrides; + + // Per-flush synthetic metrics set by stats_filter_inject_metrics. + std::vector synthetic_counters; + std::vector synthetic_gauges; + + // Non-owning pointer to the snapshot currently being flushed. + Stats::MetricSnapshot* snapshot{}; + + // Maps buffer-order index → snapshot-order index. Built before calling into + // WASM because onStatsUpdate only serializes used() metrics, but + // EnrichedMetricSnapshot indexes into the full snapshot arrays. + std::vector counter_buffer_to_snapshot; + std::vector gauge_buffer_to_snapshot; + + void clear() { + kept_counter_indices.clear(); + kept_gauge_indices.clear(); + kept_histogram_indices.clear(); + emit_called = false; + histogram_block_present = false; + name_overrides.clear(); + synthetic_counters.clear(); + synthetic_gauges.clear(); + counter_buffer_to_snapshot.clear(); + gauge_buffer_to_snapshot.clear(); + snapshot = nullptr; + } +}; + +// Returns/sets the thread-local context pointer. +StatsFilterContext* getActiveContext(); +void setActiveContext(StatsFilterContext* ctx); + +// Global tags set once by the WASM plugin at startup via +// stats_filter_set_global_tags. Stored in the sink and shared by reference +// with all enriched metric wrappers. +// Thread-local accessor for the global tags (set by stats_filter_set_global_tags). +Stats::TagVector* getGlobalTags(); +void setGlobalTags(Stats::TagVector* tags); + +// Builds the buffer-order → snapshot-order index maps needed for translating +// WASM plugin indices (which skip unused metrics) to snapshot array positions. +void buildBufferToSnapshotMaps(Stats::MetricSnapshot& snapshot, StatsFilterContext& ctx); + +// Translates WASM filter decisions from buffer-order indices to snapshot-order +// indices, applies enrichment, and flushes to the inner sink. +void processFilterDecisionsAndFlush(Stats::MetricSnapshot& snapshot, StatsFilterContext& context, + Stats::TagVector& global_tags, Stats::SymbolTable& symbol_table, + Stats::Sink& inner_sink); + +// Wraps an existing MetricSnapshot, applying: +// - Filtering by kept indices +// - Global tag injection on all metrics +// - Per-metric name overrides +// - Synthetic counter/gauge injection +class EnrichedMetricSnapshot : public Stats::MetricSnapshot { +public: + EnrichedMetricSnapshot(Stats::MetricSnapshot& original, const StatsFilterContext& ctx, + const Stats::TagVector& global_tags, Stats::SymbolTable& symbol_table); + + const std::vector& counters() override { return enriched_counters_; } + const std::vector>& gauges() override { + return enriched_gauges_; + } + const std::vector>& histograms() override { + return enriched_histograms_; + } + const std::vector>& textReadouts() override { + return original_.textReadouts(); + } + const std::vector& hostCounters() override { + return original_.hostCounters(); + } + const std::vector& hostGauges() override { + return original_.hostGauges(); + } + SystemTime snapshotTime() const override { return original_.snapshotTime(); } + +private: + Stats::MetricSnapshot& original_; + Stats::SymbolTable& symbol_table_; + + // Wrapper objects must outlive the snapshot. Stored here. + std::vector counter_wrappers_; + std::vector gauge_wrappers_; + std::vector histogram_wrappers_; + std::vector synthetic_counter_objs_; + std::vector synthetic_gauge_objs_; + + // Per-metric name overrides indexed by (type, original_index). + std::vector counter_name_overrides_; + std::vector gauge_name_overrides_; + std::vector histogram_name_overrides_; + + // Output vectors returned by accessors. + std::vector enriched_counters_; + std::vector> enriched_gauges_; + std::vector> enriched_histograms_; +}; + +// A stats sink that runs a WASM plugin as a filter/transformer/enricher before +// delegating to an inner sink. +class WasmFilterStatsSink : public Stats::Sink { +public: + WasmFilterStatsSink(Common::Wasm::PluginConfigPtr plugin_config, Stats::SinkPtr inner_sink, + Stats::SymbolTable& symbol_table, Stats::TagVector initial_global_tags = {}); + + void flush(Stats::MetricSnapshot& snapshot) override; + + void onHistogramComplete(const Stats::Histogram& histogram, uint64_t value) override { + inner_sink_->onHistogramComplete(histogram, value); + } + + Stats::TagVector& globalTags() { return global_tags_; } + +private: + Common::Wasm::PluginConfigPtr plugin_config_; + Stats::SinkPtr inner_sink_; + Stats::SymbolTable& symbol_table_; + StatsFilterContext context_; + Stats::TagVector global_tags_; +}; + +} // namespace WasmFilter +} // namespace StatSinks +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/stat_sinks/wasm_filter/test/BUILD b/contrib/stat_sinks/wasm_filter/test/BUILD new file mode 100644 index 0000000000000..7e773ca78462e --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/test/BUILD @@ -0,0 +1,52 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_contrib_package", +) +load( + "//bazel:envoy_select.bzl", + "envoy_select_wasm_cpp_tests", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_test( + name = "wasm_filter_stat_sink_test", + srcs = ["wasm_filter_stat_sink_test.cc"], + deps = [ + "//contrib/stat_sinks/wasm_filter/source:wasm_filter_stat_sink_lib", + "//test/mocks/stats:stats_mocks", + ], +) + +envoy_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + deps = [ + "//contrib/stat_sinks/wasm_filter/source:config_lib", + "//test/mocks/server:server_mocks", + "//test/test_common:utility_lib", + "@envoy_api//contrib/envoy/extensions/stat_sinks/wasm_filter/v3:pkg_cc_proto", + ], +) + +envoy_cc_test( + name = "wasm_filter_stat_sink_integration_test", + srcs = ["wasm_filter_stat_sink_integration_test.cc"], + data = envoy_select_wasm_cpp_tests([ + "//contrib/stat_sinks/wasm_filter/test/test_data:stats_filter_plugin.wasm", + ]), + rbe_pool = "6gig", + tags = ["skip_on_windows"], + deps = [ + "//contrib/stat_sinks/wasm_filter/source:wasm_filter_stat_sink_lib", + "//contrib/stat_sinks/wasm_filter/test/test_data:stats_filter_test_plugin", + "//source/extensions/common/wasm:wasm_lib", + "//source/extensions/wasm_runtime/null:config", + "//test/extensions/common/wasm:wasm_runtime", + "//test/mocks/stats:stats_mocks", + "//test/test_common:wasm_lib", + ], +) diff --git a/contrib/stat_sinks/wasm_filter/test/config_test.cc b/contrib/stat_sinks/wasm_filter/test/config_test.cc new file mode 100644 index 0000000000000..ff23a5466b698 --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/test/config_test.cc @@ -0,0 +1,45 @@ +#include "envoy/registry/registry.h" + +#include "source/common/protobuf/protobuf.h" + +#include "test/mocks/server/mocks.h" +#include "test/test_common/utility.h" + +#include "contrib/envoy/extensions/stat_sinks/wasm_filter/v3/wasm_filter.pb.validate.h" +#include "contrib/stat_sinks/wasm_filter/source/config.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace StatSinks { +namespace WasmFilter { +namespace { + +TEST(WasmFilterSinkFactoryTest, FactoryRegistered) { + auto* factory = Registry::FactoryRegistry::getFactory( + WasmFilterName); + ASSERT_NE(factory, nullptr); + EXPECT_EQ(factory->name(), "envoy.stat_sinks.wasm_filter"); +} + +TEST(WasmFilterSinkFactoryTest, CreateEmptyConfigProto) { + WasmFilterSinkFactory factory; + auto proto = factory.createEmptyConfigProto(); + ASSERT_NE(proto, nullptr); + EXPECT_NE( + dynamic_cast( + proto.get()), + nullptr); +} + +TEST(WasmFilterSinkFactoryTest, Name) { + WasmFilterSinkFactory factory; + EXPECT_EQ(factory.name(), "envoy.stat_sinks.wasm_filter"); +} + +} // namespace +} // namespace WasmFilter +} // namespace StatSinks +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/stat_sinks/wasm_filter/test/test_data/BUILD b/contrib/stat_sinks/wasm_filter/test/test_data/BUILD new file mode 100644 index 0000000000000..68417326db9b4 --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/test/test_data/BUILD @@ -0,0 +1,32 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test_library", + "envoy_contrib_package", +) +load("//bazel/wasm:wasm.bzl", "envoy_wasm_cc_binary") + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_test_library( + name = "stats_filter_test_plugin", + srcs = [ + "stats_filter_plugin.cc", + "stats_filter_plugin_null_plugin.cc", + ], + copts = ["-DNULL_PLUGIN=1"], + deps = [ + "//source/extensions/common/wasm:wasm_hdr", + "//source/extensions/common/wasm:wasm_lib", + "//source/extensions/common/wasm/ext:envoy_null_plugin", + ], +) + +envoy_wasm_cc_binary( + name = "stats_filter_plugin.wasm", + srcs = ["stats_filter_plugin.cc"], + deps = [ + "//source/extensions/common/wasm/ext:envoy_proxy_wasm_api_lib", + ], +) diff --git a/contrib/stat_sinks/wasm_filter/test/test_data/stats_filter_plugin.cc b/contrib/stat_sinks/wasm_filter/test/test_data/stats_filter_plugin.cc new file mode 100644 index 0000000000000..cce1233275029 --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/test/test_data/stats_filter_plugin.cc @@ -0,0 +1,206 @@ +// NOLINT(namespace-envoy) +// +// Test WASM stats filter plugin for envoy.stat_sinks.wasm_filter. +// Exercises all foreign functions: filtering, global tags, name overrides, +// histogram filtering, and synthetic metric injection. + +#include +#include +#include +#include + +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" + +#include "source/extensions/common/wasm/ext/envoy_proxy_wasm_api.h" +#else +#include "source/extensions/common/wasm/ext/envoy_null_plugin.h" +#endif + +START_WASM_PLUGIN(StatsFilterTestPlugin) + +namespace { +WasmResult callForeignFunction(const std::string& name, const char* data, size_t data_size, + char** result, size_t* result_size) { + return proxy_call_foreign_function(name.data(), name.size(), data, data_size, result, + result_size); +} +} // namespace + +struct HistogramInfo { + std::string name; +}; + +class StatsFilterTestRootContext : public EnvoyRootContext { +public: + explicit StatsFilterTestRootContext(uint32_t id, std::string_view root_id) + : EnvoyRootContext(id, root_id) {} + + bool onConfigure(size_t config_size) override; + void onStatsUpdate(uint32_t result_size) override; + +private: + std::vector fetchHistograms() const; +}; + +class StatsFilterTestContext : public EnvoyContext { +public: + explicit StatsFilterTestContext(uint32_t id, RootContext* root) : EnvoyContext(id, root) {} +}; + +static RegisterContextFactory + register_StatsFilterTestContext(CONTEXT_FACTORY(StatsFilterTestContext), + ROOT_FACTORY(StatsFilterTestRootContext)); + +bool StatsFilterTestRootContext::onConfigure(size_t) { + // Set global tags via stats_filter_set_global_tags. + std::string wire; + uint32_t count = 1; + wire.append(reinterpret_cast(&count), sizeof(uint32_t)); + auto appendStr = [&](const std::string& s) { + uint32_t len = s.size(); + wire.append(reinterpret_cast(&len), sizeof(uint32_t)); + wire.append(s); + }; + appendStr("test_tag"); + appendStr("test_value"); + + char* result = nullptr; + size_t result_size = 0; + callForeignFunction("stats_filter_set_global_tags", wire.data(), wire.size(), &result, + &result_size); + logInfo("StatsFilterTestPlugin: global tags set"); + return true; +} + +std::vector StatsFilterTestRootContext::fetchHistograms() const { + char* result = nullptr; + size_t result_size = 0; + auto status = + callForeignFunction("stats_filter_get_histograms", nullptr, 0, &result, &result_size); + if (status != WasmResult::Ok || result == nullptr || result_size < sizeof(uint32_t)) { + return {}; + } + + std::vector histograms; + size_t offset = 0; + uint32_t hist_count = 0; + memcpy(&hist_count, result + offset, sizeof(uint32_t)); // NOLINT(safe-memcpy) + offset += sizeof(uint32_t); + + for (uint32_t i = 0; i < hist_count && offset < result_size; ++i) { + uint32_t name_len = 0; + memcpy(&name_len, result + offset, sizeof(uint32_t)); // NOLINT(safe-memcpy) + offset += sizeof(uint32_t); + histograms.push_back({std::string(result + offset, name_len)}); + offset += name_len; + } + + free(result); // NOLINT(cppcoreguidelines-no-malloc) + return histograms; +} + +void StatsFilterTestRootContext::onStatsUpdate(uint32_t result_size) { + auto stats_buffer = getBufferBytes(WasmBufferType::CallData, 0, result_size); + auto stats = parseStatResults(stats_buffer->view()); + + logInfo("StatsFilterTestPlugin: onStatsUpdate counters=" + std::to_string(stats.counters.size()) + + " gauges=" + std::to_string(stats.gauges.size())); + + // Keep all counters except those starting with "excluded_". + std::vector kept_counter_indices; + for (uint32_t i = 0; i < stats.counters.size(); ++i) { + std::string name(stats.counters[i].name); + if (name.substr(0, 9) != "excluded_") { + kept_counter_indices.push_back(i); + } + } + + // Keep all gauges. + std::vector kept_gauge_indices; + for (uint32_t i = 0; i < stats.gauges.size(); ++i) { + kept_gauge_indices.push_back(i); + } + + // Fetch and keep all histograms. + auto histograms = fetchHistograms(); + std::vector kept_histogram_indices; + for (uint32_t i = 0; i < histograms.size(); ++i) { + kept_histogram_indices.push_back(i); + } + + logInfo("StatsFilterTestPlugin: kept counters=" + std::to_string(kept_counter_indices.size()) + + " histograms=" + std::to_string(kept_histogram_indices.size())); + + // Set name overrides: rename first kept counter with "envoy." prefix. + if (!kept_counter_indices.empty()) { + std::string ovr_wire; + uint32_t ovr_count = 1; + ovr_wire.append(reinterpret_cast(&ovr_count), sizeof(uint32_t)); + uint32_t type = 1; // counter + ovr_wire.append(reinterpret_cast(&type), sizeof(uint32_t)); + uint32_t idx = kept_counter_indices[0]; + ovr_wire.append(reinterpret_cast(&idx), sizeof(uint32_t)); + std::string new_name = "envoy." + std::string(stats.counters[idx].name); + uint32_t name_len = new_name.size(); + ovr_wire.append(reinterpret_cast(&name_len), sizeof(uint32_t)); + ovr_wire.append(new_name); + + char* nr = nullptr; + size_t nrs = 0; + callForeignFunction("stats_filter_set_name_overrides", ovr_wire.data(), ovr_wire.size(), &nr, + &nrs); + } + + // Inject one synthetic counter. + { + std::string inj_wire; + auto appendU32 = [&](uint32_t v) { + inj_wire.append(reinterpret_cast(&v), sizeof(uint32_t)); + }; + auto appendU64 = [&](uint64_t v) { + inj_wire.append(reinterpret_cast(&v), sizeof(uint64_t)); + }; + auto appendString = [&](const std::string& s) { + appendU32(s.size()); + inj_wire.append(s); + }; + + appendU32(1); // 1 synthetic counter + appendString("wasm_filter.metrics_kept"); + appendU64(static_cast(kept_counter_indices.size())); + appendU32(0); // no tags + + appendU32(0); // 0 synthetic gauges + + char* ir = nullptr; + size_t irs = 0; + callForeignFunction("stats_filter_inject_metrics", inj_wire.data(), inj_wire.size(), &ir, &irs); + } + + // Emit kept indices. + { + std::vector wire; + wire.push_back(static_cast(kept_counter_indices.size())); + for (auto idx : kept_counter_indices) { + wire.push_back(idx); + } + wire.push_back(static_cast(kept_gauge_indices.size())); + for (auto idx : kept_gauge_indices) { + wire.push_back(idx); + } + wire.push_back(static_cast(kept_histogram_indices.size())); + for (auto idx : kept_histogram_indices) { + wire.push_back(idx); + } + + char* emit_result = nullptr; + size_t emit_result_size = 0; + callForeignFunction("stats_filter_emit", reinterpret_cast(wire.data()), + wire.size() * sizeof(uint32_t), &emit_result, &emit_result_size); + } + + logInfo("StatsFilterTestPlugin: emit complete"); +} + +END_WASM_PLUGIN diff --git a/contrib/stat_sinks/wasm_filter/test/test_data/stats_filter_plugin_null_plugin.cc b/contrib/stat_sinks/wasm_filter/test/test_data/stats_filter_plugin_null_plugin.cc new file mode 100644 index 0000000000000..a9510d90fc70d --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/test/test_data/stats_filter_plugin_null_plugin.cc @@ -0,0 +1,15 @@ +// NOLINT(namespace-envoy) +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace StatsFilterTestPlugin { +NullPluginRegistry* context_registry_; +} // namespace StatsFilterTestPlugin + +RegisterNullVmPluginFactory register_stats_filter_test_plugin("StatsFilterTestPlugin", []() { + return std::make_unique(StatsFilterTestPlugin::context_registry_); +}); + +} // namespace null_plugin +} // namespace proxy_wasm diff --git a/contrib/stat_sinks/wasm_filter/test/wasm_filter_stat_sink_integration_test.cc b/contrib/stat_sinks/wasm_filter/test/wasm_filter_stat_sink_integration_test.cc new file mode 100644 index 0000000000000..2c92e369fa4e8 --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/test/wasm_filter_stat_sink_integration_test.cc @@ -0,0 +1,108 @@ +#include "source/extensions/common/wasm/wasm.h" + +#include "test/extensions/common/wasm/wasm_runtime.h" +#include "test/mocks/stats/mocks.h" +#include "test/mocks/upstream/mocks.h" +#include "test/test_common/wasm_base.h" + +#include "contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Eq; +using testing::NiceMock; + +namespace Envoy { +namespace Extensions { +namespace StatSinks { +namespace WasmFilter { + +class TestContext : public ::Envoy::Extensions::Common::Wasm::Context { +public: + using ::Envoy::Extensions::Common::Wasm::Context::Context; + ~TestContext() override = default; + using ::Envoy::Extensions::Common::Wasm::Context::log; + proxy_wasm::WasmResult log(uint32_t level, std::string_view message) override { + std::cerr << message << "\n"; + log_(static_cast(level), toAbslStringView(message)); + Extensions::Common::Wasm::Context::log(static_cast(level), message); + return proxy_wasm::WasmResult::Ok; + } + MOCK_METHOD(void, log_, (spdlog::level::level_enum level, absl::string_view message)); +}; + +class WasmFilterStatSinkIntegrationTest + : public Common::Wasm::WasmTestBase< + testing::TestWithParam>> { +public: + WasmFilterStatSinkIntegrationTest() = default; + + void setup(const std::string& code, std::string root_id = "") { + setRootId(root_id); + setupBase(std::get<0>(GetParam()), code, + [](Common::Wasm::Wasm* wasm, const std::shared_ptr& plugin) + -> proxy_wasm::ContextBase* { return new NiceMock(wasm, plugin); }); + } + + TestContext& rootContext() { return *static_cast(root_context_); } + + void setupSnapshot() { + kept_counter_.name_ = "upstream_rq_2xx"; + kept_counter_.latch_ = 10; + kept_counter_.used_ = true; + + excluded_counter_.name_ = "excluded_debug_stat"; + excluded_counter_.latch_ = 5; + excluded_counter_.used_ = true; + + gauge_.name_ = "membership_total"; + gauge_.value_ = 42; + gauge_.used_ = true; + + snapshot_.counters_ = {{10, kept_counter_}, {5, excluded_counter_}}; + snapshot_.gauges_ = {gauge_}; + + ON_CALL(snapshot_, counters()).WillByDefault(testing::ReturnRef(snapshot_.counters_)); + ON_CALL(snapshot_, gauges()).WillByDefault(testing::ReturnRef(snapshot_.gauges_)); + ON_CALL(snapshot_, histograms()).WillByDefault(testing::ReturnRef(snapshot_.histograms_)); + ON_CALL(snapshot_, textReadouts()).WillByDefault(testing::ReturnRef(snapshot_.text_readouts_)); + ON_CALL(snapshot_, hostCounters()).WillByDefault(testing::ReturnRef(snapshot_.host_counters_)); + ON_CALL(snapshot_, hostGauges()).WillByDefault(testing::ReturnRef(snapshot_.host_gauges_)); + } + + NiceMock kept_counter_; + NiceMock excluded_counter_; + NiceMock gauge_; + NiceMock snapshot_; +}; + +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFilterStatSinkIntegrationTest, + Envoy::Extensions::Common::Wasm::runtime_and_cpp_values, + Envoy::Extensions::Common::Wasm::wasmTestParamsToString); + +TEST_P(WasmFilterStatSinkIntegrationTest, PluginSetsGlobalTagsAndFiltersMetrics) { + std::string code; + if (std::get<0>(GetParam()) != "null") { + code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + absl::StrCat("{{ test_rundir " + "}}/contrib/stat_sinks/wasm_filter/test/test_data/stats_filter_plugin.wasm"))); + } else { + code = "StatsFilterTestPlugin"; + } + EXPECT_FALSE(code.empty()); + setup(code); + setupSnapshot(); + + EXPECT_CALL(rootContext(), log_(spdlog::level::info, + Eq("StatsFilterTestPlugin: onStatsUpdate counters=2 gauges=1"))); + EXPECT_CALL(rootContext(), + log_(spdlog::level::info, Eq("StatsFilterTestPlugin: kept counters=1 histograms=0"))); + EXPECT_CALL(rootContext(), log_(spdlog::level::info, Eq("StatsFilterTestPlugin: emit complete"))); + + rootContext().onStatsUpdate(snapshot_); +} + +} // namespace WasmFilter +} // namespace StatSinks +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/stat_sinks/wasm_filter/test/wasm_filter_stat_sink_test.cc b/contrib/stat_sinks/wasm_filter/test/wasm_filter_stat_sink_test.cc new file mode 100644 index 0000000000000..2bf040a7fbefe --- /dev/null +++ b/contrib/stat_sinks/wasm_filter/test/wasm_filter_stat_sink_test.cc @@ -0,0 +1,1490 @@ +#include "test/mocks/stats/mocks.h" + +#include "contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; + +namespace Envoy { +namespace Extensions { +namespace StatSinks { +namespace WasmFilter { +namespace { + +// ==================================================================== +// Helpers +// ==================================================================== + +void appendU32(std::string& buf, uint32_t v) { + buf.append(reinterpret_cast(&v), sizeof(uint32_t)); +} + +void appendU64(std::string& buf, uint64_t v) { + buf.append(reinterpret_cast(&v), sizeof(uint64_t)); +} + +void appendStr(std::string& buf, const std::string& s) { + appendU32(buf, s.size()); + buf.append(s); +} + +proxy_wasm::WasmForeignFunction getFF(const std::string& name) { + return proxy_wasm::getForeignFunction(name); +} + +proxy_wasm::WasmResult callFF(const std::string& name, const std::string& args, + const std::function& alloc = nullptr) { + auto ff = getFF(name); + if (ff == nullptr) { + return proxy_wasm::WasmResult::NotFound; + } + proxy_wasm::WasmBase wasm_base(nullptr, "", "", "", {}, {}); + auto real_alloc = alloc ? alloc : [](size_t) -> void* { return nullptr; }; + return ff(wasm_base, args, real_alloc); +} + +// ==================================================================== +// Enriched Metric Snapshot fixture +// ==================================================================== + +class EnrichedMetricSnapshotTest : public testing::Test { +protected: + void SetUp() override { + counter_a_.name_ = "upstream_rq_2xx"; + counter_a_.latch_ = 10; + counter_a_.used_ = true; + counter_a_.setTags({{"envoy.cluster_name", "local_service"}}); + + counter_b_.name_ = "upstream_rq_5xx"; + counter_b_.latch_ = 5; + counter_b_.used_ = true; + + counter_c_.name_ = "upstream_rq_total"; + counter_c_.latch_ = 15; + counter_c_.used_ = true; + + gauge_a_.name_ = "membership_total"; + gauge_a_.value_ = 100; + gauge_a_.used_ = true; + gauge_a_.setTags({{"envoy.cluster_name", "local_service"}}); + + gauge_b_.name_ = "connections_active"; + gauge_b_.value_ = 42; + gauge_b_.used_ = true; + + histogram_a_.name_ = "upstream_rq_time"; + histogram_a_.used_ = true; + histogram_a_.setTags({{"envoy.cluster_name", "local_service"}}); + + histogram_b_.name_ = "downstream_rq_time"; + histogram_b_.used_ = true; + + snapshot_.counters_ = {{10, counter_a_}, {5, counter_b_}, {15, counter_c_}}; + snapshot_.gauges_ = {gauge_a_, gauge_b_}; + snapshot_.histograms_ = {histogram_a_, histogram_b_}; + + ON_CALL(snapshot_, counters()).WillByDefault(testing::ReturnRef(snapshot_.counters_)); + ON_CALL(snapshot_, gauges()).WillByDefault(testing::ReturnRef(snapshot_.gauges_)); + ON_CALL(snapshot_, histograms()).WillByDefault(testing::ReturnRef(snapshot_.histograms_)); + ON_CALL(snapshot_, textReadouts()).WillByDefault(testing::ReturnRef(snapshot_.text_readouts_)); + ON_CALL(snapshot_, hostCounters()).WillByDefault(testing::ReturnRef(snapshot_.host_counters_)); + ON_CALL(snapshot_, hostGauges()).WillByDefault(testing::ReturnRef(snapshot_.host_gauges_)); + } + + StatsFilterContext makeCtxKeepAll() { + StatsFilterContext ctx; + ctx.kept_counter_indices = {0, 1, 2}; + ctx.kept_gauge_indices = {0, 1}; + ctx.kept_histogram_indices = {0, 1}; + ctx.emit_called = true; + ctx.histogram_block_present = true; + ctx.snapshot = &snapshot_; + return ctx; + } + + NiceMock counter_a_; + NiceMock counter_b_; + NiceMock counter_c_; + NiceMock gauge_a_; + NiceMock gauge_b_; + NiceMock histogram_a_; + NiceMock histogram_b_; + NiceMock snapshot_; + Stats::TagVector empty_tags_; + Stats::SymbolTable& symbol_table_{counter_a_.symbolTable()}; +}; + +// ==================================================================== +// Filtering tests +// ==================================================================== + +TEST_F(EnrichedMetricSnapshotTest, KeepAll) { + auto ctx = makeCtxKeepAll(); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + EXPECT_EQ(enriched.counters().size(), 3); + EXPECT_EQ(enriched.gauges().size(), 2); + EXPECT_EQ(enriched.histograms().size(), 2); +} + +TEST_F(EnrichedMetricSnapshotTest, DropAll) { + StatsFilterContext ctx; + ctx.kept_counter_indices = {}; + ctx.kept_gauge_indices = {}; + ctx.kept_histogram_indices = {999}; + ctx.emit_called = true; + ctx.histogram_block_present = true; + ctx.snapshot = &snapshot_; + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + EXPECT_EQ(enriched.counters().size(), 0); + EXPECT_EQ(enriched.gauges().size(), 0); + EXPECT_EQ(enriched.histograms().size(), 0); +} + +TEST_F(EnrichedMetricSnapshotTest, KeepSubsetOfCounters) { + StatsFilterContext ctx; + ctx.kept_counter_indices = {0, 2}; + ctx.kept_gauge_indices = {0, 1}; + ctx.snapshot = &snapshot_; + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + ASSERT_EQ(enriched.counters().size(), 2); + EXPECT_EQ(enriched.counters()[0].counter_.get().name(), "upstream_rq_2xx"); + EXPECT_EQ(enriched.counters()[0].delta_, 10); + EXPECT_EQ(enriched.counters()[1].counter_.get().name(), "upstream_rq_total"); +} + +TEST_F(EnrichedMetricSnapshotTest, EmptyHistogramIndicesPassAllThrough) { + StatsFilterContext ctx; + ctx.kept_counter_indices = {0}; + ctx.kept_gauge_indices = {0}; + ctx.snapshot = &snapshot_; + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + EXPECT_EQ(enriched.histograms().size(), 2); +} + +// ==================================================================== +// Global tag injection tests +// ==================================================================== + +TEST_F(EnrichedMetricSnapshotTest, GlobalTagsAppliedToAllMetrics) { + Stats::TagVector global_tags = {{"datacenter", "us-east-1"}, {"pod", "pod42"}}; + auto ctx = makeCtxKeepAll(); + EnrichedMetricSnapshot enriched(snapshot_, ctx, global_tags, symbol_table_); + + auto counter_tags = enriched.counters()[0].counter_.get().tags(); + EXPECT_GE(counter_tags.size(), 2); + EXPECT_EQ(counter_tags[counter_tags.size() - 2].name_, "datacenter"); + EXPECT_EQ(counter_tags[counter_tags.size() - 2].value_, "us-east-1"); + EXPECT_EQ(counter_tags[counter_tags.size() - 1].name_, "pod"); + EXPECT_EQ(counter_tags[counter_tags.size() - 1].value_, "pod42"); + + auto gauge_tags = enriched.gauges()[0].get().tags(); + EXPECT_GE(gauge_tags.size(), 2); + EXPECT_EQ(gauge_tags[gauge_tags.size() - 1].name_, "pod"); + + auto hist_tags = enriched.histograms()[0].get().tags(); + EXPECT_GE(hist_tags.size(), 2); + EXPECT_EQ(hist_tags[hist_tags.size() - 1].name_, "pod"); +} + +// ==================================================================== +// Name override tests +// ==================================================================== + +TEST_F(EnrichedMetricSnapshotTest, NameOverrideOnCounter) { + auto ctx = makeCtxKeepAll(); + ctx.name_overrides.push_back({1, 0, "envoy.upstream_rq_2xx"}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + EXPECT_EQ(enriched.counters()[0].counter_.get().name(), "envoy.upstream_rq_2xx"); + EXPECT_EQ(enriched.counters()[1].counter_.get().name(), "upstream_rq_5xx"); +} + +TEST_F(EnrichedMetricSnapshotTest, NameOverrideOnGauge) { + auto ctx = makeCtxKeepAll(); + ctx.name_overrides.push_back({2, 1, "envoy.connections_active"}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + EXPECT_EQ(enriched.gauges()[1].get().name(), "envoy.connections_active"); +} + +TEST_F(EnrichedMetricSnapshotTest, NameOverrideOnHistogram) { + auto ctx = makeCtxKeepAll(); + ctx.name_overrides.push_back({3, 0, "envoy.upstream_rq_time"}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + EXPECT_EQ(enriched.histograms()[0].get().name(), "envoy.upstream_rq_time"); +} + +TEST_F(EnrichedMetricSnapshotTest, NameOverrideOutOfRange) { + auto ctx = makeCtxKeepAll(); + ctx.name_overrides.push_back({1, 999, "out_of_range"}); + ctx.name_overrides.push_back({2, 999, "out_of_range"}); + ctx.name_overrides.push_back({3, 999, "out_of_range"}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + EXPECT_EQ(enriched.counters()[0].counter_.get().name(), "upstream_rq_2xx"); + EXPECT_EQ(enriched.gauges()[0].get().name(), "membership_total"); + EXPECT_EQ(enriched.histograms()[0].get().name(), "upstream_rq_time"); +} + +// ==================================================================== +// Synthetic metric injection tests +// ==================================================================== + +TEST_F(EnrichedMetricSnapshotTest, SyntheticCounterInjected) { + auto ctx = makeCtxKeepAll(); + ctx.synthetic_counters.push_back({"wasm_filter.metrics_emitted", 42, {}}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + ASSERT_EQ(enriched.counters().size(), 4); + EXPECT_EQ(enriched.counters()[3].counter_.get().name(), "wasm_filter.metrics_emitted"); + EXPECT_EQ(enriched.counters()[3].counter_.get().value(), 42); + EXPECT_EQ(enriched.counters()[3].delta_, 42); +} + +TEST_F(EnrichedMetricSnapshotTest, SyntheticGaugeInjected) { + auto ctx = makeCtxKeepAll(); + ctx.synthetic_gauges.push_back({"app.version", 1, {{"version", "v1.2.3"}}}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + ASSERT_EQ(enriched.gauges().size(), 3); + EXPECT_EQ(enriched.gauges()[2].get().name(), "app.version"); + EXPECT_EQ(enriched.gauges()[2].get().value(), 1); + auto tags = enriched.gauges()[2].get().tags(); + ASSERT_EQ(tags.size(), 1); + EXPECT_EQ(tags[0].name_, "version"); + EXPECT_EQ(tags[0].value_, "v1.2.3"); +} + +TEST_F(EnrichedMetricSnapshotTest, SyntheticMetricsGetGlobalTags) { + Stats::TagVector global_tags = {{"dc", "us-east-1"}}; + auto ctx = makeCtxKeepAll(); + ctx.synthetic_counters.push_back({"custom.count", 10, {}}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, global_tags, symbol_table_); + + auto last = enriched.counters().back(); + auto tags = last.counter_.get().tags(); + ASSERT_GE(tags.size(), 1); + EXPECT_EQ(tags.back().name_, "dc"); +} + +// ==================================================================== +// Snapshot forwarding tests +// ==================================================================== + +TEST_F(EnrichedMetricSnapshotTest, ForwardsTextReadoutsHostCountersHostGauges) { + auto ctx = makeCtxKeepAll(); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + EXPECT_EQ(&enriched.textReadouts(), &snapshot_.text_readouts_); + EXPECT_EQ(&enriched.hostCounters(), &snapshot_.host_counters_); + EXPECT_EQ(&enriched.hostGauges(), &snapshot_.host_gauges_); +} + +TEST_F(EnrichedMetricSnapshotTest, ForwardsSnapshotTime) { + auto ctx = makeCtxKeepAll(); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + enriched.snapshotTime(); +} + +// ==================================================================== +// Combined test +// ==================================================================== + +TEST_F(EnrichedMetricSnapshotTest, FilterRenameTagInjectCombined) { + Stats::TagVector global_tags = {{"env", "prod"}}; + StatsFilterContext ctx; + ctx.kept_counter_indices = {0}; + ctx.kept_gauge_indices = {1}; + ctx.snapshot = &snapshot_; + ctx.name_overrides.push_back({1, 0, "envoy.upstream_rq_2xx"}); + ctx.synthetic_gauges.push_back({"wasm_filter.queue_depth", 7, {}}); + + EnrichedMetricSnapshot enriched(snapshot_, ctx, global_tags, symbol_table_); + + ASSERT_EQ(enriched.counters().size(), 1); + EXPECT_EQ(enriched.counters()[0].counter_.get().name(), "envoy.upstream_rq_2xx"); + auto ctags = enriched.counters()[0].counter_.get().tags(); + EXPECT_EQ(ctags.back().name_, "env"); + + ASSERT_EQ(enriched.gauges().size(), 2); + EXPECT_EQ(enriched.gauges()[0].get().name(), "connections_active"); + EXPECT_EQ(enriched.gauges()[1].get().name(), "wasm_filter.queue_depth"); + + EXPECT_EQ(enriched.histograms().size(), 2); +} + +// ==================================================================== +// Enriched wrapper method tests +// ==================================================================== + +TEST_F(EnrichedMetricSnapshotTest, EnrichedCounterMethods) { + auto ctx = makeCtxKeepAll(); + ctx.name_overrides.push_back({1, 0, "envoy.upstream_rq_2xx"}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + const auto& cref = enriched.counters()[0].counter_.get(); + EXPECT_EQ(cref.name(), "envoy.upstream_rq_2xx"); + EXPECT_EQ(cref.tagExtractedName(), "envoy.upstream_rq_2xx"); + EXPECT_TRUE(cref.used()); + EXPECT_FALSE(cref.hidden()); + EXPECT_EQ(cref.use_count(), 1); + EXPECT_FALSE(cref.tags().empty()); + + auto& mut_cref = const_cast(cref); + mut_cref.add(0); + mut_cref.inc(); + EXPECT_EQ(mut_cref.latch(), 0); + mut_cref.reset(); + mut_cref.incRefCount(); + EXPECT_FALSE(mut_cref.decRefCount()); + mut_cref.markUnused(); +} + +TEST_F(EnrichedMetricSnapshotTest, EnrichedCounterOriginalName) { + auto ctx = makeCtxKeepAll(); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + const auto& cref = enriched.counters()[0].counter_.get(); + EXPECT_EQ(cref.name(), "upstream_rq_2xx"); + EXPECT_EQ(cref.tagExtractedName(), "upstream_rq_2xx"); +} + +TEST_F(EnrichedMetricSnapshotTest, EnrichedGaugeMethods) { + auto ctx = makeCtxKeepAll(); + ctx.name_overrides.push_back({2, 0, "envoy.membership"}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + const auto& gref = enriched.gauges()[0].get(); + EXPECT_EQ(gref.name(), "envoy.membership"); + EXPECT_EQ(gref.tagExtractedName(), "envoy.membership"); + EXPECT_EQ(gref.value(), 100); + EXPECT_TRUE(gref.used()); + EXPECT_FALSE(gref.hidden()); + EXPECT_EQ(gref.use_count(), 1); + EXPECT_FALSE(gref.tags().empty()); + + auto& mut_gref = const_cast(gref); + mut_gref.add(0); + mut_gref.dec(); + mut_gref.inc(); + mut_gref.set(0); + mut_gref.sub(0); + mut_gref.setParentValue(0); + mut_gref.importMode(); + mut_gref.mergeImportMode(Stats::Gauge::ImportMode::NeverImport); + mut_gref.incRefCount(); + EXPECT_FALSE(mut_gref.decRefCount()); + mut_gref.markUnused(); +} + +TEST_F(EnrichedMetricSnapshotTest, EnrichedGaugeOriginalName) { + auto ctx = makeCtxKeepAll(); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + const auto& gref = enriched.gauges()[0].get(); + EXPECT_EQ(gref.name(), "membership_total"); + EXPECT_EQ(gref.tagExtractedName(), "membership_total"); +} + +TEST_F(EnrichedMetricSnapshotTest, EnrichedHistogramMethods) { + auto ctx = makeCtxKeepAll(); + ctx.name_overrides.push_back({3, 0, "envoy.upstream_rq_time"}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + const auto& href = enriched.histograms()[0].get(); + EXPECT_EQ(href.name(), "envoy.upstream_rq_time"); + EXPECT_EQ(href.tagExtractedName(), "envoy.upstream_rq_time"); + EXPECT_TRUE(href.used()); + EXPECT_FALSE(href.hidden()); + EXPECT_EQ(href.use_count(), 1); + EXPECT_FALSE(href.tags().empty()); + href.unit(); + href.quantileSummary(); + href.bucketSummary(); + href.detailedTotalBuckets(); + href.detailedIntervalBuckets(); + href.cumulativeCountLessThanOrEqualToValue(1.0); + href.intervalStatistics(); + href.cumulativeStatistics(); + + auto& mut_href = const_cast(href); + mut_href.recordValue(0); + mut_href.merge(); + mut_href.incRefCount(); + EXPECT_FALSE(mut_href.decRefCount()); + mut_href.markUnused(); +} + +TEST_F(EnrichedMetricSnapshotTest, EnrichedHistogramOriginalName) { + auto ctx = makeCtxKeepAll(); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + const auto& href = enriched.histograms()[0].get(); + EXPECT_EQ(href.name(), "upstream_rq_time"); + EXPECT_EQ(href.tagExtractedName(), "upstream_rq_time"); +} + +TEST_F(EnrichedMetricSnapshotTest, SyntheticCounterMethods) { + auto ctx = makeCtxKeepAll(); + ctx.synthetic_counters.push_back({"syn.count", 77, {{"k", "v"}}}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + const auto& sc = enriched.counters().back().counter_.get(); + EXPECT_EQ(sc.name(), "syn.count"); + EXPECT_EQ(sc.tagExtractedName(), "syn.count"); + EXPECT_EQ(sc.value(), 77); + EXPECT_TRUE(sc.used()); + EXPECT_FALSE(sc.hidden()); + EXPECT_EQ(sc.use_count(), 1); + EXPECT_EQ(sc.tags().size(), 1); + sc.constSymbolTable(); + + auto& mut_sc = const_cast(sc); + mut_sc.add(0); + mut_sc.inc(); + EXPECT_EQ(mut_sc.latch(), 0); + mut_sc.reset(); + mut_sc.symbolTable(); + mut_sc.incRefCount(); + EXPECT_FALSE(mut_sc.decRefCount()); + mut_sc.markUnused(); + mut_sc.iterateTagStatNames([](Stats::StatName, Stats::StatName) { return true; }); +} + +TEST_F(EnrichedMetricSnapshotTest, SyntheticGaugeMethods) { + auto ctx = makeCtxKeepAll(); + ctx.synthetic_gauges.push_back({"syn.gauge", 33, {{"x", "y"}}}); + EnrichedMetricSnapshot enriched(snapshot_, ctx, empty_tags_, symbol_table_); + + const auto& sg = enriched.gauges().back().get(); + EXPECT_EQ(sg.name(), "syn.gauge"); + EXPECT_EQ(sg.tagExtractedName(), "syn.gauge"); + EXPECT_EQ(sg.value(), 33); + EXPECT_TRUE(sg.used()); + EXPECT_FALSE(sg.hidden()); + EXPECT_EQ(sg.use_count(), 1); + EXPECT_EQ(sg.tags().size(), 1); + sg.constSymbolTable(); + EXPECT_EQ(sg.importMode(), Stats::Gauge::ImportMode::NeverImport); + + auto& mut_sg = const_cast(sg); + mut_sg.add(0); + mut_sg.dec(); + mut_sg.inc(); + mut_sg.set(0); + mut_sg.sub(0); + mut_sg.setParentValue(0); + mut_sg.mergeImportMode(Stats::Gauge::ImportMode::NeverImport); + mut_sg.symbolTable(); + mut_sg.incRefCount(); + EXPECT_FALSE(mut_sg.decRefCount()); + mut_sg.markUnused(); + mut_sg.iterateTagStatNames([](Stats::StatName, Stats::StatName) { return true; }); +} + +// ==================================================================== +// MergeTags utility +// ==================================================================== + +TEST(MergeTagsTest, EmptyExtraReturnsOriginal) { + Stats::TagVector original = {{"a", "1"}}; + Stats::TagVector extra = {}; + auto merged = mergeTags(original, extra); + ASSERT_EQ(merged.size(), 1); + EXPECT_EQ(merged[0].name_, "a"); +} + +TEST(MergeTagsTest, MergesBothVectors) { + Stats::TagVector original = {{"a", "1"}}; + Stats::TagVector extra = {{"b", "2"}, {"c", "3"}}; + auto merged = mergeTags(original, extra); + ASSERT_EQ(merged.size(), 3); + EXPECT_EQ(merged[0].name_, "a"); + EXPECT_EQ(merged[1].name_, "b"); + EXPECT_EQ(merged[2].name_, "c"); +} + +// ==================================================================== +// StatsFilterContext tests +// ==================================================================== + +TEST(StatsFilterContextAccessorTest, ThreadLocalAccessors) { + EXPECT_EQ(getActiveContext(), nullptr); + + StatsFilterContext ctx; + setActiveContext(&ctx); + EXPECT_EQ(getActiveContext(), &ctx); + + setActiveContext(nullptr); + EXPECT_EQ(getActiveContext(), nullptr); +} + +TEST(StatsFilterContextAccessorTest, GlobalTagsAccessors) { + EXPECT_EQ(getGlobalTags(), nullptr); + + Stats::TagVector tags = {{"k", "v"}}; + setGlobalTags(&tags); + EXPECT_EQ(getGlobalTags(), &tags); + + setGlobalTags(nullptr); + EXPECT_EQ(getGlobalTags(), nullptr); +} + +TEST(StatsFilterContextAccessorTest, ClearResetsEverything) { + StatsFilterContext ctx; + ctx.kept_counter_indices.insert(1); + ctx.kept_gauge_indices.insert(2); + ctx.kept_histogram_indices.insert(3); + ctx.emit_called = true; + ctx.histogram_block_present = true; + ctx.name_overrides.push_back({1, 0, "foo"}); + ctx.synthetic_counters.push_back({"bar", 1, {}}); + ctx.synthetic_gauges.push_back({"baz", 2, {}}); + ctx.counter_buffer_to_snapshot = {0, 1, 2}; + ctx.gauge_buffer_to_snapshot = {0, 1}; + NiceMock snap; + ctx.snapshot = &snap; + + ctx.clear(); + + EXPECT_TRUE(ctx.kept_counter_indices.empty()); + EXPECT_TRUE(ctx.kept_gauge_indices.empty()); + EXPECT_TRUE(ctx.kept_histogram_indices.empty()); + EXPECT_FALSE(ctx.emit_called); + EXPECT_FALSE(ctx.histogram_block_present); + EXPECT_TRUE(ctx.name_overrides.empty()); + EXPECT_TRUE(ctx.synthetic_counters.empty()); + EXPECT_TRUE(ctx.synthetic_gauges.empty()); + EXPECT_TRUE(ctx.counter_buffer_to_snapshot.empty()); + EXPECT_TRUE(ctx.gauge_buffer_to_snapshot.empty()); + EXPECT_EQ(ctx.snapshot, nullptr); +} + +// ==================================================================== +// Foreign function: stats_filter_emit +// ==================================================================== + +TEST(StatsFilterEmitTest, ParseCountersGaugesAndHistograms) { + StatsFilterContext ctx; + setActiveContext(&ctx); + + std::vector wire = {2, 0, 2, 1, 1, 1, 0}; + std::string arguments(reinterpret_cast(wire.data()), wire.size() * sizeof(uint32_t)); + + EXPECT_EQ(callFF("stats_filter_emit", arguments), proxy_wasm::WasmResult::Ok); + + EXPECT_EQ(ctx.kept_counter_indices.size(), 2); + EXPECT_TRUE(ctx.kept_counter_indices.contains(0)); + EXPECT_TRUE(ctx.kept_counter_indices.contains(2)); + EXPECT_EQ(ctx.kept_gauge_indices.size(), 1); + EXPECT_TRUE(ctx.kept_gauge_indices.contains(1)); + EXPECT_EQ(ctx.kept_histogram_indices.size(), 1); + EXPECT_TRUE(ctx.kept_histogram_indices.contains(0)); + EXPECT_TRUE(ctx.emit_called); + EXPECT_TRUE(ctx.histogram_block_present); + + setActiveContext(nullptr); +} + +TEST(StatsFilterEmitTest, HistogramBlockIsOptional) { + StatsFilterContext ctx; + setActiveContext(&ctx); + + std::vector wire = {1, 0, 0}; + std::string arguments(reinterpret_cast(wire.data()), wire.size() * sizeof(uint32_t)); + + EXPECT_EQ(callFF("stats_filter_emit", arguments), proxy_wasm::WasmResult::Ok); + EXPECT_EQ(ctx.kept_counter_indices.size(), 1); + EXPECT_TRUE(ctx.kept_histogram_indices.empty()); + EXPECT_TRUE(ctx.emit_called); + EXPECT_FALSE(ctx.histogram_block_present); + + setActiveContext(nullptr); +} + +TEST(StatsFilterEmitTest, NoActiveContext) { + setActiveContext(nullptr); + + std::vector wire = {0, 0}; + std::string arguments(reinterpret_cast(wire.data()), wire.size() * sizeof(uint32_t)); + + EXPECT_EQ(callFF("stats_filter_emit", arguments), proxy_wasm::WasmResult::InternalFailure); +} + +TEST(StatsFilterEmitTest, EmptyArguments) { + StatsFilterContext ctx; + setActiveContext(&ctx); + EXPECT_EQ(callFF("stats_filter_emit", ""), proxy_wasm::WasmResult::BadArgument); + setActiveContext(nullptr); +} + +TEST(StatsFilterEmitTest, TruncatedCounterBlock) { + StatsFilterContext ctx; + setActiveContext(&ctx); + + std::string wire; + appendU32(wire, 5); + appendU32(wire, 0); + + EXPECT_EQ(callFF("stats_filter_emit", wire), proxy_wasm::WasmResult::BadArgument); + setActiveContext(nullptr); +} + +TEST(StatsFilterEmitTest, TruncatedGaugeBlock) { + StatsFilterContext ctx; + setActiveContext(&ctx); + + std::string wire; + appendU32(wire, 0); + appendU32(wire, 10); + appendU32(wire, 0); + + EXPECT_EQ(callFF("stats_filter_emit", wire), proxy_wasm::WasmResult::BadArgument); + setActiveContext(nullptr); +} + +// ==================================================================== +// Foreign function: stats_filter_set_global_tags +// ==================================================================== + +TEST(StatsFilterGlobalTagsTest, SetGlobalTagsWithActivePointer) { + Stats::TagVector tags; + setGlobalTags(&tags); + + std::string wire; + appendU32(wire, 2); + appendStr(wire, "dc"); + appendStr(wire, "us"); + appendStr(wire, "pod"); + appendStr(wire, "p42"); + + EXPECT_EQ(callFF("stats_filter_set_global_tags", wire), proxy_wasm::WasmResult::Ok); + + ASSERT_EQ(tags.size(), 2); + EXPECT_EQ(tags[0].name_, "dc"); + EXPECT_EQ(tags[0].value_, "us"); + EXPECT_EQ(tags[1].name_, "pod"); + EXPECT_EQ(tags[1].value_, "p42"); + + setGlobalTags(nullptr); +} + +TEST(StatsFilterGlobalTagsTest, SetGlobalTagsNullPointerReturnsFailure) { + setGlobalTags(nullptr); + + std::string wire; + appendU32(wire, 1); + appendStr(wire, "env"); + appendStr(wire, "staging"); + + EXPECT_EQ(callFF("stats_filter_set_global_tags", wire), proxy_wasm::WasmResult::InternalFailure); +} + +TEST(StatsFilterGlobalTagsTest, SetGlobalTagsBadArgument) { + Stats::TagVector tags; + setGlobalTags(&tags); + + std::string wire; + appendU32(wire, 1); + appendStr(wire, "key"); + + EXPECT_EQ(callFF("stats_filter_set_global_tags", wire), proxy_wasm::WasmResult::BadArgument); + setGlobalTags(nullptr); +} + +TEST(StatsFilterGlobalTagsTest, ActivePointerClearsBeforeReuse) { + Stats::TagVector tags = {{"old", "val"}}; + setGlobalTags(&tags); + + std::string wire; + appendU32(wire, 1); + appendStr(wire, "new"); + appendStr(wire, "val"); + + EXPECT_EQ(callFF("stats_filter_set_global_tags", wire), proxy_wasm::WasmResult::Ok); + ASSERT_EQ(tags.size(), 1); + EXPECT_EQ(tags[0].name_, "new"); + + setGlobalTags(nullptr); +} + +// ==================================================================== +// Foreign function: stats_filter_set_name_overrides +// ==================================================================== + +TEST(StatsFilterNameOverridesTest, SetNameOverrides) { + StatsFilterContext ctx; + setActiveContext(&ctx); + + std::string wire; + appendU32(wire, 1); + appendU32(wire, 1); + appendU32(wire, 0); + appendStr(wire, "envoy.rq"); + + EXPECT_EQ(callFF("stats_filter_set_name_overrides", wire), proxy_wasm::WasmResult::Ok); + + ASSERT_EQ(ctx.name_overrides.size(), 1); + EXPECT_EQ(ctx.name_overrides[0].type, 1); + EXPECT_EQ(ctx.name_overrides[0].index, 0); + EXPECT_EQ(ctx.name_overrides[0].new_name, "envoy.rq"); + + setActiveContext(nullptr); +} + +TEST(StatsFilterNameOverridesTest, NoActiveContext) { + setActiveContext(nullptr); + std::string wire; + appendU32(wire, 0); + EXPECT_EQ(callFF("stats_filter_set_name_overrides", wire), + proxy_wasm::WasmResult::InternalFailure); +} + +TEST(StatsFilterNameOverridesTest, BadArguments) { + StatsFilterContext ctx; + setActiveContext(&ctx); + EXPECT_EQ(callFF("stats_filter_set_name_overrides", ""), proxy_wasm::WasmResult::BadArgument); + setActiveContext(nullptr); +} + +TEST(StatsFilterNameOverridesTest, TruncatedOverride) { + StatsFilterContext ctx; + setActiveContext(&ctx); + + std::string wire; + appendU32(wire, 1); + appendU32(wire, 1); + + EXPECT_EQ(callFF("stats_filter_set_name_overrides", wire), proxy_wasm::WasmResult::BadArgument); + setActiveContext(nullptr); +} + +TEST(StatsFilterNameOverridesTest, TruncatedName) { + StatsFilterContext ctx; + setActiveContext(&ctx); + + std::string wire; + appendU32(wire, 1); + appendU32(wire, 1); + appendU32(wire, 0); + appendU32(wire, 100); + + EXPECT_EQ(callFF("stats_filter_set_name_overrides", wire), proxy_wasm::WasmResult::BadArgument); + setActiveContext(nullptr); +} + +// ==================================================================== +// Foreign function: stats_filter_inject_metrics +// ==================================================================== + +TEST(StatsFilterInjectMetricsTest, InjectCounterAndGauge) { + StatsFilterContext ctx; + setActiveContext(&ctx); + + std::string wire; + appendU32(wire, 1); + appendStr(wire, "custom.count"); + appendU64(wire, 99); + appendU32(wire, 0); + appendU32(wire, 1); + appendStr(wire, "custom.gauge"); + appendU64(wire, 7); + appendU32(wire, 1); + appendStr(wire, "k"); + appendStr(wire, "v"); + + EXPECT_EQ(callFF("stats_filter_inject_metrics", wire), proxy_wasm::WasmResult::Ok); + + ASSERT_EQ(ctx.synthetic_counters.size(), 1); + EXPECT_EQ(ctx.synthetic_counters[0].name, "custom.count"); + EXPECT_EQ(ctx.synthetic_counters[0].value, 99); + + ASSERT_EQ(ctx.synthetic_gauges.size(), 1); + EXPECT_EQ(ctx.synthetic_gauges[0].name, "custom.gauge"); + EXPECT_EQ(ctx.synthetic_gauges[0].value, 7); + ASSERT_EQ(ctx.synthetic_gauges[0].tags.size(), 1); + EXPECT_EQ(ctx.synthetic_gauges[0].tags[0].name_, "k"); + EXPECT_EQ(ctx.synthetic_gauges[0].tags[0].value_, "v"); + + setActiveContext(nullptr); +} + +TEST(StatsFilterInjectMetricsTest, NoActiveContext) { + setActiveContext(nullptr); + std::string wire; + appendU32(wire, 0); + appendU32(wire, 0); + EXPECT_EQ(callFF("stats_filter_inject_metrics", wire), proxy_wasm::WasmResult::InternalFailure); +} + +TEST(StatsFilterInjectMetricsTest, TruncatedCounterBlock) { + StatsFilterContext ctx; + setActiveContext(&ctx); + + std::string wire; + appendU32(wire, 1); + appendStr(wire, "name"); + + EXPECT_EQ(callFF("stats_filter_inject_metrics", wire), proxy_wasm::WasmResult::BadArgument); + setActiveContext(nullptr); +} + +TEST(StatsFilterInjectMetricsTest, TruncatedGaugeBlock) { + StatsFilterContext ctx; + setActiveContext(&ctx); + + std::string wire; + appendU32(wire, 0); + appendU32(wire, 1); + appendStr(wire, "name"); + + EXPECT_EQ(callFF("stats_filter_inject_metrics", wire), proxy_wasm::WasmResult::BadArgument); + setActiveContext(nullptr); +} + +TEST(StatsFilterInjectMetricsTest, EmptyInput) { + StatsFilterContext ctx; + setActiveContext(&ctx); + EXPECT_EQ(callFF("stats_filter_inject_metrics", ""), proxy_wasm::WasmResult::BadArgument); + setActiveContext(nullptr); +} + +// ==================================================================== +// Foreign function: stats_filter_get_metric_tags +// ==================================================================== + +class GetMetricTagsTest : public EnrichedMetricSnapshotTest { +protected: + void SetUp() override { + EnrichedMetricSnapshotTest::SetUp(); + ctx_.snapshot = &snapshot_; + ctx_.counter_buffer_to_snapshot = {0, 1, 2}; + ctx_.gauge_buffer_to_snapshot = {0, 1}; + setActiveContext(&ctx_); + } + void TearDown() override { setActiveContext(nullptr); } + + StatsFilterContext ctx_; +}; + +TEST_F(GetMetricTagsTest, GetCounterTags) { + std::string wire; + appendU32(wire, 1); + appendU32(wire, 0); + + std::string result_buf; + auto alloc = [&result_buf](size_t size) -> void* { + result_buf.resize(size); + return result_buf.data(); + }; + + auto ff = getFF("stats_filter_get_metric_tags"); + ASSERT_TRUE(ff != nullptr); + proxy_wasm::WasmBase wasm_base(nullptr, "", "", "", {}, {}); + auto r = ff(wasm_base, wire, alloc); + EXPECT_EQ(r, proxy_wasm::WasmResult::Ok); + EXPECT_GT(result_buf.size(), sizeof(uint32_t)); + + uint32_t tag_count = 0; + memcpy(&tag_count, result_buf.data(), sizeof(uint32_t)); + EXPECT_EQ(tag_count, 1); +} + +TEST_F(GetMetricTagsTest, GetGaugeTags) { + std::string wire; + appendU32(wire, 2); + appendU32(wire, 0); + + std::string result_buf; + auto alloc = [&result_buf](size_t size) -> void* { + result_buf.resize(size); + return result_buf.data(); + }; + + auto ff = getFF("stats_filter_get_metric_tags"); + proxy_wasm::WasmBase wasm_base(nullptr, "", "", "", {}, {}); + auto r = ff(wasm_base, wire, alloc); + EXPECT_EQ(r, proxy_wasm::WasmResult::Ok); + + uint32_t tag_count = 0; + memcpy(&tag_count, result_buf.data(), sizeof(uint32_t)); + EXPECT_EQ(tag_count, 1); +} + +TEST_F(GetMetricTagsTest, GetHistogramTags) { + std::string wire; + appendU32(wire, 3); + appendU32(wire, 0); + + std::string result_buf; + auto alloc = [&result_buf](size_t size) -> void* { + result_buf.resize(size); + return result_buf.data(); + }; + + auto ff = getFF("stats_filter_get_metric_tags"); + proxy_wasm::WasmBase wasm_base(nullptr, "", "", "", {}, {}); + auto r = ff(wasm_base, wire, alloc); + EXPECT_EQ(r, proxy_wasm::WasmResult::Ok); + + uint32_t tag_count = 0; + memcpy(&tag_count, result_buf.data(), sizeof(uint32_t)); + EXPECT_EQ(tag_count, 1); +} + +TEST_F(GetMetricTagsTest, InvalidType) { + std::string wire; + appendU32(wire, 99); + appendU32(wire, 0); + + EXPECT_EQ(callFF("stats_filter_get_metric_tags", wire), proxy_wasm::WasmResult::BadArgument); +} + +TEST_F(GetMetricTagsTest, CounterIndexOutOfRange) { + std::string wire; + appendU32(wire, 1); + appendU32(wire, 999); + + EXPECT_EQ(callFF("stats_filter_get_metric_tags", wire), proxy_wasm::WasmResult::BadArgument); +} + +TEST_F(GetMetricTagsTest, GaugeIndexOutOfRange) { + std::string wire; + appendU32(wire, 2); + appendU32(wire, 999); + + EXPECT_EQ(callFF("stats_filter_get_metric_tags", wire), proxy_wasm::WasmResult::BadArgument); +} + +TEST_F(GetMetricTagsTest, HistogramIndexOutOfRange) { + std::string wire; + appendU32(wire, 3); + appendU32(wire, 999); + + EXPECT_EQ(callFF("stats_filter_get_metric_tags", wire), proxy_wasm::WasmResult::BadArgument); +} + +TEST_F(GetMetricTagsTest, TooShortArguments) { + std::string wire; + appendU32(wire, 1); + + EXPECT_EQ(callFF("stats_filter_get_metric_tags", wire), proxy_wasm::WasmResult::BadArgument); +} + +TEST(StatsFilterGetMetricTagsTest, NoActiveContext) { + setActiveContext(nullptr); + std::string wire; + appendU32(wire, 1); + appendU32(wire, 0); + EXPECT_EQ(callFF("stats_filter_get_metric_tags", wire), proxy_wasm::WasmResult::InternalFailure); +} + +TEST(StatsFilterGetMetricTagsTest, NullSnapshot) { + StatsFilterContext ctx; + setActiveContext(&ctx); + std::string wire; + appendU32(wire, 1); + appendU32(wire, 0); + EXPECT_EQ(callFF("stats_filter_get_metric_tags", wire), proxy_wasm::WasmResult::InternalFailure); + setActiveContext(nullptr); +} + +// ==================================================================== +// Foreign function: stats_filter_get_all_metric_tags +// ==================================================================== + +class GetAllMetricTagsTest : public EnrichedMetricSnapshotTest { +protected: + void SetUp() override { + EnrichedMetricSnapshotTest::SetUp(); + ctx_.snapshot = &snapshot_; + buildBufferToSnapshotMaps(snapshot_, ctx_); + setActiveContext(&ctx_); + } + void TearDown() override { setActiveContext(nullptr); } + + StatsFilterContext ctx_; +}; + +TEST_F(GetAllMetricTagsTest, ReturnsAllTags) { + std::string result_buf; + auto alloc = [&result_buf](size_t size) -> void* { + result_buf.resize(size); + return result_buf.data(); + }; + + auto ff = getFF("stats_filter_get_all_metric_tags"); + ASSERT_TRUE(ff != nullptr); + proxy_wasm::WasmBase wasm_base(nullptr, "", "", "", {}, {}); + auto r = ff(wasm_base, "", alloc); + EXPECT_EQ(r, proxy_wasm::WasmResult::Ok); + EXPECT_GT(result_buf.size(), 3 * sizeof(uint32_t)); + + size_t offset = 0; + auto readU32 = [&](uint32_t& out) { + memcpy(&out, result_buf.data() + offset, sizeof(uint32_t)); + offset += sizeof(uint32_t); + }; + + uint32_t counter_count = 0; + readU32(counter_count); + EXPECT_EQ(counter_count, 3); + + for (uint32_t i = 0; i < counter_count; ++i) { + uint32_t tc = 0; + readU32(tc); + for (uint32_t t = 0; t < tc; ++t) { + uint32_t nlen = 0; + readU32(nlen); + offset += nlen; + uint32_t vlen = 0; + readU32(vlen); + offset += vlen; + } + } + + uint32_t gauge_count = 0; + readU32(gauge_count); + EXPECT_EQ(gauge_count, 2); +} + +TEST(StatsFilterGetAllMetricTagsTest, NoActiveContext) { + setActiveContext(nullptr); + EXPECT_EQ(callFF("stats_filter_get_all_metric_tags", ""), + proxy_wasm::WasmResult::InternalFailure); +} + +TEST(StatsFilterGetAllMetricTagsTest, NullSnapshot) { + StatsFilterContext ctx; + setActiveContext(&ctx); + EXPECT_EQ(callFF("stats_filter_get_all_metric_tags", ""), + proxy_wasm::WasmResult::InternalFailure); + setActiveContext(nullptr); +} + +// ==================================================================== +// Foreign function: stats_filter_get_histograms +// ==================================================================== + +class GetHistogramsTest : public EnrichedMetricSnapshotTest { +protected: + void SetUp() override { + EnrichedMetricSnapshotTest::SetUp(); + ctx_.snapshot = &snapshot_; + setActiveContext(&ctx_); + } + void TearDown() override { setActiveContext(nullptr); } + + StatsFilterContext ctx_; +}; + +TEST_F(GetHistogramsTest, ReturnsHistogramNames) { + std::string result_buf; + auto alloc = [&result_buf](size_t size) -> void* { + result_buf.resize(size); + return result_buf.data(); + }; + + auto ff = getFF("stats_filter_get_histograms"); + ASSERT_TRUE(ff != nullptr); + proxy_wasm::WasmBase wasm_base(nullptr, "", "", "", {}, {}); + auto r = ff(wasm_base, "", alloc); + EXPECT_EQ(r, proxy_wasm::WasmResult::Ok); + + size_t offset = 0; + uint32_t count = 0; + memcpy(&count, result_buf.data() + offset, sizeof(uint32_t)); + offset += sizeof(uint32_t); + EXPECT_EQ(count, 2); + + uint32_t name_len = 0; + memcpy(&name_len, result_buf.data() + offset, sizeof(uint32_t)); + offset += sizeof(uint32_t); + std::string name1(result_buf.data() + offset, name_len); + offset += name_len; + EXPECT_EQ(name1, "upstream_rq_time"); + + memcpy(&name_len, result_buf.data() + offset, sizeof(uint32_t)); + offset += sizeof(uint32_t); + std::string name2(result_buf.data() + offset, name_len); + EXPECT_EQ(name2, "downstream_rq_time"); +} + +TEST(StatsFilterGetHistogramsTest, NoActiveContext) { + setActiveContext(nullptr); + EXPECT_EQ(callFF("stats_filter_get_histograms", ""), proxy_wasm::WasmResult::InternalFailure); +} + +TEST(StatsFilterGetHistogramsTest, NullSnapshot) { + StatsFilterContext ctx; + setActiveContext(&ctx); + EXPECT_EQ(callFF("stats_filter_get_histograms", ""), proxy_wasm::WasmResult::InternalFailure); + setActiveContext(nullptr); +} + +// ==================================================================== +// Foreign function registration existence +// ==================================================================== + +TEST(StatsFilterForeignFunctionRegistration, AllFunctionsRegistered) { + EXPECT_NE(getFF("stats_filter_emit"), nullptr); + EXPECT_NE(getFF("stats_filter_set_global_tags"), nullptr); + EXPECT_NE(getFF("stats_filter_set_name_overrides"), nullptr); + EXPECT_NE(getFF("stats_filter_inject_metrics"), nullptr); + EXPECT_NE(getFF("stats_filter_get_metric_tags"), nullptr); + EXPECT_NE(getFF("stats_filter_get_all_metric_tags"), nullptr); + EXPECT_NE(getFF("stats_filter_get_histograms"), nullptr); +} + +// ==================================================================== +// buildBufferToSnapshotMaps +// ==================================================================== + +class BuildBufferToSnapshotMapsTest : public EnrichedMetricSnapshotTest {}; + +TEST_F(BuildBufferToSnapshotMapsTest, AllUsedMetrics) { + StatsFilterContext ctx; + buildBufferToSnapshotMaps(snapshot_, ctx); + + ASSERT_EQ(ctx.counter_buffer_to_snapshot.size(), 3); + EXPECT_EQ(ctx.counter_buffer_to_snapshot[0], 0); + EXPECT_EQ(ctx.counter_buffer_to_snapshot[1], 1); + EXPECT_EQ(ctx.counter_buffer_to_snapshot[2], 2); + + ASSERT_EQ(ctx.gauge_buffer_to_snapshot.size(), 2); + EXPECT_EQ(ctx.gauge_buffer_to_snapshot[0], 0); + EXPECT_EQ(ctx.gauge_buffer_to_snapshot[1], 1); +} + +TEST_F(BuildBufferToSnapshotMapsTest, MixedUsedAndUnused) { + counter_b_.used_ = false; + gauge_b_.used_ = false; + + StatsFilterContext ctx; + buildBufferToSnapshotMaps(snapshot_, ctx); + + ASSERT_EQ(ctx.counter_buffer_to_snapshot.size(), 2); + EXPECT_EQ(ctx.counter_buffer_to_snapshot[0], 0); + EXPECT_EQ(ctx.counter_buffer_to_snapshot[1], 2); + + ASSERT_EQ(ctx.gauge_buffer_to_snapshot.size(), 1); + EXPECT_EQ(ctx.gauge_buffer_to_snapshot[0], 0); +} + +TEST_F(BuildBufferToSnapshotMapsTest, NoneUsed) { + counter_a_.used_ = false; + counter_b_.used_ = false; + counter_c_.used_ = false; + gauge_a_.used_ = false; + gauge_b_.used_ = false; + + StatsFilterContext ctx; + buildBufferToSnapshotMaps(snapshot_, ctx); + + EXPECT_TRUE(ctx.counter_buffer_to_snapshot.empty()); + EXPECT_TRUE(ctx.gauge_buffer_to_snapshot.empty()); +} + +// ==================================================================== +// processFilterDecisionsAndFlush +// ==================================================================== + +class ProcessFilterDecisionsTest : public EnrichedMetricSnapshotTest { +protected: + NiceMock inner_sink_; +}; + +TEST_F(ProcessFilterDecisionsTest, NoDecisionsNoEnrichments_Passthrough) { + StatsFilterContext ctx; + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::Ref(snapshot_))); + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, GlobalTagsOnly_KeepAllAndEnrich) { + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 1, 2}; + ctx.gauge_buffer_to_snapshot = {0, 1}; + Stats::TagVector global_tags = {{"dc", "us-east-1"}}; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + EXPECT_EQ(s.counters().size(), 3); + EXPECT_EQ(s.gauges().size(), 2); + auto tags = s.counters()[0].counter_.get().tags(); + EXPECT_EQ(tags.back().name_, "dc"); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, FilterDecisions_KeepSubset) { + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 1, 2}; + ctx.gauge_buffer_to_snapshot = {0, 1}; + ctx.kept_counter_indices = {0}; + ctx.kept_gauge_indices = {1}; + ctx.emit_called = true; + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + EXPECT_EQ(s.counters().size(), 1); + EXPECT_EQ(s.counters()[0].counter_.get().name(), "upstream_rq_2xx"); + EXPECT_EQ(s.gauges().size(), 1); + EXPECT_EQ(s.gauges()[0].get().name(), "connections_active"); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, FilterDecisions_UnusedMetricsPassThrough) { + counter_c_.used_ = false; + gauge_b_.used_ = false; + + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 1}; + ctx.gauge_buffer_to_snapshot = {0}; + ctx.kept_counter_indices = {0}; + ctx.kept_gauge_indices = {0}; + ctx.emit_called = true; + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + EXPECT_EQ(s.counters().size(), 2); + EXPECT_EQ(s.gauges().size(), 2); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, IndexTranslation) { + counter_b_.used_ = false; + + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 2}; + ctx.gauge_buffer_to_snapshot = {0, 1}; + ctx.kept_counter_indices = {1}; + ctx.kept_gauge_indices = {0, 1}; + ctx.emit_called = true; + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + bool found_total = false; + bool found_5xx = false; + for (const auto& c : s.counters()) { + if (c.counter_.get().name() == "upstream_rq_total") { + found_total = true; + } + if (c.counter_.get().name() == "upstream_rq_5xx") { + found_5xx = true; + } + } + EXPECT_TRUE(found_total); + EXPECT_TRUE(found_5xx); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, NameOverrideTranslation) { + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 1, 2}; + ctx.gauge_buffer_to_snapshot = {0, 1}; + ctx.kept_counter_indices = {0, 1, 2}; + ctx.kept_gauge_indices = {0, 1}; + ctx.emit_called = true; + ctx.name_overrides.push_back({1, 0, "envoy.rq_2xx"}); + ctx.name_overrides.push_back({2, 0, "envoy.membership"}); + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + EXPECT_EQ(s.counters()[0].counter_.get().name(), "envoy.rq_2xx"); + EXPECT_EQ(s.gauges()[0].get().name(), "envoy.membership"); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, SyntheticMetrics_NoFilterDecisions) { + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 1, 2}; + ctx.gauge_buffer_to_snapshot = {0, 1}; + ctx.synthetic_counters.push_back({"custom.metric", 100, {}}); + ctx.synthetic_gauges.push_back({"custom.gauge", 50, {}}); + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + EXPECT_EQ(s.counters().size(), 4); + EXPECT_EQ(s.counters().back().counter_.get().name(), "custom.metric"); + EXPECT_EQ(s.gauges().size(), 3); + EXPECT_EQ(s.gauges().back().get().name(), "custom.gauge"); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, HistogramFilterDecisions) { + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 1, 2}; + ctx.gauge_buffer_to_snapshot = {0, 1}; + ctx.kept_counter_indices = {0, 1, 2}; + ctx.kept_gauge_indices = {0, 1}; + ctx.kept_histogram_indices = {0}; + ctx.emit_called = true; + ctx.histogram_block_present = true; + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + EXPECT_EQ(s.histograms().size(), 1); + EXPECT_EQ(s.histograms()[0].get().name(), "upstream_rq_time"); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, OutOfRangeBufferIndicesIgnored) { + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0}; + ctx.gauge_buffer_to_snapshot = {0}; + ctx.kept_counter_indices = {0, 99}; + ctx.kept_gauge_indices = {0, 99}; + ctx.emit_called = true; + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)); + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, NameOverrideOutOfRangeMapping) { + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0}; + ctx.gauge_buffer_to_snapshot = {0}; + ctx.kept_counter_indices = {0}; + ctx.kept_gauge_indices = {0}; + ctx.emit_called = true; + ctx.name_overrides.push_back({1, 99, "wont_apply"}); + ctx.name_overrides.push_back({2, 99, "wont_apply"}); + ctx.name_overrides.push_back({3, 0, "histogram_rename"}); + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)); + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, CombinedFilterEnrichSyntheticOverride) { + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 1, 2}; + ctx.gauge_buffer_to_snapshot = {0, 1}; + ctx.kept_counter_indices = {0, 2}; + ctx.kept_gauge_indices = {0}; + ctx.emit_called = true; + ctx.name_overrides.push_back({1, 0, "envoy.rq_2xx"}); + ctx.synthetic_counters.push_back({"wasm.count", 5, {{"source", "plugin"}}}); + Stats::TagVector global_tags = {{"env", "prod"}}; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + EXPECT_EQ(s.counters().size(), 3); + EXPECT_EQ(s.counters()[0].counter_.get().name(), "envoy.rq_2xx"); + auto tags = s.counters()[0].counter_.get().tags(); + EXPECT_EQ(tags.back().name_, "env"); + + EXPECT_EQ(s.counters().back().counter_.get().name(), "wasm.count"); + auto syn_tags = s.counters().back().counter_.get().tags(); + EXPECT_GE(syn_tags.size(), 2); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, EmitCalledWithEmptySets_DropsAllUsedMetrics) { + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 1, 2}; + ctx.gauge_buffer_to_snapshot = {0, 1}; + ctx.emit_called = true; + ctx.histogram_block_present = true; + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + EXPECT_EQ(s.counters().size(), 0); + EXPECT_EQ(s.gauges().size(), 0); + EXPECT_EQ(s.histograms().size(), 0); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, EmitCalledWithEmptySets_UnusedMetricsStillPassThrough) { + counter_c_.used_ = false; + gauge_b_.used_ = false; + + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 1}; + ctx.gauge_buffer_to_snapshot = {0}; + ctx.emit_called = true; + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + EXPECT_EQ(s.counters().size(), 1); + EXPECT_EQ(s.counters()[0].counter_.get().name(), "upstream_rq_total"); + EXPECT_EQ(s.gauges().size(), 1); + EXPECT_EQ(s.gauges()[0].get().name(), "connections_active"); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +TEST_F(ProcessFilterDecisionsTest, EmitCalledNoHistogramBlock_HistogramsPassThrough) { + StatsFilterContext ctx; + ctx.counter_buffer_to_snapshot = {0, 1, 2}; + ctx.gauge_buffer_to_snapshot = {0, 1}; + ctx.kept_counter_indices = {0}; + ctx.kept_gauge_indices = {0}; + ctx.emit_called = true; + ctx.histogram_block_present = false; + Stats::TagVector global_tags; + + EXPECT_CALL(inner_sink_, flush(testing::_)) + .WillOnce(testing::Invoke([](Stats::MetricSnapshot& s) { + EXPECT_EQ(s.counters().size(), 1); + EXPECT_EQ(s.gauges().size(), 1); + EXPECT_EQ(s.histograms().size(), 2); + })); + + processFilterDecisionsAndFlush(snapshot_, ctx, global_tags, symbol_table_, inner_sink_); +} + +// ==================================================================== +// Foreign function: stats_filter_set_global_tags (null pointer) +// ==================================================================== + +TEST(StatsFilterGlobalTagsTest, NullPointerBadWire) { + setGlobalTags(nullptr); + + std::string wire; + appendU32(wire, 1); + appendStr(wire, "key"); + + EXPECT_EQ(callFF("stats_filter_set_global_tags", wire), proxy_wasm::WasmResult::InternalFailure); +} + +} // namespace +} // namespace WasmFilter +} // namespace StatSinks +} // namespace Extensions +} // namespace Envoy diff --git a/docs/root/api-v3/config/contrib/contrib.rst b/docs/root/api-v3/config/contrib/contrib.rst index 5ce1c24cdb09a..cedd9bc252690 100644 --- a/docs/root/api-v3/config/contrib/contrib.rst +++ b/docs/root/api-v3/config/contrib/contrib.rst @@ -18,6 +18,7 @@ Contrib extensions kae/kae http_tcp_bridge/http_tcp_bridge kafka_stats_sink/kafka_stats_sink + wasm_filter_stats_sink/wasm_filter_stats_sink tap_sinks/tap_sinks load_balancing_policies/peak_ewma/peak_ewma istio/istio diff --git a/docs/root/api-v3/config/contrib/wasm_filter_stats_sink/wasm_filter_stats_sink.rst b/docs/root/api-v3/config/contrib/wasm_filter_stats_sink/wasm_filter_stats_sink.rst new file mode 100644 index 0000000000000..63290dc803670 --- /dev/null +++ b/docs/root/api-v3/config/contrib/wasm_filter_stats_sink/wasm_filter_stats_sink.rst @@ -0,0 +1,8 @@ +Wasm Filter Stats Sink +====================== + +.. toctree:: + :glob: + :maxdepth: 2 + + ../../../extensions/stat_sinks/wasm_filter/v3/* diff --git a/docs/root/configuration/observability/stat_sinks/stat_sinks.rst b/docs/root/configuration/observability/stat_sinks/stat_sinks.rst index 503366cd8268d..3a057b74be3f4 100644 --- a/docs/root/configuration/observability/stat_sinks/stat_sinks.rst +++ b/docs/root/configuration/observability/stat_sinks/stat_sinks.rst @@ -8,3 +8,4 @@ Stat sinks kafka_stat_sink open_telemetry_stat_sink wasm_stat_sink + wasm_filter_stat_sink diff --git a/docs/root/configuration/observability/stat_sinks/wasm_filter_stat_sink.rst b/docs/root/configuration/observability/stat_sinks/wasm_filter_stat_sink.rst new file mode 100644 index 0000000000000..94f3b8682354e --- /dev/null +++ b/docs/root/configuration/observability/stat_sinks/wasm_filter_stat_sink.rst @@ -0,0 +1,224 @@ +.. _config_stat_sinks_wasm_filter: + +Wasm Stats Filter Sink +====================== + +The :ref:`WasmFilterStatsSinkConfig ` +configuration specifies a stats sink middleware that runs a +`WebAssembly `_ (WASM) plugin to filter, transform, +and enrich metrics before delegating to an inner stats sink. + +This is useful when you need custom, programmable logic to decide which metrics are +exported -- for example, dropping high-cardinality metrics by name pattern, injecting +tags from node metadata, renaming metrics, or adding synthetic metrics. It replaces +centralized metric processing services with distributed in-proxy logic. + +* This extension should be configured with the type URL + ``type.googleapis.com/envoy.extensions.stat_sinks.wasm_filter.v3.WasmFilterStatsSinkConfig``. +* :ref:`v3 API reference ` + +.. attention:: + + The Wasm stats filter sink is only included in :ref:`contrib images ` + +.. attention:: + + The Wasm stats filter sink is experimental and is currently under active development. + Capabilities will be expanded over time and the configuration structures are likely to change. + +How it works +------------ + +The ``wasm_filter`` sink wraps any existing stats sink (the *inner sink*) and +interposes a WASM plugin on each flush cycle: + +1. Envoy's flush timer fires and passes the full ``MetricSnapshot`` to the + ``wasm_filter`` sink. +2. The sink serializes all **counters** and **gauges** into a compact binary buffer + and invokes the WASM plugin's ``onStatsUpdate()`` callback. +3. The plugin iterates the metrics and applies custom logic. It may call foreign + functions to: + + * **Get additional data**: histogram names, per-metric tags + * **Set global tags**: applied to all metrics on every flush + * **Rename metrics**: override the name of specific counters/gauges/histograms + * **Inject synthetic metrics**: add new counters/gauges to the snapshot + * **Emit kept indices**: declare which metrics to keep + +4. The host builds an enriched snapshot: filtered metrics wrapped with tag/name + overrides, plus any injected synthetic metrics. +5. The enriched snapshot is flushed to the inner sink. + +**Text readouts** and **host counters/gauges** pass through unfiltered. + +Capabilities +------------ + +.. list-table:: + :header-rows: 1 + :widths: 20 30 50 + + * - Capability + - Foreign Function + - Description + * - **Filter** + - ``stats_filter_emit`` + - Declare which counters, gauges, and histograms to keep. + * - **Global tag injection** + - ``stats_filter_set_global_tags`` + - Set tags (e.g. datacenter, pod) applied to ALL metrics. Called once at + startup from ``onConfigure()``. + * - **Metric renaming** + - ``stats_filter_set_name_overrides`` + - Override the name of specific metrics (e.g. prefix with ``envoy.``). + Called per flush. + * - **Synthetic metrics** + - ``stats_filter_inject_metrics`` + - Inject new counters/gauges with custom names, values, and tags. + Called per flush. + * - **Histogram discovery** + - ``stats_filter_get_histograms`` + - Get the list of histogram names (not in the standard ``onStatsUpdate`` + buffer). + * - **Per-metric tags** + - ``stats_filter_get_metric_tags`` + - Get tags for a single metric by type and index. + * - **Bulk tags** + - ``stats_filter_get_all_metric_tags`` + - Get tags for all metrics in one call. + +Configuration example +--------------------- + +.. code-block:: yaml + + stats_flush_interval: 10s + + stats_sinks: + - name: envoy.stat_sinks.wasm_filter + typed_config: + "@type": type.googleapis.com/envoy.extensions.stat_sinks.wasm_filter.v3.WasmFilterStatsSinkConfig + wasm_config: + name: "my_stats_filter" + root_id: "stats_filter" + vm_config: + runtime: "envoy.wasm.runtime.v8" + code: + local: + filename: "/etc/envoy/stats_filter_plugin.wasm" + configuration: + "@type": type.googleapis.com/google.protobuf.StringValue + value: '{"exclude_prefixes": ["server.compilation", "runtime."], "rename_with_prefix": true}' + inner_sink: + name: envoy.stat_sinks.kafka + typed_config: + "@type": type.googleapis.com/envoy.extensions.stat_sinks.kafka.v3.KafkaStatsSinkConfig + broker_list: "kafka:9092" + topic: "envoy-metrics" + format: PROTOBUF + emit_tags_as_labels: true + +Wire format reference +--------------------- + +``stats_filter_emit`` +^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: text + + [counter_count] [counter_idx_0] ... [counter_idx_N] + [gauge_count] [gauge_idx_0] ... [gauge_idx_M] + [hist_count] [hist_idx_0] ... [hist_idx_K] (optional) + +``stats_filter_set_global_tags`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: text + + [tag_count: uint32] + For each tag: + [name_len: uint32] [name bytes] [value_len: uint32] [value bytes] + +``stats_filter_set_name_overrides`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: text + + [count: uint32] + For each override: + [type: uint32 (1=counter, 2=gauge, 3=histogram)] + [index: uint32] + [new_name_len: uint32] [new_name bytes] + +``stats_filter_inject_metrics`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: text + + [counter_count: uint32] + For each counter: + [name_len: uint32] [name bytes] [value: uint64] + [tag_count: uint32] for each tag: [name_len][name][value_len][value] + [gauge_count: uint32] + For each gauge: + [name_len: uint32] [name bytes] [value: uint64] + [tag_count: uint32] for each tag: [name_len][name][value_len][value] + +``stats_filter_get_histograms`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Input**: empty. **Output**: + +.. code-block:: text + + [count: uint32] for each: [name_len: uint32] [name bytes] + +``stats_filter_get_metric_tags`` (single) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Input**: ``[type: uint32] [index: uint32]``. **Output**: + +.. code-block:: text + + [tag_count: uint32] + For each tag: [name_len: uint32] [name] [value_len: uint32] [value] + +``stats_filter_get_all_metric_tags`` (bulk) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Input**: empty. **Output**: three consecutive blocks (counters, gauges, histograms): + +.. code-block:: text + + [metric_count: uint32] + For each metric: + [tag_count: uint32] + For each tag: [name_len: uint32] [name] [value_len: uint32] [value] + +Reading node metadata +--------------------- + +The plugin can read Envoy's node metadata during ``onConfigure()`` using +``getProperty()`` / ``getValue()`` with paths like: + +* ``["xds", "node", "id"]`` -- node ID (hostname) +* ``["xds", "node", "cluster"]`` -- cluster name +* ``["xds", "node", "metadata", "datacenter"]`` -- custom metadata +* ``["xds", "node", "locality", "region"]`` -- locality region + +These are used to compute global tags via ``stats_filter_set_global_tags``. + +Performance considerations +-------------------------- + +* **Global tags**: set once at startup, zero per-flush overhead. +* **Metric filtering**: single WASM boundary crossing for the serialized buffer. +* **Bulk tag lookup**: ``stats_filter_get_all_metric_tags`` -- one crossing vs N. +* **Name overrides and injection**: one crossing each per flush. + +Current limitations +------------------- + +* **Text readouts are not filterable** -- they pass through to the inner sink. +* **Value transformation is not supported** -- the plugin can rename and tag + metrics but cannot modify counter/gauge values. diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index 2c95e6d0524ba..58f11e0c7084f 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -90,6 +90,7 @@ envoy_cc_extension( ], }), visibility = [ + "//contrib:__subpackages__", "//source/extensions:__subpackages__", "//test/extensions:__subpackages__", "//test/test_common:__subpackages__", diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index b08ef9d93f0a9..536b8f2ae62fc 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -383,6 +383,9 @@ paths: - source/extensions/common/wasm/context.h - source/extensions/common/wasm/context.cc - source/extensions/common/wasm/foreign.cc + - contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.cc + - contrib/stat_sinks/wasm_filter/test/test_data/stats_filter_plugin.cc + - contrib/stat_sinks/wasm_filter/test/wasm_filter_stat_sink_integration_test.cc - source/extensions/common/wasm/wasm.h - source/extensions/common/wasm/wasm.cc - source/extensions/common/wasm/wasm_vm.h diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index b0f9bc0af162f..331ffdce67635 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -379,6 +379,7 @@ OCSP OD ODCDS OKP +middleware middlewildcard monostate mpd @@ -754,6 +755,7 @@ ctor ctrl customizations darwin +datacenter datadog datagram datagrams @@ -832,6 +834,7 @@ encodings endian endianness endl +enricher enqueue enqueued enqueues @@ -1473,6 +1476,7 @@ subzone sudo suf superclass +superpod superroot superset supportability From 90594f45b3ed30bfd4bdf8bb300944b93e40959f Mon Sep 17 00:00:00 2001 From: code Date: Sat, 25 Apr 2026 18:08:46 +0800 Subject: [PATCH 141/750] dym sdk: add new ABI support to the go/cpp SDK and all related tests (#44645) --- .../extensions/dynamic_modules/sdk/cpp/sdk.h | 155 ++++- .../dynamic_modules/sdk/cpp/sdk_internal.cc | 301 +++++++++- .../dynamic_modules/sdk/go/abi/internal.go | 364 +++++++++++- .../dynamic_modules/sdk/go/shared/api.go | 28 + .../dynamic_modules/sdk/go/shared/base.go | 102 ++++ .../sdk/go/shared/mocks/mock_api.go | 14 + .../sdk/go/shared/mocks/mock_base.go | 545 +++++++++++++++++- .../dynamic_modules/http/filter_test.cc | 249 +++++++- .../dynamic_modules/http/integration_test.cc | 6 - .../dynamic_modules/sdk/cpp/sdk_mocks.h | 30 + .../dynamic_modules/test_data/cpp/http.cc | 340 +++++++++++ .../test_data/cpp/http_integration_test.cc | 79 +++ .../dynamic_modules/test_data/go/http/http.go | 265 +++++++++ .../http_integration_test.go | 56 ++ .../dynamic_modules/test_data/rust/http.rs | 273 +++++++-- 15 files changed, 2720 insertions(+), 87 deletions(-) diff --git a/source/extensions/dynamic_modules/sdk/cpp/sdk.h b/source/extensions/dynamic_modules/sdk/cpp/sdk.h index 0f8dbba3a54bf..1546d7d797201 100644 --- a/source/extensions/dynamic_modules/sdk/cpp/sdk.h +++ b/source/extensions/dynamic_modules/sdk/cpp/sdk.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -244,7 +245,8 @@ enum class AttributeID : uint32_t { XdsVirtualHostName, XdsVirtualHostMetadata, XdsUpstreamHostMetadata, - XdsFilterChainName + XdsFilterChainName, + HealthCheck }; enum class LogLevel : uint32_t { Trace, Debug, Info, Warn, Error, Critical, Off }; @@ -327,6 +329,47 @@ class HttpStreamCallback { virtual void onHttpStreamReset(uint64_t stream_id, HttpStreamResetReason reason) = 0; }; +enum class HttpFilterStreamResetReason : uint32_t { + LocalReset, + LocalRefusedStreamReset, +}; + +enum class SocketOptionState : uint32_t { + Prebind, + Bound, + Listening, +}; + +enum class SocketDirection : uint32_t { Upstream, Downstream }; + +struct ClusterHostCounts { + uint64_t total; + uint64_t healthy; + uint64_t degraded; +}; + +class ChildSpan; + +class Span { +public: + virtual ~Span() = default; + + virtual void setTag(std::string_view key, std::string_view value) = 0; + virtual void setOperation(std::string_view operation) = 0; + virtual void log(std::string_view event) = 0; + virtual void setSampled(bool sampled) = 0; + virtual std::optional getBaggage(std::string_view key) = 0; + virtual void setBaggage(std::string_view key, std::string_view value) = 0; + virtual std::optional getTraceID() = 0; + virtual std::optional getSpanID() = 0; + virtual std::unique_ptr spawnChild(std::string_view operation) = 0; +}; + +class ChildSpan : public Span { +public: + virtual void finish() = 0; +}; + class RouteSpecificConfig { public: virtual ~RouteSpecificConfig(); @@ -607,6 +650,99 @@ class HttpFilterHandle { */ virtual void refreshRouteCluster() = 0; + /** + * Returns the current body buffering limit in bytes. + * @return The buffer limit in bytes. + */ + virtual uint64_t getBufferLimit() = 0; + + /** + * Sets the current body buffering limit in bytes. + * @param limit The desired buffer limit in bytes. + */ + virtual void setBufferLimit(uint64_t limit) = 0; + + /** + * Retrieves the serialized typed filter state value of the stream. + * @param key The filter state key. + * @return The typed filter state value if found, otherwise empty. + */ + virtual std::optional getFilterStateTyped(std::string_view key) = 0; + + /** + * Sets a typed filter state value of the stream. + * @param key The filter state key. + * @param value The serialized typed value. + * @return true if the value was stored successfully. + */ + virtual bool setFilterStateTyped(std::string_view key, std::string_view value) = 0; + + /** + * Returns the worker index assigned to the current filter instance. + */ + virtual uint32_t getWorkerIndex() = 0; + + /** + * Sets an integer socket option on the upstream or downstream connection. + */ + virtual bool setSocketOptionInt(int64_t level, int64_t name, SocketOptionState state, + SocketDirection direction, int64_t value) = 0; + + /** + * Sets a bytes socket option on the upstream or downstream connection. + */ + virtual bool setSocketOptionBytes(int64_t level, int64_t name, SocketOptionState state, + SocketDirection direction, std::string_view value) = 0; + + /** + * Retrieves an integer socket option from the upstream or downstream connection. + */ + virtual std::optional getSocketOptionInt(int64_t level, int64_t name, + SocketOptionState state, + SocketDirection direction) = 0; + + /** + * Retrieves a bytes socket option from the upstream or downstream connection. + */ + virtual std::optional getSocketOptionBytes(int64_t level, int64_t name, + SocketOptionState state, + SocketDirection direction) = 0; + + /** + * Retrieves the active tracing span for the current stream. + */ + virtual std::unique_ptr getActiveSpan() = 0; + + /** + * Retrieves the selected upstream cluster name for the current stream. + */ + virtual std::optional getClusterName() = 0; + + /** + * Retrieves host counts for the selected upstream cluster at the given priority. + */ + virtual std::optional getClusterHostCounts(uint32_t priority) = 0; + + /** + * Sets an upstream override host for the selected cluster. + */ + virtual bool setUpstreamOverrideHost(std::string_view host, bool strict) = 0; + + /** + * Resets the current downstream stream with the given reason and details string. + */ + virtual void resetStream(HttpFilterStreamResetReason reason, std::string_view details) = 0; + + /** + * Sends GOAWAY and closes the downstream connection. + */ + virtual void sendGoAwayAndClose(bool graceful) = 0; + + /** + * Recreates the current stream, optionally with replacement headers. + */ + virtual bool recreateStream(std::span headers = {}) = 0; + /** * Returns reference to request headers. * @return Reference to StreamHeaderMap containing request headers. @@ -950,6 +1086,11 @@ enum class TrailersStatus : uint32_t { Stop = 1, }; +enum class LocalReplyStatus : uint32_t { + Continue = 0, + ContinueAndResetStream = 1, +}; + class HttpFilter { public: virtual ~HttpFilter(); @@ -1013,6 +1154,18 @@ class HttpFilter { * any per-stream resources. */ virtual void onDestroy() = 0; + + /** + * Called when a local reply is being sent on the stream. + * @param response_code The HTTP response code for the local reply. + * @param details The response code details string. + * @param reset_imminent Whether the stream will be reset instead of sending the local reply. + * @return LocalReplyStatus indicating how local reply processing should continue. + */ + virtual LocalReplyStatus onLocalReply(uint32_t response_code, std::string_view details, + bool reset_imminent) { + return LocalReplyStatus::Continue; + } }; class HttpFilterFactory { diff --git a/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc b/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc index 5466ae1af1af0..2ddbb993b8f02 100644 --- a/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc +++ b/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc @@ -216,6 +216,174 @@ template class SchedulerImplBase : public Scheduler { using SchedulerImpl = SchedulerImplBase; using ConfigSchedulerImpl = SchedulerImplBase; +std::optional bufferViewToOptionalStringView(const BufferView& value, + bool found) { + if (!found) { + return {}; + } + return std::string_view(value.data() == nullptr ? "" : value.data(), value.size()); +} + +class ChildSpanImpl; + +class SpanImpl : public Span { +public: + SpanImpl(envoy_dynamic_module_type_http_filter_envoy_ptr host_plugin_ptr, + envoy_dynamic_module_type_span_envoy_ptr span_ptr) + : host_plugin_ptr_(host_plugin_ptr), span_ptr_(span_ptr) {} + + void setTag(std::string_view key, std::string_view value) override { + envoy_dynamic_module_callback_http_span_set_tag( + span_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); + } + + void setOperation(std::string_view operation) override { + envoy_dynamic_module_callback_http_span_set_operation( + span_ptr_, envoy_dynamic_module_type_module_buffer{operation.data(), operation.size()}); + } + + void log(std::string_view event) override { + envoy_dynamic_module_callback_http_span_log( + host_plugin_ptr_, span_ptr_, + envoy_dynamic_module_type_module_buffer{event.data(), event.size()}); + } + + void setSampled(bool sampled) override { + envoy_dynamic_module_callback_http_span_set_sampled(span_ptr_, sampled); + } + + std::optional getBaggage(std::string_view key) override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_http_span_get_baggage( + span_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + void setBaggage(std::string_view key, std::string_view value) override { + envoy_dynamic_module_callback_http_span_set_baggage( + span_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); + } + + std::optional getTraceID() override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_http_span_get_trace_id( + span_ptr_, reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + std::optional getSpanID() override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_http_span_get_span_id( + span_ptr_, reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + std::unique_ptr spawnChild(std::string_view operation) override; + +protected: + const envoy_dynamic_module_type_http_filter_envoy_ptr host_plugin_ptr_; + envoy_dynamic_module_type_span_envoy_ptr span_ptr_; +}; + +class ChildSpanImpl : public ChildSpan { +public: + ChildSpanImpl(envoy_dynamic_module_type_http_filter_envoy_ptr host_plugin_ptr, + envoy_dynamic_module_type_child_span_module_ptr child_span_ptr) + : host_plugin_ptr_(host_plugin_ptr), child_span_ptr_(child_span_ptr), + span_ptr_(reinterpret_cast(child_span_ptr)) {} + + ~ChildSpanImpl() override { finish(); } + + void setTag(std::string_view key, std::string_view value) override { + envoy_dynamic_module_callback_http_span_set_tag( + span_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); + } + + void setOperation(std::string_view operation) override { + envoy_dynamic_module_callback_http_span_set_operation( + span_ptr_, envoy_dynamic_module_type_module_buffer{operation.data(), operation.size()}); + } + + void log(std::string_view event) override { + envoy_dynamic_module_callback_http_span_log( + host_plugin_ptr_, span_ptr_, + envoy_dynamic_module_type_module_buffer{event.data(), event.size()}); + } + + void setSampled(bool sampled) override { + envoy_dynamic_module_callback_http_span_set_sampled(span_ptr_, sampled); + } + + std::optional getBaggage(std::string_view key) override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_http_span_get_baggage( + span_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + void setBaggage(std::string_view key, std::string_view value) override { + envoy_dynamic_module_callback_http_span_set_baggage( + span_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); + } + + std::optional getTraceID() override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_http_span_get_trace_id( + span_ptr_, reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + std::optional getSpanID() override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_http_span_get_span_id( + span_ptr_, reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + std::unique_ptr spawnChild(std::string_view operation) override { + if (child_span_ptr_ == nullptr) { + return nullptr; + } + auto* child = envoy_dynamic_module_callback_http_span_spawn_child( + host_plugin_ptr_, span_ptr_, + envoy_dynamic_module_type_module_buffer{operation.data(), operation.size()}); + if (child == nullptr) { + return nullptr; + } + return std::make_unique(host_plugin_ptr_, child); + } + + void finish() override { + if (child_span_ptr_ == nullptr) { + return; + } + envoy_dynamic_module_callback_http_child_span_finish(child_span_ptr_); + child_span_ptr_ = nullptr; + span_ptr_ = nullptr; + } + +private: + const envoy_dynamic_module_type_http_filter_envoy_ptr host_plugin_ptr_; + envoy_dynamic_module_type_child_span_module_ptr child_span_ptr_; + envoy_dynamic_module_type_span_envoy_ptr span_ptr_; +}; + +std::unique_ptr SpanImpl::spawnChild(std::string_view operation) { + auto* child = envoy_dynamic_module_callback_http_span_spawn_child( + host_plugin_ptr_, span_ptr_, + envoy_dynamic_module_type_module_buffer{operation.data(), operation.size()}); + if (child == nullptr) { + return nullptr; + } + return std::make_unique(host_plugin_ptr_, child); +} + // HttpFilterHandle implementation class HttpFilterHandleImpl : public HttpFilterHandle { public: @@ -419,6 +587,20 @@ class HttpFilterHandleImpl : public HttpFilterHandle { envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); } + std::optional getFilterStateTyped(std::string_view key) override { + BufferView value{nullptr, 0}; + const bool ret = envoy_dynamic_module_callback_http_get_filter_state_typed( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, ret); + } + + bool setFilterStateTyped(std::string_view key, std::string_view value) override { + return envoy_dynamic_module_callback_http_set_filter_state_typed( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); + } + std::optional getAttributeString(AttributeID id) override { BufferView value{nullptr, 0}; @@ -456,6 +638,7 @@ class HttpFilterHandleImpl : public HttpFilterHandle { void sendLocalResponse(uint32_t status, std::span headers, std::string_view body, std::string_view detail) override { + local_reply_sent_ = true; envoy_dynamic_module_callback_http_send_response( host_plugin_ptr_, status, const_cast( @@ -507,6 +690,113 @@ class HttpFilterHandleImpl : public HttpFilterHandle { envoy_dynamic_module_callback_http_clear_route_cluster_cache(host_plugin_ptr_); } + uint32_t getWorkerIndex() override { + return envoy_dynamic_module_callback_http_filter_get_worker_index(host_plugin_ptr_); + } + + bool setSocketOptionInt(int64_t level, int64_t name, SocketOptionState state, + SocketDirection direction, int64_t value) override { + return envoy_dynamic_module_callback_http_set_socket_option_int( + host_plugin_ptr_, level, name, + static_cast(state), + static_cast(direction), value); + } + + bool setSocketOptionBytes(int64_t level, int64_t name, SocketOptionState state, + SocketDirection direction, std::string_view value) override { + return envoy_dynamic_module_callback_http_set_socket_option_bytes( + host_plugin_ptr_, level, name, + static_cast(state), + static_cast(direction), + envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); + } + + std::optional getSocketOptionInt(int64_t level, int64_t name, SocketOptionState state, + SocketDirection direction) override { + int64_t value = 0; + const bool found = envoy_dynamic_module_callback_http_get_socket_option_int( + host_plugin_ptr_, level, name, + static_cast(state), + static_cast(direction), &value); + if (!found) { + return {}; + } + return value; + } + + std::optional getSocketOptionBytes(int64_t level, int64_t name, + SocketOptionState state, + SocketDirection direction) override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_http_get_socket_option_bytes( + host_plugin_ptr_, level, name, + static_cast(state), + static_cast(direction), + reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + uint64_t getBufferLimit() override { + return envoy_dynamic_module_callback_http_get_buffer_limit(host_plugin_ptr_); + } + + void setBufferLimit(uint64_t limit) override { + envoy_dynamic_module_callback_http_set_buffer_limit(host_plugin_ptr_, limit); + } + + std::unique_ptr getActiveSpan() override { + auto* span = envoy_dynamic_module_callback_http_get_active_span(host_plugin_ptr_); + if (span == nullptr) { + return nullptr; + } + return std::make_unique(host_plugin_ptr_, span); + } + + std::optional getClusterName() override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_http_get_cluster_name( + host_plugin_ptr_, reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + std::optional getClusterHostCounts(uint32_t priority) override { + size_t total = 0; + size_t healthy = 0; + size_t degraded = 0; + const bool found = envoy_dynamic_module_callback_http_get_cluster_host_count( + host_plugin_ptr_, priority, &total, &healthy, °raded); + if (!found) { + return {}; + } + return ClusterHostCounts{static_cast(total), static_cast(healthy), + static_cast(degraded)}; + } + + bool setUpstreamOverrideHost(std::string_view host, bool strict) override { + return envoy_dynamic_module_callback_http_set_upstream_override_host( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{host.data(), host.size()}, + strict); + } + + void resetStream(HttpFilterStreamResetReason reason, std::string_view details) override { + envoy_dynamic_module_callback_http_filter_reset_stream( + host_plugin_ptr_, + static_cast(reason), + envoy_dynamic_module_type_module_buffer{details.data(), details.size()}); + } + + void sendGoAwayAndClose(bool graceful) override { + envoy_dynamic_module_callback_http_filter_send_go_away_and_close(host_plugin_ptr_, graceful); + } + + bool recreateStream(std::span headers) override { + return envoy_dynamic_module_callback_http_filter_recreate_stream( + host_plugin_ptr_, + const_cast( + reinterpret_cast(headers.data())), + headers.size()); + } + HeaderMap& requestHeaders() override { return request_headers_; } BodyBuffer& bufferedRequestBody() override { return buffered_request_body_; } @@ -1186,7 +1476,16 @@ envoy_dynamic_module_on_http_filter_local_reply( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, envoy_dynamic_module_type_http_filter_module_ptr filter_module_ptr, uint32_t response_code, envoy_dynamic_module_type_envoy_buffer details, bool reset_imminent) { - return envoy_dynamic_module_type_on_http_filter_local_reply_status_Continue; + auto* plugin_handle = unwrapPointer(filter_module_ptr); + if (plugin_handle == nullptr) { + return envoy_dynamic_module_type_on_http_filter_local_reply_status_Continue; + } + plugin_handle->local_reply_sent_ = true; + return static_cast( + plugin_handle->plugin_->onLocalReply( + response_code, + std::string_view(details.ptr == nullptr ? "" : details.ptr, details.length), + reset_imminent)); } void envoy_dynamic_module_on_http_filter_config_http_callout_done( diff --git a/source/extensions/dynamic_modules/sdk/go/abi/internal.go b/source/extensions/dynamic_modules/sdk/go/abi/internal.go index 5d764c37af9cb..076b50b5e3afa 100644 --- a/source/extensions/dynamic_modules/sdk/go/abi/internal.go +++ b/source/extensions/dynamic_modules/sdk/go/abi/internal.go @@ -390,6 +390,153 @@ func (s *dymScheduler) onScheduled(taskID uint64) { } } +type dymSpan struct { + hostPluginPtr C.envoy_dynamic_module_type_http_filter_envoy_ptr + spanPtr C.envoy_dynamic_module_type_span_envoy_ptr +} + +func (s *dymSpan) SetTag(key, value string) { + if s == nil || s.spanPtr == nil { + return + } + C.envoy_dynamic_module_callback_http_span_set_tag( + s.spanPtr, + stringToModuleBuffer(key), + stringToModuleBuffer(value), + ) + runtime.KeepAlive(key) + runtime.KeepAlive(value) +} + +func (s *dymSpan) SetOperation(operation string) { + if s == nil || s.spanPtr == nil { + return + } + C.envoy_dynamic_module_callback_http_span_set_operation( + s.spanPtr, + stringToModuleBuffer(operation), + ) + runtime.KeepAlive(operation) +} + +func (s *dymSpan) Log(event string) { + if s == nil || s.spanPtr == nil { + return + } + C.envoy_dynamic_module_callback_http_span_log( + s.hostPluginPtr, + s.spanPtr, + stringToModuleBuffer(event), + ) + runtime.KeepAlive(event) +} + +func (s *dymSpan) SetSampled(sampled bool) { + if s == nil || s.spanPtr == nil { + return + } + C.envoy_dynamic_module_callback_http_span_set_sampled(s.spanPtr, C.bool(sampled)) +} + +func (s *dymSpan) GetBaggage(key string) (shared.UnsafeEnvoyBuffer, bool) { + if s == nil || s.spanPtr == nil { + return shared.UnsafeEnvoyBuffer{}, false + } + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_http_span_get_baggage( + s.spanPtr, + stringToModuleBuffer(key), + &valueView, + ) + runtime.KeepAlive(key) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (s *dymSpan) SetBaggage(key, value string) { + if s == nil || s.spanPtr == nil { + return + } + C.envoy_dynamic_module_callback_http_span_set_baggage( + s.spanPtr, + stringToModuleBuffer(key), + stringToModuleBuffer(value), + ) + runtime.KeepAlive(key) + runtime.KeepAlive(value) +} + +func (s *dymSpan) GetTraceID() (shared.UnsafeEnvoyBuffer, bool) { + if s == nil || s.spanPtr == nil { + return shared.UnsafeEnvoyBuffer{}, false + } + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_http_span_get_trace_id(s.spanPtr, &valueView) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (s *dymSpan) GetSpanID() (shared.UnsafeEnvoyBuffer, bool) { + if s == nil || s.spanPtr == nil { + return shared.UnsafeEnvoyBuffer{}, false + } + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_http_span_get_span_id(s.spanPtr, &valueView) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (s *dymSpan) SpawnChild(operation string) shared.ChildSpan { + if s == nil || s.spanPtr == nil { + return nil + } + childPtr := C.envoy_dynamic_module_callback_http_span_spawn_child( + s.hostPluginPtr, + s.spanPtr, + stringToModuleBuffer(operation), + ) + runtime.KeepAlive(operation) + if childPtr == nil { + return nil + } + return &dymChildSpan{ + dymSpan: dymSpan{ + hostPluginPtr: s.hostPluginPtr, + spanPtr: C.envoy_dynamic_module_type_span_envoy_ptr(childPtr), + }, + childPtr: childPtr, + } +} + +type dymChildSpan struct { + dymSpan + childPtr C.envoy_dynamic_module_type_child_span_module_ptr +} + +func (s *dymChildSpan) Finish() { + if s == nil || s.childPtr == nil { + return + } + C.envoy_dynamic_module_callback_http_child_span_finish(s.childPtr) + s.childPtr = nil + s.spanPtr = nil +} + type dymHttpFilterHandle struct { hostPluginPtr C.envoy_dynamic_module_type_http_filter_envoy_ptr @@ -765,6 +912,24 @@ func (h *dymHttpFilterHandle) GetAttributeBool( return bool(value), true } +func (h *dymHttpFilterHandle) GetFilterStateTyped(key string) (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + + ret := C.envoy_dynamic_module_callback_http_get_filter_state_typed( + h.hostPluginPtr, + stringToModuleBuffer(key), + &valueView, + ) + runtime.KeepAlive(key) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + func (h *dymHttpFilterHandle) GetFilterState(key string) (shared.UnsafeEnvoyBuffer, bool) { var valueView C.envoy_dynamic_module_type_envoy_buffer @@ -791,6 +956,17 @@ func (h *dymHttpFilterHandle) SetFilterState(key string, value []byte) { runtime.KeepAlive(value) } +func (h *dymHttpFilterHandle) SetFilterStateTyped(key string, value []byte) bool { + ret := C.envoy_dynamic_module_callback_http_set_filter_state_typed( + h.hostPluginPtr, + stringToModuleBuffer(key), + bytesToModuleBuffer(value), + ) + runtime.KeepAlive(key) + runtime.KeepAlive(value) + return bool(ret) +} + func (h *dymHttpFilterHandle) GetData(key string) any { buf, found := h.GetMetadataString(shared.MetadataSourceTypeDynamic, "composer.shared_data", key) @@ -919,6 +1095,178 @@ func (h *dymHttpFilterHandle) RefreshRouteCluster() { C.envoy_dynamic_module_callback_http_clear_route_cluster_cache(h.hostPluginPtr) } +func (h *dymHttpFilterHandle) GetWorkerIndex() uint32 { + return uint32(C.envoy_dynamic_module_callback_http_filter_get_worker_index(h.hostPluginPtr)) +} + +func (h *dymHttpFilterHandle) SetSocketOptionInt( + level, name int64, + state shared.SocketOptionState, + direction shared.SocketDirection, + value int64, +) bool { + ret := C.envoy_dynamic_module_callback_http_set_socket_option_int( + h.hostPluginPtr, + C.int64_t(level), + C.int64_t(name), + C.envoy_dynamic_module_type_socket_option_state(state), + C.envoy_dynamic_module_type_socket_direction(direction), + C.int64_t(value), + ) + return bool(ret) +} + +func (h *dymHttpFilterHandle) SetSocketOptionBytes( + level, name int64, + state shared.SocketOptionState, + direction shared.SocketDirection, + value []byte, +) bool { + ret := C.envoy_dynamic_module_callback_http_set_socket_option_bytes( + h.hostPluginPtr, + C.int64_t(level), + C.int64_t(name), + C.envoy_dynamic_module_type_socket_option_state(state), + C.envoy_dynamic_module_type_socket_direction(direction), + bytesToModuleBuffer(value), + ) + runtime.KeepAlive(value) + return bool(ret) +} + +func (h *dymHttpFilterHandle) GetSocketOptionInt( + level, name int64, + state shared.SocketOptionState, + direction shared.SocketDirection, +) (int64, bool) { + var value C.int64_t + ret := C.envoy_dynamic_module_callback_http_get_socket_option_int( + h.hostPluginPtr, + C.int64_t(level), + C.int64_t(name), + C.envoy_dynamic_module_type_socket_option_state(state), + C.envoy_dynamic_module_type_socket_direction(direction), + &value, + ) + if !bool(ret) { + return 0, false + } + return int64(value), true +} + +func (h *dymHttpFilterHandle) GetSocketOptionBytes( + level, name int64, + state shared.SocketOptionState, + direction shared.SocketDirection, +) (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_http_get_socket_option_bytes( + h.hostPluginPtr, + C.int64_t(level), + C.int64_t(name), + C.envoy_dynamic_module_type_socket_option_state(state), + C.envoy_dynamic_module_type_socket_direction(direction), + &valueView, + ) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (h *dymHttpFilterHandle) GetBufferLimit() uint64 { + return uint64(C.envoy_dynamic_module_callback_http_get_buffer_limit(h.hostPluginPtr)) +} + +func (h *dymHttpFilterHandle) SetBufferLimit(limit uint64) { + C.envoy_dynamic_module_callback_http_set_buffer_limit(h.hostPluginPtr, C.uint64_t(limit)) +} + +func (h *dymHttpFilterHandle) GetActiveSpan() shared.Span { + spanPtr := C.envoy_dynamic_module_callback_http_get_active_span(h.hostPluginPtr) + if spanPtr == nil { + return nil + } + return &dymSpan{ + hostPluginPtr: h.hostPluginPtr, + spanPtr: spanPtr, + } +} + +func (h *dymHttpFilterHandle) GetClusterName() (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_http_get_cluster_name(h.hostPluginPtr, &valueView) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (h *dymHttpFilterHandle) GetClusterHostCounts(priority uint32) (shared.ClusterHostCounts, bool) { + var total C.size_t + var healthy C.size_t + var degraded C.size_t + ret := C.envoy_dynamic_module_callback_http_get_cluster_host_count( + h.hostPluginPtr, + C.uint32_t(priority), + &total, + &healthy, + °raded, + ) + if !bool(ret) { + return shared.ClusterHostCounts{}, false + } + return shared.ClusterHostCounts{ + Total: uint64(total), + Healthy: uint64(healthy), + Degraded: uint64(degraded), + }, true +} + +func (h *dymHttpFilterHandle) SetUpstreamOverrideHost(host string, strict bool) bool { + ret := C.envoy_dynamic_module_callback_http_set_upstream_override_host( + h.hostPluginPtr, + stringToModuleBuffer(host), + C.bool(strict), + ) + runtime.KeepAlive(host) + return bool(ret) +} + +func (h *dymHttpFilterHandle) ResetStream(reason shared.HttpFilterStreamResetReason, details string) { + C.envoy_dynamic_module_callback_http_filter_reset_stream( + h.hostPluginPtr, + C.envoy_dynamic_module_type_http_filter_stream_reset_reason(reason), + stringToModuleBuffer(details), + ) + runtime.KeepAlive(details) +} + +func (h *dymHttpFilterHandle) SendGoAwayAndClose(graceful bool) { + C.envoy_dynamic_module_callback_http_filter_send_go_away_and_close( + h.hostPluginPtr, + C.bool(graceful), + ) +} + +func (h *dymHttpFilterHandle) RecreateStream(headers [][2]string) bool { + headerViews := headersToModuleHttpHeaderSlice(headers) + ret := C.envoy_dynamic_module_callback_http_filter_recreate_stream( + h.hostPluginPtr, + unsafe.SliceData(headerViews), + C.size_t(len(headerViews)), + ) + runtime.KeepAlive(headers) + runtime.KeepAlive(headerViews) + return bool(ret) +} + func (h *dymHttpFilterHandle) RequestHeaders() shared.HeaderMap { return &h.requestHeaderMap } @@ -1873,7 +2221,21 @@ func envoy_dynamic_module_on_http_filter_local_reply( details C.envoy_dynamic_module_type_envoy_buffer, reset_imminent C.bool, ) C.envoy_dynamic_module_type_on_http_filter_local_reply_status { - return C.envoy_dynamic_module_type_on_http_filter_local_reply_status(0) + _ = filter_envoy_ptr + pluginWrapper := pluginManager.unwrap(unsafe.Pointer(filter_module_ptr)) + if pluginWrapper == nil || pluginWrapper.plugin == nil { + return C.envoy_dynamic_module_type_on_http_filter_local_reply_status( + shared.LocalReplyStatusContinue, + ) + } + + return C.envoy_dynamic_module_type_on_http_filter_local_reply_status( + pluginWrapper.plugin.OnLocalReply( + uint32(response_code), + envoyBufferToUnsafeEnvoyBuffer(details), + bool(reset_imminent), + ), + ) } //export envoy_dynamic_module_on_http_filter_config_http_callout_done diff --git a/source/extensions/dynamic_modules/sdk/go/shared/api.go b/source/extensions/dynamic_modules/sdk/go/shared/api.go index 7ef233a9482b2..209d6ab702f16 100644 --- a/source/extensions/dynamic_modules/sdk/go/shared/api.go +++ b/source/extensions/dynamic_modules/sdk/go/shared/api.go @@ -65,6 +65,18 @@ const ( TrailersStatusDefault TrailersStatus = TrailersStatusContinue ) +type LocalReplyStatus int32 + +const ( + // LocalReplyStatusContinue indicates that the local reply should continue to be sent after all + // filters are informed. + LocalReplyStatusContinue LocalReplyStatus = 0 + // LocalReplyStatusContinueAndResetStream indicates that the local reply notification should + // continue to all filters, but the stream should be reset instead of sending the local reply. + LocalReplyStatusContinueAndResetStream LocalReplyStatus = 1 + LocalReplyStatusDefault LocalReplyStatus = LocalReplyStatusContinue +) + // HttpFilter is the interface to implement your own plugin logic. This is a simplified version and could // not implement flexible stream control. But it should be enough for most of the use cases. type HttpFilter interface { @@ -113,6 +125,18 @@ type HttpFilter interface { // after OnStreamComplete and access logs are flushed. This is a good place to release // any per-stream resources. OnDestroy() + + // OnLocalReply is called when a local reply is being sent. This allows the filter to modify + // the local reply or decide to reset the stream instead. This is called before the local reply + // is sent to the client and before the stream is reset. + // @Param responseCode the response code of the local reply. + // @Param details the details of the local reply. This is usually used to indicate the reason + // for sending the local reply, for example, "buffer overflow" or "rate limit exceeded". + // @Param resetImminent whether the stream is going to be reset after this local reply. This allows + // the filter to decide whether to continue with sending the local reply or just reset the stream. + // @Return LocalReplyStatus the status to control whether to continue with sending the local reply + // or reset the stream. + OnLocalReply(responseCode uint32, details UnsafeEnvoyBuffer, resetImminent bool) LocalReplyStatus } type EmptyHttpFilter struct { @@ -148,6 +172,10 @@ func (p *EmptyHttpFilter) OnStreamComplete() { func (p *EmptyHttpFilter) OnDestroy() { } +func (p *EmptyHttpFilter) OnLocalReply(responseCode uint32, details UnsafeEnvoyBuffer, resetImminent bool) LocalReplyStatus { + return LocalReplyStatusDefault +} + // HttpFilterFactory is the factory interface for creating stream plugins. // This is used to create instances of the stream plugin at runtime when a new request is received. // The implementation of this interface should be thread-safe and hold the parsed configuration. diff --git a/source/extensions/dynamic_modules/sdk/go/shared/base.go b/source/extensions/dynamic_modules/sdk/go/shared/base.go index 235a8b7c70ca6..807601c425433 100644 --- a/source/extensions/dynamic_modules/sdk/go/shared/base.go +++ b/source/extensions/dynamic_modules/sdk/go/shared/base.go @@ -240,6 +240,8 @@ const ( AttributeIDXdsUpstreamHostMetadata // xds.filter_chain_name AttributeIDXdsFilterChainName + // health_check + AttributeIDHealthCheck ) type LogLevel uint32 @@ -324,6 +326,51 @@ type DownstreamWatermarkCallbacks interface { OnBelowWriteBufferLowWatermark() } +type HttpFilterStreamResetReason uint32 + +const ( + HttpFilterStreamResetReasonLocalReset HttpFilterStreamResetReason = iota + HttpFilterStreamResetReasonLocalRefusedStreamReset +) + +type SocketOptionState uint32 + +const ( + SocketOptionStatePrebind SocketOptionState = iota + SocketOptionStateBound + SocketOptionStateListening +) + +type SocketDirection uint32 + +const ( + SocketDirectionUpstream SocketDirection = iota + SocketDirectionDownstream +) + +type ClusterHostCounts struct { + Total uint64 + Healthy uint64 + Degraded uint64 +} + +type Span interface { + SetTag(key, value string) + SetOperation(operation string) + Log(event string) + SetSampled(sampled bool) + GetBaggage(key string) (UnsafeEnvoyBuffer, bool) + SetBaggage(key, value string) + GetTraceID() (UnsafeEnvoyBuffer, bool) + GetSpanID() (UnsafeEnvoyBuffer, bool) + SpawnChild(operation string) ChildSpan +} + +type ChildSpan interface { + Span + Finish() +} + // HttpFilterHandle is the interface that provides access to the plugin's context and configuration. // This should be implemented by the SDK or runtime. type HttpFilterHandle interface { @@ -421,6 +468,13 @@ type HttpFilterHandle interface { // @Param value the filter state value. SetFilterState(key string, value []byte) + // SetFilterStateTyped sets a typed filter state object from serialized bytes using the registered + // Envoy ObjectFactory for the given key. + // @Param key the filter state key. + // @Param value the serialized bytes used to construct the typed object. + // @Return true if the value was stored successfully, otherwise false. + SetFilterStateTyped(key string, value []byte) bool + // GetAttributeString retrieves the string attribute value of the stream. // @Param attributeID the attribute ID. // @Return the attribute value if found, otherwise an empty UnsafeEnvoyBuffer. @@ -438,6 +492,11 @@ type HttpFilterHandle interface { // @Return the attribute value and true if found, otherwise false. GetAttributeBool(attributeID AttributeID) (bool, bool) + // GetFilterStateTyped retrieves the serialized value of a typed filter state object. + // @Param key the filter state key. + // @Return the serialized value if found, otherwise an empty UnsafeEnvoyBuffer. + GetFilterStateTyped(key string) (UnsafeEnvoyBuffer, bool) + // GetData retrieves internal data stored for cross-phase communication. // This data is not included in DynamicMetadata responses. // @Param key the data key. @@ -501,6 +560,49 @@ type HttpFilterHandle interface { // cluster selection but not the route itself. RefreshRouteCluster() + // GetWorkerIndex returns the worker index assigned to the current filter instance. + GetWorkerIndex() uint32 + + // SetSocketOptionInt sets an integer socket option on the upstream or downstream connection. + SetSocketOptionInt(level, name int64, state SocketOptionState, direction SocketDirection, value int64) bool + + // SetSocketOptionBytes sets a bytes socket option on the upstream or downstream connection. + SetSocketOptionBytes(level, name int64, state SocketOptionState, direction SocketDirection, value []byte) bool + + // GetSocketOptionInt retrieves an integer socket option from the upstream or downstream connection. + GetSocketOptionInt(level, name int64, state SocketOptionState, direction SocketDirection) (int64, bool) + + // GetSocketOptionBytes retrieves a bytes socket option from the upstream or downstream connection. + GetSocketOptionBytes(level, name int64, state SocketOptionState, direction SocketDirection) (UnsafeEnvoyBuffer, bool) + + // GetBufferLimit returns the current body buffering limit in bytes. + GetBufferLimit() uint64 + + // SetBufferLimit sets the current body buffering limit in bytes. + SetBufferLimit(limit uint64) + + // GetActiveSpan retrieves the active tracing span for the stream. + GetActiveSpan() Span + + // GetClusterName retrieves the selected upstream cluster name for the stream. + GetClusterName() (UnsafeEnvoyBuffer, bool) + + // GetClusterHostCounts retrieves the total, healthy, and degraded host counts for the selected + // cluster at the given priority. + GetClusterHostCounts(priority uint32) (ClusterHostCounts, bool) + + // SetUpstreamOverrideHost sets an override host for the selected upstream cluster. + SetUpstreamOverrideHost(host string, strict bool) bool + + // ResetStream resets the downstream HTTP stream with the given reason and details string. + ResetStream(reason HttpFilterStreamResetReason, details string) + + // SendGoAwayAndClose sends a GOAWAY frame to the downstream and closes the connection. + SendGoAwayAndClose(graceful bool) + + // RecreateStream recreates the HTTP stream, optionally with replacement headers. + RecreateStream(headers [][2]string) bool + // RequestHeaders retrieves the request headers. // @Return the request headers. RequestHeaders() HeaderMap diff --git a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_api.go b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_api.go index c29a2a6bda6e8..34dae5915c643 100644 --- a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_api.go +++ b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_api.go @@ -52,6 +52,20 @@ func (mr *MockHttpFilterMockRecorder) OnDestroy() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnDestroy", reflect.TypeOf((*MockHttpFilter)(nil).OnDestroy)) } +// OnLocalReply mocks base method. +func (m *MockHttpFilter) OnLocalReply(responseCode uint32, details shared.UnsafeEnvoyBuffer, resetImminent bool) shared.LocalReplyStatus { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnLocalReply", responseCode, details, resetImminent) + ret0, _ := ret[0].(shared.LocalReplyStatus) + return ret0 +} + +// OnLocalReply indicates an expected call of OnLocalReply. +func (mr *MockHttpFilterMockRecorder) OnLocalReply(responseCode, details, resetImminent any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnLocalReply", reflect.TypeOf((*MockHttpFilter)(nil).OnLocalReply), responseCode, details, resetImminent) +} + // OnRequestBody mocks base method. func (m *MockHttpFilter) OnRequestBody(body shared.BodyBuffer, endOfStream bool) shared.BodyStatus { m.ctrl.T.Helper() diff --git a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_base.go b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_base.go index e289abc7c51fd..0793e6d3fcdfc 100644 --- a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_base.go +++ b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_base.go @@ -398,6 +398,304 @@ func (mr *MockDownstreamWatermarkCallbacksMockRecorder) OnBelowWriteBufferLowWat return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnBelowWriteBufferLowWatermark", reflect.TypeOf((*MockDownstreamWatermarkCallbacks)(nil).OnBelowWriteBufferLowWatermark)) } +// MockSpan is a mock of Span interface. +type MockSpan struct { + ctrl *gomock.Controller + recorder *MockSpanMockRecorder + isgomock struct{} +} + +// MockSpanMockRecorder is the mock recorder for MockSpan. +type MockSpanMockRecorder struct { + mock *MockSpan +} + +// NewMockSpan creates a new mock instance. +func NewMockSpan(ctrl *gomock.Controller) *MockSpan { + mock := &MockSpan{ctrl: ctrl} + mock.recorder = &MockSpanMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSpan) EXPECT() *MockSpanMockRecorder { + return m.recorder +} + +// GetBaggage mocks base method. +func (m *MockSpan) GetBaggage(key string) (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBaggage", key) + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetBaggage indicates an expected call of GetBaggage. +func (mr *MockSpanMockRecorder) GetBaggage(key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBaggage", reflect.TypeOf((*MockSpan)(nil).GetBaggage), key) +} + +// GetSpanID mocks base method. +func (m *MockSpan) GetSpanID() (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSpanID") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetSpanID indicates an expected call of GetSpanID. +func (mr *MockSpanMockRecorder) GetSpanID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSpanID", reflect.TypeOf((*MockSpan)(nil).GetSpanID)) +} + +// GetTraceID mocks base method. +func (m *MockSpan) GetTraceID() (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTraceID") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetTraceID indicates an expected call of GetTraceID. +func (mr *MockSpanMockRecorder) GetTraceID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTraceID", reflect.TypeOf((*MockSpan)(nil).GetTraceID)) +} + +// Log mocks base method. +func (m *MockSpan) Log(event string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Log", event) +} + +// Log indicates an expected call of Log. +func (mr *MockSpanMockRecorder) Log(event any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log", reflect.TypeOf((*MockSpan)(nil).Log), event) +} + +// SetBaggage mocks base method. +func (m *MockSpan) SetBaggage(key, value string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetBaggage", key, value) +} + +// SetBaggage indicates an expected call of SetBaggage. +func (mr *MockSpanMockRecorder) SetBaggage(key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBaggage", reflect.TypeOf((*MockSpan)(nil).SetBaggage), key, value) +} + +// SetOperation mocks base method. +func (m *MockSpan) SetOperation(operation string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetOperation", operation) +} + +// SetOperation indicates an expected call of SetOperation. +func (mr *MockSpanMockRecorder) SetOperation(operation any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetOperation", reflect.TypeOf((*MockSpan)(nil).SetOperation), operation) +} + +// SetSampled mocks base method. +func (m *MockSpan) SetSampled(sampled bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSampled", sampled) +} + +// SetSampled indicates an expected call of SetSampled. +func (mr *MockSpanMockRecorder) SetSampled(sampled any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSampled", reflect.TypeOf((*MockSpan)(nil).SetSampled), sampled) +} + +// SetTag mocks base method. +func (m *MockSpan) SetTag(key, value string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetTag", key, value) +} + +// SetTag indicates an expected call of SetTag. +func (mr *MockSpanMockRecorder) SetTag(key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTag", reflect.TypeOf((*MockSpan)(nil).SetTag), key, value) +} + +// SpawnChild mocks base method. +func (m *MockSpan) SpawnChild(operation string) shared.ChildSpan { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SpawnChild", operation) + ret0, _ := ret[0].(shared.ChildSpan) + return ret0 +} + +// SpawnChild indicates an expected call of SpawnChild. +func (mr *MockSpanMockRecorder) SpawnChild(operation any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpawnChild", reflect.TypeOf((*MockSpan)(nil).SpawnChild), operation) +} + +// MockChildSpan is a mock of ChildSpan interface. +type MockChildSpan struct { + ctrl *gomock.Controller + recorder *MockChildSpanMockRecorder + isgomock struct{} +} + +// MockChildSpanMockRecorder is the mock recorder for MockChildSpan. +type MockChildSpanMockRecorder struct { + mock *MockChildSpan +} + +// NewMockChildSpan creates a new mock instance. +func NewMockChildSpan(ctrl *gomock.Controller) *MockChildSpan { + mock := &MockChildSpan{ctrl: ctrl} + mock.recorder = &MockChildSpanMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockChildSpan) EXPECT() *MockChildSpanMockRecorder { + return m.recorder +} + +// Finish mocks base method. +func (m *MockChildSpan) Finish() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Finish") +} + +// Finish indicates an expected call of Finish. +func (mr *MockChildSpanMockRecorder) Finish() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Finish", reflect.TypeOf((*MockChildSpan)(nil).Finish)) +} + +// GetBaggage mocks base method. +func (m *MockChildSpan) GetBaggage(key string) (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBaggage", key) + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetBaggage indicates an expected call of GetBaggage. +func (mr *MockChildSpanMockRecorder) GetBaggage(key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBaggage", reflect.TypeOf((*MockChildSpan)(nil).GetBaggage), key) +} + +// GetSpanID mocks base method. +func (m *MockChildSpan) GetSpanID() (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSpanID") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetSpanID indicates an expected call of GetSpanID. +func (mr *MockChildSpanMockRecorder) GetSpanID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSpanID", reflect.TypeOf((*MockChildSpan)(nil).GetSpanID)) +} + +// GetTraceID mocks base method. +func (m *MockChildSpan) GetTraceID() (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTraceID") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetTraceID indicates an expected call of GetTraceID. +func (mr *MockChildSpanMockRecorder) GetTraceID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTraceID", reflect.TypeOf((*MockChildSpan)(nil).GetTraceID)) +} + +// Log mocks base method. +func (m *MockChildSpan) Log(event string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Log", event) +} + +// Log indicates an expected call of Log. +func (mr *MockChildSpanMockRecorder) Log(event any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log", reflect.TypeOf((*MockChildSpan)(nil).Log), event) +} + +// SetBaggage mocks base method. +func (m *MockChildSpan) SetBaggage(key, value string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetBaggage", key, value) +} + +// SetBaggage indicates an expected call of SetBaggage. +func (mr *MockChildSpanMockRecorder) SetBaggage(key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBaggage", reflect.TypeOf((*MockChildSpan)(nil).SetBaggage), key, value) +} + +// SetOperation mocks base method. +func (m *MockChildSpan) SetOperation(operation string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetOperation", operation) +} + +// SetOperation indicates an expected call of SetOperation. +func (mr *MockChildSpanMockRecorder) SetOperation(operation any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetOperation", reflect.TypeOf((*MockChildSpan)(nil).SetOperation), operation) +} + +// SetSampled mocks base method. +func (m *MockChildSpan) SetSampled(sampled bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSampled", sampled) +} + +// SetSampled indicates an expected call of SetSampled. +func (mr *MockChildSpanMockRecorder) SetSampled(sampled any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSampled", reflect.TypeOf((*MockChildSpan)(nil).SetSampled), sampled) +} + +// SetTag mocks base method. +func (m *MockChildSpan) SetTag(key, value string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetTag", key, value) +} + +// SetTag indicates an expected call of SetTag. +func (mr *MockChildSpanMockRecorder) SetTag(key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTag", reflect.TypeOf((*MockChildSpan)(nil).SetTag), key, value) +} + +// SpawnChild mocks base method. +func (m *MockChildSpan) SpawnChild(operation string) shared.ChildSpan { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SpawnChild", operation) + ret0, _ := ret[0].(shared.ChildSpan) + return ret0 +} + +// SpawnChild indicates an expected call of SpawnChild. +func (mr *MockChildSpanMockRecorder) SpawnChild(operation any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpawnChild", reflect.TypeOf((*MockChildSpan)(nil).SpawnChild), operation) +} + // MockHttpFilterHandle is a mock of HttpFilterHandle interface. type MockHttpFilterHandle struct { ctrl *gomock.Controller @@ -528,18 +826,6 @@ func (mr *MockHttpFilterHandleMockRecorder) ClearRouteCache() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearRouteCache", reflect.TypeOf((*MockHttpFilterHandle)(nil).ClearRouteCache)) } -// RefreshRouteCluster mocks base method. -func (m *MockHttpFilterHandle) RefreshRouteCluster() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "RefreshRouteCluster") -} - -// RefreshRouteCluster indicates an expected call of RefreshRouteCluster. -func (mr *MockHttpFilterHandleMockRecorder) RefreshRouteCluster() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshRouteCluster", reflect.TypeOf((*MockHttpFilterHandle)(nil).RefreshRouteCluster)) -} - // ContinueRequest mocks base method. func (m *MockHttpFilterHandle) ContinueRequest() { m.ctrl.T.Helper() @@ -583,6 +869,20 @@ func (mr *MockHttpFilterHandleMockRecorder) DecrementGaugeValue(id, value any, t return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecrementGaugeValue", reflect.TypeOf((*MockHttpFilterHandle)(nil).DecrementGaugeValue), varargs...) } +// GetActiveSpan mocks base method. +func (m *MockHttpFilterHandle) GetActiveSpan() shared.Span { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetActiveSpan") + ret0, _ := ret[0].(shared.Span) + return ret0 +} + +// GetActiveSpan indicates an expected call of GetActiveSpan. +func (mr *MockHttpFilterHandleMockRecorder) GetActiveSpan() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveSpan", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetActiveSpan)) +} + // GetAttributeBool mocks base method. func (m *MockHttpFilterHandle) GetAttributeBool(attributeID shared.AttributeID) (bool, bool) { m.ctrl.T.Helper() @@ -628,6 +928,50 @@ func (mr *MockHttpFilterHandleMockRecorder) GetAttributeString(attributeID any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttributeString", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetAttributeString), attributeID) } +// GetBufferLimit mocks base method. +func (m *MockHttpFilterHandle) GetBufferLimit() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBufferLimit") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetBufferLimit indicates an expected call of GetBufferLimit. +func (mr *MockHttpFilterHandleMockRecorder) GetBufferLimit() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBufferLimit", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetBufferLimit)) +} + +// GetClusterHostCounts mocks base method. +func (m *MockHttpFilterHandle) GetClusterHostCounts(priority uint32) (shared.ClusterHostCounts, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterHostCounts", priority) + ret0, _ := ret[0].(shared.ClusterHostCounts) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetClusterHostCounts indicates an expected call of GetClusterHostCounts. +func (mr *MockHttpFilterHandleMockRecorder) GetClusterHostCounts(priority any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterHostCounts", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetClusterHostCounts), priority) +} + +// GetClusterName mocks base method. +func (m *MockHttpFilterHandle) GetClusterName() (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterName") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetClusterName indicates an expected call of GetClusterName. +func (mr *MockHttpFilterHandleMockRecorder) GetClusterName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterName", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetClusterName)) +} + // GetData mocks base method. func (m *MockHttpFilterHandle) GetData(key string) any { m.ctrl.T.Helper() @@ -657,6 +1001,21 @@ func (mr *MockHttpFilterHandleMockRecorder) GetFilterState(key any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilterState", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetFilterState), key) } +// GetFilterStateTyped mocks base method. +func (m *MockHttpFilterHandle) GetFilterStateTyped(key string) (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFilterStateTyped", key) + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetFilterStateTyped indicates an expected call of GetFilterStateTyped. +func (mr *MockHttpFilterHandleMockRecorder) GetFilterStateTyped(key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilterStateTyped", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetFilterStateTyped), key) +} + // GetMetadataBool mocks base method. func (m *MockHttpFilterHandle) GetMetadataBool(source shared.MetadataSourceType, metadataNamespace, key string) (bool, bool) { m.ctrl.T.Helper() @@ -818,6 +1177,50 @@ func (mr *MockHttpFilterHandleMockRecorder) GetScheduler() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetScheduler", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetScheduler)) } +// GetSocketOptionBytes mocks base method. +func (m *MockHttpFilterHandle) GetSocketOptionBytes(level, name int64, state shared.SocketOptionState, direction shared.SocketDirection) (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSocketOptionBytes", level, name, state, direction) + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetSocketOptionBytes indicates an expected call of GetSocketOptionBytes. +func (mr *MockHttpFilterHandleMockRecorder) GetSocketOptionBytes(level, name, state, direction any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSocketOptionBytes", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetSocketOptionBytes), level, name, state, direction) +} + +// GetSocketOptionInt mocks base method. +func (m *MockHttpFilterHandle) GetSocketOptionInt(level, name int64, state shared.SocketOptionState, direction shared.SocketDirection) (int64, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSocketOptionInt", level, name, state, direction) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetSocketOptionInt indicates an expected call of GetSocketOptionInt. +func (mr *MockHttpFilterHandleMockRecorder) GetSocketOptionInt(level, name, state, direction any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSocketOptionInt", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetSocketOptionInt), level, name, state, direction) +} + +// GetWorkerIndex mocks base method. +func (m *MockHttpFilterHandle) GetWorkerIndex() uint32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetWorkerIndex") + ret0, _ := ret[0].(uint32) + return ret0 +} + +// GetWorkerIndex indicates an expected call of GetWorkerIndex. +func (mr *MockHttpFilterHandleMockRecorder) GetWorkerIndex() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkerIndex", reflect.TypeOf((*MockHttpFilterHandle)(nil).GetWorkerIndex)) +} + // HttpCallout mocks base method. func (m *MockHttpFilterHandle) HttpCallout(cluster string, headers [][2]string, body []byte, timeoutMs uint64, cb shared.HttpCalloutCallback) (shared.HttpCalloutInitResult, uint64) { m.ctrl.T.Helper() @@ -963,6 +1366,32 @@ func (mr *MockHttpFilterHandleMockRecorder) RecordHistogramValue(id, value any, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordHistogramValue", reflect.TypeOf((*MockHttpFilterHandle)(nil).RecordHistogramValue), varargs...) } +// RecreateStream mocks base method. +func (m *MockHttpFilterHandle) RecreateStream(headers [][2]string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RecreateStream", headers) + ret0, _ := ret[0].(bool) + return ret0 +} + +// RecreateStream indicates an expected call of RecreateStream. +func (mr *MockHttpFilterHandleMockRecorder) RecreateStream(headers any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecreateStream", reflect.TypeOf((*MockHttpFilterHandle)(nil).RecreateStream), headers) +} + +// RefreshRouteCluster mocks base method. +func (m *MockHttpFilterHandle) RefreshRouteCluster() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RefreshRouteCluster") +} + +// RefreshRouteCluster indicates an expected call of RefreshRouteCluster. +func (mr *MockHttpFilterHandleMockRecorder) RefreshRouteCluster() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshRouteCluster", reflect.TypeOf((*MockHttpFilterHandle)(nil).RefreshRouteCluster)) +} + // RequestHeaders mocks base method. func (m *MockHttpFilterHandle) RequestHeaders() shared.HeaderMap { m.ctrl.T.Helper() @@ -1003,6 +1432,18 @@ func (mr *MockHttpFilterHandleMockRecorder) ResetHttpStream(streamID any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetHttpStream", reflect.TypeOf((*MockHttpFilterHandle)(nil).ResetHttpStream), streamID) } +// ResetStream mocks base method. +func (m *MockHttpFilterHandle) ResetStream(reason shared.HttpFilterStreamResetReason, details string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ResetStream", reason, details) +} + +// ResetStream indicates an expected call of ResetStream. +func (mr *MockHttpFilterHandleMockRecorder) ResetStream(reason, details any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetStream", reflect.TypeOf((*MockHttpFilterHandle)(nil).ResetStream), reason, details) +} + // ResponseHeaders mocks base method. func (m *MockHttpFilterHandle) ResponseHeaders() shared.HeaderMap { m.ctrl.T.Helper() @@ -1031,6 +1472,18 @@ func (mr *MockHttpFilterHandleMockRecorder) ResponseTrailers() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResponseTrailers", reflect.TypeOf((*MockHttpFilterHandle)(nil).ResponseTrailers)) } +// SendGoAwayAndClose mocks base method. +func (m *MockHttpFilterHandle) SendGoAwayAndClose(graceful bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SendGoAwayAndClose", graceful) +} + +// SendGoAwayAndClose indicates an expected call of SendGoAwayAndClose. +func (mr *MockHttpFilterHandleMockRecorder) SendGoAwayAndClose(graceful any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGoAwayAndClose", reflect.TypeOf((*MockHttpFilterHandle)(nil).SendGoAwayAndClose), graceful) +} + // SendHttpStreamData mocks base method. func (m *MockHttpFilterHandle) SendHttpStreamData(streamID uint64, body []byte, endOfStream bool) bool { m.ctrl.T.Helper() @@ -1107,6 +1560,18 @@ func (mr *MockHttpFilterHandleMockRecorder) SendResponseTrailers(trailers any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendResponseTrailers", reflect.TypeOf((*MockHttpFilterHandle)(nil).SendResponseTrailers), trailers) } +// SetBufferLimit mocks base method. +func (m *MockHttpFilterHandle) SetBufferLimit(limit uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetBufferLimit", limit) +} + +// SetBufferLimit indicates an expected call of SetBufferLimit. +func (mr *MockHttpFilterHandleMockRecorder) SetBufferLimit(limit any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBufferLimit", reflect.TypeOf((*MockHttpFilterHandle)(nil).SetBufferLimit), limit) +} + // SetData mocks base method. func (m *MockHttpFilterHandle) SetData(key string, value any) { m.ctrl.T.Helper() @@ -1143,6 +1608,20 @@ func (mr *MockHttpFilterHandleMockRecorder) SetFilterState(key, value any) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFilterState", reflect.TypeOf((*MockHttpFilterHandle)(nil).SetFilterState), key, value) } +// SetFilterStateTyped mocks base method. +func (m *MockHttpFilterHandle) SetFilterStateTyped(key string, value []byte) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetFilterStateTyped", key, value) + ret0, _ := ret[0].(bool) + return ret0 +} + +// SetFilterStateTyped indicates an expected call of SetFilterStateTyped. +func (mr *MockHttpFilterHandleMockRecorder) SetFilterStateTyped(key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFilterStateTyped", reflect.TypeOf((*MockHttpFilterHandle)(nil).SetFilterStateTyped), key, value) +} + // SetGaugeValue mocks base method. func (m *MockHttpFilterHandle) SetGaugeValue(id shared.MetricID, value uint64, tagsValues ...string) shared.MetricsResult { m.ctrl.T.Helper() @@ -1174,6 +1653,48 @@ func (mr *MockHttpFilterHandleMockRecorder) SetMetadata(metadataNamespace, key, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMetadata", reflect.TypeOf((*MockHttpFilterHandle)(nil).SetMetadata), metadataNamespace, key, value) } +// SetSocketOptionBytes mocks base method. +func (m *MockHttpFilterHandle) SetSocketOptionBytes(level, name int64, state shared.SocketOptionState, direction shared.SocketDirection, value []byte) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetSocketOptionBytes", level, name, state, direction, value) + ret0, _ := ret[0].(bool) + return ret0 +} + +// SetSocketOptionBytes indicates an expected call of SetSocketOptionBytes. +func (mr *MockHttpFilterHandleMockRecorder) SetSocketOptionBytes(level, name, state, direction, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSocketOptionBytes", reflect.TypeOf((*MockHttpFilterHandle)(nil).SetSocketOptionBytes), level, name, state, direction, value) +} + +// SetSocketOptionInt mocks base method. +func (m *MockHttpFilterHandle) SetSocketOptionInt(level, name int64, state shared.SocketOptionState, direction shared.SocketDirection, value int64) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetSocketOptionInt", level, name, state, direction, value) + ret0, _ := ret[0].(bool) + return ret0 +} + +// SetSocketOptionInt indicates an expected call of SetSocketOptionInt. +func (mr *MockHttpFilterHandleMockRecorder) SetSocketOptionInt(level, name, state, direction, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSocketOptionInt", reflect.TypeOf((*MockHttpFilterHandle)(nil).SetSocketOptionInt), level, name, state, direction, value) +} + +// SetUpstreamOverrideHost mocks base method. +func (m *MockHttpFilterHandle) SetUpstreamOverrideHost(host string, strict bool) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetUpstreamOverrideHost", host, strict) + ret0, _ := ret[0].(bool) + return ret0 +} + +// SetUpstreamOverrideHost indicates an expected call of SetUpstreamOverrideHost. +func (mr *MockHttpFilterHandleMockRecorder) SetUpstreamOverrideHost(host, strict any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUpstreamOverrideHost", reflect.TypeOf((*MockHttpFilterHandle)(nil).SetUpstreamOverrideHost), host, strict) +} + // StartHttpStream mocks base method. func (m *MockHttpFilterHandle) StartHttpStream(cluster string, headers [][2]string, body []byte, endOfStream bool, timeoutMs uint64, cb shared.HttpStreamCallback) (shared.HttpCalloutInitResult, uint64) { m.ctrl.T.Helper() diff --git a/test/extensions/dynamic_modules/http/filter_test.cc b/test/extensions/dynamic_modules/http/filter_test.cc index 89407ac463d01..0212a477abf12 100644 --- a/test/extensions/dynamic_modules/http/filter_test.cc +++ b/test/extensions/dynamic_modules/http/filter_test.cc @@ -12,6 +12,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/stats/mocks.h" +#include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/thread_local_cluster.h" #include "test/test_common/utility.h" @@ -478,6 +479,10 @@ TEST_P(DynamicModuleHttpLanguageTests, FilterStateCallbacks) { // There is no filter state named key set by the filter. const auto* value = stream_info.filterState()->getDataReadOnly("key"); ASSERT_EQ(value, nullptr); + const auto* typed_value = stream_info.filterState()->getDataReadOnly( + "envoy.test.http_typed_object_for_rust"); + ASSERT_NE(typed_value, nullptr); + EXPECT_EQ(typed_value->serializeAsString(), "typed_value"); filter->onStreamComplete(); const auto* stream_complete_value = @@ -486,47 +491,247 @@ TEST_P(DynamicModuleHttpLanguageTests, FilterStateCallbacks) { EXPECT_EQ(stream_complete_value->serializeAsString(), "stream_complete_value"); } -TEST_P(DynamicModuleHttpLanguageTests, TypedFilterStateCallbacks) { - const std::string filter_name = "typed_filter_state_callbacks"; +TEST_P(DynamicModuleHttpLanguageTests, LocalReplyCallbacks) { + const std::string filter_name = "local_reply_callbacks"; const std::string filter_config = ""; - const auto language = GetParam(); - if (language != "rust") { - // Only Rust SDK has this test for now. - GTEST_SKIP(); - } + auto dynamic_module = newDynamicModule(testSharedObjectPath("http", GetParam()), false); + EXPECT_TRUE(dynamic_module.ok()) << dynamic_module.status().message(); + + NiceMock context; + Stats::IsolatedStoreImpl stats_store; + auto filter_config_or_status = + Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( + filter_name, filter_config, DefaultMetricsNamespace, false, + std::move(dynamic_module.value()), *stats_store.createScope(""), context); + ASSERT_TRUE(filter_config_or_status.ok()); - auto dynamic_module = newDynamicModule(testSharedObjectPath("http", language), false); + auto filter = std::make_shared(filter_config_or_status.value(), + stats_store.symbolTable(), 0); + filter->initializeInModuleFilter(); + + const Http::StreamFilterBase::LocalReplyData local_reply_data{ + Http::Code::Unauthorized, absl::nullopt, "local-reply", false}; + EXPECT_EQ(Http::LocalErrorStatus::ContinueAndResetStream, filter->onLocalReply(local_reply_data)); + + filter->onDestroy(); +} + +TEST_P(DynamicModuleHttpLanguageTests, ResetStream) { + const std::string filter_name = "reset_stream"; + const std::string filter_config = ""; + + auto dynamic_module = newDynamicModule(testSharedObjectPath("http", GetParam()), false); EXPECT_TRUE(dynamic_module.ok()) << dynamic_module.status().message(); NiceMock context; Stats::IsolatedStoreImpl stats_store; - auto stats_scope = stats_store.createScope(""); auto filter_config_or_status = Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( filter_name, filter_config, DefaultMetricsNamespace, false, - std::move(dynamic_module.value()), *stats_scope, context); - EXPECT_TRUE(filter_config_or_status.ok()); + std::move(dynamic_module.value()), *stats_store.createScope(""), context); + ASSERT_TRUE(filter_config_or_status.ok()); auto filter = std::make_shared(filter_config_or_status.value(), - stats_scope->symbolTable(), 0); + stats_store.symbolTable(), 0); filter->initializeInModuleFilter(); NiceMock callbacks; - NiceMock stream_info; - EXPECT_CALL(callbacks, streamInfo()).WillRepeatedly(testing::ReturnRef(stream_info)); - EXPECT_CALL(stream_info, filterState()) - .WillRepeatedly(testing::ReturnRef(stream_info.filter_state_)); filter->setDecoderFilterCallbacks(callbacks); + EXPECT_CALL(callbacks, resetStream(Http::StreamResetReason::LocalReset, "details")); - Http::TestRequestHeaderMapImpl request_headers{}; + Http::TestRequestHeaderMapImpl request_headers{{}}; EXPECT_EQ(FilterHeadersStatus::Continue, filter->decodeHeaders(request_headers, false)); - // Verify the typed filter state was set correctly by the Rust filter. - const auto* typed_value = stream_info.filterState()->getDataReadOnly( - "envoy.test.http_typed_object_for_rust"); - ASSERT_NE(typed_value, nullptr); - EXPECT_EQ(typed_value->serializeAsString(), "typed_value"); + filter->onDestroy(); +} + +TEST_P(DynamicModuleHttpLanguageTests, SendGoAwayAndClose) { + const std::string filter_name = "send_go_away_and_close"; + const std::string filter_config = ""; + + auto dynamic_module = newDynamicModule(testSharedObjectPath("http", GetParam()), false); + EXPECT_TRUE(dynamic_module.ok()) << dynamic_module.status().message(); + + NiceMock context; + Stats::IsolatedStoreImpl stats_store; + auto filter_config_or_status = + Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( + filter_name, filter_config, DefaultMetricsNamespace, false, + std::move(dynamic_module.value()), *stats_store.createScope(""), context); + ASSERT_TRUE(filter_config_or_status.ok()); + + auto filter = std::make_shared(filter_config_or_status.value(), + stats_store.symbolTable(), 0); + filter->initializeInModuleFilter(); + + NiceMock callbacks; + filter->setDecoderFilterCallbacks(callbacks); + EXPECT_CALL(callbacks, sendGoAwayAndClose(true)); + + Http::TestRequestHeaderMapImpl request_headers{{}}; + EXPECT_EQ(FilterHeadersStatus::Continue, filter->decodeHeaders(request_headers, false)); + + filter->onDestroy(); +} + +TEST_P(DynamicModuleHttpLanguageTests, RecreateStream) { + const std::string filter_name = "recreate_stream"; + const std::string filter_config = ""; + + auto dynamic_module = newDynamicModule(testSharedObjectPath("http", GetParam()), false); + EXPECT_TRUE(dynamic_module.ok()) << dynamic_module.status().message(); + + NiceMock context; + Stats::IsolatedStoreImpl stats_store; + auto filter_config_or_status = + Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( + filter_name, filter_config, DefaultMetricsNamespace, false, + std::move(dynamic_module.value()), *stats_store.createScope(""), context); + ASSERT_TRUE(filter_config_or_status.ok()); + + auto filter = std::make_shared(filter_config_or_status.value(), + stats_store.symbolTable(), 0); + filter->initializeInModuleFilter(); + + NiceMock callbacks; + filter->setDecoderFilterCallbacks(callbacks); + EXPECT_CALL(callbacks, recreateStream(testing::NotNull())) + .WillOnce(testing::Invoke([](const Http::ResponseHeaderMap* headers) { + EXPECT_EQ(headers->getStatusValue(), "302"); + const auto location = headers->get(Http::LowerCaseString("location")); + EXPECT_FALSE(location.empty()); + EXPECT_EQ(location[0]->value().getStringView(), "/recreated"); + return true; + })); + + Http::TestRequestHeaderMapImpl request_headers{{}}; + EXPECT_EQ(FilterHeadersStatus::Continue, filter->decodeHeaders(request_headers, false)); + + filter->onDestroy(); +} + +TEST_P(DynamicModuleHttpLanguageTests, SocketOptionCallbacks) { + const std::string filter_name = "socket_option_callbacks"; + const std::string filter_config = ""; + + auto dynamic_module = newDynamicModule(testSharedObjectPath("http", GetParam()), false); + EXPECT_TRUE(dynamic_module.ok()) << dynamic_module.status().message(); + + NiceMock context; + Stats::IsolatedStoreImpl stats_store; + auto filter_config_or_status = + Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( + filter_name, filter_config, DefaultMetricsNamespace, false, + std::move(dynamic_module.value()), *stats_store.createScope(""), context); + ASSERT_TRUE(filter_config_or_status.ok()); + + auto filter = std::make_shared(filter_config_or_status.value(), + stats_store.symbolTable(), 0); + filter->initializeInModuleFilter(); + + NiceMock callbacks; + Http::TestRequestHeaderMapImpl request_headers{{}}; + EXPECT_CALL(callbacks, requestHeaders()) + .WillRepeatedly(testing::Return(makeOptRef(request_headers))); + filter->setDecoderFilterCallbacks(callbacks); + + EXPECT_EQ(FilterHeadersStatus::Continue, filter->decodeHeaders(request_headers, false)); + EXPECT_EQ(request_headers.get_("x-socket-option-callbacks"), "true"); + + filter->onDestroy(); +} + +TEST_P(DynamicModuleHttpLanguageTests, SpanCallbacks) { + const std::string filter_name = "span_callbacks"; + const std::string filter_config = ""; + + auto dynamic_module = newDynamicModule(testSharedObjectPath("http", GetParam()), false); + EXPECT_TRUE(dynamic_module.ok()) << dynamic_module.status().message(); + + NiceMock context; + Stats::IsolatedStoreImpl stats_store; + auto filter_config_or_status = + Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( + filter_name, filter_config, DefaultMetricsNamespace, false, + std::move(dynamic_module.value()), *stats_store.createScope(""), context); + ASSERT_TRUE(filter_config_or_status.ok()); + + auto filter = std::make_shared(filter_config_or_status.value(), + stats_store.symbolTable(), 0); + filter->initializeInModuleFilter(); + + NiceMock callbacks; + NiceMock span; + auto* child_span = new NiceMock(); + EXPECT_CALL(callbacks, activeSpan()).WillRepeatedly(testing::ReturnRef(span)); + EXPECT_CALL(span, setTag("key", "value")); + EXPECT_CALL(span, setOperation("operation")); + EXPECT_CALL(span, log(testing::_, "event")); + EXPECT_CALL(span, setSampled(true)); + EXPECT_CALL(span, getBaggage("key")).WillOnce(testing::Return("")); + EXPECT_CALL(span, setBaggage("key", "value")); + EXPECT_CALL(span, getTraceId()).WillOnce(testing::Return("trace-id")); + EXPECT_CALL(span, getSpanId()).WillOnce(testing::Return("span-id")); + EXPECT_CALL(span, spawnChild_(testing::_, "child", testing::_)) + .WillOnce(testing::Return(child_span)); + EXPECT_CALL(*child_span, setTag("child-key", "child-value")); + EXPECT_CALL(*child_span, finishSpan()); + Http::TestRequestHeaderMapImpl request_headers{{}}; + EXPECT_CALL(callbacks, requestHeaders()) + .WillRepeatedly(testing::Return(makeOptRef(request_headers))); + filter->setDecoderFilterCallbacks(callbacks); + + EXPECT_EQ(FilterHeadersStatus::Continue, filter->decodeHeaders(request_headers, false)); + EXPECT_EQ(request_headers.get_("x-span-callbacks"), "true"); + + filter->onDestroy(); +} + +TEST_P(DynamicModuleHttpLanguageTests, ClusterCallbacks) { + const std::string filter_name = "cluster_callbacks"; + const std::string filter_config = ""; + + NiceMock context; + Upstream::MockClusterManager cluster_manager; + NiceMock thread_local_cluster; + EXPECT_CALL(context, clusterManager()).WillRepeatedly(testing::ReturnRef(cluster_manager)); + EXPECT_CALL(cluster_manager, getThreadLocalCluster("fake_cluster")) + .WillRepeatedly(testing::Return(&thread_local_cluster)); + auto* mock_host_set = thread_local_cluster.cluster_.priority_set_.getMockHostSet(0); + mock_host_set->hosts_.resize(3); + mock_host_set->healthy_hosts_.resize(2); + mock_host_set->degraded_hosts_.resize(1); + + auto dynamic_module = newDynamicModule(testSharedObjectPath("http", GetParam()), false); + EXPECT_TRUE(dynamic_module.ok()) << dynamic_module.status().message(); + + Stats::IsolatedStoreImpl stats_store; + auto filter_config_or_status = + Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig( + filter_name, filter_config, DefaultMetricsNamespace, false, + std::move(dynamic_module.value()), *stats_store.createScope(""), context); + ASSERT_TRUE(filter_config_or_status.ok()); + + auto filter = std::make_shared(filter_config_or_status.value(), + stats_store.symbolTable(), 0); + filter->initializeInModuleFilter(); + + NiceMock callbacks; + std::string cluster_name = "fake_cluster"; + EXPECT_CALL(*callbacks.cluster_info_, name()).WillRepeatedly(testing::ReturnRef(cluster_name)); + EXPECT_CALL(callbacks, setUpstreamOverrideHost(testing::_)) + .WillOnce(testing::Invoke([](Upstream::LoadBalancerContext::OverrideHost override_host) { + EXPECT_EQ(override_host.host, "127.0.0.1:1"); + EXPECT_FALSE(override_host.strict); + })); + Http::TestRequestHeaderMapImpl request_headers{{}}; + EXPECT_CALL(callbacks, requestHeaders()) + .WillRepeatedly(testing::Return(makeOptRef(request_headers))); + filter->setDecoderFilterCallbacks(callbacks); + + EXPECT_EQ(FilterHeadersStatus::Continue, filter->decodeHeaders(request_headers, false)); + EXPECT_EQ(request_headers.get_("x-cluster-callbacks"), "true"); filter->onDestroy(); } diff --git a/test/extensions/dynamic_modules/http/integration_test.cc b/test/extensions/dynamic_modules/http/integration_test.cc index 4cd57421b8a01..b5f2cd141a67d 100644 --- a/test/extensions/dynamic_modules/http/integration_test.cc +++ b/test/extensions/dynamic_modules/http/integration_test.cc @@ -980,12 +980,6 @@ TEST_P(DynamicModulesIntegrationTest, ConfigScheduler) { // Test buffer limit callbacks for non-terminal filters. TEST_P(DynamicModulesIntegrationTest, BufferLimitFilter) { - // TODO(wbpcode): Enable this test for other SDKs when supported. - if (GetParam() != "rust") { - // Buffer limit callbacks are only supported in the Rust SDK currently. - return; - } - initializeFilter("buffer_limit_filter"); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); diff --git a/test/extensions/dynamic_modules/sdk/cpp/sdk_mocks.h b/test/extensions/dynamic_modules/sdk/cpp/sdk_mocks.h index bfe78d443c13c..6139b0473ca88 100644 --- a/test/extensions/dynamic_modules/sdk/cpp/sdk_mocks.h +++ b/test/extensions/dynamic_modules/sdk/cpp/sdk_mocks.h @@ -137,6 +137,36 @@ class MockHttpFilterHandle : public HttpFilterHandle { MOCK_METHOD(void, continueResponse, (), (override)); MOCK_METHOD(void, clearRouteCache, (), (override)); MOCK_METHOD(void, refreshRouteCluster, (), (override)); + MOCK_METHOD(uint64_t, getBufferLimit, (), (override)); + MOCK_METHOD(void, setBufferLimit, (uint64_t limit), (override)); + MOCK_METHOD(std::optional, getFilterStateTyped, (std::string_view key), + (override)); + MOCK_METHOD(bool, setFilterStateTyped, (std::string_view key, std::string_view value), + (override)); + MOCK_METHOD(uint32_t, getWorkerIndex, (), (override)); + MOCK_METHOD(bool, setSocketOptionInt, + (int64_t level, int64_t name, SocketOptionState state, SocketDirection direction, + int64_t value), + (override)); + MOCK_METHOD(bool, setSocketOptionBytes, + (int64_t level, int64_t name, SocketOptionState state, SocketDirection direction, + std::string_view value), + (override)); + MOCK_METHOD(std::optional, getSocketOptionInt, + (int64_t level, int64_t name, SocketOptionState state, SocketDirection direction), + (override)); + MOCK_METHOD(std::optional, getSocketOptionBytes, + (int64_t level, int64_t name, SocketOptionState state, SocketDirection direction), + (override)); + MOCK_METHOD(std::unique_ptr, getActiveSpan, (), (override)); + MOCK_METHOD(std::optional, getClusterName, (), (override)); + MOCK_METHOD(std::optional, getClusterHostCounts, (uint32_t priority), + (override)); + MOCK_METHOD(bool, setUpstreamOverrideHost, (std::string_view host, bool strict), (override)); + MOCK_METHOD(void, resetStream, (HttpFilterStreamResetReason reason, std::string_view details), + (override)); + MOCK_METHOD(void, sendGoAwayAndClose, (bool graceful), (override)); + MOCK_METHOD(bool, recreateStream, (std::span headers), (override)); MOCK_METHOD(HeaderMap&, requestHeaders, (), (override)); MOCK_METHOD(BodyBuffer&, bufferedRequestBody, (), (override)); MOCK_METHOD(BodyBuffer&, receivedRequestBody, (), (override)); diff --git a/test/extensions/dynamic_modules/test_data/cpp/http.cc b/test/extensions/dynamic_modules/test_data/cpp/http.cc index 2587fd443cbe8..52f2b0f1c9ff4 100644 --- a/test/extensions/dynamic_modules/test_data/cpp/http.cc +++ b/test/extensions/dynamic_modules/test_data/cpp/http.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -280,6 +281,330 @@ class SendResponseConfigFactory : public HttpFilterConfigFactory { REGISTER_HTTP_FILTER_CONFIG_FACTORY(SendResponseConfigFactory, "send_response"); +// --- local_reply_callbacks --- + +class LocalReplyCallbacksFilter : public HttpFilter { +public: + explicit LocalReplyCallbacksFilter(HttpFilterHandle&) {} + + HeadersStatus onRequestHeaders(HeaderMap&, bool) override { return HeadersStatus::Continue; } + BodyStatus onRequestBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onRequestTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + HeadersStatus onResponseHeaders(HeaderMap&, bool) override { return HeadersStatus::Continue; } + BodyStatus onResponseBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onResponseTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + void onStreamComplete() override {} + void onDestroy() override {} + + LocalReplyStatus onLocalReply(uint32_t, std::string_view, bool) override { + return LocalReplyStatus::ContinueAndResetStream; + } +}; + +class LocalReplyCallbacksFactory : public HttpFilterFactory { +public: + std::unique_ptr create(HttpFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class LocalReplyCallbacksConfigFactory : public HttpFilterConfigFactory { +public: + std::unique_ptr create(HttpFilterConfigHandle&, std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_HTTP_FILTER_CONFIG_FACTORY(LocalReplyCallbacksConfigFactory, "local_reply_callbacks"); + +// --- reset_stream --- + +class ResetStreamFilter : public HttpFilter { +public: + explicit ResetStreamFilter(HttpFilterHandle& handle) : handle_(handle) {} + + HeadersStatus onRequestHeaders(HeaderMap&, bool) override { + [[maybe_unused]] const auto unused_reason = + HttpFilterStreamResetReason::LocalRefusedStreamReset; + handle_.resetStream(HttpFilterStreamResetReason::LocalReset, "details"); + return HeadersStatus::Continue; + } + + BodyStatus onRequestBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onRequestTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + HeadersStatus onResponseHeaders(HeaderMap&, bool) override { return HeadersStatus::Continue; } + BodyStatus onResponseBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onResponseTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + void onStreamComplete() override {} + void onDestroy() override {} + +private: + HttpFilterHandle& handle_; +}; + +class ResetStreamFactory : public HttpFilterFactory { +public: + std::unique_ptr create(HttpFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class ResetStreamConfigFactory : public HttpFilterConfigFactory { +public: + std::unique_ptr create(HttpFilterConfigHandle&, std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_HTTP_FILTER_CONFIG_FACTORY(ResetStreamConfigFactory, "reset_stream"); + +// --- send_go_away_and_close --- + +class SendGoAwayAndCloseFilter : public HttpFilter { +public: + explicit SendGoAwayAndCloseFilter(HttpFilterHandle& handle) : handle_(handle) {} + + HeadersStatus onRequestHeaders(HeaderMap&, bool) override { + handle_.sendGoAwayAndClose(true); + return HeadersStatus::Continue; + } + + BodyStatus onRequestBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onRequestTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + HeadersStatus onResponseHeaders(HeaderMap&, bool) override { return HeadersStatus::Continue; } + BodyStatus onResponseBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onResponseTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + void onStreamComplete() override {} + void onDestroy() override {} + +private: + HttpFilterHandle& handle_; +}; + +class SendGoAwayAndCloseFactory : public HttpFilterFactory { +public: + std::unique_ptr create(HttpFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class SendGoAwayAndCloseConfigFactory : public HttpFilterConfigFactory { +public: + std::unique_ptr create(HttpFilterConfigHandle&, std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_HTTP_FILTER_CONFIG_FACTORY(SendGoAwayAndCloseConfigFactory, "send_go_away_and_close"); + +// --- recreate_stream --- + +class RecreateStreamFilter : public HttpFilter { +public: + explicit RecreateStreamFilter(HttpFilterHandle& handle) : handle_(handle) {} + + HeadersStatus onRequestHeaders(HeaderMap&, bool) override { + std::vector headers = {{":status", "302"}, {"location", "/recreated"}}; + if (!handle_.recreateStream(headers)) { + std::abort(); + } + return HeadersStatus::Continue; + } + + BodyStatus onRequestBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onRequestTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + HeadersStatus onResponseHeaders(HeaderMap&, bool) override { return HeadersStatus::Continue; } + BodyStatus onResponseBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onResponseTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + void onStreamComplete() override {} + void onDestroy() override {} + +private: + HttpFilterHandle& handle_; +}; + +class RecreateStreamFactory : public HttpFilterFactory { +public: + std::unique_ptr create(HttpFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class RecreateStreamConfigFactory : public HttpFilterConfigFactory { +public: + std::unique_ptr create(HttpFilterConfigHandle&, std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_HTTP_FILTER_CONFIG_FACTORY(RecreateStreamConfigFactory, "recreate_stream"); + +// --- socket_option_callbacks --- + +class SocketOptionCallbacksFilter : public HttpFilter { +public: + explicit SocketOptionCallbacksFilter(HttpFilterHandle& handle) : handle_(handle) {} + + HeadersStatus onRequestHeaders(HeaderMap& headers, bool) override { + if (!handle_.setSocketOptionInt(1, 2, SocketOptionState::Prebind, SocketDirection::Upstream, + 123)) { + std::abort(); + } + if (!handle_.setSocketOptionBytes(3, 4, SocketOptionState::Bound, SocketDirection::Upstream, + "socket-bytes")) { + std::abort(); + } + auto int_value = + handle_.getSocketOptionInt(1, 2, SocketOptionState::Prebind, SocketDirection::Upstream); + if (!int_value.has_value() || *int_value != 123) { + std::abort(); + } + auto bytes_value = + handle_.getSocketOptionBytes(3, 4, SocketOptionState::Bound, SocketDirection::Upstream); + if (!bytes_value.has_value() || *bytes_value != "socket-bytes") { + std::abort(); + } + headers.set("x-socket-option-callbacks", "true"); + return HeadersStatus::Continue; + } + + BodyStatus onRequestBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onRequestTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + HeadersStatus onResponseHeaders(HeaderMap&, bool) override { return HeadersStatus::Continue; } + BodyStatus onResponseBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onResponseTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + void onStreamComplete() override {} + void onDestroy() override {} + +private: + HttpFilterHandle& handle_; +}; + +class SocketOptionCallbacksFactory : public HttpFilterFactory { +public: + std::unique_ptr create(HttpFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class SocketOptionCallbacksConfigFactory : public HttpFilterConfigFactory { +public: + std::unique_ptr create(HttpFilterConfigHandle&, std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_HTTP_FILTER_CONFIG_FACTORY(SocketOptionCallbacksConfigFactory, "socket_option_callbacks"); + +// --- span_callbacks --- + +class SpanCallbacksFilter : public HttpFilter { +public: + explicit SpanCallbacksFilter(HttpFilterHandle& handle) : handle_(handle) {} + + HeadersStatus onRequestHeaders(HeaderMap& headers, bool) override { + auto span = handle_.getActiveSpan(); + if (span != nullptr) { + span->setTag("key", "value"); + span->setOperation("operation"); + span->log("event"); + span->setSampled(true); + auto baggage = span->getBaggage("key"); + (void)baggage; + span->setBaggage("key", "value"); + auto trace_id = span->getTraceID(); + (void)trace_id; + auto span_id = span->getSpanID(); + (void)span_id; + auto child = span->spawnChild("child"); + if (child != nullptr) { + child->setTag("child-key", "child-value"); + child->finish(); + } + } + headers.set("x-span-callbacks", "true"); + return HeadersStatus::Continue; + } + + BodyStatus onRequestBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onRequestTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + HeadersStatus onResponseHeaders(HeaderMap&, bool) override { return HeadersStatus::Continue; } + BodyStatus onResponseBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onResponseTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + void onStreamComplete() override {} + void onDestroy() override {} + +private: + HttpFilterHandle& handle_; +}; + +class SpanCallbacksFactory : public HttpFilterFactory { +public: + std::unique_ptr create(HttpFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class SpanCallbacksConfigFactory : public HttpFilterConfigFactory { +public: + std::unique_ptr create(HttpFilterConfigHandle&, std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_HTTP_FILTER_CONFIG_FACTORY(SpanCallbacksConfigFactory, "span_callbacks"); + +// --- cluster_callbacks --- + +class ClusterCallbacksFilter : public HttpFilter { +public: + explicit ClusterCallbacksFilter(HttpFilterHandle& handle) : handle_(handle) {} + + HeadersStatus onRequestHeaders(HeaderMap& headers, bool) override { + auto cluster_name = handle_.getClusterName(); + if (!cluster_name.has_value() || *cluster_name != "fake_cluster") { + std::abort(); + } + auto cluster_counts = handle_.getClusterHostCounts(0); + if (!cluster_counts.has_value() || cluster_counts->total != 3 || cluster_counts->healthy != 2 || + cluster_counts->degraded != 1) { + std::abort(); + } + if (!handle_.setUpstreamOverrideHost("127.0.0.1:1", false)) { + std::abort(); + } + headers.set("x-cluster-callbacks", "true"); + return HeadersStatus::Continue; + } + + BodyStatus onRequestBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onRequestTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + HeadersStatus onResponseHeaders(HeaderMap&, bool) override { return HeadersStatus::Continue; } + BodyStatus onResponseBody(BodyBuffer&, bool) override { return BodyStatus::Continue; } + TrailersStatus onResponseTrailers(HeaderMap&) override { return TrailersStatus::Continue; } + void onStreamComplete() override {} + void onDestroy() override {} + +private: + HttpFilterHandle& handle_; +}; + +class ClusterCallbacksFactory : public HttpFilterFactory { +public: + std::unique_ptr create(HttpFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class ClusterCallbacksConfigFactory : public HttpFilterConfigFactory { +public: + std::unique_ptr create(HttpFilterConfigHandle&, std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_HTTP_FILTER_CONFIG_FACTORY(ClusterCallbacksConfigFactory, "cluster_callbacks"); + // --- dynamic_metadata_callbacks --- class DynamicMetadataCallbacksFilter : public HttpFilter { @@ -465,6 +790,21 @@ class FilterStateCallbacksFilter : public HttpFilter { FilterStateCallbacksFilter(HttpFilterHandle& handle) : handle_(handle) {} HeadersStatus onRequestHeaders(HeaderMap&, bool) override { + [[maybe_unused]] const auto health_check = AttributeID::HealthCheck; + if (handle_.getWorkerIndex() != 0) { + std::abort(); + } + auto typed_before = handle_.getFilterStateTyped("envoy.test.http_typed_object_for_rust"); + if (typed_before.has_value()) { + std::abort(); + } + if (!handle_.setFilterStateTyped("envoy.test.http_typed_object_for_rust", "typed_value")) { + std::abort(); + } + auto typed_after = handle_.getFilterStateTyped("envoy.test.http_typed_object_for_rust"); + if (!typed_after.has_value() || *typed_after != "typed_value") { + std::abort(); + } testFilterState("req_header_key", "req_header_value"); return HeadersStatus::Continue; } diff --git a/test/extensions/dynamic_modules/test_data/cpp/http_integration_test.cc b/test/extensions/dynamic_modules/test_data/cpp/http_integration_test.cc index 391bf5d2d0793..7c073e0edb61e 100644 --- a/test/extensions/dynamic_modules/test_data/cpp/http_integration_test.cc +++ b/test/extensions/dynamic_modules/test_data/cpp/http_integration_test.cc @@ -1196,6 +1196,85 @@ class StreamingTerminalConfigFactory : public HttpFilterConfigFactory { REGISTER_HTTP_FILTER_CONFIG_FACTORY(StreamingTerminalConfigFactory, "streaming_terminal_filter"); +// ----------------------------------------------------------------------------- +// BufferLimit +// ----------------------------------------------------------------------------- + +class BufferLimitFilter : public HttpFilter, public DownstreamWatermarkCallbacks { +public: + explicit BufferLimitFilter(HttpFilterHandle& handle) : handle_(handle) { + handle_.setDownstreamWatermarkCallbacks(*this); + } + + HeadersStatus onRequestHeaders(HeaderMap& headers, bool end_stream) override { + initial_buffer_limit_ = handle_.getBufferLimit(); + constexpr uint64_t desired_limit = 65536; + if (initial_buffer_limit_ < desired_limit) { + handle_.setBufferLimit(desired_limit); + } + return HeadersStatus::Continue; + } + + HeadersStatus onResponseHeaders(HeaderMap& headers, bool end_stream) override { + headers.set("x-initial-buffer-limit", std::to_string(initial_buffer_limit_)); + headers.set("x-current-buffer-limit", std::to_string(handle_.getBufferLimit())); + headers.set("x-above-watermark-count", std::to_string(above_w_)); + headers.set("x-below-watermark-count", std::to_string(below_w_)); + return HeadersStatus::Continue; + } + + void onAboveWriteBufferHighWatermark() override { above_w_++; } + + void onBelowWriteBufferLowWatermark() override { below_w_++; } + + BodyStatus onRequestBody(BodyBuffer& body, bool end_stream) override { + return BodyStatus::Continue; + } + + TrailersStatus onRequestTrailers(HeaderMap& trailers) override { + return TrailersStatus::Continue; + } + + BodyStatus onResponseBody(BodyBuffer& body, bool end_stream) override { + return BodyStatus::Continue; + } + + TrailersStatus onResponseTrailers(HeaderMap& trailers) override { + return TrailersStatus::Continue; + } + + void onStreamComplete() override {} + + void onDestroy() override { handle_.clearDownstreamWatermarkCallbacks(); } + +private: + HttpFilterHandle& handle_; + uint64_t initial_buffer_limit_{0}; + int above_w_{0}; + int below_w_{0}; +}; + +class BufferLimitFilterFactory : public HttpFilterFactory { +public: + std::unique_ptr create(HttpFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class BufferLimitConfigFactory : public HttpFilterConfigFactory { +public: + std::unique_ptr create(HttpFilterConfigHandle& handle, + std::string_view config_view) override { + return std::make_unique(); + } +}; + +REGISTER_HTTP_FILTER_CONFIG_FACTORY(BufferLimitConfigFactory, "buffer_limit_filter"); + +// ----------------------------------------------------------------------------- +// SdkSurfaceSmoke +// ----------------------------------------------------------------------------- + // ----------------------------------------------------------------------------- // HttpStreamBasic // ----------------------------------------------------------------------------- diff --git a/test/extensions/dynamic_modules/test_data/go/http/http.go b/test/extensions/dynamic_modules/test_data/go/http/http.go index 923923af6bcb7..d0827fa8e55f0 100644 --- a/test/extensions/dynamic_modules/test_data/go/http/http.go +++ b/test/extensions/dynamic_modules/test_data/go/http/http.go @@ -12,6 +12,13 @@ func init() { sdk.RegisterHttpFilterConfigFactories(map[string]shared.HttpFilterConfigFactory{ "stats_callbacks": &statsCallbacksConfigFactory{}, "header_callbacks": &headerCallbacksConfigFactory{}, + "local_reply_callbacks": &localReplyCallbacksConfigFactory{}, + "reset_stream": &resetStreamConfigFactory{}, + "send_go_away_and_close": &sendGoAwayAndCloseConfigFactory{}, + "recreate_stream": &recreateStreamConfigFactory{}, + "socket_option_callbacks": &socketOptionCallbacksConfigFactory{}, + "span_callbacks": &spanCallbacksConfigFactory{}, + "cluster_callbacks": &clusterCallbacksConfigFactory{}, "send_response": &sendResponseConfigFactory{}, "dynamic_metadata_callbacks": &dynamicMetadataCallbacksConfigFactory{}, "filter_state_callbacks": &filterStateCallbacksConfigFactory{}, @@ -133,6 +140,9 @@ func (p *headerCallbacksFilter) OnRequestHeaders(headers shared.HeaderMap, if _, ok := p.handle.GetAttributeString(shared.AttributeIDSourceAddress); !ok { panic("source address not found") } + if workerIndex := p.handle.GetWorkerIndex(); workerIndex != 0 { + panic(fmt.Sprintf("worker index mismatch: %d", workerIndex)) + } return shared.HeadersStatusContinue } @@ -228,6 +238,251 @@ func (p *sendResponseFilter) OnRequestHeaders(headers shared.HeaderMap, return shared.HeadersStatusStop } +// --- local_reply_callbacks --- +type localReplyCallbacksConfigFactory struct { + shared.EmptyHttpFilterConfigFactory +} + +type localReplyCallbacksFactory struct { + shared.EmptyHttpFilterFactory +} + +func (f *localReplyCallbacksConfigFactory) Create(handle shared.HttpFilterConfigHandle, + config []byte) (shared.HttpFilterFactory, error) { + return &localReplyCallbacksFactory{}, nil +} + +type localReplyCallbacksFilter struct { + handle shared.HttpFilterHandle + shared.EmptyHttpFilter +} + +func (f *localReplyCallbacksFactory) Create(handle shared.HttpFilterHandle) shared.HttpFilter { + return &localReplyCallbacksFilter{handle: handle} +} + +func (p *localReplyCallbacksFilter) OnRequestHeaders(headers shared.HeaderMap, + endOfStream bool) shared.HeadersStatus { + return shared.HeadersStatusContinue +} + +func (p *localReplyCallbacksFilter) OnLocalReply(responseCode uint32, details shared.UnsafeEnvoyBuffer, resetImminent bool) shared.LocalReplyStatus { + return shared.LocalReplyStatusContinueAndResetStream +} + +// --- reset_stream --- +type resetStreamConfigFactory struct { + shared.EmptyHttpFilterConfigFactory +} + +type resetStreamFactory struct { + shared.EmptyHttpFilterFactory +} + +func (f *resetStreamConfigFactory) Create(handle shared.HttpFilterConfigHandle, + config []byte) (shared.HttpFilterFactory, error) { + return &resetStreamFactory{}, nil +} + +type resetStreamFilter struct { + handle shared.HttpFilterHandle + shared.EmptyHttpFilter +} + +func (f *resetStreamFactory) Create(handle shared.HttpFilterHandle) shared.HttpFilter { + return &resetStreamFilter{handle: handle} +} + +func (p *resetStreamFilter) OnRequestHeaders(headers shared.HeaderMap, + endOfStream bool) shared.HeadersStatus { + _ = shared.HttpFilterStreamResetReasonLocalRefusedStreamReset + p.handle.ResetStream(shared.HttpFilterStreamResetReasonLocalReset, "details") + return shared.HeadersStatusContinue +} + +// --- send_go_away_and_close --- +type sendGoAwayAndCloseConfigFactory struct { + shared.EmptyHttpFilterConfigFactory +} + +type sendGoAwayAndCloseFactory struct { + shared.EmptyHttpFilterFactory +} + +func (f *sendGoAwayAndCloseConfigFactory) Create(handle shared.HttpFilterConfigHandle, + config []byte) (shared.HttpFilterFactory, error) { + return &sendGoAwayAndCloseFactory{}, nil +} + +type sendGoAwayAndCloseFilter struct { + handle shared.HttpFilterHandle + shared.EmptyHttpFilter +} + +func (f *sendGoAwayAndCloseFactory) Create(handle shared.HttpFilterHandle) shared.HttpFilter { + return &sendGoAwayAndCloseFilter{handle: handle} +} + +func (p *sendGoAwayAndCloseFilter) OnRequestHeaders(headers shared.HeaderMap, + endOfStream bool) shared.HeadersStatus { + p.handle.SendGoAwayAndClose(true) + return shared.HeadersStatusContinue +} + +// --- recreate_stream --- +type recreateStreamConfigFactory struct { + shared.EmptyHttpFilterConfigFactory +} + +type recreateStreamFactory struct { + shared.EmptyHttpFilterFactory +} + +func (f *recreateStreamConfigFactory) Create(handle shared.HttpFilterConfigHandle, + config []byte) (shared.HttpFilterFactory, error) { + return &recreateStreamFactory{}, nil +} + +type recreateStreamFilter struct { + handle shared.HttpFilterHandle + shared.EmptyHttpFilter +} + +func (f *recreateStreamFactory) Create(handle shared.HttpFilterHandle) shared.HttpFilter { + return &recreateStreamFilter{handle: handle} +} + +func (p *recreateStreamFilter) OnRequestHeaders(headers shared.HeaderMap, + endOfStream bool) shared.HeadersStatus { + if !p.handle.RecreateStream([][2]string{{":status", "302"}, {"location", "/recreated"}}) { + panic("failed to recreate stream") + } + return shared.HeadersStatusContinue +} + +// --- socket_option_callbacks --- +type socketOptionCallbacksConfigFactory struct { + shared.EmptyHttpFilterConfigFactory +} + +type socketOptionCallbacksFactory struct { + shared.EmptyHttpFilterFactory +} + +func (f *socketOptionCallbacksConfigFactory) Create(handle shared.HttpFilterConfigHandle, + config []byte) (shared.HttpFilterFactory, error) { + return &socketOptionCallbacksFactory{}, nil +} + +type socketOptionCallbacksFilter struct { + handle shared.HttpFilterHandle + shared.EmptyHttpFilter +} + +func (f *socketOptionCallbacksFactory) Create(handle shared.HttpFilterHandle) shared.HttpFilter { + return &socketOptionCallbacksFilter{handle: handle} +} + +func (p *socketOptionCallbacksFilter) OnRequestHeaders(headers shared.HeaderMap, + endOfStream bool) shared.HeadersStatus { + if !p.handle.SetSocketOptionInt(1, 2, shared.SocketOptionStatePrebind, shared.SocketDirectionUpstream, 123) { + panic("failed to set int socket option") + } + if !p.handle.SetSocketOptionBytes(3, 4, shared.SocketOptionStateBound, shared.SocketDirectionUpstream, []byte("socket-bytes")) { + panic("failed to set bytes socket option") + } + if value, ok := p.handle.GetSocketOptionInt(1, 2, shared.SocketOptionStatePrebind, shared.SocketDirectionUpstream); !ok || value != 123 { + panic(fmt.Sprintf("socket option int mismatch: ok=%v value=%d", ok, value)) + } + if value, ok := p.handle.GetSocketOptionBytes(3, 4, shared.SocketOptionStateBound, shared.SocketDirectionUpstream); !ok || + value.ToUnsafeString() != "socket-bytes" { + panic(fmt.Sprintf("socket option bytes mismatch: ok=%v value=%q", ok, value.ToUnsafeString())) + } + headers.Set("x-socket-option-callbacks", "true") + return shared.HeadersStatusContinue +} + +// --- span_callbacks --- +type spanCallbacksConfigFactory struct { + shared.EmptyHttpFilterConfigFactory +} + +type spanCallbacksFactory struct { + shared.EmptyHttpFilterFactory +} + +func (f *spanCallbacksConfigFactory) Create(handle shared.HttpFilterConfigHandle, + config []byte) (shared.HttpFilterFactory, error) { + return &spanCallbacksFactory{}, nil +} + +type spanCallbacksFilter struct { + handle shared.HttpFilterHandle + shared.EmptyHttpFilter +} + +func (f *spanCallbacksFactory) Create(handle shared.HttpFilterHandle) shared.HttpFilter { + return &spanCallbacksFilter{handle: handle} +} + +func (p *spanCallbacksFilter) OnRequestHeaders(headers shared.HeaderMap, + endOfStream bool) shared.HeadersStatus { + if span := p.handle.GetActiveSpan(); span != nil { + span.SetTag("key", "value") + span.SetOperation("operation") + span.Log("event") + span.SetSampled(true) + _, _ = span.GetBaggage("key") + span.SetBaggage("key", "value") + _, _ = span.GetTraceID() + _, _ = span.GetSpanID() + if child := span.SpawnChild("child"); child != nil { + child.SetTag("child-key", "child-value") + child.Finish() + } + } + headers.Set("x-span-callbacks", "true") + return shared.HeadersStatusContinue +} + +// --- cluster_callbacks --- +type clusterCallbacksConfigFactory struct { + shared.EmptyHttpFilterConfigFactory +} + +type clusterCallbacksFactory struct { + shared.EmptyHttpFilterFactory +} + +func (f *clusterCallbacksConfigFactory) Create(handle shared.HttpFilterConfigHandle, + config []byte) (shared.HttpFilterFactory, error) { + return &clusterCallbacksFactory{}, nil +} + +type clusterCallbacksFilter struct { + handle shared.HttpFilterHandle + shared.EmptyHttpFilter +} + +func (f *clusterCallbacksFactory) Create(handle shared.HttpFilterHandle) shared.HttpFilter { + return &clusterCallbacksFilter{handle: handle} +} + +func (p *clusterCallbacksFilter) OnRequestHeaders(headers shared.HeaderMap, + endOfStream bool) shared.HeadersStatus { + if clusterName, ok := p.handle.GetClusterName(); !ok || clusterName.ToUnsafeString() != "fake_cluster" { + panic(fmt.Sprintf("cluster name mismatch: ok=%v name=%q", ok, clusterName.ToUnsafeString())) + } + if counts, ok := p.handle.GetClusterHostCounts(0); !ok || counts.Total != 3 || counts.Healthy != 2 || counts.Degraded != 1 { + panic(fmt.Sprintf("cluster host counts mismatch: ok=%v counts=%+v", ok, counts)) + } + if !p.handle.SetUpstreamOverrideHost("127.0.0.1:1", false) { + panic("failed to set upstream override host") + } + headers.Set("x-cluster-callbacks", "true") + return shared.HeadersStatusContinue +} + // --- dynamic_metadata_callbacks --- type dynamicMetadataCallbacksConfigFactory struct { shared.EmptyHttpFilterConfigFactory @@ -430,6 +685,16 @@ func (f *filterStateCallbacksFactory) Create(handle shared.HttpFilterHandle) sha func (p *filterStateCallbacksFilter) OnRequestHeaders(headers shared.HeaderMap, endOfStream bool) shared.HeadersStatus { p.testFilterState("req_header_key", "req_header_value") + if val, ok := p.handle.GetFilterStateTyped("envoy.test.http_typed_object_for_rust"); ok { + panic(fmt.Sprintf("unexpected typed filter state: %s", val.ToUnsafeString())) + } + if !p.handle.SetFilterStateTyped("envoy.test.http_typed_object_for_rust", []byte("typed_value")) { + panic("failed to set typed filter state") + } + if val, ok := p.handle.GetFilterStateTyped("envoy.test.http_typed_object_for_rust"); !ok || + val.ToUnsafeString() != "typed_value" { + panic(fmt.Sprintf("typed filter state mismatch: ok=%v val=%q", ok, val.ToUnsafeString())) + } return shared.HeadersStatusContinue } diff --git a/test/extensions/dynamic_modules/test_data/go/http_integration_test/http_integration_test.go b/test/extensions/dynamic_modules/test_data/go/http_integration_test/http_integration_test.go index f5aa0c18ea027..473934a85c4be 100644 --- a/test/extensions/dynamic_modules/test_data/go/http_integration_test/http_integration_test.go +++ b/test/extensions/dynamic_modules/test_data/go/http_integration_test/http_integration_test.go @@ -28,6 +28,7 @@ func init() { "fake_external_cache": &FakeExternalCacheConfigFactory{}, "stats_callbacks": &StatsCallbacksConfigFactory{}, "streaming_terminal_filter": &StreamingTerminalConfigFactory{}, + "buffer_limit_filter": &BufferLimitConfigFactory{}, "http_stream_basic": &HttpStreamBasicConfigFactory{}, "http_stream_bidirectional": &HttpStreamBidirectionalConfigFactory{}, "upstream_reset": &UpstreamResetConfigFactory{}, @@ -965,6 +966,61 @@ func (p *StreamingTerminalFilter) OnStreamComplete() { runtime.GC() } +// ----------------------------------------------------------------------------- +// BufferLimit +// ----------------------------------------------------------------------------- + +type BufferLimitConfigFactory struct { + shared.EmptyHttpFilterConfigFactory +} + +func (f *BufferLimitConfigFactory) Create(h shared.HttpFilterConfigHandle, c []byte) (shared.HttpFilterFactory, error) { + return &BufferLimitFilterFactory{}, nil +} + +type BufferLimitFilterFactory struct { + shared.EmptyHttpFilterFactory +} + +func (f *BufferLimitFilterFactory) Create(h shared.HttpFilterHandle) shared.HttpFilter { + p := &BufferLimitFilter{handle: h} + h.SetDownstreamWatermarkCallbacks(p) + return p +} + +type BufferLimitFilter struct { + shared.EmptyHttpFilter + handle shared.HttpFilterHandle + initialBufferLimit uint64 + aboveW int + belowW int +} + +func (p *BufferLimitFilter) OnRequestHeaders(headers shared.HeaderMap, endOfStream bool) shared.HeadersStatus { + p.initialBufferLimit = p.handle.GetBufferLimit() + const desiredLimit uint64 = 65536 + if p.initialBufferLimit < desiredLimit { + p.handle.SetBufferLimit(desiredLimit) + } + return shared.HeadersStatusContinue +} + +func (p *BufferLimitFilter) OnResponseHeaders(headers shared.HeaderMap, endOfStream bool) shared.HeadersStatus { + headers.Set("x-initial-buffer-limit", strconv.FormatUint(p.initialBufferLimit, 10)) + headers.Set("x-current-buffer-limit", strconv.FormatUint(p.handle.GetBufferLimit(), 10)) + headers.Set("x-above-watermark-count", strconv.Itoa(p.aboveW)) + headers.Set("x-below-watermark-count", strconv.Itoa(p.belowW)) + return shared.HeadersStatusContinue +} + +func (p *BufferLimitFilter) OnAboveWriteBufferHighWatermark() { + p.aboveW++ +} + +func (p *BufferLimitFilter) OnBelowWriteBufferLowWatermark() { + p.belowW++ +} + // ----------------------------------------------------------------------------- // HttpStreamBasic / Bidi / Reset // ----------------------------------------------------------------------------- diff --git a/test/extensions/dynamic_modules/test_data/rust/http.rs b/test/extensions/dynamic_modules/test_data/rust/http.rs index ad889e510c0dd..55fb7bc85b8de 100644 --- a/test/extensions/dynamic_modules/test_data/rust/http.rs +++ b/test/extensions/dynamic_modules/test_data/rust/http.rs @@ -43,10 +43,16 @@ fn new_http_filter_config_fn( .expect("failed to define histogram vec"), })), "header_callbacks" => Some(Box::new(HeaderCallbacksFilterConfig {})), + "local_reply_callbacks" => Some(Box::new(LocalReplyCallbacksFilterConfig {})), + "reset_stream" => Some(Box::new(ResetStreamFilterConfig {})), + "send_go_away_and_close" => Some(Box::new(SendGoAwayAndCloseFilterConfig {})), + "recreate_stream" => Some(Box::new(RecreateStreamFilterConfig {})), + "socket_option_callbacks" => Some(Box::new(SocketOptionCallbacksFilterConfig {})), + "span_callbacks" => Some(Box::new(SpanCallbacksFilterConfig {})), + "cluster_callbacks" => Some(Box::new(ClusterCallbacksFilterConfig {})), "send_response" => Some(Box::new(SendResponseFilterConfig {})), "dynamic_metadata_callbacks" => Some(Box::new(DynamicMetadataCallbacksFilterConfig {})), "filter_state_callbacks" => Some(Box::new(FilterStateCallbacksFilterConfig {})), - "typed_filter_state_callbacks" => Some(Box::new(TypedFilterStateCallbacksFilterConfig {})), "body_callbacks" => Some(Box::new(BodyCallbacksFilterConfig {})), "config_init_failure" => None, _ => panic!("Unknown filter name: {name}"), @@ -453,6 +459,216 @@ impl HttpFilter for SendResponseFilter { } } +struct LocalReplyCallbacksFilterConfig {} + +impl HttpFilterConfig for LocalReplyCallbacksFilterConfig { + fn new_http_filter(&self, _envoy: &mut EHF) -> Box> { + Box::new(LocalReplyCallbacksFilter {}) + } +} + +struct LocalReplyCallbacksFilter {} + +impl HttpFilter for LocalReplyCallbacksFilter { + fn on_local_reply( + &mut self, + _envoy_filter: &mut EHF, + _response_code: u32, + _details: EnvoyBuffer, + _reset_imminent: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_local_reply_status { + abi::envoy_dynamic_module_type_on_http_filter_local_reply_status::ContinueAndResetStream + } +} + +struct ResetStreamFilterConfig {} + +impl HttpFilterConfig for ResetStreamFilterConfig { + fn new_http_filter(&self, _envoy: &mut EHF) -> Box> { + Box::new(ResetStreamFilter {}) + } +} + +struct ResetStreamFilter {} + +impl HttpFilter for ResetStreamFilter { + fn on_request_headers( + &mut self, + envoy_filter: &mut EHF, + _end_of_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + let _unused_reason = + abi::envoy_dynamic_module_type_http_filter_stream_reset_reason::LocalRefusedStreamReset; + envoy_filter.reset_stream( + abi::envoy_dynamic_module_type_http_filter_stream_reset_reason::LocalReset, + "details", + ); + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + } +} + +struct SendGoAwayAndCloseFilterConfig {} + +impl HttpFilterConfig for SendGoAwayAndCloseFilterConfig { + fn new_http_filter(&self, _envoy: &mut EHF) -> Box> { + Box::new(SendGoAwayAndCloseFilter {}) + } +} + +struct SendGoAwayAndCloseFilter {} + +impl HttpFilter for SendGoAwayAndCloseFilter { + fn on_request_headers( + &mut self, + envoy_filter: &mut EHF, + _end_of_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + envoy_filter.send_go_away_and_close(true); + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + } +} + +struct RecreateStreamFilterConfig {} + +impl HttpFilterConfig for RecreateStreamFilterConfig { + fn new_http_filter(&self, _envoy: &mut EHF) -> Box> { + Box::new(RecreateStreamFilter {}) + } +} + +struct RecreateStreamFilter {} + +impl HttpFilter for RecreateStreamFilter { + fn on_request_headers( + &mut self, + envoy_filter: &mut EHF, + _end_of_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + assert!( + envoy_filter.recreate_stream(Some(&[(":status", b"302"), ("location", b"/recreated"),])) + ); + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + } +} + +struct SocketOptionCallbacksFilterConfig {} + +impl HttpFilterConfig for SocketOptionCallbacksFilterConfig { + fn new_http_filter(&self, _envoy: &mut EHF) -> Box> { + Box::new(SocketOptionCallbacksFilter {}) + } +} + +struct SocketOptionCallbacksFilter {} + +impl HttpFilter for SocketOptionCallbacksFilter { + fn on_request_headers( + &mut self, + envoy_filter: &mut EHF, + _end_of_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + assert!(envoy_filter.set_socket_option_int( + 1, + 2, + abi::envoy_dynamic_module_type_socket_option_state::Prebind, + abi::envoy_dynamic_module_type_socket_direction::Upstream, + 123, + )); + assert!(envoy_filter.set_socket_option_bytes( + 3, + 4, + abi::envoy_dynamic_module_type_socket_option_state::Bound, + abi::envoy_dynamic_module_type_socket_direction::Upstream, + b"socket-bytes", + )); + assert_eq!( + envoy_filter.get_socket_option_int( + 1, + 2, + abi::envoy_dynamic_module_type_socket_option_state::Prebind, + abi::envoy_dynamic_module_type_socket_direction::Upstream, + ), + Some(123) + ); + let bytes = envoy_filter.get_socket_option_bytes( + 3, + 4, + abi::envoy_dynamic_module_type_socket_option_state::Bound, + abi::envoy_dynamic_module_type_socket_direction::Upstream, + ); + assert!(bytes.is_some()); + assert_eq!(bytes.unwrap().as_slice(), b"socket-bytes"); + envoy_filter.set_request_header("x-socket-option-callbacks", b"true"); + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + } +} + +struct SpanCallbacksFilterConfig {} + +impl HttpFilterConfig for SpanCallbacksFilterConfig { + fn new_http_filter(&self, _envoy: &mut EHF) -> Box> { + Box::new(SpanCallbacksFilter {}) + } +} + +struct SpanCallbacksFilter {} + +impl HttpFilter for SpanCallbacksFilter { + fn on_request_headers( + &mut self, + envoy_filter: &mut EHF, + _end_of_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + if let Some(span) = envoy_filter.get_active_span() { + span.set_tag("key", "value"); + span.set_operation("operation"); + span.log("event"); + span.set_sampled(true); + let _ = span.get_baggage("key"); + span.set_baggage("key", "value"); + let _ = span.get_trace_id(); + let _ = span.get_span_id(); + if let Some(mut child) = span.spawn_child("child") { + child.set_tag("child-key", "child-value"); + child.finish(); + } + } + envoy_filter.set_request_header("x-span-callbacks", b"true"); + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + } +} + +struct ClusterCallbacksFilterConfig {} + +impl HttpFilterConfig for ClusterCallbacksFilterConfig { + fn new_http_filter(&self, _envoy: &mut EHF) -> Box> { + Box::new(ClusterCallbacksFilter {}) + } +} + +struct ClusterCallbacksFilter {} + +impl HttpFilter for ClusterCallbacksFilter { + fn on_request_headers( + &mut self, + envoy_filter: &mut EHF, + _end_of_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + let cluster_name = envoy_filter.get_cluster_name(); + assert!(cluster_name.is_some()); + assert_eq!(cluster_name.unwrap().as_slice(), b"fake_cluster"); + let counts = envoy_filter.get_cluster_host_count(0); + assert!(counts.is_some()); + let counts = counts.unwrap(); + assert_eq!(counts.total, 3); + assert_eq!(counts.healthy, 2); + assert_eq!(counts.degraded, 1); + assert!(envoy_filter.set_upstream_override_host("127.0.0.1:1", false)); + envoy_filter.set_request_header("x-cluster-callbacks", b"true"); + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + } +} + /// A HTTP filter configuration that implements /// [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilterConfig`] to test the dynamic metadata related /// callbacks. @@ -795,6 +1011,18 @@ impl HttpFilter for FilterStateCallbacksFilter { envoy_filter: &mut EHF, _end_of_stream: bool, ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + let typed_before = + envoy_filter.get_filter_state_typed(b"envoy.test.http_typed_object_for_rust"); + assert!(typed_before.is_none()); + let ok = + envoy_filter.set_filter_state_typed(b"envoy.test.http_typed_object_for_rust", b"typed_value"); + assert!(ok); + let result = envoy_filter.get_filter_state_typed(b"envoy.test.http_typed_object_for_rust"); + assert!(result.is_some()); + assert_eq!(result.unwrap().as_slice(), b"typed_value"); + let ok = envoy_filter.set_filter_state_typed(b"no.such.factory", b"value"); + assert!(!ok); + envoy_filter.set_filter_state_bytes(b"req_header_key", b"req_header_value"); let filter_state = envoy_filter.get_filter_state_bytes(b"req_header_key"); assert!(filter_state.is_some()); @@ -882,49 +1110,6 @@ impl HttpFilter for FilterStateCallbacksFilter { } } -/// A HTTP filter configuration that implements -/// [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilterConfig`] to test the typed filter state -/// related callbacks. -struct TypedFilterStateCallbacksFilterConfig {} - -impl HttpFilterConfig for TypedFilterStateCallbacksFilterConfig { - fn new_http_filter(&self, _envoy: &mut EHF) -> Box> { - Box::new(TypedFilterStateCallbacksFilter {}) - } -} - -/// A HTTP filter that implements [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilter`] to test the -/// typed filter state callbacks. -struct TypedFilterStateCallbacksFilter {} - -impl HttpFilter for TypedFilterStateCallbacksFilter { - fn on_request_headers( - &mut self, - envoy_filter: &mut EHF, - _end_of_stream: bool, - ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { - // Set typed filter state using the registered test factory key. - let ok = - envoy_filter.set_filter_state_typed(b"envoy.test.http_typed_object_for_rust", b"typed_value"); - assert!(ok); - - // Read it back via the typed getter. - let result = envoy_filter.get_filter_state_typed(b"envoy.test.http_typed_object_for_rust"); - assert!(result.is_some()); - assert_eq!(result.unwrap().as_slice(), b"typed_value"); - - // Non-existing key should return None. - let result = envoy_filter.get_filter_state_typed(b"nonexistent_key"); - assert!(result.is_none()); - - // Setting with a key that has no registered factory should fail. - let ok = envoy_filter.set_filter_state_typed(b"no.such.factory", b"value"); - assert!(!ok); - - abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue - } -} - /// A HTTP filter configuration that implements /// [`envoy_proxy_dynamic_modules_rust_sdk::HttpFilterConfig`] /// to test the body related callbacks. From 14246681dce80930a04961f97bb8392946557e99 Mon Sep 17 00:00:00 2001 From: Sebastian Schepens Date: Sat, 25 Apr 2026 14:08:12 -0300 Subject: [PATCH 142/750] jwt: avoid unnecessary copy by returning early (#44601) Commit Message: When `normalize_payload_in_metadata` is unset, we're performing an unnecessary copy of the payload because we're not returning early. Additional Description: Risk Level: low Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: sschepens --- source/extensions/filters/http/jwt_authn/authenticator.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/extensions/filters/http/jwt_authn/authenticator.cc b/source/extensions/filters/http/jwt_authn/authenticator.cc index 125ce828dc1e4..dfa0e4a1286cc 100644 --- a/source/extensions/filters/http/jwt_authn/authenticator.cc +++ b/source/extensions/filters/http/jwt_authn/authenticator.cc @@ -433,6 +433,7 @@ void AuthenticatorImpl::setPayloadMetadata(const Protobuf::Struct& jwt_payload) const auto& normalize = provider.normalize_payload_in_metadata(); if (normalize.space_delimited_claims().empty()) { set_extracted_jwt_data_cb_(provider.payload_in_metadata(), jwt_payload); + return; } // Make a temporary copy to normalize the JWT struct. Protobuf::Struct out_payload = jwt_payload; From 70765d736d1e8cf86f3d140dea4a6a70e946e143 Mon Sep 17 00:00:00 2001 From: Joe Kralicky Date: Sat, 25 Apr 2026 13:12:05 -0400 Subject: [PATCH 143/750] filesystem: allow access to /dev/shm/* (#44102) Commit Message: filesystem: allow access to /dev/shm/* Additional Description: This adds `/dev/shm/*` as an allowed path prefix for the posix filesystem impl. I ran into this issue while running integration tests while having the `--sandbox_base=/dev/shm` bazel flag set, which prevented the tests from accessing any files in their own sandbox. Risk Level: low/moderate. Testing: Updated existing tests. The new assertions only run if /dev/shm actually exists, so the code is #ifdef'd out on macos in case anyone out there is running macos coverage builds. Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Joe Kralicky Signed-off-by: code Co-authored-by: code --- .../common/filesystem/posix/filesystem_impl.cc | 11 +++++++++++ test/common/filesystem/filesystem_impl_test.cc | 17 +++++++++++++++++ tools/spelling/spelling_dictionary.txt | 1 + 3 files changed, 29 insertions(+) diff --git a/source/common/filesystem/posix/filesystem_impl.cc b/source/common/filesystem/posix/filesystem_impl.cc index 7f95a490b51ab..5d94103c9fc80 100644 --- a/source/common/filesystem/posix/filesystem_impl.cc +++ b/source/common/filesystem/posix/filesystem_impl.cc @@ -367,6 +367,17 @@ bool InstanceImplPosix::illegalPath(const std::string& path) { absl::StartsWith(canonical_path.return_value_, "/proc/") || canonical_path.return_value_ == "/dev" || canonical_path.return_value_ == "/sys" || canonical_path.return_value_ == "/proc") { + +#ifdef __linux__ + // Allow /dev/shm/*, which is a de-facto standard tmpfs location on linux. A common use case is + // to set the bazel sandbox_base to /dev/shm, since /tmp is not always backed by memory. Some + // tests may then need to access files in bazel sandboxes under this directory. + if (absl::StartsWith(canonical_path.return_value_, "/dev/shm/") || + canonical_path.return_value_ == "/dev/shm") { + return false; + } +#endif + return true; } return false; diff --git a/test/common/filesystem/filesystem_impl_test.cc b/test/common/filesystem/filesystem_impl_test.cc index 536248b31ef66..dee53c1bf52a0 100644 --- a/test/common/filesystem/filesystem_impl_test.cc +++ b/test/common/filesystem/filesystem_impl_test.cc @@ -230,6 +230,23 @@ TEST_F(FileSystemImplTest, IllegalPath) { EXPECT_TRUE(file_system_.illegalPath("/dev/")); // Exception to allow opening from file descriptors. See #7258. EXPECT_FALSE(file_system_.illegalPath("/dev/fd/0")); + + if (file_system_.directoryExists("/dev/shm")) { + // Exception for /dev/shm/* + EXPECT_FALSE(file_system_.illegalPath("/dev/shm/")); + EXPECT_FALSE(file_system_.illegalPath("/dev/shm")); + EXPECT_TRUE(file_system_.illegalPath("/dev/shm/_some_non_existent_file")); + // This is not the same special case as /dev/fd; paths will actually need to exist here to be + // considered valid. + std::string shmTempFile = "/dev/shm/envoy_test_XXXXXX"; + auto fd = mkstemp(shmTempFile.data()); + ASSERT_NE(fd, -1); + auto res = file_system_.illegalPath(shmTempFile); + close(fd); + unlink(shmTempFile.c_str()); + EXPECT_FALSE(res); + } + EXPECT_TRUE(file_system_.illegalPath("/proc")); EXPECT_TRUE(file_system_.illegalPath("/proc/")); EXPECT_TRUE(file_system_.illegalPath("/sys")); diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 331ffdce67635..a0054bcb6f295 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1518,6 +1518,7 @@ timestamps timeval tmp tmpfile +tmpfs token tokenize tokenized From ec212bbb38be7a3638c7e5a909fe0fc674ee81c4 Mon Sep 17 00:00:00 2001 From: code Date: Mon, 27 Apr 2026 09:15:52 +0800 Subject: [PATCH 144/750] stats: make easier to reuse stats code (#44410) Commit Message: stats: make easier to reuse stats code Additional Description: 1. Prefer tags span rather than reference to vector for metrics creation. By this way, we can use inlined vector, array and so on rather than only vector. 2. minor refactor to make it's easier to reuse stats code if we want another custom scope implemenation. No public code API change and won't affect our forks. Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: wbpcode/wangbaiping Signed-off-by: code Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- envoy/stats/tag.h | 8 +++ source/common/stats/allocator.cc | 17 +++-- source/common/stats/allocator.h | 8 +-- source/common/stats/histogram_impl.h | 6 +- source/common/stats/isolated_store_impl.cc | 19 +++--- source/common/stats/isolated_store_impl.h | 61 ++++++++---------- source/common/stats/metric_impl.cc | 2 +- source/common/stats/metric_impl.h | 6 +- source/common/stats/tag_utility.cc | 3 +- source/common/stats/tag_utility.h | 15 +++++ source/common/stats/thread_local_store.cc | 74 +++++++++++++++------- source/common/stats/thread_local_store.h | 16 +++-- test/integration/server.h | 2 +- 13 files changed, 142 insertions(+), 95 deletions(-) diff --git a/envoy/stats/tag.h b/envoy/stats/tag.h index 9916b71f46e03..2454e7b885133 100644 --- a/envoy/stats/tag.h +++ b/envoy/stats/tag.h @@ -2,7 +2,12 @@ #include +#include "envoy/common/optref.h" + +#include "absl/container/inlined_vector.h" +#include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "absl/types/span.h" namespace Envoy { namespace Stats { @@ -28,5 +33,8 @@ using StatNameTagVector = std::vector; using StatNameTagVectorOptConstRef = absl::optional>; +using StatNameTagSpan = absl::Span; +using StatNameTagVec = absl::InlinedVector; + } // namespace Stats } // namespace Envoy diff --git a/source/common/stats/allocator.cc b/source/common/stats/allocator.cc index 3c6bee834681b..59cae0ebfff9b 100644 --- a/source/common/stats/allocator.cc +++ b/source/common/stats/allocator.cc @@ -71,7 +71,7 @@ void Allocator::debugPrint() { template class StatsSharedImpl : public MetricImpl { public: StatsSharedImpl(StatName name, Allocator& alloc, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags) + StatNameTagSpan stat_name_tags) : MetricImpl(name, tag_extracted_name, stat_name_tags, alloc.symbolTable()), alloc_(alloc) {} @@ -141,7 +141,7 @@ template class StatsSharedImpl : public MetricImpl class CounterImpl : public StatsSharedImpl { public: CounterImpl(StatName name, Allocator& alloc, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags) + StatNameTagSpan stat_name_tags) : StatsSharedImpl(name, alloc, tag_extracted_name, stat_name_tags) {} void removeFromSetLockHeld() ABSL_EXCLUSIVE_LOCKS_REQUIRED(alloc_.mutex_) override { @@ -171,7 +171,7 @@ class CounterImpl : public StatsSharedImpl { class GaugeImpl : public StatsSharedImpl { public: GaugeImpl(StatName name, Allocator& alloc, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags, ImportMode import_mode) + StatNameTagSpan stat_name_tags, ImportMode import_mode) : StatsSharedImpl(name, alloc, tag_extracted_name, stat_name_tags) { switch (import_mode) { case ImportMode::Accumulate: @@ -272,7 +272,7 @@ class GaugeImpl : public StatsSharedImpl { class TextReadoutImpl : public StatsSharedImpl { public: TextReadoutImpl(StatName name, Allocator& alloc, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags) + StatNameTagSpan stat_name_tags) : StatsSharedImpl(name, alloc, tag_extracted_name, stat_name_tags) {} void removeFromSetLockHeld() ABSL_EXCLUSIVE_LOCKS_REQUIRED(alloc_.mutex_) override { @@ -299,7 +299,7 @@ class TextReadoutImpl : public StatsSharedImpl { }; CounterSharedPtr Allocator::makeCounter(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags) { + StatNameTagSpan stat_name_tags) { Thread::LockGuard lock(mutex_); ASSERT(gauges_.find(name) == gauges_.end()); ASSERT(text_readouts_.find(name) == text_readouts_.end()); @@ -318,8 +318,7 @@ CounterSharedPtr Allocator::makeCounter(StatName name, StatName tag_extracted_na } GaugeSharedPtr Allocator::makeGauge(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags, - Gauge::ImportMode import_mode) { + StatNameTagSpan stat_name_tags, Gauge::ImportMode import_mode) { Thread::LockGuard lock(mutex_); ASSERT(counters_.find(name) == counters_.end()); ASSERT(text_readouts_.find(name) == text_readouts_.end()); @@ -339,7 +338,7 @@ GaugeSharedPtr Allocator::makeGauge(StatName name, StatName tag_extracted_name, } TextReadoutSharedPtr Allocator::makeTextReadout(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags) { + StatNameTagSpan stat_name_tags) { Thread::LockGuard lock(mutex_); ASSERT(counters_.find(name) == counters_.end()); ASSERT(gauges_.find(name) == gauges_.end()); @@ -367,7 +366,7 @@ bool Allocator::isMutexLockedForTest() { } Counter* Allocator::makeCounterInternal(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags) { + StatNameTagSpan stat_name_tags) { return new CounterImpl(name, *this, tag_extracted_name, stat_name_tags); } diff --git a/source/common/stats/allocator.h b/source/common/stats/allocator.h index 37aed8de6e1ae..42de79431813b 100644 --- a/source/common/stats/allocator.h +++ b/source/common/stats/allocator.h @@ -31,7 +31,7 @@ class Allocator { * @return CounterSharedPtr a counter. */ CounterSharedPtr makeCounter(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags); + StatNameTagSpan stat_name_tags); /** * @param name the full name of the stat. @@ -40,7 +40,7 @@ class Allocator { * @return GaugeSharedPtr a gauge. */ GaugeSharedPtr makeGauge(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags, Gauge::ImportMode import_mode); + StatNameTagSpan stat_name_tags, Gauge::ImportMode import_mode); /** * @param name the full name of the stat. @@ -49,7 +49,7 @@ class Allocator { * @return TextReadoutSharedPtr a text readout. */ TextReadoutSharedPtr makeTextReadout(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags); + StatNameTagSpan stat_name_tags); SymbolTable& symbolTable() { return symbol_table_; } const SymbolTable& constSymbolTable() const { return symbol_table_; } @@ -107,7 +107,7 @@ class Allocator { protected: virtual Counter* makeCounterInternal(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags); + StatNameTagSpan stat_name_tags); private: template friend class StatsSharedImpl; diff --git a/source/common/stats/histogram_impl.h b/source/common/stats/histogram_impl.h index 26441b09a073d..c8f121cf93ba2 100644 --- a/source/common/stats/histogram_impl.h +++ b/source/common/stats/histogram_impl.h @@ -80,8 +80,8 @@ class HistogramStatisticsImpl final : public HistogramStatistics, NonCopyable { class HistogramImplHelper : public MetricImpl { public: - HistogramImplHelper(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags, SymbolTable& symbol_table) + HistogramImplHelper(StatName name, StatName tag_extracted_name, StatNameTagSpan stat_name_tags, + SymbolTable& symbol_table) : MetricImpl(name, tag_extracted_name, stat_name_tags, symbol_table) {} HistogramImplHelper(SymbolTable& symbol_table) : MetricImpl(symbol_table) {} @@ -100,7 +100,7 @@ class HistogramImplHelper : public MetricImpl { class HistogramImpl : public HistogramImplHelper { public: HistogramImpl(StatName name, Unit unit, Store& parent, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags) + StatNameTagSpan stat_name_tags) : HistogramImplHelper(name, tag_extracted_name, stat_name_tags, parent.symbolTable()), unit_(unit), parent_(parent) {} ~HistogramImpl() override { diff --git a/source/common/stats/isolated_store_impl.cc b/source/common/stats/isolated_store_impl.cc index a5f6a000e920e..fc3e4b17c8fff 100644 --- a/source/common/stats/isolated_store_impl.cc +++ b/source/common/stats/isolated_store_impl.cc @@ -18,33 +18,30 @@ IsolatedStoreImpl::IsolatedStoreImpl(std::unique_ptr&& symbol_table symbol_table_storage_ = std::move(symbol_table); } -static StatNameTagVector tagVectorFromOpt(StatNameTagVectorOptConstRef tags) { - return tags ? tags->get() : StatNameTagVector{}; +static StatNameTagSpan tagSpanFromOpt(absl::optional tags) { + return tags ? tags.value() : StatNameTagSpan{}; } IsolatedStoreImpl::IsolatedStoreImpl(SymbolTable& symbol_table) : alloc_(symbol_table), - counters_([this](const TagUtility::TagStatNameJoiner& joiner, - StatNameTagVectorOptConstRef tags) -> CounterSharedPtr { + counters_([this](const TagUtility::TagStatNameJoiner& joiner) -> CounterSharedPtr { return alloc_.makeCounter(joiner.nameWithTags(), joiner.tagExtractedName(), - tagVectorFromOpt(tags)); + tagSpanFromOpt(joiner.effectiveTags())); }), - gauges_([this](const TagUtility::TagStatNameJoiner& joiner, StatNameTagVectorOptConstRef tags, + gauges_([this](const TagUtility::TagStatNameJoiner& joiner, Gauge::ImportMode import_mode) -> GaugeSharedPtr { return alloc_.makeGauge(joiner.nameWithTags(), joiner.tagExtractedName(), - tagVectorFromOpt(tags), import_mode); + tagSpanFromOpt(joiner.effectiveTags()), import_mode); }), histograms_([this](const TagUtility::TagStatNameJoiner& joiner, - StatNameTagVectorOptConstRef tags, Histogram::Unit unit) -> HistogramSharedPtr { return {new HistogramImpl(joiner.nameWithTags(), unit, *this, joiner.tagExtractedName(), - tagVectorFromOpt(tags))}; + tagSpanFromOpt(joiner.effectiveTags()))}; }), text_readouts_([this](const TagUtility::TagStatNameJoiner& joiner, - StatNameTagVectorOptConstRef tags, TextReadout::Type) -> TextReadoutSharedPtr { return alloc_.makeTextReadout(joiner.nameWithTags(), joiner.tagExtractedName(), - tagVectorFromOpt(tags)); + tagSpanFromOpt(joiner.effectiveTags())); }), null_counter_(symbol_table), null_gauge_(symbol_table), null_histogram_(symbol_table), null_text_readout_(symbol_table) {} diff --git a/source/common/stats/isolated_store_impl.h b/source/common/stats/isolated_store_impl.h index fedaa5da41f37..32a1ae6f10706 100644 --- a/source/common/stats/isolated_store_impl.h +++ b/source/common/stats/isolated_store_impl.h @@ -141,17 +141,14 @@ class IsolatedStoreImpl : public Store { */ template class IsolatedStatsCache { public: - using CounterAllocator = std::function( - const TagUtility::TagStatNameJoiner& joiner, StatNameTagVectorOptConstRef tags)>; - using GaugeAllocator = - std::function(const TagUtility::TagStatNameJoiner& joiner, - StatNameTagVectorOptConstRef tags, Gauge::ImportMode)>; - using HistogramAllocator = - std::function(const TagUtility::TagStatNameJoiner& joiner, - StatNameTagVectorOptConstRef tags, Histogram::Unit)>; - using TextReadoutAllocator = - std::function(const TagUtility::TagStatNameJoiner& joiner, - StatNameTagVectorOptConstRef tags, TextReadout::Type)>; + using CounterAllocator = + std::function(const TagUtility::TagStatNameJoiner& joiner)>; + using GaugeAllocator = std::function( + const TagUtility::TagStatNameJoiner& joiner, Gauge::ImportMode)>; + using HistogramAllocator = std::function( + const TagUtility::TagStatNameJoiner& joiner, Histogram::Unit)>; + using TextReadoutAllocator = std::function( + const TagUtility::TagStatNameJoiner& joiner, TextReadout::Type)>; using BaseOptConstRef = absl::optional>; IsolatedStatsCache(CounterAllocator alloc) : counter_alloc_(alloc) {} @@ -159,9 +156,8 @@ class IsolatedStoreImpl : public Store { IsolatedStatsCache(HistogramAllocator alloc) : histogram_alloc_(alloc) {} IsolatedStatsCache(TextReadoutAllocator alloc) : text_readout_alloc_(alloc) {} - OptRef get(StatName prefix, StatName basename, StatNameTagVectorOptConstRef tags, - SymbolTable& symbol_table, OptRef matcher = {}) { - TagUtility::TagStatNameJoiner joiner(prefix, basename, tags, symbol_table); + OptRef get(const TagUtility::TagStatNameJoiner& joiner, + OptRef matcher = {}) { StatName name = joiner.nameWithTags(); // If we have a matcher and it rejects this stat, we return nullopt. @@ -174,15 +170,13 @@ class IsolatedStoreImpl : public Store { return *stat->second; } - RefcountPtr new_stat = counter_alloc_(joiner, tags); + RefcountPtr new_stat = counter_alloc_(joiner); stats_.emplace(new_stat->statName(), new_stat); return *new_stat; } - OptRef get(StatName prefix, StatName basename, StatNameTagVectorOptConstRef tags, - SymbolTable& symbol_table, Gauge::ImportMode import_mode, + OptRef get(const TagUtility::TagStatNameJoiner& joiner, Gauge::ImportMode import_mode, OptRef matcher = {}) { - TagUtility::TagStatNameJoiner joiner(prefix, basename, tags, symbol_table); StatName name = joiner.nameWithTags(); // If we have a matcher and it rejects this stat, we return nullopt. @@ -196,15 +190,13 @@ class IsolatedStoreImpl : public Store { return *stat->second; } - RefcountPtr new_stat = gauge_alloc_(joiner, tags, import_mode); + RefcountPtr new_stat = gauge_alloc_(joiner, import_mode); stats_.emplace(new_stat->statName(), new_stat); return *new_stat; } - OptRef get(StatName prefix, StatName basename, StatNameTagVectorOptConstRef tags, - SymbolTable& symbol_table, Histogram::Unit unit, + OptRef get(const TagUtility::TagStatNameJoiner& joiner, Histogram::Unit unit, OptRef matcher = {}) { - TagUtility::TagStatNameJoiner joiner(prefix, basename, tags, symbol_table); StatName name = joiner.nameWithTags(); // If we have a matcher and it rejects this stat, we return nullopt. @@ -217,15 +209,13 @@ class IsolatedStoreImpl : public Store { return *stat->second; } - RefcountPtr new_stat = histogram_alloc_(joiner, tags, unit); + RefcountPtr new_stat = histogram_alloc_(joiner, unit); stats_.emplace(new_stat->statName(), new_stat); return *new_stat; } - OptRef get(StatName prefix, StatName basename, StatNameTagVectorOptConstRef tags, - SymbolTable& symbol_table, TextReadout::Type type, + OptRef get(const TagUtility::TagStatNameJoiner& joiner, TextReadout::Type type, OptRef matcher = {}) { - TagUtility::TagStatNameJoiner joiner(prefix, basename, tags, symbol_table); StatName name = joiner.nameWithTags(); // If we have a matcher and it rejects this stat, we return nullopt. @@ -238,7 +228,7 @@ class IsolatedStoreImpl : public Store { return *stat->second; } - RefcountPtr new_stat = text_readout_alloc_(joiner, tags, type); + RefcountPtr new_stat = text_readout_alloc_(joiner, type); stats_.emplace(new_stat->statName(), new_stat); return *new_stat; } @@ -288,6 +278,7 @@ class IsolatedStoreImpl : public Store { }; friend class IsolatedScopeImpl; + friend class ElementScopeImpl; IsolatedStoreImpl(std::unique_ptr&& symbol_table); @@ -333,8 +324,8 @@ class IsolatedScopeImpl : public Scope { Counter& counterFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef tags) override { const OptRef matcher = makeOptRefFromPtr(scope_matcher_.get()); - return store_.counters_.get(prefix(), name, tags, symbolTable(), matcher) - .value_or(store_.null_counter_); + const TagUtility::TagStatNameJoiner joiner(prefix(), name, tags, symbolTable()); + return store_.counters_.get(joiner, matcher).value_or(store_.null_counter_); } ScopeSharedPtr createScope(const std::string& name, bool evictable = false, const ScopeStatsLimitSettings& limits = {}, @@ -345,7 +336,8 @@ class IsolatedScopeImpl : public Scope { Gauge& gaugeFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef tags, Gauge::ImportMode import_mode) override { const OptRef matcher = makeOptRefFromPtr(scope_matcher_.get()); - auto gauge = store_.gauges_.get(prefix(), name, tags, symbolTable(), import_mode, matcher); + const TagUtility::TagStatNameJoiner joiner(prefix(), name, tags, symbolTable()); + auto gauge = store_.gauges_.get(joiner, import_mode, matcher); if (!gauge.has_value()) { return store_.null_gauge_; } @@ -355,14 +347,14 @@ class IsolatedScopeImpl : public Scope { Histogram& histogramFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef tags, Histogram::Unit unit) override { const OptRef matcher = makeOptRefFromPtr(scope_matcher_.get()); - return store_.histograms_.get(prefix(), name, tags, symbolTable(), unit, matcher) - .value_or(store_.null_histogram_); + const TagUtility::TagStatNameJoiner joiner(prefix(), name, tags, symbolTable()); + return store_.histograms_.get(joiner, unit, matcher).value_or(store_.null_histogram_); } TextReadout& textReadoutFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef tags) override { const OptRef matcher = makeOptRefFromPtr(scope_matcher_.get()); - return store_.text_readouts_ - .get(prefix(), name, tags, symbolTable(), TextReadout::Type::Default, matcher) + const TagUtility::TagStatNameJoiner joiner(prefix(), name, tags, symbolTable()); + return store_.text_readouts_.get(joiner, TextReadout::Type::Default, matcher) .value_or(store_.null_text_readout_); } CounterOptConstRef findCounter(StatName name) const override { @@ -413,7 +405,6 @@ class IsolatedScopeImpl : public Scope { protected: void addScopeToStore(const ScopeSharedPtr& scope) { store_.scopes_.push_back(scope); } -private: template IterateFn iterFilter(const IterateFn& fn) const { // We determine here what's in the scope by looking at name // prefixes. Strictly speaking this is not correct, as the same stat can be diff --git a/source/common/stats/metric_impl.cc b/source/common/stats/metric_impl.cc index 7aa757481385c..f05f774369a8b 100644 --- a/source/common/stats/metric_impl.cc +++ b/source/common/stats/metric_impl.cc @@ -15,7 +15,7 @@ MetricHelper::~MetricHelper() { } MetricHelper::MetricHelper(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags, SymbolTable& symbol_table) { + StatNameTagSpan stat_name_tags, SymbolTable& symbol_table) { // Encode all the names and tags into transient storage so we can count the // required bytes. 2 is added to account for the name and tag_extracted_name, // and we multiply the number of tags by 2 to account for the name and value diff --git a/source/common/stats/metric_impl.h b/source/common/stats/metric_impl.h index 937b9f5ba4bb5..bc823671ddb15 100644 --- a/source/common/stats/metric_impl.h +++ b/source/common/stats/metric_impl.h @@ -20,7 +20,7 @@ namespace Stats { */ class MetricHelper { public: - MetricHelper(StatName name, StatName tag_extracted_name, const StatNameTagVector& stat_name_tags, + MetricHelper(StatName name, StatName tag_extracted_name, StatNameTagSpan stat_name_tags, SymbolTable& symbol_table); ~MetricHelper(); @@ -79,13 +79,13 @@ using StatSet = absl::flat_hash_set class MetricImpl : public BaseClass { public: - MetricImpl(StatName name, StatName tag_extracted_name, const StatNameTagVector& stat_name_tags, + MetricImpl(StatName name, StatName tag_extracted_name, StatNameTagSpan stat_name_tags, SymbolTable& symbol_table) : helper_(name, tag_extracted_name, stat_name_tags, symbol_table) {} // Empty construction of a MetricImpl; used for null stats. explicit MetricImpl(SymbolTable& symbol_table) - : MetricImpl(StatName(), StatName(), StatNameTagVector(), symbol_table) {} + : MetricImpl(StatName(), StatName(), {}, symbol_table) {} TagVector tags() const override { return helper_.tags(constSymbolTable()); } StatName statName() const override { return helper_.statName(); } diff --git a/source/common/stats/tag_utility.cc b/source/common/stats/tag_utility.cc index c86bec30b9120..845f27387a041 100644 --- a/source/common/stats/tag_utility.cc +++ b/source/common/stats/tag_utility.cc @@ -11,7 +11,8 @@ namespace TagUtility { TagStatNameJoiner::TagStatNameJoiner(StatName prefix, StatName stat_name, StatNameTagVectorOptConstRef stat_name_tags, - SymbolTable& symbol_table) { + SymbolTable& symbol_table) + : stat_name_tags_(stat_name_tags) { prefix_storage_ = symbol_table.join({prefix, stat_name}); tag_extracted_name_ = StatName(prefix_storage_.get()); diff --git a/source/common/stats/tag_utility.h b/source/common/stats/tag_utility.h index c2a87ea582eab..fbe8c2f7044b1 100644 --- a/source/common/stats/tag_utility.h +++ b/source/common/stats/tag_utility.h @@ -34,6 +34,17 @@ class TagStatNameJoiner { */ StatName tagExtractedName() const { return tag_extracted_name_; } + /** + * @return the optional effective tags. + */ + absl::optional effectiveTags() const { + if (stat_name_tags_) { + return stat_name_tags_->get(); + } else { + return {}; + } + } + private: // TODO(snowp): This isn't really "tag extracted", but we'll use this for the sake of consistency // until we can change the naming convention throughout. @@ -42,6 +53,10 @@ class TagStatNameJoiner { SymbolTable::StoragePtr full_name_storage_; StatName name_with_tags_; + // The tags are provided to the constructor. The TagStatNameJoiner must not outlive the + // tags; therefore, it is safe to keep a reference here. + StatNameTagVectorOptConstRef stat_name_tags_; + SymbolTable::StoragePtr joinNameAndTags(StatName name, const StatNameTagVector& stat_name_tags, SymbolTable& symbol_table); }; diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index 7b5463ac631d6..eed692fd24ad0 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -433,15 +433,19 @@ ThreadLocalStoreImpl::ScopeImpl::~ScopeImpl() { // converting them to StatName. Making the tag extraction optional within this class simplifies the // RAII ergonomics (as opposed to making the construction of this object conditional). // -// The StatNameTagVector returned by this object will be valid as long as this object is in scope +// The StatNameTagSpan returned by this object will be valid as long as this object is in scope // and the provided stat_name_tags are valid. // // When tag extraction is not done, this class is just a passthrough for the provided name/tags. class StatNameTagHelper { public: StatNameTagHelper(ThreadLocalStoreImpl& tls, StatName name, - const absl::optional& stat_name_tags) - : pool_(tls.symbolTable()), stat_name_tags_(stat_name_tags.value_or(StatNameTagVector())) { + absl::optional stat_name_tags) + : pool_(tls.symbolTable()) { + if (stat_name_tags.has_value()) { + stat_name_tags_.assign(stat_name_tags->begin(), stat_name_tags->end()); + } + if (!stat_name_tags) { TagVector tags; tag_extracted_name_ = @@ -459,12 +463,12 @@ class StatNameTagHelper { } } - const StatNameTagVector& statNameTags() const { return stat_name_tags_; } + StatNameTagSpan statNameTags() const { return stat_name_tags_; } StatName tagExtractedName() const { return tag_extracted_name_; } private: StatNamePool pool_; - StatNameTagVector stat_name_tags_; + StatNameTagVec stat_name_tags_; StatName tag_extracted_name_; }; @@ -499,8 +503,7 @@ bool ThreadLocalStoreImpl::checkAndRememberRejection(StatName name, template StatType& ThreadLocalStoreImpl::ScopeImpl::safeMakeStat( - StatName full_stat_name, StatName name_no_tags, - const absl::optional& stat_name_tags, + StatName full_stat_name, StatName name_no_tags, absl::optional stat_name_tags, StatNameHashMap>& central_cache_map, StatsMatcher::FastResult fast_reject_result, StatNameStorageSet& central_rejected_stats, MakeStatFn make_stat, StatRefMap* tls_cache, @@ -575,7 +578,13 @@ Counter& ThreadLocalStoreImpl::ScopeImpl::counterFromStatNameWithTags( } // Determine the final name based on the prefix and the passed name. - TagUtility::TagStatNameJoiner joiner(prefix_.statName(), name, stat_name_tags, symbolTable()); + const TagUtility::TagStatNameJoiner joiner(prefix_.statName(), name, stat_name_tags, + symbolTable()); + return getOrCreateCounterBase(joiner); +} + +Counter& ThreadLocalStoreImpl::ScopeImpl::getOrCreateCounterBase( + const TagUtility::TagStatNameJoiner& joiner) { Stats::StatName final_stat_name = joiner.nameWithTags(); StatsMatcher::FastResult fast_reject_result = scopeFastRejects(final_stat_name); @@ -595,12 +604,10 @@ Counter& ThreadLocalStoreImpl::ScopeImpl::counterFromStatNameWithTags( const CentralCacheEntrySharedPtr& central_cache = centralCacheNoThreadAnalysis(); return safeMakeStat( - final_stat_name, joiner.tagExtractedName(), stat_name_tags, central_cache->counters_, + final_stat_name, joiner.tagExtractedName(), joiner.effectiveTags(), central_cache->counters_, fast_reject_result, central_cache->rejected_stats_, - [](Allocator& allocator, StatName name, StatName tag_extracted_name, - const StatNameTagVector& tags) -> CounterSharedPtr { - return allocator.makeCounter(name, tag_extracted_name, tags); - }, + [](Allocator& allocator, StatName name, StatName tag_extracted_name, StatNameTagSpan tags) + -> CounterSharedPtr { return allocator.makeCounter(name, tag_extracted_name, tags); }, tls_cache, tls_rejected_stats, parent_.null_counter_); } @@ -626,10 +633,18 @@ Gauge& ThreadLocalStoreImpl::ScopeImpl::gaugeFromStatNameWithTags( if (scopeRejectsAll() && import_mode != Gauge::ImportMode::HiddenAccumulate) { return parent_.null_gauge_; } - // See comments in counter(). There is no super clean way (via templates or otherwise) to // share this code so I'm leaving it largely duplicated for now. - TagUtility::TagStatNameJoiner joiner(prefix_.statName(), name, stat_name_tags, symbolTable()); + const TagUtility::TagStatNameJoiner joiner(prefix_.statName(), name, stat_name_tags, + symbolTable()); + + return getOrCreateGaugeBase(joiner, import_mode); +} + +Gauge& +ThreadLocalStoreImpl::ScopeImpl::getOrCreateGaugeBase(const TagUtility::TagStatNameJoiner& joiner, + Gauge::ImportMode import_mode) { + StatName final_stat_name = joiner.nameWithTags(); StatsMatcher::FastResult fast_reject_result; @@ -652,10 +667,10 @@ Gauge& ThreadLocalStoreImpl::ScopeImpl::gaugeFromStatNameWithTags( const CentralCacheEntrySharedPtr& central_cache = centralCacheNoThreadAnalysis(); Gauge& gauge = safeMakeStat( - final_stat_name, joiner.tagExtractedName(), stat_name_tags, central_cache->gauges_, + final_stat_name, joiner.tagExtractedName(), joiner.effectiveTags(), central_cache->gauges_, fast_reject_result, central_cache->rejected_stats_, [import_mode](Allocator& allocator, StatName name, StatName tag_extracted_name, - const StatNameTagVector& tags) -> GaugeSharedPtr { + StatNameTagSpan tags) -> GaugeSharedPtr { return allocator.makeGauge(name, tag_extracted_name, tags, import_mode); }, tls_cache, tls_rejected_stats, parent_.null_gauge_); @@ -674,6 +689,13 @@ Histogram& ThreadLocalStoreImpl::ScopeImpl::histogramFromStatNameWithTags( // See comments in counter(). There is no super clean way (via templates or otherwise) to // share this code so I'm leaving it largely duplicated for now. TagUtility::TagStatNameJoiner joiner(prefix_.statName(), name, stat_name_tags, symbolTable()); + + return getOrCreateHistogramBase(joiner, unit); +} + +Histogram& ThreadLocalStoreImpl::ScopeImpl::getOrCreateHistogramBase( + const TagUtility::TagStatNameJoiner& joiner, Histogram::Unit unit) { + StatName final_stat_name = joiner.nameWithTags(); StatsMatcher::FastResult fast_reject_result = scopeFastRejects(final_stat_name); @@ -707,7 +729,7 @@ Histogram& ThreadLocalStoreImpl::ScopeImpl::histogramFromStatNameWithTags( effectiveMatcher())) { return parent_.null_histogram_; } else { - StatNameTagHelper tag_helper(parent_, joiner.tagExtractedName(), stat_name_tags); + StatNameTagHelper tag_helper(parent_, joiner.tagExtractedName(), joiner.effectiveTags()); ConstSupportedBuckets* buckets = nullptr; const auto string_stat_name = symbolTable().toString(final_stat_name); @@ -757,6 +779,12 @@ TextReadout& ThreadLocalStoreImpl::ScopeImpl::textReadoutFromStatNameWithTags( // Determine the final name based on the prefix and the passed name. TagUtility::TagStatNameJoiner joiner(prefix_.statName(), name, stat_name_tags, symbolTable()); + + return getOrCreateTextReadoutBase(joiner); +} + +TextReadout& ThreadLocalStoreImpl::ScopeImpl::getOrCreateTextReadoutBase( + const TagUtility::TagStatNameJoiner& joiner) { Stats::StatName final_stat_name = joiner.nameWithTags(); StatsMatcher::FastResult fast_reject_result = scopeFastRejects(final_stat_name); @@ -776,10 +804,10 @@ TextReadout& ThreadLocalStoreImpl::ScopeImpl::textReadoutFromStatNameWithTags( const CentralCacheEntrySharedPtr& central_cache = centralCacheNoThreadAnalysis(); return safeMakeStat( - final_stat_name, joiner.tagExtractedName(), stat_name_tags, central_cache->text_readouts_, - fast_reject_result, central_cache->rejected_stats_, + final_stat_name, joiner.tagExtractedName(), joiner.effectiveTags(), + central_cache->text_readouts_, fast_reject_result, central_cache->rejected_stats_, [](Allocator& allocator, StatName name, StatName tag_extracted_name, - const StatNameTagVector& tags) -> TextReadoutSharedPtr { + StatNameTagSpan tags) -> TextReadoutSharedPtr { return allocator.makeTextReadout(name, tag_extracted_name, tags); }, tls_cache, tls_rejected_stats, parent_.null_text_readout_); @@ -848,7 +876,7 @@ Histogram& ThreadLocalStoreImpl::tlsHistogram(ParentHistogramImpl& parent, uint6 ThreadLocalHistogramImpl::ThreadLocalHistogramImpl(StatName name, Histogram::Unit unit, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags, + StatNameTagSpan stat_name_tags, SymbolTable& symbol_table, absl::optional bins) : HistogramImplHelper(name, tag_extracted_name, stat_name_tags, symbol_table), unit_(unit), @@ -878,7 +906,7 @@ void ThreadLocalHistogramImpl::merge(histogram_t* target) { ParentHistogramImpl::ParentHistogramImpl(StatName name, Histogram::Unit unit, ThreadLocalStoreImpl& thread_local_store, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags, + StatNameTagSpan stat_name_tags, ConstSupportedBuckets& supported_buckets, absl::optional bins, uint64_t id) : MetricImpl(name, tag_extracted_name, stat_name_tags, thread_local_store.symbolTable()), diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index a4a04f73a834e..e825c20a50085 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -19,6 +19,7 @@ #include "source/common/stats/null_gauge.h" #include "source/common/stats/null_text_readout.h" #include "source/common/stats/symbol_table.h" +#include "source/common/stats/tag_utility.h" #include "source/common/stats/utility.h" #include "absl/container/flat_hash_map.h" @@ -35,7 +36,7 @@ namespace Stats { class ThreadLocalHistogramImpl : public HistogramImplHelper { public: ThreadLocalHistogramImpl(StatName name, Histogram::Unit unit, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags, SymbolTable& symbol_table, + StatNameTagSpan stat_name_tags, SymbolTable& symbol_table, absl::optional bins); ~ThreadLocalHistogramImpl() override; @@ -85,7 +86,7 @@ class ThreadLocalStoreImpl; class ParentHistogramImpl : public MetricImpl { public: ParentHistogramImpl(StatName name, Histogram::Unit unit, ThreadLocalStoreImpl& parent, - StatName tag_extracted_name, const StatNameTagVector& stat_name_tags, + StatName tag_extracted_name, StatNameTagSpan stat_name_tags, ConstSupportedBuckets& supported_buckets, absl::optional bins, uint64_t id); ~ParentHistogramImpl() override; @@ -331,6 +332,13 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo return textReadoutFromStatName(storage.statName()); } + Counter& getOrCreateCounterBase(const TagUtility::TagStatNameJoiner& joiner); + Gauge& getOrCreateGaugeBase(const TagUtility::TagStatNameJoiner& joiner, + Gauge::ImportMode import_mode); + Histogram& getOrCreateHistogramBase(const TagUtility::TagStatNameJoiner& joiner, + Histogram::Unit unit); + TextReadout& getOrCreateTextReadoutBase(const TagUtility::TagStatNameJoiner& joiner); + template bool iterHelper(StatFn fn, const StatMap& map) const { for (auto& iter : map) { if (!fn(iter.second)) { @@ -387,7 +395,7 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo template using MakeStatFn = std::function( - Allocator&, StatName name, StatName tag_extracted_name, const StatNameTagVector& tags)>; + Allocator&, StatName name, StatName tag_extracted_name, StatNameTagSpan tags)>; /** * Makes a stat either by looking it up in the central cache, @@ -404,7 +412,7 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo */ template StatType& safeMakeStat(StatName full_stat_name, StatName name_no_tags, - const absl::optional& stat_name_tags, + absl::optional stat_name_tags, StatNameHashMap>& central_cache_map, StatsMatcher::FastResult fast_reject_result, StatNameStorageSet& central_rejected_stats, diff --git a/test/integration/server.h b/test/integration/server.h index dde90107a4225..eb5eb9ece35df 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -246,7 +246,7 @@ class NotifyingAllocator : public Stats::Allocator { protected: Stats::Counter* makeCounterInternal(StatName name, StatName tag_extracted_name, - const StatNameTagVector& stat_name_tags) override { + StatNameTagSpan stat_name_tags) override { Stats::Counter* counter = new NotifyingCounter( Stats::Allocator::makeCounterInternal(name, tag_extracted_name, stat_name_tags), mutex_, condvar_); From 6978ace3e267a5a632f646d42111c08e2343dd77 Mon Sep 17 00:00:00 2001 From: William Dauchy Date: Mon, 27 Apr 2026 12:47:22 +0200 Subject: [PATCH 145/750] contrib: fix ambiguous select for aws-lc on aarch64 (#44661) Commit Message: Building contrib targets with ``--config=aws-lc-fips`` on aarch64 fails with: ERROR: contrib/exe/BUILD:25:16: Illegal ambiguous match on configurable attribute "deps" in //contrib/exe:envoy-static: //bazel:linux_aarch64 //bazel:using_aws_lc Multiple matches are not allowed unless one is unambiguously more specialized or they resolve to the same value. Both ``//bazel:linux_aarch64`` and ``//bazel:using_aws_lc`` match in ``SELECTED_CONTRIB_EXTENSIONS``, and Bazel does not specialize between a ``config_setting_group`` and a sibling ``config_setting`` (bazelbuild/bazel#16139), so the keys must be made disjoint. The ``AWS_LC_SKIP_CONTRIB_TARGETS`` list is in fact the x86-only delta over the platform skip lists: the Intel-specific contribs ``cryptomb``, ``qat``, ``qatzip`` and ``qatzstd`` are already in ``ARM64_SKIP_CONTRIB_TARGETS`` and ``PPC_SKIP_CONTRIB_TARGETS``, and ``kae`` is in ``X86_SKIP_CONTRIB_TARGETS``. The ``using_aws_lc`` branch only does real work for ``x86_64 + aws-lc``; on ``arm64 + aws-lc`` and ``ppc + aws-lc`` the platform branches already exclude the same set. The ambiguous-match was therefore Bazel correctly flagging that the key was under-specified for what it actually meant. Mirroring the approach taken by #32382 (which fixed the analogous ambiguity for ``boringssl_fips``), this restricts the aws-lc branch of the select to ``linux_x86_64`` via a new ``using_aws_lc_on_linux_x86_64`` config setting group. The aarch64 aws-lc build then falls through to the ``linux_aarch64`` branch and uses ``ARM64_SKIP_CONTRIB_TARGETS``, matching the behaviour for non-fips aarch64 builds. The new setting group is defined in ``contrib/BUILD`` (next to its only consumer, ``SELECTED_CONTRIB_EXTENSIONS``) rather than in ``bazel/BUILD``, to keep the disambiguation tightly scoped to contrib. The base ``//bazel:using_aws_lc`` config_setting is left unchanged; it is still used by ``source/common/version/BUILD`` to pick the FIPS version string for any-arch aws-lc builds. Risk Level: low (build configuration only). Testing: ``bazel build --config=aws-lc-fips //contrib/exe:envoy-static`` on linux/aarch64 (previously failed with the ambiguous-match error, now configures through to compilation). Docs Changes: none. Release Notes: added ``bug_fixes`` entry. Platform Specific Features: Signed-off-by: William Dauchy --- changelogs/current.yaml | 5 +++++ contrib/BUILD | 16 ++++++++++++++++ contrib/all_contrib_extensions.bzl | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 27855c0fd9d53..c3bcbfc20c4b9 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,11 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: build + change: | + Fixed ``Illegal ambiguous match`` error when building contrib targets with ``--config=aws-lc-fips`` + on aarch64 by restricting the ``using_aws_lc`` branch of ``SELECTED_CONTRIB_EXTENSIONS`` to + ``linux_x86_64``. Mirrors the approach taken by #32382 for ``boringssl_fips``. - area: dynamic_modules change: | Fixed a crashing bug in the HTTP filter when a stream was already above the downstream write-buffer diff --git a/contrib/BUILD b/contrib/BUILD index 1a845f0df38d5..3b31dcffb35e5 100644 --- a/contrib/BUILD +++ b/contrib/BUILD @@ -1,3 +1,4 @@ +load("@bazel_skylib//lib:selects.bzl", "selects") load("@envoy_toolshed//:macros.bzl", "json_data") load("//bazel:envoy_build_system.bzl", "envoy_cc_test_library", "envoy_contrib_package") load(":all_contrib_extensions.bzl", "SELECTED_CONTRIB_EXTENSIONS") @@ -7,6 +8,21 @@ licenses(["notice"]) # Apache 2 envoy_contrib_package() +# Disambiguation for SELECTED_CONTRIB_EXTENSIONS' select, where both +# "//bazel:linux_aarch64" and "//bazel:using_aws_lc" otherwise match for an +# arm64 aws-lc build. Restricting the aws-lc branch to x86_64 lets the +# aarch64 branch win on arm64; arm64 + ppc64le aws-lc builds then fall through +# to their platform skip lists, which already exclude the Intel-specific +# contribs that AWS-LC cannot build. +selects.config_setting_group( + name = "using_aws_lc_on_linux_x86_64", + match_all = [ + "//bazel:using_aws_lc", + "//bazel:linux_x86_64", + ], + visibility = ["//visibility:public"], +) + exports_files([ "extensions_metadata.yaml", "contrib_build_config.bzl", diff --git a/contrib/all_contrib_extensions.bzl b/contrib/all_contrib_extensions.bzl index 2e2928c4e744e..bb5a765ba5eda 100644 --- a/contrib/all_contrib_extensions.bzl +++ b/contrib/all_contrib_extensions.bzl @@ -55,7 +55,7 @@ def envoy_all_contrib_extensions(denylist = []): SELECTED_CONTRIB_EXTENSIONS = select({ "//bazel:linux_aarch64": envoy_all_contrib_extensions(ARM64_SKIP_CONTRIB_TARGETS), "//bazel:linux_ppc": envoy_all_contrib_extensions(PPC_SKIP_CONTRIB_TARGETS), - "//bazel:using_aws_lc": envoy_all_contrib_extensions(AWS_LC_SKIP_CONTRIB_TARGETS), + "//contrib:using_aws_lc_on_linux_x86_64": envoy_all_contrib_extensions(AWS_LC_SKIP_CONTRIB_TARGETS), "//bazel:using_boringssl_fips": envoy_all_contrib_extensions(BORINGSSL_FIPS_SKIP_CONTRIB_TARGETS), "//conditions:default": envoy_all_contrib_extensions(X86_SKIP_CONTRIB_TARGETS), }) From 3c9f5fca3f1769b0dd5c60bd82dbce4135cfc5b0 Mon Sep 17 00:00:00 2001 From: ovadiagal <102482702+ovadiagal@users.noreply.github.com> Date: Mon, 27 Apr 2026 11:00:57 -0400 Subject: [PATCH 146/750] dym: fix zig cross compilation (#44656) The directive `#cgo linux LDFLAGS: -Wl,--unresolved-symbols=ignore-all` passes the `--unresolved-symbols=ignore-all` flag to the linker when compiling Go dynamic modules. This flag is not supported in zig: https://github.com/ziglang/zig/issues/21007. When trying to bump [dynamic-modules-examples to v1.38.0](https://github.com/envoyproxy/dynamic-modules-examples/blob/main/Dockerfile#L45-L46): ``` ------ 23.81 error: unsupported linker arg: --unresolved-symbols 23.81 ------ Dockerfile:45 -------------------- 43 | COPY ./go /build 44 | WORKDIR /build 45 | >>> RUN CC="zig cc -target aarch64-linux-gnu" CXX="zig c++ -target aarch64-linux-gnu" CGO_ENABLED=1 GOARCH=arm64 go build -buildmode=c-shared -o /build/arm64_libgo_module.so . 46 | RUN CC="zig cc -target x86_64-linux-gnu" CXX="zig c++ -target x86_64-linux-gnu" CGO_ENABLED=1 GOARCH=amd64 go build -buildmode=c-shared -o /build/amd64_libgo_module.so . 47 | -------------------- ``` Commit Message: dym: fix zig cross compilation Additional Description: Risk Level: Low Testing: N/A Docs Changes: N/A Release Notes: N/A Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Gal Ovadia Signed-off-by: ovadiagal <102482702+ovadiagal@users.noreply.github.com> --- source/extensions/dynamic_modules/sdk/go/abi/internal.go | 1 - 1 file changed, 1 deletion(-) diff --git a/source/extensions/dynamic_modules/sdk/go/abi/internal.go b/source/extensions/dynamic_modules/sdk/go/abi/internal.go index 076b50b5e3afa..d0dd58b972607 100644 --- a/source/extensions/dynamic_modules/sdk/go/abi/internal.go +++ b/source/extensions/dynamic_modules/sdk/go/abi/internal.go @@ -2,7 +2,6 @@ package abi /* #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup -#cgo linux LDFLAGS: -Wl,--unresolved-symbols=ignore-all #include #include #include From 2d8c8d0396da63ab875540f95959cc34a742add8 Mon Sep 17 00:00:00 2001 From: code Date: Tue, 28 Apr 2026 02:49:23 +0800 Subject: [PATCH 147/750] router: optimize router to avoid unnecessary headers/trailers copy (#44323) Commit Message: router: optimize router to avoid unnecessary headers/trailers copy Additional Description: This initially is part of https://github.com/envoyproxy/envoy/pull/44305 but I found I forgot to push it and this deserve another PR anyway. Risk Level: low. Testing: n/a. Docs Changes: n/a. Release Notes: n/a. Platform Specific Features: n/a. --------- Signed-off-by: wbpcode/wangbaiping Signed-off-by: wbpcode --- source/common/router/router.cc | 71 ++++++++++++++++++++++++++-------- source/common/router/router.h | 11 +++--- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/source/common/router/router.cc b/source/common/router/router.cc index cb61c97f146ae..582943f204a3c 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -837,9 +837,11 @@ bool Filter::continueDecodeHeaders(Upstream::ThreadLocalCluster* cluster, createRetryState(*getEffectiveRetryPolicy(), headers, *cluster_, config_->factory_context_, callbacks_->dispatcher(), route_entry_->priority()); + absl::InlinedVector, 2> active_shadow_policies; + // Determine which shadow policies to use. It's possible that we don't do any shadowing due to // runtime keys. Also the method CONNECT doesn't support shadowing. - auto method = headers.getMethodValue(); + const absl::string_view method = headers.getMethodValue(); if (method != Http::Headers::get().MethodValues.Connect) { // Use cluster-level shadow policies if they are available (most specific wins). // If no cluster-level policies are configured, fall back to route-level policies. @@ -850,12 +852,19 @@ bool Filter::continueDecodeHeaders(Upstream::ThreadLocalCluster* cluster, for (const auto& shadow_policy : policies_to_evaluate) { const auto& policy_ref = *shadow_policy; if (FilterUtility::shouldShadow(policy_ref, config_->runtime_, callbacks_->streamId())) { - active_shadow_policies_.push_back(std::cref(policy_ref)); - shadow_headers_ = Http::createHeaderMap(*downstream_headers_); + active_shadow_policies.push_back(std::cref(policy_ref)); } } } + std::unique_ptr original_shadow_headers; + if (!active_shadow_policies.empty()) { + // We need to make a copy of the original request headers to avoid the content of the headers + // being changed by UpstreamRequest::acceptHeadersFromRouter() and then being sent to the + // shadow clusters. + original_shadow_headers = Http::createHeaderMap(headers); + } + ENVOY_STREAM_LOG(debug, "router decoding headers:\n{}", *callbacks_, headers); const bool can_send_early_data = @@ -874,14 +883,22 @@ bool Filter::continueDecodeHeaders(Upstream::ThreadLocalCluster* cluster, LinkedList::moveIntoList(std::move(upstream_request), upstream_requests_); upstream_requests_.front()->acceptHeadersFromRouter(end_stream); // Start the shadow streams. - for (const auto& shadow_policy_wrapper : active_shadow_policies_) { - const auto& shadow_policy = shadow_policy_wrapper.get(); + const size_t num_shadow_policies = active_shadow_policies.size(); + for (size_t i = 0; i < num_shadow_policies; ++i) { + const auto& shadow_policy = active_shadow_policies[i].get(); const absl::optional shadow_cluster_name = getShadowCluster(shadow_policy, *downstream_headers_); if (!shadow_cluster_name.has_value()) { continue; } - auto shadow_headers = Http::createHeaderMap(*shadow_headers_); + std::unique_ptr shadow_headers; + if (i == num_shadow_policies - 1) { + // For the last shadow policy, we can reuse the original headers to save a copy because + // copy whole headers map is not cheap. + shadow_headers = std::move(original_shadow_headers); + } else { + shadow_headers = Http::createHeaderMap(*original_shadow_headers); + } applyShadowPolicyHeaders(shadow_policy, *shadow_headers); const auto options = Http::AsyncClient::RequestOptions() @@ -905,18 +922,18 @@ bool Filter::continueDecodeHeaders(Upstream::ThreadLocalCluster* cluster, if (end_stream) { // This is a header-only request, and can be dispatched immediately to the shadow // without waiting. - Http::RequestMessagePtr request(new Http::RequestMessageImpl( - Http::createHeaderMap(*shadow_headers_))); - applyShadowPolicyHeaders(shadow_policy, request->headers()); + Http::RequestMessagePtr request(new Http::RequestMessageImpl(std::move(shadow_headers))); config_->shadowWriter().shadow(std::string(shadow_cluster_name.value()), std::move(request), options); } else { Http::AsyncClient::OngoingRequest* shadow_stream = config_->shadowWriter().streamingShadow( std::string(shadow_cluster_name.value()), std::move(shadow_headers), options); if (shadow_stream != nullptr) { - shadow_streams_.insert(shadow_stream); + ASSERT(std::find(shadow_streams_.begin(), shadow_streams_.end(), shadow_stream) == + shadow_streams_.end()); + shadow_streams_.push_back(shadow_stream); shadow_stream->setDestructorCallback( - [this, shadow_stream]() { shadow_streams_.erase(shadow_stream); }); + [this, shadow_stream]() { removeShadowStream(shadow_stream); }); shadow_stream->setWatermarkCallbacks(watermark_callbacks_); } } @@ -974,6 +991,14 @@ std::unique_ptr Filter::createConnPool(Upstream::ThreadLocalClu callbacks_->streamInfo().protocol(), this, *message); } +void Filter::removeShadowStream(Http::AsyncClient::OngoingRequest* shadow_stream) { + auto it = std::find(shadow_streams_.begin(), shadow_streams_.end(), shadow_stream); + if (it != shadow_streams_.end()) { + *it = shadow_streams_.back(); + shadow_streams_.pop_back(); + } +} + void Filter::sendNoHealthyUpstreamResponse(absl::string_view optional_details, absl::optional failure_status) { const Http::Code status_code = failure_status.value_or(Http::Code::ServiceUnavailable); @@ -1119,8 +1144,9 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea Http::FilterTrailersStatus Filter::decodeTrailers(Http::RequestTrailerMap& trailers) { ENVOY_STREAM_LOG(debug, "router decoding trailers:\n{}", *callbacks_, trailers); - if (shadow_headers_) { - shadow_trailers_ = Http::createHeaderMap(trailers); + std::unique_ptr original_shadow_trailer; + if (!shadow_streams_.empty()) { + original_shadow_trailer = Http::createHeaderMap(trailers); } // upstream_requests_.size() cannot be > 1 because that only happens when a per @@ -1133,11 +1159,24 @@ Http::FilterTrailersStatus Filter::decodeTrailers(Http::RequestTrailerMap& trail if (!upstream_requests_.empty()) { upstream_requests_.front()->acceptTrailersFromRouter(trailers); } - for (auto* shadow_stream : shadow_streams_) { + + const size_t shadow_count = shadow_streams_.size(); + for (size_t i = 0; i < shadow_count; ++i) { + auto* shadow_stream = shadow_streams_[i]; + shadow_stream->removeDestructorCallback(); shadow_stream->removeWatermarkCallbacks(); - shadow_stream->captureAndSendTrailers( - Http::createHeaderMap(*shadow_trailers_)); + + std::unique_ptr shadow_trailer; + if (i == shadow_count - 1) { + // For the last shadow stream, we can reuse the original trailers to save a copy because + // copy whole trailers map is not cheap. + shadow_trailer = std::move(original_shadow_trailer); + } else { + shadow_trailer = Http::createHeaderMap(*original_shadow_trailer); + } + ASSERT(shadow_trailer != nullptr); + shadow_stream->captureAndSendTrailers(std::move(shadow_trailer)); } shadow_streams_.clear(); diff --git a/source/common/router/router.h b/source/common/router/router.h index 11465b0c15aea..34e593b5f690f 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -37,6 +37,7 @@ #include "source/common/upstream/load_balancer_context_base.h" #include "source/common/upstream/upstream_factory_context_impl.h" +#include "absl/container/inlined_vector.h" #include "absl/strings/match.h" #include "absl/types/optional.h" @@ -638,6 +639,7 @@ class Filter : Logger::Loggable, void maybeProcessOrcaLoadReport(const Envoy::Http::HeaderMap& headers_or_trailers, UpstreamRequest& upstream_request); bool isEarlyConnectData(); + void removeShadowStream(Http::AsyncClient::OngoingRequest* shadow_stream); RetryStatePtr retry_state_; const FilterConfigSharedPtr config_; @@ -664,9 +666,6 @@ class Filter : Logger::Loggable, MetadataMatchCriteriaConstPtr metadata_match_; std::function modify_headers_; std::function modify_headers_from_upstream_lb_; - std::vector> active_shadow_policies_; - std::unique_ptr shadow_headers_; - std::unique_ptr shadow_trailers_; // The stream lifetime configured by request header. absl::optional dynamic_max_stream_duration_; // list of cookies to add to upstream headers @@ -674,8 +673,10 @@ class Filter : Logger::Loggable, Network::TransportSocketOptionsConstSharedPtr transport_socket_options_; Network::Socket::OptionsSharedPtr upstream_options_; - // Set of ongoing shadow streams which have not yet received end stream. - absl::flat_hash_set shadow_streams_; + // Ongoing shadow streams which have not yet received end stream. + // Inlined vector is used here under the assumption that most cases will have only single or + // double stream, and this avoids heap allocation in that case. + absl::InlinedVector shadow_streams_; // Keep small members (bools and enums) at the end of class, to reduce alignment overhead. uint64_t request_body_buffer_limit_{std::numeric_limits::max()}; From 27ea27661d96b059676714c4e0f183891e141d4e Mon Sep 17 00:00:00 2001 From: Xuyang Tao Date: Mon, 27 Apr 2026 12:41:38 -0700 Subject: [PATCH 148/750] Stats Accesslogger: support the scope singleton for limiting cardinality (#43625) Signed-off-by: Xuyang Tao --- .../extensions/access_loggers/stats/v3/BUILD | 2 + .../access_loggers/stats/v3/stats.proto | 12 +- api/envoy/type/v3/scope.proto | 45 ++++ changelogs/current.yaml | 3 + .../common_messages/common_messages.rst | 1 + envoy/stats/scope.h | 18 +- source/common/stats/isolated_store_impl.h | 12 +- source/common/stats/thread_local_store.cc | 20 +- source/common/stats/thread_local_store.h | 5 + source/extensions/access_loggers/stats/BUILD | 14 ++ .../extensions/access_loggers/stats/config.cc | 4 + .../stats/scope_provider_singleton.cc | 84 +++++++ .../stats/scope_provider_singleton.h | 33 +++ .../extensions/access_loggers/stats/stats.cc | 13 +- test/common/stats/isolated_store_impl_test.cc | 10 + test/common/stats/thread_local_store_test.cc | 16 ++ test/extensions/access_loggers/stats/BUILD | 21 ++ .../access_loggers/stats/integration_test.cc | 78 +++++- .../stats/scope_provider_singleton_test.cc | 230 ++++++++++++++++++ .../access_loggers/stats/stats_test.cc | 211 ++++++++++++---- test/integration/server.h | 5 + test/mocks/server/server_factory_context.h | 1 + test/mocks/stats/mocks.h | 9 +- 23 files changed, 765 insertions(+), 82 deletions(-) create mode 100644 api/envoy/type/v3/scope.proto create mode 100644 source/extensions/access_loggers/stats/scope_provider_singleton.cc create mode 100644 source/extensions/access_loggers/stats/scope_provider_singleton.h create mode 100644 test/extensions/access_loggers/stats/scope_provider_singleton_test.cc diff --git a/api/envoy/extensions/access_loggers/stats/v3/BUILD b/api/envoy/extensions/access_loggers/stats/v3/BUILD index 1ab50f642118f..0636f40f48308 100644 --- a/api/envoy/extensions/access_loggers/stats/v3/BUILD +++ b/api/envoy/extensions/access_loggers/stats/v3/BUILD @@ -6,7 +6,9 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/annotations:pkg", "//envoy/data/accesslog/v3:pkg", + "//envoy/type/v3:pkg", "@xds//udpa/annotations:pkg", "@xds//xds/type/matcher/v3:pkg", ], diff --git a/api/envoy/extensions/access_loggers/stats/v3/stats.proto b/api/envoy/extensions/access_loggers/stats/v3/stats.proto index a797cf4495f70..6a554882d105c 100644 --- a/api/envoy/extensions/access_loggers/stats/v3/stats.proto +++ b/api/envoy/extensions/access_loggers/stats/v3/stats.proto @@ -3,11 +3,13 @@ syntax = "proto3"; package envoy.extensions.access_loggers.stats.v3; import "envoy/data/accesslog/v3/accesslog.proto"; +import "envoy/type/v3/scope.proto"; import "google/protobuf/wrappers.proto"; import "xds/type/matcher/v3/matcher.proto"; +import "envoy/annotations/deprecation.proto"; import "udpa/annotations/status.proto"; import "validate/validate.proto"; @@ -29,7 +31,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // leading to a denial of service in Envoy, or can overwhelm any configured // stat sinks by sending too many unique metrics. -// [#next-free-field: 6] +// [#next-free-field: 7] message Config { // Defines a tag on a stat. message Tag { @@ -153,7 +155,13 @@ message Config { } // The stat prefix for the generated stats. - string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; + // Deprecated: please use ``stats_scope.prefix`` instead. + // It will override ``stats_scope.prefix`` if non-empty. + string stat_prefix = 1 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + + // Configuration for stats scope limits and sharing. + type.v3.Scope stats_scope = 6; // The histograms this logger will emit. repeated Histogram histograms = 3; diff --git a/api/envoy/type/v3/scope.proto b/api/envoy/type/v3/scope.proto new file mode 100644 index 0000000000000..2c65669a3fdf1 --- /dev/null +++ b/api/envoy/type/v3/scope.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +package envoy.type.v3; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.type.v3"; +option java_outer_classname = "ScopeProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/v3;typev3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Scope] + +// Stats scope configuration. +// This configuration can be used to create a singleton scope that is shared +// across multiple instances within the process. +// [#next-free-field: 7] +message Scope { + // Max number of counters allowed in this scope. + google.protobuf.UInt32Value max_counters = 1; + + // Max number of gauges allowed in this scope. + google.protobuf.UInt32Value max_gauges = 2; + + // Max number of histograms allowed in this scope. + google.protobuf.UInt32Value max_histograms = 3; + + // Whether the scope and its stats can be evicted from the store caches. + // The eviction policy is a mark-and-sweep approach where stats are evicted + // if they are not updated or accessed between two successive eviction sweeps. + // The eviction will happen only if the :ref:`stats_eviction_interval ` is configured in + // the bootstrap. + bool enable_eviction = 4; + + // The stats scope prefix. + string prefix = 5; + + // The sharing name of the scope. If non-empty, the scope is shared across + // multiple instances with the same sharing_name if all fields in this message + // have the same values. + string sharing_name = 6; +} diff --git a/changelogs/current.yaml b/changelogs/current.yaml index c3bcbfc20c4b9..3554909401707 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -291,6 +291,9 @@ new_features: - area: access_log change: | Added support for gauges in the :ref:`stats access logger `. +- area: access_log + change: | + Supported the singleton stats scope in the :ref:`stats access logger `. - area: network change: | Added access logging support for network filters, similar to HTTP filters, by allowing network filters to diff --git a/docs/root/api-v3/common_messages/common_messages.rst b/docs/root/api-v3/common_messages/common_messages.rst index 94e0003c7fd05..752455e491676 100644 --- a/docs/root/api-v3/common_messages/common_messages.rst +++ b/docs/root/api-v3/common_messages/common_messages.rst @@ -36,6 +36,7 @@ Common messages ../config/common/mutation_rules/v3/mutation_rules.proto ../extensions/early_data/v3/default_early_data_policy.proto ../config/core/v3/http_uri.proto + ../type/v3/scope.proto ../extensions/matching/input_matchers/ip/v3/ip.proto ../extensions/matching/input_matchers/metadata/v3/metadata.proto ../extensions/matching/input_matchers/runtime_fraction/v3/runtime_fraction.proto diff --git a/envoy/stats/scope.h b/envoy/stats/scope.h index 895f22e6965e1..c76f2d1e698ec 100644 --- a/envoy/stats/scope.h +++ b/envoy/stats/scope.h @@ -32,12 +32,12 @@ using ScopeSharedPtr = std::shared_ptr; // Settings for limiting the number of counters, gauges and histograms allowed // in a scope. This currently only supports thread local stats. struct ScopeStatsLimitSettings { - // Max number of counters allowed in this scope. 0 means no limit. - uint32_t max_counters = 0; - // Max number of gauges allowed in this scope. 0 means no limit. - uint32_t max_gauges = 0; - // Max number of histograms allowed in this scope. 0 means no limit. - uint32_t max_histograms = 0; + // Max number of counters allowed in this scope. absl::nullopt means no limit. + absl::optional max_counters = absl::nullopt; + // Max number of gauges allowed in this scope. absl::nullopt means no limit. + absl::optional max_gauges = absl::nullopt; + // Max number of histograms allowed in this scope. absl::nullopt means no limit. + absl::optional max_histograms = absl::nullopt; }; template using IterateFn = std::function&)>; @@ -75,6 +75,12 @@ class Scope : public std::enable_shared_from_this { /** @return a const shared_ptr for this */ ConstScopeSharedPtr getConstShared() const { return shared_from_this(); } + /** + * Set a callback to be run when the scope is destroyed. + * @param callback the callback to run. + */ + virtual void setCleanupCallback(std::function callback) = 0; + /** * Allocate a new scope. NOTE: The implementation should correctly handle overlapping scopes * that point to the same reference counted backing stats. This allows a new scope to be diff --git a/source/common/stats/isolated_store_impl.h b/source/common/stats/isolated_store_impl.h index 32a1ae6f10706..6dd6d558970cc 100644 --- a/source/common/stats/isolated_store_impl.h +++ b/source/common/stats/isolated_store_impl.h @@ -316,7 +316,16 @@ class IsolatedScopeImpl : public Scope { StatsMatcherSharedPtr matcher = nullptr) : prefix_(prefix, store.symbolTable()), store_(store), scope_matcher_(std::move(matcher)) {} - ~IsolatedScopeImpl() override { prefix_.free(store_.symbolTable()); } + ~IsolatedScopeImpl() override { + if (cleanup_callback_) { + cleanup_callback_(); + } + prefix_.free(store_.symbolTable()); + } + + void setCleanupCallback(std::function callback) override { + cleanup_callback_ = std::move(callback); + } // Stats::Scope SymbolTable& symbolTable() override { return store_.symbolTable(); } @@ -431,6 +440,7 @@ class IsolatedScopeImpl : public Scope { StatNameStorage prefix_; IsolatedStoreImpl& store_; StatsMatcherSharedPtr scope_matcher_; + std::function cleanup_callback_; }; } // namespace Stats diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index eed692fd24ad0..da0a8e2dfe84c 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -417,6 +417,10 @@ ThreadLocalStoreImpl::ScopeImpl::ScopeImpl(ThreadLocalStoreImpl& parent, StatNam } ThreadLocalStoreImpl::ScopeImpl::~ScopeImpl() { + if (cleanup_callback_) { + cleanup_callback_(); + } + // Helps reproduce a previous race condition by pausing here in tests while we // loop over scopes. 'this' will not have been removed from the scopes_ table // yet, so we need to be careful. @@ -536,12 +540,14 @@ StatType& ThreadLocalStoreImpl::ScopeImpl::safeMakeStat( } else { // Stat creation here. Check limits. if constexpr (std::is_same_v) { - if (limits_.max_counters != 0 && central_cache_map.size() >= limits_.max_counters) { + if (limits_.max_counters.has_value() && + central_cache_map.size() >= limits_.max_counters.value()) { parent_.counters_overflow_->inc(); return null_stat; } } else if constexpr (std::is_same_v) { - if (limits_.max_gauges != 0 && central_cache_map.size() >= limits_.max_gauges) { + if (limits_.max_gauges.has_value() && + central_cache_map.size() >= limits_.max_gauges.value()) { parent_.gauges_overflow_->inc(); return null_stat; } @@ -743,8 +749,8 @@ Histogram& ThreadLocalStoreImpl::ScopeImpl::getOrCreateHistogramBase( if (iter != parent_.histogram_set_.end()) { stat = RefcountPtr(*iter); } else { - if (limits_.max_histograms != 0 && - central_cache->histograms_.size() >= limits_.max_histograms) { + if (limits_.max_histograms.has_value() && + central_cache->histograms_.size() >= limits_.max_histograms.value()) { parent_.histograms_overflow_->inc(); return parent_.null_histogram_; } @@ -1315,9 +1321,9 @@ void ThreadLocalStoreImpl::extractAndAppendTags(absl::string_view name, StatName } void ThreadLocalStoreImpl::ensureOverflowStats(const ScopeStatsLimitSettings& limits) { - const bool need_counter_overflow_stat = limits.max_counters != 0; - const bool need_gauge_overflow_stat = limits.max_gauges != 0; - const bool need_histogram_overflow_stat = limits.max_histograms != 0; + const bool need_counter_overflow_stat = limits.max_counters.has_value(); + const bool need_gauge_overflow_stat = limits.max_gauges.has_value(); + const bool need_histogram_overflow_stat = limits.max_histograms.has_value(); if (!need_counter_overflow_stat && !need_gauge_overflow_stat && !need_histogram_overflow_stat) { return; diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index e825c20a50085..9a3eee47f1acf 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -295,6 +295,10 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo StatsMatcherSharedPtr scope_matcher = nullptr); ~ScopeImpl() override; + void setCleanupCallback(std::function callback) override { + cleanup_callback_ = std::move(callback); + } + // Stats::Scope Counter& counterFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef tags) override; @@ -489,6 +493,7 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo private: StatNameStorage prefix_; mutable CentralCacheEntrySharedPtr central_cache_ ABSL_GUARDED_BY(parent_.lock_); + std::function cleanup_callback_; }; struct TlsCache : public ThreadLocal::ThreadLocalObject { diff --git a/source/extensions/access_loggers/stats/BUILD b/source/extensions/access_loggers/stats/BUILD index 1d0574ebb2782..933139661ef4a 100644 --- a/source/extensions/access_loggers/stats/BUILD +++ b/source/extensions/access_loggers/stats/BUILD @@ -9,11 +9,25 @@ licenses(["notice"]) # Apache 2 envoy_extension_package() +envoy_cc_library( + name = "scope_provider_singleton_lib", + srcs = ["scope_provider_singleton.cc"], + hdrs = ["scope_provider_singleton.h"], + deps = [ + "//envoy/server:factory_context_interface", + "//envoy/singleton:instance_interface", + "//envoy/stats:stats_interface", + "//source/common/protobuf:utility_lib", + "@envoy_api//envoy/type/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "stats_lib", srcs = ["stats.cc"], hdrs = ["stats.h"], deps = [ + ":scope_provider_singleton_lib", "//envoy/access_log:access_log_interface", "//envoy/stats:tag_interface", "//source/common/access_log:access_log_lib", diff --git a/source/extensions/access_loggers/stats/config.cc b/source/extensions/access_loggers/stats/config.cc index 15afd9e1ef826..61e477b98e116 100644 --- a/source/extensions/access_loggers/stats/config.cc +++ b/source/extensions/access_loggers/stats/config.cc @@ -18,6 +18,10 @@ AccessLog::InstanceSharedPtr AccessLogFactory::createAccessLogInstance( MessageUtil::downcastAndValidate( config, context.messageValidationVisitor()); + if (proto_config.stat_prefix().empty() == !proto_config.has_stats_scope()) { + throw EnvoyException("Either 'stat_prefix' or 'stats_scope' must be configured, but not both."); + } + return std::make_shared(proto_config, context, std::move(filter), command_parsers); } diff --git a/source/extensions/access_loggers/stats/scope_provider_singleton.cc b/source/extensions/access_loggers/stats/scope_provider_singleton.cc new file mode 100644 index 0000000000000..4819c1463ac81 --- /dev/null +++ b/source/extensions/access_loggers/stats/scope_provider_singleton.cc @@ -0,0 +1,84 @@ +#include "source/extensions/access_loggers/stats/scope_provider_singleton.h" + +#include "envoy/singleton/instance.h" +#include "envoy/type/v3/scope.pb.h" + +#include "source/common/common/assert.h" +#include "source/common/protobuf/utility.h" + +namespace Envoy { +namespace Stats { + +namespace { + +struct ScopeConfiguration { + Stats::ScopeStatsLimitSettings limits; + bool evictable_; +}; + +ScopeConfiguration convertProtoToScopeStatsLimitSettings(const envoy::type::v3::Scope& config) { + Stats::ScopeStatsLimitSettings limits; + if (config.has_max_counters()) { + limits.max_counters = config.max_counters().value(); + } + if (config.has_max_gauges()) { + limits.max_gauges = config.max_gauges().value(); + } + if (config.has_max_histograms()) { + limits.max_histograms = config.max_histograms().value(); + } + return {limits, config.enable_eviction()}; +} + +} // namespace + +SINGLETON_MANAGER_REGISTRATION(stats_access_logger_scope_provider); + +Stats::ScopeSharedPtr +ScopeProviderSingleton::getScope(Server::Configuration::GenericFactoryContext& factory_context, + const envoy::type::v3::Scope& config) { + ASSERT_IS_MAIN_OR_TEST_THREAD(); + if (config.sharing_name().empty()) { + ScopeConfiguration scope_cfg = convertProtoToScopeStatsLimitSettings(config); + return factory_context.statsScope().createScope(config.prefix(), scope_cfg.evictable_, + scope_cfg.limits); + } + + ScopeProviderSingletonSharedPtr provider = + factory_context.serverFactoryContext().singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(stats_access_logger_scope_provider), + [] { return std::make_shared(); }); + + size_t hash = MessageUtil::hash(config); + auto it = provider->scopes_.find(hash); + if (it != provider->scopes_.end()) { + Stats::ScopeSharedPtr scope = it->second.lock(); + if (scope != nullptr) { + return scope; + } + } + + ScopeConfiguration scope_cfg = convertProtoToScopeStatsLimitSettings(config); + Stats::ScopeSharedPtr scope = factory_context.serverFactoryContext().serverScope().createScope( + config.prefix(), scope_cfg.evictable_, scope_cfg.limits); + + Event::Dispatcher& dispatcher = factory_context.serverFactoryContext().mainThreadDispatcher(); + + // The returned scope captures the provider (the shared_ptr) by value in its cleanup callback. + // This keeps the provider (and singleton cache) alive as long as the scope exists. + scope->setCleanupCallback([hash, provider, &dispatcher]() { + dispatcher.post([hash, provider]() { + auto map_it = provider->scopes_.find(hash); + if (map_it != provider->scopes_.end() && map_it->second.expired()) { + provider->scopes_.erase(map_it); + } + }); + }); + + provider->scopes_[hash] = scope; + + return scope; +} + +} // namespace Stats +} // namespace Envoy diff --git a/source/extensions/access_loggers/stats/scope_provider_singleton.h b/source/extensions/access_loggers/stats/scope_provider_singleton.h new file mode 100644 index 0000000000000..05f09e9d73940 --- /dev/null +++ b/source/extensions/access_loggers/stats/scope_provider_singleton.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "envoy/server/factory_context.h" +#include "envoy/singleton/instance.h" +#include "envoy/stats/scope.h" +#include "envoy/type/v3/scope.pb.h" + +#include "absl/container/flat_hash_map.h" + +namespace Envoy { +namespace Stats { + +// ScopeProviderSingleton is a process-wide singleton responsible for managing +// and vending Scopes with limits configuration inline. +class ScopeProviderSingleton; +using ScopeProviderSingletonSharedPtr = std::shared_ptr; + +class ScopeProviderSingleton : public Singleton::Instance { +public: + static Stats::ScopeSharedPtr + getScope(Server::Configuration::GenericFactoryContext& factory_context, + const envoy::type::v3::Scope& config); + + ScopeProviderSingleton() = default; + +private: + absl::flat_hash_map> scopes_; +}; + +} // namespace Stats +} // namespace Envoy diff --git a/source/extensions/access_loggers/stats/stats.cc b/source/extensions/access_loggers/stats/stats.cc index c971a3efb1213..d51513c7f4a3e 100644 --- a/source/extensions/access_loggers/stats/stats.cc +++ b/source/extensions/access_loggers/stats/stats.cc @@ -7,6 +7,7 @@ #include "source/common/formatter/substitution_formatter.h" #include "source/common/stats/symbol_table.h" +#include "source/extensions/access_loggers/stats/scope_provider_singleton.h" namespace Envoy { namespace Extensions { @@ -169,9 +170,15 @@ StatsAccessLog::StatsAccessLog(const envoy::extensions::access_loggers::stats::v Server::Configuration::GenericFactoryContext& context, AccessLog::FilterPtr&& filter, const std::vector& commands) - : Common::ImplBase(std::move(filter)), - scope_(context.statsScope().createScope(config.stat_prefix(), true /* evictable */)), - stat_name_pool_(scope_->symbolTable()), histograms_([&]() { + : Common::ImplBase(std::move(filter)), scope_([&]() { + envoy::type::v3::Scope modified_config = config.stats_scope(); + if (!config.stat_prefix().empty()) { + modified_config.set_prefix(config.stat_prefix()); + } + return Stats::ScopeProviderSingleton::getScope(context, modified_config); + }()), + + stat_name_pool_(context.statsScope().symbolTable()), histograms_([&]() { std::vector histograms; for (const auto& hist_cfg : config.histograms()) { histograms.emplace_back(NameAndTags(hist_cfg.stat(), stat_name_pool_, commands, context), diff --git a/test/common/stats/isolated_store_impl_test.cc b/test/common/stats/isolated_store_impl_test.cc index 1e5cb049f214c..94c233bf2ee2b 100644 --- a/test/common/stats/isolated_store_impl_test.cc +++ b/test/common/stats/isolated_store_impl_test.cc @@ -133,6 +133,16 @@ TEST_F(StatsIsolatedStoreImplTest, All) { EXPECT_EQ(scope_->findHistogram(nonexistent_name.statName()), absl::nullopt); } +TEST_F(StatsIsolatedStoreImplTest, CleanupCallback) { + bool called = false; + { + IsolatedStoreImpl local_store(symbol_table_); + ScopeSharedPtr scope1 = local_store.rootScope()->createScope("scope1."); + scope1->setCleanupCallback([&called]() { called = true; }); + } + EXPECT_TRUE(called); +} + TEST_F(StatsIsolatedStoreImplTest, PrefixIsStatName) { ScopeSharedPtr scope1 = scope_->createScope("scope1"); ScopeSharedPtr scope2 = scope1->scopeFromStatName(makeStatName("scope2")); diff --git a/test/common/stats/thread_local_store_test.cc b/test/common/stats/thread_local_store_test.cc index 2be652af8714b..53dc393ff77f4 100644 --- a/test/common/stats/thread_local_store_test.cc +++ b/test/common/stats/thread_local_store_test.cc @@ -680,6 +680,22 @@ TEST_F(StatsThreadLocalStoreTest, ScopeDelete) { tls_.shutdownThread(); } +TEST_F(StatsThreadLocalStoreTest, CleanupCallback) { + InSequence s; + store_->initializeThreading(main_thread_dispatcher_, tls_); + + bool called = false; + { + ScopeSharedPtr scope = store_->createScope("scope."); + scope->setCleanupCallback([&called]() { called = true; }); + } + EXPECT_TRUE(called); + + tls_.shutdownGlobalThreading(); + store_->shutdownThreading(); + tls_.shutdownThread(); +} + TEST_F(StatsThreadLocalStoreTest, Eviction) { InSequence s; store_->initializeThreading(main_thread_dispatcher_, tls_); diff --git a/test/extensions/access_loggers/stats/BUILD b/test/extensions/access_loggers/stats/BUILD index fb67e4e965fe4..7e2b64685a00a 100644 --- a/test/extensions/access_loggers/stats/BUILD +++ b/test/extensions/access_loggers/stats/BUILD @@ -13,6 +13,19 @@ licenses(["notice"]) # Apache 2 envoy_package() +envoy_extension_cc_test( + name = "scope_provider_singleton_test", + srcs = ["scope_provider_singleton_test.cc"], + extension_names = ["envoy.access_loggers.stats"], + deps = [ + "//source/common/stats:symbol_table_lib", + "//source/extensions/access_loggers/stats:scope_provider_singleton_lib", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/stats:stats_mocks", + "//test/test_common:utility_lib", + ], +) + envoy_extension_cc_test( name = "stats_test", srcs = ["stats_test.cc"], @@ -22,13 +35,19 @@ envoy_extension_cc_test( "//source/common/stats:allocator_lib", "//source/common/stats:stat_match_input_lib", "//source/common/stats:thread_local_store_lib", + "//source/extensions/access_loggers/stats:config", "//source/extensions/access_loggers/stats:stats_lib", "//source/extensions/matching/actions/transform_stat:config", "//test/common/memory:memory_test_utility_lib", + "//test/mocks/config:config_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:logging_lib", + "//test/test_common:status_utility_lib", + "//test/test_common:test_runtime_lib", + "//test/test_common:utility_lib", "@abseil-cpp//absl/hash:hash_testing", + "@envoy_api//envoy/type/v3:pkg_cc_proto", ], ) @@ -42,7 +61,9 @@ envoy_extension_cc_test( "//source/extensions/access_loggers/stats:config", "//source/extensions/filters/network/tcp_proxy:config", "//source/extensions/matching/actions/transform_stat:config", + "//test/integration:ads_integration_lib", "//test/integration:http_integration_lib", + "//test/test_common:status_utility_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", ], diff --git a/test/extensions/access_loggers/stats/integration_test.cc b/test/extensions/access_loggers/stats/integration_test.cc index 00f20a07aafad..45e29f2d9a1cd 100644 --- a/test/extensions/access_loggers/stats/integration_test.cc +++ b/test/extensions/access_loggers/stats/integration_test.cc @@ -1,5 +1,6 @@ #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" +#include "test/integration/ads_integration.h" #include "test/integration/http_integration.h" #include "test/test_common/logging.h" #include "test/test_common/utility.h" @@ -49,7 +50,8 @@ TEST_P(StatsAccessLogIntegrationTest, Basic) { name: envoy.access_loggers.stats typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: fixedcounter @@ -104,7 +106,8 @@ TEST_P(StatsAccessLogIntegrationTest, Concurrency) { name: envoy.access_loggers.stats typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: formatcounter @@ -149,7 +152,8 @@ TEST_P(StatsAccessLogIntegrationTest, PercentHistogram) { name: envoy.access_loggers.stats typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix histograms: - stat: name: testhistogram @@ -185,7 +189,8 @@ TEST_P(StatsAccessLogIntegrationTest, ActiveRequestsGauge) { name: envoy.access_loggers.stats typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: active_requests @@ -237,7 +242,8 @@ TEST_P(StatsAccessLogIntegrationTest, SubtractWithoutAdd) { types: [DownstreamEnd] typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: active_requests @@ -279,7 +285,8 @@ TEST_P(StatsAccessLogIntegrationTest, GaugeInterleavedOpsWithEviction) { name: envoy.access_loggers.stats typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: active_requests @@ -368,7 +375,8 @@ TEST_P(StatsAccessLogIntegrationTest, ActiveRequestsGaugeEvictedWhileInflight) { name: envoy.access_loggers.stats typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: active_requests @@ -439,7 +447,8 @@ TEST_P(StatsAccessLogIntegrationTest, GaugeCleanupOnDestructor) { name: envoy.access_loggers.stats typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: active_requests @@ -482,6 +491,56 @@ TEST_P(StatsAccessLogIntegrationTest, GaugeCleanupOnDestructor) { "test_stat_prefix.active_requests.request_header_tag.my-evict-cleanup-tag", 0); } +TEST_P(StatsAccessLogIntegrationTest, SharedScope) { + const std::string config_yaml1 = R"EOF( + name: envoy.access_loggers.stats + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config + stats_scope: + sharing_name: "shared_scope" + prefix: shared_scope_limits + max_counters: 1 + counters: + - stat: + name: formatcounter1 + value_format: '%RESPONSE_CODE%' +)EOF"; + + const std::string config_yaml2 = R"EOF( + name: envoy.access_loggers.stats + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config + stats_scope: + sharing_name: "shared_scope" + prefix: shared_scope_limits + max_counters: 1 + counters: + - stat: + name: formatcounter2 + value_format: '%RESPONSE_CODE%' +)EOF"; + + init(std::vector{config_yaml1, config_yaml2}); + + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":authority", "envoyproxy.io"}, + {":path", "/test/long/url"}, + {":scheme", "http"}}; + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest(request_headers); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_EQ(response->headers().getStatusValue(), "200"); + + // Since both access loggers share the same configuration, they should share the same scope. + // We expect the first counter to be incremented once (by the first access logger). + // The second counter (from the second logger) should be dropped because the scope limit is 1. + test_server_->waitForCounterEq("shared_scope_limits.formatcounter1", 200); + + auto store_counter = test_server_->counter("shared_scope_limits.formatcounter2"); + EXPECT_EQ(store_counter, nullptr); +} + } // namespace class StatsAccessLogTcpIntegrationTest : public testing::TestWithParam, @@ -509,7 +568,8 @@ name: envoy.filters.network.tcp_proxy - name: envoy.access_loggers.stats typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: active_connections diff --git a/test/extensions/access_loggers/stats/scope_provider_singleton_test.cc b/test/extensions/access_loggers/stats/scope_provider_singleton_test.cc new file mode 100644 index 0000000000000..609677db9c343 --- /dev/null +++ b/test/extensions/access_loggers/stats/scope_provider_singleton_test.cc @@ -0,0 +1,230 @@ +#include "source/common/singleton/manager_impl.h" +#include "source/common/stats/symbol_table.h" +#include "source/extensions/access_loggers/stats/scope_provider_singleton.h" + +#include "test/mocks/server/server_factory_context.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/thread_factory_for_test.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace StatsAccessLog { +namespace { + +using testing::_; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +class ScopeProviderSingletonTest : public testing::Test { +public: + ScopeProviderSingletonTest() { + ON_CALL(factory_context_.server_context_, singletonManager()) + .WillByDefault(ReturnRef(singleton_manager_)); + ON_CALL(factory_context_, serverFactoryContext()) + .WillByDefault(ReturnRef(factory_context_.server_context_)); + } + + NiceMock mock_store_; + NiceMock factory_context_; + Singleton::ManagerImpl singleton_manager_; + std::vector> name_storages_; +}; + +TEST_F(ScopeProviderSingletonTest, GetScopeCachesAndReturnsSameScope) { + // First request + envoy::type::v3::Scope scope_config1; + scope_config1.set_sharing_name("shared_name"); + scope_config1.mutable_max_counters()->set_value(10); + scope_config1.mutable_max_gauges()->set_value(20); + scope_config1.mutable_max_histograms()->set_value(30); + + EXPECT_CALL(factory_context_.server_context_, serverScope()) + .WillRepeatedly(ReturnRef(mock_store_.mockScope())); + EXPECT_CALL(factory_context_, scope()).WillRepeatedly(ReturnRef(mock_store_.mockScope())); + EXPECT_CALL(factory_context_, statsScope()).WillRepeatedly(ReturnRef(mock_store_.mockScope())); + + std::shared_ptr newly_created_scope; + + EXPECT_CALL(mock_store_.mockScope(), createScope_(_)) + .WillOnce(Invoke([&](const std::string& name) { + auto scope_name_storage = + std::make_unique(name, mock_store_.symbolTable()); + newly_created_scope = std::shared_ptr( + new NiceMock(scope_name_storage->statName(), mock_store_)); + return newly_created_scope; + })); + + auto returned_scope1 = Stats::ScopeProviderSingleton::getScope(factory_context_, scope_config1); + EXPECT_EQ(returned_scope1, newly_created_scope); + EXPECT_NE(returned_scope1, nullptr); + + envoy::type::v3::Scope scope_config2; + scope_config2.set_sharing_name("shared_name"); + scope_config2.mutable_max_counters()->set_value(10); + scope_config2.mutable_max_gauges()->set_value(20); + scope_config2.mutable_max_histograms()->set_value(30); + + // Expect no new createScope_ calls here + auto returned_scope2 = Stats::ScopeProviderSingleton::getScope(factory_context_, scope_config2); + EXPECT_EQ(returned_scope2, returned_scope1); + EXPECT_EQ(returned_scope2, newly_created_scope); + + // Third request with different config should create new scope + envoy::type::v3::Scope scope_config3; + scope_config3.set_sharing_name("different_limits"); + scope_config3.mutable_max_counters()->set_value(15); + scope_config3.mutable_max_gauges()->set_value(20); + scope_config3.mutable_max_histograms()->set_value(30); + + std::shared_ptr newly_created_scope3; + EXPECT_CALL(mock_store_.mockScope(), createScope_(_)) + .WillOnce(Invoke([&](const std::string& name) { + auto scope_name_storage = + std::make_unique(name, mock_store_.symbolTable()); + newly_created_scope3 = std::shared_ptr( + new NiceMock(scope_name_storage->statName(), mock_store_)); + return newly_created_scope3; + })); + + auto returned_scope3 = Stats::ScopeProviderSingleton::getScope(factory_context_, scope_config3); + EXPECT_EQ(returned_scope3, newly_created_scope3); + EXPECT_NE(returned_scope3, returned_scope1); +} + +TEST_F(ScopeProviderSingletonTest, CleansUpExpiredScopesOnNextGetScope) { + envoy::type::v3::Scope scope_config; + scope_config.set_sharing_name("cleanup_test_name"); + scope_config.mutable_max_counters()->set_value(10); + scope_config.mutable_max_gauges()->set_value(20); + scope_config.mutable_max_histograms()->set_value(30); + + EXPECT_CALL(factory_context_.server_context_, serverScope()) + .WillRepeatedly(ReturnRef(mock_store_.mockScope())); + EXPECT_CALL(factory_context_, scope()).WillRepeatedly(ReturnRef(mock_store_.mockScope())); + EXPECT_CALL(factory_context_, statsScope()).WillRepeatedly(ReturnRef(mock_store_.mockScope())); + + // First request should trigger createScope_ + EXPECT_CALL(mock_store_.mockScope(), createScope_(_)) + .Times(1) + .WillOnce(Invoke([&](const std::string& name) { + auto scope_name_storage = + std::make_unique(name, mock_store_.symbolTable()); + return std::shared_ptr( + new NiceMock(scope_name_storage->statName(), mock_store_)); + })); + + auto returned_scope = Stats::ScopeProviderSingleton::getScope(factory_context_, scope_config); + EXPECT_NE(returned_scope, nullptr); + + // While returned_scope is alive, requesting again should not trigger createScope_ + EXPECT_CALL(mock_store_.mockScope(), createScope_(_)).Times(0); + auto returned_scope2 = Stats::ScopeProviderSingleton::getScope(factory_context_, scope_config); + EXPECT_EQ(returned_scope, returned_scope2); + + testing::Mock::VerifyAndClearExpectations(&mock_store_.mockScope()); + + // Destroy the scope holders. The weak reference will expire, and next getScope should clean it + // up. + returned_scope.reset(); + returned_scope2.reset(); + + // Next request should trigger a new createScope_ because it was deleted + EXPECT_CALL(mock_store_.mockScope(), createScope_(_)) + .Times(1) + .WillOnce(Invoke([&](const std::string& name) { + auto scope_name_storage = + std::make_unique(name, mock_store_.symbolTable()); + return std::shared_ptr( + new NiceMock(scope_name_storage->statName(), mock_store_)); + })); + + auto returned_scope3 = Stats::ScopeProviderSingleton::getScope(factory_context_, scope_config); + EXPECT_NE(returned_scope3, nullptr); +} + +TEST_F(ScopeProviderSingletonTest, GetScopeWithSharingDisabledDoesNotCache) { + envoy::type::v3::Scope scope_config; + // sharing_name is empty by default, which means sharing is disabled. + scope_config.mutable_max_counters()->set_value(10); + + EXPECT_CALL(factory_context_.server_context_, serverScope()) + .WillRepeatedly(ReturnRef(mock_store_.mockScope())); + EXPECT_CALL(factory_context_, statsScope()).WillRepeatedly(ReturnRef(mock_store_.mockScope())); + + // First request should create scope + EXPECT_CALL(mock_store_.mockScope(), createScope_(_)) + .Times(1) + .WillOnce(Invoke([&](const std::string& name) { + auto scope_name_storage = + std::make_unique(name, mock_store_.symbolTable()); + return std::shared_ptr( + new NiceMock(scope_name_storage->statName(), mock_store_)); + })); + + auto returned_scope1 = Stats::ScopeProviderSingleton::getScope(factory_context_, scope_config); + EXPECT_NE(returned_scope1, nullptr); + + // Second request should also create scope since sharing is disabled + EXPECT_CALL(mock_store_.mockScope(), createScope_(_)) + .Times(1) + .WillOnce(Invoke([&](const std::string& name) { + auto scope_name_storage = + std::make_unique(name, mock_store_.symbolTable()); + return std::shared_ptr( + new NiceMock(scope_name_storage->statName(), mock_store_)); + })); + + auto returned_scope2 = Stats::ScopeProviderSingleton::getScope(factory_context_, scope_config); + EXPECT_NE(returned_scope2, nullptr); + EXPECT_NE(returned_scope1, returned_scope2); +} + +TEST_F(ScopeProviderSingletonTest, SupportsGetSharedAndCopying) { + envoy::type::v3::Scope scope_config; + scope_config.set_sharing_name("copy_test_name"); + + EXPECT_CALL(factory_context_.server_context_, serverScope()) + .WillRepeatedly(ReturnRef(mock_store_.mockScope())); + EXPECT_CALL(factory_context_, scope()).WillRepeatedly(ReturnRef(mock_store_.mockScope())); + EXPECT_CALL(factory_context_, statsScope()).WillRepeatedly(ReturnRef(mock_store_.mockScope())); + EXPECT_CALL(factory_context_.server_context_, mainThreadDispatcher()) + .WillRepeatedly(ReturnRef(factory_context_.server_context_.dispatcher_)); + + EXPECT_CALL(mock_store_.mockScope(), createScope_(_)) + .WillOnce(Invoke([&](const std::string& name) { + auto scope_name_storage = + std::make_unique(name, mock_store_.symbolTable()); + return std::shared_ptr( + new NiceMock(scope_name_storage->statName(), mock_store_)); + })); + + auto wrapped_scope = Stats::ScopeProviderSingleton::getScope(factory_context_, scope_config); + EXPECT_NE(wrapped_scope, nullptr); + + // Users can get a shared reference via the interface's `getShared()` (which uses + // enable_shared_from_this). + Stats::ScopeSharedPtr shared_copy = wrapped_scope->getShared(); + EXPECT_EQ(wrapped_scope.get(), shared_copy.get()); + + // Resetting wrapped_scope should NOT trigger cleanup because shared_copy still holds it. + EXPECT_CALL(factory_context_.server_context_.dispatcher_, post(_)).Times(0); + wrapped_scope.reset(); + testing::Mock::VerifyAndClearExpectations(&factory_context_.server_context_.dispatcher_); + + // Resetting shared_copy should trigger cleanup because it's the last reference. + EXPECT_CALL(factory_context_.server_context_.dispatcher_, post(_)); + shared_copy.reset(); +} + +} // namespace +} // namespace StatsAccessLog +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/access_loggers/stats/stats_test.cc b/test/extensions/access_loggers/stats/stats_test.cc index 45cd949593bc0..10c874b7701f8 100644 --- a/test/extensions/access_loggers/stats/stats_test.cc +++ b/test/extensions/access_loggers/stats/stats_test.cc @@ -1,10 +1,14 @@ #include "envoy/stats/sink.h" +#include "envoy/type/v3/scope.pb.h" -#include "source/common/stats/allocator.h" +#include "source/common/config/decoded_resource_impl.h" +#include "source/common/stats/allocator_impl.h" #include "source/common/stats/thread_local_store.h" +#include "source/extensions/access_loggers/stats/config.h" #include "source/extensions/access_loggers/stats/stats.h" #include "test/common/memory/memory_test_utility.h" +#include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/server_factory_context.h" @@ -12,9 +16,12 @@ #include "test/mocks/stream_info/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/test_common/logging.h" +#include "test/test_common/status_utility.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "absl/hash/hash_testing.h" +#include "absl/status/statusor.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -75,9 +82,12 @@ class MockGaugeWithTags : public Stats::MockGauge { class StatsAccessLoggerTest : public testing::Test { public: + void TearDown() override { logger_.reset(); } + void initialize(std::string config_yaml = {}) { const std::string default_config_yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -116,20 +126,30 @@ class StatsAccessLoggerTest : public testing::Test { ON_CALL(store_, gauge(_, _)).WillByDefault(testing::ReturnRef(*gauge_)); ON_CALL(context_, statsScope()).WillByDefault(testing::ReturnRef(store_.mockScope())); + ON_CALL(context_, scope()).WillByDefault(testing::ReturnRef(store_.mockScope())); + ON_CALL(context_.server_context_, serverScope()) + .WillByDefault(testing::ReturnRef(store_.mockScope())); + ON_CALL(context_, serverScope()).WillByDefault(testing::ReturnRef(store_.mockScope())); + EXPECT_CALL(store_.mockScope(), createScope_(_)) - .WillOnce(Invoke([this](const std::string& name) { - scope_name_storage_ = + .WillRepeatedly(Invoke([this](const std::string& name) { + auto scope_name_storage = std::make_unique(name, context_.store_.symbolTable()); auto scope = std::make_shared>( - scope_name_storage_->statName(), store_); + scope_name_storage->statName(), store_); ON_CALL(*scope, gaugeFromStatNameWithTags(_, _, _)) - .WillByDefault(Invoke( - [scope_ptr = scope.get()](const Stats::StatName& name, - Stats::StatNameTagVectorOptConstRef tags, - Stats::Gauge::ImportMode import_mode) -> Stats::Gauge& { - return scope_ptr->Stats::MockScope::gaugeFromStatNameWithTags(name, tags, - import_mode); + .WillByDefault( + Invoke([this](const Stats::StatName& name, Stats::StatNameTagVectorOptConstRef, + Stats::Gauge::ImportMode import_mode) -> Stats::Gauge& { + return this->store_.gauge(this->context_.store_.symbolTable().toString(name), + import_mode); })); + ON_CALL(*scope, counterFromStatNameWithTags(_, _)) + .WillByDefault(Invoke([this](const Stats::StatName& name, + Stats::StatNameTagVectorOptConstRef) -> Stats::Counter& { + return this->store_.counter(this->context_.store_.symbolTable().toString(name)); + })); + ON_CALL(*scope, histogramFromStatNameWithTags(_, _, _)) .WillByDefault(Invoke([scope_ptr = scope.get()]( const Stats::StatName& name, @@ -137,8 +157,10 @@ class StatsAccessLoggerTest : public testing::Test { Stats::Histogram::Unit unit) -> Stats::Histogram& { return scope_ptr->Stats::MockScope::histogramFromStatNameWithTags(name, tags, unit); })); + scope_ = scope; - return scope_; + name_storages_.push_back(std::move(scope_name_storage)); + return scope; })); logger_ = std::make_shared(config, context_, std::move(filter_), @@ -148,6 +170,7 @@ class StatsAccessLoggerTest : public testing::Test { AccessLog::FilterPtr filter_; NiceMock store_; NiceMock context_; + std::vector> name_storages_; std::shared_ptr scope_; std::unique_ptr scope_name_storage_; std::shared_ptr logger_; @@ -159,7 +182,8 @@ class StatsAccessLoggerTest : public testing::Test { TEST_F(StatsAccessLoggerTest, IncorrectValueFormatter) { const std::string cfg = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -171,9 +195,30 @@ TEST_F(StatsAccessLoggerTest, IncorrectValueFormatter) { "Stats logger `value_format` string must contain exactly one substitution"); } +TEST(StatsAccessLogConfigTest, ValidationFailBothEmpty) { + NiceMock context; + envoy::extensions::access_loggers::stats::v3::Config config; + EXPECT_THROW_WITH_MESSAGE( + AccessLogFactory().createAccessLogInstance(config, nullptr, context), EnvoyException, + "Either 'stat_prefix' or 'stats_scope' must be configured, but not both."); +} + +TEST(StatsAccessLogConfigTest, DEPRECATED_FEATURE_TEST(ValidationFailBothSet)) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.features.enable_all_deprecated_features", "true"}}); + NiceMock context; + envoy::extensions::access_loggers::stats::v3::Config config; + config.set_stat_prefix("prefix"); + config.mutable_stats_scope()->set_sharing_name("scope1"); + EXPECT_THROW_WITH_MESSAGE( + AccessLogFactory().createAccessLogInstance(config, nullptr, context), EnvoyException, + "Either 'stat_prefix' or 'stats_scope' must be configured, but not both."); +} + TEST_F(StatsAccessLoggerTest, HistogramUnits) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix histograms: - stat: name: Unspecified @@ -208,7 +253,8 @@ TEST_F(StatsAccessLoggerTest, HistogramUnits) { TEST_F(StatsAccessLoggerTest, HistogramUnitsInvalid) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix histograms: - stat: name: histogram @@ -225,7 +271,8 @@ TEST_F(StatsAccessLoggerTest, HistogramUnitsInvalid) { TEST_F(StatsAccessLoggerTest, CounterBothFormatAndFixed) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -240,7 +287,8 @@ TEST_F(StatsAccessLoggerTest, CounterBothFormatAndFixed) { TEST_F(StatsAccessLoggerTest, CounterNoValueConfig) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -254,7 +302,8 @@ TEST_F(StatsAccessLoggerTest, CounterNoValueConfig) { // Format string resolved to empty optional (no value available). TEST_F(StatsAccessLoggerTest, NoValueFormatted) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -274,7 +323,8 @@ TEST_F(StatsAccessLoggerTest, NoValueFormatted) { // Format string resolved to a non-number string. TEST_F(StatsAccessLoggerTest, NonNumberValueFormatted) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix histograms: - stat: name: counter @@ -293,7 +343,8 @@ TEST_F(StatsAccessLoggerTest, NonNumberValueFormatted) { // Format string resolved to a number string. TEST_F(StatsAccessLoggerTest, NumberStringValueFormatted) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -311,7 +362,8 @@ TEST_F(StatsAccessLoggerTest, NumberStringValueFormatted) { TEST_F(StatsAccessLoggerTest, CounterValueFixed) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -330,7 +382,8 @@ TEST_F(StatsAccessLoggerTest, CounterValueFixed) { // Histogram values are in the range 0-1.0, so ensure that fractional values work. TEST_F(StatsAccessLoggerTest, HistogramPercent) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix histograms: - stat: name: histogram @@ -361,7 +414,8 @@ TEST_F(StatsAccessLoggerTest, HistogramPercent) { // Test that a tag formatter that doesn't have a value becomes an empty string. TEST_F(StatsAccessLoggerTest, EmptyTagFormatter) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -385,14 +439,16 @@ TEST_F(StatsAccessLoggerTest, EmptyTagFormatter) { EXPECT_EQ(1, tags->get().size()); EXPECT_EQ(":200", scope_->symbolTable().toString(tags->get().front().second)); - return scope_->counterFromStatNameWithTags_(name, tags); + return store_.counter_; })); + EXPECT_CALL(store_.counter_, add(1)); logger_->log(formatter_context_, stream_info_); } TEST_F(StatsAccessLoggerTest, GaugeNonNumberValueFormatted) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -416,7 +472,8 @@ TEST_F(StatsAccessLoggerTest, GaugeNonNumberValueFormatted) { // Format string resolved to a number string. TEST_F(StatsAccessLoggerTest, GaugeNumberValueFormatted) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -437,7 +494,8 @@ TEST_F(StatsAccessLoggerTest, GaugeNumberValueFormatted) { TEST_F(StatsAccessLoggerTest, GaugeValueFixed) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -464,7 +522,8 @@ TEST_F(StatsAccessLoggerTest, GaugeValueFixed) { TEST_F(StatsAccessLoggerTest, GaugeOperationTypeSet) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -485,7 +544,8 @@ TEST_F(StatsAccessLoggerTest, GaugeOperationTypeSet) { TEST_F(StatsAccessLoggerTest, GaugeBothFormatAndFixed) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -502,7 +562,8 @@ TEST_F(StatsAccessLoggerTest, GaugeBothFormatAndFixed) { TEST_F(StatsAccessLoggerTest, GaugeNoValueConfig) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -515,7 +576,8 @@ TEST_F(StatsAccessLoggerTest, GaugeNoValueConfig) { TEST_F(StatsAccessLoggerTest, GaugeBothSetAndAddSubtract) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -533,7 +595,8 @@ TEST_F(StatsAccessLoggerTest, GaugeBothSetAndAddSubtract) { TEST_F(StatsAccessLoggerTest, GaugeMultipleAdd) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -548,7 +611,8 @@ TEST_F(StatsAccessLoggerTest, GaugeMultipleAdd) { TEST_F(StatsAccessLoggerTest, GaugeNeitherSetNorAddSubtract) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -560,7 +624,8 @@ TEST_F(StatsAccessLoggerTest, GaugeNeitherSetNorAddSubtract) { TEST_F(StatsAccessLoggerTest, GaugeAddSubtractBehavior) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -597,7 +662,8 @@ TEST_F(StatsAccessLoggerTest, GaugeAddSubtractBehavior) { TEST_F(StatsAccessLoggerTest, GaugeAddZeroValue) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -627,7 +693,8 @@ TEST_F(StatsAccessLoggerTest, GaugeAddZeroValue) { TEST_F(StatsAccessLoggerTest, GaugeSubtractBeforeAdd) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -655,7 +722,8 @@ TEST_F(StatsAccessLoggerTest, GaugeSubtractBeforeAdd) { TEST_F(StatsAccessLoggerTest, GaugeMultipleSubAfterAdd) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -693,7 +761,8 @@ TEST_F(StatsAccessLoggerTest, GaugeMultipleSubAfterAdd) { TEST_F(StatsAccessLoggerTest, PairedSubtractIgnoresConfiguredValue) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -719,7 +788,8 @@ TEST_F(StatsAccessLoggerTest, PairedSubtractIgnoresConfiguredValue) { TEST_F(StatsAccessLoggerTest, DestructionSubtractsRemainingValue) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -751,7 +821,8 @@ TEST_F(StatsAccessLoggerTest, DestructionSubtractsRemainingValue) { TEST_F(StatsAccessLoggerTest, AccessLogStateDestructorSubtractsFromSavedGauge) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -810,7 +881,8 @@ TEST_F(StatsAccessLoggerTest, AccessLogStateDestructorSubtractsFromSavedGauge) { TEST_F(StatsAccessLoggerTest, SameGaugeAddSubtractDefinedTwice) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -851,7 +923,8 @@ TEST_F(StatsAccessLoggerTest, SameGaugeAddSubtractDefinedTwice) { TEST_F(StatsAccessLoggerTest, GaugeNotSet) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -863,9 +936,34 @@ TEST_F(StatsAccessLoggerTest, GaugeNotSet) { "Stats logger gauge set operation must have a valid log type."); } +TEST_F(StatsAccessLoggerTest, StatsScope) { + const std::string yaml = R"EOF( + stats_scope: + max_counters: 10 + counters: + - stat: + name: counter + value_fixed: 1 +)EOF"; + + initialize(yaml); + + Formatter::Context formatter_context; + NiceMock stream_info; + + // The newly created scope for "test_scope" is stored at the end of `name_storages_` but + // since `scope_` only stores the last created scope for non-"scope_discovery", it should + // be exactly `scope_` here. + EXPECT_CALL(*scope_, counterFromStatNameWithTags(_, _)) + .WillOnce(testing::ReturnRef(store_.counter_)); + EXPECT_CALL(store_.counter_, add(1)); + logger_->log(formatter_context, stream_info); +} + TEST_F(StatsAccessLoggerTest, DropStatAction) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -896,7 +994,8 @@ TEST_F(StatsAccessLoggerTest, DropStatAction) { logger_->log(formatter_context_, stream_info_); const std::string yaml2 = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -929,7 +1028,8 @@ TEST_F(StatsAccessLoggerTest, DropStatAction) { TEST_F(StatsAccessLoggerTest, DropStatActionOnHistogram) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix histograms: - stat: name: histogram @@ -961,7 +1061,8 @@ TEST_F(StatsAccessLoggerTest, DropStatActionOnHistogram) { logger_->log(formatter_context_, stream_info_); const std::string yaml2 = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix histograms: - stat: name: histogram @@ -997,7 +1098,8 @@ TEST_F(StatsAccessLoggerTest, DropStatActionOnHistogram) { TEST_F(StatsAccessLoggerTest, StatTagFilterUpdateTag) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -1040,7 +1142,8 @@ TEST_F(StatsAccessLoggerTest, StatTagFilterUpdateTag) { TEST_F(StatsAccessLoggerTest, StatTagFilterDropTag) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix counters: - stat: name: counter @@ -1080,7 +1183,8 @@ TEST_F(StatsAccessLoggerTest, StatTagFilterDropTag) { TEST_F(StatsAccessLoggerTest, DropStatActionOnGauge) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -1114,7 +1218,8 @@ TEST_F(StatsAccessLoggerTest, DropStatActionOnGauge) { logger_->log(formatter_context_, stream_info_); const std::string yaml2 = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -1150,7 +1255,8 @@ TEST_F(StatsAccessLoggerTest, DropStatActionOnGauge) { TEST_F(StatsAccessLoggerTest, StatTagFilterUpdateTagOnGauge) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix gauges: - stat: name: gauge @@ -1198,7 +1304,8 @@ TEST_F(StatsAccessLoggerTest, StatTagFilterUpdateTagOnGauge) { TEST_F(StatsAccessLoggerTest, StatTagFilterUpdateTagOnHistogram) { const std::string yaml = R"EOF( - stat_prefix: test_stat_prefix + stats_scope: + prefix: test_stat_prefix histograms: - stat: name: histogram diff --git a/test/integration/server.h b/test/integration/server.h index eb5eb9ece35df..a2e9c3c09ecbc 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -169,6 +169,11 @@ class TestScopeWrapper : public Scope { Store& store() override { return store_; } const Store& constStore() const override { return store_; } + void setCleanupCallback(std::function callback) override { + Thread::LockGuard lock(lock_); + wrapped_scope_->setCleanupCallback(callback); + } + private: Thread::MutexBasicLockable& lock_; ScopeSharedPtr wrapped_scope_; diff --git a/test/mocks/server/server_factory_context.h b/test/mocks/server/server_factory_context.h index 059a50aaa9032..aa2a89bf6d8b4 100644 --- a/test/mocks/server/server_factory_context.h +++ b/test/mocks/server/server_factory_context.h @@ -144,6 +144,7 @@ class MockGenericFactoryContext : public GenericFactoryContext { MOCK_METHOD(ProtobufMessage::ValidationVisitor&, messageValidationVisitor, ()); MOCK_METHOD(Stats::Scope&, scope, ()); MOCK_METHOD(Stats::Scope&, statsScope, ()); + MOCK_METHOD(Stats::Scope&, serverScope, ()); MOCK_METHOD(Init::Manager&, initManager, ()); NiceMock server_context_; diff --git a/test/mocks/stats/mocks.h b/test/mocks/stats/mocks.h index 1d170dfa15401..3a664ac0ddb09 100644 --- a/test/mocks/stats/mocks.h +++ b/test/mocks/stats/mocks.h @@ -297,15 +297,20 @@ class MockScope : public TestUtil::TestScope { public: MockScope(StatName prefix, MockStore& store); - ScopeSharedPtr createScope(const std::string& name, bool, const ScopeStatsLimitSettings&, + ScopeSharedPtr createScope(const std::string& name, bool evictable, + const ScopeStatsLimitSettings& limits, StatsMatcherSharedPtr = nullptr) override { + checkCreateScopeArgs(evictable, limits); return ScopeSharedPtr(createScope_(name)); } - ScopeSharedPtr scopeFromStatName(StatName name, bool, const ScopeStatsLimitSettings&, + ScopeSharedPtr scopeFromStatName(StatName name, bool evictable, + const ScopeStatsLimitSettings& limits, StatsMatcherSharedPtr = nullptr) override { + checkCreateScopeArgs(evictable, limits); return createScope_(symbolTable().toString(name)); } + MOCK_METHOD(void, checkCreateScopeArgs, (bool, const ScopeStatsLimitSettings&)); MOCK_METHOD(ScopeSharedPtr, createScope_, (const std::string& name)); MOCK_METHOD(CounterOptConstRef, findCounter, (StatName), (const)); MOCK_METHOD(GaugeOptConstRef, findGauge, (StatName), (const)); From cf741e2a1c3bbfc04449f2907247a6e099402827 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 28 Apr 2026 10:24:19 +0100 Subject: [PATCH 149/750] oauth2: Fix `decryptToken` crash on spurious PKCS#7 success and add tests (#44683) Signed-off-by: Ryan Northey --- changelogs/current.yaml | 8 + .../extensions/filters/http/oauth2/filter.cc | 21 ++- .../filters/http/oauth2/filter_test.cc | 142 ++++++++++++++++++ tools/spelling/spelling_dictionary.txt | 1 + 4 files changed, 167 insertions(+), 5 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 3554909401707..935feff609cc6 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -18,6 +18,14 @@ bug_fixes: Fixed a crashing bug in the HTTP filter when a stream was already above the downstream write-buffer high watermark at filter-chain construction time. Downstream watermark callback registration is now deferred until the in-module filter has been constructed. +- area: oauth2 + change: | + Fixed a crash in the OAuth2 filter where AES-CBC decryption of token cookies could spuriously + succeed (~1/256) when the configured HMAC secret did not match the secret used to encrypt the + cookie (for example after secret rotation, or when receiving legacy unencrypted tokens). The + resulting binary "plaintext" was written back into the ``Cookie:`` request header and tripped a + ``HeaderString`` validation assert. Such plaintexts are now rejected and the original cookie value + is preserved, matching the behavior already documented for the explicit decryption-failure case. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/filters/http/oauth2/filter.cc b/source/extensions/filters/http/oauth2/filter.cc index 9b62fdf25382c..940e15f14f805 100644 --- a/source/extensions/filters/http/oauth2/filter.cc +++ b/source/extensions/filters/http/oauth2/filter.cc @@ -925,15 +925,26 @@ std::string OAuth2Filter::decryptToken(const std::string& encrypted_token) const } DecryptResult decrypt_result = decrypt(encrypted_token, config_->hmacSecret()); - if (decrypt_result.error.has_value()) { + + // Decryption can spuriously succeed against a token that was either never encrypted, or was + // encrypted under a different secret — PKCS#7 padding is valid by chance with probability + // ~1/256, leaving us with arbitrary binary bytes that would later fail HeaderString validation + // when written back into the Cookie header. Treat any plaintext that is not a valid header value + // as a decrypt failure and fall through to the legacy/wrong-secret behavior below. + const bool decrypt_failed = decrypt_result.error.has_value() || + !Http::HeaderUtility::headerValueIsValid(decrypt_result.plaintext); + + if (decrypt_failed) { ENVOY_STREAM_LOG(error, "failed to decrypt token: {}, error: {}", *decoder_callbacks_, - encrypted_token, decrypt_result.error.value()); + encrypted_token, + decrypt_result.error.value_or("plaintext is not a valid header value")); // There are two cases: // 1. The token is a legacy unencrypted token. // In this case, we return the token as-is to allow the request to proceed. - // 2. The token is encrypted, but the decryption failed due to the HMAC secret is changed. - // In this case, we return the original encrypted token, the HMAC validation will fail - // and the user will be redirected to the OAuth server for re-authentication. + // 2. The token is encrypted, but the decryption failed (or produced invalid plaintext) due to + // the HMAC secret being changed. In this case, we return the original encrypted token; the HMAC + // validation will fail and the user will be redirected to the OAuth server for + // re-authentication. return encrypted_token; } return decrypt_result.plaintext; diff --git a/test/extensions/filters/http/oauth2/filter_test.cc b/test/extensions/filters/http/oauth2/filter_test.cc index 2f1d7e7d37cfb..ada81cfcfb250 100644 --- a/test/extensions/filters/http/oauth2/filter_test.cc +++ b/test/extensions/filters/http/oauth2/filter_test.cc @@ -316,6 +316,34 @@ class OAuth2Test : public testing::TestWithParam { return c; } + // Test helpers exposing private OAuth2Filter methods. OAuth2Filter declares + // `friend class OAuth2Test`, but `TEST_F(OAuth2Test, ...)` expands to a class + // *derived* from OAuth2Test, and C++ friendship is not inherited — so the + // test bodies cannot call the privates directly. These wrappers bridge that. + // + // We also prime `filter_->config_` here. In production it is set inside + // resolveAndSetActiveConfig() which only runs from decodeHeaders(); the tests + // below (DecryptTokenSameSecret, DecryptTokenDecryptionFails, + // DecryptTokenSpuriousSuccessReturnsOriginalInput, GarbagePlaintextCookieDoesNotCrash) + // call encryptToken/decryptToken directly without first going through + // decodeHeaders, so without this priming they would dereference a null + // config_ inside encryptToken/decryptToken (segfault at small offset, e.g. 0x318). + // DecryptTokenEmpty doesn't crash because decryptToken early-returns on empty + // input before touching config_. + void primeActiveConfigForTest() const { + if (filter_->config_ == nullptr) { + filter_->resolveAndSetActiveConfig(); + } + } + std::string encryptTokenForTest(const std::string& token) const { + primeActiveConfigForTest(); + return filter_->encryptToken(token); + } + std::string decryptTokenForTest(const std::string& ct) const { + primeActiveConfigForTest(); + return filter_->decryptToken(ct); + } + // Validates the behavior of the cookie validator. void expectValidCookies(const CookieNames& cookie_names, const std::string& cookie_domain) { // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. @@ -5426,6 +5454,120 @@ TEST_F(OAuth2Test, OAuthCallbackGetAccessTokenSyncContinue) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); } +/** + * Scenario: decryptToken is called with an empty string. + * Expected behavior: returns empty string. + */ +TEST_F(OAuth2Test, DecryptTokenEmpty) { EXPECT_EQ(decryptTokenForTest(""), ""); } + +/** + * Scenario: decryptToken is called with a ciphertext that was encrypted with the same HMAC secret. + * Expected behavior: returns the original plaintext. + */ +TEST_F(OAuth2Test, DecryptTokenSameSecret) { + const std::string plaintext = "some_access_token_value"; + const std::string ciphertext = encryptTokenForTest(plaintext); + EXPECT_EQ(decryptTokenForTest(ciphertext), plaintext); +} + +/** + * Scenario: decryptToken is called with a token that is not a valid AES ciphertext (e.g. a + * legacy unencrypted token or a ciphertext from a different key where PKCS#7 padding fails). + * Expected behavior: returns the original input unchanged and emits an error log. + */ +TEST_F(OAuth2Test, DecryptTokenDecryptionFails) { + // This looks like a bearer token but is not valid AES-CBC ciphertext encrypted under the + // filter's HMAC secret. EVP_DecryptFinal_ex will reject the PKCS#7 padding. + const std::string unencrypted = "j5Vhtnz_uyhDVTrSri3GzLoroprQYVoXsp61kIq_JC4"; + EXPECT_LOG_CONTAINS("error", "failed to decrypt token", + { EXPECT_EQ(decryptTokenForTest(unencrypted), unencrypted); }); +} + +/** + * Scenario: decryptToken is called with a ciphertext that, when AES-CBC decrypted under the + * filter's HMAC secret, passes the PKCS#7 padding check but produces bytes that are not valid + * HTTP header field values (e.g. null bytes, control characters). + * + * This is the "spurious success" path: PKCS#7 padding validation passes with ~1/256 probability + * when decrypting ciphertext under the wrong key, but the resulting "plaintext" is binary garbage. + * The same path is reachable by explicitly encrypting a binary string with the correct key. + * + * Before the fix: decryptToken returns the garbage plaintext, which later crashes Envoy with + * HeaderStringValidator::assertValid() when the value is used in a Cookie header. + * After the fix: decryptToken detects the invalid plaintext and returns the original ciphertext. + */ +TEST_F(OAuth2Test, DecryptTokenSpuriousSuccessReturnsOriginalInput) { + // Encrypt a string containing bytes that are not valid HTTP header field values + // (null byte, control chars, high bytes). This ciphertext decrypts correctly under the filter's + // key, so EVP_DecryptFinal_ex succeeds, but the resulting plaintext fails headerValueIsValid. + const std::string binary_plaintext("\x00\x01\x02\xff", 4); + const std::string ciphertext = encryptTokenForTest(binary_plaintext); + // Tighter assertion: match on the message that is unique to the new headerValueIsValid branch + // ("plaintext is not a valid header value"), rather than the generic "failed to decrypt token" + // prefix that is shared with the pre-existing EVP_DecryptFinal_ex failure path. A regression + // that re-introduced the crash via the old error path would emit a different message and fail + // this test. + EXPECT_LOG_CONTAINS("error", "plaintext is not a valid header value", + { EXPECT_EQ(decryptTokenForTest(ciphertext), ciphertext); }); +} + +/** + * Scenario: A request arrives with a RefreshToken cookie whose AES-CBC decryption "succeeds" + * (PKCS#7 padding is valid) but produces binary garbage that is not a valid HTTP header value. + * This can happen when the HMAC secret has been rotated and the old ciphertext accidentally passes + * the padding check under the new key (~1/256 probability per token per request). + * + * Before the fix: decryptAndUpdateOAuthTokenCookies crashes with HeaderStringValidator::assertValid + * because the binary plaintext is StrJoin-ed into the Cookie header value. + * After the fix: decodeHeaders continues gracefully and returns a redirect to the OAuth server. + */ +TEST_F(OAuth2Test, GarbagePlaintextCookieDoesNotCrash) { + // Create a ciphertext that decrypts (under the filter's key) to binary bytes that are not valid + // HTTP header field values, simulating the spurious-success case. + const std::string binary_plaintext("\x00\x01\x02\xff", 4); + const std::string garbage_ciphertext = encryptTokenForTest(binary_plaintext); + + Http::TestRequestHeaderMapImpl request_headers{ + {Http::Headers::get().Path.get(), "/original_path"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + {Http::Headers::get().Scheme.get(), "https"}, + {Http::Headers::get().Cookie.get(), "RefreshToken=" + garbage_ciphertext}, + }; + + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + + // Assert the redirect actually happened: filter took the redirect path (302 with Location) + // rather than crashing or returning a 401. We only check Status=="302" and Location is set + // to avoid brittleness from Set-Cookie values, nonce, and CSRF in the full header map. + // redirectToOAuthServer calls decoder_callbacks_->encodeHeaders with end_stream=true. + EXPECT_CALL(decoder_callbacks_, + encodeHeaders_(testing::Truly([](const Http::ResponseHeaderMap& headers) { + return headers.getStatusValue() == "302" && + !headers.getLocationValue().empty(); + }), + true)); + + // Tighter assertion: match on the message that is unique to the new headerValueIsValid branch. + // A regression that re-introduced the crash via the old EVP_DecryptFinal_ex-failure path would + // emit a different message and cause this test to fail. + // Before the fix this line would trigger a crash (ENVOY_BUG assertion in HeaderStringValidator). + // After the fix it should complete without crashing and redirect to the OAuth server. + EXPECT_LOG_CONTAINS("error", "plaintext is not a valid header value", { + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, false)); + }); + + // After the fix, the Cookie header should contain the original (valid ASCII) ciphertext, + // not the binary garbage that decryption produced. + auto cookies = Http::Utility::parseCookies(request_headers); + // Use ASSERT_TRUE first so the test fails clearly (not with out_of_range) if the cookie were + // dropped instead of preserved. + ASSERT_TRUE(cookies.contains("RefreshToken")); + EXPECT_EQ(cookies.at("RefreshToken"), garbage_ciphertext); +} + } // namespace Oauth2 } // namespace HttpFilters } // namespace Extensions diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index a0054bcb6f295..de177c01c1ec8 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -113,6 +113,7 @@ ETX FS FIXME decrypted +decrypts gperf HEXDIG HEXDIGIT From 01077ebc9056a77cead15ba5764e96f75ac0b86b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 28 Apr 2026 09:32:01 -0400 Subject: [PATCH 150/750] lrs: fix load stats reporting to avoid dropping reports with valid data (#44380) Commit Message: fix load stats reporting to avoid dropping reports with valid data Additional Description: Currently, Envoy's load stats reporter only sends reports if `rq_issued != 0` or `rq_active != 0` during the polling window. This can cause load reports to be dropped even when there is valid data to report, such as: 1. A request completes (incrementing success or error counts) but no new requests are issued in the same interval. 1. Custom host metrics are recorded by an access logger. Since access loggers run *after* the stream is finished, the active request count may have already dropped to zero, causing the report (and the custom metrics) to be skipped if no other activity occurs. This PR introduces a new runtime flag `envoy.reloadable_features.report_load_for_non_zero_stats` (default false) that expands the check to send reports if *any* of the following are non-zero: `rq_success`, `rq_error`, `rq_active`, `rq_issued`, or if there are any host custom metrics present. Risk Level: Low (increases report frequency slightly, but only when data is present). Testing: - Unit tests in `load_stats_reporter_impl_test.cc` have been updated and pass. - Integration tests in `load_stats_integration_test.cc` were add to validate the new scenarios in which expect load reports. Docs Changes: None. Release Notes: Fixed a bug in load stats reporting where reports were dropped if only custom metrics or completed requests were present in a reporting interval. Platform Specific Features: None. Runtime guard: envoy.reloadable_features.report_load_for_non_zero_stats [Disclosed usage of generative AI: Yes, used to assist in code modifications.] --------- Signed-off-by: Frank Fort --- changelogs/current.yaml | 5 + source/common/runtime/runtime_features.cc | 1 + .../upstream/load_stats_reporter_impl.cc | 46 +++- .../upstream/load_stats_reporter_impl.h | 25 ++ .../upstream/load_stats_reporter_impl_test.cc | 215 +++++++++++++++++- .../load_stats_integration_test.cc | 73 ++++-- 6 files changed, 341 insertions(+), 24 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 935feff609cc6..b0d6910e78cbd 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -8,6 +8,11 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: load_report + change: | + Fixed a bug in load stats reporting where reports were dropped if only custom metrics or completed + requests were present in a reporting interval. This behavioral change can be reverted by setting + the runtime guard ``envoy.reloadable_features.report_load_for_non_zero_stats`` to ``false``. - area: build change: | Fixed ``Illegal ambiguous match`` error when building contrib targets with ``--config=aws-lc-fips`` diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index e9d2c7d87c409..773b517e38581 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -91,6 +91,7 @@ RUNTIME_GUARD(envoy_reloadable_features_quic_upstream_reads_fixed_number_packets RUNTIME_GUARD(envoy_reloadable_features_quic_upstream_socket_use_address_cache_for_read); RUNTIME_GUARD(envoy_reloadable_features_rbac_match_headers_individually); RUNTIME_GUARD(envoy_reloadable_features_reject_empty_trusted_ca_file); +RUNTIME_GUARD(envoy_reloadable_features_report_load_for_non_zero_stats); RUNTIME_GUARD(envoy_reloadable_features_report_load_when_rq_active_is_non_zero); RUNTIME_GUARD(envoy_reloadable_features_reset_ignore_upstream_reason); RUNTIME_GUARD(envoy_reloadable_features_reset_with_error); diff --git a/source/common/upstream/load_stats_reporter_impl.cc b/source/common/upstream/load_stats_reporter_impl.cc index c19abf5faa764..51e0ad4dd0d28 100644 --- a/source/common/upstream/load_stats_reporter_impl.cc +++ b/source/common/upstream/load_stats_reporter_impl.cc @@ -107,7 +107,7 @@ void LoadStatsReporterImpl::sendLoadStatsRequest() { uint64_t rq_error = 0; uint64_t rq_active = 0; uint64_t rq_issued = 0; - LoadMetricStats::StatMap load_metrics; + LoadMetricStats::StatMap aggregated_host_custom_metrics; envoy::config::endpoint::v3::UpstreamLocalityStats locality_stats; locality_stats.mutable_locality()->MergeFrom(hosts[0]->locality()); @@ -124,6 +124,15 @@ void LoadStatsReporterImpl::sendLoadStatsRequest() { bool endpoint_has_updates = (host_rq_success + host_rq_error + host_rq_active + host_rq_issued) != 0; + std::unique_ptr host_custom_metrics; + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.report_load_for_non_zero_stats")) { + host_custom_metrics = host->loadMetricStats().latch(); + if (host_custom_metrics != nullptr) { + endpoint_has_updates = true; + } + } + if (endpoint_has_updates) { rq_success += host_rq_success; rq_error += host_rq_error; @@ -142,15 +151,18 @@ void LoadStatsReporterImpl::sendLoadStatsRequest() { upstream_endpoint_stats->set_total_issued_requests(host_rq_issued); } - const std::unique_ptr latched_stats = - host->loadMetricStats().latch(); - if (latched_stats != nullptr) { - for (const auto& metric : *latched_stats) { + // TODO(fcfort): Remove this latch() call when cleaning up + // `report_load_for_non_zero_stats`. + if (host_custom_metrics == nullptr) { + host_custom_metrics = host->loadMetricStats().latch(); + } + if (host_custom_metrics != nullptr) { + for (const auto& metric : *host_custom_metrics) { const auto& metric_name = metric.first; const auto& metric_value = metric.second; // Add the metric to the load metrics map. - LoadMetricStats::Stat& stat = load_metrics[metric_name]; + LoadMetricStats::Stat& stat = aggregated_host_custom_metrics[metric_name]; stat.num_requests_with_metric += metric_value.num_requests_with_metric; stat.total_metric_value += metric_value.total_metric_value; @@ -168,14 +180,28 @@ void LoadStatsReporterImpl::sendLoadStatsRequest() { } } + // Upstream locality stats + bool should_send_locality_stats = rq_issued != 0; - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features." - "report_load_when_rq_active_is_non_zero")) { + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.report_load_for_non_zero_stats")) { + bool has_host_custom_metrics = false; + for (const auto& metric : aggregated_host_custom_metrics) { + if (metric.second.num_requests_with_metric != 0 || + metric.second.total_metric_value != 0) { + has_host_custom_metrics = true; + break; + } + } + should_send_locality_stats = rq_success != 0 || rq_error != 0 || rq_active != 0 || + rq_issued != 0 || has_host_custom_metrics; + } else if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features." + "report_load_when_rq_active_is_non_zero")) { // If rq_active is non-zero, we should send the locality stats even if // rq_issued is zero (no new requests have been issued in this poll // window). This is needed to report long-lived connections/requests (e.g., when // web-sockets are used). - should_send_locality_stats = should_send_locality_stats || (rq_active != 0); + should_send_locality_stats = (rq_issued != 0) || (rq_active != 0); } if (should_send_locality_stats) { @@ -183,7 +209,7 @@ void LoadStatsReporterImpl::sendLoadStatsRequest() { locality_stats.set_total_error_requests(rq_error); locality_stats.set_total_requests_in_progress(rq_active); locality_stats.set_total_issued_requests(rq_issued); - for (const auto& metric : load_metrics) { + for (const auto& metric : aggregated_host_custom_metrics) { auto* load_metric_stats = locality_stats.add_load_metric_stats(); load_metric_stats->set_metric_name(metric.first); load_metric_stats->set_num_requests_finished_with_metric( diff --git a/source/common/upstream/load_stats_reporter_impl.h b/source/common/upstream/load_stats_reporter_impl.h index 7b5a06bdaf6f5..35d90fe5dd097 100644 --- a/source/common/upstream/load_stats_reporter_impl.h +++ b/source/common/upstream/load_stats_reporter_impl.h @@ -14,6 +14,31 @@ namespace Envoy { namespace Upstream { +/** + * Implementation of LoadStatsReporter that streams load reports to a management server via gRPC. + * + * The reporter sends load statistics for clusters as directed by the management server. + * The frequency of reports is determined by the load_reporting_interval in the LoadStatsResponse. + * + * By default, if no runtime flags are set, load reports for a locality are sent only if the + * sum of `rq_total_` latched values for hosts in the locality is non-zero during the reporting + * interval. + * + * The following runtime features control the behavior of the load reporter: + * - envoy.reloadable_features.report_load_when_rq_active_is_non_zero: If true, load reports + * for a locality are sent if the sum of `rq_active_` values for hosts in the locality is + * non-zero, even if no new requests were issued in the interval. + * - envoy.reloadable_features.report_load_for_non_zero_stats: If true, load reports for a + * locality are sent if any of the following conditions are met for the sum of host stats in that + * locality: + * - Latched `rq_success_` is non-zero. + * - Latched `rq_error_` is non-zero. + * - Current `rq_active_` is non-zero. + * - Latched `rq_total_` is non-zero. + * - Any custom load metrics are non-zero in `LoadMetricStats`. + * + * Only one of these runtime features should be enabled at a time. + */ class LoadStatsReporterImpl : public LoadStatsReporter, public Grpc::AsyncStreamCallbacks, diff --git a/test/common/upstream/load_stats_reporter_impl_test.cc b/test/common/upstream/load_stats_reporter_impl_test.cc index 96bd903ec1716..e64a80ce76760 100644 --- a/test/common/upstream/load_stats_reporter_impl_test.cc +++ b/test/common/upstream/load_stats_reporter_impl_test.cc @@ -560,7 +560,8 @@ TEST_F(LoadStatsReporterImplTest, ReportLoadWhenRqActiveIsNonZero) { // Keep this test when deprecating the runtime flag. TestScopedRuntime scoped_runtime; scoped_runtime.mergeValues( - {{"envoy.reloadable_features.report_load_when_rq_active_is_non_zero", "true"}}); + {{"envoy.reloadable_features.report_load_for_non_zero_stats", "false"}, + {"envoy.reloadable_features.report_load_when_rq_active_is_non_zero", "true"}}); EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); expectSendMessage({}); @@ -610,6 +611,218 @@ TEST_F(LoadStatsReporterImplTest, ReportLoadWhenRqActiveIsNonZero) { response_timer_cb_(); } +// Validate that when envoy.reloadable_features.report_load_for_non_zero_stats is true, a load +// report is sent if only rq_success is non-zero. +TEST_F(LoadStatsReporterImplTest, ReportLoadForNonZeroStatsRqSuccess) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.report_load_when_rq_active_is_non_zero", "false"}, + {"envoy.reloadable_features.report_load_for_non_zero_stats", "true"}}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage({}); + createLoadStatsReporter(); + time_system_.setMonotonicTime(std::chrono::microseconds(100)); + + NiceMock cluster; + MockHostSet& host_set = *cluster.prioritySet().getMockHostSet(0); + ::envoy::config::core::v3::Locality locality; + locality.set_region("test_region"); + + HostSharedPtr host1 = makeTestHost("host1", locality); + host_set.hosts_per_locality_ = makeHostsPerLocality({{host1}}); + + // Set rq_success to non-zero, all others to zero. + host1->stats().rq_success_.inc(); + + cluster.info_->eds_service_name_ = "eds_service_for_foo"; + + ON_CALL(cm_, getActiveCluster("foo")) + .WillByDefault(Return(OptRef(cluster))); + deliverLoadStatsResponse({"foo"}); + time_system_.setMonotonicTime(std::chrono::microseconds(101)); + { + envoy::config::endpoint::v3::ClusterStats expected_cluster_stats; + + expected_cluster_stats.set_cluster_name("foo"); + expected_cluster_stats.set_cluster_service_name("eds_service_for_foo"); + expected_cluster_stats.mutable_load_report_interval()->MergeFrom( + Protobuf::util::TimeUtil::MicrosecondsToDuration(1)); + + auto* expected_locality_stats = expected_cluster_stats.add_upstream_locality_stats(); + expected_locality_stats->mutable_locality()->MergeFrom(locality); + expected_locality_stats->set_priority(0); + expected_locality_stats->set_total_successful_requests(1); + expected_locality_stats->set_total_error_requests(0); + expected_locality_stats->set_total_requests_in_progress(0); + expected_locality_stats->set_total_issued_requests(0); + + std::vector expected_cluster_stats_vector = { + expected_cluster_stats}; + + expectSendMessage(expected_cluster_stats_vector); + } + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); + response_timer_cb_(); +} + +// Validate that when envoy.reloadable_features.report_load_for_non_zero_stats is true, a load +// report is sent if only rq_error is non-zero. +TEST_F(LoadStatsReporterImplTest, ReportLoadForNonZeroStatsRqError) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.report_load_when_rq_active_is_non_zero", "false"}, + {"envoy.reloadable_features.report_load_for_non_zero_stats", "true"}}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage({}); + createLoadStatsReporter(); + time_system_.setMonotonicTime(std::chrono::microseconds(100)); + + NiceMock cluster; + MockHostSet& host_set = *cluster.prioritySet().getMockHostSet(0); + ::envoy::config::core::v3::Locality locality; + locality.set_region("test_region"); + + HostSharedPtr host1 = makeTestHost("host1", locality); + host_set.hosts_per_locality_ = makeHostsPerLocality({{host1}}); + + // Set rq_error to non-zero, all others to zero. + host1->stats().rq_error_.inc(); + + cluster.info_->eds_service_name_ = "eds_service_for_foo"; + + ON_CALL(cm_, getActiveCluster("foo")) + .WillByDefault(Return(OptRef(cluster))); + deliverLoadStatsResponse({"foo"}); + time_system_.setMonotonicTime(std::chrono::microseconds(101)); + { + envoy::config::endpoint::v3::ClusterStats expected_cluster_stats; + + expected_cluster_stats.set_cluster_name("foo"); + expected_cluster_stats.set_cluster_service_name("eds_service_for_foo"); + expected_cluster_stats.mutable_load_report_interval()->MergeFrom( + Protobuf::util::TimeUtil::MicrosecondsToDuration(1)); + + auto* expected_locality_stats = expected_cluster_stats.add_upstream_locality_stats(); + expected_locality_stats->mutable_locality()->MergeFrom(locality); + expected_locality_stats->set_priority(0); + expected_locality_stats->set_total_successful_requests(0); + expected_locality_stats->set_total_error_requests(1); + expected_locality_stats->set_total_requests_in_progress(0); + expected_locality_stats->set_total_issued_requests(0); + + std::vector expected_cluster_stats_vector = { + expected_cluster_stats}; + + expectSendMessage(expected_cluster_stats_vector); + } + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); + response_timer_cb_(); +} + +// Validate that when envoy.reloadable_features.report_load_for_non_zero_stats is true, a load +// report is sent if only custom metrics are present. +TEST_F(LoadStatsReporterImplTest, ReportLoadForNonZeroStatsCustomMetric) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.report_load_when_rq_active_is_non_zero", "false"}, + {"envoy.reloadable_features.report_load_for_non_zero_stats", "true"}}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage({}); + createLoadStatsReporter(); + time_system_.setMonotonicTime(std::chrono::microseconds(100)); + + NiceMock cluster; + MockHostSet& host_set = *cluster.prioritySet().getMockHostSet(0); + ::envoy::config::core::v3::Locality locality; + locality.set_region("test_region"); + + HostSharedPtr host1 = makeTestHost("host1", locality); + host_set.hosts_per_locality_ = makeHostsPerLocality({{host1}}); + + // Add a custom metric, all other counters zero. + host1->loadMetricStats().add("metric_a", 1.0); + + cluster.info_->eds_service_name_ = "eds_service_for_foo"; + + ON_CALL(cm_, getActiveCluster("foo")) + .WillByDefault(Return(OptRef(cluster))); + deliverLoadStatsResponse({"foo"}); + time_system_.setMonotonicTime(std::chrono::microseconds(101)); + { + envoy::config::endpoint::v3::ClusterStats expected_cluster_stats; + + expected_cluster_stats.set_cluster_name("foo"); + expected_cluster_stats.set_cluster_service_name("eds_service_for_foo"); + expected_cluster_stats.mutable_load_report_interval()->MergeFrom( + Protobuf::util::TimeUtil::MicrosecondsToDuration(1)); + + auto* expected_locality_stats = expected_cluster_stats.add_upstream_locality_stats(); + expected_locality_stats->mutable_locality()->MergeFrom(locality); + expected_locality_stats->set_priority(0); + expected_locality_stats->set_total_successful_requests(0); + expected_locality_stats->set_total_error_requests(0); + expected_locality_stats->set_total_requests_in_progress(0); + expected_locality_stats->set_total_issued_requests(0); + addStatExpectation(expected_locality_stats, "metric_a", 1, 1.0); + + std::vector expected_cluster_stats_vector = { + expected_cluster_stats}; + + expectSendMessage(expected_cluster_stats_vector); + } + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); + response_timer_cb_(); +} + +// Validate that when envoy.reloadable_features.report_load_for_non_zero_stats is false, a load +// report is NOT sent if only rq_success is non-zero. +TEST_F(LoadStatsReporterImplTest, ReportLoadForNonZeroStatsDisabled) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.report_load_for_non_zero_stats", "false"}}); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage({}); + createLoadStatsReporter(); + time_system_.setMonotonicTime(std::chrono::microseconds(100)); + + NiceMock cluster; + MockHostSet& host_set = *cluster.prioritySet().getMockHostSet(0); + ::envoy::config::core::v3::Locality locality; + locality.set_region("test_region"); + + HostSharedPtr host1 = makeTestHost("host1", locality); + host_set.hosts_per_locality_ = makeHostsPerLocality({{host1}}); + + // Set rq_success to non-zero, all others to zero. + host1->stats().rq_success_.inc(); + + cluster.info_->eds_service_name_ = "eds_service_for_foo"; + + ON_CALL(cm_, getActiveCluster("foo")) + .WillByDefault(Return(OptRef(cluster))); + deliverLoadStatsResponse({"foo"}); + time_system_.setMonotonicTime(std::chrono::microseconds(101)); + { + // Expect no UpstreamLocalityStats + envoy::config::endpoint::v3::ClusterStats expected_cluster_stats; + expected_cluster_stats.set_cluster_name("foo"); + expected_cluster_stats.set_cluster_service_name("eds_service_for_foo"); + expected_cluster_stats.mutable_load_report_interval()->MergeFrom( + Protobuf::util::TimeUtil::MicrosecondsToDuration(1)); + + std::vector expected_cluster_stats_vector = { + expected_cluster_stats}; + + expectSendMessage(expected_cluster_stats_vector); + } + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); + response_timer_cb_(); +} + } // namespace } // namespace Upstream } // namespace Envoy diff --git a/test/integration/load_stats_integration_test.cc b/test/integration/load_stats_integration_test.cc index 242ea42ffe8c5..5449c87832032 100644 --- a/test/integration/load_stats_integration_test.cc +++ b/test/integration/load_stats_integration_test.cc @@ -728,9 +728,9 @@ TEST_P(LoadStatsIntegrationTest, InProgressThenSuccess) { waitForUpstreamResponse(0, 200); // Second window: - // rq_issued=0, rq_active=0. Stats NOT sent for the locality. - // We expect cluster stats to be present but with empty locality stats. - ASSERT_TRUE(waitForLoadStatsRequest({}, 0, false, true)); + // rq_success=1. Stats are sent for the locality. + ASSERT_TRUE( + waitForLoadStatsRequest({localityStats("winter", /*success*/ 1, 0, 0, 0)}, 0, false, true)); cleanupUpstreamAndDownstream(); cleanupLoadStatsConnection(); @@ -749,19 +749,15 @@ TEST_P(LoadStatsIntegrationTest, RequestActiveForMultipleWindows) { // First window: stats should be sent because rq_issued=1, rq_active=1. ASSERT_TRUE(waitForLoadStatsRequest({localityStats("winter", 0, 0, 1, 1)})); - // Second window: request is still active. - // Stats ARE sent because rq_active=1 and the runtime feature - // "envoy.reloadable_features.report_load_when_rq_active_is_non_zero" is - // enabled by default. - ASSERT_TRUE(waitForLoadStatsRequest({localityStats("winter", 0, 0, 1, 0)})); - // Finish the request now waitForUpstreamResponse(0, 200); - // Third window: Stats are NOT sent because rq_issued=0 and rq_active=0. - // Even though rq_success=1, it is not checked by the current logic. - // This demonstrates that success/error stats are lost if no new requests are - // issued in the window. + // Second window: rq_active=0 and rq_success=1. Stats ARE sent for the locality because of the + // rq_success=1. + ASSERT_TRUE( + waitForLoadStatsRequest({localityStats("winter", /*success*/ 1, 0, 0, 0)}, 0, false, true)); + + // Third window: rq_success=0. Stats are NOT sent for the locality. ASSERT_TRUE(waitForLoadStatsRequest({}, 0, false, true)); cleanupUpstreamAndDownstream(); @@ -1033,5 +1029,56 @@ TEST_P(LoadStatsIntegrationTest, EndpointLevelStatsReportingSuccessAndFailure) { cleanupLoadStatsConnection(); } +// Validate that load reports are sent when only a successful request occurs within the interval. +TEST_P(LoadStatsIntegrationTest, ReportLoadForNonZeroStatsSuccessOnly) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.report_load_for_non_zero_stats", + "true"); + initialize(); + + waitForLoadStatsStream(); + ASSERT_TRUE(waitForLoadStatsRequest({})); + loadstats_stream_->startGrpcStream(); + + requestLoadStatsResponse({"cluster_0"}); + updateClusterLoadAssignment({{0}}, {}, {}, {}); + + sendAndReceiveUpstream(0, 200); + + ASSERT_TRUE(waitForLoadStatsRequest({localityStats("winter", 1, 0, 0, 1)})); + + // In the next interval, there are no new requests and no active requests, so no report is sent. + cleanupLoadStatsConnection(); +} + +// Validate that load reports are sent when only a custom metric is present. +TEST_P(LoadStatsIntegrationTest, ReportLoadForNonZeroStatsCustomMetricOnly) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.report_load_for_non_zero_stats", + "true"); + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* cluster_0 = bootstrap.mutable_static_resources()->mutable_clusters(0); + cluster_0->add_lrs_report_endpoint_metrics("cpu_utilization"); + }); + initialize(); + + waitForLoadStatsStream(); + ASSERT_TRUE(waitForLoadStatsRequest({})); + loadstats_stream_->startGrpcStream(); + + requestLoadStatsResponse({"cluster_0"}); + updateClusterLoadAssignment({{0}}, {}, {}, {}); + + sendAndReceiveUpstream(0, 200, true); + + auto expected_uls = localityStats("winter", 1, 0, 0, 1); + auto* metric = expected_uls.add_load_metric_stats(); + metric->set_metric_name("cpu_utilization"); + metric->set_num_requests_finished_with_metric(1); + metric->set_total_metric_value(0.3); + ASSERT_TRUE(waitForLoadStatsRequest({expected_uls})); + + // In the next interval, there are no new requests and no custom metrics, so no report is sent. + cleanupLoadStatsConnection(); +} + } // namespace } // namespace Envoy From 9b0ee6c223695e390c97792efc9a93887040cc2d Mon Sep 17 00:00:00 2001 From: code Date: Wed, 29 Apr 2026 00:30:18 +0800 Subject: [PATCH 151/750] Welcome derek back to community as code owner of lua (#44693) Commit Message: Welcome derek back to community as code owner of lua Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: wbpcode --- CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 5720954be9dcc..8fe9e2f4cc409 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -303,8 +303,8 @@ extensions/upstreams/tcp @ggreenway @mattklein123 # health check /*/extensions/filters/http/health_check @mattklein123 @adisuissa # lua -/*/extensions/filters/http/lua @mattklein123 @wbpcode -/*/extensions/filters/common/lua @mattklein123 @wbpcode +/*/extensions/filters/http/lua @mattklein123 @wbpcode @derekargueta +/*/extensions/filters/common/lua @mattklein123 @wbpcode @derekargueta # rbac /*/extensions/filters/network/rbac @yangminzhu @yanavlasov /*/extensions/filters/http/rbac @yangminzhu @yanavlasov From 4a89303a2aadcd6fee657d15edf52d2057e65439 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 28 Apr 2026 17:48:29 +0100 Subject: [PATCH 152/750] deps/actions: Bump toolshed actions -> 0.4.10 (#44697) Signed-off-by: Ryan Northey --- .github/workflows/_check_coverage.yml | 4 +-- .github/workflows/_cve_fetch.yml | 2 +- .github/workflows/_cve_scan.yml | 2 +- .github/workflows/_finish.yml | 8 ++--- .github/workflows/_load.yml | 10 +++--- .github/workflows/_load_env.yml | 8 ++--- .github/workflows/_precheck_publish.yml | 2 +- .github/workflows/_publish_release.yml | 4 +-- .../workflows/_publish_release_container.yml | 6 ++-- .github/workflows/_publish_verify.yml | 2 +- .github/workflows/_request.yml | 24 ++++++------- .github/workflows/_request_cache_bazel.yml | 10 +++--- .github/workflows/_request_cache_docker.yml | 10 +++--- .github/workflows/_request_checks.yml | 10 +++--- .github/workflows/_run.yml | 28 +++++++-------- .github/workflows/_upload_gcs.yml | 6 ++-- .github/workflows/codeql-daily.yml | 4 +-- .github/workflows/codeql-push.yml | 4 +-- .github/workflows/command.yml | 6 ++-- .github/workflows/envoy-dependency.yml | 18 +++++----- .github/workflows/envoy-release.yml | 34 +++++++++---------- .github/workflows/envoy-sync.yml | 4 +-- .github/workflows/mobile-android_build.yml | 12 +++---- .github/workflows/mobile-ios_build.yml | 4 +-- 24 files changed, 111 insertions(+), 111 deletions(-) diff --git a/.github/workflows/_check_coverage.yml b/.github/workflows/_check_coverage.yml index 090ff4a8b9264..c1c841ba5df4d 100644 --- a/.github/workflows/_check_coverage.yml +++ b/.github/workflows/_check_coverage.yml @@ -58,7 +58,7 @@ jobs: upload-name: coverage upload-path: generated/coverage/html steps-post: | - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: output-path: generated/coverage/html/gcs-metadata.json input-format: yaml @@ -79,7 +79,7 @@ jobs: - target: fuzz_coverage name: Fuzz coverage steps-post: | - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: output-path: generated/fuzz_coverage/html/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_cve_fetch.yml b/.github/workflows/_cve_fetch.yml index ba7c60fa84424..73421a8f03d7c 100644 --- a/.github/workflows/_cve_fetch.yml +++ b/.github/workflows/_cve_fetch.yml @@ -35,7 +35,7 @@ jobs: else echo "weekly_run=false" >> $GITHUB_OUTPUT fi - - uses: envoyproxy/toolshed/actions/gcp/setup@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/gcp/setup@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_cve_scan.yml b/.github/workflows/_cve_scan.yml index 0237e10b812af..d89d99f7b5032 100644 --- a/.github/workflows/_cve_scan.yml +++ b/.github/workflows/_cve_scan.yml @@ -28,7 +28,7 @@ jobs: id: vars run: | echo "cve-data-path=${{ inputs.cve-data-path }}" > $GITHUB_OUTPUT - - uses: envoyproxy/toolshed/actions/gcp/setup@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/gcp/setup@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_finish.yml b/.github/workflows/_finish.yml index 1efa76fafe928..ed5289310e577 100644 --- a/.github/workflows/_finish.yml +++ b/.github/workflows/_finish.yml @@ -36,7 +36,7 @@ jobs: actions: read contents: read steps: - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Incoming data id: needs with: @@ -87,7 +87,7 @@ jobs: summary: "Check has finished", text: $text}}}} - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Print summary with: input: ${{ toJSON(steps.needs.outputs.value).summary-title }} @@ -95,13 +95,13 @@ jobs: "## \(.)" options: -Rr output-path: GITHUB_STEP_SUMMARY - - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/checks@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Update check with: action: update diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index 989bde97cd3db..a38057a9d945e 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -102,7 +102,7 @@ jobs: # Handle any failure in triggering job # Remove any `checks` we dont care about # Prepare a check request - - uses: envoyproxy/toolshed/actions/github/env/load@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/env/load@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Load env id: data with: @@ -113,13 +113,13 @@ jobs: GH_TOKEN: ${{ github.token }} # Update the check - - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/checks@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Update check if: ${{ fromJSON(steps.data.outputs.data).data.check.action == 'RUN' }} with: @@ -127,7 +127,7 @@ jobs: checks: ${{ toJSON(fromJSON(steps.data.outputs.data).checks) }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Print request summary with: input: | @@ -147,7 +147,7 @@ jobs: | $summary.summary as $summary | "${{ inputs.template-request-summary }}" - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: request-output name: Load request with: diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index e7ca999ee13cb..211f152d30184 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -62,18 +62,18 @@ jobs: request: ${{ steps.env.outputs.data }} trusted: true steps: - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: checkout name: Checkout Envoy repository - name: Generate environment variables - uses: envoyproxy/toolshed/actions/envoy/ci/env@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/envoy/ci/env@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: env with: branch-name: ${{ inputs.branch-name }} @@ -85,7 +85,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/env/summary@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} diff --git a/.github/workflows/_precheck_publish.yml b/.github/workflows/_precheck_publish.yml index fb346c37b7793..625028654837b 100644 --- a/.github/workflows/_precheck_publish.yml +++ b/.github/workflows/_precheck_publish.yml @@ -85,7 +85,7 @@ jobs: upload-name: docs upload-path: generated/docs steps-post: | - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: output-path: generated/docs/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_publish_release.yml b/.github/workflows/_publish_release.yml index 37a8507adf064..f3145d869e2a9 100644 --- a/.github/workflows/_publish_release.yml +++ b/.github/workflows/_publish_release.yml @@ -135,12 +135,12 @@ jobs: needs: - release steps: - - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/actions/dispatch@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/dispatch@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: ref: main repository: ${{ fromJSON(inputs.request).request.version.dev && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} diff --git a/.github/workflows/_publish_release_container.yml b/.github/workflows/_publish_release_container.yml index 0979fb032b5e4..b3d4afb63c615 100644 --- a/.github/workflows/_publish_release_container.yml +++ b/.github/workflows/_publish_release_container.yml @@ -56,7 +56,7 @@ jobs: - name: Generate manifest configuration (dev) id: dev-config if: ${{ inputs.dev && inputs.target-branch == 'main' }} - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: input-format: yaml filter: >- @@ -135,7 +135,7 @@ jobs: - tools-dev-${{ github.sha }} - name: Generate manifest configuration (release) - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: release-config if: ${{ ! inputs.dev || ! inputs.target-branch != 'main' }} with: @@ -225,7 +225,7 @@ jobs: - tools-v${{ inputs.version-major }}.${{ inputs.version-minor }}-latest - name: Collect and push OCI artifacts - uses: envoyproxy/toolshed/actions/oci/collector@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/oci/collector@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: artifacts-pattern: oci.* manifest-config: ${{ steps.dev-config.outputs.value || steps.release-config.outputs.value }} diff --git a/.github/workflows/_publish_verify.yml b/.github/workflows/_publish_verify.yml index 61dd96ca92846..1e8450654c12e 100644 --- a/.github/workflows/_publish_verify.yml +++ b/.github/workflows/_publish_verify.yml @@ -108,7 +108,7 @@ jobs: export NO_BUILD_SETUP=1 steps-pre: | - id: version-support - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: input: | version_major: ${{ fromJSON(inputs.request).request.version.major }} diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index b2272cb63abd5..472f2403a8f07 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -55,14 +55,14 @@ jobs: caches: ${{ steps.caches.outputs.value }} config: ${{ steps.config.outputs.config }} steps: - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: checkout name: Checkout Envoy repository (requested) with: @@ -76,7 +76,7 @@ jobs: # *ALL* variables collected should be treated as untrusted and should be sanitized before # use - name: Generate environment variables from commit - uses: envoyproxy/toolshed/actions/envoy/ci/request@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/envoy/ci/request@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: env with: branch-name: ${{ steps.checkout.outputs.branch-name }} @@ -87,7 +87,7 @@ jobs: vars: ${{ toJSON(vars) }} working-directory: requested - - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -95,7 +95,7 @@ jobs: config: | fetch-depth: 1 path: target - - uses: envoyproxy/toolshed/actions/hashfiles@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/hashfiles@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: bazel-cache-hash name: Bazel cache hash with: @@ -104,7 +104,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/env/summary@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} @@ -120,28 +120,28 @@ jobs: target-branch: ${{ fromJSON(steps.env.outputs.data).request.target-branch }} - id: cache-id-bazel-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-arm64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-arm64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-docs-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-docs-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-external-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-external-x64 wf-path: .github/workflows/request.yml - name: Environment data - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: data with: input: | @@ -189,7 +189,7 @@ jobs: path: /tmp/cache key: ${{ fromJSON(steps.data.outputs.value).request.build-image.default }}-arm64 - name: Caches - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: caches with: input-format: yaml diff --git a/.github/workflows/_request_cache_bazel.yml b/.github/workflows/_request_cache_bazel.yml index bf7e10462243e..7dac1a693413c 100644 --- a/.github/workflows/_request_cache_bazel.yml +++ b/.github/workflows/_request_cache_bazel.yml @@ -59,7 +59,7 @@ jobs: && ! fromJSON(inputs.caches).bazel[format('{0}-{1}', inputs.output-base, inputs.arch)]) }} steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/bind-mounts@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: mounts: | - src: /mnt/workspace @@ -73,9 +73,9 @@ jobs: target: /build chown: "runner:docker" - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/diskspace@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 if: inputs.arch == 'x64' && github.event.repository.private - - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -83,14 +83,14 @@ jobs: config: | fetch-depth: 1 - - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/cache/prime@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/cache/prime@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: bazel-cache name: Prime Bazel cache with: diff --git a/.github/workflows/_request_cache_docker.yml b/.github/workflows/_request_cache_docker.yml index 1e3664f255b0b..f2701e7603434 100644 --- a/.github/workflows/_request_cache_docker.yml +++ b/.github/workflows/_request_cache_docker.yml @@ -39,7 +39,7 @@ on: # For a job that does, you can restore with something like: # # steps: -# - uses: envoyproxy/toolshed/actions/docker/cache/restore@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 +# - uses: envoyproxy/toolshed/actions/docker/cache/restore@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 # with: # key: "${{ needs.env.outputs.build-image }}" # @@ -51,13 +51,13 @@ jobs: name: "[${{ inputs.arch }}] Prime Docker cache" if: ${{ ! fromJSON(inputs.caches).docker[inputs.arch] }} steps: - - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/docker/cache/prime@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/docker/cache/prime@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: docker name: Prime Docker cache (${{ inputs.image-tag }}${{ inputs.cache-suffix }}) with: @@ -65,7 +65,7 @@ jobs: key-suffix: ${{ inputs.cache-suffix }} lock-token: ${{ steps.appauth.outputs.token }} lock-repository: ${{ inputs.lock-repository }} - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: data name: Cache data with: @@ -73,7 +73,7 @@ jobs: input: | cached: ${{ steps.docker.outputs.cached }} key: ${{ inputs.image-tag }}${{ inputs.cache-suffix }} - - uses: envoyproxy/toolshed/actions/json/table@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/json/table@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Summary with: json: ${{ steps.data.outputs.value }} diff --git a/.github/workflows/_request_checks.yml b/.github/workflows/_request_checks.yml index 0a3a8c87c8740..77d06ff1085ae 100644 --- a/.github/workflows/_request_checks.yml +++ b/.github/workflows/_request_checks.yml @@ -55,7 +55,7 @@ jobs: runs-on: ${{ fromJSON(inputs.env).config.ci.agent-ubuntu }} name: Start checks steps: - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: check-config name: Prepare check data with: @@ -78,13 +78,13 @@ jobs: | .skipped.output.summary = "${{ inputs.skipped-summary }}" | .skipped.output.text = "" - - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/checks@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Start checks id: checks with: @@ -95,7 +95,7 @@ jobs: ${{ fromJSON(inputs.env).summary.summary }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/json/table@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/json/table@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Summary with: collapse-open: true @@ -119,7 +119,7 @@ jobs: output-path: GITHUB_STEP_SUMMARY title: Checks started/skipped - - uses: envoyproxy/toolshed/actions/github/env/save@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/env/save@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Save env id: data with: diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index a09c8da32a9c0..fd9ac09eaa628 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -143,7 +143,7 @@ on: summary-post: type: string default: | - - uses: envoyproxy/toolshed/actions/envoy/run/summary@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/envoy/run/summary@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: context: %{{ inputs.context }} steps-pre: @@ -212,7 +212,7 @@ jobs: name: ${{ inputs.target-suffix && format('[{0}] ', inputs.target-suffix) || '' }}${{ inputs.command }} ${{ inputs.target }} timeout-minutes: ${{ inputs.timeout-minutes }} steps: - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: started name: Create timestamp with: @@ -220,7 +220,7 @@ jobs: filter: | now # This controls which input vars are exposed to the run action (and related steps) - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Context id: context with: @@ -253,14 +253,14 @@ jobs: fi echo "mnt-available=$MNT_AVAILABLE" >> "$GITHUB_OUTPUT" id: disk - - uses: envoyproxy/toolshed/actions/github/remnt@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/remnt@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 if: steps.disk.outputs.should-remnt == 'true' - - uses: envoyproxy/toolshed/actions/bind-mounts@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/bind-mounts@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 if: inputs.bind-mount && steps.disk.outputs.mnt-available == 'true' with: mounts: ${{ inputs.bind-mounts }} - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/diskspace@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 if: inputs.diskspace-hack || steps.disk.outputs.mnt-available != 'true' with: to_remove: ${{ inputs.diskspace-hack-paths }} @@ -268,7 +268,7 @@ jobs: mount df -h - - uses: envoyproxy/toolshed/actions/bson@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/bson@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Configure Docker if: runner.os == 'Linux' with: @@ -284,7 +284,7 @@ jobs: | "${{ inputs.template-docker-configure }}" # Caches - - uses: envoyproxy/toolshed/actions/cache/restore@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/cache/restore@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 if: >- fromJSON(inputs.bazel-cache) name: >- @@ -325,12 +325,12 @@ jobs: key: ${{ inputs.cache-build-image }}${{ inputs.cache-build-image-key-suffix }} - if: ${{ inputs.cache-build-image && steps.cache-lookup.outputs.cache-hit == 'true' }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/actions/docker/cache/restore@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/docker/cache/restore@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: image-tag: ${{ inputs.cache-build-image }} key-suffix: ${{ inputs.cache-build-image-key-suffix }} - - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: appauth name: Appauth if: ${{ inputs.trusted }} @@ -341,7 +341,7 @@ jobs: # - the workaround is to allow the token to be passed through. token: ${{ github.token }} token-ok: true - - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: checkout name: Checkout Envoy repository with: @@ -357,7 +357,7 @@ jobs: token: ${{ inputs.trusted && steps.appauth.outputs.token || github.token }} # This is currently only use by mobile-docs and can be removed once they are updated to the newer website - - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: checkout-extra name: Checkout extra repository (for publishing) if: ${{ inputs.checkout-extra }} @@ -366,7 +366,7 @@ jobs: ssh-key: ${{ inputs.trusted && inputs.ssh-key-extra || '' }} - name: Import GPG key - uses: envoyproxy/toolshed/actions/gpg/import@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/gpg/import@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 if: ${{ inputs.import-gpg }} with: key: ${{ secrets.gpg-key }} @@ -388,7 +388,7 @@ jobs: # NOTE: This is where untrusted code can be run!!! # It MUST be the last step in the workflow - - uses: envoyproxy/toolshed/actions/github/run@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/run@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: args: ${{ inputs.args != '--' && inputs.args || inputs.target }} diff --git a/.github/workflows/_upload_gcs.yml b/.github/workflows/_upload_gcs.yml index e0ed663bc1060..c35e4c1614039 100644 --- a/.github/workflows/_upload_gcs.yml +++ b/.github/workflows/_upload_gcs.yml @@ -29,17 +29,17 @@ jobs: with: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} - - uses: envoyproxy/toolshed/actions/jq@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: metadata with: input: ${{ matrix.artifact }}/gcs-metadata.json input-format: json-path - run: | rm -rf ${{ matrix.artifact }}/gcs-metadata.json - - uses: envoyproxy/toolshed/actions/gcp/setup@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/gcp/setup@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: key: ${{ secrets.gcp-key }} - - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: bucket: ${{ fromJSON(steps.metadata.outputs.value).bucket }} path: ${{ matrix.artifact }} diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 9e7b8ff9e6f82..6404b566694cc 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -27,7 +27,7 @@ jobs: steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/bind-mounts@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 if: | ! github.event.repository.private with: @@ -42,7 +42,7 @@ jobs: if: | env.BUILD_TARGETS != '' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/diskspace@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index d6947b0ea36fb..764fbb5699f3b 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-22.04 if: github.repository == 'envoyproxy/envoy' steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/bind-mounts@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 if: | ! github.event.repository.private with: @@ -48,7 +48,7 @@ jobs: if: | env.BUILD_TARGETS != '' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/diskspace@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/command.yml b/.github/workflows/command.yml index 30466cae04104..13e387799810c 100644 --- a/.github/workflows/command.yml +++ b/.github/workflows/command.yml @@ -28,7 +28,7 @@ jobs: && github.actor != 'dependabot[bot]' }} steps: - - uses: envoyproxy/toolshed/actions/github/command@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/command@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Parse command from comment id: command with: @@ -37,14 +37,14 @@ jobs: ^/(retest) # /retest - - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 if: ${{ steps.command.outputs.command == 'retest' }} id: appauth-retest name: Appauth (retest) with: key: ${{ secrets.ENVOY_CI_APP_KEY }} app_id: ${{ secrets.ENVOY_CI_APP_ID }} - - uses: envoyproxy/toolshed/actions/retest@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/retest@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 if: ${{ steps.command.outputs.command == 'retest' }} name: Retest with: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 6a286db746779..19e40232f46de 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -53,16 +53,16 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/bson@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/bson@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: update name: Update dependency (${{ inputs.dependency }}) with: @@ -97,13 +97,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/actions/upload/diff@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/upload/diff@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.update.outputs.output }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/actions/github/pr@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/pr@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: base: main body: | @@ -134,11 +134,11 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: checkout name: Checkout Envoy repository with: @@ -184,7 +184,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/actions/docker/shas@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/docker/shas@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: images: | sha-ci: docker.io/envoyproxy/envoy-build:ci-${{ steps.build-tools.outputs.tag }} @@ -222,7 +222,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/pr@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index f4008c077c650..3df9692d679bf 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -60,14 +60,14 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} @@ -88,10 +88,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/actions/email/validate@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/email/validate@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/actions/github/run@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/run@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Create release with: source: | @@ -116,7 +116,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/pr@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: base: ${{ github.ref_name }} commit: false @@ -142,19 +142,19 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/run@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Re-open branch with: command: >- @@ -169,7 +169,7 @@ jobs: name: Dev version id: dev - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/pr@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: base: ${{ github.ref_name }} commit: false @@ -193,20 +193,20 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/github/run@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Sync version histories with: command: >- @@ -216,7 +216,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/pr@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: append-commit-message: true base: ${{ github.ref_name }} @@ -243,13 +243,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: config: | fetch-depth: 0 @@ -279,13 +279,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - name: Checkout repository - uses: envoyproxy/toolshed/actions/github/checkout@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index a45681a85a30a..aac9e91670a17 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -32,12 +32,12 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/actions/appauth@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/actions/dispatch@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/dispatch@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index 3e6752ee89e7e..230822d99d8cf 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -103,9 +103,9 @@ jobs: target: kotlin-hello-world runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/envoy/android/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: apk: bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity @@ -146,7 +146,7 @@ jobs: target: ${{ matrix.target }} runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 steps-post: ${{ matrix.steps-post }} timeout-minutes: 50 trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} @@ -157,7 +157,7 @@ jobs: include: - name: java-hello-world steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/envoy/android/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: apk: bazel-bin/examples/java/hello_world/hello_envoy.apk app: io.envoyproxy.envoymobile.helloenvoy/.MainActivity @@ -180,7 +180,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/baseline:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/envoy/android/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: apk: bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity @@ -197,7 +197,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/experimental:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + - uses: envoyproxy/toolshed/actions/envoy/android/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: apk: bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 4efe861bea185..4e7c71ef93928 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -101,7 +101,7 @@ jobs: # source ./ci/mac_ci_setup.sh # bazel shutdown # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} @@ -146,7 +146,7 @@ jobs: # source: | # source ./ci/mac_ci_setup.sh # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@0f75902a4f8ed63a5b5f9e4336ef22048f6f5670 # v0.4.6 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} From dbd74c07788667fd8ea1ff908c23a30d81020d1a Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 28 Apr 2026 17:50:34 +0100 Subject: [PATCH 153/750] test: replace flaky StringMatcher.Memory with deterministic sizeof bounds (#44701) Replaces `TEST_F(StringMatcher, Memory)` with `StringMatcher.SizeIsBounded`. The old test asserted tcmalloc page-level `consumedBytes()` against hand-tuned ceilings, which produced ~5/1000 CI flakes and had to be re-tuned on every tcmalloc/abseil/protobuf bump (#37782, #43467). The new test bounds `sizeof` of each variant alternative and of `StringMatcherImpl` itself, expressed in `sizeof(std::string)` / `sizeof(void*)` for libc++ / libstdc++ portability. Same intent as #37782, deterministic. --------- Signed-off-by: Ryan Northey --- test/common/common/matchers_test.cc | 81 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/test/common/common/matchers_test.cc b/test/common/common/matchers_test.cc index 59f227523d354..ea58a99be03b5 100644 --- a/test/common/common/matchers_test.cc +++ b/test/common/common/matchers_test.cc @@ -12,6 +12,7 @@ #include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace Envoy { @@ -453,49 +454,43 @@ TEST_F(StringMatcher, NoMatcherRejected) { fmt::format("Configuration must define a matcher: {}", matcher.DebugString())); } -// Validates the amount of memory that is being used by the different string -// matchers. Requested as part of https://github.com/envoyproxy/envoy/pull/37782. -TEST_F(StringMatcher, Memory) { - const uint32_t matchers_num = 1000; - // Prefix matcher. - { - // Add 1000 Prefix-String Matchers of varying string lengths (1 to 1000). - std::vector all_matchers; - all_matchers.reserve(matchers_num); - Memory::TestUtil::MemoryTest memory_test; - for (uint32_t i = 0; i < matchers_num; ++i) { - envoy::type::matcher::v3::StringMatcher matcher; - matcher.set_prefix(std::string(i + 1, 'a')); - all_matchers.emplace_back(Matchers::StringMatcherImpl(matcher, context_)); - } - const size_t prefix_consumed_bytes = memory_test.consumedBytes(); - // The memory constraints were added to ensure that the amount of memory - // used by matchers is carefully analyzed. These constraints can be relaxed - // when additional features are added, but it should be done in a thoughtful manner. - // Adding 5*8192 bytes because tcmalloc consumption estimation may return - // different values depending on memory alignment. - EXPECT_MEMORY_LE(prefix_consumed_bytes, 530176 + 5 * 8192); - } - // Regex matcher. - { - // Add 1000 Regex-String Matchers of varying string lengths (1 to 1000). - std::vector all_matchers; - all_matchers.reserve(matchers_num); - Memory::TestUtil::MemoryTest memory_test; - for (uint32_t i = 0; i < matchers_num; ++i) { - envoy::type::matcher::v3::StringMatcher matcher; - matcher.mutable_safe_regex()->mutable_google_re2(); - matcher.mutable_safe_regex()->set_regex(std::string(i + 1, 'a')); - all_matchers.emplace_back(Matchers::StringMatcherImpl(matcher, context_)); - } - const size_t regex_consumed_bytes = memory_test.consumedBytes(); - // The memory constraints were added to ensure that the amount of memory - // used by matchers is carefully analyzed. These constraints can be relaxed - // when additional features are added, but it should be done in a thoughtful manner. - // Adding 10*8192 bytes because tcmalloc consumption estimation may return - // different values depending on memory alignment. - EXPECT_MEMORY_LE(regex_consumed_bytes, 15603776 + 10 * 8192); - } +MATCHER_P(MemNotMoreThan, sz, + "does not use more than " + std::to_string(sz) + + ": think carefully before increasing this, and if you're sure, " + "update the corresponding expectation") { + return arg <= sz; +} + +// Validates the per-matcher memory footprint of the different string matchers. +// Requested as part of https://github.com/envoyproxy/envoy/pull/37782: each +// variant alternative should carry only the data it needs, and +// StringMatcherImpl should not retain the proto used to construct it. +// +// Bounds are expressed in terms of sizeof(std::string) and sizeof(void*) so +// they are portable across libc++, libstdc++, and 32/64-bit builds. +TEST_F(StringMatcher, SizeIsBounded) { + // String-holding alternatives: one std::string + one bool rounded up to + // pointer alignment. + const size_t string_alt_bound = sizeof(std::string) + sizeof(void*); + EXPECT_THAT(sizeof(Matchers::ExactStringMatcher), MemNotMoreThan(string_alt_bound)); + EXPECT_THAT(sizeof(Matchers::PrefixStringMatcher), MemNotMoreThan(string_alt_bound)); + EXPECT_THAT(sizeof(Matchers::SuffixStringMatcher), MemNotMoreThan(string_alt_bound)); + EXPECT_THAT(sizeof(Matchers::ContainsStringMatcher), MemNotMoreThan(string_alt_bound)); + + // Pointer-holding alternatives: a single unique_ptr. + const size_t ptr_alt_bound = 2 * sizeof(void*); + EXPECT_THAT(sizeof(Matchers::RegexStringMatcher), MemNotMoreThan(ptr_alt_bound)); + EXPECT_THAT(sizeof(Matchers::CustomStringMatcher), MemNotMoreThan(ptr_alt_bound)); + + // StringMatcherImpl layout — accounting for all four pointer-sized + // contributions: + // [1] vtable pointer for ValueMatcher base (+1 * sizeof(void*)) + // [2] vtable pointer for StringMatcher base (+1 * sizeof(void*)) + // [3] absl::variant payload = max(sizeof(alternatives)) + // = sizeof(std::string) + sizeof(void*) (string + padded bool) + // [4] absl::variant discriminant (+1 * sizeof(void*)) + EXPECT_THAT(sizeof(Matchers::StringMatcherImpl), + MemNotMoreThan(sizeof(std::string) + 4 * sizeof(void*))); } class PathMatcher : public BaseTest {}; From 2c810466bd86c58f89419f82bd85fd0c2c7e08aa Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Tue, 28 Apr 2026 12:59:32 -0400 Subject: [PATCH 154/750] docs: cleanup release notes (#44703) Signed-off-by: Adi Suissa-Peleg --- changelogs/current.yaml | 638 ---------------------------------------- 1 file changed, 638 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index b0d6910e78cbd..9af0fadabfff4 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -40,635 +40,9 @@ new_features: change: | Added ``envoy_dynamic_module_callback_is_validation_mode`` ABI callback that allows dynamic modules to check if the server is running in config validation mode. -- area: ratelimit - change: | - Added ``is_negative_hits`` boolean to the ``hits_addend`` - configuration in the rate limit API. This allows rate limit - requests to add to the rate limit budget (refill previously - consumed tokens) via - :ref:`HitsAddend.is_negative_hits - `, - or per descriptor in - :ref:`RateLimitDescriptor.is_negative_hits - `. -- area: redis - change: | - Added zone-aware routing support for Redis Cluster proxy. New read policies - ``LOCAL_ZONE_AFFINITY`` and ``LOCAL_ZONE_AFFINITY_REPLICAS_AND_PRIMARY`` route read requests - to replicas in the same availability zone. Zone discovery is enabled via - :ref:`enable_zone_discovery - `. - Note: This feature currently works with Valkey only. -- area: overload_manager - change: | - Added :ref:`ShrinkHeapConfig ` typed - configuration for the ``envoy.overload_actions.shrink_heap`` overload action. This allows - operators to configure the timer interval (``timer_interval``, minimum 1s, default 10s) and - the memory release threshold (``max_unfreed_memory_bytes``, default 100MB) passed to - ``tcmalloc::MallocExtension::ReleaseMemoryToSystem()``. -- area: dns_resolver - change: | - Added :ref:`HickoryDnsResolverConfig - `, a new DNS - resolver using the `Hickory DNS `_ library. -- area: dynamic_modules - change: | - Added :ref:`tracer ` - support for dynamic modules, enabling custom distributed tracing backends to be implemented - in dynamic modules. -- area: dynamic_modules - change: | - Added upstream HTTP TCP bridge extension for dynamic modules. This enables modules to transform - HTTP requests into raw TCP data for upstream connections and convert TCP responses back into HTTP - responses via explicit send callbacks. See :ref:`envoy.upstreams.http.dynamic_modules - `. -- area: filters - change: | - Added filters to update the filter state in :ref:`a listener filter `. -- area: tls - change: | - Added a per-connection filter state object to select a workload trust domain in the SPIFFE validator in - the multi-tenant deployments. -- area: tls - change: | - Extended TLS certificate compression (RFC 8879): added brotli to QUIC (which already supported zlib), - and added brotli and zlib to TCP TLS. Controlled by runtime flag - ``envoy.reloadable_features.tls_certificate_compression_brotli`` (defaults to ``true``). - When disabled, QUIC retains zlib-only compression, while TCP TLS has no compression. -- area: tls - change: | - Exposed the SHA-256 fingerprint and serial number of the verified issuer (CA) certificate from - the validated downstream mTLS peer certificate chain. Accessible via the - ``%DOWNSTREAM_PEER_ISSUER_FINGERPRINT_256%`` and ``%DOWNSTREAM_PEER_ISSUER_SERIAL%`` - :ref:`access log formatters `, and via the - ``sha256PeerCertificateIssuerDigest()`` and ``serialNumberPeerCertificateIssuer()`` methods on - :ref:`downstreamSslConnection() ` in the Lua filter. -- area: dynamic_modules - change: | - Added custom metrics (counters, gauges, histograms) support to load balancer dynamic modules. - Modules can now define metrics during configuration and record them during host selection. -- area: dynamic_modules - change: | - Rust SDK now provides an opt-in ``CatchUnwind`` wrapper for filter callbacks. When a - wrapped callback panics, Envoy logs the panic and returns a fail-closed error (e.g. - HTTP 500, stream reset, connection close) instead of aborting the process. -- area: http_11_proxy - change: | - Added ability to configure a default proxy address that is used when the proxy address is not - configured via metadata. -- area: compressor - change: | - Added :ref:`weaken_etag_on_compress - ` - to the :ref:`compressor filter `. When enabled in - ``response_direction_config``, strong ``ETag`` response headers are weakened (``W/`` prefix) - instead of removed when compression is applied, allowing caches and conditional requests to - work while indicating the body was modified by compression. When both ``weaken_etag_on_compress`` - and ``disable_on_etag_header`` are true, the new field takes precedence. -- area: golang - change: | - Added ``DownstreamSslConnection()`` method to the Golang HTTP filter's ``StreamInfo`` interface, - providing access to SSL/TLS connection information for the downstream connection. This includes - peer certificate details (subject, issuer, serial number, SANs, validity), TLS version, cipher - suite, and PEM-encoded certificates. This achieves feature parity with the Lua filter's - ``downstreamSslConnection()`` functionality. -- area: outlier_detection - change: | - Added :ref:`detect_degraded_hosts ` - to enable passive degraded host detection. When enabled, outlier detection marks hosts as degraded when they return - the ``x-envoy-degraded`` header. Degraded hosts are deprioritized in load balancing but remain in rotation (not - ejected). The degraded state is cleared using the same backoff algorithm as ejection. Defaults to ``false``. -- area: http2 - change: | - Added :ref:`max_header_field_size_kb - ` to configure the - maximum wire-encoded size in KiB of an individual HPACK-encoded header field that the HTTP/2 - codec will accept. This allows increasing the default nghttp2 per-header limit of 64 KiB on the - wire when larger single headers need to be supported. -- area: dynamic_modules - change: | - Added dynamic module input matcher extension that allows implementing custom matching logic - in external languages (Rust, Go, C) via dynamic modules. -- area: dynamic_modules - change: | - Added listener lifecycle event callbacks to the bootstrap dynamic module extension. Modules can - opt in via ``enable_listener_lifecycle`` to receive ``on_listener_add_or_update`` and - ``on_listener_removal`` notifications when listeners change in the ``ListenerManager``. -- area: memory - change: | - Added ``soft_memory_limit_bytes``, ``max_per_cpu_cache_size_bytes``, and ``max_unfreed_memory_bytes`` - fields to :ref:`MemoryAllocatorManager ` - for fine-grained control of tcmalloc memory management. -- area: dynamic_modules - change: | - Added :ref:`TLS certificate validator - ` - support for dynamic modules, enabling custom TLS certificate validation to be implemented in dynamic modules. -- area: dynamic_modules - change: | - Added filter state read/write support for dynamic module cert validators, allowing modules to set and - get string values in the connection's filter state during certificate chain verification. -- area: dynamic_modules - change: | - Added ``write_to_socket`` and ``close_socket`` ABI callbacks for the dynamic module listener - filter, enabling protocol negotiation use cases such as Postgres SSL and MySQL handshake at the listener - filter level. -- area: dynamic_modules - change: | - Added HTTP callout support for dynamic module listener filters, enabling listener filters to initiate - asynchronous HTTP requests to upstream clusters and receive responses via the ``send_http_callout`` ABI - callback and ``on_listener_filter_http_callout_done`` event hook. -- area: ext_authz - change: | - Added :ref:`path_override ` - to the HTTP ext_authz filter. When set, the request path sent to the authorization service is replaced - entirely by this value. Only one of ``path_prefix`` or ``path_override`` may be set; validation fails - at config load if both are specified. -- area: stats - change: | - Added support to limit the number of metrics stored in each scope within the stats library. -- area: stats - change: | - The admin prometheus stats endpoint now supports the protobuf exposition format, and will automatically - use it if the request contains the correct Accept header, or if query parameter ``prom_protobuf=1`` is - set. In a prometheus scrape configuration, add ``PrometheusProto`` to ``scrape_protocols`` to use - the protobuf format. Additionally, when using the protobuf exposition format, the admin prometheus stats - endpoint now supports `native histograms `_ - when using the prometheus protobuf exposition format, using query - ``/stats/prometheus?histogram_buckets=prometheusnative``. -- area: stats - change: | - Added support for cluster-level stats matcher, allowing more granular control over which stats - are enabled and reported at the cluster level. This the stats matcher could be configured via - the xDS API dynamically on a per-cluster basis. - See :ref:`envoy.stats_matcher ` for more details. -- area: stats - change: | - Added support for listener-level stats matcher via ``typed_filter_metadata`` key - ``envoy.stats_matcher`` in the listener's - :ref:`metadata `. When present, - the provided :ref:`StatsMatcher ` replaces - the global stats matcher for that listener's stats scope, allowing fine-grained control over - which listener stats are created. - If the value of ``envoy.stats_matcher`` is set but does not contain a valid ``StatsMatcher`` - message, this will be treated as an invalid configuration and the listener will fail to start. - This strict validation can be disabled temporarily by setting the runtime guard - ``envoy.reloadable_features.strict_stats_matcher_unpacked`` to ``false``. -- area: tls - change: | - Added support for fetching certificates on-demand via SDS in the upstream TLS transport socket - using the extension :ref:`on-demand certificate selector - `. -- area: access_log - change: | - Added stats customization support for the :ref:`access logger `. -- area: dynamic modules - change: | - Introduced the extended ABI forward compatibility mechanism for dynamic modules - where modules built with an SDK version can be loaded by Envoy - binaries of the next Envoy version. For example, a module built with the v1.38 SDK - can now be loaded by an Envoy binary of v1.39. -- area: dynamic modules - change: | - Added drain and shutdown lifecycle hooks for bootstrap dynamic modules. -- area: dynamic modules - change: | - Added support for dynamic modules authors to register any combination of HTTP, network, listener, - UDP listener, and bootstrap filters in the Rust SDK. -- area: dynamic modules - change: | - Added connection state and flow control ABI callbacks for the dynamic module network filter, - including ``read_disable``, ``read_enabled``, ``get_connection_state``, ``enable_half_close``, - ``is_half_close_enabled``, ``get_buffer_limit``, ``set_buffer_limits``, and - ``above_high_watermark``. -- area: dynamic modules - change: | - Added socket property getter and SSL/TLS information ABI callbacks for the dynamic module listener - filter, including ``get_requested_server_name``, ``get_detected_transport_protocol``, - ``get_requested_application_protocols``, ``get_ja3_hash``, ``get_ja4_hash``, ``is_ssl``, - ``get_ssl_uri_sans``, ``get_ssl_dns_sans``, and ``get_ssl_subject``. -- area: mcp_router - change: | - Added support for MCP resource methods ``resources/list``, ``resources/read``, - ``resources/subscribe``, and ``resources/unsubscribe``. -- area: mcp_router - change: | - Added support for MCP prompt methods ``prompts/list`` and ``prompts/get``. -- area: matching - change: | - Added an optional ``field`` parameter to - :ref:`FilterStateInput `. - When set, ``FilterStateInput`` calls ``getField()`` on the filter state object instead of - ``serializeAsString()``, enabling direct matching on individual fields within composite filter - state objects such as proxy protocol TLVs stored via ``tlv_location: FILTER_STATE``. -- area: ratelimit - change: | - Added per-descriptor ``x-ratelimit-*`` headers support. See the - :ref:`x_ratelimit_option ` - field documentation for more details. -- area: ratelimit - change: | - Added ``RemoteAddressMatch`` action to the rate limit filter. This action will generate a descriptor based on the remote address of the - downstream connection by matching it against specified CIDR ranges with support for inversion and formatter substitution. -- area: mcp_router - change: | - Added support for MCP client-to-server notification methods ``notifications/cancelled`` - and ``notifications/roots/list_changed``. -- area: dynamic_modules - change: | - Added typed filter state support for dynamic module HTTP and network filters. This allows modules - to set and get filter state objects using registered ``StreamInfo::FilterState::ObjectFactory`` - instances, enabling interoperability with built-in Envoy filters that expect specific typed objects. -- area: mcp_router - change: | - Added support for MCP completion method ``completion/complete`` with routing based on - ``ref/prompt`` or ``ref/resource``. -- area: mcp_router - change: | - Added support for MCP logging method ``logging/setLevel``. -- area: upstream - change: | - Coalesced load balancer rebuilds during EDS batch host updates. When multiple priorities change in a - single batch, each LB level (LoadBalancerBase, ZoneAwareLoadBalancerBase, EdfLoadBalancerBase, - ThreadAwareLoadBalancerBase) now defers expensive per-priority recalculations to a single pass after - the batch completes, reducing CPU spikes on large clusters. This behavior can be reverted by setting - the runtime guard ``envoy.reloadable_features.coalesce_lb_rebuilds_on_batch_update`` to ``false``. -- area: cel - change: | - Added functionality to reevaluate CEL expressions that attempt to read response path data on the - request path once the data is available. Allows CEL matching based on both request and response - headers. This may cause a behavior change for matchers that previously would silently fail to - match due to attempting to match response headers in the request path. This behavior can be - reverted by setting the runtime guard - ``envoy.reloadable_features.enable_cel_response_path_matching`` to ``false``. -- area: access_log - change: | - Added support for gauges in the :ref:`stats access logger `. - area: access_log change: | Supported the singleton stats scope in the :ref:`stats access logger `. -- area: network - change: | - Added access logging support for network filters, similar to HTTP filters, by allowing network filters to - register as access logger instances. -- area: formatter - change: | - Extended ``*_WITHOUT_PORT`` address formatters to accept an optional ``MASK_PREFIX_LEN`` parameter - that masks IP addresses and returns them in CIDR notation (e.g., ``%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(16)%`` - returns ``10.1.0.0/16`` for client IP ``10.1.10.23``). -- area: dynamic_modules - change: | - Added :ref:`cluster - ` - support for dynamic modules, enabling custom service discovery and host management to be implemented - in dynamic modules. -- area: dynamic_modules - change: | - Added server lifecycle callbacks (``on_server_initialized``, ``on_drain_started``, ``on_shutdown``) - for dynamic module custom clusters, enabling modules to react to server readiness, drain, and - shutdown events. -- area: dynamic_modules - change: | - Changed the ``details`` parameter in ``cluster_lb_async_host_selection_complete`` ABI callback - from raw ``const char*`` and ``size_t`` to ``envoy_dynamic_module_type_module_buffer`` for - consistency with the ABI style guide. -- area: stat_sinks - change: | - Added support for exporting OpenTelemetry metrics via HTTP. The new ``http_service`` field - in :ref:`SinkConfig ` - enables direct OTLP metrics export to backends that only accept HTTP (Dynatrace, Datadog, Elastic), - without requiring an intermediate collector sidecar. -- area: ratelimit - change: | - Added support for shadow mode in the local rate limit filter. -- area: config - change: | - Added support for :ref:`set_node_on_first_message_only - ` to Delta-xDS. - Guarded by runtime flag ``envoy.reloadable_features.xds_legacy_delta_skip_subsequent_node``. -- area: formatter - change: | - Added the new access log formatter ``DOWNSTREAM_LOCAL_CLOSE_REASON``. -- area: formatter - change: | - Added ``%UPSTREAM_DETECTED_CLOSE_TYPE%`` and ``%DOWNSTREAM_DETECTED_CLOSE_TYPE%`` to expose the detected - close type of downstream and upstream connections. The possible values are ``Normal``, ``LocalReset``, - and ``RemoteReset``. -- area: formatter - change: | - Added extensions for :ref:`%FILE_CONTENT(/path/to/file)% ` - and :ref:`%SECRET(name)% `. -- area: http_service - change: | - Added the ability for :ref:`request_headers_to_add ` - to use a formatter extension that can retrieve secrets, for including authentication tokens. This support is added for - all uses of this message, including the open telemetry, ``ext_proc``, and ``zipkin`` tracers. -- area: reverse_tunnel - change: | - Added optional tenant isolation support to the reverse tunnel network filter. When - ``enable_tenant_isolation`` is set, Envoy scopes cached reverse tunnel sockets with composite - ``@`` and ``@`` identifiers and rejects handshake headers that already - contain the ``@`` delimiter to prevent ambiguous lookups. -- area: tcp_proxy - change: | - Added an option to emit a log entry when the connection is accepted. -- area: mcp_router - change: | - Added SSE (Server-Sent Events) streaming support for MCP backend responses. The router now handles - SSE responses from backends for ``tools/call`` with direct pass-through streaming, and supports SSE - aggregation for fanout operations (``tools/list``, ``initialize``) with incremental event parsing. -- area: http - change: | - Added an optional :ref:`JSON format ` - for the :ref:`x-forwarded-client-cert ` - (XFCC) header. The format can be configured via :ref:`format - `. -- area: dynamic_modules - change: | - Added :ref:`load balancing policies - ` - support for dynamic modules, enabling custom load balancing algorithms to be implemented in dynamic modules. -- area: dynamic_modules - change: | - Added ``get_host_health_by_address`` ABI callback for dynamic module load balancers, providing O(1) - host health lookup by address string using the cross-priority host map. -- area: sse_parser - change: | - Extended the SSE parser utility to support all standard SSE fields: ``id``, ``event`` (as ``event_type``), - and ``retry``, in addition to the existing ``data`` field. The ``retry`` field is parsed as a ``uint32_t`` - and only accepts values consisting of ASCII digits per the SSE specification. -- area: http - change: | - Added :ref:`envoy.filters.http.sse_to_metadata ` filter for extracting - values from Server-Sent Events (SSE) streams and writing them to dynamic metadata. Useful for capturing - token usage metrics from LLM API responses. Supports pluggable content parsers for different SSE data formats. -- area: content_parsers - change: | - Added :ref:`envoy.content_parsers.json ` content parser for extracting - values from JSON content using JSON path selectors. Can be used by filters that need to parse structured - JSON data and extract specific fields into metadata. -- area: resource_monitors - change: | - Added cgroup v2 support to the CPU utilization resource monitor. The monitor now automatically - detects and selects between cgroup v1 and v2 at runtime by checking available cgroup files on - the system. This enables the resource monitor to work correctly in both cgroup v1 and v2 - environments without configuration changes. -- area: connection - change: | - Add support for closing connections when they stay above the buffer high watermark for a configured time. - This can be enabled by setting the :ref:`per_connection_buffer_high_watermark_timeout - ` - field on the listener and :ref:`per_connection_buffer_high_watermark_timeout - ` - field on the cluster. By default, the timeout is disabled. -- area: dynamic_modules - change: | - Added configurable :ref:`metrics_namespace - ` field - to ``DynamicModuleConfig``. This allows users to customize the prefix used for all metrics - created by dynamic modules. Metrics now appear with the standard ``envoy_`` prefix followed by - the namespace in prometheus output (e.g. ``envoy_myapp_requests_total``). The legacy behavior - (stripping the namespace prefix from prometheus output) can be restored by setting the runtime - guard ``envoy.reloadable_features.dynamic_modules_strip_custom_stat_prefix`` to ``true``. -- area: oauth2 - change: | - Added ``TLS_CLIENT_AUTH`` for the OAuth2 HTTP filter to support RFC 8705 mutual TLS client - authentication. In this mode ``token_secret`` is optional and ignored, and the token endpoint - cluster must be configured with mTLS. -- area: dynamic_modules - change: | - Added a process-wide function registry to the dynamic modules ABI. Modules can register - functions by name via ``envoy_dynamic_module_callback_register_function`` and other modules - can resolve them via ``envoy_dynamic_module_callback_get_function``, enabling zero-copy - cross-module interactions analogous to ``dlsym``. -- area: dynamic_modules - change: | - Added a process-wide shared data registry to the dynamic modules ABI. -- area: dynamic_modules - change: | - Added support for loading dynamic module binaries from local file paths via the new - :ref:`module ` - field in ``DynamicModuleConfig``. This allows specifying an absolute path to a ``.so`` file - via ``module.local.filename`` as an alternative to the name-based search path. -- area: dynamic_modules - change: | - Added support for fetching dynamic module binaries from remote HTTP sources via - ``module.remote`` in ``DynamicModuleConfig``. The module is downloaded asynchronously during - listener initialization with SHA256 verification, written to a temporary file, and loaded - via ``dlopen``. If the remote fetch fails, the filter is not installed and requests pass - through (fail-open). -- area: dynamic_modules - change: | - Added caching for remotely fetched dynamic modules. Since ``newDynamicModuleFromBytes`` - writes modules to a deterministic path based on SHA256, subsequent config updates - referencing the same SHA256 load from the existing file, avoiding redundant HTTP fetches. -- area: dynamic_modules - change: | - Added ``nack_on_cache_miss`` option for remote dynamic module sources. When enabled, - uncached remote modules cause configuration rejection (NACK) with a background fetch, - instead of blocking listener warming. This enables remote modules in ECDS and per-route - configurations where an init manager is not available. -- area: dynamic_modules - change: | - Network filter read and write buffers now persist after ``on_read``/``on_write`` callbacks, allowing - modules to access buffered data from ``on_scheduled`` and other callbacks. Added - ``envoy_dynamic_module_callback_network_filter_get_cluster_host_count`` to query cluster host counts - by name, enabling scale-to-zero and custom load balancing decisions in network filters. -- area: dynamic_modules - change: | - Added metrics definition and update support for bootstrap dynamic modules. -- area: dynamic_modules - change: | - Added timer API to the bootstrap extension dynamic modules ABI. -- area: dynamic_modules - change: | - Added admin handler API to the bootstrap extension dynamic modules ABI, enabling modules to - register custom admin HTTP endpoints. -- area: formatter - change: | - Added ``SPAN_ID`` :ref:`access log formatter ` to log the span ID of - the active (downstream) span for a request, complementing the existing ``TRACE_ID`` formatter. -- area: formatter - change: | - Added ``QUERY_PARAMS`` support for substitution formatter to log all query params. - They can either be logged in their original form or decoded. -- area: formatter - change: | - Added new access log formatters for tracking upstream hosts and connection IDs attempted during - request processing: ``%UPSTREAM_HOSTS_ATTEMPTED%``, ``%UPSTREAM_HOSTS_ATTEMPTED_WITHOUT_PORT%``, - ``%UPSTREAM_HOST_NAMES_ATTEMPTED%``, ``%UPSTREAM_HOST_NAMES_ATTEMPTED_WITHOUT_PORT%``, and - ``%UPSTREAM_CONNECTION_IDS_ATTEMPTED%``. These are useful for debugging retry behavior and - understanding which hosts were tried before a successful connection or final failure. -- area: dynamic_modules - change: | - Added init manager integration to the dynamic modules bootstrap extension ABI. An init target - is automatically registered for every bootstrap extension, blocking traffic until the module - signals readiness via ``signal_init_complete``. -- area: dynamic_modules - change: | - Added ``on_host_membership_update`` event hook and ``get_member_update_host_address`` callback - for dynamic module load balancers, enabling modules to receive notifications when hosts are - added or removed from the cluster and inspect the affected host addresses. -- area: geoip - change: | - Added ``asn_org`` field to :ref:`geo_field_keys - ` - to populate a header with the autonomous system organization name from the MaxMind ASN database. -- area: tracers - change: | - Added log events to spans created by the OpenTelemetry tracer. -- area: mcp_router - change: | - Added SSE response support for MCP ``resources/list`` fanout aggregation. -- area: mcp_router - change: | - Added support for MCP ``resources/templates/list`` method with fanout aggregation. -- area: tcp_proxy - change: | - Added :ref:`proxy_protocol_tlv_merge_policy - ` - to control how TLVs in ``proxy_protocol_tlvs`` are merged with existing PROXY protocol state. - Supports ``ADD_IF_ABSENT`` (default), ``OVERWRITE_BY_TYPE_IF_EXISTS_OR_ADD``, and - ``APPEND_IF_EXISTS_OR_ADD``. -- area: admin - change: | - Added ``filter`` query parameter support to the ``/clusters`` endpoint. The parameter accepts a RE2 - regular expression to filter clusters by name. Compatible with the ``format`` parameter for both - text and JSON output (e.g., ``/clusters?filter=service&format=json``). -- area: mcp_router - change: | - Added :ref:`statistics ` to the MCP router filter for - observability into request routing, fanout operations, and error conditions. -- area: mcp - change: | - Added HTTP DELETE session termination support to the MCP filter. DELETE requests with an - ``MCP-Session-Id`` header are now recognized as valid MCP traffic in ``REJECT_NO_MCP`` mode. -- area: a2a - change: | - Added parsing support for the A2A (Agent2Agent) protocol, enabling parsing of A2A JSON-RPC messages. -- area: mcp - change: | - Added options ``propagate_trace_context`` and ``propagate_baggage`` for extracting ``traceparent``, - ``tracestate``, and baggage from MCP parameters, respectively. -- area: mcp_json_rest_bridge - change: | - Added the MCP JSON REST Bridge HTTP filter configuration to transcode MCP JSON-RPC requests into - standard JSON-REST HTTP requests. -- area: http - change: | - Added :ref:`file_server http filter ` to allow responding with - file contents from the filesystem. -- area: mcp_router - change: | - Added SSE response support for MCP ``prompts/list`` fanout aggregation. -- area: mcp_json_rest_bridge - change: | - Added support for MCP session negotiation, including ``initialize`` and - ``notifications/initialized`` methods. This filter is currently a work-in-progress and not - recommended for production use. -- area: listener_manager - change: | - Added ``ListenerUpdateCallbacks`` interface to ``ListenerManager``, similar to the existing - ``ClusterUpdateCallbacks`` on ``ClusterManager``. -- area: matching - change: | - Added :ref:`local reply matcher input ` - to distinguish Envoy generated local replies from upstream responses. This matcher input returns ``true`` - for local replies and ``false`` for upstream responses, enabling ``custom_response`` filter policies to - selectively apply only to locally generated error responses. -- area: mcp_json_rest_bridge - change: | - Added support for MCP ``tools/call`` request transcoding. Support for ``tools/list`` and - ``tools/call`` response transcoding is planned. This filter is currently a work-in-progress and - not recommended for production use. -- area: ext_proc - change: | - Added :ref:`allow_content_length_header - ` to allow - the ext_proc filter to preserve the original ``Content-Length`` header or let ext_proc server modify it as needed. -- area: cluster - change: | - Added :ref:`MCP multi cluster ` extension that combines multiple MCP services - into a single one. This cluster type is primarily intended to support aggregation of multiple MCP services into one. -- area: tls - change: | - Enhanced TLS certificate validation failure messages for CRL-related errors in access logs. The - ``%DOWNSTREAM_TRANSPORT_FAILURE_REASON%`` and ``%UPSTREAM_TRANSPORT_FAILURE_REASON%`` access log - formatters now include the certificate's CRL Distribution Point (CRLDP) information when CRL - validation fails. For errors such as ``CRL for certificate was not provided``, ``CRL has expired``, ``CRL - is not yet valid``, or ``certificate revoked``, the error message now includes the certificate's - CRL distribution points (e.g., ``X509_verify_cert: certificate verification error at depth 0: certificate revocation - check against provided CRLs failed: unable to get certificate CRL, certificate CRL distribution points: - [http://crl.example.com/ca.crl, http://backup-crl.example.com/ca.crl]``). This provides better visibility into CRL - validation failures and helps operators identify connectivity or CRL server issues without requiring debug-level logging. -- area: lua - change: | - Added :ref:`set() ` to the Lua filter - state API, allowing Lua scripts to create and store filter state objects dynamically using - registered object factories. -- area: http - change: | - Fixed an issue where filter chain execution could continue on HTTP streams that had been reset but not yet - destroyed. This could cause use-after-free conditions when filter callbacks were invoked on filters that - had already received ``onDestroy()``. The fix ensures that ``decodeHeaders()``, ``decodeData()``, - ``decodeTrailers()``, and ``decodeMetadata()`` are blocked after a downstream reset. -- area: formatter - change: | - Added support for ``%TRACE_ID%`` in the custom response policy filter's local response policy to allow - trace ID to be substituted into the response body. -- area: redis - change: | - Added support for ``BITFIELD_RO`` in ``redis_proxy``. -- area: network_filter - change: | - Added support for - ``on_downstream_data`` (see - :ref:`envoy_v3_api_field_extensions.filters.network.set_filter_state.v3.Config.on_downstream_data`) - to the :ref:`set_filter_state network filter `, allowing - connection filter state to be populated after first receiving data from the downstream connection. -- area: oauth2 - change: | - Added :ref:`allow_failed_matcher ` - to allow requests to proceed to upstream as unauthenticated when OAuth validation fails (missing, invalid, or expired - credentials) and the request matches the configured matchers. This enables graceful degradation patterns where services - can handle both authenticated and unauthenticated requests. When triggered, all OAuth cookies are stripped and context - headers ``x-envoy-oauth-status: failed`` and ``x-envoy-oauth-failure-reason`` are added. The matcher evaluation follows - priority order: ``pass_through_matcher`` > ``allow_failed_matcher`` > ``deny_redirect_matcher`` > default OAuth behavior. -- area: mcp_json_rest_bridge - change: | - Added support for MCP ``tools/call`` response transcoding. Support for ``tools/list`` is planned. - This filter is currently a work-in-progress and not recommended for production use. -- area: http_filter - change: | - Added support for clear route cache in the :ref:`set_filter_state http filter `. When - ``clear_route_cache`` is set, the filter will clear the route cache for the current request after applying filter state updates. - This is necessary if the route configuration may depend on the filter state values set. -- area: tcp_proxy - change: | - Propagate upstream TCP RST to downstream when detected close type is RemoteReset. - This behavioral change can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.propagate_upstream_rst_through_tunneled_tcp_proxy`` to false. -- area: tcp_proxy - change: | - HttpUpstream now maps remote-originated HTTP stream reset reasons (RemoteReset, - RemoteRefusedStreamReset, RemoteConnectionFailure) to RemoteClose with - DetectedCloseType::RemoteReset, enabling RST propagation through tunneled tcp_proxy. - This behavioral change can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.map_http_stream_reset_to_tcp_rst`` to false. -- area: ratelimit - change: | - Support populating rate limit descriptors from cluster metadata. -- area: contrib - change: | - Added a new :ref:`Kafka stats sink ` contrib extension - (``envoy.stat_sinks.kafka``) that produces metrics directly to an Apache Kafka topic - using librdkafka. Supports both JSON and Protobuf serialization formats; the Protobuf - format uses the same ``StreamMetricsMessage`` wire format as the gRPC - :ref:`metrics_service ` sink, - allowing consumers to reuse existing deserializers. Configurable batching, delta counters, - tag-as-label emission, and full librdkafka producer tuning (including TLS and SASL - authentication) are supported via - :ref:`KafkaStatsSinkConfig `. - area: stat_sinks change: | Added a new :ref:`WASM stats filter ` contrib extension @@ -681,18 +55,6 @@ new_features: metric processing logic (tag enrichment, name rewriting, custom metric injection) into the proxy itself. Configured via :ref:`WasmFilterStatsSinkConfig `. -- area: tls - change: | - Added support for building Envoy with OpenSSL as an alternative to the default BoringSSL. - This is achieved through a BoringSSL compatibility layer (``bssl-compat``) that translates BoringSSL - API calls to OpenSSL, allowing Envoy's TLS code to remain unchanged. To build with OpenSSL, use - ``--config=openssl``. HTTP/3 (QUIC) is disabled for OpenSSL builds. Note that OpenSSL builds are - not currently covered by the `Envoy security policy `_. - See :repo:`bazel/SSL.md ` for details. -- area: dynamic_modules - change: | - Promoted the dynamic modules HTTP filter (``envoy.filters.http.dynamic_modules``) from alpha - to stable. - area: mysql_proxy change: | Added SSL termination support to the MySQL proxy filter with RSA-mediated ``caching_sha2_password`` From 3f5a818c2e0fbe5db26f300901305e5b1777a32a Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 28 Apr 2026 20:56:04 +0100 Subject: [PATCH 155/750] test/ext_proc: Remove unused mock dep/include (#44711) came across this while investigating a ci timeout Signed-off-by: Ryan Northey --- test/extensions/filters/http/ext_proc/BUILD | 1 - .../ext_proc/ext_proc_local_reply_streaming_integration_test.cc | 1 - 2 files changed, 2 deletions(-) diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index e3d2050b5a185..f82382a5f89a0 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -583,7 +583,6 @@ envoy_extension_cc_test( "//test/common/grpc:grpc_client_integration_lib", "//test/common/http:common_lib", "//test/integration:http_integration_lib", - "//test/mocks/http:http_mocks", "//test/test_common:utility_lib", "@abseil-cpp//absl/strings", "@envoy_api//envoy/extensions/filters/http/ext_proc/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/ext_proc/ext_proc_local_reply_streaming_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_local_reply_streaming_integration_test.cc index 53d21db2e0c65..0affefb846b6b 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_local_reply_streaming_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_local_reply_streaming_integration_test.cc @@ -8,7 +8,6 @@ #include "test/common/http/common.h" #include "test/extensions/filters/http/ext_proc/ext_proc_integration_common.h" #include "test/integration/http_integration.h" -#include "test/mocks/http/mocks.h" #include "test/test_common/utility.h" #include "absl/strings/string_view.h" From 099a9d71ebfd8aa9f823e1738b34138cb634a07b Mon Sep 17 00:00:00 2001 From: Jason Koch Date: Tue, 28 Apr 2026 14:48:48 -0700 Subject: [PATCH 156/750] stats: optimize StatName hot paths in symbol table (#44448) Optimizations to StatName to make it more friendly for use as a key in a map. Specifically we improve `operator==` and similar calls and clean up some duplicate unnecessary work across the file. (nb: if preferred i can resubmit the commits as separated PRs, do let me know) Key changes: - Inline `dataSize()`, `decodeNumber()`, `encodingSizeBytes()` into the header to make inlining easier - Fast-path `decodeNumber()` for values <= 127 - skip loop for values with length less than 127 - Branch-free `encodingSizeBytes()` via `std::countl_zero` - `StatNameList::front()` + `MetricHelper::statName()` bypass - avoid `std::function` allocation and iterator setup for the common "get first element" - `operator==` fast-path pointer-identity check and early size comparison before `memcmp`; `AbslHashValue` also avoids redundant varint decode - `empty()` checks first byte instead of full varint decode - `0x00` uniquely encodes zero length (`d040567`) Cumulative benchmark results (`--benchmark_min_time=5s`): | Benchmark | Before | After | Improvement | |---|---|---|---| | `BM_StatsNoTls` | 5697 us | 5077 us | **-10.9%** | | `BM_StatsWithTls` | 6023 us | 5453 us | **-9.5%** | | `BM_StatsWithTlsAndRejectionsWithDot` | 2889 us | 2715 us | **-6.1%** | | `BM_StatsWithTlsAndRejectionsWithoutDot` | 5486 us | 4866 us | **-11.3%** | | `bmCreateRace` | 49.2 ms | 49.7 ms | ~flat | | `bmJoinStatNames` | 101 ns | 87.6 ns | **-13.3%** | | `bmJoinElements` | 233 ns | 208 ns | **-10.7%** | | `bmCompareElements` | 34.3 ns | 33.5 ns | **-2.3%** | | `bmSortByStatNames` | 49976 us | 50547 us | ~flat | | `bmStdSort` | 60993 us | 56058 us | **-8.1%** | | `bmStatNameMapLookup` | 22.4 ns | 8.52 ns | **-62.0%** | | `bmStatNameMapLookupDifferentPointer` | 22.4 ns | 11.8 ns | **-47.3%** | | `bmStatNameMapLookupMiss` | 12.1 ns | 6.70 ns | **-44.6%** | | `bmSortStrings` | 20443 us | 20488 us | ~flat | | `bmSetStrings` | 38079 us | 38100 us | ~flat | Optimizations were developed and benchmarked incrementally (see individual commit messages). The map-lookup benchmarks were added in the first commit to establish a baseline. Generative AI (Claude) was used as a development aid for this change. I understand all code in the PR. Risk Level: Medium - all changes are to internal implementation details with no API or behavioral changes. The varint encoding format is unchanged. But, it is a core component. Testing: - Existing `bazel test //test/common/stats/...` passes incl config=asan. - New `StatNameEqualityFastPaths` test covers pointer-identity, null-vs-zero-length, and null-vs-non-empty cases - Benchmarked with `symbol_table_benchmark` and `thread_local_store_speed_test` Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A --------- Signed-off-by: Jason Koch --- source/common/stats/metric_impl.cc | 28 +- source/common/stats/symbol_table.cc | 25 - source/common/stats/symbol_table.h | 83 +++- test/common/stats/symbol_table_impl_test.cc | 157 +++++++ test/common/stats/symbol_table_speed_test.cc | 470 ++++++++++++++++++- 5 files changed, 696 insertions(+), 67 deletions(-) diff --git a/source/common/stats/metric_impl.cc b/source/common/stats/metric_impl.cc index f05f774369a8b..86239ba13bb31 100644 --- a/source/common/stats/metric_impl.cc +++ b/source/common/stats/metric_impl.cc @@ -32,33 +32,9 @@ MetricHelper::MetricHelper(StatName name, StatName tag_extracted_name, symbol_table.populateList(names.begin(), num_names, stat_names_); } -StatName MetricHelper::statName() const { - StatName stat_name; - stat_names_.iterate([&stat_name](StatName s) -> bool { - stat_name = s; - return false; // Returning 'false' stops the iteration. - }); - return stat_name; -} +StatName MetricHelper::statName() const { return stat_names_.at(0); } -StatName MetricHelper::tagExtractedStatName() const { - // The name is the first element in stat_names_. The second is the - // tag-extracted-name. We don't have random access in that format, - // so we iterate through them, skipping the first element (name), - // and terminating the iteration after capturing the tag-extracted - // name by returning false from the lambda. - StatName tag_extracted_stat_name; - bool skip = true; - stat_names_.iterate([&tag_extracted_stat_name, &skip](StatName s) -> bool { - if (skip) { - skip = false; - return true; - } - tag_extracted_stat_name = s; - return false; // Returning 'false' stops the iteration. - }); - return tag_extracted_stat_name; -} +StatName MetricHelper::tagExtractedStatName() const { return stat_names_.at(1); } void MetricHelper::iterateTagStatNames(const Metric::TagStatNameIterFn& fn) const { enum { Name, TagExtractedName, TagName, TagValue } state = Name; diff --git a/source/common/stats/symbol_table.cc b/source/common/stats/symbol_table.cc index 0f635b39bf1ba..28279ccdb9da4 100644 --- a/source/common/stats/symbol_table.cc +++ b/source/common/stats/symbol_table.cc @@ -14,13 +14,6 @@ namespace Envoy { namespace Stats { -// Masks used for variable-length encoding of arbitrary-sized integers into a -// uint8-array. The integers are typically small, so we try to store them in as -// few bytes as possible. The bottom 7 bits hold values, and the top bit is used -// to determine whether another byte is needed for more data. -static constexpr uint32_t SpilloverMask = 0x80; -static constexpr uint32_t Low7Bits = 0x7f; - // When storing Symbol arrays, we disallow Symbol 0, which is the only Symbol // that will decode into uint8_t array starting (and ending) with {0}. Thus we // can use a leading 0 in the first byte to indicate that what follows is @@ -31,13 +24,6 @@ static constexpr uint32_t Low7Bits = 0x7f; static constexpr Symbol FirstValidSymbol = 1; static constexpr uint8_t LiteralStringIndicator = 0; -size_t StatName::dataSize() const { - if (size_and_data_ == nullptr) { - return 0; - } - return SymbolTable::Encoding::decodeNumber(size_and_data_).first; -} - #ifndef ENVOY_CONFIG_COVERAGE void StatName::debugPrint() { // TODO(jmarantz): capture this functionality (always prints regardless of @@ -105,17 +91,6 @@ void SymbolTable::Encoding::addSymbols(const std::vector& symbols) { } } -std::pair SymbolTable::Encoding::decodeNumber(const uint8_t* encoding) { - uint64_t number = 0; - uint64_t uc = SpilloverMask; - const uint8_t* start = encoding; - for (uint32_t shift = 0; (uc & SpilloverMask) != 0; ++encoding, shift += 7) { - uc = static_cast(*encoding); - number |= (uc & Low7Bits) << shift; - } - return std::make_pair(number, encoding - start); -} - SymbolVec SymbolTable::Encoding::decodeSymbols(StatName stat_name) { SymbolVec symbol_vec; symbol_vec.reserve(stat_name.dataSize()); diff --git a/source/common/stats/symbol_table.h b/source/common/stats/symbol_table.h index 5b650c7175477..d008881ba263e 100644 --- a/source/common/stats/symbol_table.h +++ b/source/common/stats/symbol_table.h @@ -192,8 +192,31 @@ class SymbolTable final { * * @param The encoded byte array, written previously by appendEncoding. * @return A pair containing the decoded number, and the number of bytes consumed from encoding. + * + * Defined inline in the header to allow the compiler to inline on hot paths. */ - static std::pair decodeNumber(const uint8_t* encoding); + static std::pair decodeNumber(const uint8_t* encoding) { + // fast-path for the case 127 or less + if (encoding[0] < SpilloverMask) { + return {encoding[0], 1}; + } + + uint64_t number = 0; + uint64_t uc = SpilloverMask; + const uint8_t* start = encoding; + for (uint32_t shift = 0; (uc & SpilloverMask) != 0; ++encoding, shift += 7) { + uc = static_cast(*encoding); + number |= (uc & Low7Bits) << shift; + } + return std::make_pair(number, encoding - start); + } + + // Masks used for variable-length encoding of arbitrary-sized integers into a + // uint8-array. The integers are typically small, so we try to store them in as + // few bytes as possible. The bottom 7 bits hold values, and the top bit is used + // to determine whether another byte is needed for more data. + static constexpr uint32_t SpilloverMask = 0x80; + static constexpr uint32_t Low7Bits = 0x7f; private: friend class StatName; @@ -587,6 +610,14 @@ class StatName { uint64_t hash() const { return absl::Hash()(*this); } bool operator==(const StatName& rhs) const { + if (size_and_data_ == rhs.size_and_data_) { + return true; + } + + if (size_and_data_ == nullptr || rhs.size_and_data_ == nullptr) { + return empty() && rhs.empty(); + } + return dataAsStringView() == rhs.dataAsStringView(); } bool operator!=(const StatName& rhs) const { return !(*this == rhs); } @@ -595,7 +626,7 @@ class StatName { * @return size_t the number of bytes in the symbol array, excluding the * overhead for the size itself. */ - size_t dataSize() const; + size_t dataSize() const { return dataAsStringView().size(); } /** * @return size_t the number of bytes in the symbol array, including the @@ -624,7 +655,8 @@ class StatName { * @param mem_block_builder the builder to receive the storage. */ void appendDataToMemBlock(MemBlockBuilder& storage) { - storage.appendData(absl::MakeSpan(data(), dataSize())); + auto sv = dataAsStringView(); + storage.appendData(absl::MakeSpan(reinterpret_cast(sv.data()), sv.size())); } #ifndef ENVOY_CONFIG_COVERAGE @@ -635,10 +667,7 @@ class StatName { * @return A pointer to the first byte of data (skipping over size bytes). */ const uint8_t* data() const { - if (size_and_data_ == nullptr) { - return nullptr; - } - return size_and_data_ + SymbolTable::Encoding::encodingSizeBytes(dataSize()); + return reinterpret_cast(dataAsStringView().data()); } const uint8_t* dataIncludingSize() const { return size_and_data_; } @@ -646,7 +675,11 @@ class StatName { /** * @return whether this is empty. */ - bool empty() const { return size_and_data_ == nullptr || dataSize() == 0; } + bool empty() const { + // Avoid a full varint decode: it is sufficient to know the first byte, + // since 0x00 uniquely encodes zero + return size_and_data_ == nullptr || size_and_data_[0] == 0; + } /** * Determines whether this starts with the prefix. Note: dynamic segments @@ -660,13 +693,16 @@ class StatName { private: /** - * Casts the raw data as a string_view. Note that this string_view will not - * be in human-readable form, but it will be compatible with a string-view - * hasher and comparator. + * Casts the raw data as a string_view, decoding the varint length prefix. + * All accessors that need the data pointer and/or size should delegate to + * this method so the decode happens in exactly one place. */ absl::string_view dataAsStringView() const { - return {reinterpret_cast(data()), - static_cast(dataSize())}; + if (size_and_data_ == nullptr) { + return {}; + } + const auto [data_size, prefix_size] = SymbolTable::Encoding::decodeNumber(size_and_data_); + return {reinterpret_cast(size_and_data_ + prefix_size), data_size}; } const uint8_t* size_and_data_{nullptr}; @@ -863,6 +899,27 @@ class StatNameList { */ void clear(SymbolTable& symbol_table); + /** + * @return the number of StatNames in the list, or 0 if not populated. + */ + uint32_t size() const { return populated() ? storage_[0] : 0; } + + /** + * @return the StatName at the given index. This is O(index): each element + * has a variable-length encoding, so callers that need to visit + * many elements should prefer iterate(). + * + * Requires: populated() && index < size(). + */ + StatName at(uint32_t index) const { + ASSERT(populated() && index < storage_[0]); + const uint8_t* p = &storage_[1]; + for (uint32_t i = 0; i < index; ++i) { + p += StatName(p).size(); + } + return StatName(p); + } + private: friend class SymbolTable; diff --git a/test/common/stats/symbol_table_impl_test.cc b/test/common/stats/symbol_table_impl_test.cc index 4f4f6e5cbb42d..803b252e290e8 100644 --- a/test/common/stats/symbol_table_impl_test.cc +++ b/test/common/stats/symbol_table_impl_test.cc @@ -390,8 +390,14 @@ TEST_F(StatNameTest, List) { StatName names[] = {makeStat("hello.world"), makeStat("goodbye.world")}; StatNameList name_list; EXPECT_FALSE(name_list.populated()); + EXPECT_EQ(0, name_list.size()); table_.populateList(names, ARRAY_SIZE(names), name_list); EXPECT_TRUE(name_list.populated()); + EXPECT_EQ(2, name_list.size()); + + // Random-access via at(). + EXPECT_EQ("hello.world", table_.toString(name_list.at(0))); + EXPECT_EQ("goodbye.world", table_.toString(name_list.at(1))); // First, decode only the first name. name_list.iterate([this](StatName stat_name) -> bool { @@ -410,6 +416,36 @@ TEST_F(StatNameTest, List) { EXPECT_EQ("goodbye.world", decoded_strings[1]); name_list.clear(table_); EXPECT_FALSE(name_list.populated()); + EXPECT_EQ(0, name_list.size()); +} + +// Exercises StatNameList::at() walking across entries whose encoded sizes +// straddle the single-byte varint boundary (dataSize 127/128). This catches +// any mistake in advancing the pointer with StatName::size() across a +// 1-byte-vs-2-byte length prefix. +TEST_F(StatNameTest, ListAtVarintBoundary) { + // Build names with controlled dataSize via the dynamic-storage path: + // dataSize = 1 (LiteralStringIndicator) + varint_size(len) + len. + StatNameDynamicPool dynamic(table_); + constexpr int lower_bound = static_cast(SymbolTable::Encoding::SpilloverMask) - 3; + constexpr int upper_bound = static_cast(SymbolTable::Encoding::SpilloverMask) + 3; + + std::vector names; + std::vector expected; + for (int len = lower_bound; len <= upper_bound; ++len) { + std::string s(len, 'a' + ((len - lower_bound) % 26)); + expected.push_back(s); + names.push_back(dynamic.add(s)); + } + + StatNameList name_list; + table_.populateList(names.data(), names.size(), name_list); + EXPECT_EQ(expected.size(), name_list.size()); + + for (uint32_t i = 0; i < expected.size(); ++i) { + EXPECT_EQ(expected[i], table_.toString(name_list.at(i))) << "index=" << i; + } + name_list.clear(table_); } TEST_F(StatNameTest, HashTable) { @@ -726,6 +762,127 @@ TEST_F(StatNameTest, StatNameEmptyEquivalent) { EXPECT_NE(empty2.hash(), non_empty.hash()); } +TEST_F(StatNameTest, StatNameEqualityFastPaths) { + // Pointer-identity: same backing storage compares equal without memcmp. + StatName a = makeStat("foo.bar"); + StatName alias(a.dataIncludingSize()); + EXPECT_EQ(a, alias); + + // Null vs zero-length: both are empty() and must compare equal in both directions. + StatName null_name; + StatName zero_length = makeStat(""); + EXPECT_EQ(null_name, zero_length); + EXPECT_EQ(zero_length, null_name); + + // Null vs non-empty: must not compare equal. + EXPECT_NE(null_name, a); + EXPECT_NE(a, null_name); +} + +TEST_F(StatNameTest, EqualityWithMultiByteVarint) { + // Sweep segment counts around the varint boundary to catch off-by-one + // errors in decodeNumber's fast-path vs slow-path. Each segment gets a + // unique symbol assigned sequentially. Symbols 1-127 encode as 1 byte + // each; symbol 128+ encodes as 2 bytes. Pre-allocating throwaway symbols + // shifts the numbering so that different padding values produce different + // dataSizes for the same segment count. We verify that the sweep covers + // both sides of the 128 boundary (the fast-path/slow-path transition in + // the length-prefix varint). + bool seen[150] = {}; + + // Sweep a few potential padding numbers just to make sure we cover. This + // is over-testing but that's ok. + constexpr int lower_bound = static_cast(SymbolTable::Encoding::SpilloverMask) - 5; + constexpr int upper_bound = static_cast(SymbolTable::Encoding::SpilloverMask) + 5; + for (int padding = 0; padding <= 10; ++padding) { + for (int num_segments = lower_bound; num_segments <= upper_bound; ++num_segments) { + clearStorage(); + + // Pre-allocate throwaway symbols to shift symbol numbering. + for (int p = 0; p < padding; ++p) { + makeStat(absl::StrCat("pad", p)); + } + + std::string long_name = "s0"; + for (int i = 1; i < num_segments; ++i) { + absl::StrAppend(&long_name, ".s", i); + } + + // Two pool entries for the same name: distinct backing storage, identical content. + StatName x = makeStat(long_name); + StatName y = makeStat(long_name); + size_t ds = x.dataSize(); + ASSERT_LT(ds, 150); + seen[ds] = true; + EXPECT_NE(x.dataIncludingSize(), y.dataIncludingSize()) + << "padding=" << padding << " segments=" << num_segments; + EXPECT_EQ(x, y) << "padding=" << padding << " segments=" << num_segments; + EXPECT_EQ(x.hash(), y.hash()) << "padding=" << padding << " segments=" << num_segments; + + // Same segment count but shifted names — memcmp mismatch. + std::string long_name_alt = "s1"; + for (int i = 2; i < num_segments + 1; ++i) { + absl::StrAppend(&long_name_alt, ".s", i); + } + StatName z = makeStat(long_name_alt); + EXPECT_NE(x, z) << "padding=" << padding << " segments=" << num_segments; + } + } + // Confirm every dataSize across the varint boundary was exercised. + for (int i = lower_bound; i <= upper_bound; ++i) { + EXPECT_TRUE(seen[i]) << "dataSize " << i << " was never hit"; + } +} + +TEST_F(StatNameTest, EqualitySizeMismatch) { + // Two non-null, different-pointer names with different encoded sizes: + // exercises the early lhs_sz != rhs_sz exit in operator==. + StatName short_name = makeStat("foo.bar"); + StatName long_name = makeStat("foo.bar.baz"); + EXPECT_NE(short_name, long_name); + EXPECT_NE(long_name, short_name); +} + +TEST_F(StatNameTest, EncodingSizeBytesAtBoundaries) { + // Verify the branch-free encodingSizeBytes formula at every varint boundary. + auto esb = SymbolTable::Encoding::encodingSizeBytes; + EXPECT_EQ(1, esb(0)); + EXPECT_EQ(1, esb(1)); + EXPECT_EQ(1, esb(126)); + EXPECT_EQ(1, esb(127)); // last 1-byte value + EXPECT_EQ(2, esb(128)); // first 2-byte value + EXPECT_EQ(2, esb(129)); + EXPECT_EQ(2, esb(16383)); // last 2-byte value (2^14 - 1) + EXPECT_EQ(3, esb(16384)); // first 3-byte value (2^14) + EXPECT_EQ(3, esb(16385)); + EXPECT_EQ(3, esb((1 << 21) - 1)); // last 3-byte + EXPECT_EQ(4, esb(1 << 21)); // first 4-byte + EXPECT_EQ(4, esb((1 << 28) - 1)); // last 4-byte + EXPECT_EQ(5, esb(1 << 28)); // first 5-byte +} + +TEST_F(StatNameTest, EqualityAtVarintBoundary) { + // Dynamic names give precise control over dataSize(): + // dataSize = 1 (LiteralStringIndicator) + varint_size(name.size()) + name.size() + // Sweep base_len across the outer-varint boundary (dataSize 127/128) so we + // hit both the decodeNumber fast-path and slow-path for operator==/hash. + StatNameDynamicPool dynamic(table_); + constexpr int lower_bound = static_cast(SymbolTable::Encoding::SpilloverMask) - 5; + constexpr int upper_bound = static_cast(SymbolTable::Encoding::SpilloverMask) + 5; + + for (int base_len = lower_bound; base_len <= upper_bound; ++base_len) { + const std::string s(base_len, 'a'); + StatName a = dynamic.add(s); + StatName b = dynamic.add(s); + + // Distinct backing storage, identical content. + EXPECT_NE(a.dataIncludingSize(), b.dataIncludingSize()) << "base_len=" << base_len; + ASSERT_TRUE(a == b) << "base_len=" << base_len; + EXPECT_EQ(a.hash(), b.hash()) << "base_len=" << base_len; + EXPECT_FALSE(a.empty()) << "base_len=" << base_len; + } +} + TEST_F(StatNameTest, StartsWith) { StatName prefix = makeStat("prefix"); EXPECT_TRUE(prefix.startsWith(prefix)); diff --git a/test/common/stats/symbol_table_speed_test.cc b/test/common/stats/symbol_table_speed_test.cc index 2e6f084a07de4..e4a8d03f9168c 100644 --- a/test/common/stats/symbol_table_speed_test.cc +++ b/test/common/stats/symbol_table_speed_test.cc @@ -3,6 +3,9 @@ // // NOLINT(namespace-envoy) +#include +#include + #include "source/common/common/hash.h" #include "source/common/common/logger.h" #include "source/common/common/thread.h" @@ -99,7 +102,8 @@ static void bmJoinElements(benchmark::State& state) { BENCHMARK(bmJoinElements); static std::vector prepareNames(Envoy::Stats::StatNamePool& pool, - uint32_t num_words) { + uint32_t num_words, + uint32_t num_tokens_per_word = 4) { const std::vector all_tokens = { "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", @@ -107,8 +111,7 @@ static std::vector prepareNames(Envoy::Stats::StatNamePo }; std::vector names; - // Form 64 4-token names selecting pseudo-randomly from the above set. - const uint32_t num_tokens_per_word = 4; + // Form token combinations selecting pseudo-randomly from the above set. for (uint32_t i = 0; i < num_words; ++i) { std::vector tokens; for (uint32_t j = 0; j < num_tokens_per_word; ++j) { @@ -174,6 +177,78 @@ static void bmStdSort(benchmark::State& state) { } BENCHMARK(bmStdSort); +// Benchmarks StatName as a map key, exercising hash() + operator== on lookup. +// This covers the hot path in thread_local_store where counters/gauges are +// stored in StatNameHashMap and looked up via MetricHelper::statName(). +// Uses 1000 entries with 20-token names to reflect production-scale stat names. +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmStatNameMapLookup(benchmark::State& state) { + Envoy::Stats::SymbolTableImpl symbol_table; + Envoy::Stats::StatNamePool pool(symbol_table); + const std::vector names = prepareNames(pool, 2000, 24); + + Envoy::Stats::StatNameHashMap map; + for (uint64_t i = 0; i < names.size(); ++i) { + map[names[i]] = i; + } + + // Lookup each name -- the key is the same StatName pointer that was inserted, + // so operator== hits the pointer-equal fast path. + uint32_t index = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(map.find(names[index++ % names.size()])); + } +} +BENCHMARK(bmStatNameMapLookup); + +// Same as above but the lookup key is a different StatName with identical +// encoding, forcing operator== through the memcmp path. +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmStatNameMapLookupDifferentPointer(benchmark::State& state) { + Envoy::Stats::SymbolTableImpl symbol_table; + Envoy::Stats::StatNamePool pool_a(symbol_table); + Envoy::Stats::StatNamePool pool_b(symbol_table); + const std::vector names_a = prepareNames(pool_a, 2000, 24); + const std::vector names_b = prepareNames(pool_b, 2000, 24); + + Envoy::Stats::StatNameHashMap map; + for (uint64_t i = 0; i < names_a.size(); ++i) { + map[names_a[i]] = i; + } + + // Lookup using names from pool_b -- same encoding but different pointers. + uint32_t index = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(map.find(names_b[index++ % names_b.size()])); + } +} +BENCHMARK(bmStatNameMapLookupDifferentPointer); + +// Measures the hash+miss path where the key is not in the map. +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmStatNameMapLookupMiss(benchmark::State& state) { + Envoy::Stats::SymbolTableImpl symbol_table; + Envoy::Stats::StatNamePool pool_map(symbol_table); + Envoy::Stats::StatNamePool pool_miss(symbol_table); + const std::vector map_names = prepareNames(pool_map, 2000, 24); + // Different token count produces names that won't be in the map. + const std::vector miss_names = prepareNames(pool_miss, 2000, 25); + + Envoy::Stats::StatNameHashMap map; + for (uint64_t i = 0; i < map_names.size(); ++i) { + map[map_names[i]] = i; + } + + uint32_t index = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(map.find(miss_names[index++ % miss_names.size()])); + } +} +BENCHMARK(bmStatNameMapLookupMiss); + // NOLINTNEXTLINE(readability-identifier-naming) static void bmSortStrings(benchmark::State& state) { Envoy::Stats::SymbolTableImpl symbol_table; @@ -213,3 +288,392 @@ static void bmSetStrings(benchmark::State& state) { } } BENCHMARK(bmSetStrings); + +// --------------------------------------------------------------------------- +// decodeNumber micro-benchmarks +// +// These benchmarks isolate the varint decode function with different +// implementations so we can measure the cost of the fast-path branch, +// loop overhead, etc. Values are parameterized across varint-size +// boundaries: 1-byte (<=127), 2-byte (128-16383), 3-byte (16384-2097151). +// --------------------------------------------------------------------------- + +using Encoding = Envoy::Stats::SymbolTable::Encoding; +static constexpr size_t MaxVarintBytes = 10; + +// Encode a varint into buf[]. Returns the number of bytes written. +static size_t encodeNumberToBuffer(uint64_t number, uint8_t* buf) { + size_t len = 0; + do { + if (number < Encoding::SpilloverMask) { + buf[len++] = static_cast(number); + } else { + buf[len++] = static_cast((number & Encoding::Low7Bits) | Encoding::SpilloverMask); + } + number >>= 7; + } while (number != 0); + return len; +} + +// Implementation A: fast-path + loop (matches the current header). +static std::pair decodeNumberFastPath(const uint8_t* encoding) { + if (encoding[0] < Encoding::SpilloverMask) { + return {encoding[0], 1}; + } + uint64_t number = 0; + uint64_t uc = Encoding::SpilloverMask; + const uint8_t* start = encoding; + for (uint32_t shift = 0; (uc & Encoding::SpilloverMask) != 0; ++encoding, shift += 7) { + uc = static_cast(*encoding); + number |= (uc & Encoding::Low7Bits) << shift; + } + return {number, static_cast(encoding - start)}; +} + +// Implementation B: loop-only (the original, no fast-path check). +static std::pair decodeNumberLoopOnly(const uint8_t* encoding) { + uint64_t number = 0; + uint64_t uc = Encoding::SpilloverMask; + const uint8_t* start = encoding; + for (uint32_t shift = 0; (uc & Encoding::SpilloverMask) != 0; ++encoding, shift += 7) { + uc = static_cast(*encoding); + number |= (uc & Encoding::Low7Bits) << shift; + } + return {number, static_cast(encoding - start)}; +} + +// Implementation C: do-while with index instead of pointer arithmetic. +static std::pair decodeNumberDoWhile(const uint8_t* encoding) { + uint64_t res = 0; + uint64_t byte; + size_t i = 0; + do { + byte = encoding[i]; + res |= (byte & Encoding::Low7Bits) << (7 * i); + i++; + } while ((byte & Encoding::SpilloverMask) && i < MaxVarintBytes); + return {res, i}; +} + +// Implementation D: unrolled with early returns per byte. +static std::pair decodeNumberUnrolled(const uint8_t* p) { + if (p[0] < Encoding::SpilloverMask) { + return {p[0], 1}; + } + uint64_t res = p[0] & Encoding::Low7Bits; + if (p[1] < Encoding::SpilloverMask) { + return {res | static_cast(p[1]) << 7, 2}; + } + res |= static_cast(p[1] & Encoding::Low7Bits) << 7; + if (p[2] < Encoding::SpilloverMask) { + return {res | static_cast(p[2]) << 14, 3}; + } + // Fall back to loop for 4+ byte varint (values >= 2^21). + res |= static_cast(p[2] & Encoding::Low7Bits) << 14; + for (size_t i = 3; i < MaxVarintBytes; ++i) { + if (p[i] < Encoding::SpilloverMask) { + return {res | static_cast(p[i]) << (7 * i), i + 1}; + } + res |= static_cast(p[i] & Encoding::Low7Bits) << (7 * i); + } + return {res, MaxVarintBytes}; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmDecodeNumber_FastPath(benchmark::State& state) { + uint8_t buf[MaxVarintBytes]; + encodeNumberToBuffer(static_cast(state.range(0)), buf); + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(buf); + auto result = decodeNumberFastPath(buf); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(bmDecodeNumber_FastPath) + ->Arg(10) + ->Arg(127) + ->Arg(128) + ->Arg(16383) + ->Arg(16384) + ->Arg(100000) + ->Arg(2097151) + ->Arg(2097152); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmDecodeNumber_LoopOnly(benchmark::State& state) { + uint8_t buf[MaxVarintBytes]; + encodeNumberToBuffer(static_cast(state.range(0)), buf); + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(buf); + auto result = decodeNumberLoopOnly(buf); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(bmDecodeNumber_LoopOnly) + ->Arg(10) + ->Arg(127) + ->Arg(128) + ->Arg(16383) + ->Arg(16384) + ->Arg(100000) + ->Arg(2097151) + ->Arg(2097152); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmDecodeNumber_DoWhile(benchmark::State& state) { + uint8_t buf[MaxVarintBytes]; + encodeNumberToBuffer(static_cast(state.range(0)), buf); + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(buf); + auto result = decodeNumberDoWhile(buf); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(bmDecodeNumber_DoWhile) + ->Arg(10) + ->Arg(127) + ->Arg(128) + ->Arg(16383) + ->Arg(16384) + ->Arg(100000) + ->Arg(2097151) + ->Arg(2097152); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmDecodeNumber_Unrolled(benchmark::State& state) { + uint8_t buf[MaxVarintBytes]; + encodeNumberToBuffer(static_cast(state.range(0)), buf); + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(buf); + auto result = decodeNumberUnrolled(buf); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(bmDecodeNumber_Unrolled) + ->Arg(10) + ->Arg(127) + ->Arg(128) + ->Arg(16383) + ->Arg(16384) + ->Arg(100000) + ->Arg(2097151) + ->Arg(2097152); + +// Pre-encoded varint buffers for mixed-distribution benchmarks. +// ~80% values <= 127 (1-byte fast-path), ~20% values > 127 (multi-byte). +static std::vector> prepareDecodeNumberMixedBuffers() { + const uint64_t values[] = {1, 42, 5, 100, 200, 77, 13, 63, 500, 120, 50000}; + constexpr size_t num_entries = 1024; + std::vector> buffers(num_entries); + for (size_t i = 0; i < num_entries; ++i) { + buffers[i].fill(0); + encodeNumberToBuffer(values[i % 11], buffers[i].data()); + } + return buffers; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmDecodeNumber_FastPathMixed(benchmark::State& state) { + const auto buffers = prepareDecodeNumberMixedBuffers(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + const auto& buf = buffers[idx++ % buffers.size()]; + benchmark::DoNotOptimize(buf); + auto result = decodeNumberFastPath(buf.data()); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(bmDecodeNumber_FastPathMixed); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmDecodeNumber_LoopOnlyMixed(benchmark::State& state) { + const auto buffers = prepareDecodeNumberMixedBuffers(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + const auto& buf = buffers[idx++ % buffers.size()]; + benchmark::DoNotOptimize(buf); + auto result = decodeNumberLoopOnly(buf.data()); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(bmDecodeNumber_LoopOnlyMixed); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmDecodeNumber_DoWhileMixed(benchmark::State& state) { + const auto buffers = prepareDecodeNumberMixedBuffers(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + const auto& buf = buffers[idx++ % buffers.size()]; + benchmark::DoNotOptimize(buf); + auto result = decodeNumberDoWhile(buf.data()); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(bmDecodeNumber_DoWhileMixed); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmDecodeNumber_UnrolledMixed(benchmark::State& state) { + const auto buffers = prepareDecodeNumberMixedBuffers(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + const auto& buf = buffers[idx++ % buffers.size()]; + benchmark::DoNotOptimize(buf); + auto result = decodeNumberUnrolled(buf.data()); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(bmDecodeNumber_UnrolledMixed); + +// --------------------------------------------------------------------------- +// encodingSizeBytes micro-benchmarks +// +// Compare implementations of the varint size calculation: loop, hybrid +// (branch + count-leading-zero), loop-with-early-exit, and pure count- +// leading-zero (branch-free). Two value distributions: short (1-byte varint) +// and mixed (1-5 byte). +// --------------------------------------------------------------------------- + +static size_t encodingSizeBytesLoop(uint64_t number) { + size_t num_bytes = 0; + do { + ++num_bytes; + number >>= 7; + } while (number != 0); + return num_bytes; +} + +static size_t encodingSizeBytesHybrid(uint64_t number) { + if (number < Encoding::SpilloverMask) { + return 1; + } + return (64 - std::countl_zero(number) + 6) / 7; +} + +static size_t encodingSizeBytesLoopEarly(uint64_t number) { + if (number < Encoding::SpilloverMask) { + return 1; + } + size_t num_bytes = 0; + do { + ++num_bytes; + number >>= 7; + } while (number != 0); + return num_bytes; +} + +static size_t encodingSizeBytesClz(uint64_t number) { + return (64 - std::countl_zero(number | 1) + 6) / 7; +} + +static std::vector prepareShortValues() { + std::vector values(1024); + for (size_t i = 0; i < values.size(); ++i) { + values[i] = (i * 7 + 13) % 127 + 1; + } + return values; +} + +static std::vector prepareMixedValues() { + std::vector values(1024); + const uint64_t representatives[] = {42, 200, 100000, 0x1000000, 0x100000000ULL}; + for (size_t i = 0; i < values.size(); ++i) { + values[i] = representatives[i % 5]; + } + return values; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmEncodingSizeBytes_HybridShort(benchmark::State& state) { + const std::vector vals = prepareShortValues(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(encodingSizeBytesHybrid(vals[idx++ % vals.size()])); + } +} +BENCHMARK(bmEncodingSizeBytes_HybridShort); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmEncodingSizeBytes_HybridMixed(benchmark::State& state) { + const std::vector vals = prepareMixedValues(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(encodingSizeBytesHybrid(vals[idx++ % vals.size()])); + } +} +BENCHMARK(bmEncodingSizeBytes_HybridMixed); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmEncodingSizeBytes_LoopShort(benchmark::State& state) { + const std::vector vals = prepareShortValues(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(encodingSizeBytesLoop(vals[idx++ % vals.size()])); + } +} +BENCHMARK(bmEncodingSizeBytes_LoopShort); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmEncodingSizeBytes_LoopMixed(benchmark::State& state) { + const std::vector vals = prepareMixedValues(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(encodingSizeBytesLoop(vals[idx++ % vals.size()])); + } +} +BENCHMARK(bmEncodingSizeBytes_LoopMixed); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmEncodingSizeBytes_LoopEarlyShort(benchmark::State& state) { + const std::vector vals = prepareShortValues(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(encodingSizeBytesLoopEarly(vals[idx++ % vals.size()])); + } +} +BENCHMARK(bmEncodingSizeBytes_LoopEarlyShort); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmEncodingSizeBytes_LoopEarlyMixed(benchmark::State& state) { + const std::vector vals = prepareMixedValues(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(encodingSizeBytesLoopEarly(vals[idx++ % vals.size()])); + } +} +BENCHMARK(bmEncodingSizeBytes_LoopEarlyMixed); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmEncodingSizeBytes_ClzShort(benchmark::State& state) { + const std::vector vals = prepareShortValues(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(encodingSizeBytesClz(vals[idx++ % vals.size()])); + } +} +BENCHMARK(bmEncodingSizeBytes_ClzShort); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void bmEncodingSizeBytes_ClzMixed(benchmark::State& state) { + const std::vector vals = prepareMixedValues(); + uint32_t idx = 0; + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); + benchmark::DoNotOptimize(encodingSizeBytesClz(vals[idx++ % vals.size()])); + } +} +BENCHMARK(bmEncodingSizeBytes_ClzMixed); From 329bb772300a3aa1113e1fb74a67010817f7e582 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 29 Apr 2026 14:42:44 +0100 Subject: [PATCH 157/750] bazel: Use bazel mirrors where available (#44729) this probably wont help the github situation much - but probs worth adding anyway Signed-off-by: Ryan Northey --- api/bazel/repository_locations.bzl | 5 ++++- bazel/repository_locations.bzl | 20 ++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 29910ab6953fd..5210172922e01 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -3,7 +3,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( bazel_skylib = dict( version = "1.9.0", sha256 = "3b5b49006181f5f8ff626ef8ddceaa95e9bb8ad294f7b5d7b11ea9f7ddaf8c59", - urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/{version}/bazel-skylib-{version}.tar.gz"], + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/{version}/bazel-skylib-{version}.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/{version}/bazel-skylib-{version}.tar.gz", + ], ), com_envoyproxy_protoc_gen_validate = dict( sha256 = "20da84f8efe8f53c10cbde79d2cdd1e63365b0ae1ad9de22af56aa1a49d59330", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 99ce9a21accd6..a3ea4961962fa 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -32,7 +32,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( bazel_gazelle = dict( version = "0.47.0", sha256 = "675114d8b433d0a9f54d81171833be96ebc4113115664b791e6f204d58e93446", - urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/v{version}/bazel-gazelle-v{version}.tar.gz"], + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v{version}/bazel-gazelle-v{version}.tar.gz", + "https://github.com/bazelbuild/bazel-gazelle/releases/download/v{version}/bazel-gazelle-v{version}.tar.gz", + ], ), build_bazel_rules_apple = dict( version = "3.20.1", @@ -452,7 +455,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( io_bazel_rules_go = dict( version = "0.60.0", sha256 = "86d3dc8f59d253524f933aaf2f3c05896cb0b605fc35b460c0b4b039996124c6", - urls = ["https://github.com/bazelbuild/rules_go/releases/download/v{version}/rules_go-v{version}.zip"], + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v{version}/rules_go-v{version}.zip", + "https://github.com/bazelbuild/rules_go/releases/download/v{version}/rules_go-v{version}.zip", + ], ), rules_cc = dict( version = "0.2.17", @@ -494,7 +500,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( version = "0.7.1", sha256 = "3709d1745ba4be4ef054449647b62e424267066eca887bb00dd29242cb8463a0", strip_prefix = "rules_shell-{version}", - urls = ["https://github.com/bazelbuild/rules_shell/releases/download/v{version}/rules_shell-v{version}.tar.gz"], + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/rules_shell/releases/download/v{version}/rules_shell-v{version}.tar.gz", + "https://github.com/bazelbuild/rules_shell/releases/download/v{version}/rules_shell-v{version}.tar.gz", + ], ), wamr = dict( version = "WAMR-2.4.4", @@ -668,7 +677,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( rules_license = dict( version = "1.0.0", sha256 = "26d4021f6898e23b82ef953078389dd49ac2b5618ac564ade4ef87cced147b38", - urls = ["https://github.com/bazelbuild/rules_license/releases/download/{version}/rules_license-{version}.tar.gz"], + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/rules_license/releases/download/{version}/rules_license-{version}.tar.gz", + "https://github.com/bazelbuild/rules_license/releases/download/{version}/rules_license-{version}.tar.gz", + ], ), libmaxminddb = dict( version = "1.13.3", From e842507299a7f22ab1cc5368080c2d866536050e Mon Sep 17 00:00:00 2001 From: code Date: Wed, 29 Apr 2026 21:47:27 +0800 Subject: [PATCH 158/750] composite: add inline matcher support (#43996) Commit Message: composite: add inline matcher support Additional Description: In the previous implementation, the composite only could be used with ExtensionWithMatcher which is pretty weird and hard to understand for users. This PR added the inline matcher support to composite to make it could be configured in the `http_filters` directly. Risk Level: low. Testing: unit/integration. Docs Changes: n/a. Release Notes: n/a. Platform Specific Features: n/a. --------- Signed-off-by: wbpcode/wangbaiping Signed-off-by: code --- .../filters/http/composite/v3/composite.proto | 18 +- changelogs/current.yaml | 8 + source/extensions/extensions_metadata.yaml | 1 + .../filters/http/composite/config.cc | 100 +++- .../filters/http/composite/config.h | 25 +- .../filters/http/composite/filter.cc | 97 +++- .../filters/http/composite/filter.h | 24 +- test/extensions/filters/http/composite/BUILD | 3 + .../composite_filter_integration_test.cc | 479 +++++++++++------- .../filters/http/composite/filter_test.cc | 173 ++++++- 10 files changed, 693 insertions(+), 235 deletions(-) diff --git a/api/envoy/extensions/filters/http/composite/v3/composite.proto b/api/envoy/extensions/filters/http/composite/v3/composite.proto index 1835240d9f0ad..00ee5cd8bae4c 100644 --- a/api/envoy/extensions/filters/http/composite/v3/composite.proto +++ b/api/envoy/extensions/filters/http/composite/v3/composite.proto @@ -41,17 +41,31 @@ message Composite { // as it avoids duplicating the filter chain configuration. map named_filter_chains = 1; - // [#not-implemented-hide:] // The match tree that will be used to select an action to execute. The action type should be // :ref:`ExecuteFilterAction // `. + // + // .. warning:: + // This should only be set when using the Composite filter as in the :ref:`http_filters + // `. + // Never set this field when using the Composite filter with the :ref:`ExtensionWithMatcher + // ` which will result in + // undefined behavior. + // xds.type.matcher.v3.Matcher matcher = 2; } // Per-route configuration for the Composite filter. -// [#not-implemented-hide:] message CompositePerRoute { // Override of the match tree for this route. + // + // .. warning:: + // This should only be set when using the Composite filter as in the :ref:`http_filters + // `. + // Never set this field when using the Composite filter with the :ref:`ExtensionWithMatcher + // ` which will result in + // undefined behavior. + // xds.type.matcher.v3.Matcher matcher = 1 [(validate.rules).message = {required: true}]; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9af0fadabfff4..ed7511ffd5160 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -64,5 +64,13 @@ new_features: RSA public key exchange on behalf of the client. Added a new :ref:`downstream_ssl ` config option with ``DISABLE``, ``REQUIRE``, and ``ALLOW`` modes. +- area: composite + change: | + Added support for the + :ref:`inline matcher ` + in the composite HTTP filter. Now users could specify the matcher inline in the filter configuration + instead of using the :ref:`ExtensionWithMatcher + ` filter. + deprecated: diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 44611700b2358..d257439d04399 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -417,6 +417,7 @@ envoy.filters.http.composite: status_upstream: alpha type_urls: - envoy.extensions.filters.http.composite.v3.Composite + - envoy.extensions.filters.http.composite.v3.CompositePerRoute envoy.filters.http.compressor: categories: - envoy.filters.http diff --git a/source/extensions/filters/http/composite/config.cc b/source/extensions/filters/http/composite/config.cc index c70866a8753f0..a2c985cc38ac7 100644 --- a/source/extensions/filters/http/composite/config.cc +++ b/source/extensions/filters/http/composite/config.cc @@ -11,6 +11,33 @@ namespace Extensions { namespace HttpFilters { namespace Composite { +class MatchTreeValidationVisitor + : public Matcher::MatchTreeValidationVisitor { +public: + absl::Status + performDataInputValidation(const Matcher::DataInputFactory&, + absl::string_view) override { + return absl::OkStatus(); + } +}; + +absl::StatusOr> +createMatcherTree(const xds::type::matcher::v3::Matcher& matcher_config, + Envoy::Http::Matching::HttpFilterActionContext& action_context) { + MatchTreeValidationVisitor match_tree_validation_visitor; + Matcher::MatchTreeFactory + matcher_factory(action_context, action_context.server_factory_context_.value(), + match_tree_validation_visitor); + auto factory_cb = matcher_factory.create(matcher_config); + if (!match_tree_validation_visitor.errors().empty()) { + return absl::InvalidArgumentError( + fmt::format("requirement violation while creating match tree: {}", + match_tree_validation_visitor.errors()[0])); + } + return factory_cb(); +} + absl::StatusOr CompositeFilterFactory::compileNamedFilterChains( const envoy::extensions::filters::http::composite::v3::Composite& config, @@ -63,35 +90,88 @@ absl::StatusOr CompositeFilterFactory::createFilterFactor RETURN_IF_NOT_OK(named_chains_or_error.status()); auto named_chains = std::move(named_chains_or_error.value()); + Matcher::MatchTreeSharedPtr match_tree = nullptr; + if (proto_config.has_matcher()) { + Envoy::Http::Matching::HttpFilterActionContext action_context{ + .is_downstream_ = true, + .stat_prefix_ = stats_prefix, + .factory_context_ = context, + .upstream_factory_context_ = absl::nullopt, + .server_factory_context_ = context.serverFactoryContext()}; + auto match_tree_or_error = createMatcherTree(proto_config.matcher(), action_context); + RETURN_IF_NOT_OK(match_tree_or_error.status()); + match_tree = std::move(match_tree_or_error.value()); + } + const auto& prefix = stats_prefix + "composite."; auto stats = std::make_shared( FilterStats{ALL_COMPOSITE_FILTER_STATS(POOL_COUNTER_PREFIX(context.scope(), prefix))}); - return [stats, named_chains](Http::FilterChainFactoryCallbacks& callbacks) -> void { + return [stats, named_chains, match_tree](Http::FilterChainFactoryCallbacks& callbacks) -> void { auto filter = std::make_shared(*stats, callbacks.dispatcher(), false /* is_upstream */, - named_chains); + match_tree, named_chains); callbacks.addStreamFilter(filter); callbacks.addAccessLogHandler(filter); }; } -absl::StatusOr CompositeFilterFactory::createFilterFactoryFromProtoTyped( - const envoy::extensions::filters::http::composite::v3::Composite&, - const std::string& stat_prefix, DualInfo dual_info, - Server::Configuration::ServerFactoryContext&) { +absl::StatusOr CompositeFilterFactory::createFilterFactoryFromProto( + const Protobuf::Message& config, const std::string& stats_prefix, + Server::Configuration::UpstreamFactoryContext& context) { + const auto& proto_config = MessageUtil::downcastAndValidate< + const envoy::extensions::filters::http::composite::v3::Composite&>( + config, context.serverFactoryContext().messageValidationVisitor()); + + Matcher::MatchTreeSharedPtr match_tree = nullptr; + if (proto_config.has_matcher()) { + Envoy::Http::Matching::HttpFilterActionContext action_context{ + .is_downstream_ = false, + .stat_prefix_ = stats_prefix, + .factory_context_ = absl::nullopt, + .upstream_factory_context_ = context, + .server_factory_context_ = context.serverFactoryContext()}; + + auto match_tree_or_error = createMatcherTree(proto_config.matcher(), action_context); + RETURN_IF_NOT_OK(match_tree_or_error.status()); + match_tree = std::move(match_tree_or_error.value()); + } + // This method is called for upstream filters. // Named filter chains are not supported for upstream filters. - const auto& prefix = stat_prefix + "composite."; + const auto& prefix = stats_prefix + "composite."; auto stats = std::make_shared( - FilterStats{ALL_COMPOSITE_FILTER_STATS(POOL_COUNTER_PREFIX(dual_info.scope, prefix))}); + FilterStats{ALL_COMPOSITE_FILTER_STATS(POOL_COUNTER_PREFIX(context.scope(), prefix))}); - return [stats, dual_info](Http::FilterChainFactoryCallbacks& callbacks) -> void { - auto filter = std::make_shared(*stats, callbacks.dispatcher(), dual_info.is_upstream); + return [stats, match_tree](Http::FilterChainFactoryCallbacks& callbacks) -> void { + auto filter = std::make_shared(*stats, callbacks.dispatcher(), true /* is_upstream */, + match_tree, nullptr); callbacks.addStreamFilter(filter); callbacks.addAccessLogHandler(filter); }; } +absl::StatusOr +CompositeFilterFactory::createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::composite::v3::CompositePerRoute& config, + Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor&) { + + Envoy::Http::Matching::HttpFilterActionContext action_context{ + .is_downstream_ = true, + .stat_prefix_ = + fmt::format("http.{}.", context.scope().symbolTable().toString(context.scope().prefix())), + .factory_context_ = absl::nullopt, + .upstream_factory_context_ = absl::nullopt, + .server_factory_context_ = context}; + + auto match_tree_or_error = createMatcherTree(config.matcher(), action_context); + RETURN_IF_NOT_OK(match_tree_or_error.status()); + auto match_tree = std::move(match_tree_or_error.value()); + if (match_tree == nullptr) { + return nullptr; + } + return std::make_shared(match_tree); +} + /** * Static registration for the composite filter. @see RegisterFactory. */ diff --git a/source/extensions/filters/http/composite/config.h b/source/extensions/filters/http/composite/config.h index be0a3a18400b0..8859adecfbcce 100644 --- a/source/extensions/filters/http/composite/config.h +++ b/source/extensions/filters/http/composite/config.h @@ -23,22 +23,29 @@ namespace Composite { * Config registration for the composite filter. @see NamedHttpFilterConfigFactory. */ class CompositeFilterFactory - : public Common::DualFactoryBase { + : public HttpFilters::Common::CommonFactoryBase< + envoy::extensions::filters::http::composite::v3::Composite, + envoy::extensions::filters::http::composite::v3::CompositePerRoute>, + public Server::Configuration::NamedHttpFilterConfigFactory, + public Server::Configuration::UpstreamHttpFilterConfigFactory { public: - CompositeFilterFactory() : DualFactoryBase("envoy.filters.http.composite") {} - - // Bring base class overloads into scope to avoid hiding them. - using DualFactoryBase::createFilterFactoryFromProto; + CompositeFilterFactory() : CommonFactoryBase("envoy.filters.http.composite") {} // Override to compile named filter chains with FactoryContext access. absl::StatusOr createFilterFactoryFromProto(const Protobuf::Message& config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override; - absl::StatusOr createFilterFactoryFromProtoTyped( - const envoy::extensions::filters::http::composite::v3::Composite& proto_config, - const std::string& stats_prefix, DualInfo dual_info, - Server::Configuration::ServerFactoryContext& context) override; + absl::StatusOr + createFilterFactoryFromProto(const Protobuf::Message& proto_config, + const std::string& stats_prefix, + Server::Configuration::UpstreamFactoryContext& context) override; + + absl::StatusOr + createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::composite::v3::CompositePerRoute& config, + Server::Configuration::ServerFactoryContext& context, + ProtobufMessage::ValidationVisitor&) override; // Compiles named filter chains from the config. static absl::StatusOr diff --git a/source/extensions/filters/http/composite/filter.cc b/source/extensions/filters/http/composite/filter.cc index d3a7f1b58875c..1ea21afc13551 100644 --- a/source/extensions/filters/http/composite/filter.cc +++ b/source/extensions/filters/http/composite/filter.cc @@ -1,8 +1,11 @@ #include "source/extensions/filters/http/composite/filter.h" +#include + #include "envoy/http/filter.h" #include "source/common/common/stl_helpers.h" +#include "source/common/http/utility.h" namespace Envoy { namespace Extensions { @@ -40,6 +43,9 @@ std::unique_ptr MatchedActionInfo::buildProtoStruct() const { } Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { + resolvePerRouteConfig(); + matchAndHandleActions(); + return delegateFilterActionOr(delegated_filter_, &StreamDecoderFilter::decodeHeaders, Http::FilterHeadersStatus::Continue, headers, end_stream); } @@ -50,6 +56,7 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea } Http::FilterTrailersStatus Filter::decodeTrailers(Http::RequestTrailerMap& trailers) { + matchAndHandleActions(); return delegateFilterActionOr(delegated_filter_, &StreamDecoderFilter::decodeTrailers, Http::FilterTrailersStatus::Continue, trailers); } @@ -71,6 +78,7 @@ Http::Filter1xxHeadersStatus Filter::encode1xxHeaders(Http::ResponseHeaderMap& h } Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) { + matchAndHandleActions(); return delegateFilterActionOr(delegated_filter_, &StreamEncoderFilter::encodeHeaders, Http::FilterHeadersStatus::Continue, headers, end_stream); } @@ -93,6 +101,28 @@ void Filter::encodeComplete() { } void Filter::onMatchCallback(const Matcher::Action& action) { + resolvePerRouteConfig(); + if (match_tree_ != nullptr) { + ENVOY_LOG(error, "Inline match tree is provided and the onMatchCallback should never be called " + "from the ExtensionWithMatcher"); + + return; + } + + // This is unnecessary actually because the match tree should be nullptr if we reached here, we + // just want to be defensive here to make sure the match tree won't be evaluated after this + // callback is called. + match_tree_evaluated_ = true; + + handleAction(action); +} + +void Filter::handleAction(const Matcher::Action& action) { + if (action.typeUrl() != ExecuteFilterAction::staticTypeUrl()) { + ENVOY_LOG(error, "Received unsupported action type: {}", action.typeUrl()); + return; + } + const auto& composite_action = action.getTyped(); // Handle named filter chain lookup. @@ -203,6 +233,49 @@ void Filter::onMatchCallback(const Matcher::Action& action) { // either directly or via some return status. } +void Filter::matchAndHandleActions() { + if (match_tree_evaluated_ || match_tree_ == nullptr || delegated_filter_ != nullptr) { + return; + } + + Envoy::Http::Matching::HttpMatchingDataImpl matching_data(decoder_callbacks_->streamInfo()); + auto request_headers = decoder_callbacks_->requestHeaders(); + if (request_headers.has_value()) { + matching_data.onRequestHeaders(request_headers.value()); + } + auto response_headers = encoder_callbacks_->responseHeaders(); + if (response_headers.has_value()) { + matching_data.onResponseHeaders(response_headers.value()); + } + auto request_trailers = decoder_callbacks_->requestTrailers(); + if (request_trailers.has_value()) { + matching_data.onRequestTrailers(request_trailers.value()); + } + + auto result = Matcher::evaluateMatch(*match_tree_, matching_data); + match_tree_evaluated_ = result.isComplete(); + if (result.isMatch()) { + const auto& action = result.action(); + if (action != nullptr) { + handleAction(*action); + } + } +} + +void Filter::resolvePerRouteConfig() { + if (per_route_config_resolved_) { + return; + } + + const auto* per_route_config = + Envoy::Http::Utility::resolveMostSpecificPerFilterConfig( + decoder_callbacks_); + if (per_route_config != nullptr) { + match_tree_ = per_route_config->matchTree(); + } + per_route_config_resolved_ = true; +} + void Filter::updateFilterState(Http::StreamFilterCallbacks* callback, const std::string& filter_name, const std::string& action_name) { if (isUpstream()) { @@ -369,8 +442,8 @@ void DelegatedFilterChain::decodeComplete() { Http::Filter1xxHeadersStatus DelegatedFilterChain::encode1xxHeaders(Http::ResponseHeaderMap& headers) { // Encode operations iterate in reverse order. - for (auto it = filters_.rbegin(); it != filters_.rend(); ++it) { - auto status = (*it)->encode1xxHeaders(headers); + for (auto& filter : std::ranges::reverse_view(filters_)) { + auto status = filter->encode1xxHeaders(headers); if (status != Http::Filter1xxHeadersStatus::Continue) { return status; } @@ -381,8 +454,8 @@ DelegatedFilterChain::encode1xxHeaders(Http::ResponseHeaderMap& headers) { Http::FilterHeadersStatus DelegatedFilterChain::encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) { // Encode operations iterate in reverse order. - for (auto it = filters_.rbegin(); it != filters_.rend(); ++it) { - auto status = (*it)->encodeHeaders(headers, end_stream); + for (auto& filter : std::ranges::reverse_view(filters_)) { + auto status = filter->encodeHeaders(headers, end_stream); if (status != Http::FilterHeadersStatus::Continue) { return status; } @@ -392,8 +465,8 @@ Http::FilterHeadersStatus DelegatedFilterChain::encodeHeaders(Http::ResponseHead Http::FilterDataStatus DelegatedFilterChain::encodeData(Buffer::Instance& data, bool end_stream) { // Encode operations iterate in reverse order. - for (auto it = filters_.rbegin(); it != filters_.rend(); ++it) { - auto status = (*it)->encodeData(data, end_stream); + for (auto& filter : std::ranges::reverse_view(filters_)) { + auto status = filter->encodeData(data, end_stream); if (status != Http::FilterDataStatus::Continue) { return status; } @@ -404,8 +477,8 @@ Http::FilterDataStatus DelegatedFilterChain::encodeData(Buffer::Instance& data, Http::FilterTrailersStatus DelegatedFilterChain::encodeTrailers(Http::ResponseTrailerMap& trailers) { // Encode operations iterate in reverse order. - for (auto it = filters_.rbegin(); it != filters_.rend(); ++it) { - auto status = (*it)->encodeTrailers(trailers); + for (auto& filter : std::ranges::reverse_view(filters_)) { + auto status = filter->encodeTrailers(trailers); if (status != Http::FilterTrailersStatus::Continue) { return status; } @@ -415,8 +488,8 @@ DelegatedFilterChain::encodeTrailers(Http::ResponseTrailerMap& trailers) { Http::FilterMetadataStatus DelegatedFilterChain::encodeMetadata(Http::MetadataMap& metadata_map) { // Encode operations iterate in reverse order. - for (auto it = filters_.rbegin(); it != filters_.rend(); ++it) { - auto status = (*it)->encodeMetadata(metadata_map); + for (auto& filter : std::ranges::reverse_view(filters_)) { + auto status = filter->encodeMetadata(metadata_map); if (status != Http::FilterMetadataStatus::Continue) { return status; } @@ -433,8 +506,8 @@ void DelegatedFilterChain::setEncoderFilterCallbacks( void DelegatedFilterChain::encodeComplete() { // Encode operations iterate in reverse order. - for (auto it = filters_.rbegin(); it != filters_.rend(); ++it) { - (*it)->encodeComplete(); + for (auto& filter : std::ranges::reverse_view(filters_)) { + filter->encodeComplete(); } } diff --git a/source/extensions/filters/http/composite/filter.h b/source/extensions/filters/http/composite/filter.h index 327260e713e7c..3590dc8461151 100644 --- a/source/extensions/filters/http/composite/filter.h +++ b/source/extensions/filters/http/composite/filter.h @@ -20,6 +20,18 @@ namespace Composite { constexpr absl::string_view MatchedActionsFilterStateKey = "envoy.extensions.filters.http.composite.matched_actions"; +class CompositePerRouteConfig : public Router::RouteSpecificFilterConfig { +public: + CompositePerRouteConfig(Matcher::MatchTreeSharedPtr match_tree) + : match_tree_(std::move(match_tree)) {} + const Matcher::MatchTreeSharedPtr& matchTree() const { + return match_tree_; + } + +private: + const Matcher::MatchTreeSharedPtr match_tree_; +}; + struct FactoryCallbacksWrapper; #define ALL_COMPOSITE_FILTER_STATS(COUNTER) \ @@ -92,9 +104,10 @@ class Filter : public Http::StreamFilter, Logger::Loggable { public: Filter(FilterStats& stats, Event::Dispatcher& dispatcher, bool is_upstream, + Matcher::MatchTreeSharedPtr match_tree = nullptr, NamedFilterChainFactoryMapSharedPtr named_filter_chains = nullptr) : dispatcher_(dispatcher), stats_(stats), is_upstream_(is_upstream), - named_filter_chains_(std::move(named_filter_chains)) {} + match_tree_(std::move(match_tree)), named_filter_chains_(std::move(named_filter_chains)) {} // Http::StreamDecoderFilter Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, @@ -137,6 +150,7 @@ class Filter : public Http::StreamFilter, } void onMatchCallback(const Matcher::Action& action) override; + void handleAction(const Matcher::Action& action); // AccessLog::Instance void log(const Formatter::Context& log_context, const StreamInfo::StreamInfo& info) override { @@ -153,6 +167,9 @@ class Filter : public Http::StreamFilter, void updateFilterState(Http::StreamFilterCallbacks* callback, const std::string& filter_name, const std::string& action_name); + void resolvePerRouteConfig(); + void matchAndHandleActions(); + Event::Dispatcher& dispatcher_; // Wraps a stream encoder OR a stream decoder filter into a stream filter, making it easier to @@ -198,7 +215,10 @@ class Filter : public Http::StreamFilter, Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; FilterStats& stats_; // Filter in the upstream filter chain. - bool is_upstream_; + bool is_upstream_{false}; + Matcher::MatchTreeSharedPtr match_tree_; + bool per_route_config_resolved_{false}; + bool match_tree_evaluated_{false}; // Named filter chains compiled at config time. NamedFilterChainFactoryMapSharedPtr named_filter_chains_; }; diff --git a/test/extensions/filters/http/composite/BUILD b/test/extensions/filters/http/composite/BUILD index 9a4e5371ad5c5..1aa7ff94f89c8 100644 --- a/test/extensions/filters/http/composite/BUILD +++ b/test/extensions/filters/http/composite/BUILD @@ -18,6 +18,8 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/common/http:header_map_lib", + "//source/common/http/matching:data_impl_lib", + "//source/common/matcher:matcher_lib", "//source/extensions/filters/http/composite:config", "//source/extensions/filters/http/composite:filter_lib", "//source/extensions/filters/http/fault:config", @@ -27,6 +29,7 @@ envoy_extension_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:instance_mocks", + "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:registry_lib", ], ) diff --git a/test/extensions/filters/http/composite/composite_filter_integration_test.cc b/test/extensions/filters/http/composite/composite_filter_integration_test.cc index 431ad8b8e5858..767c7e174375a 100644 --- a/test/extensions/filters/http/composite/composite_filter_integration_test.cc +++ b/test/extensions/filters/http/composite/composite_filter_integration_test.cc @@ -31,6 +31,7 @@ namespace Envoy { namespace { using envoy::extensions::common::matching::v3::ExtensionWithMatcherPerRoute; +using envoy::extensions::filters::http::composite::v3::CompositePerRoute; using envoy::extensions::filters::http::composite::v3::ExecuteFilterAction; using envoy::type::matcher::v3::HttpRequestHeaderMatchInput; using test::integration::filters::SetResponseCodeFilterConfig; @@ -39,10 +40,47 @@ using test::integration::filters::SetResponseCodePerRouteFilterConfig; using test::integration::filters::SetResponseCodePerRouteFilterConfigDual; using xds::type::matcher::v3::Matcher_OnMatch; +// Returns the complete YAML for a composite filter config, wrapping the given matcher_yaml. +// For inline matcher: uses the Composite type with a "matcher:" key. +// For ExtensionWithMatcher: uses the ExtensionWithMatcher type with an "xds_matcher:" key. +// If matcher_yaml is empty, generates a config without any matcher section. +std::string buildCompositeFilterConfig(const std::string& name, bool use_inline_matcher, + const std::string& matcher_yaml = "") { + if (use_inline_matcher) { + std::string config = absl::StrFormat(R"EOF( + name: %s + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.composite.v3.Composite +)EOF", + name); + if (!matcher_yaml.empty()) { + config += " matcher:\n"; + config += matcher_yaml; + } + return config; + } + std::string config = absl::StrFormat(R"EOF( + name: %s + typed_config: + "@type": type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher + extension_config: + name: composite + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.composite.v3.Composite +)EOF", + name); + if (!matcher_yaml.empty()) { + config += " xds_matcher:\n"; + config += matcher_yaml; + } + return config; +} + struct CompositeFilterTestParams { - Network::Address::IpVersion version; - bool is_downstream; - bool is_dual_factory; + Network::Address::IpVersion version{}; + bool is_downstream{}; + bool is_dual_factory{}; + bool use_inline_matcher{}; }; class CompositeFilterIntegrationTest : public testing::TestWithParam, @@ -51,7 +89,8 @@ class CompositeFilterIntegrationTest : public testing::TestWithParam base_action_function) { @@ -82,23 +121,59 @@ class CompositeFilterIntegrationTest : public testing::TestWithParammutable_matcher_tree(); + auto* matcher_input = matcher_tree->mutable_input(); + matcher_input->set_name("request-headers"); + HttpRequestHeaderMatchInput match_input; + match_input.set_header_name("match-header"); + matcher_input->mutable_typed_config()->PackFrom(match_input); + + auto* map = matcher_tree->mutable_exact_match_map()->mutable_map(); + Matcher_OnMatch on_match; + auto* mutable_action = on_match.mutable_action(); + mutable_action->set_name("composite-action"); + ExecuteFilterAction filter_action; + filter_action.mutable_typed_config()->set_name("set-response-code-filter"); + filter_action.mutable_typed_config()->mutable_typed_config()->PackFrom(set_response_code); + mutable_action->mutable_typed_config()->PackFrom(filter_action); + (*map)["match"] = on_match; + + config_helper_.addConfigModifier( + [per_route, filter_name, route_prefix](ConfigHelper::HttpConnectionManager& cm) { + auto* vh = cm.mutable_route_config()->mutable_virtual_hosts(0); + auto* route = vh->mutable_routes()->Mutable(0); + route->mutable_match()->set_prefix(route_prefix); + route->mutable_route()->set_cluster("cluster_0"); + (*route->mutable_typed_per_filter_config())[filter_name].PackFrom(per_route); + }); + } else { + ProtoConfig set_response_code; + set_response_code.set_code(code); + if (response_prefix) { + set_response_code.set_prefix("skipLocalReplyAndContinue"); + } + auto per_route_config = createPerRouteConfig([set_response_code](auto* cfg) { + cfg->set_name("set-response-code-filter"); + cfg->mutable_typed_config()->PackFrom(set_response_code); + }); + config_helper_.addConfigModifier( + [per_route_config, filter_name, route_prefix](ConfigHelper::HttpConnectionManager& cm) { + auto* vh = cm.mutable_route_config()->mutable_virtual_hosts(0); + auto* route = vh->mutable_routes()->Mutable(0); + route->mutable_match()->set_prefix(route_prefix); + route->mutable_route()->set_cluster("cluster_0"); + (*route->mutable_typed_per_filter_config())[filter_name].PackFrom(per_route_config); + }); } - auto per_route_config = createPerRouteConfig([set_response_code](auto* cfg) { - cfg->set_name("set-response-code-filter"); - cfg->mutable_typed_config()->PackFrom(set_response_code); - }); - config_helper_.addConfigModifier( - [per_route_config, filter_name, route_prefix](ConfigHelper::HttpConnectionManager& cm) { - auto* vh = cm.mutable_route_config()->mutable_virtual_hosts(0); - auto* route = vh->mutable_routes()->Mutable(0); - route->mutable_match()->set_prefix(route_prefix); - route->mutable_route()->set_cluster("cluster_0"); - (*route->mutable_typed_per_filter_config())[filter_name].PackFrom(per_route_config); - }); } void addPerRouteResponseCodeFilter(const std::string& filter_name, @@ -146,16 +221,7 @@ class CompositeFilterIntegrationTest : public testing::TestWithParam& params) { return absl::StrCat( (params.param.version == Network::Address::IpVersion::v4 ? "IPv4_" : "IPv6_"), (params.param.is_downstream ? "Downstream_" : "Upstream_"), - (params.param.is_dual_factory ? "DualFactory" : "DownstreamFactory")); + (params.param.is_dual_factory ? "DualFactory" : "DownstreamFactory"), + (params.param.use_inline_matcher ? "_InlineMatcher" : "_ExtensionWithMatcher")); } const std::string proto_type_dual_ = "SetResponseCodeFilterConfigDual"; @@ -380,12 +432,13 @@ class CompositeFilterIntegrationTest : public testing::TestWithParamheaders(), Http::HttpStatusIs("402")); } -class CompositeFilterSeverContextIntegrationTest : public HttpIntegrationTest, - public Grpc::GrpcClientIntegrationParamTest { +class CompositeFilterSeverContextIntegrationTest + : public HttpIntegrationTest, + public testing::TestWithParam< + std::tuple, bool>> { public: CompositeFilterSeverContextIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, ipVersion()) {} + Network::Address::IpVersion ipVersion() const { return std::get<0>(std::get<0>(GetParam())); } + Grpc::ClientType clientType() const { return std::get<1>(std::get<0>(GetParam())); } + bool useInlineMatcher() const { return std::get<1>(GetParam()); } + void createUpstreams() override { HttpIntegrationTest::createUpstreams(); // Create separate "upstreams" for test gRPC servers @@ -719,16 +751,8 @@ class CompositeFilterSeverContextIntegrationTest : public HttpIntegrationTest, address_prefix, grpc_upstreams_[0]->localAddress()->asString()); } - config_helper_.prependFilter(absl::StrFormat(R"EOF( - name: %s - typed_config: - "@type": type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher - extension_config: - name: composite - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.composite.v3.Composite - xds_matcher: - matcher_tree: + const std::string matcher_yaml = + absl::StrFormat(R"EOF( matcher_tree: input: name: request-headers typed_config: @@ -751,9 +775,9 @@ class CompositeFilterSeverContextIntegrationTest : public HttpIntegrationTest, "@type": type.googleapis.com/test.integration.filters.%s grpc_service: %s - )EOF", - name, sample_percent, context_filter_config, - grpc_config), +)EOF", + sample_percent, context_filter_config, grpc_config); + config_helper_.prependFilter(buildCompositeFilterConfig(name, useInlineMatcher(), matcher_yaml), downstream); } @@ -811,9 +835,16 @@ class CompositeFilterSeverContextIntegrationTest : public HttpIntegrationTest, FakeStreamPtr stream_; }; -INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDeferredProcessing, - CompositeFilterSeverContextIntegrationTest, GRPC_CLIENT_INTEGRATION_PARAMS, - Grpc::GrpcClientIntegrationParamTest::protocolTestParamsToString); +INSTANTIATE_TEST_SUITE_P( + IpVersionsClientTypeDeferredProcessing, CompositeFilterSeverContextIntegrationTest, + testing::Combine(GRPC_CLIENT_INTEGRATION_PARAMS, testing::Bool()), + [](const testing::TestParamInfo& p) { + const auto& grpc_params = std::get<0>(p.param); + return absl::StrCat( + std::get<1>(grpc_params) == Grpc::ClientType::EnvoyGrpc ? "EnvoyGrpc" : "GoogleGrpc", + std::get<0>(grpc_params) == Network::Address::IpVersion::v4 ? "_IPv4" : "_IPv6", + std::get<1>(p.param) ? "_InlineMatcher" : "_ExtensionWithMatcher"); + }); TEST_P(CompositeFilterSeverContextIntegrationTest, BasicFlowDownstreamFilterInDownstreamAllSampled) { @@ -855,19 +886,11 @@ class CompositeFilterChainIntegrationTest CompositeFilterChainIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam().version), downstream_filter_(GetParam().is_downstream), is_dual_factory_(GetParam().is_dual_factory), - proto_type_(is_dual_factory_ ? proto_type_dual_ : proto_type_downstream_) {} + proto_type_(is_dual_factory_ ? proto_type_dual_ : proto_type_downstream_), + use_inline_matcher_(GetParam().use_inline_matcher) {} void prependCompositeFilterChain(const std::string& name = "composite-filter-chain") { - config_helper_.prependFilter(absl::StrFormat(R"EOF( - name: %s - typed_config: - "@type": type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher - extension_config: - name: composite - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.composite.v3.Composite - xds_matcher: - matcher_tree: + const std::string matcher_yaml = absl::StrFormat(R"EOF( matcher_tree: input: name: request-headers typed_config: @@ -890,9 +913,10 @@ class CompositeFilterChainIntegrationTest "@type": type.googleapis.com/test.integration.filters.%s prefix: "/respond-directly" code: 418 - )EOF", - name, proto_type_), - downstream_filter_); +)EOF", + proto_type_); + config_helper_.prependFilter( + buildCompositeFilterConfig(name, use_inline_matcher_, matcher_yaml), downstream_filter_); } const Http::TestRequestHeaderMapImpl match_request_headers_ = {{":method", "GET"}, @@ -919,21 +943,25 @@ class CompositeFilterChainIntegrationTest std::vector ret; for (auto ip_version : TestEnvironment::getIpVersionsForTest()) { for (bool is_dual_factory : {true, false}) { - CompositeFilterTestParams params; - params.version = ip_version; - params.is_downstream = true; // Filter chains only supported for downstream. - params.is_dual_factory = is_dual_factory; - ret.push_back(params); + for (bool use_inline_matcher : {true, false}) { + CompositeFilterTestParams params; + params.version = ip_version; + params.is_downstream = true; // Filter chains only supported for downstream. + params.is_dual_factory = is_dual_factory; + params.use_inline_matcher = use_inline_matcher; + ret.push_back(params); + } } } return ret; } static std::string - FilterChainTestParamsToString(const ::testing::TestParamInfo& params) { + filterChainTestParamsToString(const ::testing::TestParamInfo& params) { return absl::StrCat( (params.param.version == Network::Address::IpVersion::v4 ? "IPv4_" : "IPv6_"), - (params.param.is_dual_factory ? "DualFactory" : "DownstreamFactory")); + (params.param.is_dual_factory ? "DualFactory" : "DownstreamFactory"), + (params.param.use_inline_matcher ? "_InlineMatcher" : "_ExtensionWithMatcher")); } const std::string proto_type_dual_ = "SetResponseCodeFilterConfigDual"; @@ -941,12 +969,13 @@ class CompositeFilterChainIntegrationTest bool downstream_filter_{true}; bool is_dual_factory_{false}; std::string proto_type_; + bool use_inline_matcher_{false}; }; INSTANTIATE_TEST_SUITE_P( IpVersions, CompositeFilterChainIntegrationTest, testing::ValuesIn(CompositeFilterChainIntegrationTest::getValuesForFilterChainTest()), - CompositeFilterChainIntegrationTest::FilterChainTestParamsToString); + CompositeFilterChainIntegrationTest::filterChainTestParamsToString); // Verifies that a filter chain executes all filters in order. When the path matches // /respond-directly, the second filter should respond with 418. @@ -1008,13 +1037,49 @@ class NamedFilterChainIntegrationTest : public testing::TestWithParam ret; for (auto ip_version : TestEnvironment::getIpVersionsForTest()) { for (bool is_dual_factory : {true, false}) { - CompositeFilterTestParams params; - params.version = ip_version; - params.is_downstream = true; // Named filter chains only supported for downstream. - params.is_dual_factory = is_dual_factory; - ret.push_back(params); + for (bool use_inline_matcher : {true, false}) { + CompositeFilterTestParams params; + params.version = ip_version; + params.is_downstream = true; // Named filter chains only supported for downstream. + params.is_dual_factory = is_dual_factory; + params.use_inline_matcher = use_inline_matcher; + ret.push_back(params); + } } } return ret; } - static std::string NamedFilterChainTestParamsToString( + static std::string namedFilterChainTestParamsToString( const ::testing::TestParamInfo& params) { return absl::StrCat( (params.param.version == Network::Address::IpVersion::v4 ? "IPv4_" : "IPv6_"), - (params.param.is_dual_factory ? "DualFactory" : "DownstreamFactory")); + (params.param.is_dual_factory ? "DualFactory" : "DownstreamFactory"), + (params.param.use_inline_matcher ? "_InlineMatcher" : "_ExtensionWithMatcher")); } const std::string proto_type_dual_ = "SetResponseCodeFilterConfigDual"; @@ -1164,12 +1250,13 @@ class NamedFilterChainIntegrationTest : public testing::TestWithParam()); }}; - Filter filter_with_chains(stats_, decoder_callbacks_.dispatcher(), false, named_chains); + Filter filter_with_chains(stats_, decoder_callbacks_.dispatcher(), false, nullptr, named_chains); filter_with_chains.setDecoderFilterCallbacks(decoder_callbacks_); filter_with_chains.setEncoderFilterCallbacks(encoder_callbacks_); @@ -1925,7 +1935,7 @@ TEST_F(FilterTest, NamedFilterChainLookupSuccess) { [&](Http::FilterChainFactoryCallbacks& cb) { cb.addStreamFilter(filter1); }, [&](Http::FilterChainFactoryCallbacks& cb) { cb.addStreamFilter(filter2); }}; - Filter filter_with_chains(stats_, decoder_callbacks_.dispatcher(), false, named_chains); + Filter filter_with_chains(stats_, decoder_callbacks_.dispatcher(), false, nullptr, named_chains); filter_with_chains.setDecoderFilterCallbacks(decoder_callbacks_); filter_with_chains.setEncoderFilterCallbacks(encoder_callbacks_); @@ -2006,7 +2016,7 @@ TEST_F(FilterTest, NamedFilterChainLookupWithAccessLoggers) { }, }; - Filter filter_with_chains(stats_, decoder_callbacks_.dispatcher(), false, named_chains); + Filter filter_with_chains(stats_, decoder_callbacks_.dispatcher(), false, nullptr, named_chains); filter_with_chains.setDecoderFilterCallbacks(decoder_callbacks_); filter_with_chains.setEncoderFilterCallbacks(encoder_callbacks_); @@ -2025,6 +2035,161 @@ TEST_F(FilterTest, NamedFilterChainLookupWithAccessLoggers) { filter_with_chains.onDestroy(); } +// Helper to build a simple AnyMatcher that always resolves to the given action. +static Matcher::MatchTreeSharedPtr +makeAlwaysMatchTree(Matcher::ActionConstSharedPtr action) { + Matcher::OnMatch on_match{action, nullptr, false}; + return std::make_shared>(on_match); +} + +// Helper to build an AnyMatcher with no on_no_match, i.e. never matches. +static Matcher::MatchTreeSharedPtr makeNeverMatchTree() { + return std::make_shared>(absl::nullopt); +} + +// Test that onMatchCallback logs an error and is a no-op when an inline match_tree is provided. +// The inline tree is evaluated in decodeHeaders instead. +TEST_F(FilterTest, OnMatchCallbackIgnoredWhenInlineMatchTreeIsSet) { + auto stream_filter = std::make_shared(); + Http::FilterFactoryCb factory_callback = [&](Http::FilterChainFactoryCallbacks& cb) { + cb.addStreamFilter(stream_filter); + }; + auto action = std::make_shared( + [&]() -> OptRef { return factory_callback; }, "actionName", + absl::nullopt, context_.runtime_loader_); + + // Create a filter with a never-match inline tree; we only want to test onMatchCallback. + Filter filter_with_tree(stats_, decoder_callbacks_.dispatcher(), false, makeNeverMatchTree()); + filter_with_tree.setDecoderFilterCallbacks(decoder_callbacks_); + filter_with_tree.setEncoderFilterCallbacks(encoder_callbacks_); + + // onMatchCallback should log an error and skip the action when match_tree_ is set. + EXPECT_CALL(success_counter_, inc()).Times(0); + EXPECT_LOG_CONTAINS("error", + "Inline match tree is provided and the onMatchCallback should never be " + "called from the ExtensionWithMatcher", + filter_with_tree.onMatchCallback(*action)); + + filter_with_tree.onDestroy(); +} + +// Test that decodeHeaders executes the match tree action when the tree produces a match. +TEST_F(FilterTest, DecodeHeadersExecutesInlineMatchTreeAction) { + auto stream_filter = std::make_shared(); + StreamInfo::FilterStateSharedPtr filter_state = + std::make_shared(StreamInfo::FilterState::LifeSpan::Connection); + ON_CALL(decoder_callbacks_, filterConfigName()).WillByDefault(testing::Return("rootFilterName")); + ON_CALL(decoder_callbacks_.stream_info_, filterState()) + .WillByDefault(testing::ReturnRef(filter_state)); + + Http::FilterFactoryCb factory_callback = [&](Http::FilterChainFactoryCallbacks& cb) { + cb.addStreamFilter(stream_filter); + }; + auto action = std::make_shared( + [&]() -> OptRef { return factory_callback; }, "actionName", + absl::nullopt, context_.runtime_loader_); + + Filter filter_with_tree(stats_, decoder_callbacks_.dispatcher(), false, + makeAlwaysMatchTree(action)); + filter_with_tree.setDecoderFilterCallbacks(decoder_callbacks_); + filter_with_tree.setEncoderFilterCallbacks(encoder_callbacks_); + + // The match tree always matches, so the filter should be delegated. + EXPECT_CALL(*stream_filter, setDecoderFilterCallbacks(_)); + EXPECT_CALL(*stream_filter, setEncoderFilterCallbacks(_)); + EXPECT_CALL(success_counter_, inc()); + EXPECT_CALL(*stream_filter, decodeHeaders(_, false)) + .WillOnce(testing::Return(Http::FilterHeadersStatus::Continue)); + + auto status = filter_with_tree.decodeHeaders(default_request_headers_, false); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, status); + + EXPECT_CALL(*stream_filter, onDestroy()); + filter_with_tree.onDestroy(); +} + +// Test that decodeHeaders is a no-op (continues) when the match tree produces no match. +TEST_F(FilterTest, DecodeHeadersNoActionWhenMatchTreeDoesNotMatch) { + Filter filter_with_tree(stats_, decoder_callbacks_.dispatcher(), false, makeNeverMatchTree()); + filter_with_tree.setDecoderFilterCallbacks(decoder_callbacks_); + filter_with_tree.setEncoderFilterCallbacks(encoder_callbacks_); + + EXPECT_CALL(success_counter_, inc()).Times(0); + auto status = filter_with_tree.decodeHeaders(default_request_headers_, false); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, status); + + filter_with_tree.onDestroy(); +} + +// Test that decodeHeaders picks up the per-route match tree and overrides the filter-level one. +TEST_F(FilterTest, DecodeHeadersUsesPerRouteMatchTree) { + auto stream_filter = std::make_shared(); + StreamInfo::FilterStateSharedPtr filter_state = + std::make_shared(StreamInfo::FilterState::LifeSpan::Connection); + ON_CALL(decoder_callbacks_, filterConfigName()).WillByDefault(testing::Return("rootFilterName")); + ON_CALL(decoder_callbacks_.stream_info_, filterState()) + .WillByDefault(testing::ReturnRef(filter_state)); + + Http::FilterFactoryCb factory_callback = [&](Http::FilterChainFactoryCallbacks& cb) { + cb.addStreamFilter(stream_filter); + }; + auto action = std::make_shared( + [&]() -> OptRef { return factory_callback; }, "actionName", + absl::nullopt, context_.runtime_loader_); + + // The filter-level match tree never matches. + Filter filter_with_tree(stats_, decoder_callbacks_.dispatcher(), false, makeNeverMatchTree()); + filter_with_tree.setDecoderFilterCallbacks(decoder_callbacks_); + filter_with_tree.setEncoderFilterCallbacks(encoder_callbacks_); + + // Set up per-route config that always matches. + auto per_route_match_tree = makeAlwaysMatchTree(action); + auto per_route_config = std::make_shared(per_route_match_tree); + + ON_CALL(decoder_callbacks_, mostSpecificPerFilterConfig()) + .WillByDefault(testing::Return(per_route_config.get())); + + // The per-route match tree always matches, so the filter should be delegated. + EXPECT_CALL(*stream_filter, setDecoderFilterCallbacks(_)); + EXPECT_CALL(*stream_filter, setEncoderFilterCallbacks(_)); + EXPECT_CALL(success_counter_, inc()); + EXPECT_CALL(*stream_filter, decodeHeaders(_, false)) + .WillOnce(testing::Return(Http::FilterHeadersStatus::Continue)); + + auto status = filter_with_tree.decodeHeaders(default_request_headers_, false); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, status); + + EXPECT_CALL(*stream_filter, onDestroy()); + filter_with_tree.onDestroy(); +} + +// Test handleAction with an unsupported action type URL logs an error and does nothing. +TEST_F(FilterTest, HandleActionWithUnsupportedActionTypeLogsError) { + // Create a custom action with a different type URL. + class WrongTypeAction : public Matcher::ActionBase< + envoy::extensions::filters::http::composite::v3::CompositePerRoute> { + }; + + auto wrong_action = std::make_shared(); + Filter filter_with_tree(stats_, decoder_callbacks_.dispatcher(), false, + makeAlwaysMatchTree(wrong_action)); + filter_with_tree.setDecoderFilterCallbacks(decoder_callbacks_); + filter_with_tree.setEncoderFilterCallbacks(encoder_callbacks_); + + EXPECT_CALL(success_counter_, inc()).Times(0); + EXPECT_LOG_CONTAINS("error", "Received unsupported action type", + filter_with_tree.decodeHeaders(default_request_headers_, false)); + + filter_with_tree.onDestroy(); +} + +// Test CompositePerRouteConfig stores and returns the match tree. +TEST(CompositePerRouteConfigTest, StoresAndReturnsMatchTree) { + auto match_tree = makeNeverMatchTree(); + CompositePerRouteConfig config(match_tree); + EXPECT_EQ(match_tree, config.matchTree()); +} + } // namespace } // namespace Composite } // namespace HttpFilters From 6f8da597961f61f7b0874db95461788a63c7ce8b Mon Sep 17 00:00:00 2001 From: William Dauchy Date: Wed, 29 Apr 2026 16:25:13 +0200 Subject: [PATCH 159/750] golang: replace per-cgo-call destroy mutex with std::atomic (#44662) Commit Message: Every CAPI call from Go into the C++ filter (getHeader, copyHeaders, setHeader, addData, continueStatus, ...) was acquiring Filter::mutex_ purely to read the has_destroyed_ flag. Off-thread Go callers paid the cost twice: once at the entry point and again inside the dispatcher-post lambda's hasDestroyed() check. On uncontended fast paths this is two atomic compare-and-swap operations and a memory fence per call, on top of the cgo boundary cost that's already paid for every API call. Make has_destroyed_ a std::atomic with release-store on the single onDestroy() write and acquire-load on the read path. This turns the destroy check into a plain atomic load (free on x86, an ldar on ARM) and removes the mutex acquisition from 18 CAPI entry points and ~12 dispatcher-post lambdas. The Filter is kept alive across cgo calls by the shared_ptr taken at the cgo wrapper layer (cgo.cc), so seeing a false-then-true transition mid-call is benign; the lambdas all also check weak_from_this() expiration. Filter::mutex_ is retained on the five CAPI methods that write to req_->strValue (getStringValue, getDynamicMetadata, getStringFilterState, getStringProperty, getSecret) to keep the existing serialization between off-thread Go callers writing to the shared scratch buffer. No behaviour change for unit/integration tests; existing onDestroy() ordering and the post-destroy CAPIFilterIsDestroy semantics are preserved. Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: William Dauchy --- changelogs/current.yaml | 16 ++ .../filters/http/source/golang_filter.cc | 184 +++++++++--------- .../filters/http/source/golang_filter.h | 46 ++++- 3 files changed, 152 insertions(+), 94 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index ed7511ffd5160..456b63e7e7f28 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -5,6 +5,22 @@ behavior_changes: minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* +- area: golang + change: | + Reduced the per-cgo-call mutex acquisition on the Golang HTTP filter by making the + ``has_destroyed_`` flag a ``std::atomic``. CAPI methods whose only Envoy-side work is + Filter-owned or runs on the worker thread (``setHeader``, ``removeHeader``, ``setTrailer``, + ``removeTrailer``, ``addData``, ``injectData``, ``continueStatus``, ``sendLocalReply``, + ``setBufferHelper``, ``copyBuffer``, ``drainBuffer``, ``setUpstreamOverrideHost``, + ``clearRouteCache``, ``setDynamicMetadata``, ``setStringFilterState``) no longer take the + mutex, eliminating an uncontended atomic compare-and-swap pair on every such call. The + mutex is retained on the CAPI methods that inline-dereference Envoy-stream-owned objects + from off-thread (``getHeader``, ``copyHeaders``, ``copyTrailers``, ``getIntegerValue``, + ``setDrainConnectionUponCompletion``) where it serialises against ``onDestroy`` to prevent + the worker thread from freeing the underlying header map or ``StreamInfo`` mid-access, and + on the five methods that write to the per-request ``strValue`` scratch buffer + (``getStringValue``, ``getDynamicMetadata``, ``getStringFilterState``, ``getStringProperty``, + ``getSecret``). bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index eb3ed282bb341..0cee621746905 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -151,13 +151,20 @@ void Filter::onDestroy() { return; } + // Acquire mutex_ so any off-thread CAPI caller currently dereferencing an Envoy-stream-owned + // object (HeaderMap / TrailerMap / StreamInfo via getHeader, copyHeaders, copyTrailers, + // getIntegerValue) finishes before this worker thread can return into destroyFilters() and let + // the event loop process the deferred-delete that frees the underlying stream. The flag itself + // is std::atomic so unrelated CAPI methods can observe destruction lock-free; this lock only + // exists to provide the worker-stall fence. See has_destroyed_ comment in golang_filter.h. + bool already_destroyed; { Thread::LockGuard lock(mutex_); - if (has_destroyed_) { - ENVOY_LOG(debug, "golang filter has been destroyed"); - return; - } - has_destroyed_ = true; + already_destroyed = has_destroyed_.exchange(true, std::memory_order_acq_rel); + } + if (already_destroyed) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return; } auto reason = (decoding_state_.isProcessingInGo() || encoding_state_.isProcessingInGo()) @@ -473,20 +480,16 @@ Filter::sendLocalReply(ProcessorState& state, Http::Code response_code, std::str std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, std::string details) { bool on_worker_thread = state.isThreadSafe(); - { - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { - ENVOY_LOG(debug, "golang filter has been destroyed"); - return CAPIStatus::CAPIFilterIsDestroy; - } - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - ENVOY_LOG(debug, "sendLocalReply, response code: {}", int(response_code)); + if (hasDestroyed()) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return CAPIStatus::CAPIFilterIsDestroy; + } + if (!state.isProcessingInGo()) { + ENVOY_LOG(debug, "golang filter is not processing Go"); + return CAPIStatus::CAPINotInGo; } + ENVOY_LOG(debug, "sendLocalReply, response code: {}", int(response_code)); - // See continueStatus() for the reentrancy/deadlock rationale. if (on_worker_thread) { sendLocalReplyInternal(state, response_code, body_text, modify_headers, grpc_status, details); return CAPIStatus::CAPIOK; @@ -517,30 +520,22 @@ CAPIStatus Filter::sendPanicReply(ProcessorState& state, absl::string_view detai } CAPIStatus Filter::continueStatus(ProcessorState& state, GolangStatus status) { - // isThreadSafe() is a pure thread-id comparison, safe to read without the mutex. bool on_worker_thread = state.isThreadSafe(); - { - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { - ENVOY_LOG(debug, "golang filter has been destroyed"); - return CAPIStatus::CAPIFilterIsDestroy; - } - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - ENVOY_LOG(debug, "golang filter continue from Go, status: {}, state: {}", int(status), - state.stateStr()); + if (hasDestroyed()) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return CAPIStatus::CAPIFilterIsDestroy; } - // Lock released before calling into the filter chain to avoid deadlocking: - // continueStatusInternal() calls hasDestroyed() which re-acquires the non-reentrant mutex_. - // - // NB: inline execution means continueStatusInternal re-enters the filter chain - // (continueProcessing / continueDoData / sendLocalReply) while the cgo call is still on the - // stack. This is the same reentrancy model as the existing addData inline path. The unit tests - // verify the branch selection but do not reproduce the full cgo-on-stack scenario; the existing - // Go integration tests (golang_filter_integration_test) exercise that path end-to-end. + if (!state.isProcessingInGo()) { + ENVOY_LOG(debug, "golang filter is not processing Go"); + return CAPIStatus::CAPINotInGo; + } + ENVOY_LOG(debug, "golang filter continue from Go, status: {}, state: {}", int(status), + state.stateStr()); + // When on the worker thread, continueStatusInternal re-enters the filter chain + // (continueProcessing / continueDoData / sendLocalReply) while the cgo call is still on the + // stack. This synchronous reentrancy is intentional and is the same model used by the addData + // inline path below; callers must not hold any state-mutating locks across this call. if (on_worker_thread) { continueStatusInternal(state, status); return CAPIStatus::CAPIOK; @@ -566,8 +561,7 @@ CAPIStatus Filter::addData(ProcessorState& state, absl::string_view data, bool i return CAPIStatus::CAPIInvalidPhase; } - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -576,9 +570,12 @@ CAPIStatus Filter::addData(ProcessorState& state, absl::string_view data, bool i return CAPIStatus::CAPINotInGo; } + // On the worker thread we add data synchronously: state.addData() re-enters the filter chain + // while the cgo call is still on the stack (same reentrancy model as continueStatus above). + // Off the worker thread, we cannot touch the filter-chain buffer directly, so post a copy of + // the data to the dispatcher; the lambda re-checks hasDestroyed() under acquire ordering since + // onDestroy may have run between post and dispatch. if (state.isThreadSafe()) { - // NB: unlike continueStatus/sendLocalReply, we can hold mutex_ here because - // state.addData() does not call hasDestroyed() or re-acquire the mutex. Buffer::OwnedImpl buffer; buffer.add(data); state.addData(buffer, is_streaming); @@ -600,9 +597,13 @@ CAPIStatus Filter::addData(ProcessorState& state, absl::string_view data, bool i } CAPIStatus Filter::injectData(ProcessorState& state, absl::string_view data) { - // lock until this function return since it may running in a Go thread. - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + // Unlike addData, injectData is the *off-thread-only* path: it injects directly into the filter + // chain via state.injectDataToFilterChain(), which must run on the worker thread but must not + // run synchronously from within a cgo callback on that thread (it would re-enter the filter + // chain while the data path is still mid-flight). Callers from inside a synchronous Go callback + // are therefore rejected with CAPIInvalidScene below; the only legal caller is an off-thread + // goroutine, whose work we forward to the worker via a dispatcher post. + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -638,8 +639,12 @@ CAPIStatus Filter::injectData(ProcessorState& state, absl::string_view data) { CAPIStatus Filter::getHeader(ProcessorState& state, absl::string_view key, uint64_t* value_data, int* value_len) { + // mutex_ is held across the inline read of the Envoy-owned header map below: it serialises + // against onDestroy() so the worker thread cannot tear down the parent stream (and free the + // header map) while this off-thread Go caller is mid-dereference. See has_destroyed_ comment + // in the header for the full lifetime invariant. Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -694,8 +699,12 @@ void copyHeaderMapToGo(Http::HeaderMap& m, GoString* go_strs, char* go_buf) { } CAPIStatus Filter::copyHeaders(ProcessorState& state, GoString* go_strs, char* go_buf) { + // mutex_ is held across the inline iteration of the Envoy-owned header map below: it + // serialises against onDestroy() so the worker thread cannot tear down the parent stream + // (and free the header map) while this off-thread Go caller is mid-iteration. See + // has_destroyed_ comment in the header for the full lifetime invariant. Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -716,8 +725,7 @@ CAPIStatus Filter::copyHeaders(ProcessorState& state, GoString* go_strs, char* g // callback to run in the envoy worker thread. CAPIStatus Filter::setHeader(ProcessorState& state, absl::string_view key, absl::string_view value, headerAction act) { - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -780,8 +788,7 @@ CAPIStatus Filter::setHeader(ProcessorState& state, absl::string_view key, absl: // It won't take affect immediately while it's invoked from a Go thread, instead, it will post a // callback to run in the envoy worker thread. CAPIStatus Filter::removeHeader(ProcessorState& state, absl::string_view key) { - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -817,9 +824,7 @@ CAPIStatus Filter::removeHeader(ProcessorState& state, absl::string_view key) { } CAPIStatus Filter::copyBuffer(ProcessorState& state, Buffer::Instance* buffer, char* data) { - // lock until this function return since it may running in a Go thread. - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -841,9 +846,7 @@ CAPIStatus Filter::copyBuffer(ProcessorState& state, Buffer::Instance* buffer, c } CAPIStatus Filter::drainBuffer(ProcessorState& state, Buffer::Instance* buffer, uint64_t length) { - // lock until this function return since it may running in a Go thread. - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -862,9 +865,7 @@ CAPIStatus Filter::drainBuffer(ProcessorState& state, Buffer::Instance* buffer, CAPIStatus Filter::setBufferHelper(ProcessorState& state, Buffer::Instance* buffer, absl::string_view& value, bufferAction action) { - // lock until this function return since it may running in a Go thread. - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -888,8 +889,12 @@ CAPIStatus Filter::setBufferHelper(ProcessorState& state, Buffer::Instance* buff } CAPIStatus Filter::copyTrailers(ProcessorState& state, GoString* go_strs, char* go_buf) { + // mutex_ is held across the inline iteration of the Envoy-owned trailer map below: it + // serialises against onDestroy() so the worker thread cannot tear down the parent stream + // (and free the trailer map) while this off-thread Go caller is mid-iteration. See + // has_destroyed_ comment in the header for the full lifetime invariant. Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -908,8 +913,7 @@ CAPIStatus Filter::copyTrailers(ProcessorState& state, GoString* go_strs, char* CAPIStatus Filter::setTrailer(ProcessorState& state, absl::string_view key, absl::string_view value, headerAction act) { - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -967,8 +971,7 @@ CAPIStatus Filter::setTrailer(ProcessorState& state, absl::string_view key, absl } CAPIStatus Filter::removeTrailer(ProcessorState& state, absl::string_view key) { - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1004,8 +1007,7 @@ CAPIStatus Filter::removeTrailer(ProcessorState& state, absl::string_view key) { CAPIStatus Filter::setUpstreamOverrideHost(ProcessorState& state, absl::string_view host, bool strict) { - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1054,8 +1056,7 @@ CAPIStatus Filter::setUpstreamOverrideHost(ProcessorState& state, absl::string_v } CAPIStatus Filter::clearRouteCache(bool refresh) { - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1086,9 +1087,12 @@ void Filter::clearRouteCacheInternal(bool refresh) { } CAPIStatus Filter::getIntegerValue(int id, uint64_t* value) { - // lock until this function return since it may running in a Go thread. + // mutex_ is held across the inline reads of streamInfo() and its SSL/upstream sub-objects + // below: it serialises against onDestroy() so the worker thread cannot tear down the parent + // stream (and free StreamInfo) while this off-thread Go caller is mid-dereference. See + // has_destroyed_ comment in the header for the full lifetime invariant. Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1179,9 +1183,14 @@ CAPIStatus Filter::getIntegerValue(int id, uint64_t* value) { } CAPIStatus Filter::getStringValue(int id, uint64_t* value_data, int* value_len) { - // lock until this function return since it may running in a Go thread. + // mutex_ has two roles here: + // 1) it serialises writes to req_->strValue across off-thread Go callers, and + // 2) it is held across the inline streamInfo() / SSL / upstream-info dereferences below, + // stalling onDestroy() so the worker thread cannot tear down the parent stream (and + // free StreamInfo) while this off-thread Go caller is mid-read. + // See has_destroyed_ comment in the header for the full lifetime invariant. Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1369,8 +1378,9 @@ CAPIStatus Filter::getStringValue(int id, uint64_t* value_data, int* value_len) CAPIStatus Filter::getDynamicMetadata(const std::string& filter_name, uint64_t* buf_data, int* buf_len) { + // mutex_ serializes writes to req_->strValue across off-thread Go callers. Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1409,9 +1419,7 @@ void Filter::populateSliceWithMetadata(const std::string& filter_name, uint64_t* CAPIStatus Filter::setDynamicMetadata(std::string filter_name, std::string key, absl::string_view buf) { - // lock until this function return since it may running in a Go thread. - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1450,9 +1458,7 @@ void Filter::setDynamicMetadataInternal(std::string filter_name, std::string key CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view value, int state_type, int life_span, int stream_sharing) { - // lock until this function return since it may running in a Go thread. - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1484,9 +1490,9 @@ CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view CAPIStatus Filter::getStringFilterState(absl::string_view key, uint64_t* value_data, int* value_len) { - // lock until this function return since it may running in a Go thread. + // mutex_ serializes writes to req_->strValue across off-thread Go callers. Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1522,9 +1528,9 @@ CAPIStatus Filter::getStringFilterState(absl::string_view key, uint64_t* value_d CAPIStatus Filter::getStringProperty(absl::string_view path, uint64_t* value_data, int* value_len, int* rc) { - // lock until this function return since it may running in a Go thread. + // mutex_ serializes writes to req_->strValue across off-thread Go callers. Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1721,9 +1727,9 @@ void Filter::deferredDeleteRequest(HttpRequestInternal* req) { } CAPIStatus Filter::getSecret(const absl::string_view name, uint64_t* value_data, int* value_len) { - // lock until this function return since it may running in a Go thread. + // mutex_ serializes writes to req_->strValue across off-thread Go callers. Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } @@ -1762,8 +1768,12 @@ CAPIStatus Filter::getSecret(const absl::string_view name, uint64_t* value_data, } CAPIStatus Filter::setDrainConnectionUponCompletion() { + // mutex_ is held across the inline streamInfo() dereference below: it serialises against + // onDestroy() so the worker thread cannot tear down the parent stream (and free StreamInfo) + // while this off-thread Go caller is mid-write. See has_destroyed_ comment in the header for + // the full lifetime invariant. Thread::LockGuard lock(mutex_); - if (has_destroyed_) { + if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } diff --git a/contrib/golang/filters/http/source/golang_filter.h b/contrib/golang/filters/http/source/golang_filter.h index 3160d712bcc5a..950ac1e8ecfaa 100644 --- a/contrib/golang/filters/http/source/golang_filter.h +++ b/contrib/golang/filters/http/source/golang_filter.h @@ -350,10 +350,10 @@ class Filter : public Http::StreamFilter, private: friend class TestFilter; - bool hasDestroyed() { - Thread::LockGuard lock(mutex_); - return has_destroyed_; - }; + // Lock-free destroy check. The flag is set once, with release ordering, by onDestroy() on the + // worker thread; concurrent off-thread cgo callers do an acquire load to detect destruction + // without taking mutex_. See header note on has_destroyed_ below. + bool hasDestroyed() { return has_destroyed_.load(std::memory_order_acquire); }; const StreamInfo::StreamInfo& streamInfo() const { return decoding_state_.streamInfo(); } StreamInfo::StreamInfo& streamInfo() { return decoding_state_.streamInfo(); } bool isThreadSafe() { return decoding_state_.isThreadSafe(); }; @@ -410,10 +410,42 @@ class Filter : public Http::StreamFilter, Event::Dispatcher* dispatcher_; - // lock for has_destroyed_/etc, to avoid race between envoy c thread and go thread (when calling - // back from go). + // mutex_ has two distinct roles: + // + // 1. Serialises off-thread Go callers that write to req_->strValue (getStringValue, + // getDynamicMetadata, getStringFilterState, getStringProperty, getSecret) so that + // concurrent goroutines do not corrupt the per-request scratch buffer. + // + // 2. Stalls onDestroy() against off-thread CAPI methods that inline-dereference + // Envoy-owned objects whose lifetime is tied to the parent stream rather than to the + // Filter (getHeader, copyHeaders, copyTrailers, getIntegerValue). The Filter itself is + // kept alive across any cgo call by the shared_ptr taken at the cgo wrapper layer + // (see cgo.cc), but the stream's HeaderMap / TrailerMap / StreamInfo are not. The + // fence is two-sided: those off-thread CAPI methods hold mutex_ across their + // dereference, and onDestroy() re-acquires mutex_ once before letting the worker + // thread return. The worker therefore blocks on mutex_ until every in-flight + // off-thread reader has unwound, which prevents it from progressing into + // deferredDelete(stream) (and the eventual stream / header-map free) while a Go + // goroutine is still mid-deref. The atomic flag alone is not sufficient for this: + // once onDestroy has set it the off-thread caller can already be past its own check + // and committed to the deref, so the lock acquisition in onDestroy is what actually + // serialises the two sides. + // + // The bare destroy-flag check (`if (hasDestroyed()) return CAPIFilterIsDestroy;`) does + // NOT require this mutex; see has_destroyed_ below. CAPI methods whose only Envoy-side + // work is either Filter-owned (e.g. doDataList buffers) or runs on the worker thread + // (via dispatcher.post or under isThreadSafe()) do not need to take mutex_ at all. Thread::MutexBasicLockable mutex_{}; - bool has_destroyed_ ABSL_GUARDED_BY(mutex_){false}; + // Set exactly once by onDestroy() while holding mutex_ (acq_rel exchange), read lock-free + // by hasDestroyed() (acquire load) from any thread. Concurrent cgo callers from Go bail + // out with CAPIFilterIsDestroy as soon as they observe the store. The Filter itself is + // kept alive across any cgo call by the shared_ptr taken at the cgo wrapper layer (see + // cgo.cc), so observing a false-then-true transition during a call is benign for + // Filter-owned state. For Envoy-stream-owned state, the lock-free observation is not + // sufficient on its own: see the role-2 explanation on mutex_ above for how the + // worker-stall fence (mutex_ acquired in onDestroy() and across the off-thread deref) + // closes the lifetime gap. + std::atomic has_destroyed_{false}; }; struct httpConfigInternal : httpConfig { From dfa8a88d5ed61961bb7161450c2284a94c7a4b25 Mon Sep 17 00:00:00 2001 From: code Date: Wed, 29 Apr 2026 22:52:34 +0800 Subject: [PATCH 160/750] minor improvement of DFP cluster code (#44735) --- source/extensions/clusters/dynamic_forward_proxy/cluster.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/extensions/clusters/dynamic_forward_proxy/cluster.h b/source/extensions/clusters/dynamic_forward_proxy/cluster.h index 2c09d9f2ed3b3..d5fd9df880932 100644 --- a/source/extensions/clusters/dynamic_forward_proxy/cluster.h +++ b/source/extensions/clusters/dynamic_forward_proxy/cluster.h @@ -154,13 +154,13 @@ class Cluster : public Upstream::BaseDynamicClusterImpl, std::string hostname) : context_(context), cluster_(cluster), hostname_(hostname) {}; - virtual void cancel() { + void cancel() override { // Cancels the DNS callback. handle_.reset(); } - virtual void - onLoadDnsCacheComplete(const Common::DynamicForwardProxy::DnsHostInfoSharedPtr& info) { + void + onLoadDnsCacheComplete(const Common::DynamicForwardProxy::DnsHostInfoSharedPtr& info) override { Upstream::HostConstSharedPtr host = cluster_.findHostByName(hostname_); std::string details = info->details(); context_->onAsyncHostSelection(std::move(host), std::move(details)); From 2d837266090fd44b4e788c40c76f51f8d09c84b6 Mon Sep 17 00:00:00 2001 From: code Date: Wed, 29 Apr 2026 23:58:53 +0800 Subject: [PATCH 161/750] dym sdk: add network filter sdk for golang/cpp (#44678) --- source/extensions/dynamic_modules/BUILD | 3 + .../extensions/dynamic_modules/sdk/cpp/BUILD | 18 +- .../dynamic_modules/sdk/cpp/sdk_internal.cc | 77 +- .../sdk/cpp/sdk_internal_common.h | 64 + .../sdk/cpp/sdk_internal_network.cc | 815 +++++++++++ .../dynamic_modules/sdk/cpp/sdk_network.cc | 49 + .../dynamic_modules/sdk/cpp/sdk_network.h | 392 ++++++ .../dynamic_modules/sdk/go/abi/network.go | 1193 +++++++++++++++++ .../extensions/dynamic_modules/sdk/go/sdk.go | 28 + .../sdk/go/shared/network_api.go | 112 ++ .../sdk/go/shared/network_base.go | 314 +++++ test/extensions/dynamic_modules/network/BUILD | 4 + .../network/integration_test.cc | 74 +- .../dynamic_modules/test_data/cpp/BUILD | 2 + .../test_data/cpp/network_integration_test.cc | 195 +++ .../dynamic_modules/test_data/go/BUILD | 2 + .../network_integration_test.go | 172 +++ tools/code_format/config.yaml | 1 + 18 files changed, 3415 insertions(+), 100 deletions(-) create mode 100644 source/extensions/dynamic_modules/sdk/cpp/sdk_internal_common.h create mode 100644 source/extensions/dynamic_modules/sdk/cpp/sdk_internal_network.cc create mode 100644 source/extensions/dynamic_modules/sdk/cpp/sdk_network.cc create mode 100644 source/extensions/dynamic_modules/sdk/cpp/sdk_network.h create mode 100644 source/extensions/dynamic_modules/sdk/go/abi/network.go create mode 100644 source/extensions/dynamic_modules/sdk/go/shared/network_api.go create mode 100644 source/extensions/dynamic_modules/sdk/go/shared/network_base.go create mode 100644 test/extensions/dynamic_modules/test_data/cpp/network_integration_test.cc create mode 100644 test/extensions/dynamic_modules/test_data/go/network_integration_test/network_integration_test.go diff --git a/source/extensions/dynamic_modules/BUILD b/source/extensions/dynamic_modules/BUILD index 663381da6be47..7c5922745b797 100644 --- a/source/extensions/dynamic_modules/BUILD +++ b/source/extensions/dynamic_modules/BUILD @@ -54,6 +54,8 @@ go_library( srcs = [ "sdk/go/shared/api.go", "sdk/go/shared/base.go", + "sdk/go/shared/network_api.go", + "sdk/go/shared/network_base.go", ], cgo = True, importpath = "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/shared", @@ -75,6 +77,7 @@ go_library( name = "go_sdk_abi", srcs = [ "sdk/go/abi/internal.go", + "sdk/go/abi/network.go", "//source/extensions/dynamic_modules/abi:abi.h", ], cgo = True, diff --git a/source/extensions/dynamic_modules/sdk/cpp/BUILD b/source/extensions/dynamic_modules/sdk/cpp/BUILD index a40d815939406..4f4af54205005 100644 --- a/source/extensions/dynamic_modules/sdk/cpp/BUILD +++ b/source/extensions/dynamic_modules/sdk/cpp/BUILD @@ -5,8 +5,14 @@ licenses(["notice"]) # Apache 2 # All necessary abstractions for creating plugins. cc_library( name = "sdk", - srcs = ["sdk.cc"], - hdrs = ["sdk.h"], + srcs = [ + "sdk.cc", + "sdk_network.cc", + ], + hdrs = [ + "sdk.h", + "sdk_network.h", + ], visibility = ["//visibility:public"], alwayslink = True, ) @@ -19,7 +25,13 @@ cc_library( # the plugins lib. cc_library( name = "sdk_abi", - srcs = ["sdk_internal.cc"], + srcs = [ + "sdk_internal.cc", + "sdk_internal_network.cc", + ], + hdrs = [ + "sdk_internal_common.h", + ], visibility = ["//visibility:public"], deps = [ ":sdk", diff --git a/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc b/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc index 2ddbb993b8f02..70c9015876612 100644 --- a/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc +++ b/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc @@ -10,6 +10,7 @@ #include "source/extensions/dynamic_modules/abi/abi.h" #include "sdk.h" +#include "sdk_internal_common.h" namespace Envoy { namespace DynamicModules { @@ -146,75 +147,13 @@ using ResponseHeaders = HeaderMapImpl; // Scheduler implementation -template class SchedulerImplBase : public Scheduler { -public: - SchedulerImplBase(void* host_ptr) : scheduler_ptr_(newScheduler(host_ptr)) {} - - void schedule(std::function func) override { - uint64_t task_id = 0; - - // Lock to protect access to tasks_ and next_task_id_ manually - { - std::lock_guard lock(mutex_); - task_id = next_task_id_++; - tasks_[task_id] = std::move(func); - } - - commitScheduler(scheduler_ptr_, task_id); - } - - void onScheduled(uint64_t task_id) { - std::function func; - - { - // Lock to protect access to tasks_ manually - std::lock_guard lock(mutex_); - auto it = tasks_.find(task_id); - if (it != tasks_.end()) { - func = std::move(it->second); - tasks_.erase(it); - } - } - - if (func) { - func(); - } - } - - ~SchedulerImplBase() override { deleteScheduler(scheduler_ptr_); } - -private: - static void* newScheduler(void* host_ptr) { - if constexpr (IsConfigScheduler) { - return envoy_dynamic_module_callback_http_filter_config_scheduler_new(host_ptr); - } else { - return envoy_dynamic_module_callback_http_filter_scheduler_new(host_ptr); - } - } - static void deleteScheduler(void* scheduler_ptr) { - if constexpr (IsConfigScheduler) { - envoy_dynamic_module_callback_http_filter_config_scheduler_delete(scheduler_ptr); - } else { - envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler_ptr); - } - } - static void commitScheduler(void* scheduler_ptr, uint64_t task_id) { - if constexpr (IsConfigScheduler) { - envoy_dynamic_module_callback_http_filter_config_scheduler_commit(scheduler_ptr, task_id); - } else { - envoy_dynamic_module_callback_http_filter_scheduler_commit(scheduler_ptr, task_id); - } - } - - void* scheduler_ptr_{}; - - std::mutex mutex_; - uint64_t next_task_id_{1}; // 0 is reserved. - std::map> tasks_; -}; - -using SchedulerImpl = SchedulerImplBase; -using ConfigSchedulerImpl = SchedulerImplBase; +using SchedulerImpl = SchedulerImplBase; +using ConfigSchedulerImpl = + SchedulerImplBase; std::optional bufferViewToOptionalStringView(const BufferView& value, bool found) { diff --git a/source/extensions/dynamic_modules/sdk/cpp/sdk_internal_common.h b/source/extensions/dynamic_modules/sdk/cpp/sdk_internal_common.h new file mode 100644 index 0000000000000..0da25b02a5e36 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/cpp/sdk_internal_common.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include + +#include "sdk.h" + +namespace Envoy { +namespace DynamicModules { + +/** + * Generic Scheduler implementation backed by a host-managed event dispatcher. + * + * All host-side scheduler pointer types are typedef void*, so the three ABI functions are + * passed as non-type template parameters and called with plain void* arguments. + * + * @tparam NewFn ABI function that allocates the native scheduler: void*(void* host_ptr) + * @tparam CommitFn ABI function that enqueues a task_id on the worker thread: + * void(void* scheduler_ptr, uint64_t task_id) + * @tparam DeleteFn ABI function that destroys the native scheduler: void(void* scheduler_ptr) + */ +template +class SchedulerImplBase : public Scheduler { +public: + explicit SchedulerImplBase(void* host_ptr) : scheduler_ptr_(NewFn(host_ptr)) {} + + void schedule(std::function func) override { + uint64_t task_id = 0; + { + std::lock_guard lock(mutex_); + task_id = next_task_id_++; + tasks_[task_id] = std::move(func); + } + CommitFn(scheduler_ptr_, task_id); + } + + void onScheduled(uint64_t task_id) { + std::function func; + { + std::lock_guard lock(mutex_); + auto it = tasks_.find(task_id); + if (it != tasks_.end()) { + func = std::move(it->second); + tasks_.erase(it); + } + } + if (func) { + func(); + } + } + + ~SchedulerImplBase() override { DeleteFn(scheduler_ptr_); } + +private: + void* scheduler_ptr_{}; + std::mutex mutex_; + uint64_t next_task_id_{1}; // 0 is reserved. + std::map> tasks_; +}; + +} // namespace DynamicModules +} // namespace Envoy diff --git a/source/extensions/dynamic_modules/sdk/cpp/sdk_internal_network.cc b/source/extensions/dynamic_modules/sdk/cpp/sdk_internal_network.cc new file mode 100644 index 0000000000000..75266b021edad --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/cpp/sdk_internal_network.cc @@ -0,0 +1,815 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "source/extensions/dynamic_modules/abi/abi.h" + +#include "sdk.h" +#include "sdk_internal_common.h" +#include "sdk_network.h" + +namespace Envoy { +namespace DynamicModules { +namespace { + +template T* unwrapPointer(const void* ptr) { + return const_cast(reinterpret_cast(ptr)); +} + +template void* wrapPointer(const T* ptr) { + return reinterpret_cast(const_cast(ptr)); +} + +std::optional bufferViewToOptionalStringView(const BufferView& value, + bool found) { + if (!found) { + return {}; + } + return std::string_view(value.data() == nullptr ? "" : value.data(), value.size()); +} + +using NetworkSchedulerImpl = + SchedulerImplBase; +using NetworkConfigSchedulerImpl = + SchedulerImplBase; + +template class NetworkBufferImpl : public NetworkBuffer { +public: + explicit NetworkBufferImpl(envoy_dynamic_module_type_network_filter_envoy_ptr host_plugin_ptr) + : host_plugin_ptr_(host_plugin_ptr) {} + + std::vector getChunks() const override { + const size_t chunks_size = getChunksSize(); + if (chunks_size == 0) { + return {}; + } + + std::vector result_chunks(chunks_size); + const bool ok = getChunksImpl( + reinterpret_cast(result_chunks.data())); + if (!ok) { + return {}; + } + return result_chunks; + } + + size_t getSize() const override { + if constexpr (IsReadBuffer) { + return envoy_dynamic_module_callback_network_filter_get_read_buffer_size(host_plugin_ptr_); + } else { + return envoy_dynamic_module_callback_network_filter_get_write_buffer_size(host_plugin_ptr_); + } + } + + bool drain(size_t size) override { + if constexpr (IsReadBuffer) { + return envoy_dynamic_module_callback_network_filter_drain_read_buffer(host_plugin_ptr_, size); + } else { + return envoy_dynamic_module_callback_network_filter_drain_write_buffer(host_plugin_ptr_, + size); + } + } + + bool prepend(std::string_view data) override { + const envoy_dynamic_module_type_module_buffer buffer{data.data(), data.size()}; + if constexpr (IsReadBuffer) { + return envoy_dynamic_module_callback_network_filter_prepend_read_buffer(host_plugin_ptr_, + buffer); + } else { + return envoy_dynamic_module_callback_network_filter_prepend_write_buffer(host_plugin_ptr_, + buffer); + } + } + + bool append(std::string_view data) override { + const envoy_dynamic_module_type_module_buffer buffer{data.data(), data.size()}; + if constexpr (IsReadBuffer) { + return envoy_dynamic_module_callback_network_filter_append_read_buffer(host_plugin_ptr_, + buffer); + } else { + return envoy_dynamic_module_callback_network_filter_append_write_buffer(host_plugin_ptr_, + buffer); + } + } + +private: + size_t getChunksSize() const { + if constexpr (IsReadBuffer) { + return envoy_dynamic_module_callback_network_filter_get_read_buffer_chunks_size( + host_plugin_ptr_); + } else { + return envoy_dynamic_module_callback_network_filter_get_write_buffer_chunks_size( + host_plugin_ptr_); + } + } + + bool getChunksImpl(envoy_dynamic_module_type_envoy_buffer* result_chunks) const { + if constexpr (IsReadBuffer) { + return envoy_dynamic_module_callback_network_filter_get_read_buffer_chunks(host_plugin_ptr_, + result_chunks); + } else { + return envoy_dynamic_module_callback_network_filter_get_write_buffer_chunks(host_plugin_ptr_, + result_chunks); + } + } + + envoy_dynamic_module_type_network_filter_envoy_ptr host_plugin_ptr_; +}; + +using ReadBufferImpl = NetworkBufferImpl; +using WriteBufferImpl = NetworkBufferImpl; + +class NetworkFilterHandleImpl : public NetworkFilterHandle { +public: + explicit NetworkFilterHandleImpl( + envoy_dynamic_module_type_network_filter_envoy_ptr host_plugin_ptr) + : host_plugin_ptr_(host_plugin_ptr), read_buffer_(host_plugin_ptr), + write_buffer_(host_plugin_ptr) {} + + NetworkBuffer& readBuffer() override { return read_buffer_; } + NetworkBuffer& writeBuffer() override { return write_buffer_; } + + void write(std::string_view data, bool end_stream) override { + envoy_dynamic_module_callback_network_filter_write( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{data.data(), data.size()}, + end_stream); + } + + void injectReadData(std::string_view data, bool end_stream) override { + envoy_dynamic_module_callback_network_filter_inject_read_data( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{data.data(), data.size()}, + end_stream); + } + + void injectWriteData(std::string_view data, bool end_stream) override { + envoy_dynamic_module_callback_network_filter_inject_write_data( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{data.data(), data.size()}, + end_stream); + } + + void continueReading() override { + envoy_dynamic_module_callback_network_filter_continue_reading(host_plugin_ptr_); + } + + void close(NetworkConnectionCloseType close_type) override { + envoy_dynamic_module_callback_network_filter_close( + host_plugin_ptr_, + static_cast(close_type)); + } + + uint64_t getConnectionId() override { + return envoy_dynamic_module_callback_network_filter_get_connection_id(host_plugin_ptr_); + } + + std::optional> getRemoteAddress() override { + return getAddress(envoy_dynamic_module_callback_network_filter_get_remote_address); + } + + std::optional> getLocalAddress() override { + return getAddress(envoy_dynamic_module_callback_network_filter_get_local_address); + } + + bool isSsl() override { + return envoy_dynamic_module_callback_network_filter_is_ssl(host_plugin_ptr_); + } + + void disableClose(bool disabled) override { + envoy_dynamic_module_callback_network_filter_disable_close(host_plugin_ptr_, disabled); + } + + void closeWithDetails(NetworkConnectionCloseType close_type, std::string_view details) override { + envoy_dynamic_module_callback_network_filter_close_with_details( + host_plugin_ptr_, + static_cast(close_type), + envoy_dynamic_module_type_module_buffer{details.data(), details.size()}); + } + + std::optional getRequestedServerName() override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_network_filter_get_requested_server_name( + host_plugin_ptr_, reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + std::optional> getDirectRemoteAddress() override { + return getAddress(envoy_dynamic_module_callback_network_filter_get_direct_remote_address); + } + + std::vector getSslUriSans() override { + return getSans(envoy_dynamic_module_callback_network_filter_get_ssl_uri_sans_size, + envoy_dynamic_module_callback_network_filter_get_ssl_uri_sans); + } + + std::vector getSslDnsSans() override { + return getSans(envoy_dynamic_module_callback_network_filter_get_ssl_dns_sans_size, + envoy_dynamic_module_callback_network_filter_get_ssl_dns_sans); + } + + std::optional getSslSubject() override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_network_filter_get_ssl_subject( + host_plugin_ptr_, reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + bool setFilterState(std::string_view key, std::string_view value) override { + return envoy_dynamic_module_callback_network_set_filter_state_bytes( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); + } + + std::optional getFilterState(std::string_view key) override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_network_get_filter_state_bytes( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + bool setFilterStateTyped(std::string_view key, std::string_view value) override { + return envoy_dynamic_module_callback_network_set_filter_state_typed( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); + } + + std::optional getFilterStateTyped(std::string_view key) override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_network_get_filter_state_typed( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + std::optional getMetadataString(std::string_view ns, + std::string_view key) override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_network_get_dynamic_metadata_string( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{ns.data(), ns.size()}, + envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + std::optional getMetadataNumber(std::string_view ns, std::string_view key) override { + double value = 0.0; + const bool found = envoy_dynamic_module_callback_network_get_dynamic_metadata_number( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{ns.data(), ns.size()}, + envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, &value); + if (!found) { + return {}; + } + return value; + } + + std::optional getMetadataBool(std::string_view ns, std::string_view key) override { + bool value = false; + const bool found = envoy_dynamic_module_callback_network_get_dynamic_metadata_bool( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{ns.data(), ns.size()}, + envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, &value); + if (!found) { + return {}; + } + return value; + } + + void setMetadata(std::string_view ns, std::string_view key, std::string_view value) override { + envoy_dynamic_module_callback_network_set_dynamic_metadata_string( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{ns.data(), ns.size()}, + envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, + envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); + } + + void setMetadata(std::string_view ns, std::string_view key, double value) override { + envoy_dynamic_module_callback_network_set_dynamic_metadata_number( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{ns.data(), ns.size()}, + envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, value); + } + + void setMetadata(std::string_view ns, std::string_view key, bool value) override { + envoy_dynamic_module_callback_network_set_dynamic_metadata_bool( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{ns.data(), ns.size()}, + envoy_dynamic_module_type_module_buffer{key.data(), key.size()}, value); + } + + void setSocketOptionInt(int64_t level, int64_t name, SocketOptionState state, + int64_t value) override { + envoy_dynamic_module_callback_network_set_socket_option_int( + host_plugin_ptr_, level, name, + static_cast(state), value); + } + + void setSocketOptionBytes(int64_t level, int64_t name, SocketOptionState state, + std::string_view value) override { + envoy_dynamic_module_callback_network_set_socket_option_bytes( + host_plugin_ptr_, level, name, + static_cast(state), + envoy_dynamic_module_type_module_buffer{value.data(), value.size()}); + } + + std::optional getSocketOptionInt(int64_t level, int64_t name, + SocketOptionState state) override { + int64_t value = 0; + const bool found = envoy_dynamic_module_callback_network_get_socket_option_int( + host_plugin_ptr_, level, name, + static_cast(state), &value); + if (!found) { + return {}; + } + return value; + } + + std::optional getSocketOptionBytes(int64_t level, int64_t name, + SocketOptionState state) override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_network_get_socket_option_bytes( + host_plugin_ptr_, level, name, + static_cast(state), + reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + std::vector getSocketOptions() override { + const size_t option_count = + envoy_dynamic_module_callback_network_get_socket_options_size(host_plugin_ptr_); + if (option_count == 0) { + return {}; + } + std::vector raw_options(option_count); + envoy_dynamic_module_callback_network_get_socket_options(host_plugin_ptr_, raw_options.data()); + + std::vector options; + options.reserve(option_count); + for (const auto& raw_option : raw_options) { + options.push_back(SocketOption{ + raw_option.level, + raw_option.name, + static_cast(raw_option.state), + static_cast(raw_option.value_type), + raw_option.int_value, + BufferView{raw_option.byte_value.ptr, raw_option.byte_value.length}, + }); + } + return options; + } + + std::pair httpCallout(std::string_view cluster, + std::span headers, + std::string_view body, uint64_t timeout_ms, + HttpCalloutCallback& cb) override { + uint64_t callout_id_out = 0; + const auto result = envoy_dynamic_module_callback_network_filter_http_callout( + host_plugin_ptr_, &callout_id_out, + envoy_dynamic_module_type_module_buffer{cluster.data(), cluster.size()}, + const_cast( + reinterpret_cast(headers.data())), + headers.size(), envoy_dynamic_module_type_module_buffer{body.data(), body.size()}, + timeout_ms); + + if (result == envoy_dynamic_module_type_http_callout_init_result_Success) { + callout_callbacks_[callout_id_out] = &cb; + } + return {static_cast(result), callout_id_out}; + } + + MetricsResult recordHistogramValue(MetricID id, uint64_t value) override { + return static_cast( + envoy_dynamic_module_callback_network_filter_record_histogram_value(host_plugin_ptr_, id, + value)); + } + + MetricsResult setGaugeValue(MetricID id, uint64_t value) override { + return static_cast( + envoy_dynamic_module_callback_network_filter_set_gauge(host_plugin_ptr_, id, value)); + } + + MetricsResult incrementGaugeValue(MetricID id, uint64_t value) override { + return static_cast( + envoy_dynamic_module_callback_network_filter_increment_gauge(host_plugin_ptr_, id, value)); + } + + MetricsResult decrementGaugeValue(MetricID id, uint64_t value) override { + return static_cast( + envoy_dynamic_module_callback_network_filter_decrement_gauge(host_plugin_ptr_, id, value)); + } + + MetricsResult incrementCounterValue(MetricID id, uint64_t value) override { + return static_cast( + envoy_dynamic_module_callback_network_filter_increment_counter(host_plugin_ptr_, id, + value)); + } + + std::optional getClusterHostCounts(std::string_view cluster, + uint32_t priority) override { + size_t total = 0; + size_t healthy = 0; + size_t degraded = 0; + const bool found = envoy_dynamic_module_callback_network_filter_get_cluster_host_count( + host_plugin_ptr_, envoy_dynamic_module_type_module_buffer{cluster.data(), cluster.size()}, + priority, &total, &healthy, °raded); + if (!found) { + return {}; + } + return ClusterHostCounts{static_cast(total), static_cast(healthy), + static_cast(degraded)}; + } + + std::optional> getUpstreamHostAddress() override { + return getAddress(envoy_dynamic_module_callback_network_filter_get_upstream_host_address); + } + + std::optional getUpstreamHostHostname() override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_network_filter_get_upstream_host_hostname( + host_plugin_ptr_, reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + std::optional getUpstreamHostCluster() override { + BufferView value{nullptr, 0}; + const bool found = envoy_dynamic_module_callback_network_filter_get_upstream_host_cluster( + host_plugin_ptr_, reinterpret_cast(&value)); + return bufferViewToOptionalStringView(value, found); + } + + bool hasUpstreamHost() override { + return envoy_dynamic_module_callback_network_filter_has_upstream_host(host_plugin_ptr_); + } + + bool startUpstreamSecureTransport() override { + return envoy_dynamic_module_callback_network_filter_start_upstream_secure_transport( + host_plugin_ptr_); + } + + NetworkConnectionState getConnectionState() override { + return static_cast( + envoy_dynamic_module_callback_network_filter_get_connection_state(host_plugin_ptr_)); + } + + NetworkReadDisableStatus readDisable(bool disable) override { + return static_cast( + envoy_dynamic_module_callback_network_filter_read_disable(host_plugin_ptr_, disable)); + } + + bool readEnabled() override { + return envoy_dynamic_module_callback_network_filter_read_enabled(host_plugin_ptr_); + } + + bool isHalfCloseEnabled() override { + return envoy_dynamic_module_callback_network_filter_is_half_close_enabled(host_plugin_ptr_); + } + + void enableHalfClose(bool enabled) override { + envoy_dynamic_module_callback_network_filter_enable_half_close(host_plugin_ptr_, enabled); + } + + uint32_t getBufferLimit() override { + return envoy_dynamic_module_callback_network_filter_get_buffer_limit(host_plugin_ptr_); + } + + void setBufferLimits(uint32_t limit) override { + envoy_dynamic_module_callback_network_filter_set_buffer_limits(host_plugin_ptr_, limit); + } + + bool aboveHighWatermark() override { + return envoy_dynamic_module_callback_network_filter_above_high_watermark(host_plugin_ptr_); + } + + std::shared_ptr getScheduler() override { + if (!scheduler_) { + scheduler_ = std::make_shared(host_plugin_ptr_); + } + return scheduler_; + } + + uint32_t getWorkerIndex() override { + return envoy_dynamic_module_callback_network_filter_get_worker_index(host_plugin_ptr_); + } + + bool logEnabled(LogLevel level) override { + return envoy_dynamic_module_callback_log_enabled( + static_cast(level)); + } + + void log(LogLevel level, std::string_view message) override { + envoy_dynamic_module_callback_log( + static_cast(level), + envoy_dynamic_module_type_module_buffer{message.data(), message.size()}); + } + + std::unique_ptr plugin_; + std::map callout_callbacks_; + std::shared_ptr scheduler_; + +private: + using AddressGetter = bool (*)(envoy_dynamic_module_type_network_filter_envoy_ptr, + envoy_dynamic_module_type_envoy_buffer*, uint32_t*); + + std::optional> getAddress(AddressGetter getter) { + BufferView value{nullptr, 0}; + uint32_t port = 0; + const bool found = getter( + host_plugin_ptr_, reinterpret_cast(&value), &port); + if (!found) { + return {}; + } + return std::pair{ + std::string_view(value.data() == nullptr ? "" : value.data(), value.size()), port}; + } + + using SansSizeGetter = size_t (*)(envoy_dynamic_module_type_network_filter_envoy_ptr); + using SansGetter = bool (*)(envoy_dynamic_module_type_network_filter_envoy_ptr, + envoy_dynamic_module_type_envoy_buffer*); + + std::vector getSans(SansSizeGetter size_getter, SansGetter getter) { + const size_t count = size_getter(host_plugin_ptr_); + if (count == 0) { + return {}; + } + std::vector buffers(count); + const bool found = getter(host_plugin_ptr_, buffers.data()); + if (!found) { + return {}; + } + std::vector result; + result.reserve(count); + for (const auto& buffer : buffers) { + result.emplace_back(buffer.ptr == nullptr ? "" : buffer.ptr, buffer.length); + } + return result; + } + + envoy_dynamic_module_type_network_filter_envoy_ptr host_plugin_ptr_; + ReadBufferImpl read_buffer_; + WriteBufferImpl write_buffer_; +}; + +class NetworkFilterConfigHandleImpl : public NetworkFilterConfigHandle { +public: + explicit NetworkFilterConfigHandleImpl( + envoy_dynamic_module_type_network_filter_config_envoy_ptr host_config_ptr) + : host_config_ptr_(host_config_ptr) {} + + std::pair defineHistogram(std::string_view name) override { + size_t metric_id = 0; + const auto result = static_cast( + envoy_dynamic_module_callback_network_filter_config_define_histogram( + host_config_ptr_, envoy_dynamic_module_type_module_buffer{name.data(), name.size()}, + &metric_id)); + return {metric_id, result}; + } + + std::pair defineGauge(std::string_view name) override { + size_t metric_id = 0; + const auto result = + static_cast(envoy_dynamic_module_callback_network_filter_config_define_gauge( + host_config_ptr_, envoy_dynamic_module_type_module_buffer{name.data(), name.size()}, + &metric_id)); + return {metric_id, result}; + } + + std::pair defineCounter(std::string_view name) override { + size_t metric_id = 0; + const auto result = static_cast( + envoy_dynamic_module_callback_network_filter_config_define_counter( + host_config_ptr_, envoy_dynamic_module_type_module_buffer{name.data(), name.size()}, + &metric_id)); + return {metric_id, result}; + } + + std::shared_ptr getScheduler() override { + if (!scheduler_) { + scheduler_ = std::make_shared(host_config_ptr_); + } + return scheduler_; + } + + bool logEnabled(LogLevel level) override { + return envoy_dynamic_module_callback_log_enabled( + static_cast(level)); + } + + void log(LogLevel level, std::string_view message) override { + envoy_dynamic_module_callback_log( + static_cast(level), + envoy_dynamic_module_type_module_buffer{message.data(), message.size()}); + } + + std::shared_ptr scheduler_; + +private: + envoy_dynamic_module_type_network_filter_config_envoy_ptr host_config_ptr_; +}; + +struct NetworkFilterFactoryWrapper { + std::unique_ptr config_handle_; + std::unique_ptr factory_; +}; + +} // namespace + +extern "C" { + +envoy_dynamic_module_type_network_filter_config_module_ptr +envoy_dynamic_module_on_network_filter_config_new( + envoy_dynamic_module_type_network_filter_config_envoy_ptr filter_config_envoy_ptr, + envoy_dynamic_module_type_envoy_buffer name, envoy_dynamic_module_type_envoy_buffer config) { + auto config_handle = std::make_unique(filter_config_envoy_ptr); + const std::string_view name_view(name.ptr, name.length); + const std::string_view config_view(config.ptr, config.length); + + auto config_factory = NetworkFilterConfigFactoryRegistry::getRegistry().find(name_view); + if (config_factory == NetworkFilterConfigFactoryRegistry::getRegistry().end()) { + DYM_LOG((*config_handle), LogLevel::Warn, + "Network plugin config factory not found for name: {}", name_view); + return nullptr; + } + + auto plugin_factory = config_factory->second->create(*config_handle, config_view); + if (!plugin_factory) { + DYM_LOG((*config_handle), LogLevel::Warn, + "Failed to create network plugin factory for name: {}", name_view); + return nullptr; + } + + auto factory = std::make_unique(); + factory->config_handle_ = std::move(config_handle); + factory->factory_ = std::move(plugin_factory); + return wrapPointer(factory.release()); +} + +void envoy_dynamic_module_on_network_filter_config_destroy( + envoy_dynamic_module_type_network_filter_config_module_ptr filter_config_ptr) { + auto* factory_wrapper = unwrapPointer(filter_config_ptr); + if (factory_wrapper == nullptr) { + return; + } + if (factory_wrapper->factory_) { + factory_wrapper->factory_->onDestroy(); + } + delete factory_wrapper; +} + +envoy_dynamic_module_type_network_filter_module_ptr envoy_dynamic_module_on_network_filter_new( + envoy_dynamic_module_type_network_filter_config_module_ptr filter_config_ptr, + envoy_dynamic_module_type_network_filter_envoy_ptr filter_envoy_ptr) { + auto* factory_wrapper = unwrapPointer(filter_config_ptr); + if (factory_wrapper == nullptr) { + return nullptr; + } + + auto plugin_handle = std::make_unique(filter_envoy_ptr); + auto plugin = factory_wrapper->factory_->create(*plugin_handle); + if (plugin == nullptr) { + DYM_LOG((*plugin_handle), LogLevel::Warn, "Failed to create network plugin instance"); + return nullptr; + } + plugin_handle->plugin_ = std::move(plugin); + return wrapPointer(plugin_handle.release()); +} + +envoy_dynamic_module_type_on_network_filter_data_status +envoy_dynamic_module_on_network_filter_new_connection( + envoy_dynamic_module_type_network_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_network_filter_module_ptr filter_module_ptr) { + static_cast(filter_envoy_ptr); + auto* plugin_handle = unwrapPointer(filter_module_ptr); + if (plugin_handle == nullptr) { + return envoy_dynamic_module_type_on_network_filter_data_status_Continue; + } + return static_cast( + plugin_handle->plugin_->onNewConnection()); +} + +envoy_dynamic_module_type_on_network_filter_data_status envoy_dynamic_module_on_network_filter_read( + envoy_dynamic_module_type_network_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_network_filter_module_ptr filter_module_ptr, size_t data_length, + bool end_stream) { + static_cast(filter_envoy_ptr); + static_cast(data_length); + auto* plugin_handle = unwrapPointer(filter_module_ptr); + if (plugin_handle == nullptr) { + return envoy_dynamic_module_type_on_network_filter_data_status_Continue; + } + return static_cast( + plugin_handle->plugin_->onRead(plugin_handle->readBuffer(), end_stream)); +} + +envoy_dynamic_module_type_on_network_filter_data_status +envoy_dynamic_module_on_network_filter_write( + envoy_dynamic_module_type_network_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_network_filter_module_ptr filter_module_ptr, size_t data_length, + bool end_stream) { + static_cast(filter_envoy_ptr); + static_cast(data_length); + auto* plugin_handle = unwrapPointer(filter_module_ptr); + if (plugin_handle == nullptr) { + return envoy_dynamic_module_type_on_network_filter_data_status_Continue; + } + return static_cast( + plugin_handle->plugin_->onWrite(plugin_handle->writeBuffer(), end_stream)); +} + +void envoy_dynamic_module_on_network_filter_event( + envoy_dynamic_module_type_network_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_network_filter_module_ptr filter_module_ptr, + envoy_dynamic_module_type_network_connection_event event) { + static_cast(filter_envoy_ptr); + auto* plugin_handle = unwrapPointer(filter_module_ptr); + if (plugin_handle == nullptr) { + return; + } + plugin_handle->plugin_->onEvent(static_cast(event)); +} + +void envoy_dynamic_module_on_network_filter_destroy( + envoy_dynamic_module_type_network_filter_module_ptr filter_module_ptr) { + auto* plugin_handle = unwrapPointer(filter_module_ptr); + if (plugin_handle == nullptr) { + return; + } + plugin_handle->plugin_->onDestroy(); + delete plugin_handle; +} + +void envoy_dynamic_module_on_network_filter_http_callout_done( + envoy_dynamic_module_type_network_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_network_filter_module_ptr filter_module_ptr, uint64_t callout_id, + envoy_dynamic_module_type_http_callout_result result, + envoy_dynamic_module_type_envoy_http_header* headers, size_t headers_size, + envoy_dynamic_module_type_envoy_buffer* body_chunks, size_t body_chunks_size) { + static_cast(filter_envoy_ptr); + auto* plugin_handle = unwrapPointer(filter_module_ptr); + if (!plugin_handle) { + return; + } + + auto it = plugin_handle->callout_callbacks_.find(callout_id); + if (it != plugin_handle->callout_callbacks_.end()) { + auto* typed_headers = reinterpret_cast(headers); + auto* typed_body_chunks = reinterpret_cast(body_chunks); + auto* callback = it->second; + plugin_handle->callout_callbacks_.erase(it); + callback->onHttpCalloutDone(static_cast(result), + {typed_headers, headers_size}, + {typed_body_chunks, body_chunks_size}); + } +} + +void envoy_dynamic_module_on_network_filter_scheduled( + envoy_dynamic_module_type_network_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_network_filter_module_ptr filter_module_ptr, uint64_t event_id) { + static_cast(filter_envoy_ptr); + auto* plugin_handle = unwrapPointer(filter_module_ptr); + if (!plugin_handle || !plugin_handle->scheduler_) { + return; + } + plugin_handle->scheduler_->onScheduled(event_id); +} + +void envoy_dynamic_module_on_network_filter_config_scheduled( + envoy_dynamic_module_type_network_filter_config_module_ptr filter_config_ptr, + uint64_t event_id) { + auto* factory_wrapper = unwrapPointer(filter_config_ptr); + if (factory_wrapper == nullptr || factory_wrapper->config_handle_ == nullptr || + factory_wrapper->config_handle_->scheduler_ == nullptr) { + return; + } + factory_wrapper->config_handle_->scheduler_->onScheduled(event_id); +} + +void envoy_dynamic_module_on_network_filter_above_write_buffer_high_watermark( + envoy_dynamic_module_type_network_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_network_filter_module_ptr filter_module_ptr) { + static_cast(filter_envoy_ptr); + auto* plugin_handle = unwrapPointer(filter_module_ptr); + if (!plugin_handle) { + return; + } + plugin_handle->plugin_->onAboveWriteBufferHighWatermark(); +} + +void envoy_dynamic_module_on_network_filter_below_write_buffer_low_watermark( + envoy_dynamic_module_type_network_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_network_filter_module_ptr filter_module_ptr) { + static_cast(filter_envoy_ptr); + auto* plugin_handle = unwrapPointer(filter_module_ptr); + if (!plugin_handle) { + return; + } + plugin_handle->plugin_->onBelowWriteBufferLowWatermark(); +} + +} // extern "C" + +} // namespace DynamicModules +} // namespace Envoy diff --git a/source/extensions/dynamic_modules/sdk/cpp/sdk_network.cc b/source/extensions/dynamic_modules/sdk/cpp/sdk_network.cc new file mode 100644 index 0000000000000..aee35bd34ac96 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/cpp/sdk_network.cc @@ -0,0 +1,49 @@ +#include "sdk_network.h" + +#include +#include + +namespace Envoy { +namespace DynamicModules { + +NetworkBuffer::~NetworkBuffer() = default; + +NetworkFilterHandle::~NetworkFilterHandle() = default; + +NetworkFilterConfigHandle::~NetworkFilterConfigHandle() = default; + +NetworkFilter::~NetworkFilter() = default; + +NetworkFilterFactory::~NetworkFilterFactory() = default; + +NetworkFilterConfigFactory::~NetworkFilterConfigFactory() = default; + +NetworkFilterConfigFactoryRegister::NetworkFilterConfigFactoryRegister( + std::string_view name, NetworkFilterConfigFactoryPtr factory) + : name_(name) { + auto r = NetworkFilterConfigFactoryRegistry::getMutableRegistry().emplace(std::string_view(name_), + std::move(factory)); + if (!r.second) { + std::string error_msg = std::format("Factory with the same name {} already registered", name_); + std::cerr << error_msg << std::endl; + assert((void("Duplicate factory registration"), r.second)); + } +} + +NetworkFilterConfigFactoryRegister::~NetworkFilterConfigFactoryRegister() { + NetworkFilterConfigFactoryRegistry::getMutableRegistry().erase(name_); +} + +std::map& +NetworkFilterConfigFactoryRegistry::getMutableRegistry() { + static std::map registry; + return registry; +} + +const std::map& +NetworkFilterConfigFactoryRegistry::getRegistry() { + return getMutableRegistry(); +}; + +} // namespace DynamicModules +} // namespace Envoy diff --git a/source/extensions/dynamic_modules/sdk/cpp/sdk_network.h b/source/extensions/dynamic_modules/sdk/cpp/sdk_network.h new file mode 100644 index 0000000000000..cf307c9062205 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/cpp/sdk_network.h @@ -0,0 +1,392 @@ +#pragma once + +#include "sdk.h" + +namespace Envoy { +namespace DynamicModules { + +enum class NetworkFilterStatus : uint32_t { + Continue = 0, + Stop = 1, +}; + +enum class NetworkConnectionCloseType : uint32_t { + FlushWrite, + NoFlush, + FlushWriteAndDelay, + Abort, + AbortReset, +}; + +enum class NetworkConnectionEvent : uint32_t { + RemoteClose, + LocalClose, + Connected, + ConnectedZeroRtt, +}; + +enum class NetworkConnectionState : uint32_t { + Open, + Closing, + Closed, +}; + +enum class NetworkReadDisableStatus : uint32_t { + NoTransition, + StillReadDisabled, + TransitionedToReadEnabled, + TransitionedToReadDisabled, +}; + +enum class SocketOptionValueType : uint32_t { Int, Bytes }; + +struct SocketOption { + int64_t level; + int64_t name; + SocketOptionState state; + SocketOptionValueType value_type; + int64_t int_value; + BufferView byte_value; +}; + +class NetworkBuffer { +public: + virtual ~NetworkBuffer(); + + /** + * Returns the current buffer contents as Envoy-owned chunks. + * Copy the data if it must outlive the current callback. + */ + virtual std::vector getChunks() const = 0; + + /** + * Returns the total size of the current buffer in bytes. + */ + virtual size_t getSize() const = 0; + + /** + * Removes bytes from the front of the buffer. + * @return true if the operation succeeds. + */ + virtual bool drain(size_t size) = 0; + + /** + * Prepends data to the front of the buffer. + * @return true if the operation succeeds. + */ + virtual bool prepend(std::string_view data) = 0; + + /** + * Appends data to the end of the buffer. + * @return true if the operation succeeds. + */ + virtual bool append(std::string_view data) = 0; +}; + +class NetworkFilterHandle { +public: + virtual ~NetworkFilterHandle(); + + /** Returns the current downstream -> upstream buffer. */ + virtual NetworkBuffer& readBuffer() = 0; + /** Returns the current upstream -> downstream buffer. */ + virtual NetworkBuffer& writeBuffer() = 0; + + /** + * Writes data directly to the downstream connection. + * @param data The bytes to write. + * @param end_stream If true, half-closes after the write. + */ + virtual void write(std::string_view data, bool end_stream) = 0; + + /** + * Injects data into the read filter chain after this filter. + * @param data The bytes to inject. + * @param end_stream Whether this is the final read-side data. + */ + virtual void injectReadData(std::string_view data, bool end_stream) = 0; + + /** + * Injects data into the write filter chain after this filter. + * @param data The bytes to inject. + * @param end_stream Whether this is the final write-side data. + */ + virtual void injectWriteData(std::string_view data, bool end_stream) = 0; + + /** Resumes iteration after a previous Stop result. */ + virtual void continueReading() = 0; + + /** Closes the connection using the supplied close behavior. */ + virtual void close(NetworkConnectionCloseType close_type) = 0; + + /** Returns Envoy's unique ID for this connection. */ + virtual uint64_t getConnectionId() = 0; + + /** Returns the remote address and port if available. */ + virtual std::optional> getRemoteAddress() = 0; + + /** Returns the local address and port if available. */ + virtual std::optional> getLocalAddress() = 0; + + /** Returns true when the connection is using SSL/TLS. */ + virtual bool isSsl() = 0; + + /** Disables or re-enables close handling for this filter instance. */ + virtual void disableClose(bool disabled) = 0; + + /** Closes the connection and records termination details. */ + virtual void closeWithDetails(NetworkConnectionCloseType close_type, + std::string_view details) = 0; + + /** Returns the requested server name (SNI) if present. */ + virtual std::optional getRequestedServerName() = 0; + + /** Returns the direct remote address and port without proxy/XFF handling. */ + virtual std::optional> getDirectRemoteAddress() = 0; + + /** Returns the peer certificate URI SANs. */ + virtual std::vector getSslUriSans() = 0; + + /** Returns the peer certificate DNS SANs. */ + virtual std::vector getSslDnsSans() = 0; + + /** Returns the peer certificate subject if available. */ + virtual std::optional getSslSubject() = 0; + + /** Stores a raw bytes filter state value. */ + virtual bool setFilterState(std::string_view key, std::string_view value) = 0; + + /** Returns a raw bytes filter state value if available. */ + virtual std::optional getFilterState(std::string_view key) = 0; + + /** Stores a typed filter state value using Envoy's ObjectFactory for the key. */ + virtual bool setFilterStateTyped(std::string_view key, std::string_view value) = 0; + + /** Returns the serialized bytes of a typed filter state value if available. */ + virtual std::optional getFilterStateTyped(std::string_view key) = 0; + + /** Returns a dynamic metadata string value if available. */ + virtual std::optional getMetadataString(std::string_view ns, + std::string_view key) = 0; + + /** Returns a dynamic metadata numeric value if available. */ + virtual std::optional getMetadataNumber(std::string_view ns, std::string_view key) = 0; + + /** Returns a dynamic metadata bool value if available. */ + virtual std::optional getMetadataBool(std::string_view ns, std::string_view key) = 0; + + /** Sets a string dynamic metadata value, replacing any existing value. */ + virtual void setMetadata(std::string_view ns, std::string_view key, std::string_view value) = 0; + + /** Sets a numeric dynamic metadata value, replacing any existing value. */ + virtual void setMetadata(std::string_view ns, std::string_view key, double value) = 0; + + /** Sets a bool dynamic metadata value, replacing any existing value. */ + virtual void setMetadata(std::string_view ns, std::string_view key, bool value) = 0; + void setMetadata(std::string_view ns, std::string_view key, const char* value) { + setMetadata(ns, key, std::string_view(value)); + } + + /** Stores an integer socket option for the supplied socket state. */ + virtual void setSocketOptionInt(int64_t level, int64_t name, SocketOptionState state, + int64_t value) = 0; + + /** Stores a bytes socket option for the supplied socket state. */ + virtual void setSocketOptionBytes(int64_t level, int64_t name, SocketOptionState state, + std::string_view value) = 0; + + /** Returns an integer socket option value if one is present. */ + virtual std::optional getSocketOptionInt(int64_t level, int64_t name, + SocketOptionState state) = 0; + + /** Returns a bytes socket option value if one is present. */ + virtual std::optional getSocketOptionBytes(int64_t level, int64_t name, + SocketOptionState state) = 0; + + /** Returns all socket options currently stored on the connection. */ + virtual std::vector getSocketOptions() = 0; + + /** + * Initiates an asynchronous HTTP callout from this network filter. + * The callback must remain valid until completion. + */ + virtual std::pair + httpCallout(std::string_view cluster, std::span headers, std::string_view body, + uint64_t timeout_ms, HttpCalloutCallback& cb) = 0; + + /** Records a value in a histogram metric defined from the config handle. */ + virtual MetricsResult recordHistogramValue(MetricID id, uint64_t value) = 0; + + /** Sets a gauge metric to the supplied value. */ + virtual MetricsResult setGaugeValue(MetricID id, uint64_t value) = 0; + + /** Increments a gauge metric by the supplied value. */ + virtual MetricsResult incrementGaugeValue(MetricID id, uint64_t value) = 0; + + /** Decrements a gauge metric by the supplied value. */ + virtual MetricsResult decrementGaugeValue(MetricID id, uint64_t value) = 0; + + /** Increments a counter metric by the supplied value. */ + virtual MetricsResult incrementCounterValue(MetricID id, uint64_t value) = 0; + + /** Returns host counts for the supplied cluster and priority if available. */ + virtual std::optional getClusterHostCounts(std::string_view cluster, + uint32_t priority) = 0; + + /** Returns the selected upstream host address and port if available. */ + virtual std::optional> getUpstreamHostAddress() = 0; + + /** Returns the selected upstream host hostname if available. */ + virtual std::optional getUpstreamHostHostname() = 0; + + /** Returns the selected upstream host cluster name if available. */ + virtual std::optional getUpstreamHostCluster() = 0; + + /** Returns true when an upstream host has been selected. */ + virtual bool hasUpstreamHost() = 0; + + /** Attempts to promote the upstream transport into secure mode. */ + virtual bool startUpstreamSecureTransport() = 0; + + /** Returns the current connection state. */ + virtual NetworkConnectionState getConnectionState() = 0; + + /** Disables or re-enables reading, returning Envoy's transition status. */ + virtual NetworkReadDisableStatus readDisable(bool disable) = 0; + + /** Returns whether reading is currently enabled. */ + virtual bool readEnabled() = 0; + + /** Returns whether half-close semantics are enabled. */ + virtual bool isHalfCloseEnabled() = 0; + + /** Enables or disables half-close semantics. */ + virtual void enableHalfClose(bool enabled) = 0; + + /** Returns the current soft buffer limit in bytes. */ + virtual uint32_t getBufferLimit() = 0; + + /** Updates the soft buffer limit in bytes. */ + virtual void setBufferLimits(uint32_t limit) = 0; + + /** Returns true when the write buffer is currently above the high watermark. */ + virtual bool aboveHighWatermark() = 0; + + /** + * Returns a scheduler tied to this filter's worker thread. + * The scheduler must be acquired during a filter callback and may be used later from other + * threads. + */ + virtual std::shared_ptr getScheduler() = 0; + + /** Returns the worker index assigned to this filter instance. */ + virtual uint32_t getWorkerIndex() = 0; + + /** Returns whether Envoy logging is enabled for the supplied level. */ + virtual bool logEnabled(LogLevel level) = 0; + + /** Logs a message through Envoy's logging subsystem. */ + virtual void log(LogLevel level, std::string_view message) = 0; +}; + +class NetworkFilterConfigHandle { +public: + virtual ~NetworkFilterConfigHandle(); + + /** Defines a histogram metric during config initialization. */ + virtual std::pair defineHistogram(std::string_view name) = 0; + + /** Defines a gauge metric during config initialization. */ + virtual std::pair defineGauge(std::string_view name) = 0; + + /** Defines a counter metric during config initialization. */ + virtual std::pair defineCounter(std::string_view name) = 0; + + /** + * Returns a scheduler tied to the main-thread config context. + * It must be acquired during config creation and may be used later from other threads. + */ + virtual std::shared_ptr getScheduler() = 0; + + /** Returns whether Envoy logging is enabled for the supplied level. */ + virtual bool logEnabled(LogLevel level) = 0; + + /** Logs a message through Envoy's logging subsystem. */ + virtual void log(LogLevel level, std::string_view message) = 0; +}; + +class NetworkFilter { +public: + virtual ~NetworkFilter(); + + /** Called when a new TCP connection has been established. */ + virtual NetworkFilterStatus onNewConnection() = 0; + + /** Called for downstream -> upstream data. */ + virtual NetworkFilterStatus onRead(NetworkBuffer& data, bool end_stream) = 0; + + /** Called for upstream -> downstream data. */ + virtual NetworkFilterStatus onWrite(NetworkBuffer& data, bool end_stream) = 0; + + /** Called for connection lifecycle events such as connect and close. */ + virtual void onEvent(NetworkConnectionEvent event) = 0; + + /** Called when the filter is being destroyed. */ + virtual void onDestroy() = 0; + + /** Called when the write buffer crosses above the configured high watermark. */ + virtual void onAboveWriteBufferHighWatermark() {} + + /** Called when the write buffer drops below the low watermark after being above high watermark. + */ + virtual void onBelowWriteBufferLowWatermark() {} +}; + +class NetworkFilterFactory { +public: + virtual ~NetworkFilterFactory(); + + /** Creates the per-connection network filter instance. */ + virtual std::unique_ptr create(NetworkFilterHandle& handle) = 0; + + /** Called when the factory is being destroyed. */ + virtual void onDestroy() {} +}; + +class NetworkFilterConfigFactory { +public: + virtual ~NetworkFilterConfigFactory(); + + /** Parses config_view and returns the thread-safe factory used for subsequent connections. */ + virtual std::unique_ptr create(NetworkFilterConfigHandle& handle, + std::string_view config_view) = 0; +}; + +using NetworkFilterConfigFactoryPtr = std::unique_ptr; + +class NetworkFilterConfigFactoryRegistry { +public: + static const std::map& getRegistry(); + +private: + static std::map& getMutableRegistry(); + friend class NetworkFilterConfigFactoryRegister; +}; + +class NetworkFilterConfigFactoryRegister { +public: + NetworkFilterConfigFactoryRegister(std::string_view name, NetworkFilterConfigFactoryPtr factory); + ~NetworkFilterConfigFactoryRegister(); + +private: + const std::string name_; +}; + +#define REGISTER_NETWORK_FILTER_CONFIG_FACTORY(FACTORY_CLASS, NAME) \ + static Envoy::DynamicModules::NetworkFilterConfigFactoryRegister \ + NetworkFilterConfigFactoryRegister_##FACTORY_CLASS##_register_NAME( \ + NAME, std::unique_ptr( \ + new FACTORY_CLASS())); + +} // namespace DynamicModules +} // namespace Envoy diff --git a/source/extensions/dynamic_modules/sdk/go/abi/network.go b/source/extensions/dynamic_modules/sdk/go/abi/network.go new file mode 100644 index 0000000000000..7cc1e9e4cd35b --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/go/abi/network.go @@ -0,0 +1,1193 @@ +package abi + +/* +#include +#include +#include "../../../abi/abi.h" +*/ +import "C" + +import ( + "runtime" + "unsafe" + + sdk "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go" + "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/shared" +) + +type networkFilterConfigWrapper struct { + pluginFactory shared.NetworkFilterFactory + configHandle *dymNetworkConfigHandle +} + +type networkFilterWrapper = dymNetworkFilterHandle + +var networkConfigManager = newManager[networkFilterConfigWrapper]() +var networkPluginManager = newManager[networkFilterWrapper]() + +type dymNetworkBuffer struct { + hostPluginPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr + readSide bool +} + +func (b *dymNetworkBuffer) GetChunks() []shared.UnsafeEnvoyBuffer { + var chunksSize C.size_t + if b.readSide { + chunksSize = C.envoy_dynamic_module_callback_network_filter_get_read_buffer_chunks_size( + b.hostPluginPtr, + ) + } else { + chunksSize = C.envoy_dynamic_module_callback_network_filter_get_write_buffer_chunks_size( + b.hostPluginPtr, + ) + } + + if chunksSize == 0 { + return nil + } + + resultChunks := make([]C.envoy_dynamic_module_type_envoy_buffer, chunksSize) + var ok C.bool + if b.readSide { + ok = C.envoy_dynamic_module_callback_network_filter_get_read_buffer_chunks( + b.hostPluginPtr, + unsafe.SliceData(resultChunks), + ) + } else { + ok = C.envoy_dynamic_module_callback_network_filter_get_write_buffer_chunks( + b.hostPluginPtr, + unsafe.SliceData(resultChunks), + ) + } + if !bool(ok) { + return nil + } + + finalResult := envoyBufferSliceToUnsafeEnvoyBufferSlice(resultChunks) + runtime.KeepAlive(resultChunks) + return finalResult +} + +func (b *dymNetworkBuffer) GetSize() uint64 { + if b.readSide { + return uint64(C.envoy_dynamic_module_callback_network_filter_get_read_buffer_size( + b.hostPluginPtr, + )) + } + return uint64(C.envoy_dynamic_module_callback_network_filter_get_write_buffer_size( + b.hostPluginPtr, + )) +} + +func (b *dymNetworkBuffer) Drain(numBytes uint64) bool { + var ok C.bool + if b.readSide { + ok = C.envoy_dynamic_module_callback_network_filter_drain_read_buffer( + b.hostPluginPtr, + C.size_t(numBytes), + ) + } else { + ok = C.envoy_dynamic_module_callback_network_filter_drain_write_buffer( + b.hostPluginPtr, + C.size_t(numBytes), + ) + } + return bool(ok) +} + +func (b *dymNetworkBuffer) Prepend(data []byte) bool { + var ok C.bool + if b.readSide { + ok = C.envoy_dynamic_module_callback_network_filter_prepend_read_buffer( + b.hostPluginPtr, + bytesToModuleBuffer(data), + ) + } else { + ok = C.envoy_dynamic_module_callback_network_filter_prepend_write_buffer( + b.hostPluginPtr, + bytesToModuleBuffer(data), + ) + } + runtime.KeepAlive(data) + return bool(ok) +} + +func (b *dymNetworkBuffer) Append(data []byte) bool { + var ok C.bool + if b.readSide { + ok = C.envoy_dynamic_module_callback_network_filter_append_read_buffer( + b.hostPluginPtr, + bytesToModuleBuffer(data), + ) + } else { + ok = C.envoy_dynamic_module_callback_network_filter_append_write_buffer( + b.hostPluginPtr, + bytesToModuleBuffer(data), + ) + } + runtime.KeepAlive(data) + return bool(ok) +} + +type dymNetworkFilterHandle struct { + hostPluginPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr + plugin shared.NetworkFilter + readBuffer dymNetworkBuffer + writeBuffer dymNetworkBuffer + calloutCallbacks map[uint64]shared.HttpCalloutCallback + scheduler *dymScheduler + filterDestroyed bool +} + +func newDymNetworkFilterHandle( + hostPluginPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr, +) *dymNetworkFilterHandle { + return &dymNetworkFilterHandle{ + hostPluginPtr: hostPluginPtr, + readBuffer: dymNetworkBuffer{ + hostPluginPtr: hostPluginPtr, + readSide: true, + }, + writeBuffer: dymNetworkBuffer{ + hostPluginPtr: hostPluginPtr, + readSide: false, + }, + } +} + +func (h *dymNetworkFilterHandle) ReadBuffer() shared.NetworkBuffer { + return &h.readBuffer +} + +func (h *dymNetworkFilterHandle) WriteBuffer() shared.NetworkBuffer { + return &h.writeBuffer +} + +func (h *dymNetworkFilterHandle) Write(data []byte, endStream bool) { + C.envoy_dynamic_module_callback_network_filter_write( + h.hostPluginPtr, + bytesToModuleBuffer(data), + C.bool(endStream), + ) + runtime.KeepAlive(data) +} + +func (h *dymNetworkFilterHandle) InjectReadData(data []byte, endStream bool) { + C.envoy_dynamic_module_callback_network_filter_inject_read_data( + h.hostPluginPtr, + bytesToModuleBuffer(data), + C.bool(endStream), + ) + runtime.KeepAlive(data) +} + +func (h *dymNetworkFilterHandle) InjectWriteData(data []byte, endStream bool) { + C.envoy_dynamic_module_callback_network_filter_inject_write_data( + h.hostPluginPtr, + bytesToModuleBuffer(data), + C.bool(endStream), + ) + runtime.KeepAlive(data) +} + +func (h *dymNetworkFilterHandle) ContinueReading() { + C.envoy_dynamic_module_callback_network_filter_continue_reading(h.hostPluginPtr) +} + +func (h *dymNetworkFilterHandle) Close(closeType shared.NetworkConnectionCloseType) { + C.envoy_dynamic_module_callback_network_filter_close( + h.hostPluginPtr, + C.envoy_dynamic_module_type_network_connection_close_type(closeType), + ) +} + +func (h *dymNetworkFilterHandle) GetConnectionID() uint64 { + return uint64(C.envoy_dynamic_module_callback_network_filter_get_connection_id(h.hostPluginPtr)) +} + +func (h *dymNetworkFilterHandle) getAddress( + kind int, +) (shared.UnsafeEnvoyBuffer, uint32, bool) { + var address C.envoy_dynamic_module_type_envoy_buffer + var port C.uint32_t + var ret C.bool + switch kind { + case 0: + ret = C.envoy_dynamic_module_callback_network_filter_get_remote_address( + h.hostPluginPtr, + &address, + &port, + ) + case 1: + ret = C.envoy_dynamic_module_callback_network_filter_get_local_address( + h.hostPluginPtr, + &address, + &port, + ) + case 2: + ret = C.envoy_dynamic_module_callback_network_filter_get_direct_remote_address( + h.hostPluginPtr, + &address, + &port, + ) + default: + return shared.UnsafeEnvoyBuffer{}, 0, false + } + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, 0, false + } + if address.ptr == nil || address.length == 0 { + return shared.UnsafeEnvoyBuffer{}, uint32(port), true + } + return envoyBufferToUnsafeEnvoyBuffer(address), uint32(port), true +} + +func (h *dymNetworkFilterHandle) GetRemoteAddress() (shared.UnsafeEnvoyBuffer, uint32, bool) { + return h.getAddress(0) +} + +func (h *dymNetworkFilterHandle) GetLocalAddress() (shared.UnsafeEnvoyBuffer, uint32, bool) { + return h.getAddress(1) +} + +func (h *dymNetworkFilterHandle) IsSSL() bool { + return bool(C.envoy_dynamic_module_callback_network_filter_is_ssl(h.hostPluginPtr)) +} + +func (h *dymNetworkFilterHandle) DisableClose(disabled bool) { + C.envoy_dynamic_module_callback_network_filter_disable_close( + h.hostPluginPtr, + C.bool(disabled), + ) +} + +func (h *dymNetworkFilterHandle) CloseWithDetails( + closeType shared.NetworkConnectionCloseType, + details string, +) { + C.envoy_dynamic_module_callback_network_filter_close_with_details( + h.hostPluginPtr, + C.envoy_dynamic_module_type_network_connection_close_type(closeType), + stringToModuleBuffer(details), + ) + runtime.KeepAlive(details) +} + +func (h *dymNetworkFilterHandle) GetRequestedServerName() (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_network_filter_get_requested_server_name( + h.hostPluginPtr, + &valueView, + ) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (h *dymNetworkFilterHandle) GetDirectRemoteAddress() (shared.UnsafeEnvoyBuffer, uint32, bool) { + return h.getAddress(2) +} + +func (h *dymNetworkFilterHandle) getSANs( + kind int, +) []shared.UnsafeEnvoyBuffer { + var size C.size_t + switch kind { + case 0: + size = C.envoy_dynamic_module_callback_network_filter_get_ssl_uri_sans_size(h.hostPluginPtr) + case 1: + size = C.envoy_dynamic_module_callback_network_filter_get_ssl_dns_sans_size(h.hostPluginPtr) + default: + return nil + } + if size == 0 { + return nil + } + + result := make([]C.envoy_dynamic_module_type_envoy_buffer, size) + var ret C.bool + switch kind { + case 0: + ret = C.envoy_dynamic_module_callback_network_filter_get_ssl_uri_sans( + h.hostPluginPtr, + unsafe.SliceData(result), + ) + case 1: + ret = C.envoy_dynamic_module_callback_network_filter_get_ssl_dns_sans( + h.hostPluginPtr, + unsafe.SliceData(result), + ) + default: + return nil + } + if !bool(ret) { + return nil + } + + finalResult := envoyBufferSliceToUnsafeEnvoyBufferSlice(result) + runtime.KeepAlive(result) + return finalResult +} + +func (h *dymNetworkFilterHandle) GetSSLURISANs() []shared.UnsafeEnvoyBuffer { + return h.getSANs(0) +} + +func (h *dymNetworkFilterHandle) GetSSLDNSSANs() []shared.UnsafeEnvoyBuffer { + return h.getSANs(1) +} + +func (h *dymNetworkFilterHandle) GetSSLSubject() (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_network_filter_get_ssl_subject( + h.hostPluginPtr, + &valueView, + ) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (h *dymNetworkFilterHandle) SetFilterState(key string, value []byte) bool { + ret := C.envoy_dynamic_module_callback_network_set_filter_state_bytes( + h.hostPluginPtr, + stringToModuleBuffer(key), + bytesToModuleBuffer(value), + ) + runtime.KeepAlive(key) + runtime.KeepAlive(value) + return bool(ret) +} + +func (h *dymNetworkFilterHandle) GetFilterState(key string) (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_network_get_filter_state_bytes( + h.hostPluginPtr, + stringToModuleBuffer(key), + &valueView, + ) + runtime.KeepAlive(key) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (h *dymNetworkFilterHandle) SetFilterStateTyped(key string, value []byte) bool { + ret := C.envoy_dynamic_module_callback_network_set_filter_state_typed( + h.hostPluginPtr, + stringToModuleBuffer(key), + bytesToModuleBuffer(value), + ) + runtime.KeepAlive(key) + runtime.KeepAlive(value) + return bool(ret) +} + +func (h *dymNetworkFilterHandle) GetFilterStateTyped(key string) (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_network_get_filter_state_typed( + h.hostPluginPtr, + stringToModuleBuffer(key), + &valueView, + ) + runtime.KeepAlive(key) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (h *dymNetworkFilterHandle) GetMetadataString( + metadataNamespace, key string, +) (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_network_get_dynamic_metadata_string( + h.hostPluginPtr, + stringToModuleBuffer(metadataNamespace), + stringToModuleBuffer(key), + &valueView, + ) + runtime.KeepAlive(metadataNamespace) + runtime.KeepAlive(key) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (h *dymNetworkFilterHandle) GetMetadataNumber(metadataNamespace, key string) (float64, bool) { + var value C.double + ret := C.envoy_dynamic_module_callback_network_get_dynamic_metadata_number( + h.hostPluginPtr, + stringToModuleBuffer(metadataNamespace), + stringToModuleBuffer(key), + &value, + ) + runtime.KeepAlive(metadataNamespace) + runtime.KeepAlive(key) + if !bool(ret) { + return 0, false + } + return float64(value), true +} + +func (h *dymNetworkFilterHandle) GetMetadataBool(metadataNamespace, key string) (bool, bool) { + var value C.bool + ret := C.envoy_dynamic_module_callback_network_get_dynamic_metadata_bool( + h.hostPluginPtr, + stringToModuleBuffer(metadataNamespace), + stringToModuleBuffer(key), + &value, + ) + runtime.KeepAlive(metadataNamespace) + runtime.KeepAlive(key) + if !bool(ret) { + return false, false + } + return bool(value), true +} + +func (h *dymNetworkFilterHandle) SetMetadata(metadataNamespace, key string, value any) { + var numValue float64 + var isNum bool + var strValue string + var isStr bool + + switch v := value.(type) { + case uint: + numValue = float64(v) + isNum = true + case uint8: + numValue = float64(v) + isNum = true + case uint16: + numValue = float64(v) + isNum = true + case uint32: + numValue = float64(v) + isNum = true + case uint64: + numValue = float64(v) + isNum = true + case int: + numValue = float64(v) + isNum = true + case int8: + numValue = float64(v) + isNum = true + case int16: + numValue = float64(v) + isNum = true + case int32: + numValue = float64(v) + isNum = true + case int64: + numValue = float64(v) + isNum = true + case float32: + numValue = float64(v) + isNum = true + case float64: + numValue = float64(v) + isNum = true + case bool: + C.envoy_dynamic_module_callback_network_set_dynamic_metadata_bool( + h.hostPluginPtr, + stringToModuleBuffer(metadataNamespace), + stringToModuleBuffer(key), + C.bool(v), + ) + runtime.KeepAlive(metadataNamespace) + runtime.KeepAlive(key) + return + case string: + strValue = v + isStr = true + } + + if isNum { + C.envoy_dynamic_module_callback_network_set_dynamic_metadata_number( + h.hostPluginPtr, + stringToModuleBuffer(metadataNamespace), + stringToModuleBuffer(key), + C.double(numValue), + ) + } else if isStr { + C.envoy_dynamic_module_callback_network_set_dynamic_metadata_string( + h.hostPluginPtr, + stringToModuleBuffer(metadataNamespace), + stringToModuleBuffer(key), + stringToModuleBuffer(strValue), + ) + } + runtime.KeepAlive(metadataNamespace) + runtime.KeepAlive(key) + runtime.KeepAlive(strValue) +} + +func (h *dymNetworkFilterHandle) SetSocketOptionInt( + level, name int64, + state shared.SocketOptionState, + value int64, +) { + C.envoy_dynamic_module_callback_network_set_socket_option_int( + h.hostPluginPtr, + C.int64_t(level), + C.int64_t(name), + C.envoy_dynamic_module_type_socket_option_state(state), + C.int64_t(value), + ) +} + +func (h *dymNetworkFilterHandle) SetSocketOptionBytes( + level, name int64, + state shared.SocketOptionState, + value []byte, +) { + C.envoy_dynamic_module_callback_network_set_socket_option_bytes( + h.hostPluginPtr, + C.int64_t(level), + C.int64_t(name), + C.envoy_dynamic_module_type_socket_option_state(state), + bytesToModuleBuffer(value), + ) + runtime.KeepAlive(value) +} + +func (h *dymNetworkFilterHandle) GetSocketOptionInt( + level, name int64, + state shared.SocketOptionState, +) (int64, bool) { + var value C.int64_t + ret := C.envoy_dynamic_module_callback_network_get_socket_option_int( + h.hostPluginPtr, + C.int64_t(level), + C.int64_t(name), + C.envoy_dynamic_module_type_socket_option_state(state), + &value, + ) + if !bool(ret) { + return 0, false + } + return int64(value), true +} + +func (h *dymNetworkFilterHandle) GetSocketOptionBytes( + level, name int64, + state shared.SocketOptionState, +) (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_network_get_socket_option_bytes( + h.hostPluginPtr, + C.int64_t(level), + C.int64_t(name), + C.envoy_dynamic_module_type_socket_option_state(state), + &valueView, + ) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (h *dymNetworkFilterHandle) GetSocketOptions() []shared.SocketOption { + size := C.envoy_dynamic_module_callback_network_get_socket_options_size(h.hostPluginPtr) + if size == 0 { + return nil + } + + result := make([]C.envoy_dynamic_module_type_socket_option, size) + C.envoy_dynamic_module_callback_network_get_socket_options( + h.hostPluginPtr, + unsafe.SliceData(result), + ) + + finalResult := make([]shared.SocketOption, 0, len(result)) + for _, option := range result { + finalResult = append(finalResult, shared.SocketOption{ + Level: int64(option.level), + Name: int64(option.name), + State: shared.SocketOptionState(option.state), + ValueType: shared.SocketOptionValueType(option.value_type), + IntValue: int64(option.int_value), + BytesValue: envoyBufferToUnsafeEnvoyBuffer(option.byte_value), + }) + } + runtime.KeepAlive(result) + return finalResult +} + +func (h *dymNetworkFilterHandle) Log(level shared.LogLevel, format string, args ...any) { + hostLog(level, format, args) +} + +func (h *dymNetworkFilterHandle) HttpCallout( + cluster string, headers [][2]string, body []byte, timeoutMs uint64, + cb shared.HttpCalloutCallback, +) (shared.HttpCalloutInitResult, uint64) { + headerViews := headersToModuleHttpHeaderSlice(headers) + var calloutID C.uint64_t + + result := C.envoy_dynamic_module_callback_network_filter_http_callout( + h.hostPluginPtr, + &calloutID, + stringToModuleBuffer(cluster), + unsafe.SliceData(headerViews), + C.size_t(len(headerViews)), + bytesToModuleBuffer(body), + C.uint64_t(timeoutMs), + ) + + runtime.KeepAlive(cluster) + runtime.KeepAlive(headers) + runtime.KeepAlive(body) + runtime.KeepAlive(headerViews) + + goResult := shared.HttpCalloutInitResult(result) + if goResult != shared.HttpCalloutInitSuccess { + return goResult, 0 + } + + if h.calloutCallbacks == nil { + h.calloutCallbacks = make(map[uint64]shared.HttpCalloutCallback) + } + h.calloutCallbacks[uint64(calloutID)] = cb + return goResult, uint64(calloutID) +} + +func (h *dymNetworkFilterHandle) RecordHistogramValue( + id shared.MetricID, + value uint64, +) shared.MetricsResult { + result := C.envoy_dynamic_module_callback_network_filter_record_histogram_value( + h.hostPluginPtr, + C.size_t(id), + C.uint64_t(value), + ) + return shared.MetricsResult(result) +} + +func (h *dymNetworkFilterHandle) SetGaugeValue( + id shared.MetricID, + value uint64, +) shared.MetricsResult { + result := C.envoy_dynamic_module_callback_network_filter_set_gauge( + h.hostPluginPtr, + C.size_t(id), + C.uint64_t(value), + ) + return shared.MetricsResult(result) +} + +func (h *dymNetworkFilterHandle) IncrementGaugeValue( + id shared.MetricID, + value uint64, +) shared.MetricsResult { + result := C.envoy_dynamic_module_callback_network_filter_increment_gauge( + h.hostPluginPtr, + C.size_t(id), + C.uint64_t(value), + ) + return shared.MetricsResult(result) +} + +func (h *dymNetworkFilterHandle) DecrementGaugeValue( + id shared.MetricID, + value uint64, +) shared.MetricsResult { + result := C.envoy_dynamic_module_callback_network_filter_decrement_gauge( + h.hostPluginPtr, + C.size_t(id), + C.uint64_t(value), + ) + return shared.MetricsResult(result) +} + +func (h *dymNetworkFilterHandle) IncrementCounterValue( + id shared.MetricID, + value uint64, +) shared.MetricsResult { + result := C.envoy_dynamic_module_callback_network_filter_increment_counter( + h.hostPluginPtr, + C.size_t(id), + C.uint64_t(value), + ) + return shared.MetricsResult(result) +} + +func (h *dymNetworkFilterHandle) GetClusterHostCounts( + cluster string, + priority uint32, +) (shared.ClusterHostCounts, bool) { + var total C.size_t + var healthy C.size_t + var degraded C.size_t + ret := C.envoy_dynamic_module_callback_network_filter_get_cluster_host_count( + h.hostPluginPtr, + stringToModuleBuffer(cluster), + C.uint32_t(priority), + &total, + &healthy, + °raded, + ) + runtime.KeepAlive(cluster) + if !bool(ret) { + return shared.ClusterHostCounts{}, false + } + return shared.ClusterHostCounts{ + Total: uint64(total), + Healthy: uint64(healthy), + Degraded: uint64(degraded), + }, true +} + +func (h *dymNetworkFilterHandle) GetUpstreamHostAddress() (shared.UnsafeEnvoyBuffer, uint32, bool) { + var address C.envoy_dynamic_module_type_envoy_buffer + var port C.uint32_t + ret := C.envoy_dynamic_module_callback_network_filter_get_upstream_host_address( + h.hostPluginPtr, + &address, + &port, + ) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, 0, false + } + if address.ptr == nil || address.length == 0 { + return shared.UnsafeEnvoyBuffer{}, uint32(port), true + } + return envoyBufferToUnsafeEnvoyBuffer(address), uint32(port), true +} + +func (h *dymNetworkFilterHandle) GetUpstreamHostHostname() (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_network_filter_get_upstream_host_hostname( + h.hostPluginPtr, + &valueView, + ) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (h *dymNetworkFilterHandle) GetUpstreamHostCluster() (shared.UnsafeEnvoyBuffer, bool) { + var valueView C.envoy_dynamic_module_type_envoy_buffer + ret := C.envoy_dynamic_module_callback_network_filter_get_upstream_host_cluster( + h.hostPluginPtr, + &valueView, + ) + if !bool(ret) { + return shared.UnsafeEnvoyBuffer{}, false + } + if valueView.ptr == nil || valueView.length == 0 { + return shared.UnsafeEnvoyBuffer{}, true + } + return envoyBufferToUnsafeEnvoyBuffer(valueView), true +} + +func (h *dymNetworkFilterHandle) HasUpstreamHost() bool { + return bool(C.envoy_dynamic_module_callback_network_filter_has_upstream_host(h.hostPluginPtr)) +} + +func (h *dymNetworkFilterHandle) StartUpstreamSecureTransport() bool { + return bool(C.envoy_dynamic_module_callback_network_filter_start_upstream_secure_transport( + h.hostPluginPtr, + )) +} + +func (h *dymNetworkFilterHandle) GetConnectionState() shared.NetworkConnectionState { + return shared.NetworkConnectionState( + C.envoy_dynamic_module_callback_network_filter_get_connection_state(h.hostPluginPtr), + ) +} + +func (h *dymNetworkFilterHandle) ReadDisable(disable bool) shared.NetworkReadDisableStatus { + return shared.NetworkReadDisableStatus( + C.envoy_dynamic_module_callback_network_filter_read_disable( + h.hostPluginPtr, + C.bool(disable), + ), + ) +} + +func (h *dymNetworkFilterHandle) ReadEnabled() bool { + return bool(C.envoy_dynamic_module_callback_network_filter_read_enabled(h.hostPluginPtr)) +} + +func (h *dymNetworkFilterHandle) IsHalfCloseEnabled() bool { + return bool(C.envoy_dynamic_module_callback_network_filter_is_half_close_enabled( + h.hostPluginPtr, + )) +} + +func (h *dymNetworkFilterHandle) EnableHalfClose(enabled bool) { + C.envoy_dynamic_module_callback_network_filter_enable_half_close( + h.hostPluginPtr, + C.bool(enabled), + ) +} + +func (h *dymNetworkFilterHandle) GetBufferLimit() uint32 { + return uint32(C.envoy_dynamic_module_callback_network_filter_get_buffer_limit(h.hostPluginPtr)) +} + +func (h *dymNetworkFilterHandle) SetBufferLimits(limit uint32) { + C.envoy_dynamic_module_callback_network_filter_set_buffer_limits( + h.hostPluginPtr, + C.uint32_t(limit), + ) +} + +func (h *dymNetworkFilterHandle) AboveHighWatermark() bool { + return bool(C.envoy_dynamic_module_callback_network_filter_above_high_watermark( + h.hostPluginPtr, + )) +} + +func (h *dymNetworkFilterHandle) GetScheduler() shared.Scheduler { + if h.scheduler == nil { + schedulerPtr := C.envoy_dynamic_module_callback_network_filter_scheduler_new(h.hostPluginPtr) + h.scheduler = newDymScheduler( + unsafe.Pointer(schedulerPtr), + func(schedulerPtr unsafe.Pointer, taskID C.uint64_t) { + C.envoy_dynamic_module_callback_network_filter_scheduler_commit( + C.envoy_dynamic_module_type_network_filter_scheduler_module_ptr(schedulerPtr), + taskID, + ) + }, + ) + + runtime.SetFinalizer(h.scheduler, func(s *dymScheduler) { + C.envoy_dynamic_module_callback_network_filter_scheduler_delete( + C.envoy_dynamic_module_type_network_filter_scheduler_module_ptr(s.schedulerPtr), + ) + }) + } + return h.scheduler +} + +func (h *dymNetworkFilterHandle) GetWorkerIndex() uint32 { + return uint32(C.envoy_dynamic_module_callback_network_filter_get_worker_index(h.hostPluginPtr)) +} + +type dymNetworkConfigHandle struct { + hostConfigPtr C.envoy_dynamic_module_type_network_filter_config_envoy_ptr + scheduler *dymScheduler +} + +func (h *dymNetworkConfigHandle) Log(level shared.LogLevel, format string, args ...any) { + hostLog(level, format, args) +} + +func (h *dymNetworkConfigHandle) DefineHistogram(name string) (shared.MetricID, shared.MetricsResult) { + var metricID C.size_t + result := C.envoy_dynamic_module_callback_network_filter_config_define_histogram( + h.hostConfigPtr, + stringToModuleBuffer(name), + &metricID, + ) + runtime.KeepAlive(name) + return shared.MetricID(metricID), shared.MetricsResult(result) +} + +func (h *dymNetworkConfigHandle) DefineGauge(name string) (shared.MetricID, shared.MetricsResult) { + var metricID C.size_t + result := C.envoy_dynamic_module_callback_network_filter_config_define_gauge( + h.hostConfigPtr, + stringToModuleBuffer(name), + &metricID, + ) + runtime.KeepAlive(name) + return shared.MetricID(metricID), shared.MetricsResult(result) +} + +func (h *dymNetworkConfigHandle) DefineCounter(name string) (shared.MetricID, shared.MetricsResult) { + var metricID C.size_t + result := C.envoy_dynamic_module_callback_network_filter_config_define_counter( + h.hostConfigPtr, + stringToModuleBuffer(name), + &metricID, + ) + runtime.KeepAlive(name) + return shared.MetricID(metricID), shared.MetricsResult(result) +} + +func (h *dymNetworkConfigHandle) GetScheduler() shared.Scheduler { + if h.scheduler == nil { + schedulerPtr := C.envoy_dynamic_module_callback_network_filter_config_scheduler_new( + h.hostConfigPtr, + ) + h.scheduler = newDymScheduler( + unsafe.Pointer(schedulerPtr), + func(schedulerPtr unsafe.Pointer, taskID C.uint64_t) { + C.envoy_dynamic_module_callback_network_filter_config_scheduler_commit( + C.envoy_dynamic_module_type_network_filter_config_scheduler_module_ptr( + schedulerPtr, + ), + taskID, + ) + }, + ) + + runtime.SetFinalizer(h.scheduler, func(s *dymScheduler) { + C.envoy_dynamic_module_callback_network_filter_config_scheduler_delete( + C.envoy_dynamic_module_type_network_filter_config_scheduler_module_ptr( + s.schedulerPtr, + ), + ) + }) + } + return h.scheduler +} + +//export envoy_dynamic_module_on_network_filter_config_new +func envoy_dynamic_module_on_network_filter_config_new( + hostConfigPtr C.envoy_dynamic_module_type_network_filter_config_envoy_ptr, + name C.envoy_dynamic_module_type_envoy_buffer, + config C.envoy_dynamic_module_type_envoy_buffer, +) C.envoy_dynamic_module_type_network_filter_config_module_ptr { + nameString := envoyBufferToStringUnsafe(name) + configBytes := envoyBufferToBytesUnsafe(config) + + configHandle := &dymNetworkConfigHandle{hostConfigPtr: hostConfigPtr} + factory, err := sdk.NewNetworkFilterFactory(configHandle, nameString, configBytes) + if err != nil { + configHandle.Log(shared.LogLevelWarn, + "Failed to load network filter configuration for %q: %v", nameString, err) + return nil + } + if factory == nil { + configHandle.Log(shared.LogLevelWarn, + "Failed to load network filter configuration for %q: network filter factory is nil", nameString) + return nil + } + + configPtr := networkConfigManager.record(&networkFilterConfigWrapper{ + pluginFactory: factory, + configHandle: configHandle, + }) + return C.envoy_dynamic_module_type_network_filter_config_module_ptr(configPtr) +} + +//export envoy_dynamic_module_on_network_filter_config_destroy +func envoy_dynamic_module_on_network_filter_config_destroy( + configPtr C.envoy_dynamic_module_type_network_filter_config_module_ptr, +) { + configWrapper := networkConfigManager.unwrap(unsafe.Pointer(configPtr)) + if configWrapper == nil { + return + } + configWrapper.configHandle.scheduler = nil + configWrapper.pluginFactory.OnDestroy() + networkConfigManager.remove(unsafe.Pointer(configPtr)) +} + +//export envoy_dynamic_module_on_network_filter_new +func envoy_dynamic_module_on_network_filter_new( + configPtr C.envoy_dynamic_module_type_network_filter_config_module_ptr, + hostPluginPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr, +) C.envoy_dynamic_module_type_network_filter_module_ptr { + configWrapper := networkConfigManager.unwrap(unsafe.Pointer(configPtr)) + if configWrapper == nil { + return nil + } + + filterWrapper := newDymNetworkFilterHandle(hostPluginPtr) + filterWrapper.plugin = configWrapper.pluginFactory.Create(filterWrapper) + if filterWrapper.plugin == nil { + return nil + } + + filterPtr := networkPluginManager.record(filterWrapper) + return C.envoy_dynamic_module_type_network_filter_module_ptr(filterPtr) +} + +//export envoy_dynamic_module_on_network_filter_new_connection +func envoy_dynamic_module_on_network_filter_new_connection( + filterEnvoyPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr, + filterPtr C.envoy_dynamic_module_type_network_filter_module_ptr, +) C.envoy_dynamic_module_type_on_network_filter_data_status { + _ = filterEnvoyPtr + filterWrapper := networkPluginManager.unwrap(unsafe.Pointer(filterPtr)) + if filterWrapper == nil || filterWrapper.plugin == nil || filterWrapper.filterDestroyed { + return C.envoy_dynamic_module_type_on_network_filter_data_status( + shared.NetworkFilterStatusContinue, + ) + } + return C.envoy_dynamic_module_type_on_network_filter_data_status( + filterWrapper.plugin.OnNewConnection(), + ) +} + +//export envoy_dynamic_module_on_network_filter_read +func envoy_dynamic_module_on_network_filter_read( + filterEnvoyPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr, + filterPtr C.envoy_dynamic_module_type_network_filter_module_ptr, + dataLength C.size_t, + endStream C.bool, +) C.envoy_dynamic_module_type_on_network_filter_data_status { + _ = filterEnvoyPtr + _ = dataLength + filterWrapper := networkPluginManager.unwrap(unsafe.Pointer(filterPtr)) + if filterWrapper == nil || filterWrapper.plugin == nil || filterWrapper.filterDestroyed { + return C.envoy_dynamic_module_type_on_network_filter_data_status( + shared.NetworkFilterStatusContinue, + ) + } + return C.envoy_dynamic_module_type_on_network_filter_data_status( + filterWrapper.plugin.OnRead(&filterWrapper.readBuffer, bool(endStream)), + ) +} + +//export envoy_dynamic_module_on_network_filter_write +func envoy_dynamic_module_on_network_filter_write( + filterEnvoyPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr, + filterPtr C.envoy_dynamic_module_type_network_filter_module_ptr, + dataLength C.size_t, + endStream C.bool, +) C.envoy_dynamic_module_type_on_network_filter_data_status { + _ = filterEnvoyPtr + _ = dataLength + filterWrapper := networkPluginManager.unwrap(unsafe.Pointer(filterPtr)) + if filterWrapper == nil || filterWrapper.plugin == nil || filterWrapper.filterDestroyed { + return C.envoy_dynamic_module_type_on_network_filter_data_status( + shared.NetworkFilterStatusContinue, + ) + } + return C.envoy_dynamic_module_type_on_network_filter_data_status( + filterWrapper.plugin.OnWrite(&filterWrapper.writeBuffer, bool(endStream)), + ) +} + +//export envoy_dynamic_module_on_network_filter_event +func envoy_dynamic_module_on_network_filter_event( + filterEnvoyPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr, + filterPtr C.envoy_dynamic_module_type_network_filter_module_ptr, + event C.envoy_dynamic_module_type_network_connection_event, +) { + _ = filterEnvoyPtr + filterWrapper := networkPluginManager.unwrap(unsafe.Pointer(filterPtr)) + if filterWrapper == nil || filterWrapper.plugin == nil || filterWrapper.filterDestroyed { + return + } + filterWrapper.plugin.OnEvent(shared.NetworkConnectionEvent(event)) +} + +//export envoy_dynamic_module_on_network_filter_destroy +func envoy_dynamic_module_on_network_filter_destroy( + filterPtr C.envoy_dynamic_module_type_network_filter_module_ptr, +) { + filterWrapper := networkPluginManager.unwrap(unsafe.Pointer(filterPtr)) + if filterWrapper == nil || filterWrapper.filterDestroyed { + return + } + filterWrapper.filterDestroyed = true + filterWrapper.scheduler = nil + if filterWrapper.plugin != nil { + filterWrapper.plugin.OnDestroy() + } + networkPluginManager.remove(unsafe.Pointer(filterPtr)) +} + +//export envoy_dynamic_module_on_network_filter_http_callout_done +func envoy_dynamic_module_on_network_filter_http_callout_done( + filterEnvoyPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr, + filterPtr C.envoy_dynamic_module_type_network_filter_module_ptr, + calloutID C.uint64_t, + result C.envoy_dynamic_module_type_http_callout_result, + headers *C.envoy_dynamic_module_type_envoy_http_header, + headersSize C.size_t, + chunks *C.envoy_dynamic_module_type_envoy_buffer, + chunksSize C.size_t, +) { + _ = filterEnvoyPtr + filterWrapper := networkPluginManager.unwrap(unsafe.Pointer(filterPtr)) + if filterWrapper == nil || filterWrapper.filterDestroyed { + return + } + + resultHeaders := envoyHttpHeaderSliceToUnsafeHeaderSlice(unsafe.Slice(headers, int(headersSize))) + resultChunks := envoyBufferSliceToUnsafeEnvoyBufferSlice(unsafe.Slice(chunks, int(chunksSize))) + + cb := filterWrapper.calloutCallbacks[uint64(calloutID)] + if cb != nil { + delete(filterWrapper.calloutCallbacks, uint64(calloutID)) + cb.OnHttpCalloutDone(uint64(calloutID), shared.HttpCalloutResult(result), resultHeaders, resultChunks) + } +} + +//export envoy_dynamic_module_on_network_filter_scheduled +func envoy_dynamic_module_on_network_filter_scheduled( + filterEnvoyPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr, + filterPtr C.envoy_dynamic_module_type_network_filter_module_ptr, + taskID C.uint64_t, +) { + _ = filterEnvoyPtr + filterWrapper := networkPluginManager.unwrap(unsafe.Pointer(filterPtr)) + if filterWrapper == nil || filterWrapper.scheduler == nil || filterWrapper.filterDestroyed { + return + } + filterWrapper.scheduler.onScheduled(uint64(taskID)) +} + +//export envoy_dynamic_module_on_network_filter_config_scheduled +func envoy_dynamic_module_on_network_filter_config_scheduled( + configPtr C.envoy_dynamic_module_type_network_filter_config_module_ptr, + taskID C.uint64_t, +) { + configWrapper := networkConfigManager.unwrap(unsafe.Pointer(configPtr)) + if configWrapper == nil || configWrapper.configHandle == nil || configWrapper.configHandle.scheduler == nil { + return + } + configWrapper.configHandle.scheduler.onScheduled(uint64(taskID)) +} + +//export envoy_dynamic_module_on_network_filter_above_write_buffer_high_watermark +func envoy_dynamic_module_on_network_filter_above_write_buffer_high_watermark( + filterEnvoyPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr, + filterPtr C.envoy_dynamic_module_type_network_filter_module_ptr, +) { + _ = filterEnvoyPtr + filterWrapper := networkPluginManager.unwrap(unsafe.Pointer(filterPtr)) + if filterWrapper == nil || filterWrapper.plugin == nil || filterWrapper.filterDestroyed { + return + } + filterWrapper.plugin.OnAboveWriteBufferHighWatermark() +} + +//export envoy_dynamic_module_on_network_filter_below_write_buffer_low_watermark +func envoy_dynamic_module_on_network_filter_below_write_buffer_low_watermark( + filterEnvoyPtr C.envoy_dynamic_module_type_network_filter_envoy_ptr, + filterPtr C.envoy_dynamic_module_type_network_filter_module_ptr, +) { + _ = filterEnvoyPtr + filterWrapper := networkPluginManager.unwrap(unsafe.Pointer(filterPtr)) + if filterWrapper == nil || filterWrapper.plugin == nil || filterWrapper.filterDestroyed { + return + } + filterWrapper.plugin.OnBelowWriteBufferLowWatermark() +} diff --git a/source/extensions/dynamic_modules/sdk/go/sdk.go b/source/extensions/dynamic_modules/sdk/go/sdk.go index 9a6929cb34846..766fa4ed1d6b2 100644 --- a/source/extensions/dynamic_modules/sdk/go/sdk.go +++ b/source/extensions/dynamic_modules/sdk/go/sdk.go @@ -9,6 +9,7 @@ import ( // For built-in plugin factories in the host binary directly. DO NOT use this for independently // compiled module or plugins. var httpFilterConfigFactoryRegistry = make(map[string]shared.HttpFilterConfigFactory) +var networkFilterConfigFactoryRegistry = make(map[string]shared.NetworkFilterConfigFactory) // NewHttpFilterFactory creates a new plugin factory for the given plugin name and unparsed config. func NewHttpFilterFactory(handle shared.HttpFilterConfigHandle, name string, @@ -36,3 +37,30 @@ func RegisterHttpFilterConfigFactories(factories map[string]shared.HttpFilterCon httpFilterConfigFactoryRegistry[name] = factory } } + +// NewNetworkFilterFactory creates a new network filter factory for the given plugin name and +// unparsed config. +func NewNetworkFilterFactory(handle shared.NetworkFilterConfigHandle, name string, + unparsedConfig []byte) (shared.NetworkFilterFactory, error) { + configFactory := networkFilterConfigFactoryRegistry[name] + if configFactory == nil { + return nil, fmt.Errorf("failed to get network filter config factory for %s", name) + } + return configFactory.Create(handle, unparsedConfig) +} + +// GetNetworkFilterConfigFactory gets the network filter config factory for the given plugin name. +func GetNetworkFilterConfigFactory(name string) shared.NetworkFilterConfigFactory { + return networkFilterConfigFactoryRegistry[name] +} + +// RegisterNetworkFilterConfigFactories registers network filter config factories for plugins in the +// composer binary itself. This function MUST only be called from init() functions. +func RegisterNetworkFilterConfigFactories(factories map[string]shared.NetworkFilterConfigFactory) { + for name, factory := range factories { + if _, ok := networkFilterConfigFactoryRegistry[name]; ok { + panic("network filter config factory already registered: " + name) + } + networkFilterConfigFactoryRegistry[name] = factory + } +} diff --git a/source/extensions/dynamic_modules/sdk/go/shared/network_api.go b/source/extensions/dynamic_modules/sdk/go/shared/network_api.go new file mode 100644 index 0000000000000..4d8d696973520 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/go/shared/network_api.go @@ -0,0 +1,112 @@ +package shared + +// NetworkFilter is the interface to implement your own network filter logic. +type NetworkFilter interface { + // OnNewConnection is called once a TCP connection has been accepted and the filter instance has + // been created for it. + // + // Returning NetworkFilterStatusContinue lets iteration proceed to later filters immediately. + // Returning NetworkFilterStatusStop pauses iteration until the filter later calls + // NetworkFilterHandle.ContinueReading. + OnNewConnection() NetworkFilterStatus + + // OnRead is called when data has been read from the downstream side and is about to continue + // through the read filter chain toward the upstream. + // + // data exposes the current read buffer owned by Envoy. endOfStream is true when the downstream + // side has half-closed and this callback is processing the final read data. Returning + // NetworkFilterStatusStop pauses iteration until ContinueReading is called. + OnRead(data NetworkBuffer, endOfStream bool) NetworkFilterStatus + + // OnWrite is called when data is about to be written in the upstream -> downstream direction. + // + // data exposes the current write buffer owned by Envoy. endOfStream is true when this callback + // is processing the final write-side data. Returning NetworkFilterStatusStop pauses iteration + // until ContinueReading is called. + OnWrite(data NetworkBuffer, endOfStream bool) NetworkFilterStatus + + // OnEvent is called for connection lifecycle events such as connect, remote close, and local + // close. + OnEvent(event NetworkConnectionEvent) + + // OnDestroy is called when the filter instance is being destroyed and should release any + // resources tied to the connection. + OnDestroy() + + // OnAboveWriteBufferHighWatermark is called when the connection write buffer crosses above its + // configured high watermark. + OnAboveWriteBufferHighWatermark() + + // OnBelowWriteBufferLowWatermark is called when the connection write buffer drops from above + // the high watermark to below the low watermark again. + OnBelowWriteBufferLowWatermark() +} + +type EmptyNetworkFilter struct { +} + +func (p *EmptyNetworkFilter) OnNewConnection() NetworkFilterStatus { + return NetworkFilterStatusDefault +} + +func (p *EmptyNetworkFilter) OnRead(data NetworkBuffer, endOfStream bool) NetworkFilterStatus { + return NetworkFilterStatusDefault +} + +func (p *EmptyNetworkFilter) OnWrite(data NetworkBuffer, endOfStream bool) NetworkFilterStatus { + return NetworkFilterStatusDefault +} + +func (p *EmptyNetworkFilter) OnEvent(event NetworkConnectionEvent) { +} + +func (p *EmptyNetworkFilter) OnDestroy() { +} + +func (p *EmptyNetworkFilter) OnAboveWriteBufferHighWatermark() { +} + +func (p *EmptyNetworkFilter) OnBelowWriteBufferLowWatermark() { +} + +// NetworkFilterFactory is the factory interface for creating network filters. +// The implementation of this interface should be thread-safe and hold the parsed configuration. +type NetworkFilterFactory interface { + // Create constructs the per-connection NetworkFilter instance for a newly accepted TCP + // connection. + // + // Returning nil causes filter creation to fail and the connection to be closed. + Create(handle NetworkFilterHandle) NetworkFilter + + // OnDestroy is called when Envoy is destroying this factory, usually after configuration has + // been replaced and all connections using it have drained. + OnDestroy() +} + +type EmptyNetworkFilterFactory struct { +} + +func (f *EmptyNetworkFilterFactory) Create(handle NetworkFilterHandle) NetworkFilter { + return &EmptyNetworkFilter{} +} + +func (f *EmptyNetworkFilterFactory) OnDestroy() { +} + +// NetworkFilterConfigFactory is the factory interface for creating network filter configurations. +// The implementation of this interface should be thread-safe and usually stateless. +type NetworkFilterConfigFactory interface { + // Create parses the supplied configuration bytes and returns a thread-safe + // NetworkFilterFactory for subsequent connections. + // + // Returning an error rejects the filter configuration. + Create(handle NetworkFilterConfigHandle, unparsedConfig []byte) (NetworkFilterFactory, error) +} + +type EmptyNetworkFilterConfigFactory struct { +} + +func (f *EmptyNetworkFilterConfigFactory) Create(handle NetworkFilterConfigHandle, + unparsedConfig []byte) (NetworkFilterFactory, error) { + return &EmptyNetworkFilterFactory{}, nil +} diff --git a/source/extensions/dynamic_modules/sdk/go/shared/network_base.go b/source/extensions/dynamic_modules/sdk/go/shared/network_base.go new file mode 100644 index 0000000000000..7133012e4fda3 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/go/shared/network_base.go @@ -0,0 +1,314 @@ +package shared + +// NetworkBuffer is an interface that provides access to the read and write buffers of a network +// connection. This should be implemented by the SDK or runtime. +type NetworkBuffer interface { + // GetChunks returns the current buffer as Envoy-owned chunks. + // + // For a read buffer, the chunks are valid after the first OnRead callback for the lifetime of + // the connection. For a write buffer, they are valid after the first OnWrite callback for the + // lifetime of the connection. Copy the data if you need to retain it after the current callback. + GetChunks() []UnsafeEnvoyBuffer + + // GetSize returns the total size of the current buffer in bytes. + GetSize() uint64 + + // Drain removes numBytes from the front of the buffer. + // + // It returns false when the underlying Envoy buffer is unavailable or the operation fails. + Drain(numBytes uint64) bool + + // Prepend adds data to the front of the buffer. + // + // The provided bytes are owned by the caller for the duration of the call. It returns false if + // Envoy rejects the operation. + Prepend(data []byte) bool + + // Append adds data to the end of the buffer. + // + // The provided bytes are owned by the caller for the duration of the call. It returns false if + // Envoy rejects the operation. + Append(data []byte) bool +} + +type SocketOptionValueType uint32 + +const ( + SocketOptionValueTypeInt SocketOptionValueType = iota + SocketOptionValueTypeBytes +) + +type SocketOption struct { + Level int64 + Name int64 + State SocketOptionState + ValueType SocketOptionValueType + IntValue int64 + BytesValue UnsafeEnvoyBuffer +} + +type NetworkFilterStatus int32 + +const ( + NetworkFilterStatusContinue NetworkFilterStatus = 0 + NetworkFilterStatusStop NetworkFilterStatus = 1 + NetworkFilterStatusDefault NetworkFilterStatus = NetworkFilterStatusContinue +) + +type NetworkConnectionCloseType uint32 + +const ( + NetworkConnectionCloseTypeFlushWrite NetworkConnectionCloseType = iota + NetworkConnectionCloseTypeNoFlush + NetworkConnectionCloseTypeFlushWriteAndDelay + NetworkConnectionCloseTypeAbort + NetworkConnectionCloseTypeAbortReset +) + +type NetworkConnectionEvent uint32 + +const ( + NetworkConnectionEventRemoteClose NetworkConnectionEvent = iota + NetworkConnectionEventLocalClose + NetworkConnectionEventConnected + NetworkConnectionEventConnectedZeroRTT +) + +type NetworkConnectionState uint32 + +const ( + NetworkConnectionStateOpen NetworkConnectionState = iota + NetworkConnectionStateClosing + NetworkConnectionStateClosed +) + +type NetworkReadDisableStatus uint32 + +const ( + NetworkReadDisableStatusNoTransition NetworkReadDisableStatus = iota + NetworkReadDisableStatusStillReadDisabled + NetworkReadDisableStatusTransitionedToReadEnabled + NetworkReadDisableStatusTransitionedToReadDisabled +) + +// NetworkFilterHandle is the interface that provides access to the network filter's connection +// state and related host features. This should be implemented by the SDK or runtime. +type NetworkFilterHandle interface { + // ReadBuffer returns the current downstream -> upstream buffer for this connection. + ReadBuffer() NetworkBuffer + + // WriteBuffer returns the current upstream -> downstream buffer for this connection. + WriteBuffer() NetworkBuffer + + // Write writes data directly to the downstream connection. + // + // When endStream is true, Envoy half-closes the connection after writing. + Write(data []byte, endStream bool) + + // InjectReadData injects data into the read filter chain after this filter. + // + // The injected bytes continue through later read filters as if they had just been received. + InjectReadData(data []byte, endStream bool) + + // InjectWriteData injects data into the write filter chain after this filter. + // + // The injected bytes continue through later write filters as if they had just been produced. + InjectWriteData(data []byte, endStream bool) + + // ContinueReading resumes filter iteration after OnNewConnection, OnRead, or OnWrite previously + // returned NetworkFilterStatusStop. + ContinueReading() + + // Close closes the connection using the supplied close behavior. + Close(closeType NetworkConnectionCloseType) + + // GetConnectionID returns the unique Envoy connection ID. + GetConnectionID() uint64 + + // GetRemoteAddress returns the remote peer address and port as seen by Envoy. + GetRemoteAddress() (UnsafeEnvoyBuffer, uint32, bool) + + // GetLocalAddress returns the local address and port for the connection. + GetLocalAddress() (UnsafeEnvoyBuffer, uint32, bool) + + // IsSSL reports whether the connection is using SSL/TLS. + IsSSL() bool + + // DisableClose disables or re-enables close handling for this filter instance. + DisableClose(disabled bool) + + // CloseWithDetails closes the connection and records termination details that Envoy can surface + // in logs and diagnostics. + CloseWithDetails(closeType NetworkConnectionCloseType, details string) + + // GetRequestedServerName returns the requested server name (SNI) from the TLS handshake. + GetRequestedServerName() (UnsafeEnvoyBuffer, bool) + + // GetDirectRemoteAddress returns the direct remote address and port without proxy/XFF handling. + GetDirectRemoteAddress() (UnsafeEnvoyBuffer, uint32, bool) + + // GetSSLURISANs returns the peer certificate URI SAN entries. + GetSSLURISANs() []UnsafeEnvoyBuffer + + // GetSSLDNSSANs returns the peer certificate DNS SAN entries. + GetSSLDNSSANs() []UnsafeEnvoyBuffer + + // GetSSLSubject returns the peer certificate subject. + GetSSLSubject() (UnsafeEnvoyBuffer, bool) + + // SetFilterState stores a raw bytes filter state value under key. + // + // Other filters can read the value, and built-in filters may use it for routing decisions. + SetFilterState(key string, value []byte) bool + + // GetFilterState returns a raw bytes filter state value previously stored under key. + GetFilterState(key string) (UnsafeEnvoyBuffer, bool) + + // SetFilterStateTyped stores a typed filter state value using Envoy's registered ObjectFactory + // for key. + SetFilterStateTyped(key string, value []byte) bool + + // GetFilterStateTyped returns the serialized bytes of the typed filter state object stored under + // key. + GetFilterStateTyped(key string) (UnsafeEnvoyBuffer, bool) + + // GetMetadataString returns a dynamic metadata string value for metadataNamespace/key. + GetMetadataString(metadataNamespace, key string) (UnsafeEnvoyBuffer, bool) + + // GetMetadataNumber returns a dynamic metadata number value for metadataNamespace/key. + GetMetadataNumber(metadataNamespace, key string) (float64, bool) + + // GetMetadataBool returns a dynamic metadata bool value for metadataNamespace/key. + GetMetadataBool(metadataNamespace, key string) (bool, bool) + + // SetMetadata stores a string, number, or bool dynamic metadata value at + // metadataNamespace/key, replacing any existing value. + SetMetadata(metadataNamespace, key string, value any) + + // SetSocketOptionInt stores an integer socket option to be applied at the specified socket + // state. + SetSocketOptionInt(level, name int64, state SocketOptionState, value int64) + + // SetSocketOptionBytes stores a bytes socket option to be applied at the specified socket state. + SetSocketOptionBytes(level, name int64, state SocketOptionState, value []byte) + + // GetSocketOptionInt returns an integer socket option value if one is present for the supplied + // level/name/state tuple. + GetSocketOptionInt(level, name int64, state SocketOptionState) (int64, bool) + + // GetSocketOptionBytes returns a bytes socket option value if one is present for the supplied + // level/name/state tuple. + GetSocketOptionBytes(level, name int64, state SocketOptionState) (UnsafeEnvoyBuffer, bool) + + // GetSocketOptions returns all socket options currently stored on the connection. + GetSocketOptions() []SocketOption + + // Log writes a formatted message through Envoy's logging subsystem. + Log(level LogLevel, format string, args ...any) + + // HttpCallout performs an HTTP call to an external service. The call is asynchronous, and the + // response will be delivered via the provided callback. + // + // NOTE: This method should only be called during network filter callbacks or scheduled functions + // via the Scheduler so that it runs on the filter's worker thread. + HttpCallout(cluster string, headers [][2]string, body []byte, timeoutMs uint64, + cb HttpCalloutCallback) (HttpCalloutInitResult, uint64) + + // RecordHistogramValue records value in a histogram metric previously defined from the config + // handle. + RecordHistogramValue(id MetricID, value uint64) MetricsResult + + // SetGaugeValue sets a gauge metric to value. + SetGaugeValue(id MetricID, value uint64) MetricsResult + + // IncrementGaugeValue increases a gauge metric by value. + IncrementGaugeValue(id MetricID, value uint64) MetricsResult + + // DecrementGaugeValue decreases a gauge metric by value. + DecrementGaugeValue(id MetricID, value uint64) MetricsResult + + // IncrementCounterValue increases a counter metric by value. + IncrementCounterValue(id MetricID, value uint64) MetricsResult + + // GetClusterHostCounts returns the total, healthy, and degraded host counts for the named + // cluster and priority. + GetClusterHostCounts(cluster string, priority uint32) (ClusterHostCounts, bool) + + // GetUpstreamHostAddress returns the currently selected upstream host address and port. + GetUpstreamHostAddress() (UnsafeEnvoyBuffer, uint32, bool) + + // GetUpstreamHostHostname returns the currently selected upstream host hostname. + GetUpstreamHostHostname() (UnsafeEnvoyBuffer, bool) + + // GetUpstreamHostCluster returns the cluster name of the currently selected upstream host. + GetUpstreamHostCluster() (UnsafeEnvoyBuffer, bool) + + // HasUpstreamHost reports whether Envoy has already selected an upstream host for this + // connection. + HasUpstreamHost() bool + + // StartUpstreamSecureTransport upgrades the upstream connection into secure mode when the + // transport socket supports StartTLS-style promotion. + StartUpstreamSecureTransport() bool + + // GetConnectionState returns the current connection state. + GetConnectionState() NetworkConnectionState + + // ReadDisable disables or re-enables reading on the connection. + // + // Envoy reference-counts these calls; callers must balance disables with enables. + ReadDisable(disable bool) NetworkReadDisableStatus + + // ReadEnabled reports whether reading is currently enabled. + ReadEnabled() bool + + // IsHalfCloseEnabled reports whether remote half-close leaves the connection writable. + IsHalfCloseEnabled() bool + + // EnableHalfClose enables or disables half-close semantics on the connection. + EnableHalfClose(enabled bool) + + // GetBufferLimit returns the current soft buffer limit in bytes. + GetBufferLimit() uint32 + + // SetBufferLimits updates the soft buffer limit in bytes. + // + // On the write side, this controls high/low watermark transitions surfaced through the + // watermark callbacks. + SetBufferLimits(limit uint32) + + // AboveHighWatermark reports whether the write buffer is currently above the high watermark. + AboveHighWatermark() bool + + // GetScheduler retrieves the scheduler related to this network filter for asynchronous + // operations. + // + // NOTE: This MUST only be called during network filter callbacks. But then the returned + // Scheduler can be used later even outside of the callbacks and at other threads. + GetScheduler() Scheduler + + // GetWorkerIndex returns the worker index assigned to the current filter instance. + GetWorkerIndex() uint32 +} + +type NetworkFilterConfigHandle interface { + // Log writes a formatted message through Envoy's logging subsystem. + Log(level LogLevel, format string, args ...any) + + // DefineHistogram creates a histogram metric during configuration initialization and returns its + // opaque ID. + DefineHistogram(name string) (MetricID, MetricsResult) + + // DefineGauge creates a gauge metric during configuration initialization and returns its opaque + // ID. + DefineGauge(name string) (MetricID, MetricsResult) + + // DefineCounter creates a counter metric during configuration initialization and returns its + // opaque ID. + DefineCounter(name string) (MetricID, MetricsResult) + + // GetScheduler retrieves a scheduler for deferred task execution in the config context. + // This should be called only during the plugin configuration phase, and the returned + // Scheduler can be used later even outside of the callbacks and at other threads. + GetScheduler() Scheduler +} diff --git a/test/extensions/dynamic_modules/network/BUILD b/test/extensions/dynamic_modules/network/BUILD index b49368d532e7c..f7a405593dc0e 100644 --- a/test/extensions/dynamic_modules/network/BUILD +++ b/test/extensions/dynamic_modules/network/BUILD @@ -76,11 +76,15 @@ envoy_cc_test( srcs = ["integration_test.cc"], data = [ "//test/extensions/dynamic_modules/test_data/c:network_no_op", + "//test/extensions/dynamic_modules/test_data/cpp:network_integration_test", + "//test/extensions/dynamic_modules/test_data/go:network_integration_test", "//test/extensions/dynamic_modules/test_data/rust:network_integration_test", ], + env = {"GODEBUG": "cgocheck=0"}, rbe_pool = "6gig", deps = [ "//source/extensions/dynamic_modules:abi_impl", + "//source/extensions/filters/http/dynamic_modules:abi_impl", "//source/extensions/filters/network/dynamic_modules:config", "//source/extensions/filters/network/tcp_proxy:config", "//test/integration:integration_lib", diff --git a/test/extensions/dynamic_modules/network/integration_test.cc b/test/extensions/dynamic_modules/network/integration_test.cc index 874594727c105..47dbd4527f9fe 100644 --- a/test/extensions/dynamic_modules/network/integration_test.cc +++ b/test/extensions/dynamic_modules/network/integration_test.cc @@ -6,16 +6,15 @@ namespace Envoy { -class DynamicModulesNetworkIntegrationTest - : public testing::TestWithParam, - public BaseIntegrationTest { +class DynamicModulesNetworkIntegrationTestBase : public BaseIntegrationTest { public: - DynamicModulesNetworkIntegrationTest() - : BaseIntegrationTest(GetParam(), ConfigHelper::tcpProxyConfig()) { + explicit DynamicModulesNetworkIntegrationTestBase(Network::Address::IpVersion version) + : BaseIntegrationTest(version, ConfigHelper::tcpProxyConfig()) { skip_tag_extraction_rule_check_ = true; enableHalfClose(true); } +protected: void initializeFilter(const std::string& filter_name, const std::string& module_name, const std::string& search_path, const std::string& config = "") { TestEnvironment::setEnvVar("ENVOY_DYNAMIC_MODULES_SEARCH_PATH", @@ -51,16 +50,18 @@ class DynamicModulesNetworkIntegrationTest BaseIntegrationTest::initialize(); } +}; + +class DynamicModulesNetworkIntegrationTest + : public testing::TestWithParam, + public DynamicModulesNetworkIntegrationTestBase { +public: + DynamicModulesNetworkIntegrationTest() : DynamicModulesNetworkIntegrationTestBase(GetParam()) {} void initializeCFilter(const std::string& filter_name, const std::string& config = "") { initializeFilter(filter_name, "network_no_op", "{{ test_rundir }}/test/extensions/dynamic_modules/test_data/c", config); } - - void initializeRustFilter(const std::string& filter_name, const std::string& config = "") { - initializeFilter(filter_name, "network_integration_test", - "{{ test_rundir }}/test/extensions/dynamic_modules/test_data/rust", config); - } }; INSTANTIATE_TEST_SUITE_P(IpVersions, DynamicModulesNetworkIntegrationTest, @@ -134,11 +135,35 @@ TEST_P(DynamicModulesNetworkIntegrationTest, HalfClose) { tcp_client->close(); } -// Verifies that read_disable/read_enabled work correctly through the Rust SDK. -// The Rust filter disables and re-enables reads during on_read, asserting correct -// state transitions. If any assertion fails, the filter panics and the connection aborts. -TEST_P(DynamicModulesNetworkIntegrationTest, FlowControl) { - initializeRustFilter("flow_control"); +class DynamicModulesNetworkSdkIntegrationTest : public testing::TestWithParam, + public DynamicModulesNetworkIntegrationTestBase { +public: + DynamicModulesNetworkSdkIntegrationTest() + : DynamicModulesNetworkIntegrationTestBase(GetParam() == "rust" + ? Envoy::Network::Address::IpVersion::v4 + : Envoy::Network::Address::IpVersion::v6) {} + + void initializeSdkFilter(const std::string& filter_name, const std::string& config = "") { + initializeFilter(filter_name, "network_integration_test", + "{{ test_rundir }}/test/extensions/dynamic_modules/test_data/" + GetParam(), + config); + } +}; + +#ifndef __SANITIZE_ADDRESS__ +auto DynamicModulesNetworkSdkIntegrationTestValues = testing::Values("rust", "go", "cpp"); +#else +auto DynamicModulesNetworkSdkIntegrationTestValues = testing::Values("rust", "go"); +#endif + +INSTANTIATE_TEST_SUITE_P(SdkLanguages, DynamicModulesNetworkSdkIntegrationTest, + DynamicModulesNetworkSdkIntegrationTestValues, + [](const testing::TestParamInfo& info) { + return info.param; + }); + +TEST_P(DynamicModulesNetworkSdkIntegrationTest, FlowControl) { + initializeSdkFilter("flow_control"); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); ASSERT_TRUE(tcp_client->connected()); @@ -162,11 +187,8 @@ TEST_P(DynamicModulesNetworkIntegrationTest, FlowControl) { tcp_client->close(); } -// Verifies that get_connection_state returns the correct state during data processing. -// The Rust filter asserts that the connection is Open during on_new_connection, on_read, -// and on_write callbacks. -TEST_P(DynamicModulesNetworkIntegrationTest, ConnectionState) { - initializeRustFilter("connection_state"); +TEST_P(DynamicModulesNetworkSdkIntegrationTest, ConnectionState) { + initializeSdkFilter("connection_state"); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); ASSERT_TRUE(tcp_client->connected()); @@ -190,10 +212,8 @@ TEST_P(DynamicModulesNetworkIntegrationTest, ConnectionState) { tcp_client->close(); } -// Verifies that enable_half_close/is_half_close_enabled work correctly through the Rust SDK. -// The Rust filter verifies half-close state and toggles it during on_read. -TEST_P(DynamicModulesNetworkIntegrationTest, HalfCloseControl) { - initializeRustFilter("half_close"); +TEST_P(DynamicModulesNetworkSdkIntegrationTest, HalfCloseControl) { + initializeSdkFilter("half_close"); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); ASSERT_TRUE(tcp_client->connected()); @@ -213,10 +233,8 @@ TEST_P(DynamicModulesNetworkIntegrationTest, HalfCloseControl) { tcp_client->close(); } -// Verifies that get_buffer_limit/set_buffer_limits work correctly through the Rust SDK. -// The Rust filter reads the initial buffer limit, sets a new one, and asserts the change. -TEST_P(DynamicModulesNetworkIntegrationTest, BufferLimits) { - initializeRustFilter("buffer_limits"); +TEST_P(DynamicModulesNetworkSdkIntegrationTest, BufferLimits) { + initializeSdkFilter("buffer_limits"); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); ASSERT_TRUE(tcp_client->connected()); diff --git a/test/extensions/dynamic_modules/test_data/cpp/BUILD b/test/extensions/dynamic_modules/test_data/cpp/BUILD index b6a98ad0796c3..7ee61a4fba18a 100644 --- a/test/extensions/dynamic_modules/test_data/cpp/BUILD +++ b/test/extensions/dynamic_modules/test_data/cpp/BUILD @@ -5,3 +5,5 @@ licenses(["notice"]) # Apache 2 test_program(name = "http") test_program(name = "http_integration_test") + +test_program(name = "network_integration_test") diff --git a/test/extensions/dynamic_modules/test_data/cpp/network_integration_test.cc b/test/extensions/dynamic_modules/test_data/cpp/network_integration_test.cc new file mode 100644 index 0000000000000..000cbef832ce6 --- /dev/null +++ b/test/extensions/dynamic_modules/test_data/cpp/network_integration_test.cc @@ -0,0 +1,195 @@ +#include + +#include "source/extensions/dynamic_modules/sdk/cpp/sdk_network.h" + +namespace Envoy { +namespace DynamicModules { + +class FlowControlFilter : public NetworkFilter { +public: + explicit FlowControlFilter(NetworkFilterHandle& handle) : handle_(handle) {} + + NetworkFilterStatus onNewConnection() override { return NetworkFilterStatus::Continue; } + + NetworkFilterStatus onRead(NetworkBuffer&, bool) override { + if (!reads_disabled_) { + const auto disable_status = handle_.readDisable(true); + assert(disable_status == NetworkReadDisableStatus::TransitionedToReadDisabled); + assert(!handle_.readEnabled()); + reads_disabled_ = true; + + const auto enable_status = handle_.readDisable(false); + assert(enable_status == NetworkReadDisableStatus::TransitionedToReadEnabled); + assert(handle_.readEnabled()); + } + return NetworkFilterStatus::Continue; + } + + NetworkFilterStatus onWrite(NetworkBuffer&, bool) override { + return NetworkFilterStatus::Continue; + } + + void onEvent(NetworkConnectionEvent) override {} + void onDestroy() override {} + +private: + NetworkFilterHandle& handle_; + bool reads_disabled_{false}; +}; + +class FlowControlFactory : public NetworkFilterFactory { +public: + std::unique_ptr create(NetworkFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class FlowControlConfigFactory : public NetworkFilterConfigFactory { +public: + std::unique_ptr create(NetworkFilterConfigHandle&, + std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_NETWORK_FILTER_CONFIG_FACTORY(FlowControlConfigFactory, "flow_control"); + +class ConnectionStateFilter : public NetworkFilter { +public: + explicit ConnectionStateFilter(NetworkFilterHandle& handle) : handle_(handle) {} + + NetworkFilterStatus onNewConnection() override { + assert(handle_.getConnectionState() == NetworkConnectionState::Open); + return NetworkFilterStatus::Continue; + } + + NetworkFilterStatus onRead(NetworkBuffer&, bool) override { + assert(handle_.getConnectionState() == NetworkConnectionState::Open); + return NetworkFilterStatus::Continue; + } + + NetworkFilterStatus onWrite(NetworkBuffer&, bool) override { + assert(handle_.getConnectionState() == NetworkConnectionState::Open); + return NetworkFilterStatus::Continue; + } + + void onEvent(NetworkConnectionEvent) override {} + void onDestroy() override {} + +private: + NetworkFilterHandle& handle_; +}; + +class ConnectionStateFactory : public NetworkFilterFactory { +public: + std::unique_ptr create(NetworkFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class ConnectionStateConfigFactory : public NetworkFilterConfigFactory { +public: + std::unique_ptr create(NetworkFilterConfigHandle&, + std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_NETWORK_FILTER_CONFIG_FACTORY(ConnectionStateConfigFactory, "connection_state"); + +class HalfCloseFilter : public NetworkFilter { +public: + explicit HalfCloseFilter(NetworkFilterHandle& handle) : handle_(handle) {} + + NetworkFilterStatus onNewConnection() override { + assert(handle_.isHalfCloseEnabled()); + return NetworkFilterStatus::Continue; + } + + NetworkFilterStatus onRead(NetworkBuffer&, bool) override { + handle_.enableHalfClose(false); + assert(!handle_.isHalfCloseEnabled()); + handle_.enableHalfClose(true); + assert(handle_.isHalfCloseEnabled()); + return NetworkFilterStatus::Continue; + } + + NetworkFilterStatus onWrite(NetworkBuffer&, bool) override { + return NetworkFilterStatus::Continue; + } + + void onEvent(NetworkConnectionEvent) override {} + void onDestroy() override {} + +private: + NetworkFilterHandle& handle_; +}; + +class HalfCloseFactory : public NetworkFilterFactory { +public: + std::unique_ptr create(NetworkFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class HalfCloseConfigFactory : public NetworkFilterConfigFactory { +public: + std::unique_ptr create(NetworkFilterConfigHandle&, + std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_NETWORK_FILTER_CONFIG_FACTORY(HalfCloseConfigFactory, "half_close"); + +class BufferLimitsFilter : public NetworkFilter { +public: + explicit BufferLimitsFilter(NetworkFilterHandle& handle) : handle_(handle) {} + + NetworkFilterStatus onNewConnection() override { + initial_buffer_limit_ = handle_.getBufferLimit(); + handle_.setBufferLimits(32768); + assert(handle_.getBufferLimit() == 32768); + return NetworkFilterStatus::Continue; + } + + NetworkFilterStatus onRead(NetworkBuffer&, bool) override { + return NetworkFilterStatus::Continue; + } + + NetworkFilterStatus onWrite(NetworkBuffer&, bool) override { + return NetworkFilterStatus::Continue; + } + + void onEvent(NetworkConnectionEvent) override {} + void onDestroy() override {} + + void onAboveWriteBufferHighWatermark() override { above_high_watermark_called_ = true; } + void onBelowWriteBufferLowWatermark() override { below_low_watermark_called_ = true; } + +private: + NetworkFilterHandle& handle_; + inline static bool above_high_watermark_called_{false}; + inline static bool below_low_watermark_called_{false}; + inline static uint32_t initial_buffer_limit_{0}; +}; + +class BufferLimitsFactory : public NetworkFilterFactory { +public: + std::unique_ptr create(NetworkFilterHandle& handle) override { + return std::make_unique(handle); + } +}; + +class BufferLimitsConfigFactory : public NetworkFilterConfigFactory { +public: + std::unique_ptr create(NetworkFilterConfigHandle&, + std::string_view) override { + return std::make_unique(); + } +}; + +REGISTER_NETWORK_FILTER_CONFIG_FACTORY(BufferLimitsConfigFactory, "buffer_limits"); + +} // namespace DynamicModules +} // namespace Envoy diff --git a/test/extensions/dynamic_modules/test_data/go/BUILD b/test/extensions/dynamic_modules/test_data/go/BUILD index cd5c391939137..20415b8a5fe9c 100644 --- a/test/extensions/dynamic_modules/test_data/go/BUILD +++ b/test/extensions/dynamic_modules/test_data/go/BUILD @@ -5,3 +5,5 @@ licenses(["notice"]) # Apache 2 test_program(name = "http") test_program(name = "http_integration_test") + +test_program(name = "network_integration_test") diff --git a/test/extensions/dynamic_modules/test_data/go/network_integration_test/network_integration_test.go b/test/extensions/dynamic_modules/test_data/go/network_integration_test/network_integration_test.go new file mode 100644 index 0000000000000..58ed5dfba45c2 --- /dev/null +++ b/test/extensions/dynamic_modules/test_data/go/network_integration_test/network_integration_test.go @@ -0,0 +1,172 @@ +package main + +import ( + sdk "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go" + _ "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/abi" + "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/shared" +) + +func init() { + sdk.RegisterNetworkFilterConfigFactories(map[string]shared.NetworkFilterConfigFactory{ + "flow_control": &flowControlConfigFactory{}, + "connection_state": &connectionStateConfigFactory{}, + "half_close": &halfCloseConfigFactory{}, + "buffer_limits": &bufferLimitsConfigFactory{}, + }) +} + +func main() {} + +type flowControlConfigFactory struct { + shared.EmptyNetworkFilterConfigFactory +} + +func (f *flowControlConfigFactory) Create(shared.NetworkFilterConfigHandle, []byte) (shared.NetworkFilterFactory, error) { + return &flowControlFactory{}, nil +} + +type flowControlFactory struct { + shared.EmptyNetworkFilterFactory +} + +func (f *flowControlFactory) Create(handle shared.NetworkFilterHandle) shared.NetworkFilter { + return &flowControlFilter{handle: handle} +} + +type flowControlFilter struct { + handle shared.NetworkFilterHandle + readsDisabled bool + shared.EmptyNetworkFilter +} + +func (f *flowControlFilter) OnRead(shared.NetworkBuffer, bool) shared.NetworkFilterStatus { + if !f.readsDisabled { + if status := f.handle.ReadDisable(true); status != shared.NetworkReadDisableStatusTransitionedToReadDisabled { + panic("unexpected disable status") + } + if f.handle.ReadEnabled() { + panic("expected reads to be disabled") + } + f.readsDisabled = true + + if status := f.handle.ReadDisable(false); status != shared.NetworkReadDisableStatusTransitionedToReadEnabled { + panic("unexpected enable status") + } + if !f.handle.ReadEnabled() { + panic("expected reads to be enabled") + } + } + return shared.NetworkFilterStatusContinue +} + +type connectionStateConfigFactory struct { + shared.EmptyNetworkFilterConfigFactory +} + +func (f *connectionStateConfigFactory) Create(shared.NetworkFilterConfigHandle, []byte) (shared.NetworkFilterFactory, error) { + return &connectionStateFactory{}, nil +} + +type connectionStateFactory struct { + shared.EmptyNetworkFilterFactory +} + +func (f *connectionStateFactory) Create(handle shared.NetworkFilterHandle) shared.NetworkFilter { + return &connectionStateFilter{handle: handle} +} + +type connectionStateFilter struct { + handle shared.NetworkFilterHandle + shared.EmptyNetworkFilter +} + +func (f *connectionStateFilter) OnNewConnection() shared.NetworkFilterStatus { + if f.handle.GetConnectionState() != shared.NetworkConnectionStateOpen { + panic("expected open state on new connection") + } + return shared.NetworkFilterStatusContinue +} + +func (f *connectionStateFilter) OnRead(shared.NetworkBuffer, bool) shared.NetworkFilterStatus { + if f.handle.GetConnectionState() != shared.NetworkConnectionStateOpen { + panic("expected open state on read") + } + return shared.NetworkFilterStatusContinue +} + +func (f *connectionStateFilter) OnWrite(shared.NetworkBuffer, bool) shared.NetworkFilterStatus { + if f.handle.GetConnectionState() != shared.NetworkConnectionStateOpen { + panic("expected open state on write") + } + return shared.NetworkFilterStatusContinue +} + +type halfCloseConfigFactory struct { + shared.EmptyNetworkFilterConfigFactory +} + +func (f *halfCloseConfigFactory) Create(shared.NetworkFilterConfigHandle, []byte) (shared.NetworkFilterFactory, error) { + return &halfCloseFactory{}, nil +} + +type halfCloseFactory struct { + shared.EmptyNetworkFilterFactory +} + +func (f *halfCloseFactory) Create(handle shared.NetworkFilterHandle) shared.NetworkFilter { + return &halfCloseFilter{handle: handle} +} + +type halfCloseFilter struct { + handle shared.NetworkFilterHandle + shared.EmptyNetworkFilter +} + +func (f *halfCloseFilter) OnNewConnection() shared.NetworkFilterStatus { + if !f.handle.IsHalfCloseEnabled() { + panic("expected half-close to be enabled") + } + return shared.NetworkFilterStatusContinue +} + +func (f *halfCloseFilter) OnRead(shared.NetworkBuffer, bool) shared.NetworkFilterStatus { + f.handle.EnableHalfClose(false) + if f.handle.IsHalfCloseEnabled() { + panic("expected half-close to be disabled") + } + f.handle.EnableHalfClose(true) + if !f.handle.IsHalfCloseEnabled() { + panic("expected half-close to be re-enabled") + } + return shared.NetworkFilterStatusContinue +} + +type bufferLimitsConfigFactory struct { + shared.EmptyNetworkFilterConfigFactory +} + +func (f *bufferLimitsConfigFactory) Create(shared.NetworkFilterConfigHandle, []byte) (shared.NetworkFilterFactory, error) { + return &bufferLimitsFactory{}, nil +} + +type bufferLimitsFactory struct { + shared.EmptyNetworkFilterFactory +} + +func (f *bufferLimitsFactory) Create(handle shared.NetworkFilterHandle) shared.NetworkFilter { + return &bufferLimitsFilter{handle: handle} +} + +type bufferLimitsFilter struct { + handle shared.NetworkFilterHandle + shared.EmptyNetworkFilter +} + +func (f *bufferLimitsFilter) OnNewConnection() shared.NetworkFilterStatus { + _ = f.handle.GetBufferLimit() + f.handle.SetBufferLimits(32768) + if f.handle.GetBufferLimit() != 32768 { + panic("expected updated buffer limit") + } + return shared.NetworkFilterStatusContinue +} diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 536b8f2ae62fc..e00289f830a47 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -395,6 +395,7 @@ paths: - test/extensions/common/wasm/wasm_test.cc - test/extensions/dynamic_modules/test_data/cpp/http_integration_test.cc - test/extensions/dynamic_modules/test_data/cpp/http.cc + - test/extensions/dynamic_modules/test_data/cpp/network_integration_test.cc - test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc - test/test_common/wasm_base.h From 2c684f6fd7700865d89220b6899465de36768a8d Mon Sep 17 00:00:00 2001 From: aakugan <164153358+aakugan@users.noreply.github.com> Date: Wed, 29 Apr 2026 23:36:55 +0530 Subject: [PATCH 162/750] reverse_tunnels: Add EventReporter for reverse tunnel connection reporting (#44342) BUILDS ON #44327, depends on the protos introduced there. [Incremental DIFFS](https://github.com/envoyproxy/envoy/pull/44342/changes/0f8c02a02b6f25c01112f513ae4f22c72484994a) ## Commit Message Add EventReporter for reverse tunnel connection reporting ## Additional Description: Adds the EventReporter contrib extension, which aggregates reverse tunnel connection/disconnection events and fans them out to pluggable clients. Key components: - `ReverseTunnelReporterWithState` / `ReverseTunnelReporterClient`: interfaces allowing multiple transport clients to share connection state from a single reporter. Clients receive incremental diffs and can request a full state snapshot so they dont have to be "stateful" other than the protocol they manage. - `EventReporter`: A basic implementation which does refcounting of connections by the name and stores in a map for interfacing with the clients. Risk Level: Low, opt in extension. ## Testing Unit tests. Signed-off-by: aakugan --- CODEOWNERS | 1 + contrib/contrib_build_config.bzl | 6 + contrib/extensions_metadata.yaml | 8 + contrib/reverse_tunnel_reporter/source/BUILD | 35 ++ .../source/reporters/BUILD | 16 + .../source/reporters/event_reporter/BUILD | 30 + .../reporters/event_reporter/factory.cc | 58 ++ .../source/reporters/event_reporter/factory.h | 37 ++ .../reporters/event_reporter/reporter.cc | 126 ++++ .../reporters/event_reporter/reporter.h | 66 ++ .../source/reverse_tunnel_event_types.h | 112 ++++ .../test/reporters/BUILD | 24 + .../test/reporters/event_reporter_test.cc | 567 ++++++++++++++++++ 13 files changed, 1086 insertions(+) create mode 100644 contrib/reverse_tunnel_reporter/source/BUILD create mode 100644 contrib/reverse_tunnel_reporter/source/reporters/BUILD create mode 100644 contrib/reverse_tunnel_reporter/source/reporters/event_reporter/BUILD create mode 100644 contrib/reverse_tunnel_reporter/source/reporters/event_reporter/factory.cc create mode 100644 contrib/reverse_tunnel_reporter/source/reporters/event_reporter/factory.h create mode 100644 contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.cc create mode 100644 contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.h create mode 100644 contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h create mode 100644 contrib/reverse_tunnel_reporter/test/reporters/BUILD create mode 100644 contrib/reverse_tunnel_reporter/test/reporters/event_reporter_test.cc diff --git a/CODEOWNERS b/CODEOWNERS index 8fe9e2f4cc409..cf54916b308a6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -497,5 +497,6 @@ extensions/upstreams/tcp @ggreenway @mattklein123 /contrib/peak_ewma/load_balancing_policies/ @rroblak @UNOWNED /contrib/kae/ @Misakokoro @UNOWNED /contrib/istio @kyessenov @wbpcode @keithmattix @krinkinmu @zirain +/contrib/reverse_tunnel_reporter @agrawroh @aakugan @basundhara-c /compat/openssl/ @tedjpoole @envoyproxy/envoy-openssl-sync diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index 64c225c39cd03..e47ef5f12dbdc 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -119,4 +119,10 @@ CONTRIB_EXTENSIONS = { # "envoy.upstreams.http.tcp.golang": "//contrib/golang/upstreams/http/tcp/source:config", + + # + # Reverse tunnel reporters + # + + "envoy.bootstrap.reverse_tunnel.reverse_tunnel_reporting_service": "//contrib/reverse_tunnel_reporter/source:config", } diff --git a/contrib/extensions_metadata.yaml b/contrib/extensions_metadata.yaml index 6897c83357226..33bea6794b353 100644 --- a/contrib/extensions_metadata.yaml +++ b/contrib/extensions_metadata.yaml @@ -212,3 +212,11 @@ envoy.load_balancing_policies.peak_ewma: status: alpha type_urls: - envoy.extensions.load_balancing_policies.peak_ewma.v3alpha.PeakEwma +envoy.bootstrap.reverse_tunnel.reverse_tunnel_reporting_service: + categories: + - envoy.bootstrap + security_posture: requires_trusted_downstream_and_upstream + status: alpha + type_urls: + - envoy.extensions.reverse_tunnel_reporters.v3alpha.reporters.EventReporterConfig + - envoy.extensions.reverse_tunnel_reporters.v3alpha.clients.grpc_client.GrpcClientConfig diff --git a/contrib/reverse_tunnel_reporter/source/BUILD b/contrib/reverse_tunnel_reporter/source/BUILD new file mode 100644 index 0000000000000..c952f5c659e33 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/BUILD @@ -0,0 +1,35 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "reverse_tunnel_event_types", + hdrs = [ + "reverse_tunnel_event_types.h", + ], + deps = [ + "//envoy/common:pure_lib", + "//envoy/common:time_interface", + "//envoy/config:typed_config_interface", + "//envoy/extensions/bootstrap/reverse_tunnel:reverse_tunnel_reporter_lib", + "//envoy/server:factory_context_interface", + "//source/common/common:fmt_lib", + "//source/common/config:utility_lib", + "//source/common/protobuf", + "//source/common/protobuf:message_validator_lib", + ], +) + +envoy_cc_contrib_extension( + name = "config", + deps = [ + "//contrib/reverse_tunnel_reporter/source/reporters:reporters_lib", + ], +) diff --git a/contrib/reverse_tunnel_reporter/source/reporters/BUILD b/contrib/reverse_tunnel_reporter/source/reporters/BUILD new file mode 100644 index 0000000000000..399bb153885c2 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/reporters/BUILD @@ -0,0 +1,16 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "reporters_lib", + deps = [ + "//contrib/reverse_tunnel_reporter/source/reporters/event_reporter:event_reporter_lib", + ], +) diff --git a/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/BUILD b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/BUILD new file mode 100644 index 0000000000000..ace33870375d9 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/BUILD @@ -0,0 +1,30 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "event_reporter_lib", + srcs = [ + "factory.cc", + "reporter.cc", + ], + hdrs = [ + "factory.h", + "reporter.h", + ], + deps = [ + "//contrib/reverse_tunnel_reporter/source:reverse_tunnel_event_types", + "//envoy/extensions/bootstrap/reverse_tunnel:reverse_tunnel_reporter_lib", + "//envoy/registry", + "//source/common/common:logger_lib", + "//source/common/config:utility_lib", + "//source/common/protobuf:utility_lib", + "@envoy_api//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters:pkg_cc_proto", + ], +) diff --git a/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/factory.cc b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/factory.cc new file mode 100644 index 0000000000000..d4f86fe296d64 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/factory.cc @@ -0,0 +1,58 @@ +#include "contrib/reverse_tunnel_reporter/source/reporters/event_reporter/factory.h" + +#include "envoy/registry/registry.h" + +#include "source/common/config/utility.h" +#include "source/common/protobuf/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +ReverseTunnelReporterPtr +EventReporterFactory::createReporter(Server::Configuration::ServerFactoryContext& context, + ProtobufTypes::MessagePtr config) { + const auto& reporter_config = MessageUtil::downcastAndValidate( + *config, context.messageValidationVisitor()); + + std::vector clients; + clients.reserve(reporter_config.clients().size()); + for (const auto& client_config : reporter_config.clients()) { + clients.push_back(createClient(context, client_config)); + } + return std::make_unique(context, reporter_config, std::move(clients)); +} + +std::string EventReporterFactory::name() const { + return "envoy.extensions.reverse_tunnel.reverse_tunnel_reporting_service.reporters.event_" + "reporter"; +} + +ProtobufTypes::MessagePtr EventReporterFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +ReverseTunnelReporterClientPtr +EventReporterFactory::createClient(Server::Configuration::ServerFactoryContext& context, + const ClientConfigProto& client_config) { + auto* factory = + Config::Utility::getFactoryByName(client_config.name()); + if (!factory) { + throw EnvoyException( + fmt::format("Unknown Reporter Client Factory: '{}'. " + "Make sure it is registered as a ReverseTunnelReporterClientFactory.", + client_config.name())); + } + + auto typed_config = Config::Utility::translateAnyToFactoryConfig( + client_config.typed_config(), context.messageValidationVisitor(), *factory); + return factory->createClient(context, *typed_config); +} + +REGISTER_FACTORY(EventReporterFactory, ReverseTunnelReporterFactory); + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/factory.h b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/factory.h new file mode 100644 index 0000000000000..14f272506c360 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/factory.h @@ -0,0 +1,37 @@ +#pragma once + +#include "envoy/extensions/bootstrap/reverse_tunnel/reverse_tunnel_reporter.h" + +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.pb.h" +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.pb.validate.h" +#include "contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.h" +#include "contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +/// Factory that builds an EventReporter from its proto config, dynamically +/// resolving each child ReverseTunnelReporterClient by name. +class EventReporterFactory : public ReverseTunnelReporterFactory { +public: + ReverseTunnelReporterPtr createReporter(Server::Configuration::ServerFactoryContext& context, + ProtobufTypes::MessagePtr config) override; + std::string name() const override; + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + +private: + using ConfigProto = + envoy::extensions::reverse_tunnel_reporters::v3alpha::reporters::EventReporterConfig; + using ClientConfigProto = envoy::extensions::reverse_tunnel_reporters::v3alpha::reporters:: + ReverseConnectionReporterClient; + + ReverseTunnelReporterClientPtr createClient(Server::Configuration::ServerFactoryContext& context, + const ClientConfigProto& client_config); +}; + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.cc b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.cc new file mode 100644 index 0000000000000..33b5530677fc5 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.cc @@ -0,0 +1,126 @@ +#include "contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.h" + +#include "source/common/protobuf/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +EventReporter::EventReporter(Server::Configuration::ServerFactoryContext& context, + const ConfigProto& config, + std::vector&& clients) + : context_{context}, clients_{std::move(clients)}, + stats_(generateStats( + PROTOBUF_GET_STRING_OR_DEFAULT(config, stat_prefix, "reverse_tunnel_reporter"), + context.scope())) { + ENVOY_LOG(info, "Constructed with {} clients", clients_.size()); +} + +void EventReporter::onServerInitialized() { + ENVOY_LOG(info, "Initialized"); + for (auto& client : clients_) { + client->onServerInitialized(this); + } +} + +void EventReporter::reportConnectionEvent(absl::string_view node_id, absl::string_view cluster_id, + absl::string_view tenant_id) { + auto ptr = std::make_shared( + ReverseTunnelEvent::Connected{std::string(node_id), std::string(cluster_id), + std::string(tenant_id), Envoy::SystemTime::clock::now()}); + + context_.mainThreadDispatcher().post( + [this, ptr = std::move(ptr)]() mutable { this->addConnection(std::move(ptr)); }); +} + +void EventReporter::reportDisconnectionEvent(absl::string_view node_id, absl::string_view) { + std::string name = ReverseTunnelEvent::getName(node_id); + auto ptr = std::make_shared(name); + + context_.mainThreadDispatcher().post( + [this, ptr = std::move(ptr)]() mutable { this->removeConnection(std::move(ptr)); }); +} + +// This is only served on the main thread so no locks needed. +ReverseTunnelEvent::ConnectionsList EventReporter::getAllConnections() { + ASSERT(context_.mainThreadDispatcher().isThreadSafe()); + stats_.reverse_tunnel_full_pulls_total_.inc(); + + ReverseTunnelEvent::ConnectionsList all_connections; + all_connections.reserve(connections_.size()); + + for (auto& [key, val] : connections_) { + all_connections.push_back(val.connection); + } + return all_connections; +} + +EventReporterStats EventReporter::generateStats(const std::string& prefix, Stats::Scope& scope) { + return EventReporterStats{ALL_EVENT_REPORTER_STATS(POOL_COUNTER_PREFIX(scope, prefix), + POOL_GAUGE_PREFIX(scope, prefix))}; +} + +void EventReporter::notifyClients(ReverseTunnelEvent::TunnelUpdates&& updates) { + ASSERT(clients_.size() > 0, "Need atleast one client. Enforced via the protos."); + + for (size_t i = 0; i < clients_.size() - 1; i++) { + clients_[i]->receiveEvents(updates); + } + + clients_.back()->receiveEvents(std::move(updates)); +} + +void EventReporter::addConnection(std::shared_ptr&& connection) { + ASSERT(context_.mainThreadDispatcher().isThreadSafe()); + + ENVOY_LOG(info, "Accepted a new connection. Node: {}, Cluster: {}, Tenant: {}", + connection->node_id, connection->cluster_id, connection->tenant_id); + + std::string name = ReverseTunnelEvent::getName(connection->node_id); + auto [it, inserted] = connections_.try_emplace(std::move(name), std::move(connection), 1); + + if (inserted) { + stats_.reverse_tunnel_unique_active_.inc(); + notifyClients(ReverseTunnelEvent::TunnelUpdates{{it->second.connection}, {}}); + } else { + // Multiple reverse tunnels can share the same name (same node). + // We ref-count them and only notify clients of removal when the last one disconnects. + it->second.count++; + } + + stats_.reverse_tunnel_established_total_.inc(); + stats_.reverse_tunnel_active_.inc(); +} + +void EventReporter::removeConnection( + std::shared_ptr&& disconnection) { + ASSERT(context_.mainThreadDispatcher().isThreadSafe()); + + const auto& name = disconnection->name; + auto it = connections_.find(name); + + ENVOY_LOG(info, "Removed connection. Name: {}", name); + + if (it == connections_.end()) { + ENVOY_LOG(warn, "Tried to remove a connection which doesnt exist"); + return; + } + + // Only notify removal on the last ref — see addConnection for the ref-count rationale. + if (it->second.count == 1) { + connections_.erase(it); + stats_.reverse_tunnel_unique_active_.dec(); + notifyClients(ReverseTunnelEvent::TunnelUpdates{{}, {disconnection}}); + } else { + it->second.count--; + } + + stats_.reverse_tunnel_closed_total_.inc(); + stats_.reverse_tunnel_active_.dec(); +} + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.h b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.h new file mode 100644 index 0000000000000..f37f491e04de8 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.h @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include "envoy/stats/stats_macros.h" + +#include "source/common/common/logger.h" + +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.pb.h" +#include "contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +#define ALL_EVENT_REPORTER_STATS(COUNTER, GAUGE) \ + COUNTER(reverse_tunnel_established_total) \ + COUNTER(reverse_tunnel_closed_total) \ + COUNTER(reverse_tunnel_full_pulls_total) \ + GAUGE(reverse_tunnel_active, Accumulate) \ + GAUGE(reverse_tunnel_unique_active, Accumulate) + +struct EventReporterStats { + ALL_EVENT_REPORTER_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) +}; + +struct ConnectionEntry { + std::shared_ptr connection; + std::size_t count; +}; + +/// Aggregates reverse-tunnel connection/disconnection events, de-duplicates by +/// name, maintains stats, and fans out batched diffs as shared ptrs to registered clients. +class EventReporter : public ReverseTunnelReporterWithState, + public Logger::Loggable { +public: + using ConfigProto = + envoy::extensions::reverse_tunnel_reporters::v3alpha::reporters::EventReporterConfig; + + EventReporter(Server::Configuration::ServerFactoryContext& context, const ConfigProto& config, + std::vector&& clients); + + void onServerInitialized() override; + void reportConnectionEvent(absl::string_view node_id, absl::string_view cluster_id, + absl::string_view tenant_id) override; + void reportDisconnectionEvent(absl::string_view node_id, absl::string_view cluster_id) override; + ReverseTunnelEvent::ConnectionsList getAllConnections() override; + +private: + static EventReporterStats generateStats(const std::string& prefix, Stats::Scope& scope); + void notifyClients(ReverseTunnelEvent::TunnelUpdates&& updates); + void addConnection(std::shared_ptr&& connection); + void removeConnection(std::shared_ptr&& disconnection); + + Server::Configuration::ServerFactoryContext& context_; + std::vector clients_; + EventReporterStats stats_; + // Keyed by getName(node_id). + absl::flat_hash_map connections_; +}; + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h b/contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h new file mode 100644 index 0000000000000..ae4df047082da --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/common/time.h" +#include "envoy/config/typed_config.h" +#include "envoy/extensions/bootstrap/reverse_tunnel/reverse_tunnel_reporter.h" +#include "envoy/server/factory_context.h" + +#include "source/common/common/fmt.h" + +#include "absl/strings/str_cat.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +// The namespace holding the structs for the reverse tunnel events +namespace ReverseTunnelEvent { +// getName builds the canonical connection name used as the de-duplication key in the reporter. +// Assumption: node_id is unique. +// When tenant isolation is enabled we get the node_id here as +// ReverseConnectionUtility::buildTenantScopedIdentifier(node_id) from the reverse_tunnel code which +// invokes this. +inline std::string getName(absl::string_view node_id) { return std::string(node_id); } + +struct Connected { + std::string node_id; + std::string cluster_id; + std::string tenant_id; + Envoy::SystemTime created_at; +}; + +struct Disconnected { + std::string name; + + explicit Disconnected(absl::string_view n) : name(n) {} +}; + +// Most of the events are just single connections and disconnections. +// The clients may need to buffer them up -> some extra stack space for the same. +using ConnectionsList = absl::InlinedVector, 4>; +using DisconnectionsList = absl::InlinedVector, 4>; + +struct TunnelUpdates { + ConnectionsList connections; + DisconnectionsList disconnections; + + std::size_t size() const { return connections.size() + disconnections.size(); } + + void operator+=(TunnelUpdates&& events) { + connections.insert(connections.end(), std::make_move_iterator(events.connections.begin()), + std::make_move_iterator(events.connections.end())); + + disconnections.insert(disconnections.end(), + std::make_move_iterator(events.disconnections.begin()), + std::make_move_iterator(events.disconnections.end())); + + events.connections.clear(); + events.disconnections.clear(); + } + + void clear() { + connections.clear(); + disconnections.clear(); + } +}; +} // namespace ReverseTunnelEvent + +// ReverseTunnelReporterWithState will own the clients and expose an api for them to get the full +// state. ReverseTunnelReporterWithState allows multiple clients to share data -> clients can focus +// on sending the data alone. +class ReverseTunnelReporterWithState : public ReverseTunnelReporter { +public: + virtual ~ReverseTunnelReporterWithState() = default; + + virtual ReverseTunnelEvent::ConnectionsList getAllConnections() PURE; +}; + +using ReverseTunnelReporterWithStatePtr = std::unique_ptr; + +// ReverseTunnelReporterClient gets the ptr to the reporter for polling all the active connections. +// ReverseTunnelReporterClient also has receiveEvents to get the diff events from the reporter. +class ReverseTunnelReporterClient { +public: + virtual ~ReverseTunnelReporterClient() = default; + + virtual void onServerInitialized(ReverseTunnelReporterWithState* reporter) PURE; + + virtual void receiveEvents(ReverseTunnelEvent::TunnelUpdates events) PURE; +}; + +using ReverseTunnelReporterClientPtr = std::unique_ptr; + +class ReverseTunnelReporterClientFactory : public Config::TypedFactory { +public: + virtual ReverseTunnelReporterClientPtr + createClient(Server::Configuration::ServerFactoryContext& context, + const Protobuf::Message& config) PURE; + + std::string category() const override { + return "envoy.extensions.reverse_tunnel.reverse_tunnel_reporting_service.clients"; + } +}; + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/reverse_tunnel_reporter/test/reporters/BUILD b/contrib/reverse_tunnel_reporter/test/reporters/BUILD new file mode 100644 index 0000000000000..991e168a0ae8e --- /dev/null +++ b/contrib/reverse_tunnel_reporter/test/reporters/BUILD @@ -0,0 +1,24 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_test( + name = "event_reporter_test", + srcs = ["event_reporter_test.cc"], + deps = [ + "//contrib/reverse_tunnel_reporter/source/reporters/event_reporter:event_reporter_lib", + "//source/common/api:api_lib", + "//source/common/config:utility_lib", + "//test/mocks:common_lib", + "//test/mocks/server:server_mocks", + "//test/test_common:registry_lib", + "//test/test_common:test_time_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/contrib/reverse_tunnel_reporter/test/reporters/event_reporter_test.cc b/contrib/reverse_tunnel_reporter/test/reporters/event_reporter_test.cc new file mode 100644 index 0000000000000..8365198eff70b --- /dev/null +++ b/contrib/reverse_tunnel_reporter/test/reporters/event_reporter_test.cc @@ -0,0 +1,567 @@ +#include "source/common/api/api_impl.h" + +#include "test/mocks/common.h" +#include "test/mocks/server/server_factory_context.h" +#include "test/test_common/registry.h" +#include "test/test_common/test_time.h" +#include "test/test_common/utility.h" + +#include "contrib/reverse_tunnel_reporter/source/reporters/event_reporter/factory.h" +#include "contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.h" +#include "contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +class MockReverseTunnelReporterClient : public ReverseTunnelReporterClient { +public: + MockReverseTunnelReporterClient() = default; + ~MockReverseTunnelReporterClient() override = default; + + MOCK_METHOD(void, onServerInitialized, (ReverseTunnelReporterWithState*), (override)); + MOCK_METHOD(void, receiveEvents, (ReverseTunnelEvent::TunnelUpdates), (override)); +}; + +class EventReporterTest : public testing::Test { +protected: + void SetUp() override { + api_ = Api::createApiForTest(); + dispatcher_ = api_->allocateDispatcher("test_thread"); + + ON_CALL(context_, mainThreadDispatcher()).WillByDefault(ReturnRef(*dispatcher_)); + ON_CALL(context_, scope()).WillByDefault(ReturnRef(*stats_store_.rootScope())); + ON_CALL(context_, messageValidationVisitor()) + .WillByDefault(ReturnRef(ProtobufMessage::getStrictValidationVisitor())); + + auto mock_client1 = std::make_unique>(); + auto mock_client2 = std::make_unique>(); + mock_client1_ = mock_client1.get(); + mock_client2_ = mock_client2.get(); + + std::vector clients; + clients.push_back(std::move(mock_client1)); + clients.push_back(std::move(mock_client2)); + + EventReporter::ConfigProto config; + config.set_stat_prefix("test_prefix"); + + reporter_ = std::make_unique(context_, config, std::move(clients)); + } + + void createTestConnection(const std::string& node_id, const std::string& cluster_id, + const std::string& tenant_id = "tenant1") { + reporter_->reportConnectionEvent(node_id, cluster_id, tenant_id); + } + + void createTestDisconnection(const std::string& node_id, const std::string& cluster_id) { + reporter_->reportDisconnectionEvent(node_id, cluster_id); + } + + void runDispatcher() { dispatcher_->run(Event::Dispatcher::RunType::NonBlock); } + + uint64_t getCounterValue(const std::string& name) { + return stats_store_.counterFromString("test_prefix." + name).value(); + } + + uint64_t getGaugeValue(const std::string& name) { + return stats_store_.gaugeFromString("test_prefix." + name, Stats::Gauge::ImportMode::Accumulate) + .value(); + } + + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + NiceMock context_; + Stats::TestUtil::TestStore stats_store_; + NiceMock* mock_client1_; + NiceMock* mock_client2_; + std::unique_ptr reporter_; +}; + +TEST_F(EventReporterTest, AddRemoveConnections) { + EXPECT_CALL(*mock_client1_, receiveEvents(_)) + .Times(4) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(1, updates.connections.size()); + EXPECT_EQ(0, updates.disconnections.size()); + EXPECT_EQ("node1", updates.connections[0]->node_id); + })) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(1, updates.connections.size()); + EXPECT_EQ(0, updates.disconnections.size()); + EXPECT_EQ("node2", updates.connections[0]->node_id); + })) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(0, updates.connections.size()); + EXPECT_EQ(1, updates.disconnections.size()); + EXPECT_EQ(ReverseTunnelEvent::getName("node1"), updates.disconnections[0]->name); + })) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(0, updates.connections.size()); + EXPECT_EQ(1, updates.disconnections.size()); + EXPECT_EQ(ReverseTunnelEvent::getName("node2"), updates.disconnections[0]->name); + })); + + EXPECT_CALL(*mock_client2_, receiveEvents(_)).Times(4); + + createTestConnection("node1", "cluster1"); + runDispatcher(); + + EXPECT_EQ(1, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_unique_active")); + + createTestConnection("node2", "cluster2"); + runDispatcher(); + + EXPECT_EQ(2, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_unique_active")); + + auto connections = reporter_->getAllConnections(); + EXPECT_EQ(2, connections.size()); + EXPECT_EQ(1, getCounterValue("reverse_tunnel_full_pulls_total")); + + connections = reporter_->getAllConnections(); + EXPECT_EQ(2, connections.size()); + EXPECT_EQ(2, getCounterValue("reverse_tunnel_full_pulls_total")); + + createTestDisconnection("node1", "cluster1"); + runDispatcher(); + + EXPECT_EQ(1, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_unique_active")); + + createTestDisconnection("node2", "cluster2"); + runDispatcher(); + + EXPECT_EQ(2, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(0, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(0, getGaugeValue("reverse_tunnel_unique_active")); + + connections = reporter_->getAllConnections(); + EXPECT_EQ(0, connections.size()); + EXPECT_EQ(3, getCounterValue("reverse_tunnel_full_pulls_total")); +} + +TEST_F(EventReporterTest, DuplicateConnectionHandling) { + EXPECT_CALL(*mock_client1_, receiveEvents(_)) + .Times(2) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(1, updates.connections.size()); + EXPECT_EQ(0, updates.disconnections.size()); + EXPECT_EQ("node1", updates.connections[0]->node_id); + })) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(0, updates.connections.size()); + EXPECT_EQ(1, updates.disconnections.size()); + EXPECT_EQ(ReverseTunnelEvent::getName("node1"), updates.disconnections[0]->name); + })); + + EXPECT_CALL(*mock_client2_, receiveEvents(_)).Times(2); + + createTestConnection("node1", "cluster1"); + runDispatcher(); + + EXPECT_EQ(1, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_unique_active")); + + createTestConnection("node1", "cluster1"); + runDispatcher(); + + EXPECT_EQ(2, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_unique_active")); + + auto connections = reporter_->getAllConnections(); + EXPECT_EQ(1, connections.size()); + EXPECT_EQ("node1", connections[0]->node_id); + EXPECT_EQ(1, getCounterValue("reverse_tunnel_full_pulls_total")); + + createTestDisconnection("node1", "cluster1"); + runDispatcher(); + + EXPECT_EQ(1, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_unique_active")); + + connections = reporter_->getAllConnections(); + EXPECT_EQ(1, connections.size()); + EXPECT_EQ("node1", connections[0]->node_id); + EXPECT_EQ(2, getCounterValue("reverse_tunnel_full_pulls_total")); + + createTestDisconnection("node1", "cluster1"); + runDispatcher(); + + EXPECT_EQ(2, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(0, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(0, getGaugeValue("reverse_tunnel_unique_active")); + + connections = reporter_->getAllConnections(); + EXPECT_EQ(0, connections.size()); + EXPECT_EQ(3, getCounterValue("reverse_tunnel_full_pulls_total")); +} + +TEST_F(EventReporterTest, PullsBeforeConnectionEvents) { + auto connections = reporter_->getAllConnections(); + EXPECT_EQ(0, connections.size()); + EXPECT_EQ(1, getCounterValue("reverse_tunnel_full_pulls_total")); + + connections = reporter_->getAllConnections(); + EXPECT_EQ(0, connections.size()); + EXPECT_EQ(2, getCounterValue("reverse_tunnel_full_pulls_total")); +} + +TEST_F(EventReporterTest, RemoveNonExistentConnection) { + Envoy::Logger::Registry::setLogLevel(spdlog::level::warn); + MockLogSink sink(Envoy::Logger::Registry::getSink()); + + EXPECT_CALL(sink, log(_, _)) + .WillOnce(Invoke([](absl::string_view, const spdlog::details::log_msg& msg) { + EXPECT_EQ(spdlog::level::warn, msg.level); + })); + + EXPECT_CALL(*mock_client1_, receiveEvents(_)).Times(0); + EXPECT_CALL(*mock_client2_, receiveEvents(_)).Times(0); + + createTestDisconnection("nonexistent", "connection"); + runDispatcher(); + + EXPECT_EQ(0, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(0, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(0, getGaugeValue("reverse_tunnel_unique_active")); + + auto connections = reporter_->getAllConnections(); + EXPECT_EQ(0, connections.size()); + EXPECT_EQ(1, getCounterValue("reverse_tunnel_full_pulls_total")); + + EXPECT_CALL(*mock_client1_, receiveEvents(_)) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(1, updates.connections.size()); + EXPECT_EQ(0, updates.disconnections.size()); + EXPECT_EQ("node1", updates.connections[0]->node_id); + })); + EXPECT_CALL(*mock_client2_, receiveEvents(_)); + + createTestConnection("node1", "cluster1"); + runDispatcher(); + + EXPECT_EQ(1, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_unique_active")); +} + +TEST_F(EventReporterTest, OnServerInitialized) { + Envoy::Logger::Registry::setLogLevel(spdlog::level::info); + MockLogSink sink(Envoy::Logger::Registry::getSink()); + + EXPECT_CALL(sink, log(_, _)) + .WillOnce(Invoke([](absl::string_view, const spdlog::details::log_msg& msg) { + EXPECT_EQ(spdlog::level::info, msg.level); + })); + + EXPECT_CALL(*mock_client1_, onServerInitialized(_)); + EXPECT_CALL(*mock_client2_, onServerInitialized(_)); + + reporter_->onServerInitialized(); + + EXPECT_EQ(0, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(0, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(0, getCounterValue("reverse_tunnel_full_pulls_total")); + EXPECT_EQ(0, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(0, getGaugeValue("reverse_tunnel_unique_active")); +} + +TEST_F(EventReporterTest, DefaultStatPrefix) { + EventReporter::ConfigProto config; + + std::vector clients; + auto mock_client1 = std::make_unique>(); + auto mock_client2 = std::make_unique>(); + mock_client1_ = mock_client1.get(); + mock_client2_ = mock_client2.get(); + clients.push_back(std::move(mock_client1)); + clients.push_back(std::move(mock_client2)); + + auto default_reporter = std::make_unique(context_, config, std::move(clients)); + + EXPECT_CALL(*mock_client1_, receiveEvents(_)); + EXPECT_CALL(*mock_client2_, receiveEvents(_)); + + default_reporter->reportConnectionEvent("node1", "cluster1", "tenant1"); + runDispatcher(); + + EXPECT_EQ( + 1, stats_store_.counterFromString("reverse_tunnel_reporter.reverse_tunnel_established_total") + .value()); + EXPECT_EQ(1, stats_store_ + .gaugeFromString("reverse_tunnel_reporter.reverse_tunnel_active", + Stats::Gauge::ImportMode::Accumulate) + .value()); + EXPECT_EQ(1, stats_store_ + .gaugeFromString("reverse_tunnel_reporter.reverse_tunnel_unique_active", + Stats::Gauge::ImportMode::Accumulate) + .value()); + + auto connections = default_reporter->getAllConnections(); + EXPECT_EQ(1, connections.size()); + EXPECT_EQ( + 1, stats_store_.counterFromString("reverse_tunnel_reporter.reverse_tunnel_full_pulls_total") + .value()); +} + +TEST_F(EventReporterTest, MixedScenario) { + EXPECT_CALL(*mock_client1_, receiveEvents(_)) + .Times(4) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(1, updates.connections.size()); + EXPECT_EQ(0, updates.disconnections.size()); + EXPECT_EQ("node1", updates.connections[0]->node_id); + EXPECT_EQ("tenant_A", updates.connections[0]->tenant_id); + })) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(1, updates.connections.size()); + EXPECT_EQ(0, updates.disconnections.size()); + EXPECT_EQ("node2", updates.connections[0]->node_id); + EXPECT_EQ("tenant_B", updates.connections[0]->tenant_id); + })) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(0, updates.connections.size()); + EXPECT_EQ(1, updates.disconnections.size()); + EXPECT_EQ(ReverseTunnelEvent::getName("node2"), updates.disconnections[0]->name); + })) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(1, updates.connections.size()); + EXPECT_EQ(0, updates.disconnections.size()); + EXPECT_EQ("node3", updates.connections[0]->node_id); + EXPECT_EQ("tenant_C", updates.connections[0]->tenant_id); + })); + + EXPECT_CALL(*mock_client2_, receiveEvents(_)).Times(4); + + createTestConnection("node1", "cluster1", "tenant_A"); + runDispatcher(); + EXPECT_EQ(1, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_unique_active")); + + createTestConnection("node2", "cluster2", "tenant_B"); + runDispatcher(); + EXPECT_EQ(2, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_unique_active")); + + auto connections = reporter_->getAllConnections(); + EXPECT_EQ(2, connections.size()); + EXPECT_EQ(1, getCounterValue("reverse_tunnel_full_pulls_total")); + + createTestConnection("node1", "cluster1", "tenant_A"); + runDispatcher(); + EXPECT_EQ(3, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(3, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_unique_active")); + + createTestConnection("node2", "cluster2", "tenant_B"); + runDispatcher(); + EXPECT_EQ(4, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(4, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_unique_active")); + + createTestDisconnection("node1", "cluster1"); + runDispatcher(); + EXPECT_EQ(1, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(3, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_unique_active")); + + connections = reporter_->getAllConnections(); + EXPECT_EQ(2, connections.size()); + EXPECT_EQ(2, getCounterValue("reverse_tunnel_full_pulls_total")); + + createTestDisconnection("node2", "cluster2"); + runDispatcher(); + EXPECT_EQ(2, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_unique_active")); + + createTestDisconnection("node2", "cluster2"); + runDispatcher(); + EXPECT_EQ(3, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_unique_active")); + + createTestConnection("node3", "cluster3", "tenant_C"); + runDispatcher(); + EXPECT_EQ(5, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_unique_active")); + + connections = reporter_->getAllConnections(); + EXPECT_EQ(2, connections.size()); + EXPECT_EQ(3, getCounterValue("reverse_tunnel_full_pulls_total")); + + EXPECT_EQ(5, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(3, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(2, getGaugeValue("reverse_tunnel_unique_active")); +} + +TEST_F(EventReporterTest, LargeDuplicateCount) { + EXPECT_CALL(*mock_client1_, receiveEvents(_)) + .Times(2) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(1, updates.connections.size()); + EXPECT_EQ(0, updates.disconnections.size()); + EXPECT_EQ("node1", updates.connections[0]->node_id); + EXPECT_EQ("tenant_A", updates.connections[0]->tenant_id); + })) + .WillOnce(Invoke([](const ReverseTunnelEvent::TunnelUpdates& updates) { + EXPECT_EQ(0, updates.connections.size()); + EXPECT_EQ(1, updates.disconnections.size()); + EXPECT_EQ(ReverseTunnelEvent::getName("node1"), updates.disconnections[0]->name); + })); + + EXPECT_CALL(*mock_client2_, receiveEvents(_)).Times(2); + + const int DUPLICATE_COUNT = 50; + + for (int i = 0; i < DUPLICATE_COUNT; i++) { + createTestConnection("node1", "cluster1", "tenant_A"); + runDispatcher(); + } + + EXPECT_EQ(DUPLICATE_COUNT, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(DUPLICATE_COUNT, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_unique_active")); + + auto connections = reporter_->getAllConnections(); + EXPECT_EQ(1, connections.size()); + EXPECT_EQ(1, getCounterValue("reverse_tunnel_full_pulls_total")); + + for (int i = 0; i < DUPLICATE_COUNT - 1; i++) { + createTestDisconnection("node1", "cluster1"); + runDispatcher(); + } + + EXPECT_EQ(DUPLICATE_COUNT - 1, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(1, getGaugeValue("reverse_tunnel_unique_active")); + + connections = reporter_->getAllConnections(); + EXPECT_EQ(1, connections.size()); + EXPECT_EQ(2, getCounterValue("reverse_tunnel_full_pulls_total")); + + createTestDisconnection("node1", "cluster1"); + runDispatcher(); + + EXPECT_EQ(DUPLICATE_COUNT, getCounterValue("reverse_tunnel_established_total")); + EXPECT_EQ(DUPLICATE_COUNT, getCounterValue("reverse_tunnel_closed_total")); + EXPECT_EQ(0, getGaugeValue("reverse_tunnel_active")); + EXPECT_EQ(0, getGaugeValue("reverse_tunnel_unique_active")); + + connections = reporter_->getAllConnections(); + EXPECT_EQ(0, connections.size()); + EXPECT_EQ(3, getCounterValue("reverse_tunnel_full_pulls_total")); +} + +// --- Factory tests --- + +class MockReverseTunnelReporterClientFactory : public ReverseTunnelReporterClientFactory { +public: + MOCK_METHOD(ReverseTunnelReporterClientPtr, createClient, + (Server::Configuration::ServerFactoryContext&, const Protobuf::Message&), (override)); + + std::string name() const override { return "mock_client_factory"; } + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +class EventReporterFactoryTest : public testing::Test { +protected: + void SetUp() override { + ON_CALL(context_, messageValidationVisitor()) + .WillByDefault(ReturnRef(ProtobufMessage::getStrictValidationVisitor())); + ON_CALL(context_, scope()).WillByDefault(ReturnRef(*stats_store_.rootScope())); + + api_ = Api::createApiForTest(); + dispatcher_ = api_->allocateDispatcher("test_thread"); + ON_CALL(context_, mainThreadDispatcher()).WillByDefault(ReturnRef(*dispatcher_)); + } + + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + NiceMock context_; + Stats::TestUtil::TestStore stats_store_; + EventReporterFactory factory_; +}; + +TEST_F(EventReporterFactoryTest, Name) { + EXPECT_EQ( + "envoy.extensions.reverse_tunnel.reverse_tunnel_reporting_service.reporters.event_reporter", + factory_.name()); +} + +TEST_F(EventReporterFactoryTest, CreateEmptyConfigProto) { + auto config = factory_.createEmptyConfigProto(); + EXPECT_NE(nullptr, config); + EXPECT_NE( + nullptr, + dynamic_cast< + envoy::extensions::reverse_tunnel_reporters::v3alpha::reporters::EventReporterConfig*>( + config.get())); +} + +TEST_F(EventReporterFactoryTest, CreateReporterWithRegisteredClient) { + MockReverseTunnelReporterClientFactory mock_client_factory; + Registry::InjectFactory registered(mock_client_factory); + + EXPECT_CALL(mock_client_factory, createClient(_, _)) + .WillOnce(Invoke([](Server::Configuration::ServerFactoryContext&, + const Protobuf::Message&) -> ReverseTunnelReporterClientPtr { + return std::make_unique>(); + })); + + auto config = factory_.createEmptyConfigProto(); + auto& reporter_config = dynamic_cast< + envoy::extensions::reverse_tunnel_reporters::v3alpha::reporters::EventReporterConfig&>( + *config); + reporter_config.set_stat_prefix("test"); + + auto* client_entry = reporter_config.add_clients(); + client_entry->set_name("mock_client_factory"); + client_entry->mutable_typed_config()->PackFrom(Protobuf::Struct()); + + auto reporter = factory_.createReporter(context_, std::move(config)); + EXPECT_NE(nullptr, reporter); +} + +TEST_F(EventReporterFactoryTest, CreateClientWithUnknownFactoryThrows) { + auto config = factory_.createEmptyConfigProto(); + auto& reporter_config = dynamic_cast< + envoy::extensions::reverse_tunnel_reporters::v3alpha::reporters::EventReporterConfig&>( + *config); + reporter_config.set_stat_prefix("test"); + + auto* client_entry = reporter_config.add_clients(); + client_entry->set_name("nonexistent_client_factory"); + client_entry->mutable_typed_config()->PackFrom(Protobuf::Struct()); + + EXPECT_THROW_WITH_REGEX(factory_.createReporter(context_, std::move(config)), EnvoyException, + "Unknown Reporter Client Factory: 'nonexistent_client_factory'"); +} + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy From 8a53d193fc40a3cae3baeb9ee5fc512b3d75023b Mon Sep 17 00:00:00 2001 From: aakugan <164153358+aakugan@users.noreply.github.com> Date: Wed, 29 Apr 2026 23:56:18 +0530 Subject: [PATCH 163/750] reverse_tunnels: add gRPC streaming client for reverse tunnel reporting (#44344) BUILDS on #44342. Please review that one first. [INCREMENTAL DIFFS](https://github.com/envoyproxy/envoy/pull/44344/changes/1ed27c61f62cc1ed191fd67a5ac170297ad1ec2e) ## Commit Message Add gRPC streaming client for reverse tunnel reporting ## Additional Description Adds a bidirectional gRPC streaming client that pushes reverse tunnel connection state to a remote ReverseTunnelReportingService. This is a client implementation for the EventReporter introduced in the previous PR. Protocol: - On connect (and every reconnect) the client does a full state push of all known connections obtained from the reporter. - Between connects the client sends incremental diffs on a periodic send timer. Empty messages serve as heartbeats. - Each request carries an incrementing nonce. The server ACKs by echoing the nonce; a NACK carries an error_detail and triggers disconnect. - If too many nonces remain unacked the client disconnects and reconnects, treating the server as dead or stuck. - The server may adjust the send interval via report_interval in its ACK; the client floors it at 25ms to prevent tight loops. Key components: - `GrpcClient`: implements `ReverseTunnelReporterClient` and `Grpc::AsyncStreamCallbacks`. Manages stream lifecycle, nonce tracking, send/retry timers, and per-cluster stats. - `GrpcClientFactory`: dynamically registered factory resolved by the EventReporter when constructing clients from config. - `GrpcClientConfig`: parsed config struct with defaults (5s send interval, 5s retry, 5 max retries, 1M buffer). Additional considerations: - `initialized_` guard prevents silent event queuing if cluster lookup or client creation fails during onServerInitialized(). - Buffer overflow while disconnected does not re-arm the retry timer. - Nonce reset on every new stream to avoid stale ACK confusion. Risk Level: Low ## Testing Unit tests --------- Signed-off-by: aakugan Signed-off-by: Rohit Agrawal Co-authored-by: Rohit Agrawal --- contrib/reverse_tunnel_reporter/source/BUILD | 1 + .../source/clients/BUILD | 16 + .../source/clients/grpc_client/BUILD | 29 + .../source/clients/grpc_client/client.cc | 282 ++++++ .../source/clients/grpc_client/client.h | 229 +++++ .../source/clients/grpc_client/factory.cc | 36 + .../source/clients/grpc_client/factory.h | 27 + .../test/clients/BUILD | 28 + .../test/clients/grpc_client_test.cc | 810 ++++++++++++++++++ 9 files changed, 1458 insertions(+) create mode 100644 contrib/reverse_tunnel_reporter/source/clients/BUILD create mode 100644 contrib/reverse_tunnel_reporter/source/clients/grpc_client/BUILD create mode 100644 contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.cc create mode 100644 contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h create mode 100644 contrib/reverse_tunnel_reporter/source/clients/grpc_client/factory.cc create mode 100644 contrib/reverse_tunnel_reporter/source/clients/grpc_client/factory.h create mode 100644 contrib/reverse_tunnel_reporter/test/clients/BUILD create mode 100644 contrib/reverse_tunnel_reporter/test/clients/grpc_client_test.cc diff --git a/contrib/reverse_tunnel_reporter/source/BUILD b/contrib/reverse_tunnel_reporter/source/BUILD index c952f5c659e33..6890914b2f78a 100644 --- a/contrib/reverse_tunnel_reporter/source/BUILD +++ b/contrib/reverse_tunnel_reporter/source/BUILD @@ -30,6 +30,7 @@ envoy_cc_library( envoy_cc_contrib_extension( name = "config", deps = [ + "//contrib/reverse_tunnel_reporter/source/clients:clients_lib", "//contrib/reverse_tunnel_reporter/source/reporters:reporters_lib", ], ) diff --git a/contrib/reverse_tunnel_reporter/source/clients/BUILD b/contrib/reverse_tunnel_reporter/source/clients/BUILD new file mode 100644 index 0000000000000..e8886c7f0e7e5 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/clients/BUILD @@ -0,0 +1,16 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "clients_lib", + deps = [ + "//contrib/reverse_tunnel_reporter/source/clients/grpc_client:grpc_client_lib", + ], +) diff --git a/contrib/reverse_tunnel_reporter/source/clients/grpc_client/BUILD b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/BUILD new file mode 100644 index 0000000000000..17adeffe19274 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/BUILD @@ -0,0 +1,29 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "grpc_client_lib", + srcs = [ + "client.cc", + "factory.cc", + ], + hdrs = [ + "client.h", + "factory.h", + ], + deps = [ + "//contrib/reverse_tunnel_reporter/source:reverse_tunnel_event_types", + "//envoy/registry", + "//source/common/common:logger_lib", + "//source/common/grpc:typed_async_client_lib", + "//source/common/protobuf:utility_lib", + "@envoy_api//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client:pkg_cc_proto", + ], +) diff --git a/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.cc b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.cc new file mode 100644 index 0000000000000..cd8223ca2bc60 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.cc @@ -0,0 +1,282 @@ +#include "contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +GrpcClientConfig::GrpcClientConfig(const GrpcConfigProto& proto_config) + : stat_prefix(PROTOBUF_GET_STRING_OR_DEFAULT(proto_config, stat_prefix, + "reverse_tunnel_reporter_client.grpc_client")), + cluster(proto_config.cluster()), + send_interval(PROTOBUF_GET_MS_OR_DEFAULT(proto_config, default_send_interval, 5000)), + connect_retry_interval( + PROTOBUF_GET_MS_OR_DEFAULT(proto_config, connect_retry_interval, 5000)), + max_retries(proto_config.max_retries() ? proto_config.max_retries() : 5), + max_buffer(proto_config.max_buffer_count() ? proto_config.max_buffer_count() : 1000000) {} + +GrpcClient::GrpcClient(Server::Configuration::ServerFactoryContext& context, + const GrpcConfigProto& config) + : context_{context}, config_{config}, + service_method_{*Protobuf::DescriptorPool::generated_pool()->FindMethodByName( + "envoy.extensions.reverse_tunnel_reporters.v3alpha.clients.grpc_client" + ".ReverseTunnelReportingService.StreamReverseTunnels")}, + stats_(context_, config_.stat_prefix, config_.cluster) { + ENVOY_LOG(info, + "GrpcClient: constructed: cluster={}, send_interval={}ms, retry_interval={}ms, " + "max_retries={}, max_buffer={}", + config_.cluster, config_.send_interval.count(), config_.connect_retry_interval.count(), + config_.max_retries, config_.max_buffer); + + send_timer_ = context.mainThreadDispatcher().createTimer([this]() { send(false); }); + + retry_timer_ = context.mainThreadDispatcher().createTimer([this]() { connect(); }); + + stats_.send_interval_gauge_.set(config_.send_interval.count()); +} + +void GrpcClient::onServerInitialized(ReverseTunnelReporterWithState* reporter) { + reporter_ = reporter; + + envoy::config::core::v3::GrpcService grpc_service; + grpc_service.mutable_envoy_grpc()->set_cluster_name(config_.cluster); + + auto thread_local_cluster = context_.clusterManager().getThreadLocalCluster(config_.cluster); + if (!thread_local_cluster) { + ENVOY_LOG(error, "GrpcClient: cluster '{}' not found, cannot initialize", config_.cluster); + return; + } + + auto result = context_.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( + grpc_service, thread_local_cluster->info()->statsScope(), false); + if (!result.ok()) { + ENVOY_LOG(error, "GrpcClient: failed to create gRPC async client: {}", + result.status().message()); + return; + } + + async_client_ = Grpc::AsyncClient(result.value()); + ENVOY_LOG(info, "GrpcClient: initialized: cluster={}", config_.cluster); + + initialized_ = true; + connect(); +} + +void GrpcClient::receiveEvents(ReverseTunnelEvent::TunnelUpdates updates) { + // Either we errored out of the initialized -> prevent infinite growth. + // Or the onServerInitialized has not been called yet -> no worries we will do a full push on + // connect. + if (!initialized_) { + ENVOY_LOG(debug, "GrpcClient: not initialized, cannot receive events"); + return; + } + + if ((updates.size() + queued_updates_.size()) > config_.max_buffer) { + ENVOY_LOG(error, + "GrpcClient: buffer overflow: cluster={}, queued={}, incoming={}, max_buffer={}", + config_.cluster, queued_updates_.size(), updates.size(), config_.max_buffer); + + stats_.events_dropped_counter_.add(updates.size()); + + // Only disconnect if the stream is alive. If already disconnected, calling disconnect() + // would re-arm the retry timer, delaying the reconnect that is already scheduled. + if (stream_ != nullptr) { + stats_ + .getCounter(stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::ResourceExhausted, + GrpcDisconnectionReason::DisconnectReason::BUFFER_OVERFLOW)) + .inc(); + disconnect(); + } + return; + } + + const auto incoming_conns = updates.connections.size(); + const auto incoming_disconns = updates.disconnections.size(); + stats_.queued_updates_counter_.add(updates.size()); + queued_updates_ += std::move(updates); + ENVOY_LOG(debug, "GrpcClient: enqueued: cluster={}, +={}, -={}, queued_now={}", config_.cluster, + incoming_conns, incoming_disconns, queued_updates_.size()); +} + +void GrpcClient::onReceiveMessage(Grpc::ResponsePtr&& message) { + const auto resp_nonce = message->request_nonce(); + + if (message->has_error_detail()) { + ENVOY_LOG(error, "GrpcClient: NACK: cluster={}, nonce={}", config_.cluster, resp_nonce); + + stats_ + .getCounter(stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::Aborted, + GrpcDisconnectionReason::DisconnectReason::NACK_RECEIVED)) + .inc(); + return disconnect(); + } + + // A server cannot ACK a nonce we never sent. If this fires the server has a bug. + ASSERT( + resp_nonce <= nonce_current_, + fmt::format("server acked nonce {} but we only sent up to {}", resp_nonce, nonce_current_)); + + // Valid ACK: must be newer than the last acked watermark and within what we've sent. + if (resp_nonce > nonce_acked_ && resp_nonce <= nonce_current_) { + stats_.acks_received_counter_.inc(); + nonce_acked_ = resp_nonce; + stats_.nonce_acked_gauge_.set(nonce_acked_); + + // The server may dynamically adjust our send cadence via report_interval in each ACK. + // The new interval will take effect from the next schedule onwards the already scheduled send + // is not affected. This is sticky and stays across disconnects also. + config_.send_interval = std::chrono::milliseconds( + PROTOBUF_GET_MS_OR_DEFAULT(*message, report_interval, config_.send_interval.count())); + stats_.send_interval_gauge_.set(config_.send_interval.count()); + + ENVOY_LOG(debug, "GrpcClient: ACK: cluster={}, nonce={}", config_.cluster, resp_nonce); + } else { + ENVOY_LOG(error, "GrpcClient: out-of-order ACK: cluster={}, nonce={}, expected_range=[{}, {}]", + config_.cluster, resp_nonce, nonce_acked_ + 1, nonce_current_); + stats_.out_of_order_acks_counter_.inc(); + } +} + +void GrpcClient::onRemoteClose(Grpc::Status::GrpcStatus status, const std::string& message) { + // Even a graceful close is unexpected — the server should keep the stream open indefinitely. + if (status == Grpc::Status::WellKnownGrpcStatus::Ok) { + ENVOY_LOG(error, "GrpcClient: remote close (ok): cluster={}, message={}", config_.cluster, + message); + } else { + ENVOY_LOG(error, "GrpcClient: remote close: cluster={}, status={}, message={}", config_.cluster, + status, message); + } + + stats_ + .getCounter(stats_.disconnects_, + stats_.getTags(status, GrpcDisconnectionReason::DisconnectReason::REMOTE_CLOSE)) + .inc(); + disconnect(); +} + +void GrpcClient::connect() { + ENVOY_LOG(info, "GrpcClient: connecting: cluster={}", config_.cluster); + + stream_ = async_client_.start(service_method_, *this, Http::AsyncClient::StreamOptions()); + if (stream_ == nullptr) { + ENVOY_LOG(error, "GrpcClient: stream creation failed: cluster={}", config_.cluster); + + stats_ + .getCounter( + stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::Internal, + GrpcDisconnectionReason::DisconnectReason::STREAM_CREATION_FAILED)) + .inc(); + return disconnect(); + } + + // New stream, new nonce epoch. Stale nonces from the previous stream are meaningless. + nonce_acked_ = nonce_current_ = 0; + stats_.nonce_current_gauge_.set(0); + stats_.nonce_acked_gauge_.set(0); + + stats_.connection_attempts_counter_.inc(); + ENVOY_LOG(info, "GrpcClient: connected: cluster={}", config_.cluster); + send(true); +} + +void GrpcClient::disconnect() { + if (stream_ != nullptr) { + stream_.resetStream(); + stream_ = nullptr; + } + + // Stop the send loop — no point sending on a dead stream. The retry timer will reconnect. + send_timer_->disableTimer(); + setTimer(retry_timer_, config_.connect_retry_interval); + ENVOY_LOG(debug, "GrpcClient: disconnect, scheduled reconnect: cluster={}, retry_in_ms={}", + config_.cluster, config_.connect_retry_interval.count()); +} + +void GrpcClient::send(bool full_push) { + ASSERT(stream_ != nullptr); + // Too many in-flight unacked messages — the server is likely dead or stuck. Disconnect + // and let the retry timer establish a fresh stream. + if ((nonce_current_ - nonce_acked_) > config_.max_retries) { + ENVOY_LOG(error, "GrpcClient: too many unacked requests: cluster={}, nonce={}", config_.cluster, + nonce_current_); + + stats_ + .getCounter(stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::DeadlineExceeded, + GrpcDisconnectionReason::DisconnectReason::MAX_RETRIES_EXCEEDED)) + .inc(); + return disconnect(); + } + + ENVOY_LOG(debug, "GrpcClient: sending: cluster={}, full_push={}, queued_now={}", config_.cluster, + full_push, queued_updates_.size()); + stats_.send_attempts_counter_.inc(); + stream_.sendMessage(constructMessage(full_push), false); + + setTimer(send_timer_, config_.send_interval); +} + +StreamTunnelsReq GrpcClient::constructMessage(bool full_push) { + // Full push replaces the pending diff queue with a complete snapshot from the reporter. + // Any queued diffs are stale at this point because the full snapshot supersedes them. + if (full_push) { + stats_.events_dropped_counter_.add(queued_updates_.size()); + queued_updates_ = ReverseTunnelEvent::TunnelUpdates{{reporter_->getAllConnections()}, {}}; + stats_.queued_updates_counter_.add(queued_updates_.size()); + ENVOY_LOG(info, "GrpcClient: full_push queued: cluster={}, queued_now={}", config_.cluster, + queued_updates_.size()); + } + + StreamTunnelsReq message; + + auto* node = message.mutable_node(); + node->set_id(context_.localInfo().nodeName()); + node->set_cluster(context_.localInfo().clusterName()); + + auto* added_tunnels = message.mutable_added_tunnels(); + for (auto& conn : queued_updates_.connections) { + auto* new_tunnel = added_tunnels->Add(); + new_tunnel->set_name(ReverseTunnelEvent::getName(conn->node_id)); + TimestampUtil::systemClockToTimestamp(conn->created_at, *new_tunnel->mutable_created_at()); + + auto* tunnel_id = new_tunnel->mutable_identity(); + tunnel_id->set_tenant_id(conn->tenant_id); + tunnel_id->set_cluster_id(conn->cluster_id); + tunnel_id->set_node_id(conn->node_id); + } + + auto* removed_tunnels = message.mutable_removed_tunnel_names(); + for (auto& disconn : queued_updates_.disconnections) { + *removed_tunnels->Add() = disconn->name; + } + + message.set_full_push(full_push); + message.set_nonce(++nonce_current_); + stats_.nonce_current_gauge_.set(nonce_current_); + + ENVOY_LOG(debug, + "GrpcClient: built request: cluster={}, full_push={}, add={}, remove={}, nonce={}", + config_.cluster, full_push, queued_updates_.connections.size(), + queued_updates_.disconnections.size(), message.nonce()); + + stats_.sent_accepted_cnt_counter_.add(queued_updates_.connections.size()); + stats_.sent_removed_cnt_counter_.add(queued_updates_.disconnections.size()); + queued_updates_.clear(); + + return message; +} + +void GrpcClient::setTimer(Event::TimerPtr& timer, const std::chrono::milliseconds& ms) { + if (timer->enabled()) + timer->disableTimer(); + + timer->enableTimer(ms); +} + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h new file mode 100644 index 0000000000000..15676b0dba490 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h @@ -0,0 +1,229 @@ +#pragma once + +#include +#include +#include + +#include "envoy/event/timer.h" +#include "envoy/grpc/async_client.h" + +#include "source/common/common/logger.h" +#include "source/common/grpc/typed_async_client.h" +#include "source/common/protobuf/utility.h" +#include "source/common/stats/symbol_table.h" + +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.pb.h" +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/stream_reverse_tunnels.pb.h" +#include "contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +namespace GrpcDisconnectionReason { + +#define ITEMS(X) \ + X(BUFFER_OVERFLOW, buffer_overflow) \ + X(MAX_RETRIES_EXCEEDED, max_retries_exceeded) \ + X(NACK_RECEIVED, nack_received) \ + X(REMOTE_CLOSE, remote_close) \ + X(STREAM_CREATION_FAILED, stream_creation_failed) + +#define ENUM_DECLARE(name, str) name, +enum class DisconnectReason { ITEMS(ENUM_DECLARE) COUNT }; +#undef ENUM_DECLARE + +#define ENUM_STRING(name, str) #str, +constexpr std::array(DisconnectReason::COUNT)> + DisconnectReasonStrings = {ITEMS(ENUM_STRING)}; +#undef ENUM_STRING + +constexpr absl::string_view toString(DisconnectReason r) { + return DisconnectReasonStrings[static_cast(r)]; +} + +} // namespace GrpcDisconnectionReason + +using GrpcConfigProto = + envoy::extensions::reverse_tunnel_reporters::v3alpha::clients::grpc_client::GrpcClientConfig; +using StreamTunnelsReq = envoy::extensions::reverse_tunnel_reporters::v3alpha::clients:: + grpc_client::StreamReverseTunnelsRequest; +using StreamTunnelsResp = envoy::extensions::reverse_tunnel_reporters::v3alpha::clients:: + grpc_client::StreamReverseTunnelsResponse; + +/// Parsed and validated configuration from the GrpcClientConfig proto, with defaults applied. +struct GrpcClientConfig { + std::string stat_prefix; + std::string cluster; + std::chrono::milliseconds send_interval; + std::chrono::milliseconds connect_retry_interval; + uint32_t max_retries; + uint32_t max_buffer; + + explicit GrpcClientConfig(const GrpcConfigProto& config); +}; + +/// Bidirectional gRPC streaming client that reports reverse-tunnel connection +/// state to a remote ReverseTunnelReportingService. +/// +/// Protocol: +/// - On connect (and every reconnect) the client does a full push of all +/// known connections obtained from the reporter. +/// - Between connects the client sends incremental diffs (new connections / +/// removals) on a periodic send timer. +/// - Each request carries an incrementing nonce. The server ACKs by echoing +/// the nonce; a NACK carries an error_detail. If too many nonces remain +/// unacked the client disconnects and reconnects. +/// - The server may adjust the send interval via report_interval in its ACK. +/// +/// Lifecycle: constructed by GrpcClientFactory, initialized via +/// onServerInitialized() which creates the gRPC channel and opens the first +/// stream and starts the send cycle. Empty messages are also sent (considered as heartbeat). +class GrpcClient : public ReverseTunnelReporterClient, + public Logger::Loggable, + public Grpc::AsyncStreamCallbacks { +public: + GrpcClient(Server::Configuration::ServerFactoryContext& context, const GrpcConfigProto& config); + + // ReverseTunnelReporterClient overrides + void onServerInitialized(ReverseTunnelReporterWithState* reporter) override; + + void receiveEvents(ReverseTunnelEvent::TunnelUpdates updates) override; + + // RawAsyncStreamCallbacks overrides + void onCreateInitialMetadata(Http::RequestHeaderMap&) override {} + + void onReceiveInitialMetadata(Http::ResponseHeaderMapPtr&&) override {} + + void onReceiveTrailingMetadata(Http::ResponseTrailerMapPtr&&) override {} + + void onReceiveMessage(Grpc::ResponsePtr&& message) override; + + void onRemoteClose(Grpc::Status::GrpcStatus status, const std::string& message) override; + +private: + // Actions + void connect(); + + void disconnect(); + + void send(bool full_push); + + // Helpers + StreamTunnelsReq constructMessage(bool full_push); + + void setTimer(Event::TimerPtr& timer, const std::chrono::milliseconds& ms); + + // config + Server::Configuration::ServerFactoryContext& context_; + GrpcClientConfig config_; + ReverseTunnelReporterWithState* reporter_{nullptr}; + + // State management + ReverseTunnelEvent::TunnelUpdates queued_updates_; + int64_t nonce_current_{0}; // Monotonically increasing, bumped on every sendMessage. + int64_t nonce_acked_{0}; // High watermark of server-acknowledged nonces. + // Guards against processing events when onServerInitialized() failed (cluster not + // found, client creation error). Without this the client silently queues to nowhere. + bool initialized_{false}; + + // grpc client and stream requirements + Grpc::AsyncClient async_client_; + Grpc::AsyncStream stream_{}; + const Protobuf::MethodDescriptor& service_method_; + + // timers + Event::TimerPtr retry_timer_; + Event::TimerPtr send_timer_; + + struct GrpcClientStats { + GrpcClientStats(Server::Configuration::ServerFactoryContext& context, + const std::string& stat_prefix, const std::string& cluster_name) + : context_{context}, stat_name_pool_(context.scope().symbolTable()), + stat_prefix_(stat_name_pool_.add(stat_prefix)), + + disconnects_(stat_name_pool_.add("disconnects")), + + status_code_(stat_name_pool_.add("status_code")), + disconnect_reason_(stat_name_pool_.add("disconnect_reason")), + cluster_label_(stat_name_pool_.add("cluster")), + cluster_value_(stat_name_pool_.add(cluster_name)), + + connection_attempts_counter_( + getCounter(stat_name_pool_.add("connection_attempts"), getTags())), + acks_received_counter_(getCounter(stat_name_pool_.add("acks_received"), getTags())), + send_attempts_counter_(getCounter(stat_name_pool_.add("send_attempts"), getTags())), + sent_accepted_cnt_counter_( + getCounter(stat_name_pool_.add("sent_accepted_cnt"), getTags())), + sent_removed_cnt_counter_(getCounter(stat_name_pool_.add("sent_removed_cnt"), getTags())), + events_dropped_counter_(getCounter(stat_name_pool_.add("events_dropped"), getTags())), + queued_updates_counter_(getCounter(stat_name_pool_.add("queued_events"), getTags())), + out_of_order_acks_counter_( + getCounter(stat_name_pool_.add("out_of_order_acks"), getTags())), + + send_interval_gauge_(getGauge(stat_name_pool_.add("send_interval"), getTags())), + nonce_current_gauge_(getGauge(stat_name_pool_.add("nonce_current"), getTags())), + nonce_acked_gauge_(getGauge(stat_name_pool_.add("nonce_acked"), getTags())) {} + + Stats::StatNameTagVector getTags() { + return Stats::StatNameTagVector{ + {cluster_label_, cluster_value_}, + }; + } + + Stats::StatNameTagVector getTags(Grpc::Status::GrpcStatus status, + GrpcDisconnectionReason::DisconnectReason reason) { + Stats::StatName status_value = stat_name_pool_.add(std::to_string(status)); + Stats::StatName reason_value = stat_name_pool_.add(GrpcDisconnectionReason::toString(reason)); + + return Stats::StatNameTagVector{ + {cluster_label_, cluster_value_}, + {status_code_, status_value}, + {disconnect_reason_, reason_value}, + }; + } + + Stats::Counter& getCounter(const Stats::StatName& name, Stats::StatNameTagVector&& tags) { + return Stats::Utility::counterFromStatNames(context_.scope(), {stat_prefix_, name}, tags); + } + + Stats::Gauge& getGauge(const Stats::StatName& name, Stats::StatNameTagVector&& tags) { + return Stats::Utility::gaugeFromStatNames(context_.scope(), {stat_prefix_, name}, + Stats::Gauge::ImportMode::NeverImport, tags); + } + + Server::Configuration::ServerFactoryContext& context_; + Stats::StatNamePool stat_name_pool_; + const Stats::StatName stat_prefix_; + + const Stats::StatName disconnects_; + + const Stats::StatName status_code_; + const Stats::StatName disconnect_reason_; + const Stats::StatName cluster_label_; + const Stats::StatName cluster_value_; + + Stats::Counter& connection_attempts_counter_; + Stats::Counter& acks_received_counter_; + Stats::Counter& send_attempts_counter_; + Stats::Counter& sent_accepted_cnt_counter_; + Stats::Counter& sent_removed_cnt_counter_; + Stats::Counter& events_dropped_counter_; + Stats::Counter& queued_updates_counter_; + Stats::Counter& out_of_order_acks_counter_; + + Stats::Gauge& send_interval_gauge_; + Stats::Gauge& nonce_current_gauge_; + Stats::Gauge& nonce_acked_gauge_; + }; + + GrpcClientStats stats_; + + friend class GrpcClientTest; +}; + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/reverse_tunnel_reporter/source/clients/grpc_client/factory.cc b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/factory.cc new file mode 100644 index 0000000000000..440ac5ac68da9 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/factory.cc @@ -0,0 +1,36 @@ +#include "contrib/reverse_tunnel_reporter/source/clients/grpc_client/factory.h" + +#include "envoy/registry/registry.h" + +#include "source/common/protobuf/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +ReverseTunnelReporterClientPtr +GrpcClientFactory::createClient(Server::Configuration::ServerFactoryContext& context, + const Protobuf::Message& config) { + const auto& grpc_client_config = + MessageUtil::downcastAndValidate( + config, context.messageValidationVisitor()); + return std::make_unique(context, grpc_client_config); +} + +std::string GrpcClientFactory::name() const { + return "envoy.extensions.reverse_tunnel.reverse_tunnel_reporting_service.clients.grpc_client"; +} + +ProtobufTypes::MessagePtr GrpcClientFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +REGISTER_FACTORY(GrpcClientFactory, ReverseTunnelReporterClientFactory); + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/reverse_tunnel_reporter/source/clients/grpc_client/factory.h b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/factory.h new file mode 100644 index 0000000000000..765fc5a2d95a7 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/factory.h @@ -0,0 +1,27 @@ +#pragma once + +#include "source/common/protobuf/utility.h" + +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.pb.h" +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.pb.validate.h" +#include "contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +class GrpcClientFactory : public ReverseTunnelReporterClientFactory { +public: + ReverseTunnelReporterClientPtr createClient(Server::Configuration::ServerFactoryContext& context, + const Protobuf::Message& config) override; + + std::string name() const override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override; +}; + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/reverse_tunnel_reporter/test/clients/BUILD b/contrib/reverse_tunnel_reporter/test/clients/BUILD new file mode 100644 index 0000000000000..feeb167efd4d7 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/test/clients/BUILD @@ -0,0 +1,28 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_test( + name = "grpc_client_test", + srcs = ["grpc_client_test.cc"], + deps = [ + "//contrib/reverse_tunnel_reporter/source/clients/grpc_client:grpc_client_lib", + "//envoy/grpc:async_client_interface", + "//envoy/registry", + "//source/common/api:api_lib", + "//source/common/protobuf:utility_lib_header", + "//test/mocks/grpc:grpc_mocks", + "//test/mocks/local_info:local_info_mocks", + "//test/mocks/server:server_factory_context_mocks", + "//test/mocks/upstream:cluster_info_mocks", + "//test/mocks/upstream:thread_local_cluster_mocks", + "//test/test_common:registry_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/contrib/reverse_tunnel_reporter/test/clients/grpc_client_test.cc b/contrib/reverse_tunnel_reporter/test/clients/grpc_client_test.cc new file mode 100644 index 0000000000000..38c75f13b659c --- /dev/null +++ b/contrib/reverse_tunnel_reporter/test/clients/grpc_client_test.cc @@ -0,0 +1,810 @@ +#include + +#include "envoy/grpc/async_client.h" +#include "envoy/registry/registry.h" + +#include "source/common/grpc/common.h" +#include "source/common/protobuf/utility.h" + +#include "test/mocks/grpc/mocks.h" +#include "test/mocks/local_info/mocks.h" +#include "test/mocks/server/server_factory_context.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/mocks/upstream/thread_local_cluster.h" +#include "test/test_common/utility.h" + +#include "contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h" +#include "contrib/reverse_tunnel_reporter/source/clients/grpc_client/factory.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +class MockReverseTunnelReporter : public ReverseTunnelReporterWithState { +public: + MOCK_METHOD(void, onServerInitialized, (), (override)); + MOCK_METHOD(void, reportConnectionEvent, + (absl::string_view, absl::string_view, absl::string_view), (override)); + MOCK_METHOD(void, reportDisconnectionEvent, (absl::string_view, absl::string_view), (override)); + MOCK_METHOD(ReverseTunnelEvent::ConnectionsList, getAllConnections, (), (override)); +}; + +GrpcConfigProto mock_config() { + GrpcConfigProto config_; + + config_.set_cluster("test_cluster"); + config_.mutable_default_send_interval()->set_seconds(2); + config_.mutable_connect_retry_interval()->set_seconds(2); + config_.set_max_retries(2); + config_.set_max_buffer_count(1000); + config_.set_stat_prefix("test.grpc_client"); + + return config_; +} + +ReverseTunnelEvent::ConnectionsList make_connections(std::vector node_ids) { + ReverseTunnelEvent::ConnectionsList connections; + std::string cluster = "test_cluster"; + std::string tenant = "test_tenant"; + + for (const auto& node : node_ids) { + auto conn = std::make_shared(ReverseTunnelEvent::Connected{ + node, cluster, tenant, std::chrono::system_clock::time_point(std::chrono::seconds(1))}); + connections.push_back(std::move(conn)); + } + + return connections; +} + +ReverseTunnelEvent::DisconnectionsList make_disconnections(std::vector node_ids) { + std::string cluster = "test_cluster"; + ReverseTunnelEvent::DisconnectionsList disconnections; + + for (const auto& node : node_ids) { + auto disconn = std::make_shared( + ReverseTunnelEvent::Disconnected{ReverseTunnelEvent::getName(node)}); + disconnections.push_back(std::move(disconn)); + } + + return disconnections; +} + +StreamTunnelsResp validate_req(Buffer::InstancePtr& request, + const ReverseTunnelEvent::TunnelUpdates& actual, bool full_push) { + StreamTunnelsReq req; + bool success = Grpc::Common::parseBufferInstance(std::move(request), req); + EXPECT_EQ(success, true); + + EXPECT_EQ(req.added_tunnels_size(), actual.connections.size()); + EXPECT_EQ(req.removed_tunnel_names_size(), actual.disconnections.size()); + + for (std::size_t i = 0; i < actual.connections.size(); i++) + EXPECT_EQ(actual.connections[i]->node_id, req.added_tunnels(i).identity().node_id()); + + for (std::size_t i = 0; i < actual.disconnections.size(); i++) + EXPECT_EQ(actual.disconnections[i]->name, req.removed_tunnel_names(i)); + + EXPECT_EQ(full_push, req.full_push()); + + StreamTunnelsResp resp; + resp.set_request_nonce(req.nonce()); + + return resp; +} + +Protobuf::Duration getHalfDuration(const Protobuf::Duration& dur) { + return Protobuf::util::TimeUtil::MillisecondsToDuration( + DurationUtil::durationToMilliseconds(dur) / 2); +} + +class GrpcClientTest : public testing::Test { +public: + GrpcClientTest() {} + + void SetUp() override { + api_ = Api::createApiForTest(time_system_); + dispatcher_ = api_->allocateDispatcher("test_thread"); + + ON_CALL(context_, mainThreadDispatcher()).WillByDefault(ReturnRef(*dispatcher_)); + ON_CALL(context_, scope()).WillByDefault(ReturnRef(*stats_store_.rootScope())); + ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cm_)); + + ON_CALL(context_, localInfo()).WillByDefault(ReturnRef(local_info_)); + ON_CALL(local_info_, nodeName()).WillByDefault(ReturnRef(node_id)); + ON_CALL(local_info_, clusterName()).WillByDefault(ReturnRef(cluster_id)); + + ON_CALL(cm_, getThreadLocalCluster(_)).WillByDefault(Return(&thread_local_cluster_)); + ON_CALL(thread_local_cluster_, info()).WillByDefault(Return(cluster_info_)); + ON_CALL(*cluster_info_, statsScope()).WillByDefault(ReturnRef(*stats_store_.rootScope())); + + ON_CALL(cm_, grpcAsyncClientManager()).WillByDefault(ReturnRef(manager_)); + ON_CALL(manager_, getOrCreateRawAsyncClient(_, _, _)) + .WillByDefault(Return(absl::StatusOr(async_client_))); + } + +protected: + void inc_time(const Protobuf::Duration& dur) { + time_system_.advanceTimeAsyncImpl( + std::chrono::milliseconds(DurationUtil::durationToMilliseconds(dur))); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + void get_stream(int times) { + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)) + .Times(times) + .WillRepeatedly(Invoke([this](absl::string_view, absl::string_view, + Grpc::RawAsyncStreamCallbacks& callbacks, + const Http::AsyncClient::StreamOptions&) { + callbacks_ = &callbacks; + return async_stream_.get(); + })); + } + + GrpcClient::GrpcClientStats getStats() { + return GrpcClient::GrpcClientStats{context_, config_.stat_prefix(), config_.cluster()}; + } + + std::shared_ptr> async_client_{ + std::make_shared>()}; + std::unique_ptr> async_stream_{ + std::make_unique>()}; + Grpc::RawAsyncStreamCallbacks* callbacks_; + + NiceMock context_; + NiceMock cm_; + NiceMock thread_local_cluster_; + NiceMock local_info_; + NiceMock manager_; + std::shared_ptr> cluster_info_{ + std::make_shared>()}; + + Api::ApiPtr api_; + Event::SimulatedTimeSystem time_system_; + Event::DispatcherPtr dispatcher_; + Stats::IsolatedStoreImpl stats_store_; + + std::string node_id{"tunnel-v2"}; + std::string cluster_id{"tunnel-v2"}; + + GrpcConfigProto config_{mock_config()}; + NiceMock mock_reporter_; +}; + +// Check the connection behaviour on server initialization (infinite retries) +TEST_F(GrpcClientTest, RetryAttemptsOnStreamCreationFailure) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + + // The connection attempts shld not be bound by anything. + // Making it config_.max_retries + 2 for a simple check of not bound by max_retries. + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)) + .Times(config_.max_retries() + 2) + .WillRepeatedly(Return(nullptr)); + + client.onServerInitialized(&mock_reporter_); + + for (std::size_t i = 0; i < config_.max_retries() + 1; i++) { + inc_time(config_.connect_retry_interval()); + } + + // Not incremented because no connection attempt was successful. + // startRaw => nullptr. + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 0); + EXPECT_EQ(stats_.send_attempts_counter_.value(), 0); + EXPECT_EQ(stats_.nonce_acked_gauge_.value(), 0); + EXPECT_EQ(stats_.nonce_current_gauge_.value(), 0); + EXPECT_EQ(stats_ + .getCounter(stats_.disconnects_, + stats_.getTags( + Grpc::Status::WellKnownGrpcStatus::Internal, + GrpcDisconnectionReason::DisconnectReason::STREAM_CREATION_FAILED)) + .value(), + config_.max_retries() + 2); +} + +// Checks the happy path -> server connect and full push. +TEST_F(GrpcClientTest, ClientSendsFullPushOnConnect) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + + ReverseTunnelEvent::TunnelUpdates events{make_connections({"node_1"}), {}}; + + EXPECT_CALL(mock_reporter_, getAllConnections()).WillOnce(Return(events.connections)); + + get_stream(1); + + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .WillOnce(Invoke([&events](Buffer::InstancePtr& request, bool) { + auto resp = validate_req(request, events, true); + EXPECT_EQ(resp.request_nonce(), 1); + })); + + client.onServerInitialized(&mock_reporter_); + + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 1); + EXPECT_EQ(stats_.acks_received_counter_.value(), 0); + EXPECT_EQ(stats_.send_attempts_counter_.value(), 1); + EXPECT_EQ(stats_.events_dropped_counter_.value(), 0); + EXPECT_EQ(stats_.queued_updates_counter_.value(), 1); + EXPECT_EQ(stats_.out_of_order_acks_counter_.value(), 0); + EXPECT_EQ(stats_.nonce_current_gauge_.value(), 1); + EXPECT_EQ(stats_.nonce_acked_gauge_.value(), 0); + EXPECT_EQ(stats_.send_interval_gauge_.value(), + DurationUtil::durationToMilliseconds(config_.default_send_interval())); + EXPECT_EQ(stats_.sent_accepted_cnt_counter_.value(), 1); + EXPECT_EQ(stats_.sent_removed_cnt_counter_.value(), 0); +} + +// Checks the happy path -> Server up connect and send the diff after the full push +TEST_F(GrpcClientTest, ClientSendsDiffAfterFullPush) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + int cur = 0, total = 4; + + ReverseTunnelEvent::TunnelUpdates updates[] = { + ReverseTunnelEvent::TunnelUpdates{make_connections({"node_1"}), {}}, + ReverseTunnelEvent::TunnelUpdates{make_connections({"node_2"}), + make_disconnections({"node_1"})}, + ReverseTunnelEvent::TunnelUpdates{make_connections({"node_3", "node_4"}), {}}, + ReverseTunnelEvent::TunnelUpdates{make_connections({"node_5"}), + make_disconnections({"node_3"})}}; + + get_stream(1); + + EXPECT_CALL(mock_reporter_, getAllConnections()).WillOnce(Invoke([&updates]() { + return updates[0].connections; + })); + + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .Times(total) + .WillRepeatedly(Invoke([this, &updates, &cur](Buffer::InstancePtr& request, bool) { + auto resp = validate_req(request, updates[cur], cur == 0); + EXPECT_EQ(resp.request_nonce(), cur + 1); + callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); + })); + + client.onServerInitialized(&mock_reporter_); + cur++; + + for (; cur < total; cur++) { + client.receiveEvents(updates[cur]); + inc_time(config_.default_send_interval()); + } + + int total_events = 0; + for (int i = 0; i < total; i++) { + total_events += updates[i].size(); + } + + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 1); + EXPECT_EQ(stats_.acks_received_counter_.value(), total); + EXPECT_EQ(stats_.send_attempts_counter_.value(), total); + EXPECT_EQ(stats_.sent_accepted_cnt_counter_.value(), 5); + EXPECT_EQ(stats_.sent_removed_cnt_counter_.value(), 2); + EXPECT_EQ(stats_.events_dropped_counter_.value(), 0); + EXPECT_EQ(stats_.queued_updates_counter_.value(), total_events); + EXPECT_EQ(stats_.out_of_order_acks_counter_.value(), 0); + EXPECT_EQ(stats_.nonce_current_gauge_.value(), total); + EXPECT_EQ(stats_.nonce_acked_gauge_.value(), total); + EXPECT_EQ(stats_.send_interval_gauge_.value(), + DurationUtil::durationToMilliseconds(config_.default_send_interval())); +} + +// Check the happy path -> config changes from the server response is applied +TEST_F(GrpcClientTest, ReportIntervalChangesReflectInClient) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + int cur = 0; + + ReverseTunnelEvent::TunnelUpdates events; + + get_stream(1); + + EXPECT_CALL(mock_reporter_, getAllConnections()).WillOnce(Invoke([&events]() { + return events.connections; + })); + + // This should be called 3 times. + // Once for the first message and then twice for the next two increments. + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .Times(3) + .WillRepeatedly(Invoke([this, &events, &cur](Buffer::InstancePtr& request, bool) { + auto resp = validate_req(request, events, cur == 0); + EXPECT_EQ(resp.request_nonce(), ++cur); + + if (cur == 1) { + *resp.mutable_report_interval() = getHalfDuration(config_.default_send_interval()); + } + + callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); + })); + + EXPECT_EQ(stats_.send_interval_gauge_.value(), + DurationUtil::durationToMilliseconds(config_.default_send_interval())); + client.onServerInitialized(&mock_reporter_); + EXPECT_EQ(stats_.send_interval_gauge_.value(), + DurationUtil::durationToMilliseconds(getHalfDuration(config_.default_send_interval()))); + + // This is already scheduled from the next time we will use the half interval for sending. + inc_time(config_.default_send_interval()); + inc_time(getHalfDuration(config_.default_send_interval())); + + EXPECT_EQ(stats_.send_attempts_counter_.value(), 3); +} + +// Check edge case -> Full push and then diffs on reconnect +TEST_F(GrpcClientTest, FullPushAndDiffOnReconnect) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + int cur = 0, total = 4; + + ReverseTunnelEvent::TunnelUpdates updates[] = { + ReverseTunnelEvent::TunnelUpdates{make_connections({"node_1"}), {}}, + ReverseTunnelEvent::TunnelUpdates{make_connections({"node_2"}), + make_disconnections({"node_1"})}, + ReverseTunnelEvent::TunnelUpdates{make_connections({"node_3", "node_4"}), {}}, + ReverseTunnelEvent::TunnelUpdates{make_connections({"node_5"}), + make_disconnections({"node_3"})}}; + + // 2 stream creations: initial connect + reconnect after remote close. + get_stream(2); + + // getAllConnections is called once per full push (initial + reconnect). + EXPECT_CALL(mock_reporter_, getAllConnections()).Times(2).WillRepeatedly(Invoke([&updates]() { + return updates[0].connections; + })); + + // total + 1: the reconnect triggers an extra full push on top of the normal total sends. + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .Times(total + 1) + .WillRepeatedly(Invoke([this, &updates, &cur](Buffer::InstancePtr& request, bool) { + // cur is still 0 during both the initial connect and the reconnect full push, + // so both correctly validate as full_push=true against updates[0]. + auto resp = validate_req(request, updates[cur], cur == 0); + EXPECT_EQ(resp.request_nonce(), cur + 1); + callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); + })); + + client.onServerInitialized(&mock_reporter_); + + // disconnect and reconnect -> sends the full push automatically + callbacks_->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Unknown, "Testing"); + inc_time(config_.connect_retry_interval()); + cur++; // cur becomes 1 only after the reconnect full push has already fired. + + for (; cur < total; cur++) { + client.receiveEvents(updates[cur]); + inc_time(config_.default_send_interval()); + } + + int total_sz = 0; + for (int i = 0; i < total; i++) { + total_sz += updates[i].size(); + } + + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 2); + EXPECT_EQ(stats_.acks_received_counter_.value(), total + 1); + EXPECT_EQ(stats_.send_attempts_counter_.value(), total + 1); + // 6 = 1 (initial full push) + 1 (reconnect full push) + 1+2+1 from updates[1..3]. + EXPECT_EQ(stats_.sent_accepted_cnt_counter_.value(), 6); + EXPECT_EQ(stats_.sent_removed_cnt_counter_.value(), 2); + EXPECT_EQ(stats_.events_dropped_counter_.value(), 0); + // +updates[0].size(): the reconnect full push re-queues the initial connections. + EXPECT_EQ(stats_.queued_updates_counter_.value(), total_sz + updates[0].size()); + EXPECT_EQ(stats_.out_of_order_acks_counter_.value(), 0); + EXPECT_EQ(stats_.nonce_current_gauge_.value(), total); + EXPECT_EQ(stats_.nonce_acked_gauge_.value(), total); + EXPECT_EQ(stats_ + .getCounter(stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::Unknown, + GrpcDisconnectionReason::DisconnectReason::REMOTE_CLOSE)) + .value(), + 1); +} + +// Check edge case -> Disconnect on deadline exceeded +TEST_F(GrpcClientTest, DisconnectOnTooManyUnAckedRequests) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + std::size_t cur = 0; + + ReverseTunnelEvent::TunnelUpdates events; + get_stream(1); + + // max_retries + 1: the initial connect sends once, then max_retries timer ticks each send once. + // On the next timer tick send() sees (nonce_current_ - nonce_acked_) > max_retries and + // disconnects before calling sendMessage, so the total successful sends is max_retries + 1. + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .Times(config_.max_retries() + 1) + .WillRepeatedly(Invoke([&events, &cur](Buffer::InstancePtr& request, bool) { + auto resp = validate_req(request, events, cur == 0); + EXPECT_EQ(resp.request_nonce(), cur + 1); + + // No ACK is sent. It should eventually disconnect. + })); + + EXPECT_CALL(*async_stream_, resetStream()); + + client.onServerInitialized(&mock_reporter_); + cur++; + + // max_retries + 2: we need max_retries timer ticks for the sends, plus one more tick + // to trigger the disconnect check. The first send happens on connect (cur=0). + for (; cur < config_.max_retries() + 2; cur++) { + inc_time(config_.default_send_interval()); + } + + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 1); + EXPECT_EQ(stats_.acks_received_counter_.value(), 0); + EXPECT_EQ(stats_.send_attempts_counter_.value(), config_.max_retries() + 1); + EXPECT_EQ(stats_.nonce_current_gauge_.value(), config_.max_retries() + 1); + EXPECT_EQ(stats_.nonce_acked_gauge_.value(), 0); + EXPECT_EQ(stats_ + .getCounter( + stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::DeadlineExceeded, + GrpcDisconnectionReason::DisconnectReason::MAX_RETRIES_EXCEEDED)) + .value(), + 1); +} + +// Check edge case -> Disconnect with Server on NACK. +TEST_F(GrpcClientTest, DisconnectOnNack) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + + ReverseTunnelEvent::TunnelUpdates events; + get_stream(1); + + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .WillOnce(Invoke([this, &events](Buffer::InstancePtr& request, bool) { + auto resp = validate_req(request, events, true); + EXPECT_EQ(resp.request_nonce(), 1); + resp.mutable_error_detail()->set_code(Grpc::Status::WellKnownGrpcStatus::Unavailable); + callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); + })); + + EXPECT_CALL(*async_stream_, resetStream()); + + client.onServerInitialized(&mock_reporter_); + + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 1); + EXPECT_EQ(stats_.send_attempts_counter_.value(), 1); + EXPECT_EQ( + stats_ + .getCounter(stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::Aborted, + GrpcDisconnectionReason::DisconnectReason::NACK_RECEIVED)) + .value(), + 1); +} + +// Check edge case -> Disconnect on Buffer Full (Also ensure that full push has no limits) +TEST_F(GrpcClientTest, DisconnectOnBufferFull) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + + std::vector nodes; + for (std::size_t i = 0; i < config_.max_buffer_count() + 1; i++) { + nodes.push_back("node_" + std::to_string(i)); + } + + ReverseTunnelEvent::TunnelUpdates connect_events{make_connections(nodes), {}}; + get_stream(1); + + EXPECT_CALL(mock_reporter_, getAllConnections()).WillOnce([&connect_events]() { + return connect_events.connections; + }); + + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .WillOnce(Invoke([this, &connect_events](Buffer::InstancePtr& request, bool) { + auto resp = validate_req(request, connect_events, true); + EXPECT_EQ(resp.request_nonce(), 1); + callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); + })); + + EXPECT_CALL(*async_stream_, resetStream()); + + client.onServerInitialized(&mock_reporter_); + client.receiveEvents(connect_events); + + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 1); + EXPECT_EQ(stats_.send_attempts_counter_.value(), 1); + EXPECT_EQ(stats_.events_dropped_counter_.value(), nodes.size()); + EXPECT_EQ(stats_.sent_accepted_cnt_counter_.value(), nodes.size()); + EXPECT_EQ(stats_.sent_removed_cnt_counter_.value(), 0); + EXPECT_EQ(stats_.queued_updates_counter_.value(), nodes.size()); + EXPECT_EQ( + stats_ + .getCounter(stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::ResourceExhausted, + GrpcDisconnectionReason::DisconnectReason::BUFFER_OVERFLOW)) + .value(), + 1); +} + +// Check edge case -> Prev Nonce ignored +TEST_F(GrpcClientTest, OutOfOrderNonce) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + std::size_t cur = 0; + + ReverseTunnelEvent::TunnelUpdates events; + get_stream(1); + + // Send nonce=0 (already acked) for all responses to trigger out-of-order. + // nonce=0 is always <= nonce_acked_ (which starts at 0), so every response lands + // in the else branch and increments out_of_order_acks_counter_. + // Same +1/+2 arithmetic as DisconnectOnTooManyUnAckedRequests: max_retries + 1 sends + // succeed before the disconnect fires. + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .Times(config_.max_retries() + 1) + .WillRepeatedly(Invoke([this, &events, &cur](Buffer::InstancePtr& request, bool) { + auto resp = validate_req(request, events, cur == 0); + EXPECT_EQ(resp.request_nonce(), cur + 1); + + resp.set_request_nonce(0); + callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); + })); + + EXPECT_CALL(*async_stream_, resetStream()); + + client.onServerInitialized(&mock_reporter_); + cur++; + + for (; cur < config_.max_retries() + 2; cur++) { + inc_time(config_.default_send_interval()); + } + + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 1); + EXPECT_EQ(stats_.send_attempts_counter_.value(), config_.max_retries() + 1); + EXPECT_EQ(stats_.out_of_order_acks_counter_.value(), config_.max_retries() + 1); + EXPECT_EQ(stats_.nonce_current_gauge_.value(), config_.max_retries() + 1); + EXPECT_EQ(stats_.nonce_acked_gauge_.value(), 0); + EXPECT_EQ(stats_ + .getCounter( + stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::DeadlineExceeded, + GrpcDisconnectionReason::DisconnectReason::MAX_RETRIES_EXCEEDED)) + .value(), + 1); +} + +// Check edge case -> Skip Nonce +TEST_F(GrpcClientTest, SkipNonce) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + std::size_t cur = 0; + + ReverseTunnelEvent::TunnelUpdates events; + get_stream(1); + + // max_retries + 2: the normal max_retries + 1 sends that would trigger disconnect, + // but a late ACK at iteration max_retries advances nonce_acked_ and buys one more send. + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .Times(config_.max_retries() + 2) + .WillRepeatedly(Invoke([this, &events, &cur](Buffer::InstancePtr& request, bool) { + auto resp = validate_req(request, events, cur == 0); + EXPECT_EQ(resp.request_nonce(), cur + 1); + + // Only ACK the nonce at iteration max_retries, proving a single late ACK + // advances the watermark and prevents disconnect. + if (cur == config_.max_retries()) { + callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); + } + })); + + client.onServerInitialized(&mock_reporter_); + cur++; + + for (; cur < config_.max_retries() + 2; cur++) { + inc_time(config_.default_send_interval()); + } + + // After the stream is up, retroactively send ACKs for earlier nonces. + // These are all below nonce_acked_ now, so they count as out-of-order. + for (std::size_t i = 1; i <= config_.max_retries(); i++) { + StreamTunnelsResp resp; + resp.set_request_nonce(i); + callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); + } + + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 1); + EXPECT_EQ(stats_.send_attempts_counter_.value(), config_.max_retries() + 2); + EXPECT_EQ(stats_.out_of_order_acks_counter_.value(), config_.max_retries()); + EXPECT_EQ(stats_.acks_received_counter_.value(), 1); + EXPECT_EQ(stats_.nonce_current_gauge_.value(), config_.max_retries() + 2); + EXPECT_EQ(stats_.nonce_acked_gauge_.value(), config_.max_retries() + 1); +} + +// Edge case -> Remote close Status Ok +TEST_F(GrpcClientTest, OkRemoteClose) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + + ReverseTunnelEvent::TunnelUpdates events; + get_stream(1); + + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .WillOnce(Invoke([&events](Buffer::InstancePtr& request, bool) { + auto response = validate_req(request, events, true); + EXPECT_EQ(response.request_nonce(), 1); + })); + + EXPECT_CALL(*async_stream_, resetStream()); + + client.onServerInitialized(&mock_reporter_); + callbacks_->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Ok, "Testing"); + + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 1); + EXPECT_EQ(stats_.send_attempts_counter_.value(), 1); + EXPECT_EQ(stats_ + .getCounter(stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::Ok, + GrpcDisconnectionReason::DisconnectReason::REMOTE_CLOSE)) + .value(), + 1); +} + +// --- Hardening tests --- + +TEST_F(GrpcClientTest, ReceiveEventsBeforeInitialized) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + + ReverseTunnelEvent::TunnelUpdates events{make_connections({"node_1"}), {}}; + client.receiveEvents(std::move(events)); + + EXPECT_EQ(stats_.queued_updates_counter_.value(), 0); + EXPECT_EQ(stats_.events_dropped_counter_.value(), 0); +} + +TEST_F(GrpcClientTest, ClusterNotFoundLogsAndReturns) { + GrpcClient client{context_, config_}; + + EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + + client.onServerInitialized(&mock_reporter_); + + ReverseTunnelEvent::TunnelUpdates events{make_connections({"node_1"}), {}}; + client.receiveEvents(std::move(events)); + + auto stats_{getStats()}; + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 0); + EXPECT_EQ(stats_.queued_updates_counter_.value(), 0); +} + +TEST_F(GrpcClientTest, ClientCreationFailureLogsAndReturns) { + GrpcClient client{context_, config_}; + + EXPECT_CALL(manager_, getOrCreateRawAsyncClient(_, _, _)) + .WillOnce(Return(absl::InvalidArgumentError("Bad Karma"))); + + client.onServerInitialized(&mock_reporter_); + + ReverseTunnelEvent::TunnelUpdates events{make_connections({"node_1"}), {}}; + client.receiveEvents(std::move(events)); + + auto stats_{getStats()}; + EXPECT_EQ(stats_.connection_attempts_counter_.value(), 0); + EXPECT_EQ(stats_.queued_updates_counter_.value(), 0); +} + +TEST_F(GrpcClientTest, BufferOverflowWhileDisconnectedDoesNotRearmRetry) { + GrpcClient client{context_, config_}; + auto stats_{getStats()}; + + std::vector nodes; + for (std::size_t i = 0; i < config_.max_buffer_count() + 1; i++) { + nodes.push_back("node_" + std::to_string(i)); + } + + ReverseTunnelEvent::TunnelUpdates big_update{make_connections(nodes), {}}; + get_stream(1); + + EXPECT_CALL(mock_reporter_, getAllConnections()).WillOnce(Return(big_update.connections)); + + EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) + .WillOnce(Invoke([this, &big_update](Buffer::InstancePtr& request, bool) { + auto resp = validate_req(request, big_update, true); + callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); + })); + + EXPECT_CALL(*async_stream_, resetStream()); + + client.onServerInitialized(&mock_reporter_); + + // First overflow: stream is alive, should disconnect. + client.receiveEvents(ReverseTunnelEvent::TunnelUpdates{make_connections(nodes), {}}); + EXPECT_EQ( + stats_ + .getCounter(stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::ResourceExhausted, + GrpcDisconnectionReason::DisconnectReason::BUFFER_OVERFLOW)) + .value(), + 1); + + // Second overflow: stream is null, should NOT increment disconnect counter. + client.receiveEvents(ReverseTunnelEvent::TunnelUpdates{make_connections(nodes), {}}); + EXPECT_EQ( + stats_ + .getCounter(stats_.disconnects_, + stats_.getTags(Grpc::Status::WellKnownGrpcStatus::ResourceExhausted, + GrpcDisconnectionReason::DisconnectReason::BUFFER_OVERFLOW)) + .value(), + 1); + + // Events still counted as dropped both times. + EXPECT_EQ(stats_.events_dropped_counter_.value(), nodes.size() * 2); +} + +// Verify default values when proto fields are unset/zero. +TEST_F(GrpcClientTest, ConfigDefaults) { + GrpcConfigProto bare_config; + bare_config.set_cluster("test_cluster"); + + GrpcClientConfig parsed(bare_config); + + EXPECT_EQ(parsed.stat_prefix, "reverse_tunnel_reporter_client.grpc_client"); + EXPECT_EQ(parsed.cluster, "test_cluster"); + EXPECT_EQ(parsed.send_interval.count(), 5000); + EXPECT_EQ(parsed.connect_retry_interval.count(), 5000); + EXPECT_EQ(parsed.max_retries, 5); + EXPECT_EQ(parsed.max_buffer, 1000000); +} + +class GrpcClientFactoryTest : public testing::Test { +public: + void SetUp() override { + factory_ = Registry::FactoryRegistry::getFactory( + "envoy.extensions.reverse_tunnel.reverse_tunnel_reporting_service.clients.grpc_client"); + ASSERT_NE(nullptr, factory_); + + ON_CALL(context_, messageValidationVisitor()) + .WillByDefault(ReturnRef(ProtobufMessage::getStrictValidationVisitor())); + } + +protected: + ReverseTunnelReporterClientFactory* factory_{}; + NiceMock context_; +}; + +TEST_F(GrpcClientFactoryTest, Name) { + EXPECT_EQ("envoy.extensions.reverse_tunnel.reverse_tunnel_reporting_service.clients.grpc_client", + factory_->name()); +} + +TEST_F(GrpcClientFactoryTest, CreateEmptyConfigProto) { + auto config = factory_->createEmptyConfigProto(); + EXPECT_NE(nullptr, config); + EXPECT_NE(nullptr, dynamic_cast(config.get())); +} + +TEST_F(GrpcClientFactoryTest, CreateClientReturnsNonNull) { + Api::ApiPtr api = Api::createApiForTest(); + Event::DispatcherPtr dispatcher = api->allocateDispatcher("test"); + ON_CALL(context_, mainThreadDispatcher()).WillByDefault(ReturnRef(*dispatcher)); + Stats::IsolatedStoreImpl stats_store; + ON_CALL(context_, scope()).WillByDefault(ReturnRef(*stats_store.rootScope())); + + GrpcConfigProto config; + config.set_cluster("test_cluster"); + + auto client = factory_->createClient(context_, config); + EXPECT_NE(nullptr, client); +} + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy From 63e9671c7a456c88eb6139d9c3dd5331db05d124 Mon Sep 17 00:00:00 2001 From: Paul Ogilby Date: Wed, 29 Apr 2026 15:29:25 -0400 Subject: [PATCH 164/750] filters: Add per-route config to SetMetadataFilter (#44651) Risk Level: low Testing: unit Release Notes: added to current.yaml Signed-off-by: Paul Ogilby --- changelogs/current.yaml | 4 + .../filters/http/set_metadata/config.cc | 7 ++ .../filters/http/set_metadata/config.h | 6 + .../http/set_metadata/set_metadata_filter.cc | 97 +++++++++------- .../http/set_metadata/set_metadata_filter.h | 4 +- .../filters/http/set_metadata/config_test.cc | 24 ++++ .../set_metadata/set_metadata_filter_test.cc | 109 ++++++++++++++++++ 7 files changed, 208 insertions(+), 43 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 456b63e7e7f28..f8a8c45323655 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -52,6 +52,10 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: set_metadata_filter + change: | + Added :ref:`per-route configuration support ` + to the ``set_metadata`` HTTP filter. - area: dynamic_modules change: | Added ``envoy_dynamic_module_callback_is_validation_mode`` ABI callback that allows dynamic diff --git a/source/extensions/filters/http/set_metadata/config.cc b/source/extensions/filters/http/set_metadata/config.cc index 2c17079e280db..55bbb62f319ac 100644 --- a/source/extensions/filters/http/set_metadata/config.cc +++ b/source/extensions/filters/http/set_metadata/config.cc @@ -38,6 +38,13 @@ Http::FilterFactoryCb SetMetadataConfig::createFilterFactoryFromProtoWithServerC }; } +absl::StatusOr +SetMetadataConfig::createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::set_metadata::v3::Config& proto_config, + Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor&) { + return std::make_shared(proto_config, context.scope(), ""); +} + REGISTER_FACTORY(SetMetadataConfig, Server::Configuration::NamedHttpFilterConfigFactory); } // namespace SetMetadataFilter diff --git a/source/extensions/filters/http/set_metadata/config.h b/source/extensions/filters/http/set_metadata/config.h index 86b241d124221..900321e5212ed 100644 --- a/source/extensions/filters/http/set_metadata/config.h +++ b/source/extensions/filters/http/set_metadata/config.h @@ -27,6 +27,12 @@ class SetMetadataConfig const envoy::extensions::filters::http::set_metadata::v3::Config& proto_config, const std::string& stats_prefix, Server::Configuration::ServerFactoryContext& server_context) override; + + absl::StatusOr + createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::set_metadata::v3::Config& proto_config, + Server::Configuration::ServerFactoryContext& context, + ProtobufMessage::ValidationVisitor& validator) override; }; } // namespace SetMetadataFilter diff --git a/source/extensions/filters/http/set_metadata/set_metadata_filter.cc b/source/extensions/filters/http/set_metadata/set_metadata_filter.cc index 24d695f630b77..778470fab405c 100644 --- a/source/extensions/filters/http/set_metadata/set_metadata_filter.cc +++ b/source/extensions/filters/http/set_metadata/set_metadata_filter.cc @@ -13,6 +13,54 @@ namespace Extensions { namespace HttpFilters { namespace SetMetadataFilter { +namespace { + +void ApplyConfigToMetadata(const Config& config, + envoy::config::core::v3::Metadata& dynamic_metadata) { + + // Add configured untyped metadata. + if (!config.untyped().empty()) { + auto& mut_untyped_metadata = *dynamic_metadata.mutable_filter_metadata(); + + for (const auto& entry : config.untyped()) { + if (!mut_untyped_metadata.contains(entry.metadata_namespace)) { + // Insert the new entry. + mut_untyped_metadata[entry.metadata_namespace] = entry.value; + } else if (entry.allow_overwrite) { + // Get the existing metadata at this key for merging. + Protobuf::Struct& orig_fields = mut_untyped_metadata[entry.metadata_namespace]; + const auto& to_merge = entry.value; + + // Merge the new metadata into the existing metadata. + StructUtil::update(orig_fields, to_merge); + } else { + // The entry exists, and we are not allowed to overwrite -- emit a stat. + config.stats().overwrite_denied_.inc(); + } + } + } + + // Add configured typed metadata. + if (!config.typed().empty()) { + auto& mut_typed_metadata = *dynamic_metadata.mutable_typed_filter_metadata(); + + for (const auto& entry : config.typed()) { + if (!mut_typed_metadata.contains(entry.metadata_namespace)) { + // Insert the new entry. + mut_typed_metadata[entry.metadata_namespace] = entry.value; + } else if (entry.allow_overwrite) { + // Overwrite the existing typed metadata at this key. + mut_typed_metadata[entry.metadata_namespace] = entry.value; + } else { + // The entry exists, and we are not allowed to overwrite -- emit a stat. + config.stats().overwrite_denied_.inc(); + } + } + } +} + +} // namespace + Config::Config(const envoy::extensions::filters::http::set_metadata::v3::Config& proto_config, Stats::Scope& scope, const std::string& stats_prefix) : stats_(generateStats(stats_prefix, scope)) { @@ -48,47 +96,14 @@ SetMetadataFilter::SetMetadataFilter(const ConfigSharedPtr config) : config_(con SetMetadataFilter::~SetMetadataFilter() = default; Http::FilterHeadersStatus SetMetadataFilter::decodeHeaders(Http::RequestHeaderMap&, bool) { - - // Add configured untyped metadata. - if (!config_->untyped().empty()) { - auto& mut_untyped_metadata = - *decoder_callbacks_->streamInfo().dynamicMetadata().mutable_filter_metadata(); - - for (const auto& entry : config_->untyped()) { - if (!mut_untyped_metadata.contains(entry.metadata_namespace)) { - // Insert the new entry. - mut_untyped_metadata[entry.metadata_namespace] = entry.value; - } else if (entry.allow_overwrite) { - // Get the existing metadata at this key for merging. - Protobuf::Struct& orig_fields = mut_untyped_metadata[entry.metadata_namespace]; - const auto& to_merge = entry.value; - - // Merge the new metadata into the existing metadata. - StructUtil::update(orig_fields, to_merge); - } else { - // The entry exists, and we are not allowed to overwrite -- emit a stat. - config_->stats().overwrite_denied_.inc(); - } - } - } - - // Add configured typed metadata. - if (!config_->typed().empty()) { - auto& mut_typed_metadata = - *decoder_callbacks_->streamInfo().dynamicMetadata().mutable_typed_filter_metadata(); - - for (const auto& entry : config_->typed()) { - if (!mut_typed_metadata.contains(entry.metadata_namespace)) { - // Insert the new entry. - mut_typed_metadata[entry.metadata_namespace] = entry.value; - } else if (entry.allow_overwrite) { - // Overwrite the existing typed metadata at this key. - mut_typed_metadata[entry.metadata_namespace] = entry.value; - } else { - // The entry exists, and we are not allowed to overwrite -- emit a stat. - config_->stats().overwrite_denied_.inc(); - } - } + // Apply global config first. + ApplyConfigToMetadata(*config_, decoder_callbacks_->streamInfo().dynamicMetadata()); + + // Apply route-specific config if present. + const Config* route_specific_cfg = + dynamic_cast(decoder_callbacks_->mostSpecificPerFilterConfig()); + if (route_specific_cfg) { + ApplyConfigToMetadata(*route_specific_cfg, decoder_callbacks_->streamInfo().dynamicMetadata()); } return Http::FilterHeadersStatus::Continue; diff --git a/source/extensions/filters/http/set_metadata/set_metadata_filter.h b/source/extensions/filters/http/set_metadata/set_metadata_filter.h index 9aff8a68650e3..8250602ce8bf2 100644 --- a/source/extensions/filters/http/set_metadata/set_metadata_filter.h +++ b/source/extensions/filters/http/set_metadata/set_metadata_filter.h @@ -40,8 +40,8 @@ class Config : public ::Envoy::Router::RouteSpecificFilterConfig, Config(const envoy::extensions::filters::http::set_metadata::v3::Config& config, Stats::Scope& scope, const std::string& stats_prefix); - const std::vector& untyped() { return untyped_; } - const std::vector& typed() { return typed_; } + const std::vector& untyped() const { return untyped_; } + const std::vector& typed() const { return typed_; } const FilterStats& stats() const { return stats_; } private: diff --git a/test/extensions/filters/http/set_metadata/config_test.cc b/test/extensions/filters/http/set_metadata/config_test.cc index f545520021553..974a89fe0a3e8 100644 --- a/test/extensions/filters/http/set_metadata/config_test.cc +++ b/test/extensions/filters/http/set_metadata/config_test.cc @@ -84,6 +84,30 @@ TEST(SetMetadataFilterConfigTest, SimpleConfigServerContext) { cb(filter_callbacks); } +TEST(SetMetadataFilterConfigTest, CreateRouteSpecificConfig) { + const std::string yaml = R"EOF( +metadata: +- metadata_namespace: thenamespace + value: + mynumber: 20 + mylist: ["b"] + tags: + mytag1: 1 + allow_overwrite: true + )EOF"; + + SetMetadataProtoConfig proto_config; + TestUtility::loadFromYamlAndValidate(yaml, proto_config); + + testing::NiceMock context; + SetMetadataConfig factory; + + auto& validation_visitor = ProtobufMessage::getNullValidationVisitor(); + const auto result = + factory.createRouteSpecificFilterConfig(proto_config, context, validation_visitor); + EXPECT_TRUE(result.ok()); +} + } // namespace SetMetadataFilter } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/set_metadata/set_metadata_filter_test.cc b/test/extensions/filters/http/set_metadata/set_metadata_filter_test.cc index 11b89d1df4b41..fbd4068ac96b6 100644 --- a/test/extensions/filters/http/set_metadata/set_metadata_filter_test.cc +++ b/test/extensions/filters/http/set_metadata/set_metadata_filter_test.cc @@ -449,6 +449,115 @@ TEST_F(SetMetadataFilterTest, LogsErrorWhenNoValueConfigured) { runFilter(metadata, yaml_config)); } +TEST_F(SetMetadataFilterTest, RouteSpecificConfigOverride) { + const std::string global_yaml = R"EOF( + metadata: + - metadata_namespace: thenamespace + value: + global_key: global_val + )EOF"; + + const std::string route_yaml = R"EOF( + metadata: + - metadata_namespace: thenamespace + value: + route_key: route_val + allow_overwrite: true + )EOF"; + + envoy::extensions::filters::http::set_metadata::v3::Config proto_global_config; + TestUtility::loadFromYaml(global_yaml, proto_global_config); + NiceMock local_stats_store; + config_ = std::make_shared(proto_global_config, *local_stats_store.rootScope(), ""); + filter_ = std::make_shared(config_); + + envoy::extensions::filters::http::set_metadata::v3::Config proto_route_config; + TestUtility::loadFromYaml(route_yaml, proto_route_config); + auto route_config = + std::make_shared(proto_route_config, *local_stats_store.rootScope(), ""); + + Http::TestRequestHeaderMapImpl headers; + + NiceMock decoder_callbacks; + NiceMock req_info; + filter_->setDecoderFilterCallbacks(decoder_callbacks); + + EXPECT_CALL(decoder_callbacks, mostSpecificPerFilterConfig()) + .WillRepeatedly(testing::Return(route_config.get())); + + envoy::config::core::v3::Metadata metadata; + EXPECT_CALL(decoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(req_info)); + EXPECT_CALL(req_info, dynamicMetadata()).WillRepeatedly(ReturnRef(metadata)); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); + + const auto& filter_metadata = metadata.filter_metadata(); + const auto it_namespace = filter_metadata.find("thenamespace"); + ASSERT_NE(it_namespace, filter_metadata.end()); + const auto& fields = it_namespace->second.fields(); + + const auto it_global = fields.find("global_key"); + ASSERT_NE(it_global, fields.end()); + EXPECT_EQ(it_global->second.string_value(), "global_val"); + + const auto it_route = fields.find("route_key"); + ASSERT_NE(it_route, fields.end()); + EXPECT_EQ(it_route->second.string_value(), "route_val"); +} + +TEST_F(SetMetadataFilterTest, RouteSpecificConfigOverwriteDenied) { + const std::string global_yaml = R"EOF( + metadata: + - metadata_namespace: thenamespace + value: + key: global_val + )EOF"; + + const std::string route_yaml = R"EOF( + metadata: + - metadata_namespace: thenamespace + value: + key: route_val + )EOF"; + + envoy::extensions::filters::http::set_metadata::v3::Config proto_global_config; + TestUtility::loadFromYaml(global_yaml, proto_global_config); + NiceMock local_stats_store; + config_ = std::make_shared(proto_global_config, *local_stats_store.rootScope(), ""); + filter_ = std::make_shared(config_); + + envoy::extensions::filters::http::set_metadata::v3::Config proto_route_config; + TestUtility::loadFromYaml(route_yaml, proto_route_config); + auto route_config = + std::make_shared(proto_route_config, *local_stats_store.rootScope(), ""); + + Http::TestRequestHeaderMapImpl headers; + + NiceMock decoder_callbacks; + NiceMock req_info; + filter_->setDecoderFilterCallbacks(decoder_callbacks); + + EXPECT_CALL(decoder_callbacks, mostSpecificPerFilterConfig()) + .WillRepeatedly(testing::Return(route_config.get())); + + envoy::config::core::v3::Metadata metadata; + EXPECT_CALL(decoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(req_info)); + EXPECT_CALL(req_info, dynamicMetadata()).WillRepeatedly(ReturnRef(metadata)); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); + + const auto& filter_metadata = metadata.filter_metadata(); + const auto it_namespace = filter_metadata.find("thenamespace"); + ASSERT_NE(it_namespace, filter_metadata.end()); + const auto& fields = it_namespace->second.fields(); + + const auto it_global = fields.find("key"); + ASSERT_NE(it_global, fields.end()); + EXPECT_EQ(it_global->second.string_value(), "global_val"); + + EXPECT_EQ(1, route_config->stats().overwrite_denied_.value()); +} + } // namespace SetMetadataFilter } // namespace HttpFilters } // namespace Extensions From 8a8e2179429599fb7c9aa5de3956002442cb3736 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 29 Apr 2026 22:27:25 +0100 Subject: [PATCH 165/750] tests: Dont include what you dont use (#44730) Signed-off-by: Ryan Northey --- contrib/golang/filters/http/test/BUILD | 2 ++ contrib/golang/filters/http/test/config_test.cc | 1 + contrib/sxg/filters/http/test/BUILD | 1 + contrib/sxg/filters/http/test/config_test.cc | 1 + .../extensions/cert_validator/platform_bridge/BUILD | 2 +- .../platform_bridge_cert_validator_test.cc | 1 - .../extensions/stat_sinks/metrics_service/BUILD | 2 -- .../metrics_service/mobile_grpc_streamer_test.cc | 2 -- mobile/test/common/http/BUILD | 1 - mobile/test/common/integration/BUILD | 1 + mobile/test/common/stream_info/BUILD | 1 - .../common/stream_info/extra_stream_info_test.cc | 2 -- test/common/access_log/BUILD | 2 -- test/common/access_log/access_log_impl_test.cc | 2 -- .../access_log/access_log_manager_impl_test.cc | 1 - test/common/config/BUILD | 2 +- test/common/config/subscription_test_harness.h | 2 +- test/common/config/utility_test.cc | 1 - test/common/filter/BUILD | 2 -- test/common/filter/config_discovery_impl_test.cc | 2 -- .../common/formatter/substitution_formatter_test.cc | 1 - test/common/grpc/BUILD | 8 -------- test/common/grpc/async_client_manager_benchmark.cc | 2 -- test/common/grpc/async_client_manager_impl_test.cc | 1 - test/common/grpc/buffered_async_client_test.cc | 1 - test/common/grpc/google_async_client_impl_test.cc | 1 - test/common/grpc/google_grpc_creds_test.cc | 1 - .../grpc/grpc_client_integration_test_harness.h | 2 -- test/common/http/codec_client_test.cc | 1 - test/common/http/common.h | 1 - test/common/http/http1/BUILD | 5 ----- test/common/http/http1/conn_pool_test.cc | 1 - test/common/http/http2/BUILD | 5 ----- test/common/http/http2/codec_impl_test.cc | 5 ----- test/common/http/http2/metadata_encoder_test.cc | 1 - test/common/http/http3/BUILD | 1 - test/common/http/inline_cookie_test.cc | 1 - test/common/listener_manager/BUILD | 2 -- .../filter_chain_manager_impl_test.cc | 2 -- test/common/network/BUILD | 1 - test/common/network/filter_manager_impl_test.cc | 2 -- .../network/io_uring_socket_handle_impl_test.cc | 1 - .../network/listener_filter_buffer_fuzz_test.cc | 1 - test/common/network/udp_fuzz.cc | 1 - .../network/udp_listener_impl_batch_writer_test.cc | 1 - test/common/protobuf/BUILD | 3 --- test/common/protobuf/utility_test.cc | 3 --- test/common/quic/BUILD | 3 +-- test/common/quic/active_quic_listener_test.cc | 2 -- test/common/quic/envoy_quic_client_session_test.cc | 1 - test/common/quic/envoy_quic_server_session_test.cc | 1 - test/common/quic/platform/BUILD | 1 - test/common/quic/platform/quic_platform_test.cc | 1 - test/common/quic/quic_stat_names_test.cc | 2 +- test/common/router/BUILD | 13 +------------ test/common/router/header_formatter_test.cc | 2 -- test/common/router/rds_impl_test.cc | 1 - test/common/router/retry_state_impl_test.cc | 1 - test/common/router/router_test.cc | 3 --- test/common/router/router_test_base.h | 1 - test/common/router/router_upstream_filter_test.cc | 4 ---- test/common/router/router_upstream_log_test.cc | 3 --- test/common/router/vhds_test.cc | 2 -- test/common/runtime/runtime_impl_test.cc | 1 - test/common/secret/BUILD | 1 - test/common/secret/sds_api_test.cc | 1 - test/common/stats/real_thread_test_base.h | 1 - test/common/stats/stat_test_utility.h | 1 - test/common/stats/symbol_table_speed_test.cc | 1 - test/common/stats/thread_local_store_speed_test.cc | 1 - test/common/tcp/BUILD | 1 - test/common/tcp/async_tcp_client_impl_test.cc | 1 - test/common/tcp_proxy/BUILD | 2 -- test/common/tcp_proxy/tcp_proxy_test.cc | 1 - test/common/tcp_proxy/tcp_proxy_test_base.h | 2 -- test/common/tcp_proxy/upstream_test.cc | 1 - test/common/tls/BUILD | 5 ----- test/common/tls/context_impl_test.cc | 1 - test/common/tls/integration/BUILD | 2 -- test/common/tls/integration/ssl_integration_test.cc | 1 - .../tls/integration/ssl_integration_test_base.h | 1 - test/common/tls/ssl_socket_test.cc | 2 -- test/common/tls/tls_certificate_selector_test.cc | 5 ----- test/common/tracing/BUILD | 8 -------- test/common/tracing/http_tracer_impl_test.cc | 5 ----- test/common/tracing/tracer_impl_test.cc | 3 --- test/common/tracing/tracer_manager_impl_test.cc | 1 - test/common/upstream/BUILD | 8 -------- test/common/upstream/cluster_factory_impl_test.cc | 3 +-- test/common/upstream/cluster_manager_impl_test.cc | 1 - .../upstream/cluster_manager_impl_test_common.h | 1 - .../deferred_cluster_initialization_test.cc | 1 - test/common/upstream/hds_test.cc | 3 --- test/common/upstream/health_checker_impl_test.cc | 1 - .../upstream/prod_cluster_info_factory_test.cc | 1 - test/common/upstream/test_cluster_manager.h | 8 -------- test/common/upstream/upstream_impl_test.cc | 2 -- test/exe/BUILD | 6 ------ test/extensions/access_loggers/common/BUILD | 2 -- .../common/grpc_access_logger_test.cc | 2 -- .../extensions/access_loggers/dynamic_modules/BUILD | 1 - .../dynamic_modules/access_log_test.cc | 1 - .../access_loggers/filters/process_ratelimit/BUILD | 2 -- .../process_ratelimit/access_log_impl_test.cc | 1 - .../filters/process_ratelimit/integration_test.cc | 1 - test/extensions/access_loggers/fluentd/BUILD | 1 - .../fluentd/fluentd_access_log_integration_test.cc | 1 - .../fluentd/substitution_formatter_test.cc | 1 - test/extensions/access_loggers/grpc/BUILD | 1 - .../grpc/http_grpc_access_log_impl_test.cc | 2 -- test/extensions/access_loggers/open_telemetry/BUILD | 3 --- .../open_telemetry/access_log_impl_test.cc | 3 --- .../open_telemetry/http_access_log_impl_test.cc | 2 -- test/extensions/access_loggers/stats/stats_test.cc | 2 -- .../bootstrap/reverse_tunnel/common/BUILD | 1 - .../common/reverse_connection_utility_test.cc | 1 - .../downstream_socket_interface/BUILD | 5 ----- .../downstream_reverse_connection_io_handle_test.cc | 1 - .../rc_connection_wrapper_test.cc | 1 - .../reverse_connection_io_handle_test.cc | 1 - .../reverse_tunnel_initiator_extension_test.cc | 1 - .../reverse_tunnel_initiator_test.cc | 1 - .../reverse_tunnel/upstream_socket_interface/BUILD | 5 ----- .../reverse_tunnel_acceptor_extension_test.cc | 1 - .../reverse_tunnel_acceptor_test.cc | 1 - .../upstream_reverse_connection_io_handle_test.cc | 1 - .../upstream_socket_manager_test.cc | 1 - test/extensions/bootstrap/wasm/BUILD | 2 -- test/extensions/bootstrap/wasm/config_test.cc | 2 -- test/extensions/clusters/aggregate/BUILD | 5 ----- .../clusters/aggregate/cluster_integration_test.cc | 1 - test/extensions/clusters/aggregate/cluster_test.cc | 2 -- .../clusters/aggregate/cluster_update_test.cc | 2 -- test/extensions/clusters/common/BUILD | 1 - .../extensions/clusters/common/logical_host_test.cc | 1 - test/extensions/clusters/composite/BUILD | 1 - .../extensions/clusters/dynamic_forward_proxy/BUILD | 1 - .../clusters/dynamic_forward_proxy/cluster_test.cc | 1 - test/extensions/clusters/dynamic_modules/BUILD | 1 - .../clusters/dynamic_modules/cluster_test.cc | 1 - test/extensions/clusters/eds/eds_speed_test.cc | 2 -- test/extensions/clusters/eds/eds_test.cc | 2 -- test/extensions/clusters/eds/leds_test.cc | 1 - test/extensions/clusters/logical_dns/BUILD | 4 ---- .../logical_dns/logical_dns_cluster_test.cc | 4 ---- test/extensions/clusters/mcp_multicluster/BUILD | 1 - test/extensions/clusters/original_dst/BUILD | 4 ---- .../original_dst/original_dst_cluster_test.cc | 3 --- test/extensions/clusters/redis/BUILD | 8 -------- .../extensions/clusters/redis/redis_cluster_test.cc | 1 - test/extensions/clusters/reverse_connection/BUILD | 2 -- .../reverse_connection_cluster_test.cc | 3 --- test/extensions/common/aws/BUILD | 2 -- test/extensions/common/dynamic_forward_proxy/BUILD | 1 - .../dynamic_forward_proxy/dns_cache_impl_test.cc | 4 ---- .../proxy_protocol/proxy_protocol_header_test.cc | 1 - .../common/redis/cluster_refresh_manager_test.cc | 1 - test/extensions/common/wasm/BUILD | 1 - .../config/validators/minimum_clusters/BUILD | 1 - .../minimum_clusters_validator_integration_test.cc | 1 - .../filesystem/filesystem_subscription_impl_test.cc | 1 + test/extensions/config_subscription/grpc/BUILD | 7 ------- .../grpc/delta_subscription_test_harness.h | 1 - .../grpc/eds_resources_cache_impl_test.cc | 1 - .../config_subscription/grpc/grpc_mux_impl_test.cc | 1 - .../grpc/new_grpc_mux_impl_test.cc | 1 - .../grpc/sotw_subscription_state_test.cc | 1 - .../grpc/xds_failover_integration_test.cc | 1 - .../grpc/xds_grpc_mux_impl_test.cc | 1 - test/extensions/config_subscription/rest/BUILD | 1 - .../rest/http_subscription_test_harness.h | 1 - test/extensions/dynamic_modules/http/BUILD | 1 - test/extensions/dynamic_modules/http/filter_test.cc | 1 - .../filters/common/rbac/engine_impl_test.cc | 1 - .../adaptive_concurrency_filter_integration_test.cc | 1 - .../extensions/filters/http/admission_control/BUILD | 1 - .../filters/http/admission_control/config_test.cc | 1 - .../filters/http/aws_request_signing/BUILD | 1 + .../aws_request_signing_integration_test.cc | 1 - test/extensions/filters/http/buffer/BUILD | 4 ---- .../cache/http_cache_implementation_test_common.cc | 1 - .../cache/http_cache_implementation_test_common.h | 1 - .../http_cache_implementation_test_common.cc | 1 - .../http_cache_implementation_test_common.h | 1 - test/extensions/filters/http/common/BUILD | 1 - .../filters/http/common/stream_rate_limiter_test.cc | 1 - .../http/compressor/compressor_filter_speed_test.cc | 1 - .../http/compressor/compressor_filter_test.cc | 1 - .../filters/http/cors/cors_filter_test.cc | 1 - .../filters/http/credential_injector/BUILD | 1 - .../filters/http/csrf/csrf_filter_test.cc | 1 - test/extensions/filters/http/custom_response/BUILD | 2 -- .../custom_response/custom_response_filter_test.cc | 3 --- test/extensions/filters/http/decompressor/BUILD | 2 -- .../decompressor_filter_integration_test.cc | 1 - .../http/decompressor/decompressor_filter_test.cc | 2 -- .../filters/http/dynamic_forward_proxy/BUILD | 2 ++ test/extensions/filters/http/ext_authz/BUILD | 2 -- .../filters/http/ext_authz/ext_authz_test.cc | 2 -- test/extensions/filters/http/ext_proc/BUILD | 3 --- .../ext_proc_full_duplex_integration_test.cc | 1 - .../http/ext_proc/ext_proc_integration_test.cc | 1 - .../ext_proc_observability_integration_test.cc | 1 - .../extensions/filters/http/ext_proc/filter_test.cc | 3 --- .../filters/http/ext_proc/matching_utils_test.cc | 1 - .../filters/http/ext_proc/ordering_test.cc | 1 - .../filters/http/file_system_buffer/filter_test.cc | 1 - test/extensions/filters/http/gcp_authn/BUILD | 1 - .../filters/http/gcp_authn/gcp_authn_filter_test.cc | 2 -- .../http/geoip/geoip_filter_integration_test.cc | 1 - .../reverse_bridge_test.cc | 2 -- test/extensions/filters/http/header_mutation/BUILD | 1 - .../header_to_metadata_filter_test.cc | 1 - .../http/jwt_authn/filter_integration_test.cc | 1 - test/extensions/filters/http/lua/wrappers_test.cc | 1 - test/extensions/filters/http/mcp_router/BUILD | 1 - test/extensions/filters/http/oauth2/BUILD | 1 + test/extensions/filters/http/oauth2/config_test.cc | 1 + test/extensions/filters/http/on_demand/BUILD | 1 - .../filters/http/on_demand/on_demand_filter_test.cc | 1 - .../filters/http/proto_api_scrubber/BUILD | 2 -- .../filters/http/proto_api_scrubber/filter_test.cc | 2 -- .../http/proto_api_scrubber/integration_test.cc | 1 - .../proto_api_scrubber/scrubbing_util_lib/BUILD | 1 - .../filters/http/rate_limit_quota/client_test.cc | 1 - test/extensions/filters/http/ratelimit/BUILD | 3 --- .../filters/http/ratelimit/ratelimit_test.cc | 3 --- test/extensions/filters/http/rbac/BUILD | 1 - .../filters/http/rbac/rbac_filter_fuzz_test.cc | 1 - test/extensions/filters/http/set_metadata/BUILD | 1 - test/extensions/filters/http/stateful_session/BUILD | 1 - test/extensions/filters/http/transform/BUILD | 1 - .../filters/listener/http_inspector/BUILD | 3 --- .../listener/http_inspector/http_inspector_test.cc | 2 -- .../filters/listener/local_ratelimit/BUILD | 1 - .../local_ratelimit/local_ratelimit_test.cc | 1 - .../extensions/filters/listener/tls_inspector/BUILD | 3 --- .../tls_inspector/tls_inspector_ja4_test.cc | 1 - .../listener/tls_inspector/tls_inspector_test.cc | 1 - test/extensions/filters/network/common/redis/BUILD | 1 - .../filters/network/direct_response/BUILD | 1 - .../network/direct_response/direct_response_test.cc | 1 - test/extensions/filters/network/ext_authz/BUILD | 3 --- .../network/ext_authz/ext_authz_fuzz_test.cc | 1 - .../filters/network/ext_authz/ext_authz_test.cc | 2 -- test/extensions/filters/network/generic_proxy/BUILD | 1 - .../filters/network/generic_proxy/proxy_test.cc | 1 - .../filters/network/generic_proxy/router/BUILD | 2 -- .../network/generic_proxy/router/router_test.cc | 1 - .../network/generic_proxy/router/upstream_test.cc | 1 - test/extensions/filters/network/geoip/BUILD | 1 - .../filters/network/geoip/geoip_filter_test.cc | 1 - .../filters/network/http_connection_manager/BUILD | 3 ++- .../config_filter_dependencies_test.cc | 1 + .../http_connection_manager/config_test_base.h | 1 - test/extensions/filters/network/ratelimit/BUILD | 1 - .../filters/network/ratelimit/ratelimit_test.cc | 1 - test/extensions/filters/network/redis_proxy/BUILD | 1 - .../network/redis_proxy/command_split_speed_test.cc | 2 -- test/extensions/filters/network/thrift_proxy/BUILD | 1 - .../filters/ratelimit/ratelimit_test.cc | 1 - .../network/thrift_proxy/shadow_writer_test.cc | 1 - .../udp/dns_filter/dns_filter_access_log_test.cc | 1 - .../filters/udp/dns_filter/dns_filter_fuzz_test.cc | 2 -- .../filters/udp/dns_filter/dns_filter_test.cc | 1 - test/extensions/filters/udp/udp_proxy/BUILD | 1 - .../session_filters/dynamic_forward_proxy/BUILD | 1 - .../proxy_filter_integration_test.cc | 1 - .../udp_proxy/session_filters/http_capsule/BUILD | 1 - .../http_capsule/http_capsule_integration_test.cc | 1 - ..._session_extension_discovery_integration_test.cc | 1 - .../formatter/metadata/integration_test.cc | 1 - .../formatter/xfcc_value/xfcc_value_fuzz_test.cc | 1 - .../geoip_providers/maxmind/geoip_provider_test.cc | 1 - .../event_sinks/file/file_sink_impl_test.cc | 2 -- test/extensions/health_checkers/thrift/BUILD | 1 - test/extensions/http/cache/simple_http_cache/BUILD | 1 - .../simple_http_cache/simple_http_cache_test.cc | 1 - .../http/cache_v2/simple_http_cache/BUILD | 1 - .../simple_http_cache/simple_http_cache_test.cc | 1 - .../header_mutation_integration_test.cc | 1 - .../http/header_formatters/preserve_case/BUILD | 1 + ...case_formatter_reason_phrase_integration_test.cc | 1 - test/extensions/io_socket/user_space/BUILD | 1 - .../user_space/connection_compatbility_test.cc | 2 -- .../load_balancing_policies/override_host/BUILD | 1 - .../load_balancing_policies/ring_hash/BUILD | 1 - .../ring_hash/ring_hash_lb_test.cc | 1 - .../extensions/load_balancing_policies/subset/BUILD | 2 -- .../load_balancing_policies/subset/subset_test.cc | 2 -- test/extensions/network/dns_resolver/hickory/BUILD | 1 - .../dns_resolver/hickory/hickory_dns_impl_test.cc | 1 - .../quic/connection_id_generator/quic_lb/BUILD | 1 + .../connection_id_generator/quic_lb/quic_lb_test.cc | 1 + test/extensions/request_id/uuid/BUILD | 1 - test/extensions/stats_sinks/metrics_service/BUILD | 2 -- .../grpc_metrics_service_impl_test.cc | 2 -- test/extensions/stats_sinks/open_telemetry/BUILD | 1 - .../open_telemetry/open_telemetry_impl_test.cc | 1 - test/extensions/tracers/datadog/BUILD | 2 -- test/extensions/tracers/datadog/config_test.cc | 3 --- test/extensions/tracers/fluentd/BUILD | 3 --- .../tracers/fluentd/tracer_integration_test.cc | 1 - test/extensions/tracers/opentelemetry/BUILD | 4 ---- .../opentelemetry/http_trace_exporter_test.cc | 1 - .../tracers/opentelemetry/operation_name_test.cc | 1 - test/extensions/tracers/skywalking/BUILD | 2 -- .../skywalking/trace_segment_reporter_test.cc | 1 - test/extensions/tracers/skywalking/tracer_test.cc | 1 - test/extensions/tracers/xray/BUILD | 2 -- test/extensions/tracers/zipkin/tracer_test.cc | 1 - test/extensions/transport_sockets/alts/BUILD | 2 -- .../watchdog/profile_action/profile_action_test.cc | 1 - test/fuzz/BUILD | 1 - test/fuzz/utility.cc | 2 -- test/integration/BUILD | 12 +++++++++--- test/integration/access_log_integration_test.cc | 1 - test/integration/cds_integration_test.cc | 1 - test/integration/clusters/BUILD | 1 - test/integration/clusters/custom_static_cluster.h | 1 - test/integration/h1_fuzz.h | 1 - test/integration/h2_fuzz.h | 1 - test/integration/hds_integration_test.cc | 1 - test/integration/http_integration.cc | 1 - test/integration/load_balancers/BUILD | 1 - test/integration/load_balancers/custom_lb_policy.h | 1 - .../original_ip_detection_integration_test.cc | 1 - test/integration/quic_http_integration_test.h | 1 - test/integration/sds_dynamic_integration_test.cc | 1 - test/integration/sds_static_integration_test.cc | 1 - test/integration/server.cc | 1 - test/integration/tcp_proxy_many_connections_test.cc | 1 - test/integration/vhds.h | 1 - test/integration/vhds_integration_test.cc | 1 - test/integration/xds_integration_test.cc | 1 + test/mocks/config/BUILD | 3 ++- test/mocks/config/eds_resources_cache.cc | 2 -- test/mocks/server/BUILD | 1 - test/mocks/server/health_checker_factory_context.h | 1 - test/mocks/server/overload_manager.cc | 2 -- test/mocks/server/server_factory_context.h | 1 - test/mocks/ssl/BUILD | 1 - test/mocks/ssl/mocks.h | 2 -- test/mocks/upstream/BUILD | 2 -- test/mocks/upstream/cluster_manager_factory.h | 1 - test/mocks/upstream/mocks.h | 2 -- test/server/BUILD | 1 - test/server/admin/BUILD | 2 -- test/server/admin/admin_instance.h | 1 - test/server/config_validation/BUILD | 8 -------- .../config_validation/cluster_manager_test.cc | 8 -------- test/server/options_impl_test.cc | 1 - test/server/server_fuzz_test.cc | 1 - test/test_common/BUILD | 3 --- test/test_common/test_runtime.h | 2 -- test/test_common/wasm_base.h | 1 - 356 files changed, 37 insertions(+), 581 deletions(-) diff --git a/contrib/golang/filters/http/test/BUILD b/contrib/golang/filters/http/test/BUILD index 3c5c9f2c96550..292a0b9252b8d 100644 --- a/contrib/golang/filters/http/test/BUILD +++ b/contrib/golang/filters/http/test/BUILD @@ -20,6 +20,7 @@ envoy_cc_test( deps = [ "//contrib/golang/filters/http/source:config", "//contrib/golang/filters/http/test/test_data/destroyconfig:destroyconfig_test_lib", + "//test/mocks/secret:secret_mocks", "//test/mocks/server:factory_context_mocks", "//test/test_common:utility_lib", ], @@ -63,6 +64,7 @@ envoy_cc_test( "//source/extensions/filters/http/lua:config", "//test/config:v2_link_hacks", "//test/integration:http_integration_lib", + "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", ], diff --git a/contrib/golang/filters/http/test/config_test.cc b/contrib/golang/filters/http/test/config_test.cc index 16f32324d9da2..51043676af545 100644 --- a/contrib/golang/filters/http/test/config_test.cc +++ b/contrib/golang/filters/http/test/config_test.cc @@ -2,6 +2,7 @@ #include "envoy/registry/registry.h" +#include "test/mocks/secret/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" diff --git a/contrib/sxg/filters/http/test/BUILD b/contrib/sxg/filters/http/test/BUILD index 7180c2b6cf112..0f94de970f848 100644 --- a/contrib/sxg/filters/http/test/BUILD +++ b/contrib/sxg/filters/http/test/BUILD @@ -36,6 +36,7 @@ envoy_cc_test( "//source/common/secret:secret_manager_impl_lib", "//source/extensions/filters/http/common:pass_through_filter_lib", "//test/integration:http_integration_lib", + "//test/mocks/secret:secret_mocks", "//test/mocks/server:server_mocks", "//test/mocks/upstream:upstream_mocks", "@envoy_api//contrib/envoy/extensions/filters/http/sxg/v3alpha:pkg_cc_proto", diff --git a/contrib/sxg/filters/http/test/config_test.cc b/contrib/sxg/filters/http/test/config_test.cc index c54517630bc93..25c45ece3946b 100644 --- a/contrib/sxg/filters/http/test/config_test.cc +++ b/contrib/sxg/filters/http/test/config_test.cc @@ -5,6 +5,7 @@ #include "source/common/protobuf/utility.h" #include "source/common/secret/secret_provider_impl.h" +#include "test/mocks/secret/mocks.h" #include "test/mocks/server/factory_context.h" #include "contrib/envoy/extensions/filters/http/sxg/v3alpha/sxg.pb.h" diff --git a/mobile/test/common/extensions/cert_validator/platform_bridge/BUILD b/mobile/test/common/extensions/cert_validator/platform_bridge/BUILD index 03c5cb928d0bf..1abd48daea4de 100644 --- a/mobile/test/common/extensions/cert_validator/platform_bridge/BUILD +++ b/mobile/test/common/extensions/cert_validator/platform_bridge/BUILD @@ -20,11 +20,11 @@ envoy_extension_cc_test( "//library/common/extensions/cert_validator/platform_bridge:config", "//library/common/extensions/cert_validator/platform_bridge:platform_bridge_cc_proto", "//test/common/mocks/common:common_mocks", + "@envoy//source/common/crypto:utility_lib", "@envoy//source/common/tls/cert_validator:cert_validator_lib", "@envoy//test/common/tls:ssl_test_utils", "@envoy//test/common/tls/cert_validator:test_common", "@envoy//test/common/tls/test_data:cert_infos", - "@envoy//test/mocks/event:event_mocks", "@envoy//test/mocks/ssl:ssl_mocks", "@envoy//test/mocks/thread:thread_mocks", "@envoy//test/test_common:environment_lib", diff --git a/mobile/test/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator_test.cc b/mobile/test/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator_test.cc index 16b3cfa6d8008..0a1770be2ef8e 100644 --- a/mobile/test/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator_test.cc +++ b/mobile/test/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator_test.cc @@ -13,7 +13,6 @@ #include "test/common/tls/cert_validator/test_common.h" #include "test/common/tls/ssl_test_utility.h" #include "test/common/tls/test_data/san_dns2_cert_info.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/thread/mocks.h" #include "test/test_common/environment.h" diff --git a/mobile/test/common/extensions/stat_sinks/metrics_service/BUILD b/mobile/test/common/extensions/stat_sinks/metrics_service/BUILD index 0e23af06dbb1e..514b856640547 100644 --- a/mobile/test/common/extensions/stat_sinks/metrics_service/BUILD +++ b/mobile/test/common/extensions/stat_sinks/metrics_service/BUILD @@ -18,7 +18,6 @@ envoy_extension_cc_test( "//library/common/extensions/stat_sinks/metrics_service:service_cc_proto", "@envoy//test/mocks/grpc:grpc_mocks", "@envoy//test/mocks/local_info:local_info_mocks", - "@envoy//test/mocks/thread_local:thread_local_mocks", "@envoy//test/test_common:logging_lib", ], ) @@ -35,7 +34,6 @@ envoy_extension_cc_test( "@envoy//test/integration:http_integration_lib_light", "@envoy//test/mocks/grpc:grpc_mocks", "@envoy//test/mocks/local_info:local_info_mocks", - "@envoy//test/mocks/thread_local:thread_local_mocks", "@envoy//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", ], diff --git a/mobile/test/common/extensions/stat_sinks/metrics_service/mobile_grpc_streamer_test.cc b/mobile/test/common/extensions/stat_sinks/metrics_service/mobile_grpc_streamer_test.cc index e287cdfb956c8..3c50d083f3851 100644 --- a/mobile/test/common/extensions/stat_sinks/metrics_service/mobile_grpc_streamer_test.cc +++ b/mobile/test/common/extensions/stat_sinks/metrics_service/mobile_grpc_streamer_test.cc @@ -4,9 +4,7 @@ #include "test/mocks/grpc/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/stats/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/test_common/logging.h" -#include "test/test_common/simulated_time_system.h" #include "library/common/extensions/stat_sinks/metrics_service/mobile_grpc_streamer.h" #include "library/common/extensions/stat_sinks/metrics_service/service.pb.h" diff --git a/mobile/test/common/http/BUILD b/mobile/test/common/http/BUILD index 9bb2cd5024641..faedd48ff8061 100644 --- a/mobile/test/common/http/BUILD +++ b/mobile/test/common/http/BUILD @@ -20,7 +20,6 @@ envoy_cc_test( "@envoy//test/mocks/buffer:buffer_mocks", "@envoy//test/mocks/event:event_mocks", "@envoy//test/mocks/http:api_listener_mocks", - "@envoy//test/mocks/local_info:local_info_mocks", "@envoy//test/mocks/upstream:upstream_mocks", ], ) diff --git a/mobile/test/common/integration/BUILD b/mobile/test/common/integration/BUILD index 74dff840b8056..55fb8c9890b78 100644 --- a/mobile/test/common/integration/BUILD +++ b/mobile/test/common/integration/BUILD @@ -36,6 +36,7 @@ envoy_cc_test( "@envoy//source/common/quic:udp_gso_batch_writer_lib", "@envoy//source/extensions/udp_packet_writer/gso:config", "@envoy//test/extensions/filters/http/dynamic_forward_proxy:test_resolver_lib", + "@envoy//test/test_common:registry_lib", "@envoy//test/test_common:test_random_generator_lib", "@envoy//test/test_common:threadsafe_singleton_injector_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/mobile/test/common/stream_info/BUILD b/mobile/test/common/stream_info/BUILD index 528486c353e51..f7d85c7230bb8 100644 --- a/mobile/test/common/stream_info/BUILD +++ b/mobile/test/common/stream_info/BUILD @@ -11,6 +11,5 @@ envoy_cc_test( deps = [ "//library/common/stream_info:extra_stream_info_lib", "@envoy//source/common/stream_info:stream_info_lib", - "@envoy//test/test_common:simulated_time_system_lib", ], ) diff --git a/mobile/test/common/stream_info/extra_stream_info_test.cc b/mobile/test/common/stream_info/extra_stream_info_test.cc index 86031e3229205..535c55bc4a7fe 100644 --- a/mobile/test/common/stream_info/extra_stream_info_test.cc +++ b/mobile/test/common/stream_info/extra_stream_info_test.cc @@ -1,7 +1,5 @@ #include "source/common/stream_info/stream_info_impl.h" -#include "test/test_common/simulated_time_system.h" - #include "gmock/gmock.h" #include "gtest/gtest.h" #include "library/common/stream_info/extra_stream_info.h" diff --git a/test/common/access_log/BUILD b/test/common/access_log/BUILD index f36427c8589ca..5d40c880473f3 100644 --- a/test/common/access_log/BUILD +++ b/test/common/access_log/BUILD @@ -27,7 +27,6 @@ envoy_cc_test( "//test/common/upstream:utility_lib", "//test/mocks/access_log:access_log_mocks", "//test/mocks/event:event_mocks", - "//test/mocks/filesystem:filesystem_mocks", "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", @@ -52,7 +51,6 @@ envoy_cc_test( "//source/common/access_log:access_log_manager_lib", "//source/common/stats:stats_lib", "//test/common/stats:stat_test_utility_lib", - "//test/mocks/access_log:access_log_mocks", "//test/mocks/api:api_mocks", "//test/mocks/event:event_mocks", "//test/mocks/filesystem:filesystem_mocks", diff --git a/test/common/access_log/access_log_impl_test.cc b/test/common/access_log/access_log_impl_test.cc index e41beb66e6c7d..e6e1bf8251034 100644 --- a/test/common/access_log/access_log_impl_test.cc +++ b/test/common/access_log/access_log_impl_test.cc @@ -17,8 +17,6 @@ #include "test/common/stream_info/test_util.h" #include "test/common/upstream/utility.h" #include "test/mocks/access_log/mocks.h" -#include "test/mocks/event/mocks.h" -#include "test/mocks/filesystem/mocks.h" #include "test/mocks/router/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" diff --git a/test/common/access_log/access_log_manager_impl_test.cc b/test/common/access_log/access_log_manager_impl_test.cc index c048daad8b8b3..f18ab330dc7cb 100644 --- a/test/common/access_log/access_log_manager_impl_test.cc +++ b/test/common/access_log/access_log_manager_impl_test.cc @@ -5,7 +5,6 @@ #include "source/common/filesystem/file_shared_impl.h" #include "test/common/stats/stat_test_utility.h" -#include "test/mocks/access_log/mocks.h" #include "test/mocks/api/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/filesystem/mocks.h" diff --git a/test/common/config/BUILD b/test/common/config/BUILD index 5ba0d57a636c4..827bdff5caf40 100644 --- a/test/common/config/BUILD +++ b/test/common/config/BUILD @@ -53,6 +53,7 @@ envoy_cc_test_library( "//source/common/common:hash_lib", "//source/common/config:api_version_lib", "//source/extensions/config_subscription/grpc:grpc_subscription_lib", + "//test/common/stats:stat_test_utility_lib", "//test/mocks/config:config_mocks", "//test/mocks/config:custom_config_validators_mocks", "//test/mocks/event:event_mocks", @@ -212,7 +213,6 @@ envoy_cc_test( "//source/common/protobuf:utility_lib", "//test/mocks/event:event_mocks", "//test/mocks/init:init_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:status_utility_lib", diff --git a/test/common/config/subscription_test_harness.h b/test/common/config/subscription_test_harness.h index 3f043377f4600..54aee8a9ba512 100644 --- a/test/common/config/subscription_test_harness.h +++ b/test/common/config/subscription_test_harness.h @@ -2,7 +2,7 @@ #include "source/common/config/utility.h" -#include "test/mocks/stats/mocks.h" +#include "test/common/stats/stat_test_utility.h" #include "test/test_common/simulated_time_system.h" #include "gmock/gmock.h" diff --git a/test/common/config/utility_test.cc b/test/common/config/utility_test.cc index ee9d81b292aad..4736fc6d61191 100644 --- a/test/common/config/utility_test.cc +++ b/test/common/config/utility_test.cc @@ -14,7 +14,6 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/grpc/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/thread_local_cluster.h" diff --git a/test/common/filter/BUILD b/test/common/filter/BUILD index ee614bbcfe408..27e3c80f3175f 100644 --- a/test/common/filter/BUILD +++ b/test/common/filter/BUILD @@ -18,10 +18,8 @@ envoy_cc_test( "//source/common/json:json_loader_lib", "//source/common/network:filter_matcher_lib", "//test/integration/filters:add_body_filter_config_lib", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:server_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:registry_lib", "//test/test_common:simulated_time_system_lib", diff --git a/test/common/filter/config_discovery_impl_test.cc b/test/common/filter/config_discovery_impl_test.cc index 838d46fa0d256..20392fb69ae4c 100644 --- a/test/common/filter/config_discovery_impl_test.cc +++ b/test/common/filter/config_discovery_impl_test.cc @@ -15,10 +15,8 @@ #include "test/integration/filters/add_body_filter.pb.h" #include "test/mocks/init/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/printers.h" #include "test/test_common/registry.h" diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index 0ba998ddb5970..85d7a98386209 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -7,7 +7,6 @@ #include "envoy/config/core/v3/base.pb.h" #include "envoy/stream_info/stream_info.h" -#include "source/common/common/logger.h" #include "source/common/common/utility.h" #include "source/common/formatter/http_specific_formatter.h" #include "source/common/formatter/stream_info_formatter.h" diff --git a/test/common/grpc/BUILD b/test/common/grpc/BUILD index 40b134ecc05d2..2c982aeb29b94 100644 --- a/test/common/grpc/BUILD +++ b/test/common/grpc/BUILD @@ -38,7 +38,6 @@ envoy_cc_test( "//source/common/grpc:async_client_manager_lib", "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stats:stats_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:cluster_priority_set_mocks", "//test/test_common:test_runtime_lib", @@ -122,7 +121,6 @@ envoy_cc_test( "//source/common/stats:isolated_store_lib", "//source/common/stats:stats_lib", "//source/common/tracing:http_tracer_lib", - "//test/mocks/grpc:grpc_mocks", "//test/mocks/server:server_factory_context_mocks", "//test/mocks/tracing:tracing_mocks", "//test/proto:helloworld_proto_cc_proto", @@ -138,7 +136,6 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ ":utility_lib", - "//test/mocks/stats:stats_mocks", "//test/test_common:utility_lib", ] + envoy_select_google_grpc(["//source/common/grpc:google_grpc_creds_lib"]), ) @@ -148,7 +145,6 @@ envoy_cc_test_library( hdrs = ["grpc_client_integration.h"], deps = [ "//source/common/common:assert_lib", - "//test/mocks/secret:secret_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -168,7 +164,6 @@ envoy_cc_test_library( "//source/common/http/http2:conn_pool_lib", "//source/common/router:context_lib", "//test/integration:integration_lib", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:cluster_manager_mocks", @@ -215,7 +210,6 @@ envoy_cc_test( "//source/common/grpc:buffered_async_client_lib", "//test/mocks/http:http_mocks", "//test/mocks/server:server_factory_context_mocks", - "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/proto:helloworld_proto_cc_proto", "//test/test_common:test_time_lib", @@ -242,8 +236,6 @@ envoy_cc_benchmark_binary( "//source/common/api:api_lib", "//source/common/grpc:async_client_manager_lib", "//test/mocks/server:server_factory_context_mocks", - "//test/mocks/stats:stats_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:cluster_priority_set_mocks", "//test/test_common:test_runtime_lib", diff --git a/test/common/grpc/async_client_manager_benchmark.cc b/test/common/grpc/async_client_manager_benchmark.cc index c5b9c16aeaa39..61e8153346acf 100644 --- a/test/common/grpc/async_client_manager_benchmark.cc +++ b/test/common/grpc/async_client_manager_benchmark.cc @@ -10,8 +10,6 @@ #include "test/benchmark/main.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/stats/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/cluster_priority_set.h" #include "test/test_common/test_runtime.h" diff --git a/test/common/grpc/async_client_manager_impl_test.cc b/test/common/grpc/async_client_manager_impl_test.cc index c16211092e788..349c61283780d 100644 --- a/test/common/grpc/async_client_manager_impl_test.cc +++ b/test/common/grpc/async_client_manager_impl_test.cc @@ -11,7 +11,6 @@ #include "test/mocks/server/server_factory_context.h" #include "test/mocks/stats/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/cluster_priority_set.h" #include "test/test_common/test_runtime.h" diff --git a/test/common/grpc/buffered_async_client_test.cc b/test/common/grpc/buffered_async_client_test.cc index 19596b0b53ed6..8e5305960a193 100644 --- a/test/common/grpc/buffered_async_client_test.cc +++ b/test/common/grpc/buffered_async_client_test.cc @@ -11,7 +11,6 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/tracing/mocks.h" #include "test/proto/helloworld.pb.h" #include "test/test_common/test_time.h" diff --git a/test/common/grpc/google_async_client_impl_test.cc b/test/common/grpc/google_async_client_impl_test.cc index c7f4a8804224b..eb084371356ed 100644 --- a/test/common/grpc/google_async_client_impl_test.cc +++ b/test/common/grpc/google_async_client_impl_test.cc @@ -8,7 +8,6 @@ #include "source/common/stats/isolated_store_impl.h" #include "source/common/stream_info/stream_info_impl.h" -#include "test/mocks/grpc/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/tracing/mocks.h" #include "test/proto/helloworld.pb.h" diff --git a/test/common/grpc/google_grpc_creds_test.cc b/test/common/grpc/google_grpc_creds_test.cc index e405adc834d79..53c56c7427b1d 100644 --- a/test/common/grpc/google_grpc_creds_test.cc +++ b/test/common/grpc/google_grpc_creds_test.cc @@ -5,7 +5,6 @@ #include "source/common/grpc/google_grpc_creds_impl.h" #include "test/common/grpc/utility.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index 05d0d1ba41373..7be7a8dde5dad 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -32,8 +32,6 @@ #include "test/common/grpc/grpc_client_integration.h" #include "test/common/grpc/utility.h" #include "test/integration/fake_upstream.h" -#include "test/mocks/grpc/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_info.h" diff --git a/test/common/http/codec_client_test.cc b/test/common/http/codec_client_test.cc index 1da05ed8cb0cf..e45570c68e613 100644 --- a/test/common/http/codec_client_test.cc +++ b/test/common/http/codec_client_test.cc @@ -18,7 +18,6 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_info.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" diff --git a/test/common/http/common.h b/test/common/http/common.h index 1b922daf6de10..04ed75704d4cb 100644 --- a/test/common/http/common.h +++ b/test/common/http/common.h @@ -7,7 +7,6 @@ #include "source/common/http/codec_client.h" #include "test/mocks/common.h" -#include "test/mocks/event/mocks.h" namespace Envoy { /** diff --git a/test/common/http/http1/BUILD b/test/common/http/http1/BUILD index a49f85c9cec89..f6f6cfddfc5c7 100644 --- a/test/common/http/http1/BUILD +++ b/test/common/http/http1/BUILD @@ -35,14 +35,10 @@ envoy_cc_test( "//test/common/stats:stat_test_utility_lib", "//test/mocks/buffer:buffer_mocks", "//test/mocks/http:http_mocks", - "//test/mocks/init:init_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:overload_manager_mocks", "//test/mocks/stream_info:stream_info_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:logging_lib", "//test/test_common:test_runtime_lib", @@ -64,7 +60,6 @@ envoy_cc_test( "//source/common/upstream:upstream_lib", "//test/common/http:common_lib", "//test/common/upstream:utility_lib", - "//test/mocks/buffer:buffer_mocks", "//test/mocks/event:event_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", diff --git a/test/common/http/http1/conn_pool_test.cc b/test/common/http/http1/conn_pool_test.cc index 0fc0670e826eb..485e8dd135a74 100644 --- a/test/common/http/http1/conn_pool_test.cc +++ b/test/common/http/http1/conn_pool_test.cc @@ -16,7 +16,6 @@ #include "test/common/http/common.h" #include "test/common/upstream/utility.h" -#include "test/mocks/buffer/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" diff --git a/test/common/http/http2/BUILD b/test/common/http/http2/BUILD index cc2473d71557f..57c45eb774918 100644 --- a/test/common/http/http2/BUILD +++ b/test/common/http/http2/BUILD @@ -33,15 +33,10 @@ envoy_cc_test( "//test/common/memory:memory_test_utility_lib", "//test/common/stats:stat_test_utility_lib", "//test/mocks/http:http_mocks", - "//test/mocks/init:init_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:transport_socket_match_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:logging_lib", - "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@quiche//:http2_adapter", diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index e53b7872079a1..c9808854c1f1b 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -21,14 +21,9 @@ #include "test/common/memory/memory_test_utility.h" #include "test/common/stats/stat_test_utility.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/init/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/printers.h" -#include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/common/http/http2/metadata_encoder_test.cc b/test/common/http/http2/metadata_encoder_test.cc index 4512340baa04d..183b3b7a7e57c 100644 --- a/test/common/http/http2/metadata_encoder_test.cc +++ b/test/common/http/http2/metadata_encoder_test.cc @@ -3,7 +3,6 @@ #include "envoy/http/metadata_interface.h" #include "source/common/buffer/buffer_impl.h" -#include "source/common/common/logger.h" #include "source/common/common/random_generator.h" #include "source/common/http/http2/metadata_decoder.h" #include "source/common/http/http2/metadata_encoder.h" diff --git a/test/common/http/http3/BUILD b/test/common/http/http3/BUILD index c1c041974974f..b59b1939090d3 100644 --- a/test/common/http/http3/BUILD +++ b/test/common/http/http3/BUILD @@ -26,7 +26,6 @@ envoy_cc_test( "//test/mocks/http:conn_pool_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:transport_socket_match_mocks", diff --git a/test/common/http/inline_cookie_test.cc b/test/common/http/inline_cookie_test.cc index b2d08a06682ab..463ab8b7a5976 100644 --- a/test/common/http/inline_cookie_test.cc +++ b/test/common/http/inline_cookie_test.cc @@ -1,7 +1,6 @@ #include "source/common/http/header_map_impl.h" #include "source/common/http/header_utility.h" -#include "test/mocks/runtime/mocks.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/common/listener_manager/BUILD b/test/common/listener_manager/BUILD index 0d691d7a57b45..0da07aad379cb 100644 --- a/test/common/listener_manager/BUILD +++ b/test/common/listener_manager/BUILD @@ -160,8 +160,6 @@ envoy_cc_test( "//test/mocks/stream_info:stream_info_mocks", "//test/server:utility_lib", "//test/test_common:environment_lib", - "//test/test_common:registry_lib", - "//test/test_common:simulated_time_system_lib", "//test/test_common:test_time_lib", "//test/test_common:threadsafe_singleton_injector_lib", "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", diff --git a/test/common/listener_manager/filter_chain_manager_impl_test.cc b/test/common/listener_manager/filter_chain_manager_impl_test.cc index ef90051de934a..09117310c3201 100644 --- a/test/common/listener_manager/filter_chain_manager_impl_test.cc +++ b/test/common/listener_manager/filter_chain_manager_impl_test.cc @@ -28,8 +28,6 @@ #include "test/mocks/stream_info/mocks.h" #include "test/server/utility.h" #include "test/test_common/environment.h" -#include "test/test_common/registry.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "test/test_common/utility.h" diff --git a/test/common/network/BUILD b/test/common/network/BUILD index b51c973706638..e57064f567cb3 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -165,7 +165,6 @@ envoy_cc_test( "//test/mocks/ratelimit:ratelimit_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:instance_mocks", - "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:host_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", diff --git a/test/common/network/filter_manager_impl_test.cc b/test/common/network/filter_manager_impl_test.cc index 19a27252ffc2b..d001617bc28a0 100644 --- a/test/common/network/filter_manager_impl_test.cc +++ b/test/common/network/filter_manager_impl_test.cc @@ -13,10 +13,8 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/ratelimit/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/instance.h" -#include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/host.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" diff --git a/test/common/network/io_uring_socket_handle_impl_test.cc b/test/common/network/io_uring_socket_handle_impl_test.cc index 44cdc94d719e2..adcdb1d739e00 100644 --- a/test/common/network/io_uring_socket_handle_impl_test.cc +++ b/test/common/network/io_uring_socket_handle_impl_test.cc @@ -1,7 +1,6 @@ #include "source/common/network/address_impl.h" #include "source/common/network/io_uring_socket_handle_impl.h" -#include "test/mocks/api/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/io/mocks.h" #include "test/test_common/threadsafe_singleton_injector.h" diff --git a/test/common/network/listener_filter_buffer_fuzz_test.cc b/test/common/network/listener_filter_buffer_fuzz_test.cc index dbcb3e3272c92..974b99003f269 100644 --- a/test/common/network/listener_filter_buffer_fuzz_test.cc +++ b/test/common/network/listener_filter_buffer_fuzz_test.cc @@ -1,5 +1,4 @@ #include "source/common/common/assert.h" -#include "source/common/common/logger.h" #include "source/common/network/listener_filter_buffer_impl.h" #include "test/common/network/listener_filter_buffer_fuzz.pb.h" diff --git a/test/common/network/udp_fuzz.cc b/test/common/network/udp_fuzz.cc index fe5be5a253aeb..536d1faedcf0c 100644 --- a/test/common/network/udp_fuzz.cc +++ b/test/common/network/udp_fuzz.cc @@ -12,7 +12,6 @@ #include "test/common/network/udp_listener_impl_test_base.h" #include "test/fuzz/fuzz_runner.h" -#include "test/mocks/api/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" diff --git a/test/common/network/udp_listener_impl_batch_writer_test.cc b/test/common/network/udp_listener_impl_batch_writer_test.cc index 4e8c74facfb9d..3e90d25f75f2d 100644 --- a/test/common/network/udp_listener_impl_batch_writer_test.cc +++ b/test/common/network/udp_listener_impl_batch_writer_test.cc @@ -29,7 +29,6 @@ #include "source/common/quic/udp_gso_batch_writer.h" #include "test/common/network/udp_listener_impl_test_base.h" -#include "test/mocks/api/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" diff --git a/test/common/protobuf/BUILD b/test/common/protobuf/BUILD index 6b0a980a266d5..f3ca028e26131 100644 --- a/test/common/protobuf/BUILD +++ b/test/common/protobuf/BUILD @@ -61,9 +61,6 @@ envoy_cc_test( "//source/common/config:api_version_lib", "//source/common/protobuf:utility_lib", "//test/common/stats:stat_test_utility_lib", - "//test/mocks/init:init_mocks", - "//test/mocks/local_info:local_info_mocks", - "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:instance_mocks", "//test/proto:deprecated_proto_cc_proto", "//test/proto:sensitive_proto_cc_proto", diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index a549bc217fb32..be8e0c0e29635 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -21,9 +21,6 @@ #include "test/common/protobuf/utility_test_file_wip_2.pb.h" #include "test/common/protobuf/utility_test_message_field_wip.pb.h" #include "test/common/stats/stat_test_utility.h" -#include "test/mocks/init/mocks.h" -#include "test/mocks/local_info/mocks.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/instance.h" #include "test/proto/deprecated.pb.h" #include "test/proto/sensitive.pb.h" diff --git a/test/common/quic/BUILD b/test/common/quic/BUILD index c6ce9b3efe621..54a8f593783ea 100644 --- a/test/common/quic/BUILD +++ b/test/common/quic/BUILD @@ -93,7 +93,7 @@ envoy_cc_test( deps = envoy_select_enable_http3([ "//source/common/quic:quic_stat_names_lib", "//source/common/stats:stats_lib", - "//test/mocks/stats:stats_mocks", + "//test/common/stats:stat_test_utility_lib", "//test/test_common:utility_lib", ]), ) @@ -309,7 +309,6 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/http:http_server_properties_cache_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:transport_socket_match_mocks", diff --git a/test/common/quic/active_quic_listener_test.cc b/test/common/quic/active_quic_listener_test.cc index bc44387230fb5..f87bd248f5574 100644 --- a/test/common/quic/active_quic_listener_test.cc +++ b/test/common/quic/active_quic_listener_test.cc @@ -4,7 +4,6 @@ #include "envoy/config/listener/v3/quic_config.pb.validate.h" #include "envoy/network/exception.h" -#include "source/common/common/logger.h" #include "source/common/http/utility.h" #include "source/common/listener_manager/connection_handler_impl.h" #include "source/common/network/listen_socket_impl.h" @@ -23,7 +22,6 @@ #include "test/common/quic/test_proof_source.h" #include "test/common/quic/test_utils.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/instance.h" #include "test/mocks/server/listener_factory_context.h" #include "test/mocks/ssl/mocks.h" diff --git a/test/common/quic/envoy_quic_client_session_test.cc b/test/common/quic/envoy_quic_client_session_test.cc index 0d40ecf95163a..44c13faf236c9 100644 --- a/test/common/quic/envoy_quic_client_session_test.cc +++ b/test/common/quic/envoy_quic_client_session_test.cc @@ -15,7 +15,6 @@ #include "test/common/quic/test_utils.h" #include "test/mocks/api/mocks.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/http/stream_decoder.h" #include "test/mocks/network/mocks.h" diff --git a/test/common/quic/envoy_quic_server_session_test.cc b/test/common/quic/envoy_quic_server_session_test.cc index 788a31ff9e48f..2003dd0225470 100644 --- a/test/common/quic/envoy_quic_server_session_test.cc +++ b/test/common/quic/envoy_quic_server_session_test.cc @@ -15,7 +15,6 @@ #include "test/common/quic/test_proof_source.h" #include "test/common/quic/test_utils.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/http/session_idle_list.h" #include "test/mocks/http/stream_decoder.h" diff --git a/test/common/quic/platform/BUILD b/test/common/quic/platform/BUILD index 3002bfe2a3a22..b847b61d54074 100644 --- a/test/common/quic/platform/BUILD +++ b/test/common/quic/platform/BUILD @@ -30,7 +30,6 @@ envoy_cc_test( "//test/common/buffer:utility_lib", "//test/common/stats:stat_test_utility_lib", "//test/common/tls:ssl_test_utils", - "//test/mocks/api:api_mocks", "//test/test_common:logging_lib", "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:utility_lib", diff --git a/test/common/quic/platform/quic_platform_test.cc b/test/common/quic/platform/quic_platform_test.cc index 6aff0a0672f6e..38532407ae598 100644 --- a/test/common/quic/platform/quic_platform_test.cc +++ b/test/common/quic/platform/quic_platform_test.cc @@ -15,7 +15,6 @@ #include "test/common/buffer/utility.h" #include "test/common/stats/stat_test_utility.h" #include "test/common/tls/ssl_test_utility.h" -#include "test/mocks/api/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/network_utility.h" #include "test/test_common/utility.h" diff --git a/test/common/quic/quic_stat_names_test.cc b/test/common/quic/quic_stat_names_test.cc index fc4964cecc922..a1ac2224e8b1f 100644 --- a/test/common/quic/quic_stat_names_test.cc +++ b/test/common/quic/quic_stat_names_test.cc @@ -2,7 +2,7 @@ #include "source/common/quic/quic_stat_names.h" -#include "test/mocks/stats/mocks.h" +#include "test/common/stats/stat_test_utility.h" #include "gtest/gtest.h" diff --git a/test/common/router/BUILD b/test/common/router/BUILD index 31ced6333a77d..efc8fd43a194e 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -117,6 +117,7 @@ envoy_cc_test( "//source/common/router:config_lib", "//source/common/router:delegating_route_lib", "//test/integration:http_integration_lib", + "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", ], @@ -181,7 +182,6 @@ envoy_cc_test( "//source/common/json:json_loader_lib", "//source/common/router:rds_lib", "//source/server/admin:admin_lib", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/matcher:matcher_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:instance_mocks", @@ -254,10 +254,8 @@ envoy_cc_test( "//source/common/router:vhds_lib", "//source/server/admin:admin_lib", "//test/mocks/config:config_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/server:instance_mocks", "//test/mocks/thread_local:thread_local_mocks", - "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", @@ -367,7 +365,6 @@ envoy_cc_test( "//source/extensions/upstreams/http/tcp:config", "//test/common/http:common_lib", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", @@ -375,7 +372,6 @@ envoy_cc_test( "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:host_mocks", "//test/test_common:environment_lib", - "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/common/mutation_rules/v3:pkg_cc_proto", @@ -408,7 +404,6 @@ envoy_cc_test_library( "//source/extensions/upstreams/http/tcp:config", "//test/common/http:common_lib", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", @@ -439,9 +434,7 @@ envoy_cc_test( "//source/extensions/upstreams/http/generic:config", "//test/common/http:common_lib", "//test/mocks/access_log:access_log_mocks", - "//test/mocks/filesystem:filesystem_mocks", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", @@ -469,10 +462,7 @@ envoy_cc_test( "//source/extensions/upstreams/http/generic:config", "//test/common/http:common_lib", "//test/integration/filters:add_header_filter_config_lib", - "//test/mocks/access_log:access_log_mocks", - "//test/mocks/filesystem:filesystem_mocks", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", @@ -521,7 +511,6 @@ envoy_cc_test( "//source/common/router:string_accessor_lib", "//source/common/stream_info:filter_state_lib", "//test/common/stream_info:test_int_accessor_lib", - "//test/mocks/api:api_mocks", "//test/mocks/http:http_mocks", "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", diff --git a/test/common/router/header_formatter_test.cc b/test/common/router/header_formatter_test.cc index 502111455919e..8a776b8a54147 100644 --- a/test/common/router/header_formatter_test.cc +++ b/test/common/router/header_formatter_test.cc @@ -15,10 +15,8 @@ #include "source/common/stream_info/filter_state_impl.h" #include "test/common/stream_info/test_int_accessor.h" -#include "test/mocks/api/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/upstream/host.h" #include "test/test_common/test_runtime.h" diff --git a/test/common/router/rds_impl_test.cc b/test/common/router/rds_impl_test.cc index 9730254fb423a..3fe1352f41095 100644 --- a/test/common/router/rds_impl_test.cc +++ b/test/common/router/rds_impl_test.cc @@ -19,7 +19,6 @@ #include "source/server/admin/admin.h" #endif #include "test/mocks/init/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/matcher/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/instance.h" diff --git a/test/common/router/retry_state_impl_test.cc b/test/common/router/retry_state_impl_test.cc index ab4e3d5ec59cb..7b3cf64e98cf9 100644 --- a/test/common/router/retry_state_impl_test.cc +++ b/test/common/router/retry_state_impl_test.cc @@ -14,7 +14,6 @@ #include "test/mocks/router/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/upstream/cluster_info.h" #include "test/test_common/printers.h" #include "test/test_common/simulated_time_system.h" diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 1b5c5bf3aae34..59f740d894d79 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -38,17 +38,14 @@ #include "test/common/http/common.h" #include "test/common/router/router_test_base.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/ssl/mocks.h" -#include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/host.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/common/router/router_test_base.h b/test/common/router/router_test_base.h index e00cfe4c764e0..b54473077dd11 100644 --- a/test/common/router/router_test_base.h +++ b/test/common/router/router_test_base.h @@ -15,7 +15,6 @@ #include "test/mocks/common.h" #include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" #include "test/mocks/runtime/mocks.h" diff --git a/test/common/router/router_upstream_filter_test.cc b/test/common/router/router_upstream_filter_test.cc index 7737ff62605ab..7b035706c6148 100644 --- a/test/common/router/router_upstream_filter_test.cc +++ b/test/common/router/router_upstream_filter_test.cc @@ -5,13 +5,9 @@ #include "source/common/router/upstream_codec_filter.h" #include "test/common/http/common.h" -#include "test/mocks/access_log/mocks.h" -#include "test/mocks/filesystem/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/common/router/router_upstream_log_test.cc b/test/common/router/router_upstream_log_test.cc index 3210fdef785b1..aaf7d778d0218 100644 --- a/test/common/router/router_upstream_log_test.cc +++ b/test/common/router/router_upstream_log_test.cc @@ -13,12 +13,9 @@ #include "test/common/http/common.h" #include "test/mocks/access_log/mocks.h" -#include "test/mocks/filesystem/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/test_common/utility.h" diff --git a/test/common/router/vhds_test.cc b/test/common/router/vhds_test.cc index 60acf2dce7d48..cb2b311e4b578 100644 --- a/test/common/router/vhds_test.cc +++ b/test/common/router/vhds_test.cc @@ -19,9 +19,7 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/init/mocks.h" #include "test/mocks/server/instance.h" -#include "test/mocks/thread_local/mocks.h" #include "test/test_common/printers.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/common/runtime/runtime_impl_test.cc b/test/common/runtime/runtime_impl_test.cc index 3e7875e4c6559..48470c7a6acf2 100644 --- a/test/common/runtime/runtime_impl_test.cc +++ b/test/common/runtime/runtime_impl_test.cc @@ -18,7 +18,6 @@ #include "test/mocks/init/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/cluster_manager.h" diff --git a/test/common/secret/BUILD b/test/common/secret/BUILD index 0152fd2fbfa15..e4c9c398461e9 100644 --- a/test/common/secret/BUILD +++ b/test/common/secret/BUILD @@ -57,7 +57,6 @@ envoy_cc_test( "//test/common/stats:stat_test_utility_lib", "//test/mocks/config:config_mocks", "//test/mocks/filesystem:filesystem_mocks", - "//test/mocks/grpc:grpc_mocks", "//test/mocks/init:init_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/secret:secret_mocks", diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc index cc781610431b5..2228f3c034417 100644 --- a/test/common/secret/sds_api_test.cc +++ b/test/common/secret/sds_api_test.cc @@ -16,7 +16,6 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/filesystem/mocks.h" -#include "test/mocks/grpc/mocks.h" #include "test/mocks/init/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/secret/mocks.h" diff --git a/test/common/stats/real_thread_test_base.h b/test/common/stats/real_thread_test_base.h index 8450e02fc1737..1ddabfc880ed0 100644 --- a/test/common/stats/real_thread_test_base.h +++ b/test/common/stats/real_thread_test_base.h @@ -3,7 +3,6 @@ #include "source/common/stats/thread_local_store.h" #include "source/common/thread_local/thread_local_impl.h" -#include "test/mocks/thread_local/mocks.h" #include "test/test_common/real_threads_test_helper.h" namespace Envoy { diff --git a/test/common/stats/stat_test_utility.h b/test/common/stats/stat_test_utility.h index f43b5fa011eb3..c0fba217a7ccb 100644 --- a/test/common/stats/stat_test_utility.h +++ b/test/common/stats/stat_test_utility.h @@ -2,7 +2,6 @@ #include "envoy/stats/store.h" -#include "source/common/common/logger.h" #include "source/common/stats/isolated_store_impl.h" #include "test/common/memory/memory_test_utility.h" diff --git a/test/common/stats/symbol_table_speed_test.cc b/test/common/stats/symbol_table_speed_test.cc index e4a8d03f9168c..acda8a35a9550 100644 --- a/test/common/stats/symbol_table_speed_test.cc +++ b/test/common/stats/symbol_table_speed_test.cc @@ -7,7 +7,6 @@ #include #include "source/common/common/hash.h" -#include "source/common/common/logger.h" #include "source/common/common/thread.h" #include "source/common/stats/isolated_store_impl.h" #include "source/common/stats/symbol_table.h" diff --git a/test/common/stats/thread_local_store_speed_test.cc b/test/common/stats/thread_local_store_speed_test.cc index 75523c1ffd558..70d16f9b6461f 100644 --- a/test/common/stats/thread_local_store_speed_test.cc +++ b/test/common/stats/thread_local_store_speed_test.cc @@ -3,7 +3,6 @@ #include "envoy/config/metrics/v3/stats.pb.h" -#include "source/common/common/logger.h" #include "source/common/common/thread.h" #include "source/common/event/dispatcher_impl.h" #include "source/common/stats/allocator.h" diff --git a/test/common/tcp/BUILD b/test/common/tcp/BUILD index 6b94888238d78..fbc07009e6bbd 100644 --- a/test/common/tcp/BUILD +++ b/test/common/tcp/BUILD @@ -40,7 +40,6 @@ envoy_cc_test( "//source/common/network:utility_lib", "//source/common/tcp:async_tcp_client_lib", "//test/common/upstream:utility_lib", - "//test/mocks/buffer:buffer_mocks", "//test/mocks/network:network_mocks", "//test/mocks/tcp:tcp_mocks", "//test/mocks/upstream:cluster_info_mocks", diff --git a/test/common/tcp/async_tcp_client_impl_test.cc b/test/common/tcp/async_tcp_client_impl_test.cc index 0fc1d6e2d25f1..f1050ecc1da94 100644 --- a/test/common/tcp/async_tcp_client_impl_test.cc +++ b/test/common/tcp/async_tcp_client_impl_test.cc @@ -1,7 +1,6 @@ #include "source/common/tcp/async_tcp_client_impl.h" #include "test/common/upstream/utility.h" -#include "test/mocks/buffer/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/cluster_manager.h" diff --git a/test/common/tcp_proxy/BUILD b/test/common/tcp_proxy/BUILD index d2ab33222df9e..34f79b44f5524 100644 --- a/test/common/tcp_proxy/BUILD +++ b/test/common/tcp_proxy/BUILD @@ -31,9 +31,7 @@ envoy_cc_test_library( "//source/extensions/access_loggers/file:config", "//source/extensions/upstreams/http/generic:config", "//test/common/upstream:utility_lib", - "//test/mocks/buffer:buffer_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:instance_mocks", "//test/mocks/ssl:ssl_mocks", diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index e11fdfbef36bd..69db3b38f2659 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -32,7 +32,6 @@ #include "test/common/upstream/utility.h" #include "test/mocks/buffer/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/instance.h" #include "test/mocks/ssl/mocks.h" diff --git a/test/common/tcp_proxy/tcp_proxy_test_base.h b/test/common/tcp_proxy/tcp_proxy_test_base.h index 35e2367037709..54c5dfadcf3a0 100644 --- a/test/common/tcp_proxy/tcp_proxy_test_base.h +++ b/test/common/tcp_proxy/tcp_proxy_test_base.h @@ -26,10 +26,8 @@ #include "test/common/upstream/utility.h" #include "test/mocks/buffer/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/instance.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/tcp/mocks.h" #include "test/mocks/upstream/host.h" diff --git a/test/common/tcp_proxy/upstream_test.cc b/test/common/tcp_proxy/upstream_test.cc index 5cce6101fe458..f8e3a237158a4 100644 --- a/test/common/tcp_proxy/upstream_test.cc +++ b/test/common/tcp_proxy/upstream_test.cc @@ -7,7 +7,6 @@ #include "source/common/tcp_proxy/upstream.h" #include "test/common/memory/memory_test_utility.h" -#include "test/mocks/buffer/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/http/stream_encoder.h" #include "test/mocks/router/router_filter_interface.h" diff --git a/test/common/tls/BUILD b/test/common/tls/BUILD index a827b9485f61b..f56330b338067 100644 --- a/test/common/tls/BUILD +++ b/test/common/tls/BUILD @@ -64,7 +64,6 @@ envoy_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_mocks", "//test/mocks/ssl:ssl_mocks", - "//test/mocks/stats:stats_mocks", "//test/test_common:environment_lib", "//test/test_common:logging_lib", "//test/test_common:network_utility_lib", @@ -126,7 +125,6 @@ envoy_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_mocks", "//test/mocks/ssl:ssl_mocks", - "//test/mocks/stats:stats_mocks", "//test/test_common:environment_lib", "//test/test_common:logging_lib", "//test/test_common:network_utility_lib", @@ -169,7 +167,6 @@ envoy_cc_test( "//test/mocks/server:factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/test_common:environment_lib", - "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", @@ -268,7 +265,6 @@ envoy_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_mocks", "//test/mocks/ssl:ssl_mocks", - "//test/mocks/stats:stats_mocks", ], ) @@ -290,7 +286,6 @@ envoy_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_mocks", "//test/mocks/ssl:ssl_mocks", - "//test/mocks/stats:stats_mocks", "//test/test_common:registry_lib", "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", ], diff --git a/test/common/tls/context_impl_test.cc b/test/common/tls/context_impl_test.cc index 08b0e4f76401a..429005a45cf3d 100644 --- a/test/common/tls/context_impl_test.cc +++ b/test/common/tls/context_impl_test.cc @@ -30,7 +30,6 @@ #include "test/mocks/server/server_factory_context.h" #include "test/mocks/ssl/mocks.h" #include "test/test_common/environment.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/common/tls/integration/BUILD b/test/common/tls/integration/BUILD index 8086b2a289798..234c7af183d44 100644 --- a/test/common/tls/integration/BUILD +++ b/test/common/tls/integration/BUILD @@ -19,7 +19,6 @@ envoy_cc_test_library( deps = [ "//test/common/config:dummy_config_proto_cc_proto", "//test/integration:http_integration_lib", - "//test/mocks/secret:secret_mocks", "//test/test_common:utility_lib", ], ) @@ -50,7 +49,6 @@ envoy_cc_test( "//test/common/tls/cert_selector:async_cert_selector", "//test/common/tls/cert_validator:timed_cert_validator", "//test/integration/filters:stream_info_to_headers_filter_lib", - "//test/mocks/secret:secret_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", diff --git a/test/common/tls/integration/ssl_integration_test.cc b/test/common/tls/integration/ssl_integration_test.cc index 7d3869da464a8..8489ccc2a05a8 100644 --- a/test/common/tls/integration/ssl_integration_test.cc +++ b/test/common/tls/integration/ssl_integration_test.cc @@ -25,7 +25,6 @@ #include "test/integration/ssl_utility.h" #include "test/integration/utility.h" #include "test/test_common/network_utility.h" -#include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/common/tls/integration/ssl_integration_test_base.h b/test/common/tls/integration/ssl_integration_test_base.h index d2bd7b45f87ef..157639c03b439 100644 --- a/test/common/tls/integration/ssl_integration_test_base.h +++ b/test/common/tls/integration/ssl_integration_test_base.h @@ -6,7 +6,6 @@ #include "test/integration/http_integration.h" #include "test/integration/server.h" #include "test/integration/ssl_utility.h" -#include "test/mocks/secret/mocks.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/common/tls/ssl_socket_test.cc b/test/common/tls/ssl_socket_test.cc index a05247694f405..cb59f7de4bd2f 100644 --- a/test/common/tls/ssl_socket_test.cc +++ b/test/common/tls/ssl_socket_test.cc @@ -51,10 +51,8 @@ #include "test/mocks/network/io_handle.h" #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/secret/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/ssl/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/registry.h" diff --git a/test/common/tls/tls_certificate_selector_test.cc b/test/common/tls/tls_certificate_selector_test.cc index 57e6c5d4977fb..8ff9f53d1695c 100644 --- a/test/common/tls/tls_certificate_selector_test.cc +++ b/test/common/tls/tls_certificate_selector_test.cc @@ -26,15 +26,10 @@ #include "test/common/tls/cert_validator/timed_cert_validator.h" #include "test/mocks/buffer/mocks.h" -#include "test/mocks/init/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/io_handle.h" #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/secret/mocks.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/ssl/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/registry.h" diff --git a/test/common/tracing/BUILD b/test/common/tracing/BUILD index 2ae71cb3094d1..2781ade6e4e5d 100644 --- a/test/common/tracing/BUILD +++ b/test/common/tracing/BUILD @@ -28,10 +28,6 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/router:router_mocks", - "//test/mocks/runtime:runtime_mocks", - "//test/mocks/stats:stats_mocks", - "//test/mocks/thread_local:thread_local_mocks", - "//test/mocks/tracing:tracing_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -54,10 +50,7 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/router:router_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/stats:stats_mocks", - "//test/mocks/thread_local:thread_local_mocks", - "//test/mocks/tracing:tracing_mocks", "//test/test_common:environment_lib", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", @@ -76,7 +69,6 @@ envoy_cc_test( "//source/common/tracing:tracer_manager_lib", "//test/mocks/server:instance_mocks", "//test/mocks/server:tracer_factory_mocks", - "//test/mocks/tracing:tracing_mocks", "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", ], diff --git a/test/common/tracing/http_tracer_impl_test.cc b/test/common/tracing/http_tracer_impl_test.cc index d000ad5bcfb8e..dc2393bf007ea 100644 --- a/test/common/tracing/http_tracer_impl_test.cc +++ b/test/common/tracing/http_tracer_impl_test.cc @@ -15,12 +15,7 @@ #include "source/common/tracing/http_tracer_impl.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/runtime/mocks.h" -#include "test/mocks/stats/mocks.h" -#include "test/mocks/thread_local/mocks.h" -#include "test/mocks/tracing/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" diff --git a/test/common/tracing/tracer_impl_test.cc b/test/common/tracing/tracer_impl_test.cc index 732f606f85586..6c6a3eb95c2d2 100644 --- a/test/common/tracing/tracer_impl_test.cc +++ b/test/common/tracing/tracer_impl_test.cc @@ -6,10 +6,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/stats/mocks.h" -#include "test/mocks/thread_local/mocks.h" -#include "test/mocks/tracing/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" #include "test/test_common/test_runtime.h" diff --git a/test/common/tracing/tracer_manager_impl_test.cc b/test/common/tracing/tracer_manager_impl_test.cc index 8a29f403fa50b..c19a798e7f840 100644 --- a/test/common/tracing/tracer_manager_impl_test.cc +++ b/test/common/tracing/tracer_manager_impl_test.cc @@ -4,7 +4,6 @@ #include "test/mocks/server/instance.h" #include "test/mocks/server/tracer_factory.h" -#include "test/mocks/tracing/mocks.h" #include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 7d7dc704e88f3..95916b289680e 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -94,7 +94,6 @@ envoy_cc_test( "//source/extensions/load_balancing_policies/ring_hash:config", "//source/extensions/load_balancing_policies/round_robin:config", "//test/mocks/config:config_mocks", - "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", "@abseil-cpp//absl/base", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", @@ -122,7 +121,6 @@ envoy_cc_test_library( "//source/common/grpc:context_lib", "//source/common/http:context_lib", "//source/common/router:context_lib", - "//test/mocks/access_log:access_log_mocks", "//test/mocks/config:xds_manager_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:admin_mocks", @@ -439,7 +437,6 @@ envoy_cc_test( "//source/extensions/health_checkers/http:health_checker_lib", "//source/extensions/health_checkers/tcp:health_checker_lib", "//source/extensions/transport_sockets/raw_buffer:config", - "//test/mocks/access_log:access_log_mocks", "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", @@ -674,21 +671,16 @@ envoy_cc_test_library( "//source/extensions/transport_sockets/raw_buffer:config", "//test/common/stats:stat_test_utility_lib", "//test/integration/clusters:custom_static_cluster", - "//test/mocks/access_log:access_log_mocks", - "//test/mocks/api:api_mocks", "//test/mocks/config:xds_manager_mocks", "//test/mocks/http:http_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/secret:secret_mocks", "//test/mocks/server:admin_mocks", "//test/mocks/server:instance_mocks", "//test/mocks/tcp:tcp_mocks", "//test/mocks/thread_local:thread_local_mocks", - "//test/test_common:registry_lib", - "//test/test_common:simulated_time_system_lib", "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", diff --git a/test/common/upstream/cluster_factory_impl_test.cc b/test/common/upstream/cluster_factory_impl_test.cc index e37b3f2ca7e95..5f5769e81dc3d 100644 --- a/test/common/upstream/cluster_factory_impl_test.cc +++ b/test/common/upstream/cluster_factory_impl_test.cc @@ -19,13 +19,12 @@ #include "test/integration/clusters/cluster_factory_config.pb.validate.h" #include "test/integration/clusters/custom_static_cluster.h" #include "test/mocks/common.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" #include "test/mocks/server/options.h" #include "test/mocks/ssl/mocks.h" +#include "test/test_common/registry.h" using testing::NiceMock; diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 4c95c72b21c97..43e5d5b4ea3a9 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -19,7 +19,6 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/http/conn_pool.h" #include "test/mocks/matcher/mocks.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/instance.h" #include "test/mocks/upstream/cluster_priority_set.h" #include "test/mocks/upstream/load_balancer_context.h" diff --git a/test/common/upstream/cluster_manager_impl_test_common.h b/test/common/upstream/cluster_manager_impl_test_common.h index 82af10104e511..0a6bde7222061 100644 --- a/test/common/upstream/cluster_manager_impl_test_common.h +++ b/test/common/upstream/cluster_manager_impl_test_common.h @@ -13,7 +13,6 @@ #include "source/common/router/context_impl.h" #include "test/common/upstream/test_cluster_manager.h" -#include "test/mocks/access_log/mocks.h" #include "test/mocks/config/xds_manager.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/admin.h" diff --git a/test/common/upstream/deferred_cluster_initialization_test.cc b/test/common/upstream/deferred_cluster_initialization_test.cc index fb713f3087ce4..8afb826d7a3d8 100644 --- a/test/common/upstream/deferred_cluster_initialization_test.cc +++ b/test/common/upstream/deferred_cluster_initialization_test.cc @@ -14,7 +14,6 @@ #include "test/mocks/config/xds_manager.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/instance.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" namespace Envoy { diff --git a/test/common/upstream/hds_test.cc b/test/common/upstream/hds_test.cc index 9d26dbdcd0e75..92194721ab5b7 100644 --- a/test/common/upstream/hds_test.cc +++ b/test/common/upstream/hds_test.cc @@ -13,12 +13,9 @@ #include "source/extensions/health_checkers/common/health_checker_base_impl.h" #include "source/extensions/transport_sockets/raw_buffer/config.h" -#include "test/mocks/access_log/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" #include "test/mocks/upstream/cluster_info.h" diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 63aad863e1f90..aaeddf49fe1bc 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -31,7 +31,6 @@ #include "test/mocks/common.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/health_checker_factory_context.h" diff --git a/test/common/upstream/prod_cluster_info_factory_test.cc b/test/common/upstream/prod_cluster_info_factory_test.cc index 9fc1dc5bb68bf..289da099160bb 100644 --- a/test/common/upstream/prod_cluster_info_factory_test.cc +++ b/test/common/upstream/prod_cluster_info_factory_test.cc @@ -5,7 +5,6 @@ #include "source/extensions/transport_sockets/raw_buffer/config.h" #include "test/common/upstream/utility.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" diff --git a/test/common/upstream/test_cluster_manager.h b/test/common/upstream/test_cluster_manager.h index 34fc7af9953c6..e76513e3327e5 100644 --- a/test/common/upstream/test_cluster_manager.h +++ b/test/common/upstream/test_cluster_manager.h @@ -26,22 +26,14 @@ #include "test/common/stats/stat_test_utility.h" #include "test/common/upstream/utility.h" #include "test/integration/clusters/custom_static_cluster.h" -#include "test/mocks/access_log/mocks.h" -#include "test/mocks/api/mocks.h" #include "test/mocks/config/xds_manager.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" -#include "test/mocks/secret/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" #include "test/mocks/server/overload_manager.h" #include "test/mocks/tcp/mocks.h" #include "test/mocks/thread_local/mocks.h" -#include "test/test_common/registry.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "test/test_common/utility.h" diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index 766fe5afad862..d3898794c0084 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -38,12 +38,10 @@ #include "test/common/upstream/utility.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" #include "test/mocks/server/options.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/health_checker.h" diff --git a/test/exe/BUILD b/test/exe/BUILD index 490244185f888..6759807f38a71 100644 --- a/test/exe/BUILD +++ b/test/exe/BUILD @@ -79,7 +79,6 @@ envoy_cc_test_library( "//source/common/stats:isolated_store_lib", "//source/exe:envoy_main_common_with_core_extensions_lib", "//source/exe:platform_impl_lib", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:contention_lib", "//test/test_common:environment_lib", "//test/test_common:thread_factory_for_test_lib", @@ -99,7 +98,6 @@ envoy_cc_test( "//source/exe:envoy_main_common_with_core_extensions_lib", "//source/exe:platform_impl_lib", "//source/extensions/clusters/dns:dns_cluster_lib", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:contention_lib", "//test/test_common:environment_lib", ], @@ -118,7 +116,6 @@ envoy_cc_test( "//source/exe:envoy_main_common_with_core_extensions_lib", "//source/exe:platform_impl_lib", "//source/extensions/clusters/dns:dns_cluster_lib", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:contention_lib", "//test/test_common:environment_lib", ], @@ -193,7 +190,6 @@ envoy_cc_test( deps = [ "//source/common/api:api_lib", "//source/exe:envoy_main_common_with_core_extensions_lib", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:contention_lib", "//test/test_common:environment_lib", ], @@ -216,7 +212,6 @@ envoy_cc_test( deps = [ "//source/common/api:api_lib", "//source/exe:main_common_with_all_extensions_lib", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:contention_lib", "//test/test_common:environment_lib", ], @@ -228,6 +223,5 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ "//source/exe:platform_impl_lib", - "//test/mocks/runtime:runtime_mocks", ], ) diff --git a/test/extensions/access_loggers/common/BUILD b/test/extensions/access_loggers/common/BUILD index 5236fc07bb3f1..2d6cd453fb8a4 100644 --- a/test/extensions/access_loggers/common/BUILD +++ b/test/extensions/access_loggers/common/BUILD @@ -26,9 +26,7 @@ envoy_cc_test( deps = [ "//source/common/protobuf", "//source/extensions/access_loggers/common:grpc_access_logger", - "//test/mocks/access_log:access_log_mocks", "//test/mocks/grpc:grpc_mocks", - "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/mocks/thread_local:thread_local_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/access_loggers/common/grpc_access_logger_test.cc b/test/extensions/access_loggers/common/grpc_access_logger_test.cc index ae31325695b29..c5311d75c50b1 100644 --- a/test/extensions/access_loggers/common/grpc_access_logger_test.cc +++ b/test/extensions/access_loggers/common/grpc_access_logger_test.cc @@ -11,9 +11,7 @@ #include "source/common/protobuf/protobuf.h" #include "source/extensions/access_loggers/common/grpc_access_logger.h" -#include "test/mocks/access_log/mocks.h" #include "test/mocks/grpc/mocks.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/thread_local/mocks.h" diff --git a/test/extensions/access_loggers/dynamic_modules/BUILD b/test/extensions/access_loggers/dynamic_modules/BUILD index 85ac807853e85..dec6098d0b1a7 100644 --- a/test/extensions/access_loggers/dynamic_modules/BUILD +++ b/test/extensions/access_loggers/dynamic_modules/BUILD @@ -42,7 +42,6 @@ envoy_cc_test( "//source/common/stats:isolated_store_lib", "//source/extensions/access_loggers/dynamic_modules:access_log_lib", "//test/extensions/dynamic_modules:util", - "//test/mocks/access_log:access_log_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/mocks/thread_local:thread_local_mocks", diff --git a/test/extensions/access_loggers/dynamic_modules/access_log_test.cc b/test/extensions/access_loggers/dynamic_modules/access_log_test.cc index e094fe59d0d4e..f293ec7df6b96 100644 --- a/test/extensions/access_loggers/dynamic_modules/access_log_test.cc +++ b/test/extensions/access_loggers/dynamic_modules/access_log_test.cc @@ -3,7 +3,6 @@ #include "source/extensions/access_loggers/dynamic_modules/access_log_config.h" #include "test/extensions/dynamic_modules/util.h" -#include "test/mocks/access_log/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/thread_local/mocks.h" diff --git a/test/extensions/access_loggers/filters/process_ratelimit/BUILD b/test/extensions/access_loggers/filters/process_ratelimit/BUILD index 0c2b684c8f126..6e8b60007b062 100644 --- a/test/extensions/access_loggers/filters/process_ratelimit/BUILD +++ b/test/extensions/access_loggers/filters/process_ratelimit/BUILD @@ -26,10 +26,8 @@ envoy_extension_cc_test( "//test/common/stream_info:test_util", "//test/common/upstream:utility_lib", "//test/mocks/access_log:access_log_mocks", - "//test/mocks/event:event_mocks", "//test/mocks/filesystem:filesystem_mocks", "//test/mocks/router:router_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/test_common:environment_lib", diff --git a/test/extensions/access_loggers/filters/process_ratelimit/access_log_impl_test.cc b/test/extensions/access_loggers/filters/process_ratelimit/access_log_impl_test.cc index c7356d0b00831..1287263cc202b 100644 --- a/test/extensions/access_loggers/filters/process_ratelimit/access_log_impl_test.cc +++ b/test/extensions/access_loggers/filters/process_ratelimit/access_log_impl_test.cc @@ -18,7 +18,6 @@ #include "test/common/stream_info/test_util.h" #include "test/mocks/access_log/mocks.h" #include "test/mocks/config/mocks.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/init/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/test_common/printers.h" diff --git a/test/extensions/access_loggers/filters/process_ratelimit/integration_test.cc b/test/extensions/access_loggers/filters/process_ratelimit/integration_test.cc index 86788d8492d75..d0a693c30e418 100644 --- a/test/extensions/access_loggers/filters/process_ratelimit/integration_test.cc +++ b/test/extensions/access_loggers/filters/process_ratelimit/integration_test.cc @@ -8,7 +8,6 @@ #include "test/integration/ads_integration.h" #include "test/integration/http_integration.h" #include "test/test_common/environment.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/access_loggers/fluentd/BUILD b/test/extensions/access_loggers/fluentd/BUILD index afc4c09463c73..78bd85662304c 100644 --- a/test/extensions/access_loggers/fluentd/BUILD +++ b/test/extensions/access_loggers/fluentd/BUILD @@ -52,7 +52,6 @@ envoy_extension_cc_test( "//source/extensions/filters/network/tcp_proxy:config", "//source/extensions/formatter/cel:config", "//test/integration:integration_lib", - "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/access_loggers/fluentd/v3:pkg_cc_proto", diff --git a/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc b/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc index 8f831373c3e95..eb96dafac5e9c 100644 --- a/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc +++ b/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc @@ -7,7 +7,6 @@ #include "test/integration/integration.h" #include "test/integration/utility.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc b/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc index 865646f017203..a3245ee45634e 100644 --- a/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc +++ b/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc @@ -4,7 +4,6 @@ #include "source/common/json/json_loader.h" #include "source/extensions/access_loggers/fluentd/substitution_formatter.h" -#include "test/mocks/access_log/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/access_loggers/grpc/BUILD b/test/extensions/access_loggers/grpc/BUILD index ebaffe9927926..3326d83e0762b 100644 --- a/test/extensions/access_loggers/grpc/BUILD +++ b/test/extensions/access_loggers/grpc/BUILD @@ -65,7 +65,6 @@ envoy_extension_cc_test( "//source/common/stream_info:uint32_accessor_lib", "//source/extensions/access_loggers/grpc:http_grpc_access_log_lib", "//test/mocks/access_log:access_log_mocks", - "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", diff --git a/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc b/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc index c856b355319c8..244b7c5b8049b 100644 --- a/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc +++ b/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc @@ -12,8 +12,6 @@ #include "test/mocks/access_log/mocks.h" #include "test/mocks/common.h" -#include "test/mocks/grpc/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/thread_local/mocks.h" diff --git a/test/extensions/access_loggers/open_telemetry/BUILD b/test/extensions/access_loggers/open_telemetry/BUILD index e4f0dbdf5ffa4..aa14e486b53e3 100644 --- a/test/extensions/access_loggers/open_telemetry/BUILD +++ b/test/extensions/access_loggers/open_telemetry/BUILD @@ -61,7 +61,6 @@ envoy_extension_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:server_factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:utility_lib", @@ -80,10 +79,8 @@ envoy_extension_cc_test( "//source/common/router:string_accessor_lib", "//source/extensions/access_loggers/open_telemetry:access_log_lib", "//test/mocks/access_log:access_log_mocks", - "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/server:server_mocks", - "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/mocks/thread_local:thread_local_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc b/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc index 9c65513b242c4..1e50cd01daf23 100644 --- a/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc +++ b/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc @@ -15,10 +15,7 @@ #include "test/mocks/access_log/mocks.h" #include "test/mocks/common.h" -#include "test/mocks/grpc/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/server/mocks.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/tracing/mocks.h" diff --git a/test/extensions/access_loggers/open_telemetry/http_access_log_impl_test.cc b/test/extensions/access_loggers/open_telemetry/http_access_log_impl_test.cc index 5fa51814ffffd..c7dc52ef1fca9 100644 --- a/test/extensions/access_loggers/open_telemetry/http_access_log_impl_test.cc +++ b/test/extensions/access_loggers/open_telemetry/http_access_log_impl_test.cc @@ -4,8 +4,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/stats/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/utility.h" diff --git a/test/extensions/access_loggers/stats/stats_test.cc b/test/extensions/access_loggers/stats/stats_test.cc index 10c874b7701f8..1f17cfb8cbef2 100644 --- a/test/extensions/access_loggers/stats/stats_test.cc +++ b/test/extensions/access_loggers/stats/stats_test.cc @@ -9,12 +9,10 @@ #include "test/common/memory/memory_test_utility.h" #include "test/mocks/config/mocks.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/stream_info/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/status_utility.h" #include "test/test_common/test_runtime.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/common/BUILD b/test/extensions/bootstrap/reverse_tunnel/common/BUILD index 09da77533096f..c4819baadf43d 100644 --- a/test/extensions/bootstrap/reverse_tunnel/common/BUILD +++ b/test/extensions/bootstrap/reverse_tunnel/common/BUILD @@ -18,7 +18,6 @@ envoy_cc_test( "//source/extensions/bootstrap/reverse_tunnel/common:reverse_connection_utility_lib", "//test/common/tls:mock_ssl_handshaker_lib", "//test/mocks/network:network_mocks", - "//test/mocks/ssl:ssl_mocks", "//test/test_common:logging_lib", "//test/test_common:test_runtime_lib", ], diff --git a/test/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility_test.cc b/test/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility_test.cc index ec509135021fc..25cd54ab99e5c 100644 --- a/test/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility_test.cc @@ -4,7 +4,6 @@ #include "test/common/tls/mock_ssl_handshaker.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/ssl/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/test_runtime.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD index d5563610fa04a..cf198bf4f06a4 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD @@ -25,7 +25,6 @@ envoy_extension_cc_test( "//source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface:reverse_tunnel_initiator_lib", "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:logging_lib", @@ -42,7 +41,6 @@ envoy_cc_test( "//source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface:reverse_tunnel_initiator_lib", "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "@envoy_api//envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3:pkg_cc_proto", @@ -62,7 +60,6 @@ envoy_cc_test( "//test/mocks/api:api_mocks", "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:threadsafe_singleton_injector_lib", @@ -81,7 +78,6 @@ envoy_cc_test( "//source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface:reverse_tunnel_initiator_lib", "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "@envoy_api//envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3:pkg_cc_proto", @@ -127,7 +123,6 @@ envoy_cc_test( "//source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface:reverse_tunnel_initiator_lib", "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "@envoy_api//envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3:pkg_cc_proto", diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle_test.cc index 206e35f01542a..eb872c8318373 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle_test.cc @@ -21,7 +21,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc index 4ed8ab1ef12c2..a315fbd7c2395 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc @@ -13,7 +13,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc index ce72514289fa8..0014534275cec 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc @@ -18,7 +18,6 @@ #include "test/mocks/api/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/threadsafe_singleton_injector.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc index 3f111d79c9c8f..977ec6072d485 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc @@ -10,7 +10,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_test.cc index 45eee1313f734..6e65a6f614568 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_test.cc @@ -12,7 +12,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/logging.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD index d26edf31c8627..4c752e4e3c1b9 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD @@ -22,7 +22,6 @@ envoy_extension_cc_test( "//source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface:reverse_tunnel_acceptor_lib", "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "@envoy_api//envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3:pkg_cc_proto", ], @@ -37,7 +36,6 @@ envoy_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/reverse_tunnel_reporting_service:reporter_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:logging_lib", "//test/test_common:registry_lib", @@ -55,7 +53,6 @@ envoy_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/reverse_tunnel_reporting_service:reporter_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:logging_lib", "//test/test_common:registry_lib", @@ -72,10 +69,8 @@ envoy_cc_test( "//source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface:reverse_tunnel_acceptor_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:logging_lib", - "//test/test_common:registry_lib", ], ) diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension_test.cc b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension_test.cc index 5652641daae87..7bca8bae663df 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension_test.cc @@ -12,7 +12,6 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/reverse_tunnel_reporting_service/reporter.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/registry.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_test.cc b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_test.cc index f2d3bc3cae835..1c5ce08077dec 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_test.cc @@ -7,7 +7,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "gmock/gmock.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_reverse_connection_io_handle_test.cc b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_reverse_connection_io_handle_test.cc index dccddfaf12994..4c37b43e658bb 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_reverse_connection_io_handle_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_reverse_connection_io_handle_test.cc @@ -11,7 +11,6 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/instance.h" -#include "test/test_common/registry.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager_test.cc b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager_test.cc index 024b61f0abbf5..b011667bfbd9e 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager_test.cc @@ -8,7 +8,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/reverse_tunnel_reporting_service/reporter.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/test_common/registry.h" diff --git a/test/extensions/bootstrap/wasm/BUILD b/test/extensions/bootstrap/wasm/BUILD index 9369812d1207f..5f83c02dc0b79 100644 --- a/test/extensions/bootstrap/wasm/BUILD +++ b/test/extensions/bootstrap/wasm/BUILD @@ -84,9 +84,7 @@ envoy_extension_cc_test( "//source/extensions/common/wasm:wasm_lib", "//test/extensions/bootstrap/wasm/test_data:start_cpp_plugin", "//test/extensions/common/wasm:wasm_runtime", - "//test/mocks/event:event_mocks", "//test/mocks/server:server_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", "@envoy_api//envoy/extensions/wasm/v3:pkg_cc_proto", diff --git a/test/extensions/bootstrap/wasm/config_test.cc b/test/extensions/bootstrap/wasm/config_test.cc index 24091074b5977..801c8abbb22ec 100644 --- a/test/extensions/bootstrap/wasm/config_test.cc +++ b/test/extensions/bootstrap/wasm/config_test.cc @@ -6,9 +6,7 @@ #include "source/extensions/bootstrap/wasm/config.h" #include "test/extensions/common/wasm/wasm_runtime.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/server/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/environment.h" diff --git a/test/extensions/clusters/aggregate/BUILD b/test/extensions/clusters/aggregate/BUILD index b6ff2718e79c7..27775f37caef4 100644 --- a/test/extensions/clusters/aggregate/BUILD +++ b/test/extensions/clusters/aggregate/BUILD @@ -21,10 +21,8 @@ envoy_extension_cc_test( "//source/extensions/load_balancing_policies/cluster_provided:config", "//source/extensions/transport_sockets/raw_buffer:config", "//test/common/upstream:utility_lib", - "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:admin_mocks", "//test/mocks/server:instance_mocks", - "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:load_balancer_context_mock", "//test/mocks/upstream:load_balancer_mocks", "//test/mocks/upstream:priority_set_mocks", @@ -51,9 +49,7 @@ envoy_extension_cc_test( "//source/extensions/transport_sockets/raw_buffer:config", "//test/common/upstream:test_cluster_manager", "//test/common/upstream:utility_lib", - "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_update_callbacks_mocks", "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", @@ -78,7 +74,6 @@ envoy_extension_cc_test( "//test/config:v2_link_hacks", "//test/integration:http_integration_lib", "//test/integration:integration_lib", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:network_utility_lib", "//test/test_common:resources_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/clusters/aggregate/cluster_integration_test.cc b/test/extensions/clusters/aggregate/cluster_integration_test.cc index fe6f7a17783c4..fe0ab80276998 100644 --- a/test/extensions/clusters/aggregate/cluster_integration_test.cc +++ b/test/extensions/clusters/aggregate/cluster_integration_test.cc @@ -14,7 +14,6 @@ #include "test/integration/utility.h" #include "test/test_common/network_utility.h" #include "test/test_common/resources.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "absl/synchronization/notification.h" diff --git a/test/extensions/clusters/aggregate/cluster_test.cc b/test/extensions/clusters/aggregate/cluster_test.cc index 0c1287ca5095c..e2ece779d9bf5 100644 --- a/test/extensions/clusters/aggregate/cluster_test.cc +++ b/test/extensions/clusters/aggregate/cluster_test.cc @@ -6,10 +6,8 @@ #include "source/extensions/clusters/aggregate/cluster.h" #include "test/common/upstream/utility.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/load_balancer.h" #include "test/mocks/upstream/load_balancer_context.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/clusters/aggregate/cluster_update_test.cc b/test/extensions/clusters/aggregate/cluster_update_test.cc index 85c44f33f8d91..0c99a5c78ba16 100644 --- a/test/extensions/clusters/aggregate/cluster_update_test.cc +++ b/test/extensions/clusters/aggregate/cluster_update_test.cc @@ -11,10 +11,8 @@ #include "test/common/upstream/test_cluster_manager.h" #include "test/common/upstream/utility.h" #include "test/mocks/config/xds_manager.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_update_callbacks.h" #include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" diff --git a/test/extensions/clusters/common/BUILD b/test/extensions/clusters/common/BUILD index 31909ca3211ca..43db0369ad3f8 100644 --- a/test/extensions/clusters/common/BUILD +++ b/test/extensions/clusters/common/BUILD @@ -30,7 +30,6 @@ envoy_cc_test( "//source/common/router:string_accessor_lib", "//source/common/stream_info:filter_state_lib", "//source/extensions/clusters/common:logical_host_lib", - "//test/mocks/event:event_mocks", "//test/mocks/network:transport_socket_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:host_mocks", diff --git a/test/extensions/clusters/common/logical_host_test.cc b/test/extensions/clusters/common/logical_host_test.cc index 20f3bc3928bdd..5de65c2cb0ba3 100644 --- a/test/extensions/clusters/common/logical_host_test.cc +++ b/test/extensions/clusters/common/logical_host_test.cc @@ -3,7 +3,6 @@ #include "source/common/stream_info/filter_state_impl.h" #include "source/extensions/clusters/common/logical_host.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/network/transport_socket.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/host.h" diff --git a/test/extensions/clusters/composite/BUILD b/test/extensions/clusters/composite/BUILD index 311288ab2c555..7cfc96395399a 100644 --- a/test/extensions/clusters/composite/BUILD +++ b/test/extensions/clusters/composite/BUILD @@ -23,7 +23,6 @@ envoy_extension_cc_test( "//test/mocks/http:conn_pool_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:connection_mocks", - "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:admin_mocks", "//test/mocks/server:instance_mocks", "//test/mocks/ssl:ssl_mocks", diff --git a/test/extensions/clusters/dynamic_forward_proxy/BUILD b/test/extensions/clusters/dynamic_forward_proxy/BUILD index c838d583885cd..9c75ba50c759f 100644 --- a/test/extensions/clusters/dynamic_forward_proxy/BUILD +++ b/test/extensions/clusters/dynamic_forward_proxy/BUILD @@ -26,7 +26,6 @@ envoy_extension_cc_test( "//source/extensions/transport_sockets/tls:config", "//test/common/upstream:utility_lib", "//test/extensions/common/dynamic_forward_proxy:mocks", - "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:admin_mocks", "//test/mocks/server:instance_mocks", "//test/mocks/server:options_mocks", diff --git a/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc b/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc index 0ed0237ef0ac6..7f346e01677e2 100644 --- a/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc +++ b/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc @@ -11,7 +11,6 @@ #include "test/extensions/common/dynamic_forward_proxy/mocks.h" #include "test/mocks/http/conn_pool.h" #include "test/mocks/network/connection.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" #include "test/mocks/server/options.h" diff --git a/test/extensions/clusters/dynamic_modules/BUILD b/test/extensions/clusters/dynamic_modules/BUILD index 7880b24743416..34ecf00e5d820 100644 --- a/test/extensions/clusters/dynamic_modules/BUILD +++ b/test/extensions/clusters/dynamic_modules/BUILD @@ -27,7 +27,6 @@ envoy_cc_test( "//source/extensions/transport_sockets/raw_buffer:config", "//test/common/upstream:utility_lib", "//test/extensions/dynamic_modules:util", - "//test/mocks/event:event_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:instance_mocks", diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index 7550325635f38..f0ea6b5404291 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -7,7 +7,6 @@ #include "test/common/upstream/utility.h" #include "test/extensions/dynamic_modules/util.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/connection.h" #include "test/mocks/server/instance.h" diff --git a/test/extensions/clusters/eds/eds_speed_test.cc b/test/extensions/clusters/eds/eds_speed_test.cc index 2c886d74ed614..67d045b8bc07c 100644 --- a/test/extensions/clusters/eds/eds_speed_test.cc +++ b/test/extensions/clusters/eds/eds_speed_test.cc @@ -24,11 +24,9 @@ #include "test/mocks/config/custom_config_validators.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" #include "test/mocks/server/options.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/clusters/eds/eds_test.cc b/test/extensions/clusters/eds/eds_test.cc index 92bc8a04f48fe..d6f0e9d08516c 100644 --- a/test/extensions/clusters/eds/eds_test.cc +++ b/test/extensions/clusters/eds/eds_test.cc @@ -15,9 +15,7 @@ #include "test/common/stats/stat_test_utility.h" #include "test/common/upstream/utility.h" #include "test/mocks/config/eds_resources_cache.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" #include "test/mocks/ssl/mocks.h" diff --git a/test/extensions/clusters/eds/leds_test.cc b/test/extensions/clusters/eds/leds_test.cc index b31deabd324bf..39e25ca15ef5d 100644 --- a/test/extensions/clusters/eds/leds_test.cc +++ b/test/extensions/clusters/eds/leds_test.cc @@ -10,7 +10,6 @@ #include "source/extensions/clusters/eds/leds.h" #include "test/common/stats/stat_test_utility.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/admin.h" diff --git a/test/extensions/clusters/logical_dns/BUILD b/test/extensions/clusters/logical_dns/BUILD index 8836173b6a5e9..b95be0057a6cf 100644 --- a/test/extensions/clusters/logical_dns/BUILD +++ b/test/extensions/clusters/logical_dns/BUILD @@ -23,14 +23,10 @@ envoy_cc_test( "//source/server:transport_socket_config_lib", "//test/common/upstream:utility_lib", "//test/mocks:common_lib", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:admin_mocks", "//test/mocks/server:instance_mocks", - "//test/mocks/ssl:ssl_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc b/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc index 75531cee4898e..98c31ec08b6b8 100644 --- a/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc +++ b/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc @@ -21,14 +21,10 @@ #include "test/common/upstream/utility.h" #include "test/mocks/common.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" -#include "test/mocks/ssl/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/clusters/mcp_multicluster/BUILD b/test/extensions/clusters/mcp_multicluster/BUILD index 1594d04d3d0b9..7eb0fbd0a8791 100644 --- a/test/extensions/clusters/mcp_multicluster/BUILD +++ b/test/extensions/clusters/mcp_multicluster/BUILD @@ -23,7 +23,6 @@ envoy_extension_cc_test( "//test/mocks/http:conn_pool_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:connection_mocks", - "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:admin_mocks", "//test/mocks/server:instance_mocks", "//test/mocks/ssl:ssl_mocks", diff --git a/test/extensions/clusters/original_dst/BUILD b/test/extensions/clusters/original_dst/BUILD index 7e170231e17e7..b226dcc7b33a0 100644 --- a/test/extensions/clusters/original_dst/BUILD +++ b/test/extensions/clusters/original_dst/BUILD @@ -22,13 +22,9 @@ envoy_cc_test( "//source/extensions/transport_sockets/raw_buffer:config", "//test/common/upstream:utility_lib", "//test/mocks:common_lib", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:admin_mocks", "//test/mocks/server:instance_mocks", - "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", diff --git a/test/extensions/clusters/original_dst/original_dst_cluster_test.cc b/test/extensions/clusters/original_dst/original_dst_cluster_test.cc index c77138470ad22..05ea8478c3e3f 100644 --- a/test/extensions/clusters/original_dst/original_dst_cluster_test.cc +++ b/test/extensions/clusters/original_dst/original_dst_cluster_test.cc @@ -19,11 +19,8 @@ #include "test/common/upstream/utility.h" #include "test/mocks/common.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/utility.h" diff --git a/test/extensions/clusters/redis/BUILD b/test/extensions/clusters/redis/BUILD index d0b8031f86312..2f16341c58744 100644 --- a/test/extensions/clusters/redis/BUILD +++ b/test/extensions/clusters/redis/BUILD @@ -36,14 +36,10 @@ envoy_extension_cc_test( "//test/extensions/filters/network/common/redis:test_utils_lib", "//test/extensions/filters/network/redis_proxy:redis_mocks", "//test/mocks:common_lib", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:admin_mocks", "//test/mocks/server:instance_mocks", - "//test/mocks/ssl:ssl_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:health_check_event_logger_mocks", @@ -81,13 +77,9 @@ envoy_extension_cc_test( "//test/extensions/filters/network/common/redis:test_utils_lib", "//test/extensions/filters/network/redis_proxy:redis_mocks", "//test/mocks:common_lib", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_mocks", - "//test/mocks/ssl:ssl_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/clusters/redis/redis_cluster_test.cc b/test/extensions/clusters/redis/redis_cluster_test.cc index daf888f59e5da..363b56f18ec9b 100644 --- a/test/extensions/clusters/redis/redis_cluster_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_test.cc @@ -24,7 +24,6 @@ #include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/cluster_priority_set.h" #include "test/mocks/upstream/health_check_event_logger.h" diff --git a/test/extensions/clusters/reverse_connection/BUILD b/test/extensions/clusters/reverse_connection/BUILD index a13065d252de0..1d57201e14b7d 100644 --- a/test/extensions/clusters/reverse_connection/BUILD +++ b/test/extensions/clusters/reverse_connection/BUILD @@ -28,11 +28,9 @@ envoy_cc_test( "//test/common/upstream:utility_lib", "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:admin_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:instance_mocks", - "//test/mocks/ssl:ssl_mocks", "//test/test_common:registry_lib", "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc b/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc index d65285c5cd278..937895d94b425 100644 --- a/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc +++ b/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc @@ -27,14 +27,11 @@ #include "test/mocks/common.h" #include "test/mocks/network/mocks.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/instance.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/upstream/cluster_manager.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "absl/strings/str_cat.h" diff --git a/test/extensions/common/aws/BUILD b/test/extensions/common/aws/BUILD index b2394147f7ba8..e1ccda24b87e1 100644 --- a/test/extensions/common/aws/BUILD +++ b/test/extensions/common/aws/BUILD @@ -37,7 +37,6 @@ envoy_cc_test( "//source/extensions/common/aws:credential_provider_chains_lib", "//source/extensions/common/aws/credential_providers:inline_credentials_provider_lib", "//test/extensions/common/aws:aws_mocks", - "//test/mocks/api:api_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:environment_lib", @@ -90,7 +89,6 @@ envoy_cc_test( "//source/extensions/common/aws:metadata_fetcher_lib", "//test/extensions/common/aws:aws_mocks", "//test/extensions/filters/http/common:mock_lib", - "//test/mocks/api:api_mocks", "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", "//test/test_common:environment_lib", diff --git a/test/extensions/common/dynamic_forward_proxy/BUILD b/test/extensions/common/dynamic_forward_proxy/BUILD index 9377fb37c9fd8..c85e0369b61ba 100644 --- a/test/extensions/common/dynamic_forward_proxy/BUILD +++ b/test/extensions/common/dynamic_forward_proxy/BUILD @@ -21,7 +21,6 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:registry_lib", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", diff --git a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc index 4bf75efde53d7..e68c91ab49038 100644 --- a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc +++ b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc @@ -9,12 +9,8 @@ #include "source/extensions/common/dynamic_forward_proxy/dns_cache_manager_impl.h" #include "test/extensions/common/dynamic_forward_proxy/mocks.h" -#include "test/mocks/filesystem/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/thread_local/mocks.h" #include "test/test_common/registry.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" diff --git a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc index 3698f0d42c10b..2fb67266f9ddc 100644 --- a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc +++ b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc @@ -1,7 +1,6 @@ #include "envoy/network/address.h" #include "source/common/buffer/buffer_impl.h" -#include "source/common/common/logger.h" #include "source/common/network/address_impl.h" #include "source/extensions/common/proxy_protocol/proxy_protocol_header.h" diff --git a/test/extensions/common/redis/cluster_refresh_manager_test.cc b/test/extensions/common/redis/cluster_refresh_manager_test.cc index f834d6fc4a038..acff419cdbbea 100644 --- a/test/extensions/common/redis/cluster_refresh_manager_test.cc +++ b/test/extensions/common/redis/cluster_refresh_manager_test.cc @@ -8,7 +8,6 @@ #include "test/extensions/filters/network/common/redis/mocks.h" #include "test/extensions/filters/network/redis_proxy/mocks.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/priority_set.h" #include "test/test_common/simulated_time_system.h" diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index e40dfd6258832..f185858a54c80 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -180,7 +180,6 @@ envoy_cc_test( "//source/extensions/common/wasm:remote_async_datasource_lib", "//test/mocks/event:event_mocks", "//test/mocks/init:init_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/config/validators/minimum_clusters/BUILD b/test/extensions/config/validators/minimum_clusters/BUILD index ba1aea991f0fd..1639e3311bfe7 100644 --- a/test/extensions/config/validators/minimum_clusters/BUILD +++ b/test/extensions/config/validators/minimum_clusters/BUILD @@ -51,7 +51,6 @@ envoy_extension_cc_test( "//test/common/grpc:grpc_client_integration_lib", "//test/config:v2_link_hacks", "//test/integration:http_integration_lib", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:network_utility_lib", "//test/test_common:resources_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc b/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc index 42ac0309d646a..dd2ebb697dec7 100644 --- a/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc +++ b/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc @@ -14,7 +14,6 @@ #include "test/integration/utility.h" #include "test/test_common/network_utility.h" #include "test/test_common/resources.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/config_subscription/filesystem/filesystem_subscription_impl_test.cc b/test/extensions/config_subscription/filesystem/filesystem_subscription_impl_test.cc index e412e1d759df2..9285b302417ba 100644 --- a/test/extensions/config_subscription/filesystem/filesystem_subscription_impl_test.cc +++ b/test/extensions/config_subscription/filesystem/filesystem_subscription_impl_test.cc @@ -6,6 +6,7 @@ #include "test/extensions/config_subscription/filesystem/filesystem_subscription_test_harness.h" #include "test/mocks/event/mocks.h" #include "test/mocks/filesystem/mocks.h" +#include "test/mocks/stats/mocks.h" #include "test/test_common/logging.h" #include "gmock/gmock.h" diff --git a/test/extensions/config_subscription/grpc/BUILD b/test/extensions/config_subscription/grpc/BUILD index 4c40a538527cf..6aec3a82f348a 100644 --- a/test/extensions/config_subscription/grpc/BUILD +++ b/test/extensions/config_subscription/grpc/BUILD @@ -28,7 +28,6 @@ envoy_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:logging_lib", "//test/test_common:resources_lib", "//test/test_common:simulated_time_system_lib", @@ -61,7 +60,6 @@ envoy_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/upstream:load_stats_reporter_mocks", "//test/test_common:logging_lib", "//test/test_common:resources_lib", @@ -90,7 +88,6 @@ envoy_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:logging_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", @@ -112,7 +109,6 @@ envoy_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:logging_lib", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", @@ -133,7 +129,6 @@ envoy_cc_test_library( "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", @@ -153,7 +148,6 @@ envoy_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:logging_lib", "//test/test_common:utility_lib", "@abseil-cpp//absl/container:flat_hash_set", @@ -179,7 +173,6 @@ envoy_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/test_common:logging_lib", "//test/test_common:resources_lib", "//test/test_common:simulated_time_system_lib", diff --git a/test/extensions/config_subscription/grpc/delta_subscription_test_harness.h b/test/extensions/config_subscription/grpc/delta_subscription_test_harness.h index d506ca004d7f7..da1f74e033ebb 100644 --- a/test/extensions/config_subscription/grpc/delta_subscription_test_harness.h +++ b/test/extensions/config_subscription/grpc/delta_subscription_test_harness.h @@ -20,7 +20,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" #include "test/mocks/local_info/mocks.h" -#include "test/mocks/stats/mocks.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/config_subscription/grpc/eds_resources_cache_impl_test.cc b/test/extensions/config_subscription/grpc/eds_resources_cache_impl_test.cc index ae65e05b390bb..ae4ec40c35004 100644 --- a/test/extensions/config_subscription/grpc/eds_resources_cache_impl_test.cc +++ b/test/extensions/config_subscription/grpc/eds_resources_cache_impl_test.cc @@ -1,6 +1,5 @@ #include "source/extensions/config_subscription/grpc/eds_resources_cache_impl.h" -#include "test/mocks/event/mocks.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" diff --git a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc index 3130053717657..15037f23eb5e0 100644 --- a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc @@ -23,7 +23,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" #include "test/mocks/local_info/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/upstream/load_stats_reporter.h" #include "test/test_common/logging.h" #include "test/test_common/resources.h" diff --git a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc index ad14ca457b13d..8a5431debc91d 100644 --- a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc @@ -23,7 +23,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" #include "test/mocks/local_info/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/resources.h" diff --git a/test/extensions/config_subscription/grpc/sotw_subscription_state_test.cc b/test/extensions/config_subscription/grpc/sotw_subscription_state_test.cc index 43ae101bd42d5..0edb24b57a432 100644 --- a/test/extensions/config_subscription/grpc/sotw_subscription_state_test.cc +++ b/test/extensions/config_subscription/grpc/sotw_subscription_state_test.cc @@ -8,7 +8,6 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/test_common/simulated_time_system.h" #include "absl/container/flat_hash_set.h" diff --git a/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc b/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc index 943a5328ee466..e6dbc02089a1c 100644 --- a/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc +++ b/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc @@ -1,7 +1,6 @@ #include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" -#include "source/common/common/logger.h" #include "source/common/tls/server_context_config_impl.h" #include "source/common/tls/server_ssl_socket.h" #include "source/common/tls/ssl_socket.h" diff --git a/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc index 57023ac5b1d72..2567f4e68d148 100644 --- a/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc @@ -22,7 +22,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" #include "test/mocks/local_info/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/resources.h" #include "test/test_common/simulated_time_system.h" diff --git a/test/extensions/config_subscription/rest/BUILD b/test/extensions/config_subscription/rest/BUILD index db6f973b8a81d..c314701d29b43 100644 --- a/test/extensions/config_subscription/rest/BUILD +++ b/test/extensions/config_subscription/rest/BUILD @@ -32,7 +32,6 @@ envoy_cc_test_library( "//test/mocks/event:event_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/config_subscription/rest/http_subscription_test_harness.h b/test/extensions/config_subscription/rest/http_subscription_test_harness.h index 36059d460535d..36b2f31eede89 100644 --- a/test/extensions/config_subscription/rest/http_subscription_test_harness.h +++ b/test/extensions/config_subscription/rest/http_subscription_test_harness.h @@ -20,7 +20,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/utility.h" diff --git a/test/extensions/dynamic_modules/http/BUILD b/test/extensions/dynamic_modules/http/BUILD index 090025aa762d6..10a3a7812329a 100644 --- a/test/extensions/dynamic_modules/http/BUILD +++ b/test/extensions/dynamic_modules/http/BUILD @@ -96,7 +96,6 @@ envoy_cc_test( "//test/extensions/dynamic_modules:util", "//test/mocks/http:http_mocks", "//test/mocks/server:server_factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:thread_local_cluster_mocks", ], diff --git a/test/extensions/dynamic_modules/http/filter_test.cc b/test/extensions/dynamic_modules/http/filter_test.cc index 0212a477abf12..4efb3248d17dd 100644 --- a/test/extensions/dynamic_modules/http/filter_test.cc +++ b/test/extensions/dynamic_modules/http/filter_test.cc @@ -11,7 +11,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/thread_local_cluster.h" diff --git a/test/extensions/filters/common/rbac/engine_impl_test.cc b/test/extensions/filters/common/rbac/engine_impl_test.cc index 1bd52515017ed..4c333bd7758ca 100644 --- a/test/extensions/filters/common/rbac/engine_impl_test.cc +++ b/test/extensions/filters/common/rbac/engine_impl_test.cc @@ -10,7 +10,6 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.cc b/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.cc index 4d1231966960e..57affa3a51801 100644 --- a/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.cc +++ b/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.cc @@ -3,7 +3,6 @@ #include "source/common/http/header_map_impl.h" #include "test/integration/http_integration.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/http/admission_control/BUILD b/test/extensions/filters/http/admission_control/BUILD index c7e148f35db38..0f9c56ed535d1 100644 --- a/test/extensions/filters/http/admission_control/BUILD +++ b/test/extensions/filters/http/admission_control/BUILD @@ -43,7 +43,6 @@ envoy_extension_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/thread_local:thread_local_mocks", - "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/admission_control/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/admission_control/config_test.cc b/test/extensions/filters/http/admission_control/config_test.cc index 3e25181aadc35..c8458eb1022ae 100644 --- a/test/extensions/filters/http/admission_control/config_test.cc +++ b/test/extensions/filters/http/admission_control/config_test.cc @@ -12,7 +12,6 @@ #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/thread_local/mocks.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/aws_request_signing/BUILD b/test/extensions/filters/http/aws_request_signing/BUILD index 181e778ed0608..459880e290ca6 100644 --- a/test/extensions/filters/http/aws_request_signing/BUILD +++ b/test/extensions/filters/http/aws_request_signing/BUILD @@ -47,6 +47,7 @@ envoy_extension_cc_test( "//source/extensions/network/dns_resolver/getaddrinfo:config", "//test/extensions/common/aws:aws_mocks", "//test/integration:http_integration_lib", + "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/aws_request_signing/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_integration_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_integration_test.cc index b4233aeacb656..fa2de5db10138 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_integration_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_integration_test.cc @@ -1,7 +1,6 @@ #include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.h" #include "envoy/upstream/load_balancer.h" -#include "source/common/common/logger.h" #include "source/common/upstream/cluster_factory_impl.h" #include "source/extensions/clusters/dns/dns_cluster.h" #include "source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.h" diff --git a/test/extensions/filters/http/buffer/BUILD b/test/extensions/filters/http/buffer/BUILD index dd9bbce49d3b0..a777195e98d6c 100644 --- a/test/extensions/filters/http/buffer/BUILD +++ b/test/extensions/filters/http/buffer/BUILD @@ -23,10 +23,6 @@ envoy_extension_cc_test( "//source/extensions/filters/http/buffer:buffer_filter_lib", "//test/mocks/buffer:buffer_mocks", "//test/mocks/http:http_mocks", - "//test/mocks/init:init_mocks", - "//test/mocks/local_info:local_info_mocks", - "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/extensions/filters/http/buffer/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc b/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc index ba1fe450a86b8..c66fcfd1ea5b6 100644 --- a/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc +++ b/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc @@ -9,7 +9,6 @@ #include "source/extensions/filters/http/cache/http_cache.h" #include "test/mocks/http/mocks.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "absl/cleanup/cleanup.h" diff --git a/test/extensions/filters/http/cache/http_cache_implementation_test_common.h b/test/extensions/filters/http/cache/http_cache_implementation_test_common.h index 300ba8029e657..0a14772cac3e6 100644 --- a/test/extensions/filters/http/cache/http_cache_implementation_test_common.h +++ b/test/extensions/filters/http/cache/http_cache_implementation_test_common.h @@ -7,7 +7,6 @@ #include "source/extensions/filters/http/cache/cache_headers_utils.h" #include "source/extensions/filters/http/cache/http_cache.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/test_common/simulated_time_system.h" diff --git a/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc b/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc index 92c09b8596f88..5ff99d105d25b 100644 --- a/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc +++ b/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc @@ -10,7 +10,6 @@ #include "test/extensions/filters/http/cache_v2/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "absl/cleanup/cleanup.h" diff --git a/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.h b/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.h index 8ddfbd6a82aea..6e25a07227829 100644 --- a/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.h +++ b/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.h @@ -7,7 +7,6 @@ #include "source/extensions/filters/http/cache_v2/cache_headers_utils.h" #include "source/extensions/filters/http/cache_v2/http_cache.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/common/BUILD b/test/extensions/filters/http/common/BUILD index 64b2c71d8a981..7e4544de3673c 100644 --- a/test/extensions/filters/http/common/BUILD +++ b/test/extensions/filters/http/common/BUILD @@ -70,7 +70,6 @@ envoy_cc_test( "//test/common/http:common_lib", "//test/mocks/http:http_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/filters/http/common/stream_rate_limiter_test.cc b/test/extensions/filters/http/common/stream_rate_limiter_test.cc index 741c4f21cd1f9..cc8dbbe5214a2 100644 --- a/test/extensions/filters/http/common/stream_rate_limiter_test.cc +++ b/test/extensions/filters/http/common/stream_rate_limiter_test.cc @@ -7,7 +7,6 @@ #include "test/common/http/common.h" #include "test/mocks/http/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/printers.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/compressor/compressor_filter_speed_test.cc b/test/extensions/filters/http/compressor/compressor_filter_speed_test.cc index cdb1f93aeb3d9..36afae5af090a 100644 --- a/test/extensions/filters/http/compressor/compressor_filter_speed_test.cc +++ b/test/extensions/filters/http/compressor/compressor_filter_speed_test.cc @@ -10,7 +10,6 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/stats/mocks.h" #include "benchmark/benchmark.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/compressor/compressor_filter_test.cc b/test/extensions/filters/http/compressor/compressor_filter_test.cc index 660ca44aaef17..5384bdc70862f 100644 --- a/test/extensions/filters/http/compressor/compressor_filter_test.cc +++ b/test/extensions/filters/http/compressor/compressor_filter_test.cc @@ -9,7 +9,6 @@ #include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/cors/cors_filter_test.cc b/test/extensions/filters/http/cors/cors_filter_test.cc index 21f6505be08a3..84e929ca27e4c 100644 --- a/test/extensions/filters/http/cors/cors_filter_test.cc +++ b/test/extensions/filters/http/cors/cors_filter_test.cc @@ -7,7 +7,6 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/printers.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/credential_injector/BUILD b/test/extensions/filters/http/credential_injector/BUILD index 7dfca908c5101..57def3a845305 100644 --- a/test/extensions/filters/http/credential_injector/BUILD +++ b/test/extensions/filters/http/credential_injector/BUILD @@ -41,7 +41,6 @@ envoy_extension_cc_test( "//source/extensions/filters/http/credential_injector:credential_injector_lib", "//source/extensions/http/injected_credentials/generic:config", "//source/extensions/http/injected_credentials/oauth2:client_credentials_lib", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:server_mocks", "//test/test_common:utility_lib", diff --git a/test/extensions/filters/http/csrf/csrf_filter_test.cc b/test/extensions/filters/http/csrf/csrf_filter_test.cc index 400e390c178ad..83da3aee088e5 100644 --- a/test/extensions/filters/http/csrf/csrf_filter_test.cc +++ b/test/extensions/filters/http/csrf/csrf_filter_test.cc @@ -7,7 +7,6 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/printers.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/custom_response/BUILD b/test/extensions/filters/http/custom_response/BUILD index f6c4894f18d0d..ed5e284435508 100644 --- a/test/extensions/filters/http/custom_response/BUILD +++ b/test/extensions/filters/http/custom_response/BUILD @@ -67,10 +67,8 @@ envoy_extension_cc_test( "//source/extensions/http/custom_response/redirect_policy:redirect_policy_lib", "//source/extensions/matching/network/common:inputs_lib", "//test/common/http:common_lib", - "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:server_mocks", - "//test/mocks/tracing:tracing_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/custom_response/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/custom_response/custom_response_filter_test.cc b/test/extensions/filters/http/custom_response/custom_response_filter_test.cc index 66b3cc7b50e80..deb68ce3b86e1 100644 --- a/test/extensions/filters/http/custom_response/custom_response_filter_test.cc +++ b/test/extensions/filters/http/custom_response/custom_response_filter_test.cc @@ -5,12 +5,9 @@ #include "source/extensions/filters/http/custom_response/factory.h" #include "test/common/http/common.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/init/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/server/mocks.h" -#include "test/mocks/tracing/mocks.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/decompressor/BUILD b/test/extensions/filters/http/decompressor/BUILD index 19b962efdf38a..4d30b3f65945d 100644 --- a/test/extensions/filters/http/decompressor/BUILD +++ b/test/extensions/filters/http/decompressor/BUILD @@ -23,7 +23,6 @@ envoy_extension_cc_test( "//source/extensions/filters/http/decompressor:config", "//test/mocks/compression/decompressor:decompressor_mocks", "//test/mocks/http:http_mocks", - "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/decompressor/v3:pkg_cc_proto", @@ -45,7 +44,6 @@ envoy_extension_cc_test( "//test/integration:http_integration_lib", "//test/integration/filters:encoder_decoder_buffer_filter_lib", "//test/mocks/server:factory_context_mocks", - "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc b/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc index f0c063e0df256..8f20be58b0a94 100644 --- a/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc +++ b/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc @@ -4,7 +4,6 @@ #include "test/integration/http_integration.h" #include "test/mocks/server/factory_context.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/http/decompressor/decompressor_filter_test.cc b/test/extensions/filters/http/decompressor/decompressor_filter_test.cc index 646132ef4f727..1aeb59715cd97 100644 --- a/test/extensions/filters/http/decompressor/decompressor_filter_test.cc +++ b/test/extensions/filters/http/decompressor/decompressor_filter_test.cc @@ -7,9 +7,7 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/compression/decompressor/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/http/dynamic_forward_proxy/BUILD b/test/extensions/filters/http/dynamic_forward_proxy/BUILD index 1800e3b7d6803..99eaea69c275f 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/BUILD +++ b/test/extensions/filters/http/dynamic_forward_proxy/BUILD @@ -103,6 +103,7 @@ envoy_extension_cc_test( "//source/extensions/network/dns_resolver/getaddrinfo:config", "//test/integration:http_integration_lib", "//test/integration/filters:stream_info_to_headers_filter_lib", + "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", "//test/test_common:threadsafe_singleton_injector_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", @@ -138,6 +139,7 @@ envoy_extension_cc_test( "//source/extensions/network/dns_resolver/getaddrinfo:config", "//test/integration:http_integration_lib", "//test/integration/filters:stream_info_to_headers_filter_lib", + "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", "//test/test_common:threadsafe_singleton_injector_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/ext_authz/BUILD b/test/extensions/filters/http/ext_authz/BUILD index 56be0192ba29e..18014166b846e 100644 --- a/test/extensions/filters/http/ext_authz/BUILD +++ b/test/extensions/filters/http/ext_authz/BUILD @@ -34,9 +34,7 @@ envoy_extension_cc_test( "//test/extensions/filters/common/ext_authz:ext_authz_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_factory_context_mocks", - "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/proto:helloworld_proto_cc_proto", "//test/test_common:test_runtime_lib", diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index 9ba634d404048..ea0bf9268010d 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -23,9 +23,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/proto/helloworld.pb.h" #include "test/test_common/printers.h" diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index f82382a5f89a0..9a6b2c0c01fa8 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -112,7 +112,6 @@ envoy_extension_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/http:stream_encoder_mock", "//test/mocks/http:stream_mock", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:overload_manager_mocks", "//test/mocks/server:server_factory_context_mocks", "//test/proto:helloworld_proto_cc_proto", @@ -300,7 +299,6 @@ envoy_extension_cc_test( "//source/extensions/filters/http/ext_proc", "//test/common/http:common_lib", "//test/mocks/event:event_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/server:server_factory_context_mocks", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -341,7 +339,6 @@ envoy_extension_cc_test( deps = [ ":utils_lib", "//source/extensions/filters/http/ext_proc:matching_utils_lib", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:utility_lib", diff --git a/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc index adf4b9dd4a51b..49e1ddabbddff 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc @@ -26,7 +26,6 @@ #include "test/extensions/filters/http/ext_proc/utils.h" #include "test/integration/filters/common.h" #include "test/integration/http_integration.h" -#include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index 708b03d72fead..bf8d5969e381c 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -28,7 +28,6 @@ #include "test/integration/filters/common.h" #include "test/integration/http_integration.h" #include "test/test_common/environment.h" -#include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/ext_proc/ext_proc_observability_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_observability_integration_test.cc index 4c9c426483f45..f0576b0f6ef94 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_observability_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_observability_integration_test.cc @@ -26,7 +26,6 @@ #include "test/extensions/filters/http/ext_proc/utils.h" #include "test/integration/filters/common.h" #include "test/integration/http_integration.h" -#include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index 469b1f67a455a..99a4536070e83 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -29,13 +29,10 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/http/stream_encoder.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" -#include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/printers.h" #include "test/test_common/registry.h" diff --git a/test/extensions/filters/http/ext_proc/matching_utils_test.cc b/test/extensions/filters/http/ext_proc/matching_utils_test.cc index 724549dd2fc32..b91d0addee8d3 100644 --- a/test/extensions/filters/http/ext_proc/matching_utils_test.cc +++ b/test/extensions/filters/http/ext_proc/matching_utils_test.cc @@ -2,7 +2,6 @@ #include "source/extensions/filters/common/expr/evaluator.h" #include "source/extensions/filters/http/ext_proc/matching_utils.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/ext_proc/ordering_test.cc b/test/extensions/filters/http/ext_proc/ordering_test.cc index f8e4bfe1c0d86..1c1dfe2cb62a9 100644 --- a/test/extensions/filters/http/ext_proc/ordering_test.cc +++ b/test/extensions/filters/http/ext_proc/ordering_test.cc @@ -11,7 +11,6 @@ #include "test/mocks/router/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" -#include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/test_runtime.h" diff --git a/test/extensions/filters/http/file_system_buffer/filter_test.cc b/test/extensions/filters/http/file_system_buffer/filter_test.cc index 80a854a031e3d..7a0e3e1f52016 100644 --- a/test/extensions/filters/http/file_system_buffer/filter_test.cc +++ b/test/extensions/filters/http/file_system_buffer/filter_test.cc @@ -6,7 +6,6 @@ #include "test/extensions/common/async_files/mocks.h" #include "test/mocks/buffer/mocks.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/server/mocks.h" #include "test/mocks/upstream/mocks.h" diff --git a/test/extensions/filters/http/gcp_authn/BUILD b/test/extensions/filters/http/gcp_authn/BUILD index d8cb865c6b17a..caa8b206e7878 100644 --- a/test/extensions/filters/http/gcp_authn/BUILD +++ b/test/extensions/filters/http/gcp_authn/BUILD @@ -38,7 +38,6 @@ envoy_extension_cc_test( "//source/extensions/filters/http/gcp_authn", "//source/extensions/filters/http/gcp_authn:config", "//test/common/http:common_lib", - "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:server_mocks", "//test/test_common:test_runtime_lib", diff --git a/test/extensions/filters/http/gcp_authn/gcp_authn_filter_test.cc b/test/extensions/filters/http/gcp_authn/gcp_authn_filter_test.cc index cc5cb11084996..7eeae367515a6 100644 --- a/test/extensions/filters/http/gcp_authn/gcp_authn_filter_test.cc +++ b/test/extensions/filters/http/gcp_authn/gcp_authn_filter_test.cc @@ -6,9 +6,7 @@ #include "test/common/http/common.h" #include "test/extensions/filters/http/gcp_authn/mocks.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/init/mocks.h" #include "test/mocks/server/mocks.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc b/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc index ba955d332683c..8a6b8d9ddf931 100644 --- a/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc +++ b/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc @@ -1,7 +1,6 @@ #include "envoy/extensions/filters/http/geoip/v3/geoip.pb.h" #include "test/integration/http_integration.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/grpc_http1_reverse_bridge/reverse_bridge_test.cc b/test/extensions/filters/http/grpc_http1_reverse_bridge/reverse_bridge_test.cc index 20b175d5b4a40..54cf53ae2aefe 100644 --- a/test/extensions/filters/http/grpc_http1_reverse_bridge/reverse_bridge_test.cc +++ b/test/extensions/filters/http/grpc_http1_reverse_bridge/reverse_bridge_test.cc @@ -10,8 +10,6 @@ #include "source/extensions/filters/http/grpc_http1_reverse_bridge/filter.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/runtime/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/header_mutation/BUILD b/test/extensions/filters/http/header_mutation/BUILD index efb65286af738..ecee60a74e08c 100644 --- a/test/extensions/filters/http/header_mutation/BUILD +++ b/test/extensions/filters/http/header_mutation/BUILD @@ -21,7 +21,6 @@ envoy_extension_cc_test( deps = [ "//source/common/formatter:formatter_extension_lib", "//source/extensions/filters/http/header_mutation:config", - "//test/mocks/api:api_mocks", "//test/mocks/http:http_mocks", "//test/mocks/server:factory_context_mocks", "//test/test_common:registry_lib", diff --git a/test/extensions/filters/http/header_to_metadata/header_to_metadata_filter_test.cc b/test/extensions/filters/http/header_to_metadata/header_to_metadata_filter_test.cc index 6dd58a2358d84..42d026810a69e 100644 --- a/test/extensions/filters/http/header_to_metadata/header_to_metadata_filter_test.cc +++ b/test/extensions/filters/http/header_to_metadata/header_to_metadata_filter_test.cc @@ -9,7 +9,6 @@ #include "source/extensions/filters/http/well_known_names.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc index 480ef91a0fb03..2c0be8ee2ba06 100644 --- a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc @@ -7,7 +7,6 @@ #include "test/extensions/filters/http/jwt_authn/test_common.h" #include "test/integration/http_protocol_integration.h" -#include "test/test_common/registry.h" using envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication; using envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig; diff --git a/test/extensions/filters/http/lua/wrappers_test.cc b/test/extensions/filters/http/lua/wrappers_test.cc index 9918989aa307c..888738d73233a 100644 --- a/test/extensions/filters/http/lua/wrappers_test.cc +++ b/test/extensions/filters/http/lua/wrappers_test.cc @@ -11,7 +11,6 @@ #include "test/extensions/filters/common/lua/lua_wrappers.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/mcp_router/BUILD b/test/extensions/filters/http/mcp_router/BUILD index 27cb72ae75a13..b21eede7acd09 100644 --- a/test/extensions/filters/http/mcp_router/BUILD +++ b/test/extensions/filters/http/mcp_router/BUILD @@ -31,7 +31,6 @@ envoy_extension_cc_test( "//source/common/http:message_lib", "//source/extensions/filters/http/mcp_router:mcp_router_lib", "//test/common/stats:stat_test_utility_lib", - "//test/mocks/event:event_mocks", "//test/mocks/http:http_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", diff --git a/test/extensions/filters/http/oauth2/BUILD b/test/extensions/filters/http/oauth2/BUILD index 8d7bce78a67bc..270257b989911 100644 --- a/test/extensions/filters/http/oauth2/BUILD +++ b/test/extensions/filters/http/oauth2/BUILD @@ -18,6 +18,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/filters/http/oauth2:config", + "//test/mocks/secret:secret_mocks", "//test/mocks/server:factory_context_mocks", "@envoy_api//envoy/extensions/filters/http/oauth2/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/oauth2/config_test.cc b/test/extensions/filters/http/oauth2/config_test.cc index fec68435b0424..59e3ab19cfed8 100644 --- a/test/extensions/filters/http/oauth2/config_test.cc +++ b/test/extensions/filters/http/oauth2/config_test.cc @@ -5,6 +5,7 @@ #include "source/common/secret/secret_provider_impl.h" #include "source/extensions/filters/http/oauth2/config.h" +#include "test/mocks/secret/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/test_common/logging.h" diff --git a/test/extensions/filters/http/on_demand/BUILD b/test/extensions/filters/http/on_demand/BUILD index af2064991de7e..64902464f68ce 100644 --- a/test/extensions/filters/http/on_demand/BUILD +++ b/test/extensions/filters/http/on_demand/BUILD @@ -32,7 +32,6 @@ envoy_extension_cc_test( "//source/extensions/filters/http/on_demand:on_demand_update_lib", "//test/mocks/http:http_mocks", "//test/mocks/router:router_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/filters/http/on_demand/on_demand_filter_test.cc b/test/extensions/filters/http/on_demand/on_demand_filter_test.cc index b75a92aea8782..0d274774cebe8 100644 --- a/test/extensions/filters/http/on_demand/on_demand_filter_test.cc +++ b/test/extensions/filters/http/on_demand/on_demand_filter_test.cc @@ -5,7 +5,6 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/proto_api_scrubber/BUILD b/test/extensions/filters/http/proto_api_scrubber/BUILD index f7077f29bd199..8c3c39986c302 100644 --- a/test/extensions/filters/http/proto_api_scrubber/BUILD +++ b/test/extensions/filters/http/proto_api_scrubber/BUILD @@ -47,7 +47,6 @@ envoy_cc_test( "//source/extensions/matching/input_matchers/cel_matcher:config", "//test/common/matcher:test_utility_lib", "//test/mocks/http:http_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/proto:apikeys_proto_cc_proto", "//test/test_common:environment_lib", @@ -116,7 +115,6 @@ envoy_extension_cc_test( "//test/extensions/filters/http/grpc_field_extraction/message_converter:message_converter_test_lib", "//test/integration:http_protocol_integration_lib", "//test/proto:apikeys_proto_cc_proto", - "//test/test_common:registry_lib", "@abseil-cpp//absl/strings:str_format", "@cel-cpp//parser", "@envoy_api//envoy/extensions/filters/http/proto_api_scrubber/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/proto_api_scrubber/filter_test.cc b/test/extensions/filters/http/proto_api_scrubber/filter_test.cc index 79683b7132d16..2a9c249fc5393 100644 --- a/test/extensions/filters/http/proto_api_scrubber/filter_test.cc +++ b/test/extensions/filters/http/proto_api_scrubber/filter_test.cc @@ -17,9 +17,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/matcher/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/stream_info/mocks.h" -#include "test/mocks/tracing/mocks.h" #include "test/proto/apikeys.pb.h" #include "test/proto/bookstore.pb.h" #include "test/test_common/environment.h" diff --git a/test/extensions/filters/http/proto_api_scrubber/integration_test.cc b/test/extensions/filters/http/proto_api_scrubber/integration_test.cc index 3f719350a5d4c..0ce6516c01f95 100644 --- a/test/extensions/filters/http/proto_api_scrubber/integration_test.cc +++ b/test/extensions/filters/http/proto_api_scrubber/integration_test.cc @@ -12,7 +12,6 @@ #include "test/extensions/filters/http/proto_api_scrubber/scrubber_test.pb.h" #include "test/integration/http_protocol_integration.h" #include "test/proto/apikeys.pb.h" -#include "test/test_common/registry.h" #include "cel/expr/syntax.pb.h" #include "fmt/format.h" diff --git a/test/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/BUILD b/test/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/BUILD index a5c2bbc80f0f9..c3320cdbc1331 100644 --- a/test/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/BUILD +++ b/test/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/BUILD @@ -23,7 +23,6 @@ envoy_cc_test( "//source/extensions/matching/http/cel_input:cel_input_lib", "//source/extensions/matching/input_matchers/cel_matcher:config", "//test/mocks/http:http_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/proto:apikeys_proto_cc_proto", "//test/test_common:environment_lib", diff --git a/test/extensions/filters/http/rate_limit_quota/client_test.cc b/test/extensions/filters/http/rate_limit_quota/client_test.cc index 0d3d7031c496e..36ea8bdad7dfb 100644 --- a/test/extensions/filters/http/rate_limit_quota/client_test.cc +++ b/test/extensions/filters/http/rate_limit_quota/client_test.cc @@ -21,7 +21,6 @@ #include "source/extensions/filters/http/rate_limit_quota/quota_bucket_cache.h" #include "test/extensions/filters/http/rate_limit_quota/client_test_utils.h" -#include "test/mocks/grpc/mocks.h" #include "test/mocks/upstream/cluster_info.h" #include "test/test_common/logging.h" diff --git a/test/extensions/filters/http/ratelimit/BUILD b/test/extensions/filters/http/ratelimit/BUILD index 644a05f4fbd9b..da4e6b9414767 100644 --- a/test/extensions/filters/http/ratelimit/BUILD +++ b/test/extensions/filters/http/ratelimit/BUILD @@ -27,11 +27,8 @@ envoy_extension_cc_test( "//test/extensions/filters/common/ratelimit:ratelimit_mocks", "//test/extensions/filters/common/ratelimit:ratelimit_utils", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/ratelimit:ratelimit_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/tracing:tracing_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/ratelimit/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/ratelimit/ratelimit_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_test.cc index 0f2d08c423e42..5f6de2adc7cc1 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_test.cc @@ -16,11 +16,8 @@ #include "test/extensions/filters/common/ratelimit/mocks.h" #include "test/extensions/filters/common/ratelimit/utils.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/ratelimit/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/tracing/mocks.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/rbac/BUILD b/test/extensions/filters/http/rbac/BUILD index c3ec4deba3850..86c2a2eba3f1d 100644 --- a/test/extensions/filters/http/rbac/BUILD +++ b/test/extensions/filters/http/rbac/BUILD @@ -138,7 +138,6 @@ envoy_cc_fuzz_test( "//source/extensions/filters/http/rbac:rbac_filter_lib", "//test/extensions/filters/http/common/fuzz:http_filter_fuzzer_lib", "//test/mocks/http:http_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_factory_context_mocks", "@envoy_api//envoy/extensions/filters/http/rbac/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/rbac/rbac_filter_fuzz_test.cc b/test/extensions/filters/http/rbac/rbac_filter_fuzz_test.cc index ac67db10774a8..b61aa3a4e9283 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_fuzz_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_fuzz_test.cc @@ -8,7 +8,6 @@ #include "test/extensions/filters/http/rbac/rbac_filter_fuzz.pb.validate.h" #include "test/fuzz/fuzz_runner.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/set_metadata/BUILD b/test/extensions/filters/http/set_metadata/BUILD index 082967d48daed..d5743f4291b03 100644 --- a/test/extensions/filters/http/set_metadata/BUILD +++ b/test/extensions/filters/http/set_metadata/BUILD @@ -21,7 +21,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/set_metadata:config", "//test/integration:http_integration_lib", - "//test/mocks/api:api_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/http/stateful_session/BUILD b/test/extensions/filters/http/stateful_session/BUILD index 4e39ce20ded3d..10be4144b8b89 100644 --- a/test/extensions/filters/http/stateful_session/BUILD +++ b/test/extensions/filters/http/stateful_session/BUILD @@ -21,7 +21,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/stateful_session:config", "//source/server:generic_factory_context_lib", - "//test/mocks/api:api_mocks", "//test/mocks/http:http_mocks", "//test/mocks/http:stateful_session_mock", "//test/mocks/server:factory_context_mocks", diff --git a/test/extensions/filters/http/transform/BUILD b/test/extensions/filters/http/transform/BUILD index 7e38aec87d7de..70aee538c81cb 100644 --- a/test/extensions/filters/http/transform/BUILD +++ b/test/extensions/filters/http/transform/BUILD @@ -21,7 +21,6 @@ envoy_extension_cc_test( deps = [ "//source/common/formatter:formatter_extension_lib", "//source/extensions/filters/http/transform:config", - "//test/mocks/api:api_mocks", "//test/mocks/http:http_mocks", "//test/mocks/server:factory_context_mocks", "//test/test_common:registry_lib", diff --git a/test/extensions/filters/listener/http_inspector/BUILD b/test/extensions/filters/listener/http_inspector/BUILD index 7c1c5ffb9f50f..c9afde3e766ee 100644 --- a/test/extensions/filters/listener/http_inspector/BUILD +++ b/test/extensions/filters/listener/http_inspector/BUILD @@ -29,7 +29,6 @@ envoy_extension_cc_test( "//source/extensions/filters/listener/http_inspector:http_inspector_lib", "//test/mocks/api:api_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/stats:stats_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:threadsafe_singleton_injector_lib", ], @@ -46,7 +45,6 @@ envoy_extension_cc_test( "//test/mocks/api:api_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:listener_factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/test_common:threadsafe_singleton_injector_lib", ], ) @@ -59,7 +57,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/listener/http_inspector:http_inspector_lib", "//test/mocks/api:api_mocks", - "//test/mocks/stats:stats_mocks", "//test/test_common:threadsafe_singleton_injector_lib", ], ) diff --git a/test/extensions/filters/listener/http_inspector/http_inspector_test.cc b/test/extensions/filters/listener/http_inspector/http_inspector_test.cc index a8cf466c6ab06..1922e669a5351 100644 --- a/test/extensions/filters/listener/http_inspector/http_inspector_test.cc +++ b/test/extensions/filters/listener/http_inspector/http_inspector_test.cc @@ -1,5 +1,4 @@ #include "source/common/common/hex.h" -#include "source/common/common/logger.h" #include "source/common/http/utility.h" #include "source/common/network/io_socket_handle_impl.h" #include "source/common/network/listener_filter_buffer_impl.h" @@ -7,7 +6,6 @@ #include "test/mocks/api/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/test_runtime.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/listener/local_ratelimit/BUILD b/test/extensions/filters/listener/local_ratelimit/BUILD index 0250be60eee30..b32c2d277d5e3 100644 --- a/test/extensions/filters/listener/local_ratelimit/BUILD +++ b/test/extensions/filters/listener/local_ratelimit/BUILD @@ -23,7 +23,6 @@ envoy_extension_cc_test( "//source/extensions/filters/listener/local_ratelimit:config", "//source/extensions/filters/listener/local_ratelimit:local_ratelimit_lib", "//test/mocks/network:network_mocks", - "//test/mocks/stats:stats_mocks", ], ) diff --git a/test/extensions/filters/listener/local_ratelimit/local_ratelimit_test.cc b/test/extensions/filters/listener/local_ratelimit/local_ratelimit_test.cc index 5f3b3de683848..c11d9b1724fba 100644 --- a/test/extensions/filters/listener/local_ratelimit/local_ratelimit_test.cc +++ b/test/extensions/filters/listener/local_ratelimit/local_ratelimit_test.cc @@ -6,7 +6,6 @@ #include "source/extensions/filters/listener/local_ratelimit/local_ratelimit.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/stats/mocks.h" #include "absl/strings/str_format.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/listener/tls_inspector/BUILD b/test/extensions/filters/listener/tls_inspector/BUILD index 034cad3edaf1d..582f6590db8f6 100644 --- a/test/extensions/filters/listener/tls_inspector/BUILD +++ b/test/extensions/filters/listener/tls_inspector/BUILD @@ -42,7 +42,6 @@ envoy_cc_test( "//test/common/stats:stat_test_utility_lib", "//test/mocks/api:api_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/stats:stats_mocks", "//test/test_common:threadsafe_singleton_injector_lib", ], ) @@ -62,7 +61,6 @@ envoy_cc_test( "//test/common/stats:stat_test_utility_lib", "//test/mocks/api:api_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/stats:stats_mocks", "//test/test_common:threadsafe_singleton_injector_lib", ], ) @@ -147,7 +145,6 @@ envoy_cc_test( "//source/extensions/filters/network/echo:config", "//source/extensions/transport_sockets/tls:config", "//test/integration:integration_lib", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/secret:secret_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", diff --git a/test/extensions/filters/listener/tls_inspector/tls_inspector_ja4_test.cc b/test/extensions/filters/listener/tls_inspector/tls_inspector_ja4_test.cc index 40bee491fd58a..4b5f8143983e5 100644 --- a/test/extensions/filters/listener/tls_inspector/tls_inspector_ja4_test.cc +++ b/test/extensions/filters/listener/tls_inspector/tls_inspector_ja4_test.cc @@ -7,7 +7,6 @@ #include "test/common/stats/stat_test_utility.h" #include "test/mocks/api/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc b/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc index 1e10e3e533d62..b7009a79adf1a 100644 --- a/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc +++ b/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc @@ -9,7 +9,6 @@ #include "test/extensions/filters/listener/tls_inspector/tls_utility.h" #include "test/mocks/api/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "absl/strings/str_format.h" diff --git a/test/extensions/filters/network/common/redis/BUILD b/test/extensions/filters/network/common/redis/BUILD index 6c22955d84b5a..38489ae8f13ee 100644 --- a/test/extensions/filters/network/common/redis/BUILD +++ b/test/extensions/filters/network/common/redis/BUILD @@ -57,7 +57,6 @@ envoy_cc_test( "//source/common/upstream:upstream_lib", "//source/extensions/filters/network/common/redis:client_lib", "//test/mocks/network:network_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:host_mocks", "//test/test_common:simulated_time_system_lib", "@envoy_api//envoy/extensions/filters/network/redis_proxy/v3:pkg_cc_proto", diff --git a/test/extensions/filters/network/direct_response/BUILD b/test/extensions/filters/network/direct_response/BUILD index e8631fb750686..f8e8586c47204 100644 --- a/test/extensions/filters/network/direct_response/BUILD +++ b/test/extensions/filters/network/direct_response/BUILD @@ -33,7 +33,6 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/filters/network/direct_response:filter", - "//test/mocks/api:api_mocks", "//test/mocks/network:network_mocks", "@envoy_api//envoy/extensions/filters/network/direct_response/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/network/direct_response/direct_response_test.cc b/test/extensions/filters/network/direct_response/direct_response_test.cc index 061e39d25920c..30a51e8aa74ae 100644 --- a/test/extensions/filters/network/direct_response/direct_response_test.cc +++ b/test/extensions/filters/network/direct_response/direct_response_test.cc @@ -2,7 +2,6 @@ #include "source/extensions/filters/network/direct_response/filter.h" -#include "test/mocks/api/mocks.h" #include "test/mocks/network/mocks.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/network/ext_authz/BUILD b/test/extensions/filters/network/ext_authz/BUILD index a6f24915aad1b..7edcca3e2ad92 100644 --- a/test/extensions/filters/network/ext_authz/BUILD +++ b/test/extensions/filters/network/ext_authz/BUILD @@ -29,9 +29,7 @@ envoy_extension_cc_test( "//source/extensions/filters/network/ext_authz", "//test/extensions/filters/common/ext_authz:ext_authz_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_factory_context_mocks", - "//test/mocks/tracing:tracing_mocks", "//test/proto:helloworld_proto_cc_proto", "@envoy_api//envoy/extensions/filters/network/ext_authz/v3:pkg_cc_proto", ], @@ -69,7 +67,6 @@ envoy_cc_fuzz_test( "//source/extensions/filters/network/ext_authz", "//test/extensions/filters/common/ext_authz:ext_authz_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_factory_context_mocks", "@envoy_api//envoy/extensions/filters/network/ext_authz/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/network/ext_authz/ext_authz_fuzz_test.cc b/test/extensions/filters/network/ext_authz/ext_authz_fuzz_test.cc index a4e52e4e3376f..994a006baf908 100644 --- a/test/extensions/filters/network/ext_authz/ext_authz_fuzz_test.cc +++ b/test/extensions/filters/network/ext_authz/ext_authz_fuzz_test.cc @@ -8,7 +8,6 @@ #include "test/extensions/filters/network/ext_authz/ext_authz_fuzz.pb.validate.h" #include "test/fuzz/fuzz_runner.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/network/ext_authz/ext_authz_test.cc b/test/extensions/filters/network/ext_authz/ext_authz_test.cc index 37c1e0e65d2b1..b13f6543babfb 100644 --- a/test/extensions/filters/network/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/network/ext_authz/ext_authz_test.cc @@ -16,9 +16,7 @@ #include "test/extensions/filters/common/ext_authz/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/server_factory_context.h" -#include "test/mocks/tracing/mocks.h" #include "test/proto/helloworld.pb.h" #include "test/test_common/printers.h" diff --git a/test/extensions/filters/network/generic_proxy/BUILD b/test/extensions/filters/network/generic_proxy/BUILD index 1d1a360ea3671..d317667047610 100644 --- a/test/extensions/filters/network/generic_proxy/BUILD +++ b/test/extensions/filters/network/generic_proxy/BUILD @@ -59,7 +59,6 @@ envoy_cc_test( "//test/extensions/filters/network/generic_proxy/mocks:filter_mocks", "//test/extensions/filters/network/generic_proxy/mocks:route_mocks", "//test/mocks/server:factory_context_mocks", - "//test/test_common:registry_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/network/generic_proxy/proxy_test.cc b/test/extensions/filters/network/generic_proxy/proxy_test.cc index c2e6726ec2d26..5b1be9a26307a 100644 --- a/test/extensions/filters/network/generic_proxy/proxy_test.cc +++ b/test/extensions/filters/network/generic_proxy/proxy_test.cc @@ -13,7 +13,6 @@ #include "test/extensions/filters/network/generic_proxy/mocks/filter.h" #include "test/extensions/filters/network/generic_proxy/mocks/route.h" #include "test/mocks/server/factory_context.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/network/generic_proxy/router/BUILD b/test/extensions/filters/network/generic_proxy/router/BUILD index 39609e95a0108..ec391e52e7f3e 100644 --- a/test/extensions/filters/network/generic_proxy/router/BUILD +++ b/test/extensions/filters/network/generic_proxy/router/BUILD @@ -22,7 +22,6 @@ envoy_cc_test( "//test/extensions/filters/network/generic_proxy/mocks:filter_mocks", "//test/extensions/filters/network/generic_proxy/mocks:route_mocks", "//test/mocks/server:factory_context_mocks", - "//test/test_common:registry_lib", "//test/test_common:utility_lib", ], ) @@ -41,7 +40,6 @@ envoy_cc_test( "//test/extensions/filters/network/generic_proxy/mocks:filter_mocks", "//test/extensions/filters/network/generic_proxy/mocks:route_mocks", "//test/mocks/server:factory_context_mocks", - "//test/test_common:registry_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/network/generic_proxy/router/router_test.cc b/test/extensions/filters/network/generic_proxy/router/router_test.cc index b06084849b6f8..53e482433062c 100644 --- a/test/extensions/filters/network/generic_proxy/router/router_test.cc +++ b/test/extensions/filters/network/generic_proxy/router/router_test.cc @@ -8,7 +8,6 @@ #include "test/extensions/filters/network/generic_proxy/mocks/filter.h" #include "test/extensions/filters/network/generic_proxy/mocks/route.h" #include "test/mocks/server/factory_context.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/network/generic_proxy/router/upstream_test.cc b/test/extensions/filters/network/generic_proxy/router/upstream_test.cc index 889312d0adcee..a7b683eb8f0ae 100644 --- a/test/extensions/filters/network/generic_proxy/router/upstream_test.cc +++ b/test/extensions/filters/network/generic_proxy/router/upstream_test.cc @@ -8,7 +8,6 @@ #include "test/extensions/filters/network/generic_proxy/mocks/filter.h" #include "test/extensions/filters/network/generic_proxy/mocks/route.h" #include "test/mocks/server/factory_context.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/network/geoip/BUILD b/test/extensions/filters/network/geoip/BUILD index 0b70aecdc362d..10755f469668e 100644 --- a/test/extensions/filters/network/geoip/BUILD +++ b/test/extensions/filters/network/geoip/BUILD @@ -20,7 +20,6 @@ envoy_cc_test( "//source/common/stream_info:filter_state_lib", "//source/extensions/filters/network/geoip:geoip_filter_lib", "//test/extensions/filters/http/geoip:geoip_mocks", - "//test/mocks/event:event_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/stats:stats_mocks", diff --git a/test/extensions/filters/network/geoip/geoip_filter_test.cc b/test/extensions/filters/network/geoip/geoip_filter_test.cc index c70c0b2926faa..f710b814fd635 100644 --- a/test/extensions/filters/network/geoip/geoip_filter_test.cc +++ b/test/extensions/filters/network/geoip/geoip_filter_test.cc @@ -7,7 +7,6 @@ #include "source/extensions/filters/network/geoip/geoip_filter.h" #include "test/extensions/filters/http/geoip/mocks.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/stats/mocks.h" diff --git a/test/extensions/filters/network/http_connection_manager/BUILD b/test/extensions/filters/network/http_connection_manager/BUILD index 78a1fa841b20e..abfc3e37584a9 100644 --- a/test/extensions/filters/network/http_connection_manager/BUILD +++ b/test/extensions/filters/network/http_connection_manager/BUILD @@ -36,7 +36,6 @@ envoy_extension_cc_test_library( "//test/integration/filters:encoder_decoder_buffer_filter_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", - "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", ], ) @@ -61,6 +60,7 @@ envoy_extension_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", + "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", @@ -93,6 +93,7 @@ envoy_extension_cc_test( ":config_test_base", "//source/common/formatter:formatter_extension_lib", "//source/extensions/filters/network/http_connection_manager:config", + "//test/test_common:registry_lib", "@envoy_api//envoy/extensions/filters/common/dependency/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc b/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc index 4d8730ef91974..5af63a048f207 100644 --- a/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc @@ -7,6 +7,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" +#include "test/test_common/registry.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/network/http_connection_manager/config_test_base.h b/test/extensions/filters/network/http_connection_manager/config_test_base.h index 12ee459af20d6..57f81bb430f08 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test_base.h +++ b/test/extensions/filters/network/http_connection_manager/config_test_base.h @@ -13,7 +13,6 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/network/ratelimit/BUILD b/test/extensions/filters/network/ratelimit/BUILD index 55257ac29fb59..1fceaa756d117 100644 --- a/test/extensions/filters/network/ratelimit/BUILD +++ b/test/extensions/filters/network/ratelimit/BUILD @@ -33,7 +33,6 @@ envoy_extension_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", - "//test/mocks/tracing:tracing_mocks", "@envoy_api//envoy/extensions/filters/network/ratelimit/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/network/ratelimit/ratelimit_test.cc b/test/extensions/filters/network/ratelimit/ratelimit_test.cc index f183d6eeedff7..cdda71d535ae7 100644 --- a/test/extensions/filters/network/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/network/ratelimit/ratelimit_test.cc @@ -19,7 +19,6 @@ #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/stream_info/mocks.h" -#include "test/mocks/tracing/mocks.h" #include "test/test_common/printers.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/network/redis_proxy/BUILD b/test/extensions/filters/network/redis_proxy/BUILD index 5f173a7364106..ac828d25d097a 100644 --- a/test/extensions/filters/network/redis_proxy/BUILD +++ b/test/extensions/filters/network/redis_proxy/BUILD @@ -228,7 +228,6 @@ envoy_extension_cc_benchmark_binary( "//source/extensions/filters/network/redis_proxy:command_splitter_lib", "//source/extensions/filters/network/redis_proxy:router_lib", "//test/test_common:printers_lib", - "//test/test_common:simulated_time_system_lib", "@benchmark", ], ) diff --git a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc index 084138716546f..f6caa6c349848 100644 --- a/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_split_speed_test.cc @@ -12,8 +12,6 @@ #include "source/extensions/filters/network/redis_proxy/command_splitter_impl.h" #include "source/extensions/filters/network/redis_proxy/router_impl.h" -#include "test/test_common/simulated_time_system.h" - #include "absl/types/variant.h" #include "benchmark/benchmark.h" diff --git a/test/extensions/filters/network/thrift_proxy/BUILD b/test/extensions/filters/network/thrift_proxy/BUILD index 109247e59e873..26a1123436265 100644 --- a/test/extensions/filters/network/thrift_proxy/BUILD +++ b/test/extensions/filters/network/thrift_proxy/BUILD @@ -413,7 +413,6 @@ envoy_extension_cc_test( "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:host_mocks", "//test/test_common:printers_lib", - "//test/test_common:registry_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/thrift_proxy/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/network/thrift_proxy/filters/ratelimit/ratelimit_test.cc b/test/extensions/filters/network/thrift_proxy/filters/ratelimit/ratelimit_test.cc index 5cb3a7fc22059..77f0ff7b14bde 100644 --- a/test/extensions/filters/network/thrift_proxy/filters/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/network/thrift_proxy/filters/ratelimit/ratelimit_test.cc @@ -15,7 +15,6 @@ #include "test/mocks/local_info/mocks.h" #include "test/mocks/ratelimit/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/network/thrift_proxy/shadow_writer_test.cc b/test/extensions/filters/network/thrift_proxy/shadow_writer_test.cc index 5399b5c138b1f..7fa468ea5b076 100644 --- a/test/extensions/filters/network/thrift_proxy/shadow_writer_test.cc +++ b/test/extensions/filters/network/thrift_proxy/shadow_writer_test.cc @@ -14,7 +14,6 @@ #include "test/mocks/server/factory_context.h" #include "test/mocks/upstream/host.h" #include "test/test_common/printers.h" -#include "test/test_common/registry.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc index 7e3e87b933652..0977d04619dc1 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc @@ -1,7 +1,6 @@ #include "envoy/extensions/filters/udp/dns_filter/v3/dns_filter.pb.h" #include "envoy/extensions/filters/udp/dns_filter/v3/dns_filter.pb.validate.h" -#include "source/common/common/logger.h" #include "source/common/stream_info/stream_info_impl.h" #include "source/extensions/filters/udp/dns_filter/dns_filter.h" #include "source/extensions/filters/udp/dns_filter/dns_filter_access_log.h" diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_fuzz_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_fuzz_test.cc index 43ef4d77b7b9c..78d1ea7bc2568 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_fuzz_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_fuzz_test.cc @@ -1,10 +1,8 @@ -#include "source/common/common/logger.h" #include "source/extensions/filters/udp/dns_filter/dns_filter.h" #include "test/fuzz/fuzz_runner.h" #include "test/fuzz/utility.h" #include "test/mocks/common.h" -#include "test/mocks/event/mocks.h" #include "test/test_common/environment.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc index a707e7f9fcc79..4d2573c8fd85a 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc @@ -1,7 +1,6 @@ #include "envoy/extensions/filters/udp/dns_filter/v3/dns_filter.pb.h" #include "envoy/extensions/filters/udp/dns_filter/v3/dns_filter.pb.validate.h" -#include "source/common/common/logger.h" #include "source/extensions/filters/udp/dns_filter/dns_filter_constants.h" #include "source/extensions/filters/udp/dns_filter/dns_filter_utils.h" #include "source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.h" diff --git a/test/extensions/filters/udp/udp_proxy/BUILD b/test/extensions/filters/udp/udp_proxy/BUILD index 4bd5473c54069..5390915796c26 100644 --- a/test/extensions/filters/udp/udp_proxy/BUILD +++ b/test/extensions/filters/udp/udp_proxy/BUILD @@ -126,7 +126,6 @@ envoy_extension_cc_test( "//test/extensions/filters/udp/udp_proxy/session_filters:drainer_filter_config_lib", "//test/extensions/filters/udp/udp_proxy/session_filters:drainer_filter_proto_cc_proto", "//test/integration:integration_lib", - "//test/test_common:registry_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/udp/udp_proxy/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD index c592e2a428f62..180f567e47fbf 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD @@ -65,7 +65,6 @@ envoy_extension_cc_test( "//test/extensions/filters/udp/udp_proxy/session_filters:psc_setter_filter_config_lib", "//test/extensions/filters/udp/udp_proxy/session_filters:psc_setter_filter_proto_cc_proto", "//test/integration:integration_lib", - "//test/test_common:registry_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc index f56f2771e825c..7c486ce20a639 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -15,7 +15,6 @@ #include "test/extensions/filters/udp/udp_proxy/session_filters/psc_setter.pb.h" #include "test/integration/integration.h" #include "test/test_common/network_utility.h" -#include "test/test_common/registry.h" namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/BUILD b/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/BUILD index 1b948bd26efa0..c77f2118f20bc 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/BUILD +++ b/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/BUILD @@ -36,7 +36,6 @@ envoy_extension_cc_test( "//source/extensions/filters/udp/udp_proxy/session_filters/http_capsule:config", "//source/extensions/filters/udp/udp_proxy/session_filters/http_capsule:http_capsule_filter_lib", "//test/integration:integration_lib", - "//test/test_common:registry_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule_integration_test.cc b/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule_integration_test.cc index ddb2695220f07..ac85002260bb0 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule_integration_test.cc +++ b/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule_integration_test.cc @@ -7,7 +7,6 @@ #include "test/integration/integration.h" #include "test/test_common/network_utility.h" -#include "test/test_common/registry.h" namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/udp/udp_proxy/udp_session_extension_discovery_integration_test.cc b/test/extensions/filters/udp/udp_proxy/udp_session_extension_discovery_integration_test.cc index 15e39ca37d720..4c137d944f4b3 100644 --- a/test/extensions/filters/udp/udp_proxy/udp_session_extension_discovery_integration_test.cc +++ b/test/extensions/filters/udp/udp_proxy/udp_session_extension_discovery_integration_test.cc @@ -10,7 +10,6 @@ #include "test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.pb.h" #include "test/integration/integration.h" #include "test/test_common/network_utility.h" -#include "test/test_common/registry.h" namespace Envoy { namespace { diff --git a/test/extensions/formatter/metadata/integration_test.cc b/test/extensions/formatter/metadata/integration_test.cc index d8bd89860cd4e..7ffeb466b69f0 100644 --- a/test/extensions/formatter/metadata/integration_test.cc +++ b/test/extensions/formatter/metadata/integration_test.cc @@ -3,7 +3,6 @@ #include "source/common/protobuf/protobuf.h" #include "test/integration/http_integration.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/formatter/xfcc_value/xfcc_value_fuzz_test.cc b/test/extensions/formatter/xfcc_value/xfcc_value_fuzz_test.cc index ea4b3c0a50fb3..0e53bec0d5167 100644 --- a/test/extensions/formatter/xfcc_value/xfcc_value_fuzz_test.cc +++ b/test/extensions/formatter/xfcc_value/xfcc_value_fuzz_test.cc @@ -1,4 +1,3 @@ -#include "source/common/common/logger.h" #include "source/common/formatter/http_formatter_context.h" #include "source/extensions/formatter/xfcc_value/xfcc_value.h" diff --git a/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc b/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc index b40aed5a511a1..cba0af7b71d8f 100644 --- a/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc +++ b/test/extensions/geoip_providers/maxmind/geoip_provider_test.cc @@ -7,7 +7,6 @@ #include "source/extensions/geoip_providers/maxmind/geoip_provider.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/health_check/event_sinks/file/file_sink_impl_test.cc b/test/extensions/health_check/event_sinks/file/file_sink_impl_test.cc index 11515145aa250..3b8a75f956309 100644 --- a/test/extensions/health_check/event_sinks/file/file_sink_impl_test.cc +++ b/test/extensions/health_check/event_sinks/file/file_sink_impl_test.cc @@ -6,9 +6,7 @@ #include "source/extensions/health_check/event_sinks/file/file_sink_impl.h" #include "test/mocks/access_log/mocks.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/server/health_checker_factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/health_checkers/thrift/BUILD b/test/extensions/health_checkers/thrift/BUILD index 1a94be7530080..f227a9ee6a3aa 100644 --- a/test/extensions/health_checkers/thrift/BUILD +++ b/test/extensions/health_checkers/thrift/BUILD @@ -40,7 +40,6 @@ envoy_extension_cc_test( "//source/extensions/health_checkers/thrift", "//source/extensions/health_checkers/thrift:client_lib", "//source/extensions/health_checkers/thrift:utility", - "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:simulated_time_system_lib", ], ) diff --git a/test/extensions/http/cache/simple_http_cache/BUILD b/test/extensions/http/cache/simple_http_cache/BUILD index 58340ba9815f8..fde636031170b 100644 --- a/test/extensions/http/cache/simple_http_cache/BUILD +++ b/test/extensions/http/cache/simple_http_cache/BUILD @@ -18,7 +18,6 @@ envoy_extension_cc_test( "//source/extensions/http/cache/simple_http_cache:config", "//test/extensions/filters/http/cache:http_cache_implementation_test_common_lib", "//test/mocks/server:factory_context_mocks", - "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/http/cache/simple_http_cache/simple_http_cache_test.cc b/test/extensions/http/cache/simple_http_cache/simple_http_cache_test.cc index 3bb376ac41ea5..cfeb3ed22faea 100644 --- a/test/extensions/http/cache/simple_http_cache/simple_http_cache_test.cc +++ b/test/extensions/http/cache/simple_http_cache/simple_http_cache_test.cc @@ -8,7 +8,6 @@ #include "test/extensions/filters/http/cache/http_cache_implementation_test_common.h" #include "test/mocks/server/factory_context.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/http/cache_v2/simple_http_cache/BUILD b/test/extensions/http/cache_v2/simple_http_cache/BUILD index ad7a737e42339..16603c0c51d77 100644 --- a/test/extensions/http/cache_v2/simple_http_cache/BUILD +++ b/test/extensions/http/cache_v2/simple_http_cache/BUILD @@ -18,7 +18,6 @@ envoy_extension_cc_test( "//source/extensions/http/cache_v2/simple_http_cache:config", "//test/extensions/filters/http/cache_v2:http_cache_implementation_test_common_lib", "//test/mocks/server:factory_context_mocks", - "//test/test_common:simulated_time_system_lib", "//test/test_common:status_utility_lib", "//test/test_common:utility_lib", ], diff --git a/test/extensions/http/cache_v2/simple_http_cache/simple_http_cache_test.cc b/test/extensions/http/cache_v2/simple_http_cache/simple_http_cache_test.cc index d05da2344b4a7..e2c819e0f0de7 100644 --- a/test/extensions/http/cache_v2/simple_http_cache/simple_http_cache_test.cc +++ b/test/extensions/http/cache_v2/simple_http_cache/simple_http_cache_test.cc @@ -9,7 +9,6 @@ #include "test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.h" #include "test/mocks/server/factory_context.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/status_utility.h" #include "test/test_common/utility.h" diff --git a/test/extensions/http/early_header_mutation/header_mutation/header_mutation_integration_test.cc b/test/extensions/http/early_header_mutation/header_mutation/header_mutation_integration_test.cc index a2714078db35c..b968dadaea9df 100644 --- a/test/extensions/http/early_header_mutation/header_mutation/header_mutation_integration_test.cc +++ b/test/extensions/http/early_header_mutation/header_mutation/header_mutation_integration_test.cc @@ -3,7 +3,6 @@ #include "test/integration/filters/common.h" #include "test/integration/http_integration.h" -#include "test/test_common/registry.h" namespace Envoy { namespace { diff --git a/test/extensions/http/header_formatters/preserve_case/BUILD b/test/extensions/http/header_formatters/preserve_case/BUILD index 93495a1a4c0a2..08cc69a5d311f 100644 --- a/test/extensions/http/header_formatters/preserve_case/BUILD +++ b/test/extensions/http/header_formatters/preserve_case/BUILD @@ -38,6 +38,7 @@ envoy_extension_cc_test( "//source/extensions/http/header_formatters/preserve_case:config", "//source/extensions/http/header_formatters/preserve_case:preserve_case_formatter", "//test/integration:http_integration_lib", + "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/http/header_formatters/preserve_case/v3:pkg_cc_proto", ], diff --git a/test/extensions/http/header_formatters/preserve_case/preserve_case_formatter_reason_phrase_integration_test.cc b/test/extensions/http/header_formatters/preserve_case/preserve_case_formatter_reason_phrase_integration_test.cc index 8e455ef51ffe1..28235fd4fedb7 100644 --- a/test/extensions/http/header_formatters/preserve_case/preserve_case_formatter_reason_phrase_integration_test.cc +++ b/test/extensions/http/header_formatters/preserve_case/preserve_case_formatter_reason_phrase_integration_test.cc @@ -2,7 +2,6 @@ #include "test/integration/filters/common.h" #include "test/integration/http_integration.h" -#include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/io_socket/user_space/BUILD b/test/extensions/io_socket/user_space/BUILD index 2f3fde2282840..1f57a960afc1c 100644 --- a/test/extensions/io_socket/user_space/BUILD +++ b/test/extensions/io_socket/user_space/BUILD @@ -55,7 +55,6 @@ envoy_extension_cc_test( "//source/common/network:connection_lib", "//source/common/network:listen_socket_lib", "//source/extensions/io_socket/user_space:io_handle_impl_lib", - "//test/mocks/api:api_mocks", "//test/mocks/event:event_mocks", "//test/mocks/network:network_mocks", "//test/test_common:network_utility_lib", diff --git a/test/extensions/io_socket/user_space/connection_compatbility_test.cc b/test/extensions/io_socket/user_space/connection_compatbility_test.cc index e3018d9c4dc94..561303b0476bd 100644 --- a/test/extensions/io_socket/user_space/connection_compatbility_test.cc +++ b/test/extensions/io_socket/user_space/connection_compatbility_test.cc @@ -8,8 +8,6 @@ #include "source/common/network/utility.h" #include "source/extensions/io_socket/user_space/io_handle_impl.h" -#include "test/mocks/api/mocks.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/network/mocks.h" #include "gmock/gmock.h" diff --git a/test/extensions/load_balancing_policies/override_host/BUILD b/test/extensions/load_balancing_policies/override_host/BUILD index 8d5c0759a2354..4a74179b5d5ca 100644 --- a/test/extensions/load_balancing_policies/override_host/BUILD +++ b/test/extensions/load_balancing_policies/override_host/BUILD @@ -26,7 +26,6 @@ envoy_extension_cc_test( "//source/extensions/load_balancing_policies/override_host:config", "//test/common/upstream:utility_lib", "//test/mocks:common_lib", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:host_mocks", diff --git a/test/extensions/load_balancing_policies/ring_hash/BUILD b/test/extensions/load_balancing_policies/ring_hash/BUILD index 6ceab5bf9e904..20a51cbfaf54a 100644 --- a/test/extensions/load_balancing_policies/ring_hash/BUILD +++ b/test/extensions/load_balancing_policies/ring_hash/BUILD @@ -26,7 +26,6 @@ envoy_extension_cc_test( "//source/extensions/load_balancing_policies/ring_hash:ring_hash_lb_lib", "//test/common/upstream:utility_lib", "//test/mocks:common_lib", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:host_mocks", diff --git a/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_test.cc b/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_test.cc index b453ddef41bc1..e4597dd028271 100644 --- a/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_test.cc +++ b/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_test.cc @@ -12,7 +12,6 @@ #include "test/common/upstream/utility.h" #include "test/mocks/common.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/host.h" diff --git a/test/extensions/load_balancing_policies/subset/BUILD b/test/extensions/load_balancing_policies/subset/BUILD index 5efc1fd25af22..5ad2b1d01fd73 100644 --- a/test/extensions/load_balancing_policies/subset/BUILD +++ b/test/extensions/load_balancing_policies/subset/BUILD @@ -64,8 +64,6 @@ envoy_extension_cc_test( "//source/extensions/load_balancing_policies/subset:config", "//test/common/upstream:utility_lib", "//test/mocks:common_lib", - "//test/mocks/access_log:access_log_mocks", - "//test/mocks/filesystem:filesystem_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", diff --git a/test/extensions/load_balancing_policies/subset/subset_test.cc b/test/extensions/load_balancing_policies/subset/subset_test.cc index 369bb1bcced98..46c0bee0cf4fa 100644 --- a/test/extensions/load_balancing_policies/subset/subset_test.cc +++ b/test/extensions/load_balancing_policies/subset/subset_test.cc @@ -17,9 +17,7 @@ #include "source/extensions/load_balancing_policies/subset/config.h" #include "test/common/upstream/utility.h" -#include "test/mocks/access_log/mocks.h" #include "test/mocks/common.h" -#include "test/mocks/filesystem/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/upstream/cluster_info.h" diff --git a/test/extensions/network/dns_resolver/hickory/BUILD b/test/extensions/network/dns_resolver/hickory/BUILD index 8b31145faac4b..4c9a9ff8d106e 100644 --- a/test/extensions/network/dns_resolver/hickory/BUILD +++ b/test/extensions/network/dns_resolver/hickory/BUILD @@ -47,7 +47,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/network/dns_resolver/hickory:config", "//test/common/stats:stat_test_utility_lib", - "//test/mocks/api:api_mocks", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/extensions/network/dns_resolver/hickory/v3:pkg_cc_proto", ] + HICKORY_ABI_IMPL_DEPS, diff --git a/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc b/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc index ea1a9218c7cef..0e89b8ae36ff2 100644 --- a/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc +++ b/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc @@ -4,7 +4,6 @@ #include "source/extensions/network/dns_resolver/hickory/hickory_dns_impl.h" #include "test/common/stats/stat_test_utility.h" -#include "test/mocks/api/mocks.h" #include "test/test_common/test_runtime.h" #include "gtest/gtest.h" diff --git a/test/extensions/quic/connection_id_generator/quic_lb/BUILD b/test/extensions/quic/connection_id_generator/quic_lb/BUILD index 44d7e2b2347d7..16d447b9479ba 100644 --- a/test/extensions/quic/connection_id_generator/quic_lb/BUILD +++ b/test/extensions/quic/connection_id_generator/quic_lb/BUILD @@ -19,6 +19,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = envoy_select_enable_http3([ "//source/extensions/quic/connection_id_generator/quic_lb:quic_lb_lib", + "//test/mocks/secret:secret_mocks", "//test/mocks/server:factory_context_mocks", "@quiche//:quic_test_tools_test_utils_lib", ]), diff --git a/test/extensions/quic/connection_id_generator/quic_lb/quic_lb_test.cc b/test/extensions/quic/connection_id_generator/quic_lb/quic_lb_test.cc index 9c7bdc896ca6e..a76dea7974d9c 100644 --- a/test/extensions/quic/connection_id_generator/quic_lb/quic_lb_test.cc +++ b/test/extensions/quic/connection_id_generator/quic_lb/quic_lb_test.cc @@ -1,6 +1,7 @@ #include "source/common/network/socket_option_factory.h" #include "source/extensions/quic/connection_id_generator/quic_lb/quic_lb.h" +#include "test/mocks/secret/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/test_common/network_utility.h" diff --git a/test/extensions/request_id/uuid/BUILD b/test/extensions/request_id/uuid/BUILD index a139870f1828a..68d24c504b608 100644 --- a/test/extensions/request_id/uuid/BUILD +++ b/test/extensions/request_id/uuid/BUILD @@ -16,7 +16,6 @@ envoy_cc_test( deps = [ "//source/common/common:random_generator_lib", "//source/extensions/request_id/uuid:config", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/stream_info:stream_info_mocks", ], ) diff --git a/test/extensions/stats_sinks/metrics_service/BUILD b/test/extensions/stats_sinks/metrics_service/BUILD index 97b6f763706fb..0ce6865fb2a9b 100644 --- a/test/extensions/stats_sinks/metrics_service/BUILD +++ b/test/extensions/stats_sinks/metrics_service/BUILD @@ -24,9 +24,7 @@ envoy_extension_cc_test( "//test/common/upstream:utility_lib", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", - "//test/test_common:simulated_time_system_lib", "@envoy_api//envoy/service/metrics/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc b/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc index d20204f83a897..03ae132e40fa1 100644 --- a/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc +++ b/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc @@ -7,8 +7,6 @@ #include "test/mocks/grpc/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/stats/mocks.h" -#include "test/mocks/thread_local/mocks.h" -#include "test/test_common/simulated_time_system.h" #include "absl/strings/str_format.h" #include "io/prometheus/client/metrics.pb.h" diff --git a/test/extensions/stats_sinks/open_telemetry/BUILD b/test/extensions/stats_sinks/open_telemetry/BUILD index 14dc36cb29d6c..1aef689750bba 100644 --- a/test/extensions/stats_sinks/open_telemetry/BUILD +++ b/test/extensions/stats_sinks/open_telemetry/BUILD @@ -36,7 +36,6 @@ envoy_extension_cc_test( "//source/extensions/stat_sinks/open_telemetry:open_telemetry_lib", "//test/mocks/grpc:grpc_mocks", "//test/mocks/server:instance_mocks", - "//test/test_common:simulated_time_system_lib", ], ) diff --git a/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc b/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc index 77c97dd621590..973be132ed899 100644 --- a/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc +++ b/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc @@ -8,7 +8,6 @@ #include "test/mocks/grpc/mocks.h" #include "test/mocks/server/instance.h" #include "test/mocks/stats/mocks.h" -#include "test/test_common/simulated_time_system.h" #include "gtest/gtest.h" diff --git a/test/extensions/tracers/datadog/BUILD b/test/extensions/tracers/datadog/BUILD index f9fb7d7862d65..831da3152c6d0 100644 --- a/test/extensions/tracers/datadog/BUILD +++ b/test/extensions/tracers/datadog/BUILD @@ -43,8 +43,6 @@ envoy_extension_cc_test( "//source/extensions/tracers/datadog:config", "//source/extensions/tracers/datadog:datadog_tracer_lib", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:tracer_factory_context_mocks", "//test/mocks/server:tracer_factory_mocks", "//test/mocks/stats:stats_mocks", diff --git a/test/extensions/tracers/datadog/config_test.cc b/test/extensions/tracers/datadog/config_test.cc index b0095d438997f..30ac7b6f1a1d3 100644 --- a/test/extensions/tracers/datadog/config_test.cc +++ b/test/extensions/tracers/datadog/config_test.cc @@ -18,11 +18,8 @@ #include "source/extensions/tracers/datadog/tracer.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/tracer_factory.h" #include "test/mocks/server/tracer_factory_context.h" -#include "test/mocks/stats/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" diff --git a/test/extensions/tracers/fluentd/BUILD b/test/extensions/tracers/fluentd/BUILD index ed039ca722e61..e1618ff5ccc92 100644 --- a/test/extensions/tracers/fluentd/BUILD +++ b/test/extensions/tracers/fluentd/BUILD @@ -45,13 +45,10 @@ envoy_extension_cc_test( "//source/extensions/tracers/fluentd:config", "//source/extensions/tracers/fluentd:fluentd_tracer_lib", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:tracer_factory_context_mocks", "//test/mocks/server:tracer_factory_mocks", "//test/mocks/stats:stats_mocks", "//test/mocks/stream_info:stream_info_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:thread_local_cluster_mocks", diff --git a/test/extensions/tracers/fluentd/tracer_integration_test.cc b/test/extensions/tracers/fluentd/tracer_integration_test.cc index b6425949889eb..d1c0c61360a05 100644 --- a/test/extensions/tracers/fluentd/tracer_integration_test.cc +++ b/test/extensions/tracers/fluentd/tracer_integration_test.cc @@ -4,7 +4,6 @@ #include "test/mocks/server/tracer_factory.h" #include "test/mocks/server/tracer_factory_context.h" #include "test/mocks/stream_info/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" diff --git a/test/extensions/tracers/opentelemetry/BUILD b/test/extensions/tracers/opentelemetry/BUILD index 224f82a76dfd0..c0d386f8285fc 100644 --- a/test/extensions/tracers/opentelemetry/BUILD +++ b/test/extensions/tracers/opentelemetry/BUILD @@ -35,12 +35,9 @@ envoy_extension_cc_test( "//source/extensions/tracers/opentelemetry:opentelemetry_tracer_lib", "//test/mocks:common_lib", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:tracer_factory_context_mocks", "//test/mocks/stats:stats_mocks", "//test/mocks/stream_info:stream_info_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:thread_local_cluster_mocks", @@ -117,7 +114,6 @@ envoy_extension_cc_test( "//source/extensions/tracers/opentelemetry:opentelemetry_tracer_lib", "//test/mocks/server:tracer_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:utility_lib", diff --git a/test/extensions/tracers/opentelemetry/http_trace_exporter_test.cc b/test/extensions/tracers/opentelemetry/http_trace_exporter_test.cc index 74bba1d870373..9ac962dadcf90 100644 --- a/test/extensions/tracers/opentelemetry/http_trace_exporter_test.cc +++ b/test/extensions/tracers/opentelemetry/http_trace_exporter_test.cc @@ -5,7 +5,6 @@ #include "source/extensions/tracers/opentelemetry/http_trace_exporter.h" #include "test/mocks/common.h" -#include "test/mocks/grpc/mocks.h" #include "test/mocks/server/tracer_factory_context.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/upstream/cluster_manager.h" diff --git a/test/extensions/tracers/opentelemetry/operation_name_test.cc b/test/extensions/tracers/opentelemetry/operation_name_test.cc index 0d96159e40534..ef931eb8de90d 100644 --- a/test/extensions/tracers/opentelemetry/operation_name_test.cc +++ b/test/extensions/tracers/opentelemetry/operation_name_test.cc @@ -4,7 +4,6 @@ #include "test/mocks/server/tracer_factory_context.h" #include "test/mocks/stream_info/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/utility.h" diff --git a/test/extensions/tracers/skywalking/BUILD b/test/extensions/tracers/skywalking/BUILD index 921b3caca352b..6444ec5899a4f 100644 --- a/test/extensions/tracers/skywalking/BUILD +++ b/test/extensions/tracers/skywalking/BUILD @@ -39,7 +39,6 @@ envoy_extension_cc_test( "//test/mocks/grpc:grpc_mocks", "//test/mocks/server:tracer_factory_context_mocks", "//test/mocks/stats:stats_mocks", - "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", ], ) @@ -76,7 +75,6 @@ envoy_extension_cc_test( "//test/mocks/server:tracer_factory_context_mocks", "//test/mocks/stats:stats_mocks", "//test/mocks/upstream:cluster_manager_mocks", - "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc b/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc index e10cabf7b225a..f9c024eed5da8 100644 --- a/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc +++ b/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc @@ -6,7 +6,6 @@ #include "test/mocks/grpc/mocks.h" #include "test/mocks/server/tracer_factory_context.h" #include "test/mocks/stats/mocks.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/tracers/skywalking/tracer_test.cc b/test/extensions/tracers/skywalking/tracer_test.cc index de8ce15d44d67..1d25b8f3b1a53 100644 --- a/test/extensions/tracers/skywalking/tracer_test.cc +++ b/test/extensions/tracers/skywalking/tracer_test.cc @@ -7,7 +7,6 @@ #include "test/mocks/stats/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/tracers/xray/BUILD b/test/extensions/tracers/xray/BUILD index fc79bcdbd3146..d66fdf913d22d 100644 --- a/test/extensions/tracers/xray/BUILD +++ b/test/extensions/tracers/xray/BUILD @@ -26,8 +26,6 @@ envoy_extension_cc_test( "//source/extensions/tracers/xray:xray_lib", "//test/mocks:common_lib", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:instance_mocks", "//test/mocks/server:tracer_factory_context_mocks", "//test/mocks/stats:stats_mocks", diff --git a/test/extensions/tracers/zipkin/tracer_test.cc b/test/extensions/tracers/zipkin/tracer_test.cc index bb54c0d55779b..2628b419ee580 100644 --- a/test/extensions/tracers/zipkin/tracer_test.cc +++ b/test/extensions/tracers/zipkin/tracer_test.cc @@ -7,7 +7,6 @@ #include "source/extensions/tracers/zipkin/zipkin_core_constants.h" #include "test/mocks/common.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" diff --git a/test/extensions/transport_sockets/alts/BUILD b/test/extensions/transport_sockets/alts/BUILD index e5b9993193d33..4894b1358a3fd 100644 --- a/test/extensions/transport_sockets/alts/BUILD +++ b/test/extensions/transport_sockets/alts/BUILD @@ -168,8 +168,6 @@ envoy_extension_cc_test( "//source/extensions/transport_sockets/alts:tsi_socket", "//test/integration:http_integration_lib", "//test/integration/filters:decode_dynamic_metadata_filter_lib", - "//test/mocks/runtime:runtime_mocks", - "//test/mocks/secret:secret_mocks", "//test/mocks/server:server_mocks", "//test/test_common:status_utility_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/watchdog/profile_action/profile_action_test.cc b/test/extensions/watchdog/profile_action/profile_action_test.cc index 5efc0d3bc1c01..c412b0bc0a67f 100644 --- a/test/extensions/watchdog/profile_action/profile_action_test.cc +++ b/test/extensions/watchdog/profile_action/profile_action_test.cc @@ -15,7 +15,6 @@ #include "source/extensions/watchdog/profile_action/profile_action.h" #include "test/common/stats/stat_test_utility.h" -#include "test/mocks/event/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_time.h" diff --git a/test/fuzz/BUILD b/test/fuzz/BUILD index da576dac59b27..1494cfbd29104 100644 --- a/test/fuzz/BUILD +++ b/test/fuzz/BUILD @@ -72,7 +72,6 @@ envoy_cc_test_library( deps = [ ":common_proto_cc_proto", "//source/common/common:empty_string", - "//source/common/common:logger_lib", "//source/common/network:resolver_lib", "//source/common/network:utility_lib", "//source/common/protobuf:utility_lib_header", diff --git a/test/fuzz/utility.cc b/test/fuzz/utility.cc index 810007e000c07..400c89dca5394 100644 --- a/test/fuzz/utility.cc +++ b/test/fuzz/utility.cc @@ -1,7 +1,5 @@ #include "test/fuzz/utility.h" -#include "source/common/common/logger.h" - #include "absl/strings/str_format.h" namespace Envoy { diff --git a/test/integration/BUILD b/test/integration/BUILD index f49f41eb67d93..4d090d4b32eeb 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -343,6 +343,7 @@ envoy_cc_test( ":upstream_socket_proto_cc_proto", "//source/common/router:string_accessor_lib", "//source/extensions/transport_sockets/common:passthrough_lib", + "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto", @@ -778,6 +779,7 @@ envoy_cc_test( "//source/extensions/filters/network/tcp_proxy:config", "//test/common/grpc:grpc_client_integration_lib", "//test/integration/filters:test_network_filter_lib", + "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", @@ -1035,6 +1037,7 @@ envoy_cc_test_library( "//test/integration/filters:remove_response_trailers_lib", "//test/integration/filters:stop_iteration_headers_inject_body", "//test/test_common:logging_lib", + "//test/test_common:registry_lib", "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", @@ -1101,6 +1104,7 @@ envoy_cc_test( "//test/integration/filters:encoder_decoder_buffer_filter_lib", "//test/integration/filters:local_reply_during_decoding_filter_lib", "//test/integration/filters:random_pause_filter_lib", + "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", @@ -1188,7 +1192,6 @@ envoy_cc_test_library( "//test/common/http/http2:http2_frame", "//test/common/upstream:utility_lib", "//test/mocks/upstream:cluster_info_mocks", - "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/early_data/v3:pkg_cc_proto", @@ -1497,7 +1500,6 @@ envoy_cc_test_library( "//test/common/upstream:utility_lib", "//test/config:utility_lib", "//test/mocks:common_lib", - "//test/mocks/event:event_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:instance_mocks", @@ -2007,6 +2009,7 @@ envoy_cc_test( "//test/config/integration/certs:certs_info", "//test/mocks/runtime:runtime_mocks", "//test/mocks/secret:secret_mocks", + "//test/test_common:registry_lib", "//test/test_common:resources_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", @@ -2052,6 +2055,7 @@ envoy_cc_test_library( hdrs = ["tcp_proxy_integration.h"], deps = [ ":integration_lib", + "//test/mocks/secret:secret_mocks", "//test/test_common:utility_lib", ], ) @@ -2122,7 +2126,6 @@ envoy_cc_test( "//source/extensions/transport_sockets/tls:config", "//test/mocks/runtime:runtime_mocks", "//test/mocks/secret:secret_mocks", - "//test/test_common:registry_lib", "//test/test_common:utility_lib", ], ) @@ -2341,6 +2344,7 @@ envoy_cc_test( "//source/extensions/transport_sockets/tls:config", "//test/common/grpc:grpc_client_integration_lib", "//test/integration/filters:test_listener_filter_lib", + "//test/mocks/secret:secret_mocks", "//test/test_common:environment_lib", "//test/test_common:status_utility_lib", "//test/test_common:utility_lib", @@ -2661,6 +2665,7 @@ envoy_cc_test( deps = [ ":http_integration_lib", "//test/common/formatter:command_extension_lib", + "//test/test_common:registry_lib", "//test/test_common:utility_lib", ], ) @@ -2698,6 +2703,7 @@ envoy_cc_test( "//source/common/config:api_version_lib", "//source/common/protobuf", "//test/integration/upstreams:per_host_upstream_config", + "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", diff --git a/test/integration/access_log_integration_test.cc b/test/integration/access_log_integration_test.cc index 837490cc7925e..405a115d8a704 100644 --- a/test/integration/access_log_integration_test.cc +++ b/test/integration/access_log_integration_test.cc @@ -3,7 +3,6 @@ #include "source/common/protobuf/protobuf.h" #include "test/integration/http_integration.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/integration/cds_integration_test.cc b/test/integration/cds_integration_test.cc index 9e6b9996519a8..4b84e83e936df 100644 --- a/test/integration/cds_integration_test.cc +++ b/test/integration/cds_integration_test.cc @@ -13,7 +13,6 @@ #include "test/integration/utility.h" #include "test/test_common/network_utility.h" #include "test/test_common/resources.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "absl/synchronization/notification.h" diff --git a/test/integration/clusters/BUILD b/test/integration/clusters/BUILD index 79173a158252a..7bea78bd88fa7 100644 --- a/test/integration/clusters/BUILD +++ b/test/integration/clusters/BUILD @@ -33,7 +33,6 @@ envoy_cc_test_library( "//source/server:transport_socket_config_lib", "//test/common/upstream:utility_lib", "//test/integration/load_balancers:custom_lb_policy", - "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/integration/clusters/custom_static_cluster.h b/test/integration/clusters/custom_static_cluster.h index 74fb6bf0506ed..1a2d6b0901bcd 100644 --- a/test/integration/clusters/custom_static_cluster.h +++ b/test/integration/clusters/custom_static_cluster.h @@ -18,7 +18,6 @@ #include "test/common/upstream/utility.h" #include "test/integration/clusters/cluster_factory_config.pb.h" #include "test/integration/clusters/cluster_factory_config.pb.validate.h" -#include "test/test_common/registry.h" namespace Envoy { diff --git a/test/integration/h1_fuzz.h b/test/integration/h1_fuzz.h index fac23385806cd..fe5fbc0856569 100644 --- a/test/integration/h1_fuzz.h +++ b/test/integration/h1_fuzz.h @@ -1,7 +1,6 @@ #pragma once #include "source/common/common/assert.h" -#include "source/common/common/logger.h" #include "test/fuzz/fuzz_runner.h" #include "test/integration/capture_fuzz.pb.h" diff --git a/test/integration/h2_fuzz.h b/test/integration/h2_fuzz.h index c17f784f807e3..81cb28e727c7f 100644 --- a/test/integration/h2_fuzz.h +++ b/test/integration/h2_fuzz.h @@ -1,7 +1,6 @@ #pragma once #include "source/common/common/assert.h" -#include "source/common/common/logger.h" #include "test/common/http/http2/http2_frame.h" #include "test/fuzz/fuzz_runner.h" diff --git a/test/integration/hds_integration_test.cc b/test/integration/hds_integration_test.cc index b58ffd33fc299..ce2190c753d39 100644 --- a/test/integration/hds_integration_test.cc +++ b/test/integration/hds_integration_test.cc @@ -18,7 +18,6 @@ #include "test/integration/http_integration.h" #include "test/test_common/network_utility.h" #include "test/test_common/resources.h" -#include "test/test_common/simulated_time_system.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 89178b76d57f9..d59e06f72bf7b 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -45,7 +45,6 @@ #include "test/mocks/upstream/cluster_info.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" -#include "test/test_common/registry.h" #include "absl/time/time.h" #include "base_integration_test.h" diff --git a/test/integration/load_balancers/BUILD b/test/integration/load_balancers/BUILD index e807c485503bc..59faa88f1fe0d 100644 --- a/test/integration/load_balancers/BUILD +++ b/test/integration/load_balancers/BUILD @@ -21,7 +21,6 @@ envoy_cc_test_library( ":config_cc_proto", "//envoy/upstream:load_balancer_interface", "//source/common/upstream:load_balancer_factory_base_lib", - "//test/test_common:registry_lib", ], ) diff --git a/test/integration/load_balancers/custom_lb_policy.h b/test/integration/load_balancers/custom_lb_policy.h index 69d7c2569d96b..6585c3711cc13 100644 --- a/test/integration/load_balancers/custom_lb_policy.h +++ b/test/integration/load_balancers/custom_lb_policy.h @@ -5,7 +5,6 @@ #include "source/common/upstream/load_balancer_factory_base.h" #include "test/integration/load_balancers/config.pb.h" -#include "test/test_common/registry.h" namespace Envoy { diff --git a/test/integration/original_ip_detection_integration_test.cc b/test/integration/original_ip_detection_integration_test.cc index 6e1a5f8a26e76..120d6d0be48fd 100644 --- a/test/integration/original_ip_detection_integration_test.cc +++ b/test/integration/original_ip_detection_integration_test.cc @@ -1,7 +1,6 @@ #include "envoy/extensions/http/original_ip_detection/custom_header/v3/custom_header.pb.h" #include "test/integration/http_integration.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/integration/quic_http_integration_test.h b/test/integration/quic_http_integration_test.h index 1dc77f85b6d89..f208cf616cbba 100644 --- a/test/integration/quic_http_integration_test.h +++ b/test/integration/quic_http_integration_test.h @@ -30,7 +30,6 @@ #include "test/integration/http_integration.h" #include "test/integration/socket_interface_swap.h" #include "test/integration/ssl_utility.h" -#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "quiche/quic/core/crypto/quic_client_session_cache.h" diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index f0075de3f50fd..776ffff346cc9 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -32,7 +32,6 @@ #include "test/integration/http_integration.h" #include "test/integration/server.h" #include "test/integration/ssl_utility.h" -#include "test/mocks/secret/mocks.h" #include "test/test_common/network_utility.h" #include "test/test_common/registry.h" #include "test/test_common/resources.h" diff --git a/test/integration/sds_static_integration_test.cc b/test/integration/sds_static_integration_test.cc index 0e63eb3d42676..d9065434a00cc 100644 --- a/test/integration/sds_static_integration_test.cc +++ b/test/integration/sds_static_integration_test.cc @@ -16,7 +16,6 @@ #include "test/integration/http_integration.h" #include "test/integration/server.h" #include "test/integration/ssl_utility.h" -#include "test/mocks/secret/mocks.h" #include "test/test_common/network_utility.h" #include "test/test_common/test_time_system.h" #include "test/test_common/utility.h" diff --git a/test/integration/server.cc b/test/integration/server.cc index 4203d3f42d3a3..b80f346e24659 100644 --- a/test/integration/server.cc +++ b/test/integration/server.cc @@ -18,7 +18,6 @@ #include "test/integration/utility.h" #include "test/mocks/common.h" -#include "test/mocks/runtime/mocks.h" #include "test/test_common/environment.h" #include "absl/strings/str_replace.h" diff --git a/test/integration/tcp_proxy_many_connections_test.cc b/test/integration/tcp_proxy_many_connections_test.cc index 18d8ed2c01f17..e838f073e7503 100644 --- a/test/integration/tcp_proxy_many_connections_test.cc +++ b/test/integration/tcp_proxy_many_connections_test.cc @@ -5,7 +5,6 @@ #include "test/integration/integration.h" #include "test/integration/utility.h" -#include "test/test_common/registry.h" #include "gtest/gtest.h" diff --git a/test/integration/vhds.h b/test/integration/vhds.h index 7f4307db671cb..bff245a828b46 100644 --- a/test/integration/vhds.h +++ b/test/integration/vhds.h @@ -15,7 +15,6 @@ #include "test/integration/utility.h" #include "test/test_common/network_utility.h" #include "test/test_common/resources.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "absl/synchronization/notification.h" diff --git a/test/integration/vhds_integration_test.cc b/test/integration/vhds_integration_test.cc index 4b943de68d5ea..66ae280eec78f 100644 --- a/test/integration/vhds_integration_test.cc +++ b/test/integration/vhds_integration_test.cc @@ -14,7 +14,6 @@ #include "test/integration/vhds.h" #include "test/test_common/network_utility.h" #include "test/test_common/resources.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" #include "absl/synchronization/notification.h" diff --git a/test/integration/xds_integration_test.cc b/test/integration/xds_integration_test.cc index 7064e8f157e9c..07329d618449c 100644 --- a/test/integration/xds_integration_test.cc +++ b/test/integration/xds_integration_test.cc @@ -11,6 +11,7 @@ #include "test/integration/http_integration.h" #include "test/integration/http_protocol_integration.h" #include "test/integration/ssl_utility.h" +#include "test/mocks/secret/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/status_utility.h" #include "test/test_common/utility.h" diff --git a/test/mocks/config/BUILD b/test/mocks/config/BUILD index bea03f75c49c8..8a62ba23469d0 100644 --- a/test/mocks/config/BUILD +++ b/test/mocks/config/BUILD @@ -41,8 +41,9 @@ envoy_cc_mock( srcs = ["eds_resources_cache.cc"], hdrs = ["eds_resources_cache.h"], deps = [ + "//envoy/common:optref_lib", + "//envoy/common:pure_lib", "//envoy/config:eds_resources_cache_interface", - "//source/common/common:logger_lib", ], ) diff --git a/test/mocks/config/eds_resources_cache.cc b/test/mocks/config/eds_resources_cache.cc index aaa825a81ae38..6cec1902885cc 100644 --- a/test/mocks/config/eds_resources_cache.cc +++ b/test/mocks/config/eds_resources_cache.cc @@ -1,7 +1,5 @@ #include "test/mocks/config/eds_resources_cache.h" -#include "source/common/common/logger.h" - namespace Envoy { namespace Config { using testing::_; diff --git a/test/mocks/server/BUILD b/test/mocks/server/BUILD index b5e5acd77016b..9f6b695abdd92 100644 --- a/test/mocks/server/BUILD +++ b/test/mocks/server/BUILD @@ -251,7 +251,6 @@ envoy_cc_mock( "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/secret:secret_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/tracing:tracing_mocks", diff --git a/test/mocks/server/health_checker_factory_context.h b/test/mocks/server/health_checker_factory_context.h index 5fad6da1259b4..211af4e6755b7 100644 --- a/test/mocks/server/health_checker_factory_context.h +++ b/test/mocks/server/health_checker_factory_context.h @@ -6,7 +6,6 @@ #include "test/mocks/api/mocks.h" #include "test/mocks/common.h" #include "test/mocks/event/mocks.h" -#include "test/mocks/protobuf/mocks.h" #include "test/mocks/router/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/factory_context.h" diff --git a/test/mocks/server/overload_manager.cc b/test/mocks/server/overload_manager.cc index 7380007304873..0e9221b0068a0 100644 --- a/test/mocks/server/overload_manager.cc +++ b/test/mocks/server/overload_manager.cc @@ -2,8 +2,6 @@ #include -#include "test/mocks/event/mocks.h" - #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/mocks/server/server_factory_context.h b/test/mocks/server/server_factory_context.h index aa2a89bf6d8b4..b595447280114 100644 --- a/test/mocks/server/server_factory_context.h +++ b/test/mocks/server/server_factory_context.h @@ -20,7 +20,6 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/secret/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/drain_manager.h" #include "test/mocks/server/hot_restart.h" diff --git a/test/mocks/ssl/BUILD b/test/mocks/ssl/BUILD index c2b862df2a2d8..3a0353c3c666b 100644 --- a/test/mocks/ssl/BUILD +++ b/test/mocks/ssl/BUILD @@ -20,7 +20,6 @@ envoy_cc_mock( "//envoy/ssl:context_manager_interface", "//envoy/stats:stats_interface", "//source/common/tls/cert_validator:cert_validator_lib", - "//test/mocks/secret:secret_mocks", "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", ], ) diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index 5eea5bbdc7f2f..7b73c859c2a2e 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -14,8 +14,6 @@ #include "source/common/tls/cert_validator/san_matcher.h" -#include "test/mocks/secret/mocks.h" - #include "gmock/gmock.h" namespace Envoy { diff --git a/test/mocks/upstream/BUILD b/test/mocks/upstream/BUILD index 31ef69cfc17ff..e920ac0776964 100644 --- a/test/mocks/upstream/BUILD +++ b/test/mocks/upstream/BUILD @@ -127,7 +127,6 @@ envoy_cc_mock( "//test/mocks/grpc:grpc_mocks", "//test/mocks/http:http_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/secret:secret_mocks", "//test/mocks/stats:stats_mocks", "//test/mocks/tcp:tcp_mocks", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", @@ -259,7 +258,6 @@ envoy_cc_mock( "//envoy/upstream:cluster_manager_interface", "//source/common/quic:envoy_quic_network_observer_registry_factory_lib", "//source/common/singleton:manager_impl_lib", - "//test/mocks/secret:secret_mocks", "//test/test_common:thread_factory_for_test_lib", ], ) diff --git a/test/mocks/upstream/cluster_manager_factory.h b/test/mocks/upstream/cluster_manager_factory.h index a11f5101b3a8d..2de3940a6e227 100644 --- a/test/mocks/upstream/cluster_manager_factory.h +++ b/test/mocks/upstream/cluster_manager_factory.h @@ -5,7 +5,6 @@ #include "source/common/quic/envoy_quic_network_observer_registry_factory.h" #include "source/common/singleton/manager_impl.h" -#include "test/mocks/secret/mocks.h" #include "test/test_common/thread_factory_for_test.h" #include "gmock/gmock.h" diff --git a/test/mocks/upstream/mocks.h b/test/mocks/upstream/mocks.h index ec1828d095b7b..8328469f311f3 100644 --- a/test/mocks/upstream/mocks.h +++ b/test/mocks/upstream/mocks.h @@ -20,8 +20,6 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/grpc/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/runtime/mocks.h" -#include "test/mocks/secret/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/tcp/mocks.h" #include "test/mocks/upstream/basic_resource_limit.h" diff --git a/test/server/BUILD b/test/server/BUILD index 10c348bba4f69..23dcf07f66a43 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -274,7 +274,6 @@ envoy_cc_test( "//test/mocks/filesystem:filesystem_mocks", "//test/test_common:environment_lib", "//test/test_common:logging_lib", - "//test/test_common:registry_lib", "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", diff --git a/test/server/admin/BUILD b/test/server/admin/BUILD index 4f59cae0cf5c0..e3bcbf81d16c3 100644 --- a/test/server/admin/BUILD +++ b/test/server/admin/BUILD @@ -42,7 +42,6 @@ envoy_cc_test_library( "//source/common/formatter:formatter_extension_lib", "//source/server:configuration_lib", "//source/server/admin:admin_lib", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:instance_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", @@ -66,7 +65,6 @@ envoy_cc_test( "//source/common/stats:thread_local_store_lib", "//source/server:null_overload_manager_lib", "//source/server/admin:admin_lib", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:logging_lib", diff --git a/test/server/admin/admin_instance.h b/test/server/admin/admin_instance.h index 544d9b3471946..828730a4ceb5e 100644 --- a/test/server/admin/admin_instance.h +++ b/test/server/admin/admin_instance.h @@ -3,7 +3,6 @@ #include "source/server/admin/admin.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/instance.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index 57a7e413654f8..a1b2aa1791921 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -18,19 +18,11 @@ envoy_cc_test( "//source/common/stats:stats_lib", "//source/common/tls:context_lib", "//source/server/config_validation:cluster_manager_lib", - "//test/mocks/access_log:access_log_mocks", - "//test/mocks/event:event_mocks", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/runtime:runtime_mocks", - "//test/mocks/secret:secret_mocks", "//test/mocks/server:admin_mocks", "//test/mocks/server:instance_mocks", "//test/mocks/server:options_mocks", - "//test/mocks/thread_local:thread_local_mocks", - "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", ], diff --git a/test/server/config_validation/cluster_manager_test.cc b/test/server/config_validation/cluster_manager_test.cc index 3063c33deacdd..67c200e5088d3 100644 --- a/test/server/config_validation/cluster_manager_test.cc +++ b/test/server/config_validation/cluster_manager_test.cc @@ -9,19 +9,11 @@ #include "source/common/tls/context_manager_impl.h" #include "source/server/config_validation/cluster_manager.h" -#include "test/mocks/access_log/mocks.h" -#include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" -#include "test/mocks/secret/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/instance.h" #include "test/mocks/server/options.h" -#include "test/mocks/thread_local/mocks.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" namespace Envoy { diff --git a/test/server/options_impl_test.cc b/test/server/options_impl_test.cc index e90d669f635d3..65215bb36a730 100644 --- a/test/server/options_impl_test.cc +++ b/test/server/options_impl_test.cc @@ -29,7 +29,6 @@ #include "test/mocks/api/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/logging.h" -#include "test/test_common/registry.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "test/test_common/utility.h" diff --git a/test/server/server_fuzz_test.cc b/test/server/server_fuzz_test.cc index c2f2ee9a22c63..731de8c810dba 100644 --- a/test/server/server_fuzz_test.cc +++ b/test/server/server_fuzz_test.cc @@ -13,7 +13,6 @@ #include "test/integration/server.h" #include "test/mocks/server/hot_restart.h" #include "test/mocks/server/options.h" -#include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/test_time.h" diff --git a/test/test_common/BUILD b/test/test_common/BUILD index f93255c4ed36d..7a566e12aacd4 100644 --- a/test/test_common/BUILD +++ b/test/test_common/BUILD @@ -174,10 +174,8 @@ envoy_cc_test_library( "//source/common/runtime:runtime_lib", "//source/common/stats:isolated_store_lib", "//test/mocks/event:event_mocks", - "//test/mocks/init:init_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/runtime:runtime_mocks", "//test/mocks/thread_local:thread_local_mocks", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", ], @@ -350,7 +348,6 @@ envoy_cc_test_library( deps = [ "//source/common/stream_info:stream_info_lib", "//source/extensions/common/wasm:wasm_lib", - "//test/mocks/grpc:grpc_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", diff --git a/test/test_common/test_runtime.h b/test/test_common/test_runtime.h index 98e39503af454..ca25c31d90bce 100644 --- a/test/test_common/test_runtime.h +++ b/test/test_common/test_runtime.h @@ -26,10 +26,8 @@ #include "test/mocks/common.h" #include "test/mocks/event/mocks.h" -#include "test/mocks/init/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "gmock/gmock.h" diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index 2269d83d56329..923b82575d800 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -11,7 +11,6 @@ #include "source/common/stream_info/stream_info_impl.h" #include "source/extensions/common/wasm/wasm.h" -#include "test/mocks/grpc/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/mocks.h" From 53f74bb5cbcd99a7ba4d1f509142b14363cd1e16 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Wed, 29 Apr 2026 23:28:06 +0200 Subject: [PATCH 166/750] dynamic_modules: serialize Struct configs to JSON on per-route and upstream bridge paths (#44743) ## Description The HTTP filter per-route configuration and the upstream HTTP TCP bridge configuration were calling `MessageUtil::anyToBytes` instead of `MessageUtil::knownAnyToBytes`, so a `google.protobuf.Struct` payload was forwarded to the dynamic module as raw protobuf wire bytes instead of as a JSON string. This PR migrates both the call sites to `knownAnyToBytes`, matching every other dynamic-module extension factory and the regular (non-per-route) HTTP filter path that was already fixed in #43753. Fix https://github.com/envoyproxy/envoy/issues/44733 --- **Commit Message:** dynamic_modules: serialize Struct configs to JSON on per-route and upstream bridge paths **Additional Description:** Migrates both the call sites to `knownAnyToBytes`, matching every other dynamic-module extension factory **Risk Level:** Low **Testing:** Added Unit + Integration Tests **Docs Changes:** Added **Release Notes:** Added Signed-off-by: Rohit Agrawal --- changelogs/current.yaml | 7 +++ .../filters/http/dynamic_modules/factory.cc | 2 +- .../upstreams/http/dynamic_modules/config.cc | 2 +- .../dynamic_modules/http/factory_test.cc | 34 +++++++++++++ .../dynamic_modules/http/integration_test.cc | 50 ++++++++++++++++++- .../dynamic_modules/upstream_request_test.cc | 22 ++++++++ 6 files changed, 113 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index f8a8c45323655..51059e9b1f6fc 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -39,6 +39,13 @@ bug_fixes: Fixed a crashing bug in the HTTP filter when a stream was already above the downstream write-buffer high watermark at filter-chain construction time. Downstream watermark callback registration is now deferred until the in-module filter has been constructed. +- area: dynamic_modules + change: | + Fixed a bug where the HTTP filter per-route configuration and the upstream HTTP TCP bridge + configuration did not handle the ``google.protobuf.Struct`` configuration message as the API + definition requires. Both factories now serialize the ``Struct`` to a JSON string and pass the + string to the dynamic module side as the configuration, matching the behavior already in place + for every other dynamic module extension factory. - area: oauth2 change: | Fixed a crash in the OAuth2 filter where AES-CBC decryption of token cookies could spuriously diff --git a/source/extensions/filters/http/dynamic_modules/factory.cc b/source/extensions/filters/http/dynamic_modules/factory.cc index fe099a8771f60..3d9594c651581 100644 --- a/source/extensions/filters/http/dynamic_modules/factory.cc +++ b/source/extensions/filters/http/dynamic_modules/factory.cc @@ -245,7 +245,7 @@ DynamicModuleConfigFactory::createRouteSpecificFilterConfigTyped( std::string config; if (proto_config.has_filter_config()) { - auto config_or_error = MessageUtil::anyToBytes(proto_config.filter_config()); + auto config_or_error = MessageUtil::knownAnyToBytes(proto_config.filter_config()); RETURN_IF_NOT_OK_REF(config_or_error.status()); config = std::move(config_or_error.value()); } diff --git a/source/extensions/upstreams/http/dynamic_modules/config.cc b/source/extensions/upstreams/http/dynamic_modules/config.cc index 7be6bc24e06d5..3ba662d35d389 100644 --- a/source/extensions/upstreams/http/dynamic_modules/config.cc +++ b/source/extensions/upstreams/http/dynamic_modules/config.cc @@ -37,7 +37,7 @@ absl::StatusOr getOrCreateBridgeConfig( std::string bridge_config_bytes; if (proto_config.has_bridge_config()) { - auto config_or_error = MessageUtil::anyToBytes(proto_config.bridge_config()); + auto config_or_error = MessageUtil::knownAnyToBytes(proto_config.bridge_config()); if (!config_or_error.ok()) { return absl::InvalidArgumentError("failed to parse bridge_config: " + std::string(config_or_error.status().message())); diff --git a/test/extensions/dynamic_modules/http/factory_test.cc b/test/extensions/dynamic_modules/http/factory_test.cc index 0b5ec7308342e..00eb4bdca5663 100644 --- a/test/extensions/dynamic_modules/http/factory_test.cc +++ b/test/extensions/dynamic_modules/http/factory_test.cc @@ -89,6 +89,40 @@ filter_name: foo proto_config, context.server_factory_context_, context.messageValidationVisitor()); } +TEST(DynamicModuleConfigFactory, LoadOKPerRouteWithStruct) { + TestEnvironment::setEnvVar( + "ENVOY_DYNAMIC_MODULES_SEARCH_PATH", + TestEnvironment::substitute("{{ test_rundir }}/test/extensions/dynamic_modules/test_data/c"), + 1); + + // ``google.protobuf.Struct`` per-route configs must be serialized to a JSON string instead of + // being forwarded as raw protobuf binary, matching the behavior of the regular filter config. + const std::string yaml = R"EOF( +dynamic_module_config: + name: no_op + do_not_close: true + load_globally: false +filter_name: foo +filter_config: + "@type": "type.googleapis.com/google.protobuf.Struct" + value: + key: value +)EOF"; + + envoy::extensions::filters::http::dynamic_modules::v3::DynamicModuleFilterPerRoute proto_config; + TestUtility::loadFromYamlAndValidate(yaml, proto_config); + + NiceMock context; + Api::ApiPtr api = Api::createApiForTest(); + EXPECT_CALL(context.server_factory_context_, api()).WillRepeatedly(testing::ReturnRef(*api)); + ON_CALL(context.server_factory_context_.options_, concurrency()) + .WillByDefault(testing::Return(1)); + + Envoy::Server::Configuration::DynamicModuleConfigFactory factory; + auto result = factory.createRouteSpecificFilterConfig( + proto_config, context.server_factory_context_, context.messageValidationVisitor()); +} + TEST(DynamicModuleConfigFactory, DEPRECATED_FEATURE_TEST(LoadOKPerRouteWithLegacyName)) { TestEnvironment::setEnvVar( "ENVOY_DYNAMIC_MODULES_SEARCH_PATH", diff --git a/test/extensions/dynamic_modules/http/integration_test.cc b/test/extensions/dynamic_modules/http/integration_test.cc index b5f2cd141a67d..f800a6aa29ebd 100644 --- a/test/extensions/dynamic_modules/http/integration_test.cc +++ b/test/extensions/dynamic_modules/http/integration_test.cc @@ -22,7 +22,7 @@ class DynamicModulesIntegrationTest : public testing::TestWithParam initializeFilter(const std::string& filter_name, const std::string& config = "", const std::string& per_route_config = "", const std::string& type_url = "type.googleapis.com/google.protobuf.StringValue", - bool upstream_filter = false) { + bool upstream_filter = false, const std::string& per_route_type_url = "") { std::string module_name = "http_integration_test"; if (GetParam() != "rust_static") { TestEnvironment::setEnvVar( @@ -48,6 +48,10 @@ name: envoy.extensions.filters.http.dynamic_modules )EOF"; if (!per_route_config.empty()) { + // The per-route config defaults to using ``type_url`` so existing callers continue to share + // the same Any type for both the filter and per-route configuration. + const std::string& effective_per_route_type_url = + per_route_type_url.empty() ? type_url : per_route_type_url; constexpr auto filter_per_route_config = R"EOF( dynamic_module_config: name: {} @@ -59,7 +63,7 @@ filter_name: {} envoy::extensions::filters::http::dynamic_modules::v3::DynamicModuleFilterPerRoute per_route_config_proto; TestUtility::loadFromYaml(fmt::format(filter_per_route_config, module_name, filter_name, - type_url, per_route_config), + effective_per_route_type_url, per_route_config), per_route_config_proto); config_helper_.addConfigModifier( @@ -309,6 +313,48 @@ TEST_P(DynamicModulesIntegrationTest, PerRouteConfig) { .getStringView()); } +// Verifies that a ``google.protobuf.Struct`` per-route configuration is serialized to a JSON +// string before being passed to the dynamic module. +TEST_P(DynamicModulesIntegrationTest, PerRouteStructConfig) { + if (GetParam() != "rust" && GetParam() != "rust_static") { + // The per_route_config test filter that surfaces the raw config bytes back as a header is + // implemented by the Rust integration test data, so the Struct check is scoped to those + // language flavors. + return; + } + + // The regular filter config remains a ``StringValue``, preserving the existing semantics of + // the per_route_config test filter for the ``x-config`` header, while the per-route override + // is supplied as a ``google.protobuf.Struct``. + initializeFilter("per_route_config", "a", R"({"struct_key":"struct_value"})", + "type.googleapis.com/google.protobuf.StringValue", false, + "type.googleapis.com/google.protobuf.Struct"); + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + + Http::TestRequestHeaderMapImpl request_headers{{"foo", "bar"}, + {":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}; + + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ("a", upstream_request_->headers() + .get(Http::LowerCaseString("x-config"))[0] + ->value() + .getStringView()); + // The per-route ``Struct`` must arrive as its JSON serialization rather than as raw protobuf + // binary bytes (which would have started with a ``0x0a`` wire-tag byte and broken any module + // that attempted to ``json.Unmarshal`` the payload). See + // https://github.com/envoyproxy/envoy/issues/44733. + EXPECT_EQ(R"({"struct_key":"struct_value"})", + upstream_request_->headers() + .get(Http::LowerCaseString("x-per-route-config"))[0] + ->value() + .getStringView()); +} + TEST_P(DynamicModulesIntegrationTest, BodyCallbacks) { initializeFilter("body_callbacks"); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); diff --git a/test/extensions/upstreams/http/dynamic_modules/upstream_request_test.cc b/test/extensions/upstreams/http/dynamic_modules/upstream_request_test.cc index f3b23ae86a1f2..8b189049d5d83 100644 --- a/test/extensions/upstreams/http/dynamic_modules/upstream_request_test.cc +++ b/test/extensions/upstreams/http/dynamic_modules/upstream_request_test.cc @@ -592,6 +592,28 @@ TEST_F(DynamicModuleGenericConnPoolFactoryTest, BridgeConfigParseFailure) { EXPECT_EQ(pool, nullptr); } +// Smoke test that a ``google.protobuf.Struct`` ``bridge_config`` is accepted by the factory. +// See https://github.com/envoyproxy/envoy/issues/44733. The previous ``MessageUtil::anyToBytes`` +// implementation did not handle ``Struct`` per the API contract; ``knownAnyToBytes`` unpacks the +// ``Struct`` and serializes it to a JSON string before handing it to the bridge module. Note +// that the ``upstream_bridge_no_op`` test module deliberately ignores the config bytes, so this +// test only locks in that the ``Struct`` unpack and JSON serialization path does not surface as +// an error to the factory; the byte-level semantics are covered by the HTTP per-route +// integration test which exercises the same ``knownAnyToBytes`` helper. +TEST_F(DynamicModuleGenericConnPoolFactoryTest, BridgeConfigWithStruct) { + auto config = createProtoConfig(); + config.set_bridge_name("struct_test_bridge"); + + Protobuf::Struct struct_value; + (*struct_value.mutable_fields())["key"].set_string_value("value"); + config.mutable_bridge_config()->PackFrom(struct_value); + + auto pool = factory_.createGenericConnPool( + host_, cm_.thread_local_cluster_, Router::GenericConnPoolFactory::UpstreamProtocol::HTTP, + Upstream::ResourcePriority::Default, absl::nullopt, nullptr, config); + EXPECT_NE(pool, nullptr); +} + TEST_F(DynamicModuleGenericConnPoolFactoryTest, NameAndCategory) { EXPECT_EQ("envoy.upstreams.http.dynamic_modules", factory_.name()); EXPECT_EQ("envoy.upstreams", factory_.category()); From ff9e47a3ad74f6b4daf00bcdc502ad86200c3a10 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 29 Apr 2026 15:51:14 -0700 Subject: [PATCH 167/750] envoy_log: allows Envoy version embedding (#44337) Commit Message: envoy_log: allows Envoy version embedding Additional Description: We are running multiple Envoy versions on a same VM instance sometimes, so the Envoy's application logs are sometimes interleaved with different Envoy versions, which made it a little bit harder for us to debug later. This commit adds a new 'E' specifier in the application log to bundle the envoy version in the log line. Risk Level: low (new code path) Testing: done Docs Changes: done Release Notes: done Platform Specific Features: n/a --------- Signed-off-by: Takeshi Yoneda --- changelogs/current.yaml | 5 ++++ docs/root/operations/cli.rst | 1 + source/common/common/BUILD | 5 ++++ source/common/common/logger.cc | 11 +++++++ source/common/common/logger.h | 18 ++++++++++++ source/common/version/BUILD | 39 ++++++++++++++++++++----- source/common/version/version.cc | 25 +++------------- source/common/version/version_string.cc | 38 ++++++++++++++++++++++++ source/common/version/version_string.h | 19 ++++++++++++ source/exe/BUILD | 2 ++ test/common/common/BUILD | 1 + test/common/common/logger_test.cc | 17 +++++++++++ test/test_common/BUILD | 5 +++- 13 files changed, 157 insertions(+), 29 deletions(-) create mode 100644 source/common/version/version_string.cc create mode 100644 source/common/version/version_string.h diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 51059e9b1f6fc..85728e96a1a69 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -63,6 +63,11 @@ new_features: change: | Added :ref:`per-route configuration support ` to the ``set_metadata`` HTTP filter. +- area: logging + change: | + Added ``%N`` as a custom spdlog pattern flag that emits the Envoy version string. It can be + used in the ``--log-format`` CLI flag or the bootstrap ``application_log_config.log_format`` + to include the running version in every log line, e.g. ``--log-format "[%N][%l] %v"``. - area: dynamic_modules change: | Added ``envoy_dynamic_module_callback_is_validation_mode`` ABI callback that allows dynamic diff --git a/docs/root/operations/cli.rst b/docs/root/operations/cli.rst index 7ecbf9fc48bbc..aefe58eb67c0f 100644 --- a/docs/root/operations/cli.rst +++ b/docs/root/operations/cli.rst @@ -138,6 +138,7 @@ following are the command line options that Envoy supports. :%v: The actual message to log ("some user text") :%_: The actual message to log, but with escaped newlines (from (if using ``%v``) "some user text\nbelow", to "some user text\\nbelow") :%j: The actual message to log as JSON escaped string (https://tools.ietf.org/html/rfc7159#page-8). + :%N: The Envoy version string: ``{revision}/{version}/{status}/{build_type}/{ssl_version}`` (e.g. "c93f9f6c1e5adddd10a3e3646c7e049c649ae177/1.38.0/Clean/RELEASE/BoringSSL"), matching the output of :option:`--version`. :%t: Thread id ("1232") :%P: Process id ("3456") :%n: Logger's name ("filter") diff --git a/source/common/common/BUILD b/source/common/common/BUILD index ebc894f407bab..50ed49abdbda5 100644 --- a/source/common/common/BUILD +++ b/source/common/common/BUILD @@ -222,6 +222,11 @@ envoy_cc_library( ":macros", ":non_copyable", "//source/common/protobuf", + # Depend on the header-only interface, not the implementation lib, so that every commit to + # main does not invalidate the build cache of minimal_logger_lib (and everything that + # transitively depends on it). The implementation is linked by the final binary via + # version_linkstamp (production) / test_version_linkstamp (tests). + "//source/common/version:version_string_interface_lib", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/synchronization", ] + select({ diff --git a/source/common/common/logger.cc b/source/common/common/logger.cc index c2979d1072125..ac70c16582db5 100644 --- a/source/common/common/logger.cc +++ b/source/common/common/logger.cc @@ -9,6 +9,7 @@ #include "source/common/common/json_escape_string.h" #include "source/common/common/lock_guard.h" +#include "source/common/version/version_string.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" @@ -321,6 +322,10 @@ void setLogFormatForLogger(spdlog::logger& logger, const std::string& log_format CustomFlagFormatter::ExtractedMessage::Placeholder) .set_pattern(log_format); + formatter + ->add_flag(CustomFlagFormatter::EnvoyVersion::Placeholder) + .set_pattern(log_format); + logger.set_formatter(std::move(formatter)); } @@ -420,6 +425,12 @@ void ExtractedMessage::format(const spdlog::details::log_msg& msg, const std::tm Envoy::Logger::Utility::escapeMessageJsonString(original_message, dest); } +void EnvoyVersion::format(const spdlog::details::log_msg&, const std::tm&, + spdlog::memory_buf_t& dest) { + const std::string& version = envoyVersionString(); + dest.append(version.data(), version.data() + version.size()); +} + } // namespace CustomFlagFormatter } // namespace Logger } // namespace Envoy diff --git a/source/common/common/logger.h b/source/common/common/logger.h index cd298c4659ecc..75352be4712a6 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -480,6 +480,24 @@ class ExtractedMessage : public spdlog::custom_flag_formatter { constexpr static char Placeholder = '+'; }; +/** + * When added to a formatter, this adds 'N' as a user defined flag in the log pattern that emits + * the Envoy version string set via setVersion(). Use %N in --log-format to include the running + * version in every log line. Call setVersion() once at server start-up (before any logs are + * written) with the result of VersionInfo::version(). + */ +class EnvoyVersion : public spdlog::custom_flag_formatter { +public: + void format(const spdlog::details::log_msg& msg, const std::tm& tm, + spdlog::memory_buf_t& dest) override; + + std::unique_ptr clone() const override { + return spdlog::details::make_unique(); + } + + constexpr static char Placeholder = 'N'; +}; + } // namespace CustomFlagFormatter } // namespace Logger diff --git a/source/common/version/BUILD b/source/common/version/BUILD index 7f588bc079c59..f324e41a6cd0a 100644 --- a/source/common/version/BUILD +++ b/source/common/version/BUILD @@ -47,6 +47,37 @@ genrule( visibility = ["//visibility:private"], ) +# Header-only interface for the Envoy version string. Consumers that need only to declare the +# version-string API (such as the logger) depend on this target so that their build cache is not +# invalidated every time the SCM revision changes. The actual implementation lives in +# ":version_string_lib" below and must be linked by the final binary (or equivalent) alongside +# "version_linkstamp" (production) or "test_version_linkstamp" (tests). +envoy_cc_library( + name = "version_string_interface_lib", + hdrs = ["version_string.h"], +) + +envoy_cc_library( + name = "version_string_lib", + srcs = ["version_string.cc"], + hdrs = [":generate_version_number"], + copts = select({ + "//bazel:using_boringssl_fips": ["-DENVOY_SSL_VERSION=\\\"BoringSSL-FIPS\\\""], + "//bazel:using_aws_lc": ["-DENVOY_SSL_VERSION=\\\"AWS-LC-FIPS\\\""], + "//bazel:using_openssl": ["-DENVOY_SSL_VERSION=\\\"OpenSSL\\\""], + "//conditions:default": ["-DENVOY_SSL_VERSION=\\\"BoringSSL\\\""], + }), + deps = [ + ":version_string_interface_lib", + ":version_suffix_default", + "//source/common/common:fmt_lib", + "//source/common/common:macros", + ], + # Ensure that linkers that strip unreferenced archive objects (e.g. ld with --gc-sections) + # still pull in version_string.cc so that envoyVersionString() resolves in final binaries. + alwayslink = 1, +) + envoy_cc_library( name = "version_includes", hdrs = [ @@ -62,17 +93,11 @@ envoy_cc_library( envoy_cc_library( name = "version_lib", srcs = ["version.cc"], - copts = select({ - "//bazel:using_boringssl_fips": ["-DENVOY_SSL_VERSION=\\\"BoringSSL-FIPS\\\""], - "//bazel:using_aws_lc": ["-DENVOY_SSL_VERSION=\\\"AWS-LC-FIPS\\\""], - "//bazel:using_openssl": ["-DENVOY_SSL_VERSION=\\\"OpenSSL\\\""], - "//conditions:default": ["-DENVOY_SSL_VERSION=\\\"BoringSSL\\\""], - }), external_deps = ["ssl"], tags = ["notidy"], deps = [ ":version_includes", - ":version_suffix_default", + ":version_string_lib", "//source/common/common:macros", "//source/common/protobuf:utility_lib", ], diff --git a/source/common/version/version.cc b/source/common/version/version.cc index 2e429f3c12318..6a080506403c6 100644 --- a/source/common/version/version.cc +++ b/source/common/version/version.cc @@ -7,6 +7,7 @@ #include "source/common/common/fmt.h" #include "source/common/common/macros.h" #include "source/common/protobuf/utility.h" +#include "source/common/version/version_string.h" #include "absl/strings/numbers.h" #include "absl/strings/str_split.h" @@ -26,11 +27,7 @@ const std::string& VersionInfo::revisionStatus() { CONSTRUCT_ON_FIRST_USE(std::string, build_scm_status); } -const std::string& VersionInfo::version() { - CONSTRUCT_ON_FIRST_USE(std::string, fmt::format("{}/{}{}/{}/{}/{}", revision(), - BUILD_VERSION_NUMBER, build_version_suffix, - revisionStatus(), buildType(), sslVersion())); -} +const std::string& VersionInfo::version() { return envoyVersionString(); } const envoy::config::core::v3::BuildVersion& VersionInfo::buildVersion() { static const auto* result = new envoy::config::core::v3::BuildVersion( @@ -40,23 +37,9 @@ const envoy::config::core::v3::BuildVersion& VersionInfo::buildVersion() { bool VersionInfo::sslFipsCompliant() { return FIPS_mode() == 1; } -const std::string& VersionInfo::buildType() { -#ifdef NDEBUG - static const std::string release_type = "RELEASE"; -#else - static const std::string release_type = "DEBUG"; -#endif - return release_type; -} +const std::string& VersionInfo::buildType() { return envoyBuildType(); } -const std::string& VersionInfo::sslVersion() { -#ifdef ENVOY_SSL_VERSION - static const std::string ssl_version = ENVOY_SSL_VERSION; -#else - static const std::string ssl_version = "no-ssl"; -#endif - return ssl_version; -} +const std::string& VersionInfo::sslVersion() { return envoySSLVersion(); } envoy::config::core::v3::BuildVersion VersionInfo::makeBuildVersion(const char* version) { envoy::config::core::v3::BuildVersion result; diff --git a/source/common/version/version_string.cc b/source/common/version/version_string.cc new file mode 100644 index 0000000000000..e997b853abc2f --- /dev/null +++ b/source/common/version/version_string.cc @@ -0,0 +1,38 @@ +#include "source/common/version/version_string.h" + +#include "source/common/common/fmt.h" +#include "source/common/common/macros.h" +#include "source/common/version/version_number.h" + +extern const char build_scm_revision[]; +extern const char build_scm_status[]; +extern const char build_version_suffix[]; + +namespace Envoy { + +const std::string& envoyBuildType() { +#ifdef NDEBUG + static const std::string type = "RELEASE"; +#else + static const std::string type = "DEBUG"; +#endif + return type; +} + +const std::string& envoySSLVersion() { +#ifdef ENVOY_SSL_VERSION + static const std::string version = ENVOY_SSL_VERSION; +#else + static const std::string version = "no-ssl"; +#endif + return version; +} + +const std::string& envoyVersionString() { + CONSTRUCT_ON_FIRST_USE(std::string, + fmt::format("{}/{}{}/{}/{}/{}", build_scm_revision, BUILD_VERSION_NUMBER, + build_version_suffix, build_scm_status, envoyBuildType(), + envoySSLVersion())); +} + +} // namespace Envoy diff --git a/source/common/version/version_string.h b/source/common/version/version_string.h new file mode 100644 index 0000000000000..716af98cbdf28 --- /dev/null +++ b/source/common/version/version_string.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace Envoy { + +// Returns the Envoy version string (e.g. "1.32.0/abc123.Modified/RELEASE/BoringSSL"). +// This is intentionally a slim alternative to VersionInfo::version() for use by low-level +// libraries (such as the logger) that cannot depend on version_lib without creating a +// circular BUILD dependency through protobuf. +const std::string& envoyVersionString(); + +// Returns the build type string: "RELEASE" or "DEBUG". +const std::string& envoyBuildType(); + +// Returns the SSL library version string (e.g. "BoringSSL", "OpenSSL"). +const std::string& envoySSLVersion(); + +} // namespace Envoy diff --git a/source/exe/BUILD b/source/exe/BUILD index 16a51c89b39b1..6d4dd4fff6f26 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -169,6 +169,7 @@ envoy_cc_library( "//source/common/listener_manager:listener_manager_lib", "//source/extensions/listener_managers/validation_listener_manager:validation_listener_manager_lib", "//source/common/version:version_linkstamp", + "//source/common/version:version_string_lib", "//source/common/formatter:formatter_extension_lib", "//source/server:options_lib", # TODO(wbpcode) make this a proper extension @@ -185,6 +186,7 @@ envoy_cc_library( deps = [ ":stripped_main_base_lib", "//source/common/version:version_linkstamp", + "//source/common/version:version_string_lib", ], ) diff --git a/test/common/common/BUILD b/test/common/common/BUILD index a6ceeec1952d1..890e94b4b2166 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -245,6 +245,7 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ "//source/common/common:minimal_logger_lib", + "//source/common/version:version_string_lib", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", ], diff --git a/test/common/common/logger_test.cc b/test/common/common/logger_test.cc index c949b8bd43a70..5093095fca32d 100644 --- a/test/common/common/logger_test.cc +++ b/test/common/common/logger_test.cc @@ -4,6 +4,7 @@ #include "source/common/common/json_escape_string.h" #include "source/common/common/logger.h" +#include "source/common/version/version_string.h" #include "test/mocks/common.h" #include "test/mocks/http/mocks.h" @@ -173,6 +174,10 @@ class LoggerCustomFlagsTest : public testing::TestWithParam { ->add_flag( CustomFlagFormatter::ExtractedMessage::Placeholder) .set_pattern(pattern); + formatter + ->add_flag( + CustomFlagFormatter::EnvoyVersion::Placeholder) + .set_pattern(pattern); logger_->set_formatter(std::move(formatter)); logger_->set_level(spdlog::level::info); @@ -238,6 +243,18 @@ TEST_P(LoggerCustomFlagsTest, LogMessageWithTagsAndExtractTags) { expectLogMessage("%*", "[Tags: \"key\":\"val\"] mes\"] sge4", ",\"key\":\"val\""); } +TEST_P(LoggerCustomFlagsTest, LogMessageWithEnvoyVersion) { + // Sanity-check the version string before using it as expected output: it must be non-empty + // and follow the slash-separated format produced by envoyVersionString(). + const std::string& version = envoyVersionString(); + EXPECT_FALSE(version.empty()); + EXPECT_NE(version.find('/'), std::string::npos); + + // %N emits the Envoy version string from envoyVersionString(). + expectLogMessage("%N %v", "hello", absl::StrCat(version, " hello")); + expectLogMessage("%v", "hello", "hello"); +} + class NamedLogTest : public Loggable, public testing::Test {}; TEST_F(NamedLogTest, NamedLogsAreSentToSink) { diff --git a/test/test_common/BUILD b/test/test_common/BUILD index 7a566e12aacd4..bf69cf8f74120 100644 --- a/test/test_common/BUILD +++ b/test/test_common/BUILD @@ -364,7 +364,10 @@ envoy_cc_test_library( envoy_basic_cc_library( name = "test_version_linkstamp", srcs = ["test_version_linkstamp.cc"], - deps = ["//source/common/version:version_suffix_default"], + deps = [ + "//source/common/version:version_string_lib", + "//source/common/version:version_suffix_default", + ], alwayslink = 1, ) From 2db47c910e6340bb523ab9e2e81dbcdbb1d47547 Mon Sep 17 00:00:00 2001 From: "Antonio V. Leonti" <53806445+antoniovleonti@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:14:08 -0400 Subject: [PATCH 168/750] (xds) decouple SubscriptionBase from SubscriptionCallbacks & rename (#44047) Commit Message: decouple SubscriptionBase from SubscriptionCallbacks & rename Additional Description: Decouple `SubscriptionBase` from `SubscriptionCallbacks` so that `SubscriptionBase` can be used with other types of callbacks (e.g. `SingletonSubscriptionCallbacks`). * Remove inheritance of `SubscriptionCallbacks` from `SubscriptionBase` * Rename `SubscriptionBase` to `ResourceTypeHelper`. * For classes previously inheriting from `SubscriptionBase`: * Add a `ResourceTypeHelper` member * Inherit directly from `SubscriptionCallbacks` Risk Level: low / none Testing: no behavior change, all existing tests pass Docs Changes: none Release Notes: none Context: https://github.com/envoyproxy/envoy/issues/44237 Disclosure: an LLM was used to generate some of this change --------- Signed-off-by: antoniovleonti --- contrib/istio/filters/common/source/BUILD | 2 +- .../common/source/workload_discovery.cc | 17 +++++------ source/common/config/BUILD | 4 +-- ...cription_base.h => resource_type_helper.h} | 16 +++++++---- source/common/filter/BUILD | 2 +- source/common/filter/config_discovery_impl.cc | 11 ++++---- source/common/filter/config_discovery_impl.h | 12 ++++---- source/common/listener_manager/BUILD | 2 +- source/common/listener_manager/lds_api.cc | 27 +++++++++--------- source/common/listener_manager/lds_api.h | 5 ++-- source/common/rds/BUILD | 2 +- source/common/router/BUILD | 6 ++-- source/common/router/scoped_rds.cc | 10 +++---- source/common/router/scoped_rds.h | 9 +++--- source/common/router/vhds.cc | 11 ++++---- source/common/router/vhds.h | 7 +++-- source/common/runtime/BUILD | 2 +- source/common/runtime/runtime_impl.cc | 13 ++++----- source/common/runtime/runtime_impl.h | 5 ++-- source/common/secret/BUILD | 2 +- source/common/secret/sds_api.cc | 22 +++++++-------- source/common/secret/sds_api.h | 7 +++-- source/common/upstream/BUILD | 4 +-- source/common/upstream/cds_api_impl.cc | 13 +++++---- source/common/upstream/cds_api_impl.h | 6 ++-- source/common/upstream/od_cds_api_impl.cc | 28 ++++++++++--------- source/common/upstream/od_cds_api_impl.h | 5 ++-- .../filters/process_ratelimit/BUILD | 2 +- .../process_ratelimit/provider_singleton.cc | 17 +++++------ .../process_ratelimit/provider_singleton.h | 5 ++-- source/extensions/clusters/eds/BUILD | 4 +-- source/extensions/clusters/eds/eds.cc | 10 +++---- source/extensions/clusters/eds/eds.h | 11 ++++---- source/extensions/clusters/eds/leds.cc | 25 +++++++++-------- source/extensions/clusters/eds/leds.h | 8 +++--- test/common/config/BUILD | 11 ++++++++ .../config/resource_type_helper_test.cc | 23 +++++++++++++++ 37 files changed, 213 insertions(+), 153 deletions(-) rename source/common/config/{subscription_base.h => resource_type_helper.h} (50%) create mode 100644 test/common/config/resource_type_helper_test.cc diff --git a/contrib/istio/filters/common/source/BUILD b/contrib/istio/filters/common/source/BUILD index 62ffcc62486f7..ef5bc61a34a0b 100644 --- a/contrib/istio/filters/common/source/BUILD +++ b/contrib/istio/filters/common/source/BUILD @@ -54,7 +54,7 @@ envoy_cc_library( "//envoy/stats:stats_macros", "//envoy/thread_local:thread_local_interface", "//source/common/common:non_copyable", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/grpc:common_lib", "//source/common/init:target_lib", "@envoy_api//contrib/envoy/extensions/filters/common/workload_discovery/v3:pkg_cc_proto", diff --git a/contrib/istio/filters/common/source/workload_discovery.cc b/contrib/istio/filters/common/source/workload_discovery.cc index 5af0d8e352b55..d97634db78e7c 100644 --- a/contrib/istio/filters/common/source/workload_discovery.cc +++ b/contrib/istio/filters/common/source/workload_discovery.cc @@ -7,7 +7,7 @@ #include "envoy/thread_local/thread_local.h" #include "source/common/common/non_copyable.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/grpc/common.h" #include "source/common/init/target_impl.h" @@ -137,18 +137,18 @@ class WorkloadMetadataProviderImpl : public WorkloadMetadataProvider, public Sin IdToAddress id_to_address_; AddressToWorkload address_to_workload_; }; - class WorkloadSubscription : Config::SubscriptionBase { + class WorkloadSubscription : Config::SubscriptionCallbacks { public: WorkloadSubscription(WorkloadMetadataProviderImpl& parent) - : Config::SubscriptionBase( - parent.factory_context_.messageValidationVisitor(), "uid"), - parent_(parent) { + : parent_(parent), + resource_type_helper_(parent.factory_context_.messageValidationVisitor(), "uid") { subscription_ = THROW_OR_RETURN_VALUE( parent.factory_context_.clusterManager() .subscriptionFactory() - .subscriptionFromConfigSource(parent.config_source_, - Grpc::Common::typeUrl(getResourceName()), - *parent.scope_, *this, resource_decoder_, {}), + .subscriptionFromConfigSource( + parent.config_source_, + Grpc::Common::typeUrl(resource_type_helper_.getResourceName()), *parent.scope_, + *this, resource_type_helper_.resourceDecoder(), {}), Config::SubscriptionPtr); } void start() { subscription_->start({}); } @@ -197,6 +197,7 @@ class WorkloadMetadataProviderImpl : public WorkloadMetadataProvider, public Sin // TODO: Potential issue with the expiration of the metadata. } WorkloadMetadataProviderImpl& parent_; + const Config::ResourceTypeHelper resource_type_helper_; Config::SubscriptionPtr subscription_; }; diff --git a/source/common/config/BUILD b/source/common/config/BUILD index 96ac32798366f..778188e70192f 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -245,8 +245,8 @@ envoy_cc_library( ) envoy_cc_library( - name = "subscription_base_interface", - hdrs = ["subscription_base.h"], + name = "resource_type_helper_lib", + hdrs = ["resource_type_helper.h"], deps = [ ":opaque_resource_decoder_lib", ":resource_name_lib", diff --git a/source/common/config/subscription_base.h b/source/common/config/resource_type_helper.h similarity index 50% rename from source/common/config/subscription_base.h rename to source/common/config/resource_type_helper.h index dc15941c67200..31b7f3edfe087 100644 --- a/source/common/config/subscription_base.h +++ b/source/common/config/resource_type_helper.h @@ -8,17 +8,23 @@ namespace Envoy { namespace Config { -template struct SubscriptionBase : public Config::SubscriptionCallbacks { +/** + * Helper for resource type decoding and name identification. + * This class is intended to be used via composition in xDS API implementations. + */ +template class ResourceTypeHelper { public: - SubscriptionBase(ProtobufMessage::ValidationVisitor& validation_visitor, - absl::string_view name_field) + ResourceTypeHelper(ProtobufMessage::ValidationVisitor& validation_visitor, + absl::string_view name_field) : resource_decoder_(std::make_shared>( validation_visitor, name_field)) {} std::string getResourceName() const { return Envoy::Config::getResourceName(); } -protected: - OpaqueResourceDecoderSharedPtr resource_decoder_; + OpaqueResourceDecoderSharedPtr resourceDecoder() const { return resource_decoder_; } + +private: + const OpaqueResourceDecoderSharedPtr resource_decoder_; }; } // namespace Config diff --git a/source/common/filter/BUILD b/source/common/filter/BUILD index e3180f7e42386..9c72bd5f023e2 100644 --- a/source/common/filter/BUILD +++ b/source/common/filter/BUILD @@ -19,7 +19,7 @@ envoy_cc_library( "//envoy/stats:stats_macros", "//envoy/thread_local:thread_local_interface", "//source/common/common:containers_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/config:utility_lib", "//source/common/grpc:common_lib", "//source/common/init:manager_lib", diff --git a/source/common/filter/config_discovery_impl.cc b/source/common/filter/config_discovery_impl.cc index e94e6aef49309..a04c641cb69cf 100644 --- a/source/common/filter/config_discovery_impl.cc +++ b/source/common/filter/config_discovery_impl.cc @@ -80,20 +80,21 @@ FilterConfigSubscription::FilterConfigSubscription( Upstream::ClusterManager& cluster_manager, const std::string& stat_prefix, FilterConfigProviderManagerImplBase& filter_config_provider_manager, const std::string& subscription_id, absl::Status& creation_status) - : Config::SubscriptionBase( - factory_context.messageValidationContext().dynamicValidationVisitor(), "name"), - filter_config_name_(filter_config_name), + : filter_config_name_(filter_config_name), last_(std::make_shared("", factory_context.timeSource().systemTime())), factory_context_(factory_context), init_target_(fmt::format("FilterConfigSubscription init {}", filter_config_name_), [this]() { start(); }), + resource_type_helper_(factory_context.messageValidationContext().dynamicValidationVisitor(), + "name"), scope_(factory_context.scope().createScope(stat_prefix)), stats_({ALL_EXTENSION_CONFIG_DISCOVERY_STATS(POOL_COUNTER(*scope_))}), filter_config_provider_manager_(filter_config_provider_manager), subscription_id_(subscription_id) { - const auto resource_name = getResourceName(); + const auto resource_name = resource_type_helper_.getResourceName(); auto subscription_or_error = cluster_manager.subscriptionFactory().subscriptionFromConfigSource( - config_source, Grpc::Common::typeUrl(resource_name), *scope_, *this, resource_decoder_, {}); + config_source, Grpc::Common::typeUrl(resource_name), *scope_, *this, + resource_type_helper_.resourceDecoder(), {}); SET_AND_RETURN_IF_NOT_OK(subscription_or_error.status(), creation_status); subscription_ = std::move(*subscription_or_error); } diff --git a/source/common/filter/config_discovery_impl.h b/source/common/filter/config_discovery_impl.h index d95300ae4d863..081e1b3d84cce 100644 --- a/source/common/filter/config_discovery_impl.h +++ b/source/common/filter/config_discovery_impl.h @@ -15,7 +15,7 @@ #include "envoy/stats/stats_macros.h" #include "source/common/common/assert.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/config/utility.h" #include "source/common/init/manager_impl.h" #include "source/common/init/target_impl.h" @@ -418,10 +418,9 @@ struct ExtensionConfigDiscoveryStats { * Subscriptions are shared between the filter config providers. The filter config providers are * notified when a new config is accepted. */ -class FilterConfigSubscription - : Config::SubscriptionBase, - Logger::Loggable, - public std::enable_shared_from_this { +class FilterConfigSubscription : public Config::SubscriptionCallbacks, + Logger::Loggable, + public std::enable_shared_from_this { public: static absl::StatusOr> create(const envoy::config::core::v3::ConfigSource& config_source, @@ -483,6 +482,9 @@ class FilterConfigSubscription Server::Configuration::ServerFactoryContext& factory_context_; Init::SharedTargetImpl init_target_; + const Config::ResourceTypeHelper + resource_type_helper_; + bool started_{false}; Stats::ScopeSharedPtr scope_; diff --git a/source/common/listener_manager/BUILD b/source/common/listener_manager/BUILD index e39899aaeb548..ee20038b9aea3 100644 --- a/source/common/listener_manager/BUILD +++ b/source/common/listener_manager/BUILD @@ -137,7 +137,7 @@ envoy_cc_library( "//envoy/server:listener_manager_interface", "//source/common/common:cleanup_lib", "//source/common/config:api_version_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/config:utility_lib", "//source/common/grpc:common_lib", "//source/common/init:target_lib", diff --git a/source/common/listener_manager/lds_api.cc b/source/common/listener_manager/lds_api.cc index b5059231e8d21..32713986c9f12 100644 --- a/source/common/listener_manager/lds_api.cc +++ b/source/common/listener_manager/lds_api.cc @@ -26,21 +26,22 @@ LdsApiImpl::LdsApiImpl(const envoy::config::core::v3::ConfigSource& lds_config, Config::XdsManager& xds_manager, Upstream::ClusterManager& cm, Init::Manager& init_manager, Stats::Scope& scope, ListenerManager& lm, ProtobufMessage::ValidationVisitor& validation_visitor) - : Envoy::Config::SubscriptionBase(validation_visitor, - "name"), - listener_manager_(lm), scope_(scope.createScope("listener_manager.lds.")), - xds_manager_(xds_manager), init_target_("LDS", [this]() { subscription_->start({}); }) { - const auto resource_name = getResourceName(); + : listener_manager_(lm), scope_(scope.createScope("listener_manager.lds.")), + resource_type_helper_(validation_visitor, "name"), xds_manager_(xds_manager), + init_target_("LDS", [this]() { subscription_->start({}); }) { + const auto resource_name = resource_type_helper_.getResourceName(); if (lds_resources_locator == nullptr) { - subscription_ = THROW_OR_RETURN_VALUE(cm.subscriptionFactory().subscriptionFromConfigSource( - lds_config, Grpc::Common::typeUrl(resource_name), - *scope_, *this, resource_decoder_, {}), - Config::SubscriptionPtr); + subscription_ = + THROW_OR_RETURN_VALUE(cm.subscriptionFactory().subscriptionFromConfigSource( + lds_config, Grpc::Common::typeUrl(resource_name), *scope_, *this, + resource_type_helper_.resourceDecoder(), {}), + Config::SubscriptionPtr); } else { - subscription_ = THROW_OR_RETURN_VALUE( - cm.subscriptionFactory().collectionSubscriptionFromUrl( - *lds_resources_locator, lds_config, resource_name, *scope_, *this, resource_decoder_), - Config::SubscriptionPtr); + subscription_ = + THROW_OR_RETURN_VALUE(cm.subscriptionFactory().collectionSubscriptionFromUrl( + *lds_resources_locator, lds_config, resource_name, *scope_, *this, + resource_type_helper_.resourceDecoder()), + Config::SubscriptionPtr); } init_manager.add(init_target_); } diff --git a/source/common/listener_manager/lds_api.h b/source/common/listener_manager/lds_api.h index 1500441182c80..c453ef282e31c 100644 --- a/source/common/listener_manager/lds_api.h +++ b/source/common/listener_manager/lds_api.h @@ -13,7 +13,7 @@ #include "envoy/stats/scope.h" #include "source/common/common/logger.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/init/target_impl.h" namespace Envoy { @@ -23,7 +23,7 @@ namespace Server { * LDS API implementation that fetches via Subscription. */ class LdsApiImpl : public LdsApi, - Envoy::Config::SubscriptionBase, + public Config::SubscriptionCallbacks, Logger::Loggable { public: LdsApiImpl(const envoy::config::core::v3::ConfigSource& lds_config, @@ -49,6 +49,7 @@ class LdsApiImpl : public LdsApi, std::string system_version_info_; ListenerManager& listener_manager_; Stats::ScopeSharedPtr scope_; + const Config::ResourceTypeHelper resource_type_helper_; Config::XdsManager& xds_manager_; Init::TargetImpl init_target_; }; diff --git a/source/common/rds/BUILD b/source/common/rds/BUILD index ba6825a8f8be8..850774c639771 100644 --- a/source/common/rds/BUILD +++ b/source/common/rds/BUILD @@ -39,7 +39,7 @@ envoy_cc_library( "//source/common/common:cleanup_lib", "//source/common/common:minimal_logger_lib", "//source/common/config:api_version_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/config:subscription_factory_lib", "//source/common/config:utility_lib", "//source/common/init:manager_lib", diff --git a/source/common/router/BUILD b/source/common/router/BUILD index e3450e834e43d..865d159df0d48 100644 --- a/source/common/router/BUILD +++ b/source/common/router/BUILD @@ -202,6 +202,7 @@ envoy_cc_library( hdrs = ["vhds.h"], deps = [ ":config_lib", + ":route_config_update_impl_lib", "//envoy/config:subscription_interface", "//envoy/http:codes_interface", "//envoy/local_info:local_info_interface", @@ -213,11 +214,10 @@ envoy_cc_library( "//source/common/common:assert_lib", "//source/common/common:minimal_logger_lib", "//source/common/config:api_version_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/config:utility_lib", "//source/common/init:target_lib", "//source/common/protobuf:utility_lib", - "//source/common/router:route_config_update_impl_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", @@ -314,7 +314,7 @@ envoy_cc_library( "//source/common/config:api_version_lib", "//source/common/config:config_provider_lib", "//source/common/config:resource_name_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/config:xds_resource_lib", "//source/common/init:manager_lib", "//source/common/init:watcher_lib", diff --git a/source/common/router/scoped_rds.cc b/source/common/router/scoped_rds.cc index 010a685a6881d..ab4a21c11ba5f 100644 --- a/source/common/router/scoped_rds.cc +++ b/source/common/router/scoped_rds.cc @@ -138,19 +138,19 @@ ScopedRdsConfigSubscription::ScopedRdsConfigSubscription( ScopedRoutesConfigProviderManager& config_provider_manager) : DeltaConfigSubscriptionInstance("SRDS", manager_identifier, config_provider_manager, factory_context), - Envoy::Config::SubscriptionBase( - factory_context.messageValidationContext().dynamicValidationVisitor(), "name"), factory_context_(factory_context), name_(name), scope_(factory_context.scope().createScope(stat_prefix + "scoped_rds." + name + ".")), stats_({ALL_SCOPED_RDS_STATS(POOL_COUNTER(*scope_), POOL_GAUGE(*scope_))}), + resource_type_helper_(factory_context.messageValidationContext().dynamicValidationVisitor(), + "name"), rds_config_source_(std::move(rds_config_source)), stat_prefix_(stat_prefix), route_config_provider_manager_(route_config_provider_manager) { - const auto resource_name = getResourceName(); + const auto resource_name = resource_type_helper_.getResourceName(); if (scoped_rds.srds_resources_locator().empty()) { subscription_ = THROW_OR_RETURN_VALUE( factory_context.clusterManager().subscriptionFactory().subscriptionFromConfigSource( scoped_rds.scoped_rds_config_source(), Grpc::Common::typeUrl(resource_name), *scope_, - *this, resource_decoder_, {}), + *this, resource_type_helper_.resourceDecoder(), {}), Envoy::Config::SubscriptionPtr); } else { const auto srds_resources_locator = THROW_OR_RETURN_VALUE( @@ -159,7 +159,7 @@ ScopedRdsConfigSubscription::ScopedRdsConfigSubscription( subscription_ = THROW_OR_RETURN_VALUE( factory_context.clusterManager().subscriptionFactory().collectionSubscriptionFromUrl( srds_resources_locator, scoped_rds.scoped_rds_config_source(), resource_name, *scope_, - *this, resource_decoder_), + *this, resource_type_helper_.resourceDecoder()), Envoy::Config::SubscriptionPtr); } diff --git a/source/common/router/scoped_rds.h b/source/common/router/scoped_rds.h index 11e2f0a8103b1..d3416eaeb3a22 100644 --- a/source/common/router/scoped_rds.h +++ b/source/common/router/scoped_rds.h @@ -15,7 +15,7 @@ #include "envoy/stats/scope.h" #include "source/common/config/config_provider_impl.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/init/manager_impl.h" #include "source/common/router/rds_impl.h" #include "source/common/router/scoped_config_impl.h" @@ -106,9 +106,8 @@ struct ScopedRdsStats { }; // A scoped RDS subscription to be used with the dynamic scoped RDS ConfigProvider. -class ScopedRdsConfigSubscription - : public Envoy::Config::DeltaConfigSubscriptionInstance, - public Envoy::Config::SubscriptionBase { +class ScopedRdsConfigSubscription : public Envoy::Config::DeltaConfigSubscriptionInstance, + public Envoy::Config::SubscriptionCallbacks { public: using ScopedRouteConfigurationMap = std::map; @@ -232,6 +231,8 @@ class ScopedRdsConfigSubscription Stats::ScopeSharedPtr scope_; ScopedRdsStats stats_; Envoy::Config::SubscriptionPtr subscription_; + const Envoy::Config::ResourceTypeHelper + resource_type_helper_; const envoy::config::core::v3::ConfigSource rds_config_source_; const std::string stat_prefix_; RouteConfigProviderManager& route_config_provider_manager_; diff --git a/source/common/router/vhds.cc b/source/common/router/vhds.cc index 526c0256f0df8..c8b1848ed1395 100644 --- a/source/common/router/vhds.cc +++ b/source/common/router/vhds.cc @@ -66,9 +66,7 @@ VhdsSubscription::VhdsSubscription(RouteConfigUpdatePtr& config_update_info, const std::string& stat_prefix, Rds::RouteConfigProvider* route_config_provider, absl::Status& status) - : Envoy::Config::SubscriptionBase( - factory_context.messageValidationContext().dynamicValidationVisitor(), "name"), - config_update_info_(config_update_info), + : config_update_info_(config_update_info), scope_(factory_context.scope().createScope( stat_prefix + "vhds." + config_update_info_->protobufConfigurationCast().name() + ".")), stats_({ALL_VHDS_STATS(POOL_COUNTER(*scope_))}), @@ -78,14 +76,17 @@ VhdsSubscription::VhdsSubscription(RouteConfigUpdatePtr& config_update_info, subscription_->start( {config_update_info_->protobufConfigurationCast().name()}); }), + resource_type_helper_(factory_context.messageValidationContext().dynamicValidationVisitor(), + "name"), route_config_provider_(route_config_provider) { - const auto resource_name = getResourceName(); + const auto resource_name = resource_type_helper_.getResourceName(); Envoy::Config::SubscriptionOptions options; options.use_namespace_matching_ = true; absl::StatusOr status_or = factory_context.clusterManager().subscriptionFactory().subscriptionFromConfigSource( config_update_info_->protobufConfigurationCast().vhds().config_source(), - Grpc::Common::typeUrl(resource_name), *scope_, *this, resource_decoder_, options); + Grpc::Common::typeUrl(resource_name), *scope_, *this, + resource_type_helper_.resourceDecoder(), options); SET_AND_RETURN_IF_NOT_OK(status_or.status(), status); subscription_ = std::move(status_or.value()); } diff --git a/source/common/router/vhds.h b/source/common/router/vhds.h index 1167a779b64be..2682ff7e1c550 100644 --- a/source/common/router/vhds.h +++ b/source/common/router/vhds.h @@ -19,7 +19,7 @@ #include "envoy/thread_local/thread_local.h" #include "source/common/common/logger.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/init/target_impl.h" #include "source/common/protobuf/utility.h" @@ -36,7 +36,7 @@ struct VhdsStats { ALL_VHDS_STATS(GENERATE_COUNTER_STRUCT) }; -class VhdsSubscription : Envoy::Config::SubscriptionBase, +class VhdsSubscription : public Envoy::Config::SubscriptionCallbacks, Logger::Loggable { public: static absl::StatusOr> @@ -80,6 +80,9 @@ class VhdsSubscription : Envoy::Config::SubscriptionBase + resource_type_helper_; + Rds::RouteConfigProvider* route_config_provider_; }; diff --git a/source/common/runtime/BUILD b/source/common/runtime/BUILD index 88eb3e17c32c4..496d596770ecc 100644 --- a/source/common/runtime/BUILD +++ b/source/common/runtime/BUILD @@ -91,7 +91,7 @@ envoy_cc_library( "//source/common/common:thread_lib", "//source/common/common:utility_lib", "//source/common/config:api_version_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/filesystem:directory_lib", "//source/common/grpc:common_lib", "//source/common/http:utility_lib", diff --git a/source/common/runtime/runtime_impl.cc b/source/common/runtime/runtime_impl.cc index fcabbe0070d8a..a3c1ce1927713 100644 --- a/source/common/runtime/runtime_impl.cc +++ b/source/common/runtime/runtime_impl.cc @@ -555,17 +555,16 @@ void LoaderImpl::onRtdsReady() { RtdsSubscription::RtdsSubscription( LoaderImpl& parent, const envoy::config::bootstrap::v3::RuntimeLayer::RtdsLayer& rtds_layer, Stats::Store& store, ProtobufMessage::ValidationVisitor& validation_visitor) - : Envoy::Config::SubscriptionBase(validation_visitor, - "name"), - parent_(parent), config_source_(rtds_layer.rtds_config()), store_(store), + : parent_(parent), config_source_(rtds_layer.rtds_config()), store_(store), stats_scope_(store_.createScope("runtime")), resource_name_(rtds_layer.name()), - init_target_("RTDS " + resource_name_, [this]() { start(); }) {} + init_target_("RTDS " + resource_name_, [this]() { start(); }), + resource_type_helper_(validation_visitor, "name") {} absl::Status RtdsSubscription::createSubscription() { - const auto resource_name = getResourceName(); + const auto resource_name = resource_type_helper_.getResourceName(); auto subscription_or_error = parent_.cm_->subscriptionFactory().subscriptionFromConfigSource( - config_source_, Grpc::Common::typeUrl(resource_name), *stats_scope_, *this, resource_decoder_, - {}); + config_source_, Grpc::Common::typeUrl(resource_name), *stats_scope_, *this, + resource_type_helper_.resourceDecoder(), {}); RETURN_IF_NOT_OK(subscription_or_error.status()); subscription_ = std::move(*subscription_or_error); return absl::OkStatus(); diff --git a/source/common/runtime/runtime_impl.h b/source/common/runtime/runtime_impl.h index 37b43d962bcd3..0e66169ff409a 100644 --- a/source/common/runtime/runtime_impl.h +++ b/source/common/runtime/runtime_impl.h @@ -24,7 +24,7 @@ #include "source/common/common/assert.h" #include "source/common/common/logger.h" #include "source/common/common/thread.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/init/manager_impl.h" #include "source/common/init/target_impl.h" #include "source/common/singleton/threadsafe_singleton.h" @@ -172,7 +172,7 @@ class ProtoLayer : public OverrideLayerImpl, Logger::Loggable, +struct RtdsSubscription : public Config::SubscriptionCallbacks, Logger::Loggable { RtdsSubscription(LoaderImpl& parent, const envoy::config::bootstrap::v3::RuntimeLayer::RtdsLayer& rtds_layer, @@ -200,6 +200,7 @@ struct RtdsSubscription : Envoy::Config::SubscriptionBase resource_type_helper_; Protobuf::Struct proto_; }; diff --git a/source/common/secret/BUILD b/source/common/secret/BUILD index d6e61c39531f9..4f7d45c20c6ab 100644 --- a/source/common/secret/BUILD +++ b/source/common/secret/BUILD @@ -58,7 +58,7 @@ envoy_cc_library( "//source/common/common:callback_impl_lib", "//source/common/common:cleanup_lib", "//source/common/config:api_version_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/config:utility_lib", "//source/common/config:watched_directory_lib", "//source/common/init:target_lib", diff --git a/source/common/secret/sds_api.cc b/source/common/secret/sds_api.cc index 0c88bd4761b33..d7e37e5c87238 100644 --- a/source/common/secret/sds_api.cc +++ b/source/common/secret/sds_api.cc @@ -21,21 +21,21 @@ SdsApi::SdsApi(envoy::config::core::v3::ConfigSource sds_config, absl::string_vi ProtobufMessage::ValidationVisitor& validation_visitor, Stats::Store& stats, std::function destructor_cb, Event::Dispatcher& dispatcher, Api::Api& api, bool warm) - : Envoy::Config::SubscriptionBase( - validation_visitor, "name"), - init_target_(fmt::format("SdsApi {}", sds_config_name), [this, warm] { initialize(warm); }), + : init_target_(fmt::format("SdsApi {}", sds_config_name), [this, warm] { initialize(warm); }), dispatcher_(dispatcher), api_(api), scope_(stats.createScope(absl::StrCat("sds.", sds_config_name, "."))), - sds_api_stats_(generateStats(*scope_)), sds_config_(std::move(sds_config)), - sds_config_name_(sds_config_name), clean_up_(std::move(destructor_cb)), - subscription_factory_(subscription_factory), time_source_(time_source), + sds_api_stats_(generateStats(*scope_)), resource_type_helper_(validation_visitor, "name"), + sds_config_(std::move(sds_config)), sds_config_name_(sds_config_name), + clean_up_(std::move(destructor_cb)), subscription_factory_(subscription_factory), + time_source_(time_source), secret_data_{sds_config_name_, "uninitialized", time_source_.systemTime()} { - const auto resource_name = getResourceName(); + const auto resource_name = resource_type_helper_.getResourceName(); // This has to happen here (rather than in initialize()) as it can throw exceptions. - subscription_ = THROW_OR_RETURN_VALUE( - subscription_factory_.subscriptionFromConfigSource( - sds_config_, Grpc::Common::typeUrl(resource_name), *scope_, *this, resource_decoder_, {}), - Config::SubscriptionPtr); + subscription_ = + THROW_OR_RETURN_VALUE(subscription_factory_.subscriptionFromConfigSource( + sds_config_, Grpc::Common::typeUrl(resource_name), *scope_, *this, + resource_type_helper_.resourceDecoder(), {}), + Config::SubscriptionPtr); } void SdsApi::resolveDataSource(const FileContentMap& files, diff --git a/source/common/secret/sds_api.h b/source/common/secret/sds_api.h index 355e3a9a04871..9b7eb849a2728 100644 --- a/source/common/secret/sds_api.h +++ b/source/common/secret/sds_api.h @@ -21,7 +21,7 @@ #include "source/common/common/callback_impl.h" #include "source/common/common/cleanup.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/config/utility.h" #include "source/common/config/watched_directory.h" #include "source/common/init/target_impl.h" @@ -46,8 +46,7 @@ struct SdsApiStats { /** * SDS API implementation that fetches secrets from SDS server via Subscription. */ -class SdsApi : public Envoy::Config::SubscriptionBase< - envoy::extensions::transport_sockets::tls::v3::Secret> { +class SdsApi : public Config::SubscriptionCallbacks { public: struct SecretData { const std::string resource_name_; @@ -108,6 +107,8 @@ class SdsApi : public Envoy::Config::SubscriptionBase< Stats::ScopeSharedPtr scope_; SdsApiStats sds_api_stats_; + const Config::ResourceTypeHelper + resource_type_helper_; const envoy::config::core::v3::ConfigSource sds_config_; Config::SubscriptionPtr subscription_; diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index 54026713392b6..4be665ba7f4e8 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -37,7 +37,7 @@ envoy_cc_library( "//envoy/stats:stats_interface", "//envoy/upstream:cluster_manager_interface", "//source/common/common:minimal_logger_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/grpc:common_lib", "//source/common/protobuf", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", @@ -56,7 +56,7 @@ envoy_cc_library( "//envoy/stats:stats_interface", "//envoy/upstream:cluster_manager_interface", "//source/common/common:minimal_logger_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/grpc:common_lib", "//source/common/protobuf", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", diff --git a/source/common/upstream/cds_api_impl.cc b/source/common/upstream/cds_api_impl.cc index 71eefecfd82f6..c9c6f443bdcc1 100644 --- a/source/common/upstream/cds_api_impl.cc +++ b/source/common/upstream/cds_api_impl.cc @@ -28,20 +28,21 @@ CdsApiImpl::CdsApiImpl(const envoy::config::core::v3::ConfigSource& cds_config, ProtobufMessage::ValidationVisitor& validation_visitor, Server::Configuration::ServerFactoryContext& factory_context, bool support_multi_ads_sources, absl::Status& creation_status) - : Envoy::Config::SubscriptionBase(validation_visitor, - "name"), - helper_(cm, factory_context.xdsManager(), "cds"), cm_(cm), + : helper_(cm, factory_context.xdsManager(), "cds"), + resource_type_helper_(validation_visitor, "name"), cm_(cm), scope_(scope.createScope("cluster_manager.cds.")), factory_context_(factory_context), stats_({ALL_CDS_STATS(POOL_COUNTER(*scope_), POOL_GAUGE(*scope_))}), support_multi_ads_sources_(support_multi_ads_sources) { - const auto resource_name = getResourceName(); + const auto resource_name = resource_type_helper_.getResourceName(); absl::StatusOr subscription_or_error; if (cds_resources_locator == nullptr) { subscription_or_error = cm_.subscriptionFactory().subscriptionFromConfigSource( - cds_config, Grpc::Common::typeUrl(resource_name), *scope_, *this, resource_decoder_, {}); + cds_config, Grpc::Common::typeUrl(resource_name), *scope_, *this, + resource_type_helper_.resourceDecoder(), {}); } else { subscription_or_error = cm.subscriptionFactory().collectionSubscriptionFromUrl( - *cds_resources_locator, cds_config, resource_name, *scope_, *this, resource_decoder_); + *cds_resources_locator, cds_config, resource_name, *scope_, *this, + resource_type_helper_.resourceDecoder()); } SET_AND_RETURN_IF_NOT_OK(subscription_or_error.status(), creation_status); subscription_ = std::move(*subscription_or_error); diff --git a/source/common/upstream/cds_api_impl.h b/source/common/upstream/cds_api_impl.h index d6614f012e2e4..a2780ce926bf3 100644 --- a/source/common/upstream/cds_api_impl.h +++ b/source/common/upstream/cds_api_impl.h @@ -13,7 +13,7 @@ #include "envoy/stats/scope.h" #include "envoy/upstream/cluster_manager.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/protobuf/protobuf.h" #include "source/common/upstream/cds_api_helper.h" @@ -32,8 +32,7 @@ struct CdsStats { * CDS API implementation that fetches via Subscription. * This supports the wildcard subscription to a single source. */ -class CdsApiImpl : public CdsApi, - Envoy::Config::SubscriptionBase { +class CdsApiImpl : public CdsApi, public Config::SubscriptionCallbacks { public: static absl::StatusOr create(const envoy::config::core::v3::ConfigSource& cds_config, @@ -66,6 +65,7 @@ class CdsApiImpl : public CdsApi, void runInitializeCallbackIfAny(); CdsApiHelper helper_; + const Config::ResourceTypeHelper resource_type_helper_; ClusterManager& cm_; Stats::ScopeSharedPtr scope_; Server::Configuration::ServerFactoryContext& factory_context_; diff --git a/source/common/upstream/od_cds_api_impl.cc b/source/common/upstream/od_cds_api_impl.cc index 54df8c74409cb..c0fc3faf37877 100644 --- a/source/common/upstream/od_cds_api_impl.cc +++ b/source/common/upstream/od_cds_api_impl.cc @@ -30,20 +30,21 @@ OdCdsApiImpl::OdCdsApiImpl(const envoy::config::core::v3::ConfigSource& odcds_co MissingClusterNotifier& notifier, Stats::Scope& scope, ProtobufMessage::ValidationVisitor& validation_visitor, absl::Status& creation_status) - : Envoy::Config::SubscriptionBase(validation_visitor, - "name"), - helper_(cm, xds_manager, "odcds"), notifier_(notifier), - scope_(scope.createScope("cluster_manager.odcds.")) { + : helper_(cm, xds_manager, "odcds"), notifier_(notifier), + scope_(scope.createScope("cluster_manager.odcds.")), + resource_type_helper_(validation_visitor, "name") { // TODO(krnowak): Move the subscription setup to CdsApiHelper. Maybe make CdsApiHelper a base // class for CDS and ODCDS. - const auto resource_name = getResourceName(); + const auto resource_name = resource_type_helper_.getResourceName(); absl::StatusOr subscription_or_error; if (!odcds_resources_locator.has_value()) { subscription_or_error = cm.subscriptionFactory().subscriptionFromConfigSource( - odcds_config, Grpc::Common::typeUrl(resource_name), *scope_, *this, resource_decoder_, {}); + odcds_config, Grpc::Common::typeUrl(resource_name), *scope_, *this, + resource_type_helper_.resourceDecoder(), {}); } else { subscription_or_error = cm.subscriptionFactory().collectionSubscriptionFromUrl( - *odcds_resources_locator, odcds_config, resource_name, *scope_, *this, resource_decoder_); + *odcds_resources_locator, odcds_config, resource_name, *scope_, *this, + resource_type_helper_.resourceDecoder()); } SET_AND_RETURN_IF_NOT_OK(subscription_or_error.status(), creation_status); subscription_ = std::move(*subscription_or_error); @@ -196,16 +197,15 @@ class XdstpOdCdsApiImpl::XdstpOdcdsSubscriptionsManager : public Singleton::Inst private: // A singleton subscription handler. - class PerSubscriptionData : Envoy::Config::SubscriptionBase { + class PerSubscriptionData : public Config::SubscriptionCallbacks { public: PerSubscriptionData(XdstpOdcdsSubscriptionsManager& parent, absl::string_view resource_name, ProtobufMessage::ValidationVisitor& validation_visitor) - : Envoy::Config::SubscriptionBase(validation_visitor, - "name"), - parent_(parent), resource_name_(resource_name) {} + : parent_(parent), resource_name_(resource_name), + resource_type_helper_(validation_visitor, "name") {} absl::Status initializeSubscription(bool old_ads) { - const auto resource_type = getResourceName(); + const auto resource_type = resource_type_helper_.getResourceName(); // If old_ads is set, creates a subscription using the staticAdsConfigSource. // Otherwise, the subscribeToSingletonResource will take care of // subscription via the ADS source. @@ -215,7 +215,8 @@ class XdstpOdCdsApiImpl::XdstpOdcdsSubscriptionsManager : public Singleton::Inst old_ads ? makeOptRef(staticAdsConfigSource()) : absl::nullopt, - Grpc::Common::typeUrl(resource_type), *parent_.scope_, *this, resource_decoder_, {}); + Grpc::Common::typeUrl(resource_type), *parent_.scope_, *this, + resource_type_helper_.resourceDecoder(), {}); RETURN_IF_NOT_OK_REF(subscription_or_error.status()); subscription_ = std::move(subscription_or_error.value()); subscription_->start({resource_name_}); @@ -283,6 +284,7 @@ class XdstpOdCdsApiImpl::XdstpOdcdsSubscriptionsManager : public Singleton::Inst // TODO(adisuissa): this can be converted to an absl::string_view and point to the // subscriptions_ map key. const std::string resource_name_; + const Config::ResourceTypeHelper resource_type_helper_; Config::SubscriptionPtr subscription_; bool resource_was_updated_{false}; }; diff --git a/source/common/upstream/od_cds_api_impl.h b/source/common/upstream/od_cds_api_impl.h index 577d37be06a0b..043669f1b6dfb 100644 --- a/source/common/upstream/od_cds_api_impl.h +++ b/source/common/upstream/od_cds_api_impl.h @@ -12,7 +12,7 @@ #include "envoy/stats/scope.h" #include "envoy/upstream/cluster_manager.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/protobuf/protobuf.h" #include "source/common/upstream/cds_api_helper.h" @@ -32,7 +32,7 @@ enum class StartStatus { * ODCDS API implementation that fetches via Subscription. */ class OdCdsApiImpl : public OdCdsApi, - Envoy::Config::SubscriptionBase, + public Config::SubscriptionCallbacks, Logger::Loggable { public: static absl::StatusOr @@ -68,6 +68,7 @@ class OdCdsApiImpl : public OdCdsApi, Stats::ScopeSharedPtr scope_; StartStatus status_{StartStatus::NotStarted}; absl::flat_hash_set awaiting_names_; + const Config::ResourceTypeHelper resource_type_helper_; Config::SubscriptionPtr subscription_; }; diff --git a/source/extensions/access_loggers/filters/process_ratelimit/BUILD b/source/extensions/access_loggers/filters/process_ratelimit/BUILD index b42ce86034ad2..091f0be55fa4e 100644 --- a/source/extensions/access_loggers/filters/process_ratelimit/BUILD +++ b/source/extensions/access_loggers/filters/process_ratelimit/BUILD @@ -20,7 +20,7 @@ envoy_cc_library( "//envoy/server:factory_context_interface", "//source/common/common:thread_synchronizer_lib", "//source/common/common:token_bucket_impl_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/grpc:common_lib", "//source/common/init:target_lib", "//source/extensions/filters/common/local_ratelimit:local_ratelimit_lib", diff --git a/source/extensions/access_loggers/filters/process_ratelimit/provider_singleton.cc b/source/extensions/access_loggers/filters/process_ratelimit/provider_singleton.cc index add6d1067a102..c3a67c9c6b1d5 100644 --- a/source/extensions/access_loggers/filters/process_ratelimit/provider_singleton.cc +++ b/source/extensions/access_loggers/filters/process_ratelimit/provider_singleton.cc @@ -114,14 +114,15 @@ RateLimiterProviderSingleton::TokenBucketSubscription::getLimiter() { RateLimiterProviderSingleton::TokenBucketSubscription::TokenBucketSubscription( RateLimiterProviderSingleton& parent, absl::string_view resource_name) - : Config::SubscriptionBase( - parent.factory_context_.messageValidationVisitor(), ""), - parent_(parent), resource_name_(resource_name), token_bucket_config_hash_(0) { - subscription_ = THROW_OR_RETURN_VALUE( - parent.factory_context_.xdsManager().subscribeToSingletonResource( - resource_name, parent.config_source_, Grpc::Common::typeUrl(getResourceName()), - *parent.scope_, *this, resource_decoder_, {}), - Config::SubscriptionPtr); + : parent_(parent), resource_name_(resource_name), + resource_type_helper_(parent.factory_context_.messageValidationVisitor(), ""), + token_bucket_config_hash_(0) { + subscription_ = + THROW_OR_RETURN_VALUE(parent.factory_context_.xdsManager().subscribeToSingletonResource( + resource_name, parent.config_source_, + Grpc::Common::typeUrl(resource_type_helper_.getResourceName()), + *parent.scope_, *this, resource_type_helper_.resourceDecoder(), {}), + Config::SubscriptionPtr); subscription_->start({resource_name_}); } diff --git a/source/extensions/access_loggers/filters/process_ratelimit/provider_singleton.h b/source/extensions/access_loggers/filters/process_ratelimit/provider_singleton.h index 78f720499ced0..8888dd3985a88 100644 --- a/source/extensions/access_loggers/filters/process_ratelimit/provider_singleton.h +++ b/source/extensions/access_loggers/filters/process_ratelimit/provider_singleton.h @@ -8,7 +8,7 @@ #include "envoy/type/v3/token_bucket.pb.h" #include "envoy/type/v3/token_bucket.pb.validate.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/init/target_impl.h" #include "source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h" @@ -126,7 +126,7 @@ class RateLimiterProviderSingleton : public Singleton::Instance { scope_(factory_context.scope().createScope("local_ratelimit_discovery")), fallback_always_deny_limiter_(std::make_shared()) {} - class TokenBucketSubscription : Config::SubscriptionBase { + class TokenBucketSubscription : public Config::SubscriptionCallbacks { public: explicit TokenBucketSubscription(RateLimiterProviderSingleton& parent, absl::string_view resource_name); @@ -159,6 +159,7 @@ class RateLimiterProviderSingleton : public Singleton::Instance { absl::flat_hash_map setters_; absl::optional config_; std::weak_ptr limiter_; + const Config::ResourceTypeHelper resource_type_helper_; size_t token_bucket_config_hash_; }; diff --git a/source/extensions/clusters/eds/BUILD b/source/extensions/clusters/eds/BUILD index b7b2984fb8ecc..ccad5ffa02bab 100644 --- a/source/extensions/clusters/eds/BUILD +++ b/source/extensions/clusters/eds/BUILD @@ -27,7 +27,7 @@ envoy_cc_extension( "//source/common/config:api_version_lib", "//source/common/config:decoded_resource_lib", "//source/common/config:metadata_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/config:subscription_factory_lib", "//source/common/config:utility_lib", "//source/common/network:address_lib", @@ -53,7 +53,7 @@ envoy_cc_library( "//envoy/config:subscription_interface", "//envoy/local_info:local_info_interface", "//source/common/config:decoded_resource_lib", - "//source/common/config:subscription_base_interface", + "//source/common/config:resource_type_helper_lib", "//source/common/config:subscription_factory_lib", "//source/common/config:utility_lib", "//source/common/network:address_lib", diff --git a/source/extensions/clusters/eds/eds.cc b/source/extensions/clusters/eds/eds.cc index 436b4cb024210..89eee3887b3a9 100644 --- a/source/extensions/clusters/eds/eds.cc +++ b/source/extensions/clusters/eds/eds.cc @@ -29,8 +29,7 @@ EdsClusterImpl::EdsClusterImpl(const envoy::config::cluster::v3::Cluster& cluste ClusterFactoryContext& cluster_context, absl::Status& creation_status) : BaseDynamicClusterImpl(cluster, cluster_context, creation_status), - Envoy::Config::SubscriptionBase( - cluster_context.messageValidationVisitor(), "cluster_name"), + resource_type_helper_(cluster_context.messageValidationVisitor(), "cluster_name"), local_info_(cluster_context.serverFactoryContext().localInfo()), eds_resources_cache_( cluster_context.serverFactoryContext().clusterManager().edsResourcesCache()) { @@ -44,13 +43,13 @@ EdsClusterImpl::EdsClusterImpl(const envoy::config::cluster::v3::Cluster& cluste } else { initialize_phase_ = InitializePhase::Secondary; } - const auto resource_name = getResourceName(); + const auto resource_name = resource_type_helper_.getResourceName(); if (Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.xdstp_based_config_singleton_subscriptions")) { subscription_ = THROW_OR_RETURN_VALUE( cluster_context.serverFactoryContext().xdsManager().subscribeToSingletonResource( edsServiceName(), eds_config, Grpc::Common::typeUrl(resource_name), info_->statsScope(), - *this, resource_decoder_, {}), + *this, resource_type_helper_.resourceDecoder(), {}), Config::SubscriptionPtr); } else { subscription_ = THROW_OR_RETURN_VALUE( @@ -58,7 +57,8 @@ EdsClusterImpl::EdsClusterImpl(const envoy::config::cluster::v3::Cluster& cluste .clusterManager() .subscriptionFactory() .subscriptionFromConfigSource(eds_config, Grpc::Common::typeUrl(resource_name), - info_->statsScope(), *this, resource_decoder_, {}), + info_->statsScope(), *this, + resource_type_helper_.resourceDecoder(), {}), Config::SubscriptionPtr); } } diff --git a/source/extensions/clusters/eds/eds.h b/source/extensions/clusters/eds/eds.h index a693c938d3385..f067aa93b70fd 100644 --- a/source/extensions/clusters/eds/eds.h +++ b/source/extensions/clusters/eds/eds.h @@ -17,7 +17,7 @@ #include "envoy/stats/scope.h" #include "envoy/upstream/locality.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/upstream/cluster_factory_impl.h" #include "source/common/upstream/upstream_impl.h" #include "source/extensions/clusters/eds/leds.h" @@ -28,10 +28,9 @@ namespace Upstream { /** * Cluster implementation that reads host information from the Endpoint Discovery Service. */ -class EdsClusterImpl - : public BaseDynamicClusterImpl, - Envoy::Config::SubscriptionBase, - private Config::EdsResourceRemovalCallback { +class EdsClusterImpl : public BaseDynamicClusterImpl, + public Config::SubscriptionCallbacks, + private Config::EdsResourceRemovalCallback { public: static absl::StatusOr> create(const envoy::config::cluster::v3::Cluster& cluster, @@ -104,6 +103,8 @@ class EdsClusterImpl const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment_; }; + const Config::ResourceTypeHelper + resource_type_helper_; Config::SubscriptionPtr subscription_; const LocalInfo::LocalInfo& local_info_; std::vector locality_weights_map_; diff --git a/source/extensions/clusters/eds/leds.cc b/source/extensions/clusters/eds/leds.cc index 2bbeafb0eb536..ad000e0775ff7 100644 --- a/source/extensions/clusters/eds/leds.cc +++ b/source/extensions/clusters/eds/leds.cc @@ -15,22 +15,23 @@ LedsSubscription::LedsSubscription( const std::string& cluster_name, Server::Configuration::TransportSocketFactoryContext& factory_context, Stats::Scope& cluster_stats_scope, const UpdateCb& callback) - : Envoy::Config::SubscriptionBase( - factory_context.messageValidationVisitor(), leds_config.leds_collection_name()), - local_info_(factory_context.serverFactoryContext().localInfo()), cluster_name_(cluster_name), + : local_info_(factory_context.serverFactoryContext().localInfo()), cluster_name_(cluster_name), stats_scope_(cluster_stats_scope.createScope("leds.")), - stats_({ALL_LEDS_STATS(POOL_COUNTER(*stats_scope_))}), callback_(callback) { + stats_({ALL_LEDS_STATS(POOL_COUNTER(*stats_scope_))}), callback_(callback), + resource_type_helper_(factory_context.messageValidationVisitor(), + leds_config.leds_collection_name()) { const xds::core::v3::ResourceLocator leds_resource_locator = THROW_OR_RETURN_VALUE( Config::XdsResourceIdentifier::decodeUrl(leds_config.leds_collection_name()), xds::core::v3::ResourceLocator); - const auto resource_name = getResourceName(); - subscription_ = THROW_OR_RETURN_VALUE( - factory_context.serverFactoryContext() - .clusterManager() - .subscriptionFactory() - .collectionSubscriptionFromUrl(leds_resource_locator, leds_config.leds_config(), - resource_name, *stats_scope_, *this, resource_decoder_), - Config::SubscriptionPtr); + const auto resource_name = resource_type_helper_.getResourceName(); + subscription_ = + THROW_OR_RETURN_VALUE(factory_context.serverFactoryContext() + .clusterManager() + .subscriptionFactory() + .collectionSubscriptionFromUrl( + leds_resource_locator, leds_config.leds_config(), resource_name, + *stats_scope_, *this, resource_type_helper_.resourceDecoder()), + Config::SubscriptionPtr); subscription_->start({}); } diff --git a/source/extensions/clusters/eds/leds.h b/source/extensions/clusters/eds/leds.h index dab7a5bff3899..3c8e636e989e8 100644 --- a/source/extensions/clusters/eds/leds.h +++ b/source/extensions/clusters/eds/leds.h @@ -9,7 +9,7 @@ #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" -#include "source/common/config/subscription_base.h" +#include "source/common/config/resource_type_helper.h" #include "source/common/upstream/upstream_impl.h" namespace Envoy { @@ -34,9 +34,8 @@ struct LedsStats { * Multiple subscriptions with the same LEDS collection name can use a single * subscription. */ -class LedsSubscription - : private Envoy::Config::SubscriptionBase, - private Logger::Loggable { +class LedsSubscription : public Config::SubscriptionCallbacks, + private Logger::Loggable { public: using UpdateCb = std::function; using LbEndpointsMap = absl::flat_hash_map; @@ -76,6 +75,7 @@ class LedsSubscription const UpdateCb callback_; // Once the endpoints of the locality are updated, it is considered active. bool initial_update_attempt_complete_{false}; + const Config::ResourceTypeHelper resource_type_helper_; Config::SubscriptionPtr subscription_; }; diff --git a/test/common/config/BUILD b/test/common/config/BUILD index 827bdff5caf40..f2ca47573c0fd 100644 --- a/test/common/config/BUILD +++ b/test/common/config/BUILD @@ -290,3 +290,14 @@ envoy_cc_test( "//test/test_common:test_runtime_lib", ], ) + +envoy_cc_test( + name = "resource_type_helper_test", + srcs = ["resource_type_helper_test.cc"], + deps = [ + "//source/common/config:resource_type_helper_lib", + "//source/common/protobuf:message_validator_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + ], +) diff --git a/test/common/config/resource_type_helper_test.cc b/test/common/config/resource_type_helper_test.cc new file mode 100644 index 0000000000000..09ee53ccdea5c --- /dev/null +++ b/test/common/config/resource_type_helper_test.cc @@ -0,0 +1,23 @@ +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/config/cluster/v3/cluster.pb.validate.h" + +#include "source/common/config/resource_type_helper.h" +#include "source/common/protobuf/message_validator_impl.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Config { +namespace { + +TEST(ResourceTypeHelperTest, GetResourceName) { + ProtobufMessage::NullValidationVisitorImpl validation_visitor; + ResourceTypeHelper helper(validation_visitor, "name"); + EXPECT_EQ("envoy.config.cluster.v3.Cluster", helper.getResourceName()); + EXPECT_NE(nullptr, helper.resourceDecoder()); +} + +} // namespace +} // namespace Config +} // namespace Envoy From 3b991df30ff7790f8666564bd1b5cfa7fd56b608 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 30 Apr 2026 09:45:53 +0100 Subject: [PATCH 169/750] Revert "bazel: Use bazel mirrors where available (#44729)" (#44759) This reverts commit 329bb772300a3aa1113e1fb74a67010817f7e582. This does not work in the same way when using remote downloader and breaks it currently Signed-off-by: Ryan Northey --- api/bazel/repository_locations.bzl | 5 +---- bazel/repository_locations.bzl | 20 ++++---------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 5210172922e01..29910ab6953fd 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -3,10 +3,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( bazel_skylib = dict( version = "1.9.0", sha256 = "3b5b49006181f5f8ff626ef8ddceaa95e9bb8ad294f7b5d7b11ea9f7ddaf8c59", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/{version}/bazel-skylib-{version}.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/releases/download/{version}/bazel-skylib-{version}.tar.gz", - ], + urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/{version}/bazel-skylib-{version}.tar.gz"], ), com_envoyproxy_protoc_gen_validate = dict( sha256 = "20da84f8efe8f53c10cbde79d2cdd1e63365b0ae1ad9de22af56aa1a49d59330", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index a3ea4961962fa..99ce9a21accd6 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -32,10 +32,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( bazel_gazelle = dict( version = "0.47.0", sha256 = "675114d8b433d0a9f54d81171833be96ebc4113115664b791e6f204d58e93446", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v{version}/bazel-gazelle-v{version}.tar.gz", - "https://github.com/bazelbuild/bazel-gazelle/releases/download/v{version}/bazel-gazelle-v{version}.tar.gz", - ], + urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/v{version}/bazel-gazelle-v{version}.tar.gz"], ), build_bazel_rules_apple = dict( version = "3.20.1", @@ -455,10 +452,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( io_bazel_rules_go = dict( version = "0.60.0", sha256 = "86d3dc8f59d253524f933aaf2f3c05896cb0b605fc35b460c0b4b039996124c6", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v{version}/rules_go-v{version}.zip", - "https://github.com/bazelbuild/rules_go/releases/download/v{version}/rules_go-v{version}.zip", - ], + urls = ["https://github.com/bazelbuild/rules_go/releases/download/v{version}/rules_go-v{version}.zip"], ), rules_cc = dict( version = "0.2.17", @@ -500,10 +494,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( version = "0.7.1", sha256 = "3709d1745ba4be4ef054449647b62e424267066eca887bb00dd29242cb8463a0", strip_prefix = "rules_shell-{version}", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/rules_shell/releases/download/v{version}/rules_shell-v{version}.tar.gz", - "https://github.com/bazelbuild/rules_shell/releases/download/v{version}/rules_shell-v{version}.tar.gz", - ], + urls = ["https://github.com/bazelbuild/rules_shell/releases/download/v{version}/rules_shell-v{version}.tar.gz"], ), wamr = dict( version = "WAMR-2.4.4", @@ -677,10 +668,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( rules_license = dict( version = "1.0.0", sha256 = "26d4021f6898e23b82ef953078389dd49ac2b5618ac564ade4ef87cced147b38", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/rules_license/releases/download/{version}/rules_license-{version}.tar.gz", - "https://github.com/bazelbuild/rules_license/releases/download/{version}/rules_license-{version}.tar.gz", - ], + urls = ["https://github.com/bazelbuild/rules_license/releases/download/{version}/rules_license-{version}.tar.gz"], ), libmaxminddb = dict( version = "1.13.3", From 9c24834bf2f1c170a37a74ff5fa5e272edd09b0a Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 30 Apr 2026 10:09:27 +0100 Subject: [PATCH 170/750] ci/bazel: Use remote downloader for RBE (#44760) Signed-off-by: Ryan Northey --- .bazelrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.bazelrc b/.bazelrc index 0340779e1ce24..bc9a853deecc1 100644 --- a/.bazelrc +++ b/.bazelrc @@ -448,6 +448,7 @@ common:remote-cache --remote_cache=grpcs://mordenite.cluster.engflow.com common:remote-cache --remote_timeout=3600s common:remote-exec --remote_executor=grpcs://mordenite.cluster.engflow.com +common:remote-exec --experimental_remote_downloader=grpcs://mordenite.cluster.engflow.com common:remote-exec --jobs=200 common:remote-exec --define=engflow_rbe=true From e5000d2a1d79b3afbafe4b5de4f6ecadda312b74 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 30 Apr 2026 10:42:57 +0100 Subject: [PATCH 171/750] mobile: Add test version linkstamp (#44762) fixes breakage from ff9e47a3ad74f6b4daf00bcdc502ad86200c3a10 Signed-off-by: Ryan Northey --- mobile/test/jni/BUILD | 2 ++ mobile/test/objective-c/BUILD | 2 ++ 2 files changed, 4 insertions(+) diff --git a/mobile/test/jni/BUILD b/mobile/test/jni/BUILD index 35d8af47050fa..67973dca522dd 100644 --- a/mobile/test/jni/BUILD +++ b/mobile/test/jni/BUILD @@ -160,6 +160,7 @@ cc_library( ], deps = [ "//library/jni:jni_helper_lib", + "@envoy//test/test_common:test_version_linkstamp", ], alwayslink = True, ) @@ -182,6 +183,7 @@ cc_library( deps = [ "//library/common/http:header_utility_lib", "//library/jni:jni_utility_lib", + "@envoy//test/test_common:test_version_linkstamp", "@envoy//test/test_common:utility_lib", ], alwayslink = True, diff --git a/mobile/test/objective-c/BUILD b/mobile/test/objective-c/BUILD index 2460ad8698cd9..3aa291e36cad6 100644 --- a/mobile/test/objective-c/BUILD +++ b/mobile/test/objective-c/BUILD @@ -14,6 +14,7 @@ envoy_mobile_objc_test( visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_objc_bridge_lib", + "@envoy//test/test_common:test_version_linkstamp", ], ) @@ -27,6 +28,7 @@ envoy_mobile_objc_test( deps = [ "//library/objective-c:envoy_key_value_store_bridge_impl_lib", "//library/objective-c:envoy_objc_bridge_lib", + "@envoy//test/test_common:test_version_linkstamp", ], ) From 555e8b641d4648e04a5f27f89c2ffacf76a6a110 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 30 Apr 2026 12:00:37 +0100 Subject: [PATCH 172/750] ci: Generalize branch regex in _request.yml to match patches/X.Y branches (#44764) Signed-off-by: Ryan Northey --- .github/workflows/_request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index 472f2403a8f07..a27043eb4e850 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -205,8 +205,8 @@ jobs: target-branch: ${{ fromJSON(steps.env.outputs.data).request.target-branch }} filter: | .["target-branch"] as $branch - | if ($branch | test("^release/v[0-9]+\\.[0-9]+$")) then - ($branch | sub("^release/v"; "") + ".0") as $version_str + | if ($branch | test("^(release/v|patches/)[0-9]+\\.[0-9]+$")) then + ($branch | sub("^(release/v|patches/)"; "") + ".0") as $version_str | ($version_str | utils::version) as $version | if ($version.major < 1 or ($version.major == 1 and $version.minor <= 37)) then .bazel["docs-x64"] = "skip" From 857282718a019ceb7b67da2f86252c89d215fd38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:35:58 +0100 Subject: [PATCH 173/750] build(deps): bump pyopenssl from 26.0.0 to 26.1.0 in /tools/base (#44672) Bumps [pyopenssl](https://github.com/pyca/pyopenssl) from 26.0.0 to 26.1.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.in | 2 +- tools/base/requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/base/requirements.in b/tools/base/requirements.in index fc63cbbde75d2..c65a504ae1242 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -34,7 +34,7 @@ ply # `MessageFactory class is deprecated. Please use GetMessageClass() instead of MessageFactory.GetPrototype` protobuf<7.35.0 pygithub -pyopenssl>=24.2.1 +pyopenssl>=26.1.0 pyreadline pyyaml setuptools diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index ac0baa30de4a6..63e41a4e3a41c 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -1287,9 +1287,9 @@ pynacl==1.6.2 \ --hash=sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6 \ --hash=sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465 # via pygithub -pyopenssl==26.0.0 \ - --hash=sha256:df94d28498848b98cc1c0ffb8ef1e71e40210d3b0a8064c9d29571ed2904bf81 \ - --hash=sha256:f293934e52936f2e3413b89c6ce36df66a0b34ae1ea3a053b8c5020ff2f513fc +pyopenssl==26.1.0 \ + --hash=sha256:115563879b2c8ccb207975705d3e491434d8c9d7c79667c902ecbf5f3bbd2ece \ + --hash=sha256:737f0a2275c5bc54f3b02137687e1a765931fb3949b9a92a825e4d33b9eec08b # via # -r requirements.in # aioquic From 9eed03025e678795edbaa719161cd64bdab40b85 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 30 Apr 2026 09:18:14 -0400 Subject: [PATCH 174/750] otel: add option to disable telemetry SDK resource attributes (#44689) Currently, the OpenTelemetry tracer always populates telemetry SDK attributes (`telemetry.sdk.language`, `telemetry.sdk.name`, `telemetry.sdk.version`). We have a customer-facing user interface for viewing traces and we don't want to leak internal Envoy details of our product. This PR introduces a new configuration option `set_telemetry_sdk_resource_attributes` (default true) in `OpenTelemetryConfig` to allow opting-out of populating these attributes. When set to false, the attributes listed above will be omitted from the resource spans. The new behavior (omitting attributes) is default off to preserve backward compatibility. Risk Level: Low Testing: - Unit tests in test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc have been updated and a new test added to validate that attributes are omitted when disabled. - Unit tests in test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc have been updated to verify the configuration flag is passed correctly. Docs Changes: None. Release Notes: Added a configuration option to disable populating telemetry SDK attributes in the OpenTelemetry tracer. Platform Specific Features: None. Runtime guard: None. [Disclosed usage of generative AI: Yes, used to assist in code modifications.] --------- Signed-off-by: Frank Fort --- api/envoy/config/trace/v3/opentelemetry.proto | 12 +++- .../stat_sinks/open_telemetry/config.cc | 3 +- .../opentelemetry_tracer_impl.cc | 9 ++- .../resource_detectors/resource_provider.cc | 18 ++--- .../resource_detectors/resource_provider.h | 8 +-- .../opentelemetry_tracer_impl_test.cc | 67 +++++++++++++++++- .../resource_provider_test.cc | 69 +++++++++++++------ 7 files changed, 146 insertions(+), 40 deletions(-) diff --git a/api/envoy/config/trace/v3/opentelemetry.proto b/api/envoy/config/trace/v3/opentelemetry.proto index 5a295d6f41b68..c20cf15196f22 100644 --- a/api/envoy/config/trace/v3/opentelemetry.proto +++ b/api/envoy/config/trace/v3/opentelemetry.proto @@ -21,7 +21,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Configuration for the OpenTelemetry tracer. // [#extension: envoy.tracers.opentelemetry] -// [#next-free-field: 7] +// [#next-free-field: 8] message OpenTelemetryConfig { // The upstream gRPC cluster that will receive OTLP traces. // Note that the tracer drops traces if the server does not read data fast enough. @@ -62,4 +62,14 @@ message OpenTelemetryConfig { // This field specifies the maximum number of spans that can be cached. If not specified, the // default is 1024. google.protobuf.UInt32Value max_cache_size = 6; + + // Specifies whether to set the telemetry SDK resource attributes. + // The following attributes will be set: + // + // - telemetry.sdk.language + // - telemetry.sdk.name + // - telemetry.sdk.version + // + // If not specified, the default is to set these attributes. + google.protobuf.BoolValue set_telemetry_sdk_resource_attributes = 7; } diff --git a/source/extensions/stat_sinks/open_telemetry/config.cc b/source/extensions/stat_sinks/open_telemetry/config.cc index 1ae89c68a14cf..528fae6c76408 100644 --- a/source/extensions/stat_sinks/open_telemetry/config.cc +++ b/source/extensions/stat_sinks/open_telemetry/config.cc @@ -25,7 +25,8 @@ OpenTelemetrySinkFactory::createStatsSink(const Protobuf::Message& config, auto otlp_options = std::make_shared( sink_config, resource_provider->getResource(sink_config.resource_detectors(), server, - /*service_name=*/""), + /*service_name=*/"", + /*set_telemetry_sdk_resource_attributes=*/true), server); std::shared_ptr otlp_metrics_flusher = std::make_shared(otlp_options); diff --git a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc index 9c901619d7bee..ed005bf32275a 100644 --- a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc +++ b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc @@ -76,10 +76,17 @@ Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetr POOL_COUNTER_PREFIX(context.serverFactoryContext().scope(), "tracing.opentelemetry"))} { auto& factory_context = context.serverFactoryContext(); + bool set_telemetry_sdk_resource_attributes = true; + if (opentelemetry_config.has_set_telemetry_sdk_resource_attributes()) { + set_telemetry_sdk_resource_attributes = + opentelemetry_config.set_telemetry_sdk_resource_attributes().value(); + } + Resource resource = resource_provider.getResource( opentelemetry_config.resource_detectors(), context.serverFactoryContext(), opentelemetry_config.service_name().empty() ? kDefaultServiceName - : opentelemetry_config.service_name()); + : opentelemetry_config.service_name(), + set_telemetry_sdk_resource_attributes); ResourceConstSharedPtr resource_ptr = std::make_shared(std::move(resource)); if (opentelemetry_config.has_grpc_service() && opentelemetry_config.has_http_service()) { diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc index b6e04cb1333fb..52496fa04a132 100644 --- a/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc +++ b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc @@ -14,18 +14,20 @@ namespace OpenTelemetry { namespace { bool isEmptyResource(const Resource& resource) { return resource.attributes_.empty(); } -Resource createInitialResource(absl::string_view service_name) { +Resource createInitialResource(absl::string_view service_name, + bool set_telemetry_sdk_resource_attributes) { Resource resource{}; // Creates initial resource with the static service.name and telemetry.sdk.* attributes. if (!service_name.empty()) { resource.attributes_[kServiceNameKey] = service_name; } - resource.attributes_[kTelemetrySdkLanguageKey] = kDefaultTelemetrySdkLanguage; - resource.attributes_[kTelemetrySdkNameKey] = kDefaultTelemetrySdkName; - - resource.attributes_[kTelemetrySdkVersionKey] = Envoy::VersionInfo::version(); + if (set_telemetry_sdk_resource_attributes) { + resource.attributes_[kTelemetrySdkLanguageKey] = kDefaultTelemetrySdkLanguage; + resource.attributes_[kTelemetrySdkNameKey] = kDefaultTelemetrySdkName; + resource.attributes_[kTelemetrySdkVersionKey] = Envoy::VersionInfo::version(); + } return resource; } @@ -85,10 +87,10 @@ void mergeResource(Resource& old_resource, const Resource& updating_resource) { Resource ResourceProviderImpl::getResource( const Protobuf::RepeatedPtrField& resource_detectors, - Envoy::Server::Configuration::ServerFactoryContext& context, - absl::string_view service_name) const { + Envoy::Server::Configuration::ServerFactoryContext& context, absl::string_view service_name, + bool set_telemetry_sdk_resource_attributes) const { - Resource resource = createInitialResource(service_name); + Resource resource = createInitialResource(service_name, set_telemetry_sdk_resource_attributes); for (const auto& detector_config : resource_detectors) { ResourceDetectorPtr detector; diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h index a9c56e18d6fbb..b25712c0ecf43 100644 --- a/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h +++ b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h @@ -38,8 +38,8 @@ class ResourceProvider : public Logger::Loggable { virtual Resource getResource(const Protobuf::RepeatedPtrField& resource_detectors, - Server::Configuration::ServerFactoryContext& context, - absl::string_view service_name) const PURE; + Server::Configuration::ServerFactoryContext& context, absl::string_view service_name, + bool set_telemetry_sdk_resource_attributes) const PURE; }; using ResourceProviderPtr = std::shared_ptr; @@ -48,8 +48,8 @@ class ResourceProviderImpl : public ResourceProvider { Resource getResource(const Protobuf::RepeatedPtrField& resource_detectors, - Server::Configuration::ServerFactoryContext& context, - absl::string_view service_name) const override; + Server::Configuration::ServerFactoryContext& context, absl::string_view service_name, + bool set_telemetry_sdk_resource_attributes) const override; }; } // namespace OpenTelemetry diff --git a/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc b/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc index 39b613b71f98d..441f603d08584 100644 --- a/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc +++ b/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc @@ -34,8 +34,8 @@ class MockResourceProvider : public ResourceProvider { MOCK_METHOD(Resource, getResource, (const Protobuf::RepeatedPtrField& resource_detectors, - Server::Configuration::ServerFactoryContext& context, - absl::string_view service_name), + Server::Configuration::ServerFactoryContext& context, absl::string_view service_name, + bool set_telemetry_sdk_resource_attributes), (const)); }; @@ -59,7 +59,7 @@ class OpenTelemetryDriverTest : public testing::Test { resource.attributes_.insert(std::pair("key1", "val1")); auto mock_resource_provider = NiceMock(); - EXPECT_CALL(mock_resource_provider, getResource(_, _, _)).WillRepeatedly(Return(resource)); + EXPECT_CALL(mock_resource_provider, getResource(_, _, _, _)).WillRepeatedly(Return(resource)); driver_ = std::make_unique(opentelemetry_config, context_, mock_resource_provider); } @@ -122,6 +122,67 @@ TEST_F(OpenTelemetryDriverTest, InitializeDriverValidConfigHttpExporter) { EXPECT_NE(driver_, nullptr); } +// Verifies that set_telemetry_sdk_resource_attributes=false is passed to ResourceProvider +TEST_F(OpenTelemetryDriverTest, PassSetTelemetrySdkResourceAttributesFalse) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + set_telemetry_sdk_resource_attributes: false + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + auto mock_client_factory = std::make_unique>(); + auto mock_client = std::make_unique>(); + mock_client_ = mock_client.get(); + ON_CALL(*mock_client_factory, createUncachedRawAsyncClient()) + .WillByDefault(Return(ByMove(std::move(mock_client)))); + auto& factory_context = context_.server_factory_context_; + ON_CALL(factory_context, runtime()).WillByDefault(ReturnRef(runtime_)); + ON_CALL(factory_context.cluster_manager_.async_client_manager_, factoryForGrpcService(_, _, _)) + .WillByDefault(Return(ByMove(std::move(mock_client_factory)))); + ON_CALL(factory_context, scope()).WillByDefault(ReturnRef(scope_)); + + Resource resource; + auto mock_resource_provider = NiceMock(); + + EXPECT_CALL(mock_resource_provider, getResource(_, _, _, false)).WillOnce(Return(resource)); + + driver_ = std::make_unique(opentelemetry_config, context_, mock_resource_provider); +} + +// Verifies that set_telemetry_sdk_resource_attributes defaults to true +TEST_F(OpenTelemetryDriverTest, PassSetTelemetrySdkResourceAttributesDefaultTrue) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + auto mock_client_factory = std::make_unique>(); + auto mock_client = std::make_unique>(); + mock_client_ = mock_client.get(); + ON_CALL(*mock_client_factory, createUncachedRawAsyncClient()) + .WillByDefault(Return(ByMove(std::move(mock_client)))); + auto& factory_context = context_.server_factory_context_; + ON_CALL(factory_context, runtime()).WillByDefault(ReturnRef(runtime_)); + ON_CALL(factory_context.cluster_manager_.async_client_manager_, factoryForGrpcService(_, _, _)) + .WillByDefault(Return(ByMove(std::move(mock_client_factory)))); + ON_CALL(factory_context, scope()).WillByDefault(ReturnRef(scope_)); + + Resource resource; + auto mock_resource_provider = NiceMock(); + + EXPECT_CALL(mock_resource_provider, getResource(_, _, _, true)).WillOnce(Return(resource)); + + driver_ = std::make_unique(opentelemetry_config, context_, mock_resource_provider); +} + // Verifies that the tracer cannot be configured with two exporters at the same time TEST_F(OpenTelemetryDriverTest, BothGrpcAndHttpExportersConfigured) { const std::string yaml_string = R"EOF( diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc index cd08b58e55878..306e485aebd89 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc +++ b/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc @@ -78,9 +78,9 @@ TEST_F(ResourceProviderTest, NoResourceDetectorsConfigured) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = - resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, opentelemetry_config.service_name()); + Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), + server_factory_context_, + opentelemetry_config.service_name(), true); EXPECT_EQ(resource.schema_url_, ""); @@ -101,6 +101,30 @@ TEST_F(ResourceProviderTest, NoResourceDetectorsConfigured) { } } +// Verifies a resource with only service name is returned when telemetry SDK attributes are disabled +TEST_F(ResourceProviderTest, NoResourceDetectorsConfiguredAttributesDisabled) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + set_telemetry_sdk_resource_attributes: false + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), + server_factory_context_, + opentelemetry_config.service_name(), false); + + // Verify that telemetry SDK attributes are NOT present + EXPECT_TRUE(resource.attributes_.find("telemetry.sdk.language") == resource.attributes_.end()); + EXPECT_TRUE(resource.attributes_.find("telemetry.sdk.name") == resource.attributes_.end()); + EXPECT_TRUE(resource.attributes_.find("telemetry.sdk.version") == resource.attributes_.end()); +} + // Verifies it is possible to configure multiple resource detectors TEST_F(ResourceProviderTest, MultipleResourceDetectorsConfigured) { auto detector_a = std::make_unique>(); @@ -147,9 +171,9 @@ TEST_F(ResourceProviderTest, MultipleResourceDetectorsConfigured) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = - resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, opentelemetry_config.service_name()); + Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), + server_factory_context_, + opentelemetry_config.service_name(), true); EXPECT_EQ(resource.schema_url_, ""); @@ -184,7 +208,8 @@ TEST_F(ResourceProviderTest, UnknownResourceDetectors) { ResourceProviderImpl resource_provider; EXPECT_THROW_WITH_MESSAGE( resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, opentelemetry_config.service_name()), + server_factory_context_, opentelemetry_config.service_name(), + true), EnvoyException, "Resource detector factory not found: " "'envoy.tracers.opentelemetry.resource_detectors.UnkownResourceDetector'"); @@ -214,9 +239,9 @@ TEST_F(ResourceProviderTest, ProblemCreatingResourceDetector) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - EXPECT_THROW_WITH_MESSAGE(resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, - opentelemetry_config.service_name()), + EXPECT_THROW_WITH_MESSAGE(resource_provider.getResource( + opentelemetry_config.resource_detectors(), server_factory_context_, + opentelemetry_config.service_name(), true), EnvoyException, "Resource detector could not be created: " "'envoy.tracers.opentelemetry.resource_detectors.a'"); @@ -266,9 +291,9 @@ TEST_F(ResourceProviderTest, OldSchemaEmptyUpdatingSet) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = - resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, opentelemetry_config.service_name()); + Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), + server_factory_context_, + opentelemetry_config.service_name(), true); // OTel spec says the updating schema should be used EXPECT_EQ(expected_schema_url, resource.schema_url_); @@ -318,9 +343,9 @@ TEST_F(ResourceProviderTest, OldSchemaSetUpdatingEmpty) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = - resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, opentelemetry_config.service_name()); + Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), + server_factory_context_, + opentelemetry_config.service_name(), true); // OTel spec says the updating schema should be used EXPECT_EQ(expected_schema_url, resource.schema_url_); @@ -370,9 +395,9 @@ TEST_F(ResourceProviderTest, OldAndUpdatingSchemaAreEqual) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = - resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, opentelemetry_config.service_name()); + Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), + server_factory_context_, + opentelemetry_config.service_name(), true); EXPECT_EQ(expected_schema_url, resource.schema_url_); } @@ -421,9 +446,9 @@ TEST_F(ResourceProviderTest, OldAndUpdatingSchemaAreDifferent) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = - resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, opentelemetry_config.service_name()); + Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), + server_factory_context_, + opentelemetry_config.service_name(), true); // OTel spec says Old schema should be used EXPECT_EQ(expected_schema_url, resource.schema_url_); From 68b407e3aa1d226bb7108bd32a93e991192e6203 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 30 Apr 2026 14:21:11 +0100 Subject: [PATCH 175/750] ci/codeql: Attempt to suppress issues in stdlib (#44768) this is another attempt to suppress the spurious alert --------- Signed-off-by: Ryan Northey --- .github/workflows/codeql-daily.yml | 4 ++++ .github/workflows/codeql-push.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 6404b566694cc..3d76f0e61df8c 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -53,6 +53,10 @@ jobs: - name: Initialize CodeQL uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # codeql-bundle-v4.35.2 + env: + # NOTE: these env vars are undocumented/experimental - they are likely to require a future update + CODEQL_EXTRACTOR_CPP_OPTION_EXCLUDE_HEADER_PATTERNS: "bin/clang.*/.*" + CODEQL_EXTRACTOR_CPP_WIP_IGNORE_SYSTEM_HEADERS: "true" with: languages: cpp trap-caching: false diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 764fbb5699f3b..22d3b6e55629d 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -89,6 +89,10 @@ jobs: - name: Initialize CodeQL uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # codeql-bundle-v4.35.2 + env: + # NOTE: these env vars are undocumented/experimental - they are likely to require a future update + CODEQL_EXTRACTOR_CPP_OPTION_EXCLUDE_HEADER_PATTERNS: "bin/clang.*/.*" + CODEQL_EXTRACTOR_CPP_WIP_IGNORE_SYSTEM_HEADERS: "true" with: languages: cpp trap-caching: false From 7ea91de6c0d1c29791712e5843189e246cb0162a Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 30 Apr 2026 15:44:51 +0100 Subject: [PATCH 176/750] ci/bazel: Shift remote_downloader to cache config (#44777) Signed-off-by: Ryan Northey --- .bazelrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazelrc b/.bazelrc index bc9a853deecc1..9e6b2f1e55676 100644 --- a/.bazelrc +++ b/.bazelrc @@ -445,10 +445,10 @@ common:rbe --config=remote-exec # this provides access to just cache common:remote-cache --config=engflow-common common:remote-cache --remote_cache=grpcs://mordenite.cluster.engflow.com +common:remote-cache --experimental_remote_downloader=grpcs://mordenite.cluster.engflow.com common:remote-cache --remote_timeout=3600s common:remote-exec --remote_executor=grpcs://mordenite.cluster.engflow.com -common:remote-exec --experimental_remote_downloader=grpcs://mordenite.cluster.engflow.com common:remote-exec --jobs=200 common:remote-exec --define=engflow_rbe=true From c6c39e4ca337c3621411267cc2cec08c9f011281 Mon Sep 17 00:00:00 2001 From: Doogie Min Date: Fri, 1 May 2026 00:08:31 +0900 Subject: [PATCH 177/750] quic: add TLS session ticket resumption support (#42734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit Message: quic: add session ticket resumption support using configured session ticket keys Additional Description: ## Summary TLS session resumption is essential for QUIC performance. Without it, every connection requires a full TLS handshake, and 0-RTT becomes meaningless since there's no session state to resume from. As noted in #42682, TLS-related data accounts for roughly 1/3 of bytes during connection establishment - session resumption eliminates most of this overhead. Currently, Envoy's QUIC implementation does not support session resumption across workers or processes. While users can configure `session_ticket_keys` or `session_ticket_keys_sds_secret_config` in downstream TLS context, these settings have no effect on QUIC connections. This limitation is documented in #25418, which explicitly states that session ticket key plumbing is missing from the QUIC implementation. This PR bridges that gap by enabling QUIC to use the same session ticket keys configured for TCP TLS, allowing session resumption to work across workers and processes. ## Implementation We subclass QUICHE's `TlsServerHandshaker` as `EnvoyTlsServerHandshaker` and install a session-ticket key callback on the shared QUICHE `SSL_CTX`. The callback reuses `ServerContextImpl::sessionTicketProcess()` so QUIC and TCP TLS share identical session-ticket handling (same keys, same format, same rotation semantics). **Key design decisions:** 1. **Per-connection pinning of `ServerContextImpl`**: Each `EnvoyTlsServerHandshaker` holds a `ServerContextSharedPtr` captured at connection creation, and stores `this` in SSL ex_data. The static ticket callback retrieves the handshaker from ex_data and delegates to the pinned context's `sessionTicketProcess()`. Because the shared pointer keeps the context alive, an SDS update that rotates the factory's active context does not invalidate in-flight connections — matching TCP TLS behavior where each connection is bound to the `ServerContextImpl` active at connection creation. 2. **`SSL_CTX_set_tlsext_ticket_key_cb` over `SSL_CTX_set_ticket_aead_method`**: We use the same callback mechanism as TCP TLS rather than QUICHE's `TicketCrypter` interface, so `ServerContextImpl::sessionTicketProcess()` can be reused unchanged. 3. **Graceful fallback**: If the runtime guard is toggled between `OnNewSslCtx` (which installs the callback on the shared `SSL_CTX`) and connection creation (which may fall back to the vanilla handshaker), the ticket callback finds a null handshaker in ex_data and returns 0 to skip ticket issuance for that connection rather than crashing. ## Flow ``` Server startup (once per SSL_CTX): EnvoyQuicProofSource::OnNewSslCtx() └─ if runtime flag on: SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, EnvoyTlsServerHandshaker::ticketKeyCallback) Per connection: EnvoyQuicCryptoServerStreamFactoryImpl::createEnvoyQuicCryptoServerStream() └─ reads SessionTicketConfig from QuicServerTransportSocketFactory └─ constructs EnvoyTlsServerHandshaker(session, crypto_config, factory.sslCtx(), disable_resumption) └─ pins ServerContextSharedPtr └─ SSL_set_ex_data(ssl, handshakerExDataIndex(), this) └─ if disable_resumption || no ticket keys: DisableResumption() // SSL_OP_NO_TICKET During handshake (BoringSSL-driven): ticketKeyCallback(ssl, ...) └─ handshaker = SSL_get_ex_data(ssl, handshakerExDataIndex()) └─ if null: return 0 // guard toggled after OnNewSslCtx — skip ticket └─ return handshaker->pinnedServerContext()->sessionTicketProcess(ssl, ...) ``` Risk Level: Low (behind runtime guard, disabled by default) Testing: New unit tests for `EnvoyTlsServerHandshaker` and `EnvoyQuicProofSource`; new integration coverage in `sds_dynamic_integration_test` (`SessionTicketKeysViaSds`, `SessionTicketKeysRemovedViaSds`) and in `quic_http_integration_test` (`SessionTicketResumptionWithStaticKeys`, `NoSessionTicketResumptionWithoutKeys`). Docs Changes: N/A Release Notes: Added Platform Specific Features: N/A [Optional Runtime guard:] `envoy.reloadable_features.quic_session_ticket_support` (default: false) [Optional Fixes #Issue] Partially addresses #25418 --------- Signed-off-by: Doogie Min --- changelogs/current.yaml | 8 +- source/common/quic/BUILD | 14 +++ source/common/quic/envoy_quic_proof_source.cc | 9 +- .../envoy_quic_server_crypto_stream_factory.h | 1 - .../quic/envoy_tls_server_handshaker.cc | 45 ++++++++ .../common/quic/envoy_tls_server_handshaker.h | 50 ++++++++ .../quic_server_transport_socket_factory.h | 28 +++++ source/common/runtime/runtime_features.cc | 2 + source/common/tls/server_context_impl.h | 6 +- source/extensions/quic/crypto_stream/BUILD | 3 + .../envoy_quic_crypto_server_stream.cc | 27 ++++- test/common/quic/BUILD | 8 ++ .../quic/envoy_quic_proof_source_test.cc | 52 ++++++++- .../quic/envoy_tls_server_handshaker_test.cc | 30 +++++ .../quic_transport_socket_factory_test.cc | 54 +++++++++ .../integration/quic_http_integration_test.cc | 69 +++++++++++ .../sds_dynamic_integration_test.cc | 108 ++++++++++++++++++ tools/spelling/spelling_dictionary.txt | 1 + 18 files changed, 504 insertions(+), 11 deletions(-) create mode 100644 source/common/quic/envoy_tls_server_handshaker.cc create mode 100644 source/common/quic/envoy_tls_server_handshaker.h create mode 100644 test/common/quic/envoy_tls_server_handshaker_test.cc diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 85728e96a1a69..e3ceaa0da8df6 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -96,6 +96,13 @@ new_features: RSA public key exchange on behalf of the client. Added a new :ref:`downstream_ssl ` config option with ``DISABLE``, ``REQUIRE``, and ``ALLOW`` modes. +- area: quic + change: | + Added support for TLS session ticket resumption in QUIC using configured session ticket keys from + :ref:`session_ticket_keys `. + This enables faster reconnection across server instances by allowing clients to resume TLS sessions + without full handshakes. The feature is disabled by default and can be enabled by setting runtime guard + ``envoy.reloadable_features.quic_session_ticket_support`` to ``true``. - area: composite change: | Added support for the @@ -104,5 +111,4 @@ new_features: instead of using the :ref:`ExtensionWithMatcher ` filter. - deprecated: diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index b9c1faa5796af..52997bcf1de32 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -122,6 +122,19 @@ envoy_cc_library( ]), ) +envoy_cc_library( + name = "envoy_tls_server_handshaker", + srcs = envoy_select_enable_http3(["envoy_tls_server_handshaker.cc"]), + hdrs = envoy_select_enable_http3(["envoy_tls_server_handshaker.h"]), + external_deps = ["ssl"], + deps = envoy_select_enable_http3([ + "//source/common/common:assert_lib", + "//source/common/common:macros", + "//source/common/tls:server_context_lib", + "@quiche//:quic_server_session_lib", + ]), +) + envoy_cc_library( name = "envoy_quic_proof_source_lib", srcs = envoy_select_enable_http3(["envoy_quic_proof_source.cc"]), @@ -130,6 +143,7 @@ envoy_cc_library( deps = envoy_select_enable_http3([ ":envoy_quic_proof_source_base_lib", ":envoy_quic_utils_lib", + ":envoy_tls_server_handshaker", ":quic_io_handle_wrapper_lib", ":quic_transport_socket_factory_lib", "//envoy/ssl:tls_certificate_config_interface", diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index d957f50b4771c..da99b88981b65 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -4,7 +4,9 @@ #include "envoy/ssl/tls_certificate_config.h" +#include "source/common/common/assert.h" #include "source/common/quic/envoy_quic_utils.h" +#include "source/common/quic/envoy_tls_server_handshaker.h" #include "source/common/quic/quic_io_handle_wrapper.h" #include "source/common/stream_info/stream_info_impl.h" @@ -113,7 +115,12 @@ void EnvoyQuicProofSource::updateFilterChainManager( filter_chain_manager_ = &filter_chain_manager; } -void EnvoyQuicProofSource::OnNewSslCtx(SSL_CTX* ssl_ctx) { registerCertCompression(ssl_ctx); } +void EnvoyQuicProofSource::OnNewSslCtx(SSL_CTX* ssl_ctx) { + registerCertCompression(ssl_ctx); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.quic_session_ticket_support")) { + SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, EnvoyTlsServerHandshaker::ticketKeyCallback); + } +} } // namespace Quic } // namespace Envoy diff --git a/source/common/quic/envoy_quic_server_crypto_stream_factory.h b/source/common/quic/envoy_quic_server_crypto_stream_factory.h index 61a39bfde3b97..b744dddeefb60 100644 --- a/source/common/quic/envoy_quic_server_crypto_stream_factory.h +++ b/source/common/quic/envoy_quic_server_crypto_stream_factory.h @@ -7,7 +7,6 @@ #include "quiche/quic/core/crypto/quic_crypto_server_config.h" #include "quiche/quic/core/quic_crypto_server_stream_base.h" #include "quiche/quic/core/quic_session.h" -#include "quiche/quic/core/tls_server_handshaker.h" namespace Envoy { namespace Quic { diff --git a/source/common/quic/envoy_tls_server_handshaker.cc b/source/common/quic/envoy_tls_server_handshaker.cc new file mode 100644 index 0000000000000..262f5453f1a33 --- /dev/null +++ b/source/common/quic/envoy_tls_server_handshaker.cc @@ -0,0 +1,45 @@ +#include "source/common/quic/envoy_tls_server_handshaker.h" + +#include "source/common/common/macros.h" + +namespace Envoy { +namespace Quic { + +EnvoyTlsServerHandshaker::EnvoyTlsServerHandshaker( + quic::QuicSession* session, const quic::QuicCryptoServerConfig* crypto_config, + Ssl::ServerContextSharedPtr pinned_ssl_ctx, bool disable_resumption) + : TlsServerHandshaker(session, crypto_config), pinned_ssl_ctx_(std::move(pinned_ssl_ctx)) { + SSL_set_ex_data(ssl(), handshakerExDataIndex(), this); + // Also check the pinned context for keys: the factory is shared across workers and + // config_ may reflect an SDS update before ssl_ctx_ is swapped on the main thread. + if (disable_resumption || !pinnedServerContext()->hasSessionTicketKeys()) { + DisableResumption(); + } +} + +int EnvoyTlsServerHandshaker::handshakerExDataIndex() { + CONSTRUCT_ON_FIRST_USE(int, []() -> int { + int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); + RELEASE_ASSERT(index >= 0, "Failed to allocate SSL ex_data index for handshaker"); + return index; + }()); +} + +int EnvoyTlsServerHandshaker::ticketKeyCallback(SSL* ssl, uint8_t* key_name, uint8_t* iv, + EVP_CIPHER_CTX* ctx, HMAC_CTX* hmac_ctx, + int encrypt) { + auto* handshaker = + static_cast(SSL_get_ex_data(ssl, handshakerExDataIndex())); + if (handshaker == nullptr || handshaker->pinnedServerContext() == nullptr) { + // Null handshaker can occur if the runtime guard was toggled between + // OnNewSslCtx (which installed this callback on the SSL_CTX) and + // connection creation (which fell back to the vanilla TlsServerHandshaker). + // Return 0 to disable ticket for this connection — graceful fallback. + return 0; + } + return handshaker->pinnedServerContext()->sessionTicketProcess(ssl, key_name, iv, ctx, hmac_ctx, + encrypt); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/common/quic/envoy_tls_server_handshaker.h b/source/common/quic/envoy_tls_server_handshaker.h new file mode 100644 index 0000000000000..33bd6d836e42d --- /dev/null +++ b/source/common/quic/envoy_tls_server_handshaker.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include "source/common/common/assert.h" +#include "source/common/tls/server_context_impl.h" + +#include "quiche/quic/core/tls_server_handshaker.h" + +namespace Envoy { +namespace Quic { + +// TlsServerHandshaker subclass for QUIC session ticket handling. +// +// The session ticket key callback is installed on the shared QUICHE ssl +// context, so every connection reaches the same callback regardless of which +// filter chain served it. To find the right session ticket keys at callback +// time, each connection pins a shared pointer to its ServerContextImpl in +// ssl ex data at creation time. The pinned pointer keeps the context alive +// for the connection even after an SDS update rotates the factory's active +// context, and it matches TCP TLS behavior where each connection is bound +// to the ServerContextImpl that was current at connection creation. +class EnvoyTlsServerHandshaker : public quic::TlsServerHandshaker { +public: + EnvoyTlsServerHandshaker(quic::QuicSession* session, + const quic::QuicCryptoServerConfig* crypto_config, + Ssl::ServerContextSharedPtr pinned_ssl_ctx, bool disable_resumption); + + // Session ticket key callback installed on the QUICHE ssl context. + // Retrieves the handshaker from ssl ex_data and delegates to the pinned + // ServerContextImpl::sessionTicketProcess(). + static int ticketKeyCallback(SSL* ssl, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx, + HMAC_CTX* hmac_ctx, int encrypt); + + // SSL ex_data index for storing the handshaker pointer per-connection. + static int handshakerExDataIndex(); + +private: + // QuicServerTransportSocketFactory always creates ServerContextImpl, + // so this downcast is safe for all QUIC connections. + Extensions::TransportSockets::Tls::ServerContextImpl* pinnedServerContext() const { + return static_cast( + pinned_ssl_ctx_.get()); + } + + Ssl::ServerContextSharedPtr pinned_ssl_ctx_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/common/quic/quic_server_transport_socket_factory.h b/source/common/quic/quic_server_transport_socket_factory.h index 4b553890854b0..31a3a8a5abc25 100644 --- a/source/common/quic/quic_server_transport_socket_factory.h +++ b/source/common/quic/quic_server_transport_socket_factory.h @@ -38,6 +38,34 @@ class QuicServerTransportSocketFactory : public Network::DownstreamTransportSock bool earlyDataEnabled() const { return enable_early_data_; } + struct SessionTicketConfig { + // True when session ticket encryption keys are explicitly configured via + // session_ticket_keys or session_ticket_keys_sds_secret_config. Without + // keys, the server cannot encrypt or decrypt session tickets. + bool has_keys; + // True when disable_stateless_session_resumption is set in + // DownstreamTlsContext. When enabled, the server will not issue session + // tickets and clients must perform full handshakes on every connection. + bool disable_stateless_resumption; + // True when an external mechanism (e.g., SDS provider) manages session + // resumption including ticket encryption/decryption. When set, Envoy + // should not install its own session ticket key processing callback. + bool handles_session_resumption; + }; + + SessionTicketConfig getSessionTicketConfig() const { + return {!config_->sessionTicketKeys().empty(), config_->disableStatelessSessionResumption(), + config_->capabilities().handles_session_resumption}; + } + + // Returns the current ServerContextImpl, pinning a shared_ptr so it + // remains valid for the caller's lifetime. May return null before + // initialize() completes or if context creation failed. + Ssl::ServerContextSharedPtr sslCtx() const { + absl::ReaderMutexLock l(ssl_ctx_mu_); + return ssl_ctx_; + } + protected: QuicServerTransportSocketFactory(bool enable_early_data, Stats::Scope& store, Ssl::ServerContextConfigPtr config, diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 773b517e38581..148f800457ab1 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -145,6 +145,8 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_always_use_v6); FALSE_RUNTIME_GUARD(envoy_restart_features_upstream_http_filters_with_tcp_proxy); // TODO(danzh) false deprecate it once QUICHE has its own enable/disable flag. FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_reject_all); +// TODO(doogie): Flip to true once QUIC session ticket support is stable. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_session_ticket_support); // TODO(#10646) change to true when UHV is sufficiently tested // For more information about Universal Header Validation, please see // https://github.com/envoyproxy/envoy/issues/10646 diff --git a/source/common/tls/server_context_impl.h b/source/common/tls/server_context_impl.h index ce5934bf35eac..25288ac875ef5 100644 --- a/source/common/tls/server_context_impl.h +++ b/source/common/tls/server_context_impl.h @@ -70,6 +70,10 @@ class ServerContextImpl : public ContextImpl, Ssl::CurveNIDVector getClientEcdsaCapabilities(const SSL_CLIENT_HELLO& ssl_client_hello) const; + int sessionTicketProcess(SSL* ssl, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx, + HMAC_CTX* hmac_ctx, int encrypt); + bool hasSessionTicketKeys() const { return !session_ticket_keys_.empty(); } + protected: ServerContextImpl( Stats::Scope& scope, const Envoy::Ssl::ServerContextConfig& config, @@ -82,8 +86,6 @@ class ServerContextImpl : public ContextImpl, int alpnSelectCallback(const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen); - int sessionTicketProcess(SSL* ssl, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx, - HMAC_CTX* hmac_ctx, int encrypt); absl::StatusOr generateHashForSessionContextId(const std::vector& server_names); diff --git a/source/extensions/quic/crypto_stream/BUILD b/source/extensions/quic/crypto_stream/BUILD index 6e2e7acb21f93..73e86e51f3c28 100644 --- a/source/extensions/quic/crypto_stream/BUILD +++ b/source/extensions/quic/crypto_stream/BUILD @@ -27,7 +27,10 @@ envoy_cc_library( deps = envoy_select_enable_http3([ "//envoy/registry", "//source/common/quic:envoy_quic_server_crypto_stream_factory_lib", + "//source/common/quic:envoy_tls_server_handshaker", + "//source/common/quic:quic_server_transport_socket_factory_lib", "@envoy_api//envoy/extensions/quic/crypto_stream/v3:pkg_cc_proto", + "@quiche//:quic_server_session_lib", ]), alwayslink = LEGACY_ALWAYSLINK, ) diff --git a/source/extensions/quic/crypto_stream/envoy_quic_crypto_server_stream.cc b/source/extensions/quic/crypto_stream/envoy_quic_crypto_server_stream.cc index 949bc5fafdd7a..518c99ce5ac4f 100644 --- a/source/extensions/quic/crypto_stream/envoy_quic_crypto_server_stream.cc +++ b/source/extensions/quic/crypto_stream/envoy_quic_crypto_server_stream.cc @@ -1,5 +1,9 @@ #include "source/extensions/quic/crypto_stream/envoy_quic_crypto_server_stream.h" +#include "source/common/quic/envoy_tls_server_handshaker.h" +#include "source/common/quic/quic_server_transport_socket_factory.h" +#include "source/common/runtime/runtime_features.h" + namespace Envoy { namespace Quic { @@ -8,11 +12,26 @@ EnvoyQuicCryptoServerStreamFactoryImpl::createEnvoyQuicCryptoServerStream( const quic::QuicCryptoServerConfig* crypto_config, quic::QuicCompressedCertsCache* compressed_certs_cache, quic::QuicSession* session, quic::QuicCryptoServerStreamBase::Helper* helper, - // Though this extension doesn't use the two parameters below, they might be used by - // downstreams. Do not remove them. - OptRef /*transport_socket_factory*/, + OptRef transport_socket_factory, + // Though this extension doesn't use the dispatcher parameter, it might be used by + // downstreams. Do not remove it. Envoy::Event::Dispatcher& /*dispatcher*/) { - return quic::CreateCryptoServerStream(crypto_config, compressed_certs_cache, session, helper); + + if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.quic_session_ticket_support") || + !transport_socket_factory.has_value()) { + return quic::CreateCryptoServerStream(crypto_config, compressed_certs_cache, session, helper); + } + + // QUIC listeners always use QuicServerTransportSocketFactory. The factory's + // ssl_ctx_ is set in the constructor and swapped (never nulled) on SDS updates. + auto& factory = static_cast(*transport_socket_factory); + + auto ticket_config = factory.getSessionTicketConfig(); + bool disable_resumption = ticket_config.disable_stateless_resumption || !ticket_config.has_keys || + ticket_config.handles_session_resumption; + + return std::make_unique(session, crypto_config, factory.sslCtx(), + disable_resumption); } REGISTER_FACTORY(EnvoyQuicCryptoServerStreamFactoryImpl, diff --git a/test/common/quic/BUILD b/test/common/quic/BUILD index 54a8f593783ea..b9ac664f3958c 100644 --- a/test/common/quic/BUILD +++ b/test/common/quic/BUILD @@ -53,6 +53,14 @@ envoy_cc_test( ]), ) +envoy_cc_test( + name = "envoy_tls_server_handshaker_test", + srcs = envoy_select_enable_http3(["envoy_tls_server_handshaker_test.cc"]), + deps = envoy_select_enable_http3([ + "//source/common/quic:envoy_tls_server_handshaker", + ]), +) + envoy_cc_test( name = "envoy_quic_proof_source_test", srcs = envoy_select_enable_http3(["envoy_quic_proof_source_test.cc"]), diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc index f854f68bc9044..3e4d8dfb9a039 100644 --- a/test/common/quic/envoy_quic_proof_source_test.cc +++ b/test/common/quic/envoy_quic_proof_source_test.cc @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -193,10 +195,16 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { EXPECT_CALL(filter_chain_, transportSocketFactory()) .WillRepeatedly(ReturnRef(*transport_socket_factory_)); + loadCertsIntoFactory(cert, expect_private_key); + } + + // Sets up mock config expectations and triggers cert loading into the transport + // socket factory. Does NOT set proof-source-level expectations (ioHandle, + // findFilterChain, transportSocketFactory) — callers add those as needed. + void loadCertsIntoFactory(const std::string& cert, bool with_private_key) { auto factory = Extensions::TransportSockets::Tls::TlsCertificateSelectorConfigFactoryImpl:: getDefaultTlsCertificateSelectorConfigFactory(); ASSERT_TRUE(factory); - ASSERT_EQ("envoy.tls.certificate_selectors.default", factory->name()); const Protobuf::Any any; Server::Configuration::MockGenericFactoryContext ctx; @@ -221,7 +229,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { EXPECT_CALL(tls_cert_config_, certificateChain()) .Times(testing::AtLeast(1)) .WillRepeatedly(ReturnRef(cert)); - if (expect_private_key) { + if (with_private_key) { EXPECT_CALL(tls_cert_config_, privateKey()) .Times(testing::AtLeast(1)) .WillRepeatedly(ReturnRef(pkey_)); @@ -348,5 +356,45 @@ TEST_F(EnvoyQuicProofSourceTest, ComputeSignatureFailNoFilterChain) { std::make_unique(false, filter_chain_, signature)); } +TEST_F(EnvoyQuicProofSourceTest, ComputeSignatureFailAlgorithmMismatch) { + EXPECT_CALL(listen_socket_, ioHandle()).Times(testing::AnyNumber()); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_, _)) + .WillRepeatedly(Invoke([&](const Network::ConnectionSocket&, const StreamInfo::StreamInfo&) { + return &filter_chain_; + })); + EXPECT_CALL(filter_chain_, transportSocketFactory()) + .WillRepeatedly(ReturnRef(*transport_socket_factory_)); + + loadCertsIntoFactory(expected_certs_, true); + + std::string signature; + // Use ECDSA algorithm with an RSA key — triggers algorithm mismatch error path. + proof_source_.ComputeTlsSignature( + server_address_, client_address_, hostname_, SSL_SIGN_ECDSA_SECP256R1_SHA256, "payload", + std::make_unique(false, filter_chain_, signature)); +} + +// Smoke test: verify OnNewSslCtx installs the ticket key callback when the +// runtime guard is enabled. We cannot directly inspect the callback, but we +// verify the call completes without error. +TEST_F(EnvoyQuicProofSourceTest, OnNewSslCtxWithSessionTicketSupport) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.quic_session_ticket_support", "true"}}); + + bssl::UniquePtr ssl_ctx(SSL_CTX_new(TLS_method())); + ASSERT_NE(ssl_ctx, nullptr); + proof_source_.OnNewSslCtx(ssl_ctx.get()); +} + +// Smoke test: verify OnNewSslCtx is a no-op when the runtime guard is disabled. +TEST_F(EnvoyQuicProofSourceTest, OnNewSslCtxWithSessionTicketSupportDisabled) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.quic_session_ticket_support", "false"}}); + + bssl::UniquePtr ssl_ctx(SSL_CTX_new(TLS_method())); + ASSERT_NE(ssl_ctx, nullptr); + proof_source_.OnNewSslCtx(ssl_ctx.get()); +} + } // namespace Quic } // namespace Envoy diff --git a/test/common/quic/envoy_tls_server_handshaker_test.cc b/test/common/quic/envoy_tls_server_handshaker_test.cc new file mode 100644 index 0000000000000..de13613391064 --- /dev/null +++ b/test/common/quic/envoy_tls_server_handshaker_test.cc @@ -0,0 +1,30 @@ +#include + +#include "source/common/quic/envoy_tls_server_handshaker.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Quic { +namespace { + +TEST(EnvoyTlsServerHandshakerTest, HandshakerExDataIndex) { + int index = EnvoyTlsServerHandshaker::handshakerExDataIndex(); + EXPECT_GE(index, 0); + // Calling again returns the same index. + EXPECT_EQ(index, EnvoyTlsServerHandshaker::handshakerExDataIndex()); +} + +TEST(EnvoyTlsServerHandshakerTest, TicketKeyCallbackNullHandshaker) { + bssl::UniquePtr ssl_ctx(SSL_CTX_new(TLS_method())); + ASSERT_NE(ssl_ctx, nullptr); + bssl::UniquePtr ssl(SSL_new(ssl_ctx.get())); + ASSERT_NE(ssl, nullptr); + // No ex_data set → returns 0 gracefully (no crash, no ENVOY_BUG). + EXPECT_EQ(0, EnvoyTlsServerHandshaker::ticketKeyCallback(ssl.get(), nullptr, nullptr, nullptr, + nullptr, 0)); +} + +} // namespace +} // namespace Quic +} // namespace Envoy diff --git a/test/common/quic/quic_transport_socket_factory_test.cc b/test/common/quic/quic_transport_socket_factory_test.cc index d99c5c3a68279..87d8154829e8c 100644 --- a/test/common/quic/quic_transport_socket_factory_test.cc +++ b/test/common/quic/quic_transport_socket_factory_test.cc @@ -113,6 +113,60 @@ TEST_F(QuicServerTransportSocketFactoryConfigTest, ClientAuthUnsupported) { "TLS Client Authentication is not supported over QUIC"); } +// QuicServerTransportSocketFactory implements DownstreamTransportSocketFactory +// only so it can be stored on a FilterChain, not to actually create transport +// sockets — QUIC connections use the QUICHE stack directly via +// EnvoyQuicServerSession. Verify createDownstreamTransportSocket() panics if +// accidentally called. +TEST_F(QuicServerTransportSocketFactoryConfigTest, CreateDownstreamTransportSocketPanics) { + const std::string yaml = TestEnvironment::substitute(R"EOF( +downstream_tls_context: + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_uri_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_uri_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/common/tls/test_data/ca_cert.pem" +)EOF"); + + envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + Network::DownstreamTransportSocketFactoryPtr transport_socket_factory = THROW_OR_RETURN_VALUE( + config_factory_.createTransportSocketFactory(proto_config, context_, {}), + Network::DownstreamTransportSocketFactoryPtr); + EXPECT_DEATH(transport_socket_factory->createDownstreamTransportSocket(), "not implemented"); +} + +TEST_F(QuicServerTransportSocketFactoryConfigTest, GetSessionTicketConfig) { + const std::string yaml = TestEnvironment::substitute(R"EOF( +downstream_tls_context: + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_uri_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_uri_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/common/tls/test_data/ca_cert.pem" +)EOF"); + + envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + Network::DownstreamTransportSocketFactoryPtr transport_socket_factory = THROW_OR_RETURN_VALUE( + config_factory_.createTransportSocketFactory(proto_config, context_, {}), + Network::DownstreamTransportSocketFactoryPtr); + auto& quic_factory = static_cast(*transport_socket_factory); + auto config = quic_factory.getSessionTicketConfig(); + // Default config has no session ticket keys and doesn't disable resumption. + EXPECT_FALSE(config.has_keys); + EXPECT_FALSE(config.disable_stateless_resumption); + EXPECT_FALSE(config.handles_session_resumption); +} + class QuicClientTransportSocketFactoryTest : public testing::Test { public: QuicClientTransportSocketFactoryTest() { diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index a1265332cc480..bea58ca4ad299 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -2040,5 +2040,74 @@ TEST_P(QuicHttpIntegrationTest, QuicListenerFilterReceivesFirstPacketWithCmsg) { EXPECT_GT(std::stoi(metrics.at(2)), 0); } +TEST_P(QuicHttpIntegrationTest, SessionTicketResumptionWithStaticKeys) { + concurrency_ = 1; + config_helper_.addRuntimeOverride("envoy.reloadable_features.quic_session_ticket_support", + "true"); + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* ts = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_filter_chains(0) + ->mutable_transport_socket(); + auto quic_transport_socket_config = MessageUtil::anyConvert< + envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport>( + *ts->mutable_typed_config()); + auto* keys = quic_transport_socket_config.mutable_downstream_tls_context() + ->mutable_session_ticket_keys(); + // Session ticket keys must be exactly 80 bytes. + keys->add_keys()->set_inline_bytes(std::string(80, '\x01')); + ts->mutable_typed_config()->PackFrom(quic_transport_socket_config); + }); + + initialize(); + + // First connection: full handshake. + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + auto response1 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response1->waitForEndStream()); + EnvoyQuicClientSession* quic_session1 = + static_cast(codec_client_->connection()); + EXPECT_FALSE(quic_session1->IsResumption()); + codec_client_->close(); + + // Second connection: should resume via session ticket. + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response2->waitForEndStream()); + EnvoyQuicClientSession* quic_session2 = + static_cast(codec_client_->connection()); + EXPECT_TRUE(quic_session2->IsResumption()); + codec_client_->close(); +} + +TEST_P(QuicHttpIntegrationTest, NoSessionTicketResumptionWithoutKeys) { + concurrency_ = 1; + config_helper_.addRuntimeOverride("envoy.reloadable_features.quic_session_ticket_support", + "true"); + // No session_ticket_keys configured — tickets should be disabled. + initialize(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + auto response1 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response1->waitForEndStream()); + codec_client_->close(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response2->waitForEndStream()); + EnvoyQuicClientSession* quic_session = + static_cast(codec_client_->connection()); + EXPECT_FALSE(quic_session->IsResumption()); + codec_client_->close(); +} + } // namespace Quic } // namespace Envoy diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index 776ffff346cc9..bc654c9baae54 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -233,6 +233,31 @@ class SdsDynamicDownstreamIntegrationTest : public SdsDynamicIntegrationBaseTest default_request_headers_.setHost("www.lyft.com"); }); + if (configure_session_ticket_sds_) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.quic_session_ticket_support", + "true"); + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* ts = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_filter_chains(0) + ->mutable_transport_socket(); + if (test_quic_) { + auto quic_config = MessageUtil::anyConvert< + envoy::extensions::transport_sockets::quic::v3::QuicDownstreamTransport>( + *ts->mutable_typed_config()); + configureSdsSecretConfig(quic_config.mutable_downstream_tls_context() + ->mutable_session_ticket_keys_sds_secret_config()); + ts->mutable_typed_config()->PackFrom(quic_config); + } else { + auto tls_context = MessageUtil::anyConvert< + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext>( + *ts->mutable_typed_config()); + configureSdsSecretConfig(tls_context.mutable_session_ticket_keys_sds_secret_config()); + ts->mutable_typed_config()->PackFrom(tls_context); + } + }); + } + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { // Add a static SDS cluster as the first cluster in the list. // The SDS cluster needs to appear before the cluster that uses it for secrets, so that it @@ -359,10 +384,54 @@ version_info: "0" return makeClientConnectionWithOptions(port, nullptr); } + void configureSdsSecretConfig( + envoy::extensions::transport_sockets::tls::v3::SdsSecretConfig* sds_config) { + sds_config->set_name(session_ticket_keys_secret_); + auto* config_source = sds_config->mutable_sds_config(); + config_source->mutable_path_config_source()->set_path(session_ticket_keys_sds_path_); + config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); + } + + void writeSessionTicketKeysSdsYaml(const std::string& inline_key_bytes) { + constexpr absl::string_view sds_template = + R"EOF( +--- +version_info: "0" +resources: +- "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret + name: "{}" + session_ticket_keys: + keys: + - inline_bytes: "{}" +)EOF"; + const std::string sds_content = fmt::format(sds_template, session_ticket_keys_secret_, + absl::Base64Escape(inline_key_bytes)); + TestEnvironment::writeStringToFileForTest("session_ticket_keys.sds.yaml", sds_content, false); + } + + void writeEmptySessionTicketKeysSdsYaml() { + constexpr absl::string_view sds_template = + R"EOF( +--- +version_info: "0" +resources: +- "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret + name: "{}" + session_ticket_keys: + keys: [] +)EOF"; + const std::string sds_content = fmt::format(sds_template, session_ticket_keys_secret_); + TestEnvironment::writeStringToFileForTest("session_ticket_keys.sds.yaml", sds_content, false); + } + protected: Network::UpstreamTransportSocketFactoryPtr client_ssl_ctx_; bool dual_cert_{false}; bool multi_cert_{false}; + bool configure_session_ticket_sds_{false}; + const std::string session_ticket_keys_secret_{"session_ticket_keys"}; + const std::string session_ticket_keys_sds_path_{ + TestEnvironment::temporaryPath("session_ticket_keys.sds.yaml")}; }; INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, SdsDynamicDownstreamIntegrationTest, @@ -615,6 +684,45 @@ TEST_P(SdsDynamicDownstreamIntegrationTest, WrongSecretFirst) { EXPECT_EQ(1, test_server_->counter("sds.server_cert_rsa.update_rejected")->value()); } +TEST_P(SdsDynamicDownstreamIntegrationTest, SessionTicketKeysViaSds) { + configure_session_ticket_sds_ = true; + // Write session ticket keys SDS YAML before server starts. + writeSessionTicketKeysSdsYaml(std::string(80, '\x01')); + on_server_init_function_ = [this]() { + createSdsStream(*sdsUpstream()); + sendSdsResponse(getServerSecretRsa()); + }; + initialize(); + + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(); + }; + testRouterHeaderOnlyRequestAndResponse(&creator, dataPlaneUpstreamIndex()); +} + +TEST_P(SdsDynamicDownstreamIntegrationTest, SessionTicketKeysRemovedViaSds) { + configure_session_ticket_sds_ = true; + writeSessionTicketKeysSdsYaml(std::string(80, '\x01')); + on_server_init_function_ = [this]() { + createSdsStream(*sdsUpstream()); + sendSdsResponse(getServerSecretRsa()); + }; + initialize(); + + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(); + }; + testRouterHeaderOnlyRequestAndResponse(&creator, dataPlaneUpstreamIndex()); + cleanupUpstreamAndDownstream(); + + // Update SDS file to remove session ticket keys. + writeEmptySessionTicketKeysSdsYaml(); + // Do not wait for SDS update; let the 2nd request race to verify no crash. + // The connection may resume (old keys still pinned) or do a full handshake + // (new empty-keys context pinned with resumption disabled). Both are valid. + testRouterHeaderOnlyRequestAndResponse(&creator, dataPlaneUpstreamIndex()); +} + class SdsDynamicDownstreamCertValidationContextTest : public SdsDynamicDownstreamIntegrationTest { public: SdsDynamicDownstreamCertValidationContextTest() = default; diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index de177c01c1ec8..c1125d30480e5 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -720,6 +720,7 @@ codebase codec codecs codepath +codepoint codepoints codings combinatorial From c87235de001d9680d884b0025e2dee5c52739797 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 30 Apr 2026 16:27:47 +0100 Subject: [PATCH 178/750] mobile/ci: Update and pin pypi publisher (#44766) Signed-off-by: Ryan Northey --- .github/workflows/mobile-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index 3353a64a076e9..e1018192a7e8f 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -236,4 +236,4 @@ jobs: name: python-wheel path: dist/ - name: Publish to PyPi - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1.14 From 11ed11067295b76d18f27d92a1d854a4cb6c4541 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 30 Apr 2026 16:30:06 +0100 Subject: [PATCH 179/750] repo: Update security reporting guidance (#44770) Signed-off-by: Ryan Northey --- .github/ISSUE_TEMPLATE/config.yml | 6 ++++-- .github/ISSUE_TEMPLATE/other.md | 7 +++++-- PULL_REQUEST_TEMPLATE.md | 11 ++++++++--- README.md | 9 +++++---- SECURITY.md | 8 ++++++-- docs/root/intro/arch_overview/security/google_vrp.rst | 7 +++++-- 6 files changed, 33 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 53a28632fc9f3..2f8baf66b3a57 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,7 @@ blank_issues_enabled: false contact_links: - name: "Crash bug" - url: https://github.com/envoyproxy/envoy/security/policy - about: "Please file any crash bug (including asserts in debug builds) with envoy-security@googlegroups.com." + url: https://github.com/envoyproxy/envoy/security/advisories/new + about: | + Please report any crash bug (including asserts in debug builds) by opening a GitHub Security Advisory. + Alternatively, please email envoy-security@googlegroups.com." diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md index 98cc3b7808c98..8c7f11f564e94 100644 --- a/.github/ISSUE_TEMPLATE/other.md +++ b/.github/ISSUE_TEMPLATE/other.md @@ -8,8 +8,11 @@ assignees: '' --- **If you are reporting *any* crash or *any* potential security issue, *do not* -open an issue in this repo. Please report the issue via emailing -envoy-security@googlegroups.com where the issue will be triaged appropriately.** +open an issue in this repo. Instead, please +[open a GitHub Security Advisory](https://github.com/envoyproxy/envoy/security/advisories/new) +(preferred). + +Alternatively, you may email envoy-security@googlegroups.com.** *Title*: *One line description* diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index caaead782fead..59aa8e653d570 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,14 @@ Commit Message: reverse_tunnel: fix initiator crash on SIGINT shutdown Additional Description: `ReverseConnectionIOHandle::cleanup()` called `rev_conn_retry_timer_->enabled()` which asserts `dispatcher_.isThreadSafe()` (timer_impl.cc:56). During shutdown, the destructor chain (`InstanceBase → ListenerManager → ListenerImpl → SocketFactory → Socket → ReverseConnectionIOHandle`) runs on the main thread, not the worker thread that created the timer — so the assertion fails and the process aborts. The fix removes the `enabled()` guard. `unique_ptr::reset()` is safe regardless of timer state. The `close()` method already uses this exact pattern at line 360. This PR was authored with the assistance of generative AI (Claude). All code is fully understood and verified by the author. Risk Level: Low Testing: Unit tests (`reverse_connection_io_handle_test`), integration test (`reverse_connection_cluster_integration_test`), and manual SIGINT test all pass. Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Krishna Sharma Signed-off-by: Krishna Sharma --- .../reverse_connection_io_handle.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.cc b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.cc index b10e408fc79af..1349ccd787047 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.cc +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.cc @@ -72,9 +72,11 @@ void ReverseConnectionIOHandle::cleanup() { trigger_pipe_read_fd_ = -1; } - // Cancel the retry timer safely. - if (rev_conn_retry_timer_ && rev_conn_retry_timer_->enabled()) { - ENVOY_LOG_MISC(trace, "reverse_tunnel: cancelling and resetting retry timer."); + // Reset the retry timer. Don't call enabled() here — it asserts + // dispatcher_.isThreadSafe(), which fails when the destructor runs on the main + // thread during shutdown (the timer was created on a worker thread). + if (rev_conn_retry_timer_) { + ENVOY_LOG_MISC(trace, "reverse_tunnel: resetting retry timer."); rev_conn_retry_timer_.reset(); } From 2193880c3e16743701b3118778fca37cb4807507 Mon Sep 17 00:00:00 2001 From: nshipilov Date: Thu, 30 Apr 2026 15:07:58 -0700 Subject: [PATCH 193/750] stats: optimize prometheus stats endpoint (#44658) This PR optimizes the prometheus stats endpoint. In practice, at Uber, this takes us from ~250-300ms prom stat scrapes to ~125-150ms. There are a few observations that are used throughout (from top to bottom as viewed in the diff) (1) `::sanitizeName` returns a new string, when we could sanitize names in place in certain scenarios, use a new function `::sanitizeNameInPlace` where appropriate. (2) `Primitive*Snapshot` types can be modified in place, as they are copies of the underlying metrics from the cluster manager. For instance, we can use this to modify the already copied, and soon to be destructed, Tags, instead of incurring additional copies in `formattedTags()`. Similarly, in `::addLabelsToMetric` we use this to move the tag name into the proto directly instead of incurring a copy. (3) Instead of maintaining a rb-tree `std::map<...> groups` to give a sorted presentation of the stats, we maintain a mapping of `string/StatName -> vector`, and iterate over the sorted set of keys instead. Creation of this map is significant for cases with lots of groups, and this is the biggest perf improvement in the diff. (4) `PrometheusStatsFormatter::metricName` is only ever called with newly allocated strings, so we can, again, sanitize it in place. Signed-off-by: Nick Shipilov --- changelogs/current.yaml | 5 + envoy/stats/primitive_stats.h | 3 + source/server/admin/prometheus_stats.cc | 180 +++++++++++++----- source/server/admin/prometheus_stats.h | 8 +- test/server/admin/prometheus_stats_test.cc | 12 +- test/server/admin/stats_handler_speed_test.cc | 25 +++ 6 files changed, 175 insertions(+), 58 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index b39e567bcdc32..9b8fcc68e7dc7 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -21,6 +21,11 @@ minor_behavior_changes: on the five methods that write to the per-request ``strValue`` scratch buffer (``getStringValue``, ``getDynamicMetadata``, ``getStringFilterState``, ``getStringProperty``, ``getSecret``). +- area: stats + change: | + Optimized prometheus stats endpoint. Users should see a roughly 30-40% latency improvement in calls to the endpoint + for cases where the scrape results in lots of cluster stats. + There should be no visible changes to users, or incompatibilities. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/envoy/stats/primitive_stats.h b/envoy/stats/primitive_stats.h index be210b581b998..659e09672e5dc 100644 --- a/envoy/stats/primitive_stats.h +++ b/envoy/stats/primitive_stats.h @@ -69,6 +69,9 @@ class PrimitiveMetricMetadata { const std::string& tagExtractedName() const { return tag_extracted_name_; } const std::string& name() const { return name_; } const Stats::TagVector& tags() const { return tags_; } + + // Returns the current tags, leaves tags_ in an unknown state. + Stats::TagVector getAndClearTags() { return std::move(tags_); } bool used() const { return true; } bool hidden() const { return false; } diff --git a/source/server/admin/prometheus_stats.cc b/source/server/admin/prometheus_stats.cc index bcc87ab44e2bf..297a11f49d522 100644 --- a/source/server/admin/prometheus_stats.cc +++ b/source/server/admin/prometheus_stats.cc @@ -20,6 +20,9 @@ namespace Server { namespace { +constexpr absl::string_view kCounter = "counter"; +constexpr absl::string_view kGauge = "gauge"; + const Regex::CompiledGoogleReMatcher& promRegex() { CONSTRUCT_ON_FIRST_USE(Regex::CompiledGoogleReMatcherNoSafetyChecks, "[^a-zA-Z0-9_]"); } @@ -34,6 +37,15 @@ std::string sanitizeName(const absl::string_view name) { return promRegex().replaceAll(name, "_"); } +// same logic as above, but does it in place (no allocations) +void sanitizeNameInPlace(std::string& name) { + for (char& c : name) { + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) { + c = '_'; + } + } +} + /** * Take tag values and sanitize it for text serialization, according to * Prometheus conventions. @@ -76,9 +88,9 @@ class TextFormat : public PrometheusStatsFormatter::OutputFormat { } void generateOutput(Buffer::Instance& output, - const std::vector& counters, + std::vector&& counters, const std::string& prefixed_tag_extracted_name) const override { - generateNumericOutput(output, counters, prefixed_tag_extracted_name); + generateNumericOutput(output, std::move(counters), prefixed_tag_extracted_name); } void generateOutput(Buffer::Instance& output, const std::vector& gauges, @@ -87,9 +99,9 @@ class TextFormat : public PrometheusStatsFormatter::OutputFormat { } void generateOutput(Buffer::Instance& output, - const std::vector& gauges, + std::vector&& gauges, const std::string& prefixed_tag_extracted_name) const override { - generateNumericOutput(output, gauges, prefixed_tag_extracted_name); + generateNumericOutput(output, std::move(gauges), prefixed_tag_extracted_name); } void generateOutput(Buffer::Instance& output, @@ -124,7 +136,7 @@ class TextFormat : public PrometheusStatsFormatter::OutputFormat { for (const auto* text_readout : text_readouts) { auto tags = text_readout->tags(); tags.push_back(Stats::Tag{"text_value", text_readout->value()}); - const std::string formattedTags = PrometheusStatsFormatter::formattedTags(tags); + const std::string formattedTags = PrometheusStatsFormatter::formattedTags(std::move(tags)); output.add(fmt::format("{0}{{{1}}} 0\n", prefixed_tag_extracted_name, formattedTags)); } } @@ -139,12 +151,10 @@ class TextFormat : public PrometheusStatsFormatter::OutputFormat { void generateNumericOutput(Buffer::Instance& output, const std::vector& metrics, const std::string& prefixed_tag_extracted_name) const { absl::string_view type; - if constexpr (std::is_same_v || - std::is_same_v) { - type = "counter"; - } else if constexpr (std::is_same_v || - std::is_same_v) { - type = "gauge"; + if constexpr (std::is_same_v) { + type = kCounter; + } else if constexpr (std::is_same_v) { + type = kGauge; } else { static_assert(false, "Unexpected StatsType"); } @@ -157,6 +167,27 @@ class TextFormat : public PrometheusStatsFormatter::OutputFormat { } } + template + void generateNumericOutput(Buffer::Instance& output, std::vector&& metrics, + const std::string& prefixed_tag_extracted_name) const { + absl::string_view type; + if constexpr (std::is_same_v) { + type = kCounter; + } else if constexpr (std::is_same_v) { + type = kGauge; + } else { + static_assert(false, "Unexpected StatsType"); + } + + generateTypeOutput(output, type, prefixed_tag_extracted_name); + for (auto* metric : metrics) { + const std::string formatted_tags = + PrometheusStatsFormatter::formattedTags(metric->getAndClearTags()); + output.add(fmt::format("{0}{{{1}}} {2}\n", prefixed_tag_extracted_name, formatted_tags, + metric->value())); + } + } + /* * Returns the prometheus output for a histogram. The output is a multi-line string (with embedded * newlines) that contains all the individual bucket counts and sum/count for a single histogram @@ -168,8 +199,11 @@ class TextFormat : public PrometheusStatsFormatter::OutputFormat { generateTypeOutput(output, "histogram", prefixed_tag_extracted_name); for (const auto* histogram : histograms) { - const std::string tags = PrometheusStatsFormatter::formattedTags(histogram->tags()); - const std::string hist_tags = histogram->tags().empty() ? EMPTY_STRING : (tags + ","); + auto histogram_tags = histogram->tags(); + const bool empty_tags = histogram_tags.empty(); + + const std::string tags = PrometheusStatsFormatter::formattedTags(std::move(histogram_tags)); + const std::string hist_tags = empty_tags ? EMPTY_STRING : (tags + ","); const Stats::HistogramStatistics& stats = histogram->cumulativeStatistics(); Stats::ConstSupportedBuckets& supported_buckets = stats.supportedBuckets(); @@ -243,9 +277,9 @@ class ProtobufFormat : public PrometheusStatsFormatter::OutputFormat { // Return the prometheus output for a group of PrimitiveCounters. void generateOutput(Buffer::Instance& output, - const std::vector& counters, + std::vector&& counters, const std::string& prefixed_tag_extracted_name) const override { - generateNumericOutput(output, counters, prefixed_tag_extracted_name, + generateNumericOutput(output, std::move(counters), prefixed_tag_extracted_name, io::prometheus::client::MetricType::COUNTER); } @@ -286,9 +320,9 @@ class ProtobufFormat : public PrometheusStatsFormatter::OutputFormat { // Return the prometheus output for a group of PrimitiveGauges. void generateOutput(Buffer::Instance& output, - const std::vector& gauges, + std::vector&& gauges, const std::string& prefixed_tag_extracted_name) const override { - generateNumericOutput(output, gauges, prefixed_tag_extracted_name, + generateNumericOutput(output, std::move(gauges), prefixed_tag_extracted_name, io::prometheus::client::MetricType::GAUGE); } @@ -319,11 +353,12 @@ class ProtobufFormat : public PrometheusStatsFormatter::OutputFormat { private: // Helper method to add labels to a metric from tags. void addLabelsToMetric(io::prometheus::client::Metric* metric, - const std::vector& tags) const { + std::vector&& tags) const { metric->mutable_label()->Reserve(tags.size()); - for (const auto& tag : tags) { + for (auto& tag : tags) { auto* label = metric->add_label(); - label->set_name(sanitizeName(tag.name_)); + sanitizeNameInPlace(tag.name_); + label->set_name(std::move(tag.name_)); label->set_value(sanitizeValue(tag.value_)); } } @@ -356,6 +391,38 @@ class ProtobufFormat : public PrometheusStatsFormatter::OutputFormat { writeDelimitedMessage(metric_family, output); } + template + void generateNumericOutput(Buffer::Instance& output, std::vector&& metrics, + const std::string& prefixed_tag_extracted_name, + io::prometheus::client::MetricType type) const { + ASSERT(!metrics.empty()); + + io::prometheus::client::MetricFamily metric_family; + metric_family.set_name(prefixed_tag_extracted_name); + metric_family.set_type(type); + metric_family.mutable_metric()->Reserve(metrics.size()); + + for (auto* metric : metrics) { + auto* prom_metric = metric_family.add_metric(); + + uint64_t value = metric->value(); + auto tags = metric->getAndClearTags(); + + addLabelsToMetric(prom_metric, std::move(tags)); + + // Set value based on type + if (type == io::prometheus::client::MetricType::COUNTER) { + auto* counter = prom_metric->mutable_counter(); + counter->set_value(value); + } else { + auto* gauge = prom_metric->mutable_gauge(); + gauge->set_value(value); + } + } + + writeDelimitedMessage(metric_family, output); + } + void generateHistogramOutput(io::prometheus::client::MetricFamily& metric_family, const std::vector& histograms) const { metric_family.set_type(io::prometheus::client::MetricType::HISTOGRAM); @@ -681,10 +748,9 @@ uint64_t outputStatType(Buffer::Instance& response, const StatsParams& params, // comparison. const Stats::SymbolTable& global_symbol_table = metrics.front()->constSymbolTable(); - // Sorted collection of metrics sorted by their tagExtractedName, to satisfy the requirements - // of the exposition format. - std::map groups( - global_symbol_table); + // Collection of metrics by their tagExtractedName. + // Sorting will be done on the names separately. + absl::flat_hash_map groups; for (const auto& metric : metrics) { ASSERT(&global_symbol_table == &metric->constSymbolTable()); @@ -694,10 +760,19 @@ uint64_t outputStatType(Buffer::Instance& response, const StatsParams& params, groups[metric->tagExtractedStatName()].push_back(metric.get()); } + std::vector sorted_stat_names; + sorted_stat_names.reserve(groups.size()); + for (const auto& [group, _] : groups) { + sorted_stat_names.push_back(group); + } + Stats::StatNameLessThan comp(global_symbol_table); + std::sort(sorted_stat_names.begin(), sorted_stat_names.end(), comp); + auto result = groups.size(); - for (auto& group : groups) { + for (auto& group_name : sorted_stat_names) { + auto& group = groups[group_name]; const absl::optional prefixed_tag_extracted_name = - PrometheusStatsFormatter::metricName(global_symbol_table.toString(group.first), + PrometheusStatsFormatter::metricName(global_symbol_table.toString(group_name), custom_namespaces); if (!prefixed_tag_extracted_name.has_value()) { --result; @@ -707,17 +782,16 @@ uint64_t outputStatType(Buffer::Instance& response, const StatsParams& params, // Sort before producing the final output to satisfy the "preferred" ordering from the // prometheus spec: metrics will be sorted by their tags' textual representation, which will // be consistent across calls. - std::sort(group.second.begin(), group.second.end(), MetricLessThan()); + std::sort(group.begin(), group.end(), MetricLessThan()); - output_format.generateOutput(response, group.second, prefixed_tag_extracted_name.value()); + output_format.generateOutput(response, group, prefixed_tag_extracted_name.value()); } return result; } template uint64_t outputPrimitiveStatType(Buffer::Instance& response, const StatsParams& params, - const std::vector& metrics, - const OutputFormat& output_format, + std::vector&& metrics, const OutputFormat& output_format, const Stats::CustomStatNamespaces& custom_namespaces) { /* @@ -735,28 +809,36 @@ uint64_t outputPrimitiveStatType(Buffer::Instance& response, const StatsParams& // be sorted before producing the final output to satisfy the "preferred" ordering from the // prometheus spec: metrics will be sorted by their tags' textual representation, which will be // consistent across calls. - using StatTypeUnsortedCollection = std::vector; + using StatTypeUnsortedCollection = std::vector; // Return early to avoid crashing when getting the symbol table from the first metric. if (metrics.empty()) { return 0; } - // Sorted collection of metrics sorted by their tagExtractedName, to satisfy the requirements - // of the exposition format. - std::map groups; + // Collection of metrics sorted by their tagExtractedName. + // We satisfy the requirements of the exposition format by iterating over the sorted keys. + absl::flat_hash_map groups; - for (const auto& metric : metrics) { + for (auto& metric : metrics) { if (!params.shouldShowMetric(metric)) { continue; } groups[metric.tagExtractedName()].push_back(&metric); } + std::vector sorted_group_names; + sorted_group_names.reserve(groups.size()); + for (const auto& [group, _] : groups) { + sorted_group_names.push_back(group); + } + std::sort(sorted_group_names.begin(), sorted_group_names.end()); + auto result = groups.size(); - for (auto& group : groups) { + for (auto& group_name : sorted_group_names) { + auto& group = groups[group_name]; const absl::optional prefixed_tag_extracted_name = - PrometheusStatsFormatter::metricName(group.first, custom_namespaces); + PrometheusStatsFormatter::metricName(std::move(group_name), custom_namespaces); if (!prefixed_tag_extracted_name.has_value()) { --result; continue; @@ -765,9 +847,9 @@ uint64_t outputPrimitiveStatType(Buffer::Instance& response, const StatsParams& // Sort before producing the final output to satisfy the "preferred" ordering from the // prometheus spec: metrics will be sorted by their tags' textual representation, which will // be consistent across calls. - std::sort(group.second.begin(), group.second.end(), PrimitiveMetricSnapshotLessThan()); + std::sort(group.begin(), group.end(), PrimitiveMetricSnapshotLessThan()); - output_format.generateOutput(response, group.second, prefixed_tag_extracted_name.value()); + output_format.generateOutput(response, std::move(group), prefixed_tag_extracted_name.value()); } return result; } @@ -819,11 +901,12 @@ bool useProtobufFormat(const StatsParams& params, const Http::RequestHeaderMap& } // namespace -std::string PrometheusStatsFormatter::formattedTags(const std::vector& tags) { +std::string PrometheusStatsFormatter::formattedTags(std::vector&& tags) { std::vector buf; buf.reserve(tags.size()); - for (const Stats::Tag& tag : tags) { - buf.push_back(fmt::format("{}=\"{}\"", sanitizeName(tag.name_), sanitizeValue(tag.value_))); + for (Stats::Tag& tag : tags) { + sanitizeNameInPlace(tag.name_); + buf.push_back(fmt::format("{}=\"{}\"", tag.name_, sanitizeValue(tag.value_))); } return absl::StrJoin(buf, ","); } @@ -854,7 +937,7 @@ absl::Status PrometheusStatsFormatter::validateParams(const StatsParams& params, } absl::optional -PrometheusStatsFormatter::metricName(const std::string& extracted_name, +PrometheusStatsFormatter::metricName(std::string&& extracted_name, const Stats::CustomStatNamespaces& custom_namespaces) { const absl::optional custom_namespace_stripped = custom_namespaces.stripRegisteredPrefix(extracted_name); @@ -876,7 +959,8 @@ PrometheusStatsFormatter::metricName(const std::string& extracted_name, // If it does not have a custom namespace, add namespacing prefix to avoid conflicts, as per best // practice: https://prometheus.io/docs/practices/naming/#metric-names Also, naming conventions on // https://prometheus.io/docs/concepts/data_model/ - return absl::StrCat("envoy_", sanitizeName(extracted_name)); + sanitizeNameInPlace(extracted_name); + return absl::StrCat("envoy_", extracted_name); } uint64_t PrometheusStatsFormatter::generateWithOutputFormat( @@ -938,10 +1022,10 @@ uint64_t PrometheusStatsFormatter::generateWithOutputFormat( }, [&](Stats::PrimitiveGaugeSnapshot&& metric) { host_gauges.emplace_back(std::move(metric)); }); - metric_name_count += - outputPrimitiveStatType(response, params, host_counters, output_format, custom_namespaces); - metric_name_count += - outputPrimitiveStatType(response, params, host_gauges, output_format, custom_namespaces); + metric_name_count += outputPrimitiveStatType(response, params, std::move(host_counters), + output_format, custom_namespaces); + metric_name_count += outputPrimitiveStatType(response, params, std::move(host_gauges), + output_format, custom_namespaces); return metric_name_count; } diff --git a/source/server/admin/prometheus_stats.h b/source/server/admin/prometheus_stats.h index 3bb9a140aa2ee..1377511963fea 100644 --- a/source/server/admin/prometheus_stats.h +++ b/source/server/admin/prometheus_stats.h @@ -41,7 +41,7 @@ class PrometheusStatsFormatter { // Return the prometheus output for a group of PrimitiveCounters. virtual void generateOutput(Buffer::Instance& output, - const std::vector& counters, + std::vector&& counters, const std::string& prefixed_tag_extracted_name) const PURE; // Return the prometheus output for a group of Gauges. @@ -56,7 +56,7 @@ class PrometheusStatsFormatter { // Return the prometheus output for a group of PrimitiveGauges. virtual void generateOutput(Buffer::Instance& output, - const std::vector& gauges, + std::vector&& gauges, const std::string& prefixed_tag_extracted_name) const PURE; // Return the prometheus output for a group of Histograms. @@ -117,7 +117,7 @@ class PrometheusStatsFormatter { * Format the given tags, returning a string as a comma-separated list * of ="" pairs. */ - static std::string formattedTags(const std::vector& tags); + static std::string formattedTags(std::vector&& tags); /** * Validate the given params, returning an error on invalid arguments @@ -132,7 +132,7 @@ class PrometheusStatsFormatter { * Otherwise, return nullopt. */ static absl::optional - metricName(const std::string& extracted_name, + metricName(std::string&& extracted_name, const Stats::CustomStatNamespaces& custom_namespace_factory); }; diff --git a/test/server/admin/prometheus_stats_test.cc b/test/server/admin/prometheus_stats_test.cc index a9814baf25397..440c7f2155bff 100644 --- a/test/server/admin/prometheus_stats_test.cc +++ b/test/server/admin/prometheus_stats_test.cc @@ -147,7 +147,7 @@ TEST_F(PrometheusStatsFormatterTest, MetricName) { Stats::CustomStatNamespacesImpl custom_namespaces; std::string raw = "vulture.eats-liver"; std::string expected = "envoy_vulture_eats_liver"; - auto actual = PrometheusStatsFormatter::metricName(raw, custom_namespaces); + auto actual = PrometheusStatsFormatter::metricName(std::move(raw), custom_namespaces); EXPECT_TRUE(actual.has_value()); EXPECT_EQ(expected, actual.value()); } @@ -156,7 +156,7 @@ TEST_F(PrometheusStatsFormatterTest, SanitizeMetricName) { Stats::CustomStatNamespacesImpl custom_namespaces; std::string raw = "An.artist.plays-violin@019street"; std::string expected = "envoy_An_artist_plays_violin_019street"; - auto actual = PrometheusStatsFormatter::metricName(raw, custom_namespaces); + auto actual = PrometheusStatsFormatter::metricName(std::move(raw), custom_namespaces); EXPECT_EQ(expected, actual.value()); } @@ -164,7 +164,7 @@ TEST_F(PrometheusStatsFormatterTest, SanitizeMetricNameDigitFirst) { Stats::CustomStatNamespacesImpl custom_namespaces; std::string raw = "3.artists.play-violin@019street"; std::string expected = "envoy_3_artists_play_violin_019street"; - auto actual = PrometheusStatsFormatter::metricName(raw, custom_namespaces); + auto actual = PrometheusStatsFormatter::metricName(std::move(raw), custom_namespaces); EXPECT_TRUE(actual.has_value()); EXPECT_EQ(expected, actual.value()); } @@ -174,7 +174,7 @@ TEST_F(PrometheusStatsFormatterTest, CustomNamespace) { custom_namespaces.registerStatNamespace("promstattest"); std::string raw = "promstattest.vulture.eats-liver"; std::string expected = "vulture_eats_liver"; - auto actual = PrometheusStatsFormatter::metricName(raw, custom_namespaces); + auto actual = PrometheusStatsFormatter::metricName(std::move(raw), custom_namespaces); EXPECT_TRUE(actual.has_value()); EXPECT_EQ(expected, actual.value()); } @@ -183,7 +183,7 @@ TEST_F(PrometheusStatsFormatterTest, CustomNamespaceWithInvalidPromnamespace) { Stats::CustomStatNamespacesImpl custom_namespaces; custom_namespaces.registerStatNamespace("promstattest"); std::string raw = "promstattest.1234abcd.eats-liver"; - auto actual = PrometheusStatsFormatter::metricName(raw, custom_namespaces); + auto actual = PrometheusStatsFormatter::metricName(std::move(raw), custom_namespaces); EXPECT_FALSE(actual.has_value()); } @@ -198,7 +198,7 @@ TEST_F(PrometheusStatsFormatterTest, FormattedTags) { tags.push_back(tag3); std::string expected = "a_tag_name=\"a.tag-value\",another_tag_name=\"another_tag-value\"," "replace_problematic=\"val\\\"ue with\\\\ some\\n issues\""; - auto actual = PrometheusStatsFormatter::formattedTags(tags); + auto actual = PrometheusStatsFormatter::formattedTags(std::move(tags)); EXPECT_EQ(expected, actual); } diff --git a/test/server/admin/stats_handler_speed_test.cc b/test/server/admin/stats_handler_speed_test.cc index 6ccaffdfe98dd..b21f8c3d11e0c 100644 --- a/test/server/admin/stats_handler_speed_test.cc +++ b/test/server/admin/stats_handler_speed_test.cc @@ -435,6 +435,31 @@ BENCHMARK_CAPTURE(BM_FilteredCountersPrometheus, per_endpoint_stats_disabled, fa BENCHMARK_CAPTURE(BM_FilteredCountersPrometheus, per_endpoint_stats_enabled, true) ->Unit(benchmark::kMillisecond); +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_PrometheusFull(benchmark::State& state, bool per_endpoint_stats) { + Envoy::Server::StatsHandlerTest& test_context = testContext(per_endpoint_stats); + Envoy::Server::StatsParams params; + Envoy::Buffer::OwnedImpl response; + params.parse("?format=prometheus", response); + // per_endpoint_stats: 418M for true, 261M for false + const uint64_t lower_limit = per_endpoint_stats ? 400 * 1000 * 1000 : 200 * 1000 * 1000; + const uint64_t upper_limit = per_endpoint_stats ? 420 * 1000 * 1000 : 300 * 1000 * 1000; + + uint64_t count; + for (auto _ : state) { // NOLINT + count = test_context.handlerStats(params); + RELEASE_ASSERT(count > lower_limit, "expected count > lower_limit"); + RELEASE_ASSERT(count < upper_limit, "expected count < upper_limit"); + } + + auto label = absl::StrCat("output per iteration: ", count); + state.SetLabel(label); +} +BENCHMARK_CAPTURE(BM_PrometheusFull, per_endpoint_stats_disabled, false) + ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(BM_PrometheusFull, per_endpoint_stats_enabled, true) + ->Unit(benchmark::kMillisecond); + // NOLINTNEXTLINE(readability-identifier-naming) static void BM_HistogramsJson(benchmark::State& state, bool per_endpoint_stats) { Envoy::Server::StatsHandlerTest& test_context = testContext(per_endpoint_stats); From f946d63e90917e6dae95098ff3d3c81d8f42456f Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 1 May 2026 00:32:18 +0100 Subject: [PATCH 194/750] github/ci: Dont fail on sync cancellation (#44796) Signed-off-by: Ryan Northey --- .github/workflows/envoy-sync.yml | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index aac9e91670a17..dcd5e701e06c9 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -11,18 +11,16 @@ on: concurrency: group: ${{ github.workflow }} - cancel-in-progress: true + jobs: sync: runs-on: ubuntu-24.04 if: >- - ${{ - github.repository == 'envoyproxy/envoy' - && (github.ref_name == 'main') - && (github.event.push - || !contains(github.actor, '[bot]')) - }} + github.repository == 'envoyproxy/envoy' + && (github.ref_name == 'main') + && (github.event.push + || !contains(github.actor, '[bot]')) strategy: fail-fast: false matrix: @@ -32,12 +30,26 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - name: Skip if superseded by a newer commit on main + id: tip + env: + GH_TOKEN: ${{ github.token }} + run: | + TIP=$(gh api "repos/${GITHUB_REPOSITORY}/commits/main" --jq .sha) + echo "Tip of main: ${TIP}" + echo "This commit: ${GITHUB_SHA}" + if [ "${TIP}" != "${GITHUB_SHA}" ]; then + echo "Superseded by ${TIP} - skipping sync of ${{ matrix.downstream }}." + echo "skip=true" >> "$GITHUB_OUTPUT" + fi + - if: steps.tip.outputs.skip != 'true' + uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/actions/dispatch@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - if: steps.tip.outputs.skip != 'true' + uses: envoyproxy/toolshed/actions/dispatch@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main From 4277c198f9be09f19700a2826a71ef8e28d17247 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 11:20:00 +0100 Subject: [PATCH 195/750] build(deps): bump cryptography from 46.0.7 to 47.0.0 in /tools/base (#44800) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 100 ++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 63e41a4e3a41c..85f08be0687db 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -444,56 +444,56 @@ coloredlogs==15.0.1 \ # via # -r requirements.in # aio-run-runner -cryptography==46.0.7 \ - --hash=sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65 \ - --hash=sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832 \ - --hash=sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067 \ - --hash=sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de \ - --hash=sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4 \ - --hash=sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0 \ - --hash=sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b \ - --hash=sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968 \ - --hash=sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef \ - --hash=sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b \ - --hash=sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4 \ - --hash=sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3 \ - --hash=sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308 \ - --hash=sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e \ - --hash=sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163 \ - --hash=sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f \ - --hash=sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee \ - --hash=sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77 \ - --hash=sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85 \ - --hash=sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99 \ - --hash=sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7 \ - --hash=sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83 \ - --hash=sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85 \ - --hash=sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006 \ - --hash=sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb \ - --hash=sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e \ - --hash=sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba \ - --hash=sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325 \ - --hash=sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d \ - --hash=sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1 \ - --hash=sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1 \ - --hash=sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2 \ - --hash=sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0 \ - --hash=sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455 \ - --hash=sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842 \ - --hash=sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457 \ - --hash=sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15 \ - --hash=sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2 \ - --hash=sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c \ - --hash=sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb \ - --hash=sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5 \ - --hash=sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4 \ - --hash=sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902 \ - --hash=sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246 \ - --hash=sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022 \ - --hash=sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f \ - --hash=sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e \ - --hash=sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298 \ - --hash=sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce +cryptography==47.0.0 \ + --hash=sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7 \ + --hash=sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27 \ + --hash=sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd \ + --hash=sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7 \ + --hash=sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001 \ + --hash=sha256:14432c8a9bcb37009784f9594a62fae211a2ae9543e96c92b2a8e4c3cd5cd0c4 \ + --hash=sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca \ + --hash=sha256:160ad728f128972d362e714054f6ba0067cab7fb350c5202a9ae8ae4ce3ef1a0 \ + --hash=sha256:1a405c08857258c11016777e11c02bacbe7ef596faf259305d282272a3a05cbe \ + --hash=sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93 \ + --hash=sha256:20fdbe3e38fb67c385d233c89371fa27f9909f6ebca1cecc20c13518dae65475 \ + --hash=sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe \ + --hash=sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515 \ + --hash=sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10 \ + --hash=sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7 \ + --hash=sha256:34b4358b925a5ea3e14384ca781a2c0ef7ac219b57bb9eacc4457078e2b19f92 \ + --hash=sha256:3fb8fa48075fad7193f2e5496135c6a76ac4b2aa5a38433df0a539296b377829 \ + --hash=sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8 \ + --hash=sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52 \ + --hash=sha256:51c9313e90bd1690ec5a75ed047c27c0b8e6c570029712943d6116ef9a90620b \ + --hash=sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc \ + --hash=sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c \ + --hash=sha256:6eebcaf0df1d21ce1f90605c9b432dd2c4f4ab665ac29a40d5e3fc68f51b5e63 \ + --hash=sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac \ + --hash=sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31 \ + --hash=sha256:7f1207974a904e005f762869996cf620e9bf79ecb4622f148550bb48e0eb35a7 \ + --hash=sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1 \ + --hash=sha256:7fda2f02c9015db3f42bb8a22324a454516ed10a8c29ca6ece6cdbb5efe2a203 \ + --hash=sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7 \ + --hash=sha256:835d2d7f47cdc53b3224e90810fb1d36ca94ea29cc1801fb4c1bc43876735769 \ + --hash=sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923 \ + --hash=sha256:9af828c0d5a65c70ec729cd7495a4bf1a67ecb66417b8f02ff125ab8a6326a74 \ + --hash=sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b \ + --hash=sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb \ + --hash=sha256:9fe6b7c64926c765f9dff301f9c1b867febcda5768868ca084e18589113732ab \ + --hash=sha256:a49a3eb5341b9503fa3000a9a0db033161db90d47285291f53c2a9d2cd1b7f76 \ + --hash=sha256:a9b761f012a943b7de0e828843c5688d0de94a0578d44d6c85a1bae32f87791f \ + --hash=sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7 \ + --hash=sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973 \ + --hash=sha256:be12cb6a204f77ed968bcefe68086eb061695b540a3dd05edac507a3111b25f0 \ + --hash=sha256:cffbba3392df0fa8629bb7f43454ee2925059ee158e23c54620b9063912b86c8 \ + --hash=sha256:ed67ea4e0cfb5faa5bc7ecb6e2b8838f3807a03758eec239d6c21c8769355310 \ + --hash=sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b \ + --hash=sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318 \ + --hash=sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab \ + --hash=sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8 \ + --hash=sha256:f5c3296dab66202f1b18a91fa266be93d6aa0c2806ea3d67762c69f60adc71aa \ + --hash=sha256:f7db373287273d8af1414cf95dc4118b13ffdc62be521997b0f2b270771fef50 \ + --hash=sha256:f9a034b642b960767fb343766ae5ba6ad653f2e890ddd82955aef288ffea8736 # via # -r requirements.in # aioquic From 12cdcd228ffbe1374567882f242bb6b6039c3a0f Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 1 May 2026 12:36:41 +0100 Subject: [PATCH 196/750] ci/codeql: Move clang bundle to RUNNER_TEMP and cleanup (#44806) this is a good idea anyway (surprised it works unpacking where it does) hopefully finally fix libc++ false positives (alert #1881) cleanup previous attempts Signed-off-by: Ryan Northey --- .github/codeql/codeql-config.yml | 15 --------------- .github/workflows/codeql-daily.yml | 18 ++++++++++-------- .github/workflows/codeql-push.yml | 18 ++++++++++-------- 3 files changed, 20 insertions(+), 31 deletions(-) delete mode 100644 .github/codeql/codeql-config.yml diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml deleted file mode 100644 index 66554f85ab465..0000000000000 --- a/.github/codeql/codeql-config.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: "Envoy CodeQL config" - -# The CodeQL workflows download a prebuilt clang bundle and untar it into -# $GITHUB_WORKSPACE/bin/clang18.1.8/. Bazel is then pointed at that path via -# BAZEL_LLVM_PATH, which causes CodeQL to see the bundled libc++ headers as -# project source files. Alerts whose primary location is inside those headers -# (e.g. cpp/new-free-mismatch in ) are structural false positives -# from dataflow losing precision across allocator/template instantiations -- -# they are not fixable in Envoy and are not indicative of real libc++ bugs. -# -# Scope is intentionally narrow: only libc++ (clang bundle) headers, matched -# by the standard v1 layout. Do not broaden to bin/** or to libstdc++ -# (include/c++/** without the v1/ suffix). -paths-ignore: -- '**/include/c++/v1/**' diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 3d76f0e61df8c..aa42cc9cfa2bf 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -53,14 +53,9 @@ jobs: - name: Initialize CodeQL uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # codeql-bundle-v4.35.2 - env: - # NOTE: these env vars are undocumented/experimental - they are likely to require a future update - CODEQL_EXTRACTOR_CPP_OPTION_EXCLUDE_HEADER_PATTERNS: "bin/clang.*/.*" - CODEQL_EXTRACTOR_CPP_WIP_IGNORE_SYSTEM_HEADERS: "true" with: languages: cpp trap-caching: false - config-file: ./.github/codeql/codeql-config.yml - name: Install deps shell: bash @@ -72,8 +67,15 @@ jobs: # - bazel/repository_locations.bzl # - .github/workflows/codeql-push.yml # - https://github.com/envoyproxy/envoy-build-tools/blob/main/build_container/build_container_ubuntu.sh#L84 - mkdir -p bin/clang18.1.8 - cd bin/clang18.1.8 + # + # IMPORTANT: the clang bundle MUST be unpacked outside $GITHUB_WORKSPACE (we use $RUNNER_TEMP). + # The CodeQL C++ extractor treats anything under the source root as project code, so headers + # under a checkout subdirectory would be analysed as Envoy source and produce false positives + # (e.g. cpp/new-free-mismatch in , alert #1881). Keeping it under $RUNNER_TEMP makes + # CodeQL classify those headers as system headers — same as /usr/include/c++/** on a normal + # build. Do NOT move this back into the checkout tree. + mkdir -p "${RUNNER_TEMP}/clang18.1.8" + cd "${RUNNER_TEMP}/clang18.1.8" wget -q https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04.tar.xz tar -xf clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04.tar.xz --strip-components 1 @@ -82,7 +84,7 @@ jobs: bazelisk shutdown bazel build \ -c fastbuild \ - --repo_env=BAZEL_LLVM_PATH="$(realpath bin/clang18.1.8)" \ + --repo_env=BAZEL_LLVM_PATH="${RUNNER_TEMP}/clang18.1.8" \ --spawn_strategy=local \ --discard_analysis_cache \ --nouse_action_cache \ diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 2326b0d77dcdc..7fcb79b280eb3 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -102,14 +102,9 @@ jobs: - name: Initialize CodeQL if: env.CPP_CHANGED == 'true' uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # codeql-bundle-v4.35.2 - env: - # NOTE: these env vars are undocumented/experimental - they are likely to require a future update - CODEQL_EXTRACTOR_CPP_OPTION_EXCLUDE_HEADER_PATTERNS: "bin/clang.*/.*" - CODEQL_EXTRACTOR_CPP_WIP_IGNORE_SYSTEM_HEADERS: "true" with: languages: cpp trap-caching: false - config-file: ./.github/codeql/codeql-config.yml # No-op analysis to satisfy Scorecard SAST check when no C++ changes are present. # Running codeql-action/analyze unconditionally ensures every merge commit gets @@ -133,8 +128,15 @@ jobs: # - bazel/repository_locations.bzl # - .github/workflows/codeql-daily.yml # - https://github.com/envoyproxy/envoy-build-tools/blob/main/build_container/build_container_ubuntu.sh#L84 - mkdir -p bin/clang18.1.8 - cd bin/clang18.1.8 + # + # IMPORTANT: the clang bundle MUST be unpacked outside $GITHUB_WORKSPACE (we use $RUNNER_TEMP). + # The CodeQL C++ extractor treats anything under the source root as project code, so headers + # under a checkout subdirectory would be analysed as Envoy source and produce false positives + # (e.g. cpp/new-free-mismatch in , alert #1881). Keeping it under $RUNNER_TEMP makes + # CodeQL classify those headers as system headers — same as /usr/include/c++/** on a normal + # build. Do NOT move this back into the checkout tree. + mkdir -p "${RUNNER_TEMP}/clang18.1.8" + cd "${RUNNER_TEMP}/clang18.1.8" wget -q https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04.tar.xz tar -xf clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04.tar.xz --strip-components 1 @@ -144,7 +146,7 @@ jobs: bazel shutdown bazel build \ -c fastbuild \ - --repo_env=BAZEL_LLVM_PATH="$(realpath bin/clang18.1.8)" \ + --repo_env=BAZEL_LLVM_PATH="${RUNNER_TEMP}/clang18.1.8" \ --spawn_strategy=local \ --discard_analysis_cache \ --nouse_action_cache \ From 92669b766084b641d05b7df6f290fe9601a8cd07 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 1 May 2026 13:53:36 +0100 Subject: [PATCH 197/750] test/fuzz: Dynamically generate binary fuzz seed (#44807) this removes the checked in binary which is flagging as security smell Signed-off-by: Ryan Northey --- test/common/json/BUILD | 15 ++++- test/common/json/gen_binary_corpus.sh | 62 ++++++++++++++++++ .../json/json_sanitizer_corpus/binary_file | Bin 5698 -> 0 bytes 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100755 test/common/json/gen_binary_corpus.sh delete mode 100644 test/common/json/json_sanitizer_corpus/binary_file diff --git a/test/common/json/BUILD b/test/common/json/BUILD index 0eaaceaf53074..0781e6077e175 100644 --- a/test/common/json/BUILD +++ b/test/common/json/BUILD @@ -66,10 +66,23 @@ envoy_cc_benchmark_binary( ], ) +genrule( + name = "json_sanitizer_corpus_binary_file", + srcs = ["gen_binary_corpus.sh"], + outs = ["json_sanitizer_corpus/binary_file"], + cmd = "bash $(location gen_binary_corpus.sh) $@", +) + +filegroup( + name = "json_sanitizer_corpus_files", + testonly = 1, + srcs = glob(["json_sanitizer_corpus/**"]) + [":json_sanitizer_corpus_binary_file"], +) + envoy_cc_fuzz_test( name = "json_sanitizer_fuzz_test", srcs = ["json_sanitizer_fuzz_test.cc"], - corpus = "json_sanitizer_corpus", + corpus = ":json_sanitizer_corpus_files", rbe_pool = "6gig", deps = [ ":json_sanitizer_test_util_lib", diff --git a/test/common/json/gen_binary_corpus.sh b/test/common/json/gen_binary_corpus.sh new file mode 100755 index 0000000000000..775545f9b43a1 --- /dev/null +++ b/test/common/json/gen_binary_corpus.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Generates a deterministic binary seed corpus file for json_sanitizer_fuzz_test. +# +# The output is designed to exercise the non-UTF-8 / octal-escape fallback path +# of Envoy::Json::sanitize(). libFuzzer mutates from this seed. +# +# Usage: gen_binary_corpus.sh +set -euo pipefail +export LC_ALL=C + +OUT="$1" + +{ + # All 256 single-byte values (\x00..\xff) — covers every single-byte input. + for ((i = 0; i < 256; i++)); do + printf '%b' "\\0$(printf '%o' "$i")" + done + + # Truncated multi-byte UTF-8 starters (missing continuation bytes). + printf '\xc2' # truncated 2-byte starter + printf '\xe0\xa4' # truncated 3-byte starter + printf '\xf0\x9d\x84' # truncated 4-byte starter (treble clef prefix) + + # Invalid continuation byte. + printf '\xc2\xff' + + # Surrogate-range 3-byte encoding — always invalid UTF-8. + printf '\xed\xa0\x80' + + # Overlong encoding of NUL. + printf '\xc0\x80' + + # JSON-significant ASCII (" \ control chars \x00..\x1f \x7f) followed by + # high-bit bytes \x80..\xff to trigger the octal-escape path on every value. + printf '"' + printf '%b' $'\\\\' + for ((i = 0; i < 32; i++)); do + printf '%b' "\\0$(printf '%o' "$i")" + done + printf '\x7f' + for ((i = 128; i < 256; i++)); do + printf '%b' "\\0$(printf '%o' "$i")" + done + + # High-bit bytes embedded in an ASCII context. + printf 'Hello, \xff\xfe\xfd, world!' + + # Deterministic pseudo-random tail: b[i] = (b[i-1] * 31 + 7) & 0xff + # Seed: last byte of prefix above is '!' (ASCII 0x21 = 33). + # Prefix byte count: + # 256 (all bytes) + 6 (truncated starters: 1+2+3) + 2 (invalid cont.) + # + 3 (surrogate) + 2 (overlong NUL) + # + 163 (JSON section: 1 quote + 1 backslash + 32 controls + 1 DEL + 128 high) + # + 18 (Hello world) = 450 bytes + # Tail byte count needed to reach target of 5698: 5698 - 450 = 5248 + val=33 + for ((i = 0; i < 5248; i++)); do + val=$(( (val * 31 + 7) & 255 )) + printf '%b' "\\0$(printf '%o' "$val")" + done + +} > "$OUT" diff --git a/test/common/json/json_sanitizer_corpus/binary_file b/test/common/json/json_sanitizer_corpus/binary_file deleted file mode 100644 index eb70d74b0caaf1a01e1d88e578015a34f089f718..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5698 zcmb8zSx6O87{Kx4QkjZbQeH&swiyvuBgLXcZ&BWtx>;frC|XFUiAEcj7QB>1lu=sL zLrDuokq<4_gZOfVB7`!mh(buo78D=Mtb8cwoS8Y5a{fUBbH6#iv&~r;ovB%~Ha9#h zOz0zAWDAw@2DVz{bu#W$H$*#^!Yh`DSoxbSJYu}WNO^jlOs{ZTS2|t}w=;B%a>_-> zsOt4N`90xoD@Vs!{aZzg^7#wouMlcqI$c<4`cZYJ7%~&2xecyn$>iDKo28rD zF9t6KuK?c(9soZCei-~HcpZ2H_<8U~@LS;bz+1r|f_H+y1OE)Jo(87%{Q({+H=L<_ zJa{7bbnsMg4|taJ$?{&u9xI@HF8Dg|0`NlcYVca{dhqMuE#QyAyQG`u=M$7y&t=p4 z#>$70$rHe5fX@fd0AC932QLQS4!#fk5O^*4aq!dN=fE4muYos#-vw_04}m`ge-7RY z-VZ(mJ^~&s|1g^Np$j|(JQX}0d@*3wQ^35BM;6jC?sZt#1l=26!%bA^2|adhjOj`{1qMZQxJA z-+=dm_kjP89i34|myTRv4caMFUlIi8(dEjNx{sHh~;FrLg!P~*R!M}n> z$`4bfc}@hM1HKqM2iym~9()sc5%{!;=5g(pZd&(iP`(MucYqH Date: Fri, 1 May 2026 10:37:36 -0400 Subject: [PATCH 198/750] bootstrap: move listener manger protos (#44738) Commit Message: move `ListenerManager`, `ApiListenerManager` and `ValidationListenerManager` from listener.proto into bootstrap.proto Additional Description: these are not part of the xDS protocol, not published and only used in Envoy bootstrap proto. So moving them immediately is safe instead of marking them deprecated. Risk Level: high, might break existing bootstrap config Testing: existing tests pass Docs Changes: N Release Notes: N, these are marked with `#not-implemented-hide` Platform Specific Features: N --------- Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- api/envoy/config/bootstrap/v3/bootstrap.proto | 18 ++++++++++++++++++ api/envoy/config/listener/v3/listener.proto | 18 ------------------ mobile/library/cc/engine_builder.cc | 3 ++- .../api_listener_manager/BUILD | 1 + .../api_listener_manager.h | 3 ++- source/common/listener_manager/BUILD | 1 + .../listener_manager/listener_manager_impl.h | 3 ++- .../validation_listener_manager.h | 2 +- 8 files changed, 27 insertions(+), 22 deletions(-) diff --git a/api/envoy/config/bootstrap/v3/bootstrap.proto b/api/envoy/config/bootstrap/v3/bootstrap.proto index 2e745852594ea..f3fe408f76c4b 100644 --- a/api/envoy/config/bootstrap/v3/bootstrap.proto +++ b/api/envoy/config/bootstrap/v3/bootstrap.proto @@ -813,3 +813,21 @@ message MemoryAllocatorManager { // Defaults to ``104857600`` (100 MB). uint64 max_unfreed_memory_bytes = 5; } + +// A placeholder proto so that users can explicitly configure the standard +// Listener Manager via the bootstrap's :ref:`listener_manager `. +// [#not-implemented-hide:] +message ListenerManager { +} + +// A placeholder proto so that users can explicitly configure the standard +// Validation Listener Manager via the bootstrap's :ref:`listener_manager `. +// [#not-implemented-hide:] +message ValidationListenerManager { +} + +// A placeholder proto so that users can explicitly configure the API +// Listener Manager via the bootstrap's :ref:`listener_manager `. +// [#not-implemented-hide:] +message ApiListenerManager { +} diff --git a/api/envoy/config/listener/v3/listener.proto b/api/envoy/config/listener/v3/listener.proto index db297d93e6ed3..9bdbb12a62c3b 100644 --- a/api/envoy/config/listener/v3/listener.proto +++ b/api/envoy/config/listener/v3/listener.proto @@ -448,21 +448,3 @@ message Listener { // to explicitly configure TCP keepalive settings for individual additional addresses. core.v3.TcpKeepalive tcp_keepalive = 37; } - -// A placeholder proto so that users can explicitly configure the standard -// Listener Manager via the bootstrap's :ref:`listener_manager `. -// [#not-implemented-hide:] -message ListenerManager { -} - -// A placeholder proto so that users can explicitly configure the standard -// Validation Listener Manager via the bootstrap's :ref:`listener_manager `. -// [#not-implemented-hide:] -message ValidationListenerManager { -} - -// A placeholder proto so that users can explicitly configure the API -// Listener Manager via the bootstrap's :ref:`listener_manager `. -// [#not-implemented-hide:] -message ApiListenerManager { -} diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index 726e3c1546057..7fe8ce1a346d3 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -3,6 +3,7 @@ #include #include +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/config/core/v3/socket_option.pb.h" #include "envoy/config/metrics/v3/metrics_service.pb.h" #include "envoy/extensions/compression/brotli/decompressor/v3/brotli.pb.h" @@ -1141,7 +1142,7 @@ std::unique_ptr EngineBuilder::generate } #endif // ENVOY_MOBILE_XDS - envoy::config::listener::v3::ApiListenerManager api; + envoy::config::bootstrap::v3::ApiListenerManager api; auto* listener_manager = bootstrap->mutable_listener_manager(); listener_manager->mutable_typed_config()->PackFrom(api); listener_manager->set_name("envoy.listener_manager_impl.api"); diff --git a/mobile/library/common/extensions/listener_managers/api_listener_manager/BUILD b/mobile/library/common/extensions/listener_managers/api_listener_manager/BUILD index a6027931e1aad..96514ed28e2a2 100644 --- a/mobile/library/common/extensions/listener_managers/api_listener_manager/BUILD +++ b/mobile/library/common/extensions/listener_managers/api_listener_manager/BUILD @@ -23,6 +23,7 @@ envoy_cc_extension( "@envoy//envoy/server:listener_manager_interface", "@envoy//source/extensions/api_listeners/default_api_listener:api_listener_lib", "@envoy//source/server:listener_manager_factory_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", ], ) diff --git a/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.h b/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.h index ddec0dd782133..bffdbc8776cc5 100644 --- a/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.h +++ b/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.h @@ -2,6 +2,7 @@ #include +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/server/instance.h" #include "envoy/server/listener_manager.h" @@ -62,7 +63,7 @@ class ApiListenerManagerFactoryImpl : public ListenerManagerFactory { } std::string name() const override { return "envoy.listener_manager_impl.api"; } ProtobufTypes::MessagePtr createEmptyConfigProto() override { - return std::make_unique(); + return std::make_unique(); } }; diff --git a/source/common/listener_manager/BUILD b/source/common/listener_manager/BUILD index ee20038b9aea3..1e9288ef03180 100644 --- a/source/common/listener_manager/BUILD +++ b/source/common/listener_manager/BUILD @@ -63,6 +63,7 @@ envoy_cc_library( "//source/server:listener_manager_factory_lib", "//source/server:transport_socket_config_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", "@envoy_api//envoy/config/metrics/v3:pkg_cc_proto", diff --git a/source/common/listener_manager/listener_manager_impl.h b/source/common/listener_manager/listener_manager_impl.h index 6bd65b333e2c5..ce05eceb2c6d7 100644 --- a/source/common/listener_manager/listener_manager_impl.h +++ b/source/common/listener_manager/listener_manager_impl.h @@ -4,6 +4,7 @@ #include #include "envoy/admin/v3/config_dump.pb.h" +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/config/core/v3/address.pb.h" #include "envoy/config/core/v3/base.pb.h" #include "envoy/config/core/v3/config_source.pb.h" @@ -423,7 +424,7 @@ class DefaultListenerManagerFactoryImpl : public ListenerManagerFactory { return Config::ServerExtensionValues::get().DEFAULT_LISTENER; } ProtobufTypes::MessagePtr createEmptyConfigProto() override { - return std::make_unique(); + return std::make_unique(); } }; diff --git a/source/extensions/listener_managers/validation_listener_manager/validation_listener_manager.h b/source/extensions/listener_managers/validation_listener_manager/validation_listener_manager.h index cb6bea9887536..7700e52064323 100644 --- a/source/extensions/listener_managers/validation_listener_manager/validation_listener_manager.h +++ b/source/extensions/listener_managers/validation_listener_manager/validation_listener_manager.h @@ -85,7 +85,7 @@ class ValidationListenerManagerFactoryImpl : public ListenerManagerFactory { return Config::ServerExtensionValues::get().VALIDATION_LISTENER; } ProtobufTypes::MessagePtr createEmptyConfigProto() override { - return std::make_unique(); + return std::make_unique(); } }; From d9cd10deabaebd3ce8b5ab821624df9d2c34c000 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Fri, 1 May 2026 08:20:01 -0700 Subject: [PATCH 199/750] router: remove upstream failure details from downstream body (#44746) When an upstream request fails, a local response is generated for the failure which previously included %UPSTREAM_TRANSPORT_FAILURE_REASON%. This could include details such as invalid CAs or SAN mismatch, which are inappropriate to send to the client by default. This data is still included in all of uses of this substitution format string (access logs, traces, etc). Fixes #44694 Signed-off-by: Greg Greenway --- changelogs/current.yaml | 8 ++ source/common/router/router.cc | 8 +- source/common/runtime/runtime_features.cc | 1 + test/common/router/router_test.cc | 4 +- .../local_reply_integration_test.cc | 96 +++++++++---------- 5 files changed, 61 insertions(+), 56 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9b8fcc68e7dc7..bbb438b4cca8b 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -5,6 +5,14 @@ behavior_changes: minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* +- area: router + change: | + The upstream transport failure reason (e.g. TLS certificate validation errors) is no longer included + in the HTTP response body sent to downstream clients. It remains available in access logs via + ``%UPSTREAM_TRANSPORT_FAILURE_REASON%``. This behavioral change can be temporarily reverted by setting + runtime guard ``envoy.reloadable_features.hide_transport_failure_reason_in_response_body`` to ``false``. + This is being changed because in many cases the upstream failure details are inappropriate to send to + the downstream client as it discloses too many internal details. - area: golang change: | Reduced the per-cgo-call mutex acquisition on the Golang HTTP filter by making the diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 582943f204a3c..fe3290bccb89e 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -1622,12 +1622,16 @@ void Filter::onUpstreamReset(Http::StreamResetReason reset_reason, const StreamInfo::CoreResponseFlag response_flags = streamResetReasonToResponseFlag(reset_reason); + const bool show_transport_failure = + !transport_failure_reason.empty() && + !Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.hide_transport_failure_reason_in_response_body"); const std::string body = absl::StrCat("upstream connect error or disconnect/reset before headers. ", (is_retry_ ? "retried and the latest " : ""), "reset reason: ", Http::Utility::resetReasonToString(reset_reason), - !transport_failure_reason.empty() ? ", transport failure reason: " : "", - transport_failure_reason); + show_transport_failure ? ", transport failure reason: " : "", + show_transport_failure ? transport_failure_reason : ""); const std::string& basic_details = downstream_response_started_ ? StreamInfo::ResponseCodeDetails::get().LateUpstreamReset : StreamInfo::ResponseCodeDetails::get().EarlyUpstreamReset; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 148f800457ab1..a97925ea9e82e 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -62,6 +62,7 @@ RUNTIME_GUARD(envoy_reloadable_features_grpc_side_stream_flow_control); RUNTIME_GUARD(envoy_reloadable_features_happy_eyeballs_sort_non_ip_addresses); RUNTIME_GUARD(envoy_reloadable_features_header_mutation_url_encode_query_params); RUNTIME_GUARD(envoy_reloadable_features_health_check_after_cluster_warming); +RUNTIME_GUARD(envoy_reloadable_features_hide_transport_failure_reason_in_response_body); RUNTIME_GUARD(envoy_reloadable_features_http1_close_connection_on_zombie_stream_complete); RUNTIME_GUARD(envoy_reloadable_features_http2_discard_host_header); RUNTIME_GUARD(envoy_reloadable_features_http_async_client_retry_respect_buffer_limits); diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 59f740d894d79..4dbd98eeb9e06 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -415,7 +415,7 @@ TEST_F(RouterTest, PoolFailureWithPriority) { })); Http::TestResponseHeaderMapImpl response_headers{ - {":status", "503"}, {"content-length", "146"}, {"content-type", "text/plain"}}; + {":status", "503"}, {"content-length", "98"}, {"content-type", "text/plain"}}; EXPECT_CALL(callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), false)); EXPECT_CALL(callbacks_, encodeData(_, true)); EXPECT_CALL(callbacks_.stream_info_, @@ -448,7 +448,7 @@ TEST_F(RouterTest, PoolFailureDueToConnectTimeout) { })); Http::TestResponseHeaderMapImpl response_headers{ - {":status", "503"}, {"content-length", "134"}, {"content-type", "text/plain"}}; + {":status", "503"}, {"content-length", "91"}, {"content-type", "text/plain"}}; EXPECT_CALL(callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), false)); EXPECT_CALL(callbacks_, encodeData(_, true)); EXPECT_CALL(callbacks_.stream_info_, diff --git a/test/integration/local_reply_integration_test.cc b/test/integration/local_reply_integration_test.cc index 95b7234dab05d..df2af113a0417 100644 --- a/test/integration/local_reply_integration_test.cc +++ b/test/integration/local_reply_integration_test.cc @@ -49,19 +49,11 @@ TEST_P(LocalReplyIntegrationTest, MapStatusCodeAndFormatToJson) { initialize(); std::string expected_body; - if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - expected_body = R"({ - "level": "TRACE", - "user_agent": null, - "response_body": "upstream connect error or disconnect/reset before headers. reset reason: connection termination, transport failure reason: QUIC_NO_ERROR|FROM_PEER|Closed by application" - })"; - } else { - expected_body = R"({ + expected_body = R"({ "level": "TRACE", "user_agent": null, "response_body": "upstream connect error or disconnect/reset before headers. reset reason: connection termination" })"; - } codec_client_ = makeHttpConnection(lookupPort("http")); @@ -92,11 +84,7 @@ TEST_P(LocalReplyIntegrationTest, MapStatusCodeAndFormatToJson) { EXPECT_TRUE(response->complete()); EXPECT_EQ("application/json-custom", response->headers().ContentType()->value().getStringView()); - if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - EXPECT_EQ("223", response->headers().ContentLength()->value().getStringView()); - } else { - EXPECT_EQ("150", response->headers().ContentLength()->value().getStringView()); - } + EXPECT_EQ("150", response->headers().ContentLength()->value().getStringView()); EXPECT_EQ("550", response->headers().Status()->value().getStringView()); if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { EXPECT_EQ(response->headers().getProxyStatusValue(), @@ -186,17 +174,10 @@ TEST_P(LocalReplyIntegrationTest, MapStatusCodeAndFormatToJson4Grpc) { std::string expected_grpc_message; - if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - expected_grpc_message = R"({ - "code": 503, - "message":"upstream connect error or disconnect/reset before headers. reset reason: connection termination, transport failure reason: QUIC_NO_ERROR|FROM_PEER|Closed by application" -})"; - } else { - expected_grpc_message = R"({ + expected_grpc_message = R"({ "code": 503, "message":"upstream connect error or disconnect/reset before headers. reset reason: connection termination" })"; - } codec_client_ = makeHttpConnection(lookupPort("http")); @@ -247,17 +228,9 @@ TEST_P(LocalReplyIntegrationTest, MapStatusCodeAndFormat2Text4Grpc) { initialize(); // Note: there should be an %0A at the end. - std::string expected_grpc_message; - if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - expected_grpc_message = - "upstream connect error or disconnect/reset before headers. reset reason:" - " connection termination, transport failure reason: " - "QUIC_NO_ERROR|FROM_PEER|Closed by application:503:path=/package.service/method%0A"; - } else { - expected_grpc_message = - "upstream connect error or disconnect/reset before headers. reset reason:" - " connection termination:503:path=/package.service/method%0A"; - } + std::string expected_grpc_message = + "upstream connect error or disconnect/reset before headers. reset reason:" + " connection termination:503:path=/package.service/method%0A"; codec_client_ = makeHttpConnection(lookupPort("http")); @@ -411,20 +384,11 @@ TEST_P(LocalReplyIntegrationTest, ShouldNotMatchAnyFilter) { setLocalReplyConfig(yaml); initialize(); - std::string expected_body; - if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - expected_body = R"({ - "level": "TRACE", - "response_flags": "UC", - "response_body": "upstream connect error or disconnect/reset before headers. reset reason: connection termination, transport failure reason: QUIC_NO_ERROR|FROM_PEER|Closed by application" - })"; - } else { - expected_body = R"({ + std::string expected_body = R"({ "level": "TRACE", "response_flags": "UC", "response_body": "upstream connect error or disconnect/reset before headers. reset reason: connection termination" })"; - } codec_client_ = makeHttpConnection(lookupPort("http")); @@ -455,11 +419,7 @@ TEST_P(LocalReplyIntegrationTest, ShouldNotMatchAnyFilter) { EXPECT_TRUE(response->complete()); EXPECT_EQ("application/json", response->headers().ContentType()->value().getStringView()); - if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - EXPECT_EQ("227", response->headers().ContentLength()->value().getStringView()); - } else { - EXPECT_EQ("154", response->headers().ContentLength()->value().getStringView()); - } + EXPECT_EQ("154", response->headers().ContentLength()->value().getStringView()); EXPECT_EQ("503", response->headers().Status()->value().getStringView()); // Check if returned json is same as expected EXPECT_TRUE(TestUtility::jsonStringEqual(response->body(), expected_body)); @@ -523,13 +483,45 @@ TEST_P(LocalReplyIntegrationTest, ShouldMapResponseCodeAndMapToDefaultTextRespon EXPECT_TRUE(response->complete()); EXPECT_EQ("text/plain", response->headers().ContentType()->value().getStringView()); - if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { - EXPECT_EQ("168", response->headers().ContentLength()->value().getStringView()); + EXPECT_EQ("95", response->headers().ContentLength()->value().getStringView()); + + EXPECT_EQ("551", response->headers().Status()->value().getStringView()); + + EXPECT_EQ(response->body(), "upstream connect error or disconnect/reset before headers. reset " + "reason: connection termination"); +} + +// When the runtime guard is disabled, transport failure reason should appear in the response body. +TEST_P(LocalReplyIntegrationTest, TransportFailureReasonInBodyWhenRuntimeGuardDisabled) { + config_helper_.addRuntimeOverride( + "envoy.reloadable_features.hide_transport_failure_reason_in_response_body", "false"); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto encoder_decoder = + codec_client_->startRequest(Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "sni.lyft.com"}}); + auto response = std::move(encoder_decoder.second); + + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + ASSERT_TRUE(fake_upstream_connection_->close()); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + ASSERT_TRUE(response->waitForEndStream()); + + if (downstream_protocol_ == Http::CodecType::HTTP1) { + ASSERT_TRUE(codec_client_->waitForDisconnect()); } else { - EXPECT_EQ("95", response->headers().ContentLength()->value().getStringView()); + codec_client_->close(); } - EXPECT_EQ("551", response->headers().Status()->value().getStringView()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("503", response->headers().Status()->value().getStringView()); if (GetParam().upstream_protocol == Http::CodecType::HTTP3) { EXPECT_EQ(response->body(), "upstream connect error or disconnect/reset before headers. reset " From a7a77b78f5db1f96c9b6b59ff52a053d75bd353a Mon Sep 17 00:00:00 2001 From: Anton Kanugalawattage Date: Fri, 1 May 2026 11:55:10 -0400 Subject: [PATCH 200/750] tcp_proxy: support formatter extensions in tunneling config headers (#44599) Fixes https://github.com/envoyproxy/envoy/issues/44532 --------- Signed-off-by: Anton Kanugalawattage --- .../network/tcp_proxy/v3/tcp_proxy.proto | 9 +++- source/common/router/header_parser.cc | 8 +++- source/common/router/header_parser.h | 9 ++++ source/common/tcp_proxy/tcp_proxy.cc | 26 ++++++++-- test/common/router/BUILD | 1 + test/common/router/header_formatter_test.cc | 20 ++++++++ test/common/tcp_proxy/BUILD | 2 + test/common/tcp_proxy/upstream_test.cc | 28 +++++++++++ .../filters/network/tcp_proxy/BUILD | 2 + .../filters/network/tcp_proxy/config_test.cc | 47 +++++++++++++++++++ 10 files changed, 147 insertions(+), 5 deletions(-) diff --git a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto index 280baff908c4a..b3dd0f63191c2 100644 --- a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto +++ b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto @@ -6,6 +6,7 @@ import "envoy/config/accesslog/v3/accesslog.proto"; import "envoy/config/core/v3/backoff.proto"; import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/config_source.proto"; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/proxy_protocol.proto"; import "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto"; import "envoy/type/v3/hash_policy.proto"; @@ -119,7 +120,7 @@ message TcpProxy { // Configuration for tunneling TCP over other transports or application layers. // Tunneling is supported over HTTP/1.1 and HTTP/2. The upstream protocol is // determined by the cluster configuration. - // [#next-free-field: 10] + // [#next-free-field: 11] message TunnelingConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.tcp_proxy.v2.TcpProxy.TunnelingConfig"; @@ -200,6 +201,12 @@ message TcpProxy { // This enables customizing the key used by access log formatters such as // ``%DYNAMIC_METADATA(envoy.filters.network.tcp_proxy:)%``. string request_id_metadata_key = 9; + + // Specifies a collection of Formatter plugins that can be used in substitution formatters + // in ``headers_to_add``. + // See the formatters extensions documentation for details. + // [#extension-category: envoy.formatter] + repeated config.core.v3.TypedExtensionConfig formatters = 10; } message OnDemand { diff --git a/source/common/router/header_parser.cc b/source/common/router/header_parser.cc index 8e9a79b57df12..edc49fa7f3083 100644 --- a/source/common/router/header_parser.cc +++ b/source/common/router/header_parser.cc @@ -94,10 +94,16 @@ HeadersToAddEntry::HeadersToAddEntry(const HeaderValue& header_value, absl::StatusOr HeaderParser::configure(const Protobuf::RepeatedPtrField& headers_to_add) { + return configure(headers_to_add, Formatter::CommandParserPtrVector{}); +} + +absl::StatusOr +HeaderParser::configure(const Protobuf::RepeatedPtrField& headers_to_add, + const Formatter::CommandParserPtrVector& command_parsers) { HeaderParserPtr header_parser(new HeaderParser()); header_parser->headers_to_add_.reserve(headers_to_add.size()); for (const auto& header_value_option : headers_to_add) { - auto entry_or_error = HeadersToAddEntry::create(header_value_option); + auto entry_or_error = HeadersToAddEntry::create(header_value_option, command_parsers); RETURN_IF_NOT_OK_REF(entry_or_error.status()); header_parser->headers_to_add_.emplace_back( Http::LowerCaseString(header_value_option.header().key()), diff --git a/source/common/router/header_parser.h b/source/common/router/header_parser.h index dd7bed5814b9b..f2dd3fe135d45 100644 --- a/source/common/router/header_parser.h +++ b/source/common/router/header_parser.h @@ -71,6 +71,15 @@ class HeaderParser : public Http::HeaderEvaluator { static absl::StatusOr configure(const Protobuf::RepeatedPtrField& headers_to_add); + /* + * @param headers_to_add defines the headers to add during calls to evaluateHeaders. + * @param command_parsers custom formatter command parsers (e.g. for %SECRET()% substitution). + * @return HeaderParserPtr a configured HeaderParserPtr. + */ + static absl::StatusOr + configure(const Protobuf::RepeatedPtrField& headers_to_add, + const Formatter::CommandParserPtrVector& command_parsers); + /* * @param headers_to_add defines headers to add during calls to evaluateHeaders. * @param append_action defines action taken to append/overwrite the given value for an existing diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index 9b4597b1fbc0d..edc244c73acae 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -27,6 +27,7 @@ #include "source/common/config/metadata.h" #include "source/common/config/utility.h" #include "source/common/config/well_known_names.h" +#include "source/common/formatter/substitution_format_string.h" #include "source/common/http/request_id_extension_impl.h" #include "source/common/network/application_protocol.h" #include "source/common/network/proxy_protocol_filter_state.h" @@ -39,6 +40,7 @@ #include "source/common/stream_info/stream_id_provider_impl.h" #include "source/common/stream_info/uint64_accessor_impl.h" #include "source/common/tracing/http_tracer_impl.h" +#include "source/server/generic_factory_context.h" #include "absl/container/flat_hash_set.h" @@ -941,13 +943,31 @@ const std::string& TunnelResponseTrailers::key() { CONSTRUCT_ON_FIRST_USE(std::string, "envoy.tcp_proxy.propagate_response_trailers"); } +Router::HeaderParserPtr buildTunnelingHeaderParser( + const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy::TunnelingConfig& + tunneling_config, + Server::Configuration::FactoryContext& context) { + if (tunneling_config.formatters().empty()) { + return THROW_OR_RETURN_VALUE( + Envoy::Router::HeaderParser::configure(tunneling_config.headers_to_add()), + Router::HeaderParserPtr); + } + + Server::GenericFactoryContextImpl generic_context(context); + auto command_parsers = + THROW_OR_RETURN_VALUE(Formatter::SubstitutionFormatStringUtils::parseFormatters( + tunneling_config.formatters(), generic_context), + Formatter::CommandParserPtrVector); + return THROW_OR_RETURN_VALUE( + Envoy::Router::HeaderParser::configure(tunneling_config.headers_to_add(), command_parsers), + Router::HeaderParserPtr); +} + TunnelingConfigHelperImpl::TunnelingConfigHelperImpl( Stats::Scope& stats_scope, const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy& config_message, Server::Configuration::FactoryContext& context) - : header_parser_(THROW_OR_RETURN_VALUE(Envoy::Router::HeaderParser::configure( - config_message.tunneling_config().headers_to_add()), - Router::HeaderParserPtr)), + : header_parser_(buildTunnelingHeaderParser(config_message.tunneling_config(), context)), propagate_response_headers_(config_message.tunneling_config().propagate_response_headers()), propagate_response_trailers_(config_message.tunneling_config().propagate_response_trailers()), post_path_(config_message.tunneling_config().post_path()), diff --git a/test/common/router/BUILD b/test/common/router/BUILD index efc8fd43a194e..f7d3909fa46c0 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -510,6 +510,7 @@ envoy_cc_test( "//source/common/router:header_parser_lib", "//source/common/router:string_accessor_lib", "//source/common/stream_info:filter_state_lib", + "//test/common/formatter:command_extension_lib", "//test/common/stream_info:test_int_accessor_lib", "//test/mocks/http:http_mocks", "//test/mocks/server:server_factory_context_mocks", diff --git a/test/common/router/header_formatter_test.cc b/test/common/router/header_formatter_test.cc index 8a776b8a54147..cd9acd6969c02 100644 --- a/test/common/router/header_formatter_test.cc +++ b/test/common/router/header_formatter_test.cc @@ -14,6 +14,7 @@ #include "source/common/router/string_accessor_impl.h" #include "source/common/stream_info/filter_state_impl.h" +#include "test/common/formatter/command_extension.h" #include "test/common/stream_info/test_int_accessor.h" #include "test/mocks/http/mocks.h" #include "test/mocks/server/server_factory_context.h" @@ -1202,6 +1203,25 @@ response_headers_to_remove: ["x-baz-header"] } } +TEST(HeaderParserTest, ConfigureWithCommandParsers) { + Formatter::CommandParserPtrVector command_parsers; + command_parsers.push_back(std::make_unique()); + + Protobuf::RepeatedPtrField to_add; + auto* header = to_add.Add(); + header->mutable_header()->set_key("x-secret"); + header->mutable_header()->set_value("Bearer %COMMAND_EXTENSION()%"); + + HeaderParserPtr parser = HeaderParser::configure(to_add, command_parsers).value(); + + Http::TestRequestHeaderMapImpl header_map{{":method", "POST"}}; + NiceMock stream_info; + parser->evaluateHeaders(header_map, stream_info); + + EXPECT_TRUE(header_map.has("x-secret")); + EXPECT_EQ("Bearer TestFormatter", header_map.get_("x-secret")); +} + } // namespace } // namespace Router } // namespace Envoy diff --git a/test/common/tcp_proxy/BUILD b/test/common/tcp_proxy/BUILD index 34f79b44f5524..d85cc1453662f 100644 --- a/test/common/tcp_proxy/BUILD +++ b/test/common/tcp_proxy/BUILD @@ -83,6 +83,7 @@ envoy_cc_test( deps = [ "//source/common/formatter:formatter_extension_lib", "//source/common/tcp_proxy", + "//test/common/formatter:command_extension_lib", "//test/common/memory:memory_test_utility_lib", "//test/mocks/http:http_mocks", "//test/mocks/router:router_filter_interface", @@ -92,6 +93,7 @@ envoy_cc_test( "//test/mocks/tcp:tcp_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:load_balancer_context_mock", + "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/request_id/uuid/v3:pkg_cc_proto", diff --git a/test/common/tcp_proxy/upstream_test.cc b/test/common/tcp_proxy/upstream_test.cc index f8e3a237158a4..7d3287efc27da 100644 --- a/test/common/tcp_proxy/upstream_test.cc +++ b/test/common/tcp_proxy/upstream_test.cc @@ -6,6 +6,7 @@ #include "source/common/tcp_proxy/tcp_proxy.h" #include "source/common/tcp_proxy/upstream.h" +#include "test/common/formatter/command_extension.h" #include "test/common/memory/memory_test_utility.h" #include "test/mocks/http/mocks.h" #include "test/mocks/http/stream_encoder.h" @@ -16,6 +17,7 @@ #include "test/mocks/upstream/load_balancer_context.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" +#include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "gmock/gmock.h" @@ -572,6 +574,32 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHeadersWithDownstreamInfo) this->upstream_->setRequestEncoder(this->encoder_, false); } +TEST(TunnelingConfigHelperImplTest, FormatterExtensionRendersInHeader) { + Envoy::Formatter::TestCommandFactory test_factory; + Registry::InjectFactory register_factory(test_factory); + + NiceMock context; + NiceMock store; + Stats::MockScope& scope = store.mockScope(); + + TcpProxy config; + auto* tunneling = config.mutable_tunneling_config(); + tunneling->set_hostname("host.example.com:443"); + auto* formatter = tunneling->add_formatters(); + formatter->set_name("envoy.formatter.TestFormatter"); + formatter->mutable_typed_config()->PackFrom(Protobuf::StringValue()); + auto* header = tunneling->add_headers_to_add(); + header->mutable_header()->set_key("x-custom"); + header->mutable_header()->set_value("%COMMAND_EXTENSION()%"); + + TunnelingConfigHelperImpl helper(scope, config, context); + + Http::TestRequestHeaderMapImpl headers; + NiceMock stream_info; + helper.headerEvaluator().evaluateHeaders(headers, {&headers, nullptr}, stream_info); + EXPECT_EQ(headers.get_("x-custom"), "TestFormatter"); +} + TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHostnameWithDownstreamInfoRequestedServerName) { this->tcp_proxy_.mutable_tunneling_config()->set_hostname("%REQUESTED_SERVER_NAME%:443"); diff --git a/test/extensions/filters/network/tcp_proxy/BUILD b/test/extensions/filters/network/tcp_proxy/BUILD index de7bdd728935b..329e7fe705e3c 100644 --- a/test/extensions/filters/network/tcp_proxy/BUILD +++ b/test/extensions/filters/network/tcp_proxy/BUILD @@ -18,7 +18,9 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/filters/network/tcp_proxy:config", + "//test/common/formatter:command_extension_lib", "//test/mocks/server:factory_context_mocks", + "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/network/tcp_proxy/config_test.cc b/test/extensions/filters/network/tcp_proxy/config_test.cc index f1d52b1921a49..e42a52e9c86a6 100644 --- a/test/extensions/filters/network/tcp_proxy/config_test.cc +++ b/test/extensions/filters/network/tcp_proxy/config_test.cc @@ -5,7 +5,9 @@ #include "source/extensions/filters/network/tcp_proxy/config.h" +#include "test/common/formatter/command_extension.h" #include "test/mocks/server/factory_context.h" +#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -73,6 +75,51 @@ TEST(ConfigTest, ConfigTest) { cb(connection); } +TEST(ConfigTest, TunnelingConfigWithFormatters) { + Envoy::Formatter::TestCommandFactory test_factory; + Registry::InjectFactory register_factory(test_factory); + + NiceMock context; + ConfigFactory factory; + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = + *dynamic_cast( + factory.createEmptyConfigProto().get()); + config.set_stat_prefix("prefix"); + config.set_cluster("cluster"); + auto* tunneling = config.mutable_tunneling_config(); + tunneling->set_hostname("example.com:80"); + + auto* header = tunneling->add_headers_to_add(); + auto* hdr = header->mutable_header(); + hdr->set_key("x-custom"); + hdr->set_value("%COMMAND_EXTENSION()%"); + + auto* formatter = tunneling->add_formatters(); + formatter->set_name("envoy.formatter.TestFormatter"); + formatter->mutable_typed_config()->PackFrom(Protobuf::StringValue()); + + EXPECT_TRUE(factory.createFilterFactoryFromProto(config, context).ok()); +} + +TEST(ConfigTest, TunnelingConfigWithUnknownFormatter) { + NiceMock context; + ConfigFactory factory; + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = + *dynamic_cast( + factory.createEmptyConfigProto().get()); + config.set_stat_prefix("prefix"); + config.set_cluster("cluster"); + auto* tunneling = config.mutable_tunneling_config(); + tunneling->set_hostname("example.com:80"); + + auto* formatter = tunneling->add_formatters(); + formatter->set_name("envoy.formatter.does_not_exist"); + formatter->mutable_typed_config()->PackFrom(Protobuf::StringValue()); + + EXPECT_THROW_WITH_REGEX(factory.createFilterFactoryFromProto(config, context).IgnoreError(), + EnvoyException, "envoy.formatter.does_not_exist"); +} + } // namespace TcpProxy } // namespace NetworkFilters } // namespace Extensions From 2a8d390f8395e065f19d1dbb74657b8bc59cf030 Mon Sep 17 00:00:00 2001 From: danzh Date: Fri, 1 May 2026 12:31:20 -0400 Subject: [PATCH 201/750] Update QUICHE from e68fe05e7 to 13384c955 (#44811) Update QUICHE from e68fe05e7 to 13384c955 https://github.com/google/quiche/compare/e68fe05e7..13384c955 ``` $ git log e68fe05e7..13384c955 --date=short --no-merges --format="%ad %al %s" 2026-04-29 ianswett Enable gfe2_reloadable_flag_quic_reject_empty_cid_in_ncid in Chrome. 2026-04-29 ianswett Deprecate gfe2_reloadable_flag_quic_no_path_degrading_before_handshake_confirmed. 2026-04-28 ripere Add gzip response decompression support to Masque OHTTP client. 2026-04-28 dschinazi MasqueOhttpClient: Print chunked body to stdout 2026-04-27 dschinazi Deprecate quic_stop_sending_legacy_version_info and quic_stop_parsing_legacy_version_info 2026-04-27 ianswett BBR3: Update startup pacing gain, drain gain, and max ack height window to match the BBR IETF draft: https://datatracker.ietf.org/doc/draft-ietf-ccwg-bbr/ 2026-04-23 rch Additional PendingStream cleanup * Remove unused constructors * Pass PendingStream by reference in CreateIncomingStream 2026-04-23 rch Remove unused QuicSession::CreateIncomingStream(PendingStream*). Remove overrides in subclasses which considered it an error to call this method. Move from override to virtual in those subclasses which actually use this method. 2026-04-22 wub No public description 2026-04-22 rch Refactor QuicSession PendingStream methods to take references. 2026-04-22 ianswett Refactor: Cache Bbr2DebugState in simulator tests. 2026-04-22 rch Refactor QuicStream constructor to take PendingStream by reference. 2026-04-21 martinduke Allow MoqtClient and MoqtServer to control session parameters. 2026-04-21 martinduke Move some non-message-related data structures out of moqt_messages.h 2026-04-21 ianswett Refactor BBR2/BBR3 initialization and inline some methods. 2026-04-21 rch Convert quic::PendingStream constructor to take QuicSession reference. 2026-04-20 ianswett Refactor BBR2/BBR3 parameters by moving kDefaultMinimumCongestionWindow and kInitialPacingGain into Bbr2Params. 2026-04-20 quiche-dev Add ohttp_ping_pong_mode_{gateway_name} to test ping-pong interactive chunking. It guarantees there is no buffering between the client and server, bidirectionally. 2026-04-20 ianswett Refactor: Extract DebugState from Bbr2Sender and Bbr3Sender to a common header. 2026-04-20 martinduke Get rid of moqt::SubscribeWindow. 2026-04-20 ianswett Refactor and simplify BBR3. ``` Risk Level: low Testing: existing tests pass Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A --------- Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- bazel/deps.yaml | 2 +- bazel/external/quiche.BUILD | 21 +++++++++++++++++++ bazel/repository_locations.bzl | 4 ++-- .../common/quic/envoy_quic_client_session.cc | 7 ------- .../common/quic/envoy_quic_client_session.h | 1 - .../common/quic/envoy_quic_server_session.cc | 6 ------ .../common/quic/envoy_quic_server_session.h | 1 - 7 files changed, 24 insertions(+), 18 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 2115ef7f2bf0d..6799e18a42e2e 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -296,7 +296,7 @@ quiche: project_name: "QUICHE" project_desc: "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols" project_url: "https://github.com/google/quiche" - release_date: "2026-04-20" + release_date: "2026-04-30" use_category: - controlplane - dataplane_core diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 885ddcbc8bfc4..0a6ef9a0c8b55 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -2112,6 +2112,26 @@ envoy_quic_cc_library( ], ) +envoy_quic_cc_library( + name = "quic_core_congestion_control_bbr3_sender_lib", + srcs = ["quiche/quic/core/congestion_control/bbr3_sender.cc"], + hdrs = ["quiche/quic/core/congestion_control/bbr3_sender.h"], + deps = [ + ":quic_core_bandwidth_lib", + ":quic_core_congestion_control_bandwidth_sampler_lib", + ":quic_core_congestion_control_bbr2_lib", + ":quic_core_congestion_control_bbr_lib", + ":quic_core_congestion_control_congestion_control_interface_lib", + ":quic_core_congestion_control_rtt_stats_lib", + ":quic_core_congestion_control_windowed_filter_lib", + ":quic_core_crypto_encryption_lib", + ":quic_core_tag_lib", + ":quic_core_types_lib", + ":quic_platform_base", + ":quiche_common_print_elements_lib", + ], +) + envoy_quic_cc_library( name = "quic_core_congestion_control_general_loss_algorithm_lib", srcs = ["quiche/quic/core/congestion_control/general_loss_algorithm.cc"], @@ -2159,6 +2179,7 @@ envoy_quic_cc_library( ":quic_core_bandwidth_lib", ":quic_core_config_lib", ":quic_core_congestion_control_bbr2_lib", + ":quic_core_congestion_control_bbr3_sender_lib", ":quic_core_congestion_control_bbr_lib", ":quic_core_congestion_control_prague_sender_lib", ":quic_core_congestion_control_tcp_cubic_bytes_lib", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 99ce9a21accd6..0e6150efa0a77 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -553,8 +553,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/simdutf/simdutf/releases/download/v{version}/singleheader.zip"], ), quiche = dict( - version = "e68fe05e70da74a3ea282d927c76f76b4bc4e710", - sha256 = "08033a0886b470d4ea836a6b785ef6ef7d638265e5523a37718cdd6d1ef6a409", + version = "13384c955f76e3a2b157f7b613ce6c02d594770f", + sha256 = "a2310cab30a46e9ccf533cd10632d44852d46784f4192e202cc53b6e353d5876", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", ), diff --git a/source/common/quic/envoy_quic_client_session.cc b/source/common/quic/envoy_quic_client_session.cc index 2e0388b22b987..70948cf74efe0 100644 --- a/source/common/quic/envoy_quic_client_session.cc +++ b/source/common/quic/envoy_quic_client_session.cc @@ -210,13 +210,6 @@ quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::QuicStr return nullptr; } -quic::QuicSpdyStream* -EnvoyQuicClientSession::CreateIncomingStream(quic::PendingStream* /*pending*/) { - // Envoy doesn't support server push. - IS_ENVOY_BUG("unexpectes server push call"); - return nullptr; -} - bool EnvoyQuicClientSession::hasDataToWrite() { return HasDataToWrite(); } const quic::QuicConnection* EnvoyQuicClientSession::quicConnection() const { diff --git a/source/common/quic/envoy_quic_client_session.h b/source/common/quic/envoy_quic_client_session.h index ba941ea2f59c5..b69459a6aa4ff 100644 --- a/source/common/quic/envoy_quic_client_session.h +++ b/source/common/quic/envoy_quic_client_session.h @@ -122,7 +122,6 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, std::unique_ptr CreateClientStream() override; // quic::QuicSpdySession quic::QuicSpdyStream* CreateIncomingStream(quic::QuicStreamId id) override; - quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override; std::unique_ptr CreateQuicCryptoStream() override; bool ShouldCreateOutgoingBidirectionalStream() override { // quic::QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream() diff --git a/source/common/quic/envoy_quic_server_session.cc b/source/common/quic/envoy_quic_server_session.cc index 09f6b1630a5d8..f0fabfea8df25 100644 --- a/source/common/quic/envoy_quic_server_session.cc +++ b/source/common/quic/envoy_quic_server_session.cc @@ -118,12 +118,6 @@ quic::QuicSpdyStream* EnvoyQuicServerSession::CreateIncomingStream(quic::QuicStr return stream; } -quic::QuicSpdyStream* -EnvoyQuicServerSession::CreateIncomingStream(quic::PendingStream* /*pending*/) { - IS_ENVOY_BUG("Unexpected disallowed server push call"); - return nullptr; -} - quic::QuicSpdyStream* EnvoyQuicServerSession::CreateOutgoingBidirectionalStream() { IS_ENVOY_BUG("Unexpected disallowed server initiated stream"); return nullptr; diff --git a/source/common/quic/envoy_quic_server_session.h b/source/common/quic/envoy_quic_server_session.h index 488a0b5e1e94f..ef233194c8688 100644 --- a/source/common/quic/envoy_quic_server_session.h +++ b/source/common/quic/envoy_quic_server_session.h @@ -140,7 +140,6 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, // quic::QuicSession // Overridden to create stream as encoder and associate it with an decoder. quic::QuicSpdyStream* CreateIncomingStream(quic::QuicStreamId id) override; - quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override; quic::QuicSpdyStream* CreateOutgoingBidirectionalStream() override; quic::HttpDatagramSupport LocalHttpDatagramSupport() override { return http_datagram_support_; } From aee34d08379ce10bf4b89dc708785a62841aa754 Mon Sep 17 00:00:00 2001 From: Xin Date: Fri, 1 May 2026 17:15:23 -0400 Subject: [PATCH 202/750] ext_proc: no need a dup of filter_callbacks_ in directional ProcessorState, also better alignments on fields. (#44604) Commit Message: dedup directional de/encoder_filter_callbacks_ since they are the same as filter_callbacks_. Additional Description: Also move enums together, gives better alignment than 4,8,4,8 ==> 4,4,8,8. Risk Level: LOW Testing: existing tests. Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Xin Zhuang --- .../filters/http/ext_proc/ext_proc.cc | 8 +- .../filters/http/ext_proc/processor_state.cc | 80 +++++++++---------- .../filters/http/ext_proc/processor_state.h | 24 +++--- 3 files changed, 54 insertions(+), 58 deletions(-) diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 43fe0899581e0..f5c8d30340337 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -707,7 +707,7 @@ void Filter::sendRequest(const ProcessorState& state, ProcessingRequest&& req, b if (processing_request_modifier_) { ProcessingRequestModifier::Params params = { .traffic_direction = state.trafficDirection(), - .callbacks = state.callbacks(), + .callbacks = state.filterCallbacks(), .request_headers = state.requestHeaders(), .response_headers = state.responseHeaders(), .response_trailers = state.responseTrailers(), @@ -1536,7 +1536,7 @@ void Filter::onNewTimeout(const Protobuf::Duration& override_message_timeout) { void Filter::addDynamicMetadata(const ProcessorState& state, ProcessingRequest& req) { // get the callbacks from the ProcessorState. This will be the appropriate // callbacks for the current state of the filter - auto* cb = state.callbacks(); + auto* cb = state.filterCallbacks(); envoy::config::core::v3::Metadata forwarding_metadata; // Forward cluster metadata if so configured. @@ -1610,8 +1610,8 @@ void Filter::addAttributes(ProcessorState& state, ProcessingRequest& req) { } auto activation_ptr = Filters::Common::Expr::createActivation( - &config_->expressionManager().localInfo(), state.callbacks()->streamInfo(), - state.callbacks()->streamInfo().getRequestHeaders(), + &config_->expressionManager().localInfo(), state.filterCallbacks()->streamInfo(), + state.filterCallbacks()->streamInfo().getRequestHeaders(), dynamic_cast(state.responseHeaders()), dynamic_cast(state.responseTrailers())); auto attributes = state.evaluateAttributes(config_->expressionManager(), *activation_ptr); diff --git a/source/extensions/filters/http/ext_proc/processor_state.cc b/source/extensions/filters/http/ext_proc/processor_state.cc index 91c86d5089910..ac1e9eae0ebfd 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.cc +++ b/source/extensions/filters/http/ext_proc/processor_state.cc @@ -25,16 +25,16 @@ using envoy::service::ext_proc::v3::TrailersResponse; void ProcessorState::onStartProcessorCall(Event::TimerCb cb, std::chrono::milliseconds timeout, CallbackState callback_state, bool send_body) { - ENVOY_STREAM_LOG(debug, "Start external processing call", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "Start external processing call", *filterCallbacks()); callback_state_ = callback_state; // Skip starting timer For FULL_DUPLEX_STREAMED body mode. if (bodyMode() != ProcessingMode::FULL_DUPLEX_STREAMED) { if (message_timer_ == nullptr) { - message_timer_ = filter_callbacks_->dispatcher().createTimer(cb); + message_timer_ = filterCallbacks()->dispatcher().createTimer(cb); } message_timer_->enableTimer(timeout); - ENVOY_STREAM_LOG(debug, "Traffic direction {}: {} ms timer enabled", *filter_callbacks_, + ENVOY_STREAM_LOG(debug, "Traffic direction {}: {} ms timer enabled", *filterCallbacks(), trafficDirectionDebugStr(), timeout.count()); } ExtProcLoggingInfo* logging_info = filter_.loggingInfo(); @@ -46,13 +46,13 @@ void ProcessorState::onStartProcessorCall(Event::TimerCb cb, std::chrono::millis } } - call_start_time_ = filter_callbacks_->dispatcher().timeSource().monotonicTime(); + call_start_time_ = filterCallbacks()->dispatcher().timeSource().monotonicTime(); new_timeout_received_ = false; } void ProcessorState::onFinishProcessorCall(Grpc::Status::GrpcStatus call_status, CallbackState next_state) { - ENVOY_STREAM_LOG(debug, "Finish external processing call. Next state: {}", *filter_callbacks_, + ENVOY_STREAM_LOG(debug, "Finish external processing call. Next state: {}", *filterCallbacks(), static_cast(next_state)); filter_.logStreamInfo(); @@ -60,7 +60,7 @@ void ProcessorState::onFinishProcessorCall(Grpc::Status::GrpcStatus call_status, if (call_start_time_.has_value() && callback_state_ != CallbackState::Idle) { std::chrono::microseconds duration = std::chrono::duration_cast( - filter_callbacks_->dispatcher().timeSource().monotonicTime() - call_start_time_.value()); + filterCallbacks()->dispatcher().timeSource().monotonicTime() - call_start_time_.value()); ExtProcLoggingInfo* logging_info = filter_.loggingInfo(); if (logging_info != nullptr) { logging_info->recordGrpcCall(duration, call_status, callback_state_, trafficDirection()); @@ -72,7 +72,7 @@ void ProcessorState::onFinishProcessorCall(Grpc::Status::GrpcStatus call_status, void ProcessorState::stopMessageTimer() { if (message_timer_) { - ENVOY_STREAM_LOG(debug, "Traffic direction {}: timer disabled", *filter_callbacks_, + ENVOY_STREAM_LOG(debug, "Traffic direction {}: timer disabled", *filterCallbacks(), trafficDirectionDebugStr()); message_timer_->disableTimer(); } @@ -93,7 +93,7 @@ bool ProcessorState::restartMessageTimer(const uint32_t message_timeout_ms) { ENVOY_STREAM_LOG(debug, "Traffic direction {}: Server needs more time to process the request, start a " "new timer with timeout {} ms", - *filter_callbacks_, trafficDirectionDebugStr(), message_timeout_ms); + *filterCallbacks(), trafficDirectionDebugStr(), message_timeout_ms); message_timer_->disableTimer(); message_timer_->enableTimer(std::chrono::milliseconds(message_timeout_ms)); // Setting this flag to true to make sure Envoy ignore the future such @@ -105,7 +105,7 @@ bool ProcessorState::restartMessageTimer(const uint32_t message_timeout_ms) { debug, "Traffic direction {}: Ignoring server new timeout message {} ms due to timer not " "enabled or not the 1st such message", - *filter_callbacks_, trafficDirectionDebugStr(), message_timeout_ms); + *filterCallbacks(), trafficDirectionDebugStr(), message_timeout_ms); return false; } } @@ -115,7 +115,7 @@ void ProcessorState::sendBufferedDataInStreamedMode(bool end_stream) { if (hasBufferedData()) { Buffer::OwnedImpl buffered_chunk; modifyBufferedData([&buffered_chunk](Buffer::Instance& data) { buffered_chunk.move(data); }); - ENVOY_STREAM_LOG(debug, "Sending a chunk of buffered data ({})", *filter_callbacks_, + ENVOY_STREAM_LOG(debug, "Sending a chunk of buffered data ({})", *filterCallbacks(), buffered_chunk.length()); auto req = filter_.setupBodyChunk(*this, buffered_chunk, end_stream); if (body_mode_ != ProcessingMode::FULL_DUPLEX_STREAMED) { @@ -133,7 +133,7 @@ void ProcessorState::sendBufferedDataInStreamedMode(bool end_stream) { absl::Status ProcessorState::processHeaderMutation(const CommonResponse& common_response, Effect& processing_effect) { - ENVOY_STREAM_LOG(debug, "Applying header mutations", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "Applying header mutations", *filterCallbacks()); const auto mut_status = MutationUtils::applyHeaderMutations( common_response.header_mutation(), *headers_, common_response.status() == CommonResponse::CONTINUE_AND_REPLACE, @@ -174,7 +174,7 @@ absl::Status ProcessorState::handleHeadersResponse(const HeadersResponse& respon return absl::FailedPreconditionError("spurious message"); } - ENVOY_STREAM_LOG(debug, "applying headers response. body mode = {}", *filter_callbacks_, + ENVOY_STREAM_LOG(debug, "applying headers response. body mode = {}", *filterCallbacks(), ProcessingMode::BodySendMode_Name(body_mode_)); const auto& common_response = response.response(); @@ -202,7 +202,7 @@ absl::Status ProcessorState::handleHeadersResponse(const HeadersResponse& respon } absl::Status ProcessorState::handleHeaderContinueAndReplace(const HeadersResponse& response) { - ENVOY_STREAM_LOG(debug, "Replacing complete message", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "Replacing complete message", *filterCallbacks()); const auto& common_response = response.response(); // Completely replace the body that may already exist. @@ -237,7 +237,7 @@ absl::Status ProcessorState::handleHeaderContinueAndReplace(const HeadersRespons // If we got here, then the processor doesn't care about the body or is not ready for // trailers, so we can just continue. - ENVOY_STREAM_LOG(trace, "Clearing stored headers", *filter_callbacks_); + ENVOY_STREAM_LOG(trace, "Clearing stored headers", *filterCallbacks()); headers_ = nullptr; filter_.onProcessHeadersResponse(response, absl::OkStatus(), trafficDirection()); continueIfNecessary(); @@ -247,7 +247,7 @@ absl::Status ProcessorState::handleHeaderContinueAndReplace(const HeadersRespons absl::Status ProcessorState::handleHeaderContinue() { if (no_body_) { // Fall through if there was never a body in the first place. - ENVOY_STREAM_LOG(debug, "The message had no body", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "The message had no body", *filterCallbacks()); } else if (complete_body_available_ && body_mode_ != ProcessingMode::NONE) { return handleCompleteBodyAvailable(); } else if (body_mode_ == ProcessingMode::BUFFERED) { @@ -298,7 +298,7 @@ absl::Status ProcessorState::handleBufferedPartialMode() { if (hasBufferedData()) { // Put the data buffered so far into the buffer queue. When more data comes in // we'll check to see if we have reached the watermark. - ENVOY_STREAM_LOG(debug, "Enqueuing body data buffered so far", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "Enqueuing body data buffered so far", *filterCallbacks()); Buffer::OwnedImpl buffered_chunk; modifyBufferedData([&buffered_chunk](Buffer::Instance& data) { buffered_chunk.move(data); }); enqueueStreamingChunk(buffered_chunk, false); @@ -311,7 +311,7 @@ absl::Status ProcessorState::handleBufferedPartialMode() { const auto& all_data = consolidateStreamedChunks(); ENVOY_STREAM_LOG( debug, "Sending {} bytes of data end_stream {} in buffered partial mode before end stream", - *filter_callbacks_, chunkQueue().receivedData().length(), all_data.end_stream); + *filterCallbacks(), chunkQueue().receivedData().length(), all_data.end_stream); auto req = filter_.setupBodyChunk(*this, chunkQueue().receivedData(), false); filter_.sendBodyChunk(*this, ProcessorState::CallbackState::BufferedPartialBodyCallback, req); } else { @@ -334,7 +334,7 @@ absl::Status ProcessorState::handleTrailersAndCleanup() { // If we got here, then the processor doesn't care about the body or is not ready for // trailers, so we can just continue. - ENVOY_STREAM_LOG(trace, "Clearing stored headers", *filter_callbacks_); + ENVOY_STREAM_LOG(trace, "Clearing stored headers", *filterCallbacks()); headers_ = nullptr; continueIfNecessary(); clearWatermark(); @@ -346,7 +346,7 @@ absl::Status ProcessorState::handleBodyResponse(const BodyResponse& response) { return absl::FailedPreconditionError("spurious message"); } - ENVOY_STREAM_LOG(debug, "Processing body response", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "Processing body response", *filterCallbacks()); const auto& common_response = response.response(); absl::StatusOr result; @@ -421,7 +421,7 @@ ProcessorState::handleStreamedBodyCallback(const CommonResponse& common_response if (common_response.has_body_mutation() && common_response.body_mutation().has_streamed_response()) { ENVOY_STREAM_LOG(debug, "FULL_DUPLEX_STREAMED body response is received and body_mode_: {} ", - *filter_callbacks_, ProcessingMode::BodySendMode_Name(body_mode_)); + *filterCallbacks(), ProcessingMode::BodySendMode_Name(body_mode_)); // streamed_response will only be supported if the ext_proc filter has body_mode set to // FULL_DUPLEX_STREAMED. if (body_mode_ != ProcessingMode::FULL_DUPLEX_STREAMED) { @@ -471,7 +471,7 @@ ProcessorState::handleBufferedPartialBodyCallback(const CommonResponse& common_r // Process chunk data if (chunk_data.length() > 0) { ENVOY_STREAM_LOG(trace, "Injecting {} bytes of processed data to filter stream", - *filter_callbacks_, chunk_data.length()); + *filterCallbacks(), chunk_data.length()); injectDataToFilterChain(chunk_data, chunk->end_stream); } onFinishProcessorCall(Grpc::Status::Ok); @@ -479,7 +479,7 @@ ProcessorState::handleBufferedPartialBodyCallback(const CommonResponse& common_r if (chunkQueue().receivedData().length() > 0) { const QueuedChunk& all_data = consolidateStreamedChunks(); ENVOY_STREAM_LOG(trace, "Injecting {} bytes of leftover data to filter stream", - *filter_callbacks_, chunkQueue().receivedData().length()); + *filterCallbacks(), chunkQueue().receivedData().length()); injectDataToFilterChain(chunkQueue().receivedData(), all_data.end_stream); } @@ -495,7 +495,7 @@ absl::Status ProcessorState::processHeaderMutationIfAvailable(const CommonRespon return mut_status; } ENVOY_STREAM_LOG(debug, "Response had header mutations but headers aren't available", - *filter_callbacks_); + *filterCallbacks()); return absl::OkStatus(); } @@ -519,7 +519,7 @@ absl::Status ProcessorState::validateContentLength(const CommonResponse& common_ void ProcessorState::applyBufferedBodyMutation(const CommonResponse& common_response, Effect& effect) { - ENVOY_STREAM_LOG(debug, "Applying body response to buffered data. State = {}", *filter_callbacks_, + ENVOY_STREAM_LOG(debug, "Applying body response to buffered data. State = {}", *filterCallbacks(), static_cast(callback_state_)); modifyBufferedData([&common_response, &effect](Buffer::Instance& data) { effect = MutationUtils::applyBodyMutations(common_response.body_mutation(), data); @@ -546,7 +546,7 @@ absl::Status ProcessorState::handleTrailersResponse(const TrailersResponse& resp if (isValidTrailersCallbackState()) { callback_state_ = CallbackState::TrailersCallback; ENVOY_STREAM_LOG(debug, "Applying response to buffered trailers, body_mode_ {}", - *filter_callbacks_, ProcessingMode::BodySendMode_Name(body_mode_)); + *filterCallbacks(), ProcessingMode::BodySendMode_Name(body_mode_)); if (response.has_header_mutation() && trailers_ != nullptr) { Effect processing_effect = Effect::None; auto mut_status = MutationUtils::applyHeaderMutations( @@ -582,7 +582,7 @@ void ProcessorState::clearAsyncState(Grpc::Status::GrpcStatus call_status) { onFinishProcessorCall(call_status); if (!chunkQueue().empty()) { const auto& all_data = consolidateStreamedChunks(); - ENVOY_STREAM_LOG(trace, "Injecting leftover buffer of {} bytes", *filter_callbacks_, + ENVOY_STREAM_LOG(trace, "Injecting leftover buffer of {} bytes", *filterCallbacks(), chunkQueue().receivedData().length()); injectDataToFilterChain(chunkQueue().receivedData(), all_data.end_stream); } @@ -594,7 +594,7 @@ void ProcessorState::setBodyMode(ProcessingMode_BodySendMode body_mode) { body_m void ProcessorState::continueIfNecessary() { if (paused_) { - ENVOY_STREAM_LOG(debug, "Continuing processing", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "Continuing processing", *filterCallbacks()); paused_ = false; continueProcessing(); } @@ -607,13 +607,13 @@ bool ProcessorState::handleStreamedBodyResponse(const CommonResponse& common_res if (common_response.has_body_mutation()) { Effect processing_effect; ENVOY_STREAM_LOG(debug, "Applying body response to chunk of data. Size = {}", - *filter_callbacks_, chunk->length); + *filterCallbacks(), chunk->length); processing_effect = MutationUtils::applyBodyMutations(common_response.body_mutation(), chunk_data); logMutation(callback_state_, processing_effect); } bool should_continue = chunk->end_stream; - ENVOY_STREAM_LOG(trace, "Injecting {} bytes of data to filter stream", *filter_callbacks_, + ENVOY_STREAM_LOG(trace, "Injecting {} bytes of data to filter stream", *filterCallbacks(), chunk_data.length()); injectDataToFilterChain(chunk_data, chunk->end_stream); @@ -640,7 +640,7 @@ bool ProcessorState::handleDuplexStreamedBodyResponse(const CommonResponse& comm ENVOY_STREAM_LOG(trace, "Injecting {} bytes of data to filter stream in FULL_DUPLEX_STREAMED mode. " "end_of_stream is {}", - *filter_callbacks_, buffer.length(), end_of_stream); + *filterCallbacks(), buffer.length(), end_of_stream); injectDataToFilterChain(buffer, end_of_stream); // Assume mutations are applied in FULL_DUPLEX_STREAMED_MODE. logMutation(callback_state_, Effect::MutationApplied); @@ -738,7 +738,7 @@ void DecodingProcessorState::setProcessingModeInternal(const ProcessingMode& mod void DecodingProcessorState::requestWatermark() { if (!watermark_requested_) { - ENVOY_STREAM_LOG(debug, "Watermark raised on decoding", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "Watermark raised on decoding", *decoder_callbacks_); watermark_requested_ = true; decoder_callbacks_->onDecoderFilterAboveWriteBufferHighWatermark(); } @@ -746,7 +746,7 @@ void DecodingProcessorState::requestWatermark() { void DecodingProcessorState::clearWatermark() { if (watermark_requested_) { - ENVOY_STREAM_LOG(debug, "Watermark lowered on decoding", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "Watermark lowered on decoding", *decoder_callbacks_); watermark_requested_ = false; decoder_callbacks_->onDecoderFilterBelowWriteBufferLowWatermark(); } @@ -758,7 +758,7 @@ void DecodingProcessorState::clearRouteCache(const CommonResponse& common_respon if (response_clear_route_cache) { filter_.stats().clear_route_cache_upstream_ignored_.inc(); ENVOY_STREAM_LOG(debug, "NOT clearing route cache. The filter is in upstream filter chain.", - *filter_callbacks_); + *decoder_callbacks_); } return; } @@ -767,7 +767,7 @@ void DecodingProcessorState::clearRouteCache(const CommonResponse& common_respon if (response_clear_route_cache) { filter_.stats().clear_route_cache_ignored_.inc(); ENVOY_STREAM_LOG(debug, "NOT clearing route cache. No header mutation in the response", - *filter_callbacks_); + *decoder_callbacks_); } return; } @@ -780,21 +780,21 @@ void DecodingProcessorState::clearRouteCache(const CommonResponse& common_respon ENVOY_STREAM_LOG(debug, "Clearing route cache due to the filter RouterCacheAction is configured " "with DEFAULT and response has clear_route_cache set.", - *filter_callbacks_); + *decoder_callbacks_); decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); } break; case envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor::CLEAR: ENVOY_STREAM_LOG( debug, "Clearing route cache due to the filter RouterCacheAction is configured with CLEAR", - *filter_callbacks_); + *decoder_callbacks_); decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); break; case envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor::RETAIN: if (response_clear_route_cache) { filter_.stats().clear_route_cache_disabled_.inc(); ENVOY_STREAM_LOG(debug, "NOT clearing route cache, it is disabled by the filter config", - *filter_callbacks_); + *decoder_callbacks_); } break; } @@ -826,7 +826,7 @@ void EncodingProcessorState::setProcessingModeInternal(const ProcessingMode& mod void EncodingProcessorState::requestWatermark() { if (!watermark_requested_) { - ENVOY_STREAM_LOG(debug, "Watermark raised on encoding", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "Watermark raised on encoding", *encoder_callbacks_); watermark_requested_ = true; encoder_callbacks_->onEncoderFilterAboveWriteBufferHighWatermark(); } @@ -834,7 +834,7 @@ void EncodingProcessorState::requestWatermark() { void EncodingProcessorState::clearWatermark() { if (watermark_requested_) { - ENVOY_STREAM_LOG(debug, "Watermark lowered on encoding", *filter_callbacks_); + ENVOY_STREAM_LOG(debug, "Watermark lowered on encoding", *encoder_callbacks_); watermark_requested_ = false; encoder_callbacks_->onEncoderFilterBelowWriteBufferLowWatermark(); } @@ -911,7 +911,7 @@ ProcessingResult DecodingProcessorState::startLocalResponse( } ENVOY_STREAM_LOG(debug, "applying local response headers response. body mode = {}", - *filter_callbacks_, ProcessingMode::BodySendMode_Name(body_mode_)); + *decoder_callbacks_, ProcessingMode::BodySendMode_Name(body_mode_)); auto local_response_headers = Http::createHeaderMap({}); const auto mut_status = MutationUtils::protoToHeaders( response_headers.headers(), *local_response_headers, filter_.config().mutationChecker(), diff --git a/source/extensions/filters/http/ext_proc/processor_state.h b/source/extensions/filters/http/ext_proc/processor_state.h index ff2f24446bdb8..c0aac5f8f2b7c 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.h +++ b/source/extensions/filters/http/ext_proc/processor_state.h @@ -92,8 +92,8 @@ class ProcessorState : public Logger::Loggable { const std::vector& untyped_cluster_metadata_forwarding_namespaces, const std::vector& typed_cluster_metadata_forwarding_namespaces, bool allow_content_length_header) - : filter_(filter), allow_content_length_header_(allow_content_length_header), - traffic_direction_(traffic_direction), + : filter_(filter), traffic_direction_(traffic_direction), + allow_content_length_header_(allow_content_length_header), untyped_forwarding_namespaces_(&untyped_forwarding_namespaces), typed_forwarding_namespaces_(&typed_forwarding_namespaces), untyped_receiving_namespaces_(&untyped_receiving_namespaces), @@ -260,7 +260,7 @@ class ProcessorState : public Logger::Loggable { virtual envoy::service::ext_proc::v3::HttpTrailers* mutableTrailers(envoy::service::ext_proc::v3::ProcessingRequest& request) const PURE; - virtual Http::StreamFilterCallbacks* callbacks() const PURE; + virtual Http::StreamFilterCallbacks* filterCallbacks() const PURE; virtual bool sendAttributes(const ExpressionManager& mgr) const PURE; @@ -333,8 +333,10 @@ class ProcessorState : public Logger::Loggable { virtual bool isValidTrailersCallbackState() const; Filter& filter_; - Http::StreamFilterCallbacks* filter_callbacks_; CallbackState callback_state_ = CallbackState::Idle; + // The specific mode for body handling + envoy::extensions::filters::http::ext_proc::v3::ProcessingMode_BodySendMode body_mode_; + const envoy::config::core::v3::TrafficDirection traffic_direction_; // Keep track of whether we requested a watermark. bool watermark_requested_ : 1 = false; @@ -367,9 +369,6 @@ class ProcessorState : public Logger::Loggable { bool attributes_sent_ : 1 = false; const bool allow_content_length_header_ : 1; - // The specific mode for body handling - envoy::extensions::filters::http::ext_proc::v3::ProcessingMode_BodySendMode body_mode_; - // The request_headers_ field is guaranteed to hold the pointer to the request // headers as set in decodeHeaders. This allows both decoding and encoding states // to have access to the request headers map. @@ -379,7 +378,6 @@ class ProcessorState : public Logger::Loggable { Event::TimerPtr message_timer_; ChunkQueue chunk_queue_; absl::optional call_start_time_ = absl::nullopt; - const envoy::config::core::v3::TrafficDirection traffic_direction_; const std::vector* untyped_forwarding_namespaces_{}; const std::vector* typed_forwarding_namespaces_{}; @@ -527,7 +525,6 @@ class DecodingProcessorState : public ProcessorState { void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) { decoder_callbacks_ = &callbacks; - filter_callbacks_ = &callbacks; } const Buffer::Instance* bufferedData() const override { @@ -578,7 +575,7 @@ class DecodingProcessorState : public ProcessorState { void requestWatermark() override; void clearWatermark() override; - Http::StreamFilterCallbacks* callbacks() const override { return decoder_callbacks_; } + Http::StreamFilterCallbacks* filterCallbacks() const override { return decoder_callbacks_; } bool sendAttributes(const ExpressionManager& mgr) const override { return !attributes_sent_ && mgr.hasRequestExpr(); @@ -648,8 +645,8 @@ class DecodingProcessorState : public ProcessorState { absl::Status handleLocalResponseHeadersContinue(const ::envoy::service::ext_proc::v3::HttpHeaders& response); - Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; bool local_response_started_{false}; + Http::StreamDecoderFilterCallbacks* decoder_callbacks_ = nullptr; }; class EncodingProcessorState : public ProcessorState { @@ -673,7 +670,6 @@ class EncodingProcessorState : public ProcessorState { void setEncoderFilterCallbacks(Http::StreamEncoderFilterCallbacks& callbacks) { encoder_callbacks_ = &callbacks; - filter_callbacks_ = &callbacks; } const Buffer::Instance* bufferedData() const override { @@ -724,7 +720,7 @@ class EncodingProcessorState : public ProcessorState { void requestWatermark() override; void clearWatermark() override; - Http::StreamFilterCallbacks* callbacks() const override { return encoder_callbacks_; } + Http::StreamFilterCallbacks* filterCallbacks() const override { return encoder_callbacks_; } bool sendAttributes(const ExpressionManager& mgr) const override { return !attributes_sent_ && mgr.hasResponseExpr(); @@ -750,8 +746,8 @@ class EncodingProcessorState : public ProcessorState { void setProcessingModeInternal( const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode); - Http::StreamEncoderFilterCallbacks* encoder_callbacks_{}; bool local_response_streaming_{false}; + Http::StreamEncoderFilterCallbacks* encoder_callbacks_ = nullptr; }; } // namespace ExternalProcessing From 1cae52161b407954fd51b43d180ca11ccc21e6fe Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Sun, 3 May 2026 23:10:28 -0400 Subject: [PATCH 203/750] match_delegate: Fix metadata encoding in delegating filter (#44818) Copy/paste bug in untested code. Risk Level: low Testing: unit tests Docs Changes: no Release Notes: no Platform Specific Features: no Signed-off-by: Yan Avlasov --- .../filters/http/match_delegate/config.cc | 2 +- .../filters/http/match_delegate/config_test.cc | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/source/extensions/filters/http/match_delegate/config.cc b/source/extensions/filters/http/match_delegate/config.cc index fb6709f5e8d83..91e046248f7d5 100644 --- a/source/extensions/filters/http/match_delegate/config.cc +++ b/source/extensions/filters/http/match_delegate/config.cc @@ -222,7 +222,7 @@ DelegatingStreamFilter::encodeMetadata(Envoy::Http::MetadataMap& metadata_map) { if (match_state_.skipFilter()) { return Envoy::Http::FilterMetadataStatus::Continue; } - return decoder_filter_->decodeMetadata(metadata_map); + return encoder_filter_->encodeMetadata(metadata_map); } void DelegatingStreamFilter::encodeComplete() { diff --git a/test/extensions/filters/http/match_delegate/config_test.cc b/test/extensions/filters/http/match_delegate/config_test.cc index 797dc860ec0ed..868072701711d 100644 --- a/test/extensions/filters/http/match_delegate/config_test.cc +++ b/test/extensions/filters/http/match_delegate/config_test.cc @@ -613,6 +613,8 @@ TEST(DelegatingFilterTest, MatchTreeSkipActionRequestAndResponseHeaders) { Buffer::OwnedImpl empty_buffer; filter->decoder_callbacks_->encodeData(empty_buffer, false); + filter->decoder_callbacks_->encodeMetadata(std::make_unique()); + Envoy::Http::ResponseTrailerMapPtr trailers{ new Envoy::Http::TestResponseTrailerMapImpl{{"test", "test"}}}; filter->decoder_callbacks_->encodeTrailers(std::move(trailers)); @@ -630,6 +632,12 @@ TEST(DelegatingFilterTest, MatchTreeSkipActionRequestAndResponseHeaders) { EXPECT_CALL(*filter, encodeData(_, _)).Times(0); delegating_filter->encodeData(data, end_stream); })); + EXPECT_CALL(decoder_callbacks, encodeMetadata_(_)) + .WillOnce(Invoke([&](Envoy::Http::MetadataMapPtr metadata) { + // All the following encoding callbacks will be skipped. + EXPECT_CALL(*filter, encodeMetadata(_)).Times(0); + delegating_filter->encodeMetadata(*metadata); + })); EXPECT_CALL(decoder_callbacks, encodeTrailers_(_)) .WillOnce(Invoke([&](Envoy::Http::ResponseTrailerMap& trailers) { EXPECT_CALL(*filter, encodeTrailers(_)).Times(0); @@ -747,6 +755,8 @@ TEST(DelegatingFilterTest, MatchTreeFilterActionEncodingTrailers) { Buffer::OwnedImpl empty_buffer; filter->decoder_callbacks_->encodeData(empty_buffer, false); + filter->decoder_callbacks_->encodeMetadata(std::make_unique()); + Envoy::Http::ResponseTrailerMapPtr trailers{ new Envoy::Http::TestResponseTrailerMapImpl{{"match-trailer", "match"}}}; filter->decoder_callbacks_->encodeTrailers(std::move(trailers)); @@ -763,6 +773,12 @@ TEST(DelegatingFilterTest, MatchTreeFilterActionEncodingTrailers) { EXPECT_CALL(*filter, encodeData(_, _)); delegating_filter->encodeData(data, end_stream); })); + EXPECT_CALL(decoder_callbacks, encodeMetadata_(_)) + .WillOnce(Invoke([&](Envoy::Http::MetadataMapPtr metadata) { + // All the following encoding callbacks will be skipped. + EXPECT_CALL(*filter, encodeMetadata(_)); + delegating_filter->encodeMetadata(*metadata); + })); EXPECT_CALL(decoder_callbacks, encodeTrailers_(_)) .WillOnce(Invoke([&](Envoy::Http::ResponseTrailerMap& trailers) { EXPECT_CALL(*filter, onMatchCallback(_)); From b03da5f87bd4117eecab5c51d4e6d6be9cccc794 Mon Sep 17 00:00:00 2001 From: Jason Koch Date: Sun, 3 May 2026 20:43:40 -0700 Subject: [PATCH 204/750] tracing(perf): skip tag setup in finalizeDownstreamSpan for non-recording spans (#44614) Profiling shows `HttpTracerUtility::finalizeDownstreamSpan` consuming high CPU at high high request rates, because it executes for every request. For Zipkin this costs around 4% of the total cost of envoy, in particular the tag/header setup. This change gives Spans a new `exportedSpan`, and each can declare that they do/do-not intend to export the span. If tags are known to be unnecessary for further processing, the Span can return false and the expensive tag preparation work can be skipped. By returning `true`, the existing behavior is maintained for the tracing module. `finishSpan()` will still be called in all cases. **Risk Level** - Low. Touches the HCM request-complete hot path (skip of tag setup on non-recording spans) and adds a method `exportedSpan` to the Tracing::Span interface used by all tracer extensions. **Testing** - Adds new tests, and fix improves perf on test cluster. **Docs Changes** - None. The new function is documented inline in envoy/tracing/trace_driver.h. **Release Notes** - None. **Platform Specific Features** - n/a **Runtime guard** - n/a **API Considerations** - Tracing::Span gains one new method which implementations must provide: ``` virtual bool exportedSpan() const PURE; ``` image --------- Signed-off-by: Jason Koch --- envoy/tracing/trace_driver.h | 10 ++++++ source/common/tracing/http_tracer_impl.cc | 6 ++++ source/common/tracing/http_tracer_impl.h | 4 +-- source/common/tracing/null_span_impl.h | 1 + source/extensions/tracers/datadog/span.cc | 7 ++++ source/extensions/tracers/datadog/span.h | 1 + .../tracers/dynamic_modules/tracer_config.cc | 5 +++ .../tracers/dynamic_modules/tracer_config.h | 1 + .../tracers/fluentd/fluentd_tracer_impl.h | 3 ++ .../extensions/tracers/opentelemetry/tracer.h | 5 +++ source/extensions/tracers/skywalking/tracer.h | 3 ++ source/extensions/tracers/xray/tracer.h | 5 +++ .../tracers/zipkin/zipkin_core_types.h | 1 + test/common/http/conn_manager_impl_test.cc | 34 +++++++++++++++++++ test/common/tracing/tracer_impl_test.cc | 3 ++ .../http/ext_proc/tracer_test_filter.cc | 1 + test/extensions/tracers/zipkin/tracer_test.cc | 7 ++++ test/mocks/tracing/mocks.cc | 2 +- test/mocks/tracing/mocks.h | 1 + 19 files changed, 97 insertions(+), 3 deletions(-) diff --git a/envoy/tracing/trace_driver.h b/envoy/tracing/trace_driver.h index 4e5e9617c6ae3..28bab0f271bae 100644 --- a/envoy/tracing/trace_driver.h +++ b/envoy/tracing/trace_driver.h @@ -113,6 +113,16 @@ class Span { */ virtual void setSampled(bool sampled) PURE; + /** + * @return whether this span will be exported to the tracing backend. The HTTP connection + * manager may skip finalize-time tag work for spans that return false, since those tags + * would be discarded by the driver anyway. + * + * If the driver cannot conclusively determine that the span will be dropped, it MUST + * return true so that the span is fully populated and suitable for export. + */ + virtual bool exportedSpan() const PURE; + /** * When the startSpan() of tracer is called, the Envoy tracing decision is passed to the * tracer to help determine whether the span should be sampled. diff --git a/source/common/tracing/http_tracer_impl.cc b/source/common/tracing/http_tracer_impl.cc index c7200d0329c71..1a46819f3759e 100644 --- a/source/common/tracing/http_tracer_impl.cc +++ b/source/common/tracing/http_tracer_impl.cc @@ -151,6 +151,12 @@ void HttpTracerUtility::finalizeDownstreamSpan(Span& span, const Http::ResponseTrailerMap* response_trailers, const StreamInfo::StreamInfo& stream_info, const Config& tracing_config) { + // Early exit if tags are not needed + if (!span.exportedSpan()) { + span.finishSpan(); + return; + } + // Pre response data. if (request_headers) { if (request_headers->RequestId()) { diff --git a/source/common/tracing/http_tracer_impl.h b/source/common/tracing/http_tracer_impl.h index 42f5ef438cceb..d62c9fd6d4248 100644 --- a/source/common/tracing/http_tracer_impl.h +++ b/source/common/tracing/http_tracer_impl.h @@ -74,8 +74,8 @@ class HttpTraceContext : public HttpTraceContextBase { class HttpTracerUtility { public: /** - * Adds information obtained from the downstream request headers as tags to the active span. - * Then finishes the span. + * Adds information obtained from the downstream request headers as tags if the span will be + * exported (per Span::exportedSpan), then finishes the span. */ static void finalizeDownstreamSpan(Span& span, const Http::RequestHeaderMap* request_headers, const Http::ResponseHeaderMap* response_headers, diff --git a/source/common/tracing/null_span_impl.h b/source/common/tracing/null_span_impl.h index 1de48d2e437e8..109f71d0fbf56 100644 --- a/source/common/tracing/null_span_impl.h +++ b/source/common/tracing/null_span_impl.h @@ -31,6 +31,7 @@ class NullSpan : public Span { return SpanPtr{new NullSpan()}; } void setSampled(bool) override {} + bool exportedSpan() const override { return false; } bool useLocalDecision() const override { return false; } }; diff --git a/source/extensions/tracers/datadog/span.cc b/source/extensions/tracers/datadog/span.cc index d3617d233d788..bc1135301baf2 100644 --- a/source/extensions/tracers/datadog/span.cc +++ b/source/extensions/tracers/datadog/span.cc @@ -127,6 +127,13 @@ void Span::setSampled(bool sampled) { span_->trace_segment().override_sampling_priority(priority); } +bool Span::exportedSpan() const { + // DD does expose sampling_decision()->priority, but the span sampling + // (eg, DD_SPAN_SAMPLING_RULES and similar) might be configured to resurrect + // the span later. + return true; +} + std::string Span::getBaggage(absl::string_view) { // not implemented return EMPTY_STRING; diff --git a/source/extensions/tracers/datadog/span.h b/source/extensions/tracers/datadog/span.h index 38c3acbb8a151..63e7b230f5501 100644 --- a/source/extensions/tracers/datadog/span.h +++ b/source/extensions/tracers/datadog/span.h @@ -46,6 +46,7 @@ class Span : public Tracing::Span { SystemTime start_time) override; void setSampled(bool sampled) override; bool useLocalDecision() const override { return use_local_decision_; } + bool exportedSpan() const override; std::string getBaggage(absl::string_view key) override; void setBaggage(absl::string_view key, absl::string_view value) override; std::string getTraceId() const override; diff --git a/source/extensions/tracers/dynamic_modules/tracer_config.cc b/source/extensions/tracers/dynamic_modules/tracer_config.cc index 1074f2d781aba..5f9d1e0b6862d 100644 --- a/source/extensions/tracers/dynamic_modules/tracer_config.cc +++ b/source/extensions/tracers/dynamic_modules/tracer_config.cc @@ -142,6 +142,11 @@ void DynamicModuleSpan::log(SystemTime timestamp, const std::string& event) { config_->on_span_log_(in_module_span_, timestamp_ns, event_buf); } +bool DynamicModuleSpan::exportedSpan() const { + // TODO(jkoch): extend module ABI with hook as an optimization + return true; +} + void DynamicModuleSpan::finishSpan() { config_->on_span_finish_(in_module_span_); } void DynamicModuleSpan::injectContext(Tracing::TraceContext& trace_context, diff --git a/source/extensions/tracers/dynamic_modules/tracer_config.h b/source/extensions/tracers/dynamic_modules/tracer_config.h index ab609336ec5bd..71408591cb5b9 100644 --- a/source/extensions/tracers/dynamic_modules/tracer_config.h +++ b/source/extensions/tracers/dynamic_modules/tracer_config.h @@ -294,6 +294,7 @@ class DynamicModuleSpan : public Tracing::Span { SystemTime start_time) override; void setSampled(bool sampled) override; bool useLocalDecision() const override; + bool exportedSpan() const override; std::string getBaggage(absl::string_view key) override; void setBaggage(absl::string_view key, absl::string_view value) override; std::string getTraceId() const override; diff --git a/source/extensions/tracers/fluentd/fluentd_tracer_impl.h b/source/extensions/tracers/fluentd/fluentd_tracer_impl.h index 040878679b3a2..8b9002a161496 100644 --- a/source/extensions/tracers/fluentd/fluentd_tracer_impl.h +++ b/source/extensions/tracers/fluentd/fluentd_tracer_impl.h @@ -172,6 +172,9 @@ class Span : public Tracing::Span { void setSampled(bool sampled) override { span_context_.setSampled(sampled); } bool sampled() const { return span_context_.sampled(); } bool useLocalDecision() const override { return use_local_decision_; } + // Fluentd's finishSpan exports unconditionally; the `sampled` flag is only + // propagated in the W3C traceparent header, not consulted locally. + bool exportedSpan() const override { return true; } std::string getBaggage(absl::string_view key) override; void setBaggage(absl::string_view key, absl::string_view value) override; diff --git a/source/extensions/tracers/opentelemetry/tracer.h b/source/extensions/tracers/opentelemetry/tracer.h index 40e32028da6d9..e65c2010098be 100644 --- a/source/extensions/tracers/opentelemetry/tracer.h +++ b/source/extensions/tracers/opentelemetry/tracer.h @@ -108,6 +108,11 @@ class Span : Logger::Loggable, public Tracing::Span { */ bool useLocalDecision() const override { return use_local_decision_; } + /** + * @return whether the span will be exported to the OTLP backend. + */ + bool exportedSpan() const override { return sampled_; } + /** * @return whether or not the sampled attribute is set */ diff --git a/source/extensions/tracers/skywalking/tracer.h b/source/extensions/tracers/skywalking/tracer.h index ec046f9d386f0..d25ceffa86e42 100644 --- a/source/extensions/tracers/skywalking/tracer.h +++ b/source/extensions/tracers/skywalking/tracer.h @@ -90,6 +90,9 @@ class Span : public Tracing::Span { // trace. That make it is impossible to update the sampling decision. So, the useLocalDecision() // always return false now. This should be resolved in the future. bool useLocalDecision() const override { return false; } + // SkyWalking ships every finished span. setSampled(false) only sets a skip flag, it does + // not drop the span. + bool exportedSpan() const override { return true; } std::string getBaggage(absl::string_view) override { return EMPTY_STRING; } void setBaggage(absl::string_view, absl::string_view) override {} std::string getTraceId() const override { return tracing_context_->traceId(); } diff --git a/source/extensions/tracers/xray/tracer.h b/source/extensions/tracers/xray/tracer.h index 5708ab98b59f0..1dc83c10643de 100644 --- a/source/extensions/tracers/xray/tracer.h +++ b/source/extensions/tracers/xray/tracer.h @@ -156,6 +156,11 @@ class Span : public Tracing::Span, Logger::Loggable { // X-Ray tracer does not use the sampling decision from Envoy anyway. bool useLocalDecision() const override { return false; } + /** + * @return whether the span will be exported to X-Ray. + */ + bool exportedSpan() const override { return sampled_; }; + /** * Sets the server error as true for the traced operation/request. */ diff --git a/source/extensions/tracers/zipkin/zipkin_core_types.h b/source/extensions/tracers/zipkin/zipkin_core_types.h index 52c8d3806e2ab..a9e751f93164d 100644 --- a/source/extensions/tracers/zipkin/zipkin_core_types.h +++ b/source/extensions/tracers/zipkin/zipkin_core_types.h @@ -559,6 +559,7 @@ class Span : public ZipkinBase, public Tracing::Span { void setTag(absl::string_view name, absl::string_view value) override; void log(SystemTime timestamp, const std::string& event) override; void setSampled(bool val) override { sampled_ = val; } + bool exportedSpan() const override { return sampled_; } bool useLocalDecision() const override { return use_local_decision_; } void setOperation(absl::string_view operation) override { setName(std::string(operation)); } void injectContext(Tracing::TraceContext& trace_context, diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 0ec9bb817bf21..b7a3dd5573679 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -3320,6 +3320,40 @@ TEST_F(HttpConnectionManagerImplTest, DoNotStartSpanIfTracingIsNotEnabled) { conn_manager_->onData(fake_input, false); } +// When the active span reports exportedSpan()==false, the HCM calls +// HttpTracerUtility::finalizeDownstreamSpan but skips tag string building and storage +// work on spans whose driver-level export pipeline will drop them. Propagation and +// per-request span creation are unaffected (exercised elsewhere). The span should +// still be finished. +TEST_F(HttpConnectionManagerImplTest, SkipFinalizeDownstreamSpanWhenNotRecording) { + setup(SetupOpts().setTracing(true)); + + auto* span = new NiceMock(); + EXPECT_CALL(*tracer_, startSpan_(_, _, _, _)).WillOnce(Return(span)); + EXPECT_CALL(*span, exportedSpan()).WillRepeatedly(Return(false)); + + // finalizeDownstreamSpan-owned methods must not fire when not wanted + EXPECT_CALL(*span, setTag(_, _)).Times(0); + EXPECT_CALL(*span, log(_, _)).Times(0); + // but finishSpan must still be called + EXPECT_CALL(*span, finishSpan()); + + EXPECT_CALL(*codec_, dispatch(_)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> Http::Status { + decoder_ = &conn_manager_->newStream(response_encoder_); + RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ + {":method", "GET"}, {":authority", "host"}, {":path", "/"}}}; + decoder_->decodeHeaders(std::move(headers), true); + ResponseHeaderMapPtr response_headers{new TestResponseHeaderMapImpl{{":status", "200"}}}; + decoder_->streamInfo().setResponseCodeDetails(""); + response_encoder_.getStream().resetStream(StreamResetReason::LocalReset); + data.drain(4); + return Http::okStatus(); + })); + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); +} + TEST_F(HttpConnectionManagerImplTest, NoPath) { setup(); diff --git a/test/common/tracing/tracer_impl_test.cc b/test/common/tracing/tracer_impl_test.cc index 6c6a3eb95c2d2..ec7db6e88cd16 100644 --- a/test/common/tracing/tracer_impl_test.cc +++ b/test/common/tracing/tracer_impl_test.cc @@ -363,6 +363,9 @@ TEST(NullTracerTest, BasicFunctionality) { span_ptr->log(SystemTime(), "fake_event"); span_ptr->useLocalDecision(); + // NullSpan is never exported + EXPECT_FALSE(span_ptr->exportedSpan()); + EXPECT_NE(nullptr, span_ptr->spawnChild(config, "foo", SystemTime())); } diff --git a/test/extensions/filters/http/ext_proc/tracer_test_filter.cc b/test/extensions/filters/http/ext_proc/tracer_test_filter.cc index 3d2b1ad673bbd..e2c661c90b69b 100644 --- a/test/extensions/filters/http/ext_proc/tracer_test_filter.cc +++ b/test/extensions/filters/http/ext_proc/tracer_test_filter.cc @@ -74,6 +74,7 @@ class Span : public Tracing::Span { ENVOY_LOG_MISC(trace, "TestTracer setSampled: {}", do_sample); sampled_ = do_sample; } + bool exportedSpan() const override { return true; } bool useLocalDecision() const override { // NOTE: the trace decision from Envoy will be ignored in the startSpan() method // of this test implementation. So, the useLocalDecision() method is only for logging diff --git a/test/extensions/tracers/zipkin/tracer_test.cc b/test/extensions/tracers/zipkin/tracer_test.cc index 2628b419ee580..b7110ad2f362d 100644 --- a/test/extensions/tracers/zipkin/tracer_test.cc +++ b/test/extensions/tracers/zipkin/tracer_test.cc @@ -583,6 +583,13 @@ TEST_F(ZipkinTracerTest, FinishNotSampledSpan) { // Creates a root-span with a CS annotation SpanPtr span = tracer.startSpan(config, "my_span", timestamp); span->setSampled(false); + + // exportedSpan() must track sampled state + EXPECT_FALSE(span->exportedSpan()); + span->setSampled(true); + EXPECT_TRUE(span->exportedSpan()); + span->setSampled(false); + span->finishSpan(); // Test if the reporter's reportSpan method was NOT called upon finishing the span diff --git a/test/mocks/tracing/mocks.cc b/test/mocks/tracing/mocks.cc index 1744aae4cca75..bc8b7ec186823 100644 --- a/test/mocks/tracing/mocks.cc +++ b/test/mocks/tracing/mocks.cc @@ -9,7 +9,7 @@ using testing::ReturnPointee; namespace Envoy { namespace Tracing { -MockSpan::MockSpan() = default; +MockSpan::MockSpan() { ON_CALL(*this, exportedSpan()).WillByDefault(Return(true)); } MockSpan::~MockSpan() = default; MockConfig::MockConfig() { diff --git a/test/mocks/tracing/mocks.h b/test/mocks/tracing/mocks.h index 148eb636d940c..a65dca9b133fe 100644 --- a/test/mocks/tracing/mocks.h +++ b/test/mocks/tracing/mocks.h @@ -42,6 +42,7 @@ class MockSpan : public Span { MOCK_METHOD(void, injectContext, (Tracing::TraceContext & request_headers, const Tracing::UpstreamContext& upstream)); MOCK_METHOD(void, setSampled, (bool sampled)); + MOCK_METHOD(bool, exportedSpan, (), (const, override)); MOCK_METHOD(bool, useLocalDecision, (), (const)); MOCK_METHOD(void, setBaggage, (absl::string_view key, absl::string_view value)); MOCK_METHOD(std::string, getBaggage, (absl::string_view key)); From 6d005fef127c86b38a4a902fbc8333bd113e5c8b Mon Sep 17 00:00:00 2001 From: Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com> Date: Mon, 4 May 2026 08:47:27 -0400 Subject: [PATCH 205/750] jwt_authn: add verification_status_header for extract_only_without_validation (#43630) This PR adds a configurable verification_status_header to the extract_only_without_validation JWT requirement type. When extract_only_without_validation is used with claim_to_headers, extracted claims are set as HTTP headers identical to headers from cryptographically validated JWTs. Downstream filters (RBAC, ext_authz) have no mechanism to distinguish verified from unverified claims. **Solution:** A verification status header (x-jwt-signature-verified: false) is now set on all requests processed by extract_only_without_validation. Downstream filters can check this header before trusting JWT-derived claim headers for authorization. Default header name: x-jwt-signature-verified Configurable via verification_status_header field in ExtractOnlyWithoutValidation Runtime-guarded by envoy.reloadable_features.jwt_authn_add_verification_status_header **Risk Level:** Low: additive only, no existing behavior changes. Runtime guard allows rollback. **Testing:** Proto config test + behavioral verification in verifier. **Docs:** Security considerations section with RBAC integration example. **Context:** This was originally reported via the Envoy security process. Per maintainer feedback @yanavlasov , the proposed changes have been submitted as a public PR. Thanks, team! Signed-off-by: Layau Eulizier Jr [Layau.eulizier@netguard24-7.com] ``` --------- Signed-off-by: Layau Eulizier Jr Signed-off-by: Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com> Signed-off-by: cybrdude <130326664+cybrdude@users.noreply.github.com> Co-authored-by: Layau Eulizier Jr Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: phlax Co-authored-by: Claude Opus 4.7 (1M context) --- .../filters/http/jwt_authn/v3/config.proto | 84 ++++++++++++----- changelogs/current.yaml | 10 ++ .../_include/jwt-authn-extract-only-rbac.yaml | 91 +++++++++++++++++++ .../http/http_filters/jwt_authn_filter.rst | 52 +++++++++++ source/common/runtime/runtime_features.cc | 1 + .../filters/http/jwt_authn/verifier.cc | 50 +++++++--- test/extensions/filters/http/jwt_authn/BUILD | 1 + .../http/jwt_authn/all_verifier_test.cc | 70 ++++++++++++++ .../http/jwt_authn/authenticator_test.cc | 16 ++++ 9 files changed, 338 insertions(+), 37 deletions(-) create mode 100644 docs/root/configuration/http/http_filters/_include/jwt-authn-extract-only-rbac.yaml diff --git a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 9a955bdd80ec6..20baa318396eb 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -78,6 +78,7 @@ message JwtProvider { // otherwise the JWT ``iss`` field is not checked. // // .. note:: + // // ``JwtRequirement`` :ref:`allow_missing ` // and :ref:`allow_missing_or_failed ` // are implemented differently than other ``JwtRequirements``. Hence the usage of this field @@ -324,10 +325,11 @@ message JwtProvider { // alg: PS256 // // .. warning:: - // Using the same key name for :ref:`header_in_metadata ` - // and :ref:`payload_in_metadata ` - // is not suggested due to potential override of existing entry, while it is not enforced during - // config validation. + // + // Using the same key name for :ref:`header_in_metadata ` + // and :ref:`payload_in_metadata ` + // is not suggested due to potential override of existing entry, while it is not enforced during + // config validation. // string header_in_metadata = 14; @@ -593,38 +595,70 @@ message JwtRequirement { // different is this mode will reject requests with invalid tokens. google.protobuf.Empty allow_missing = 6; - // Extract JWT claims without performing signature validation. - // This mode will decode the JWT, extract claims, and forward them as - // configured (via claim_to_headers, forward_payload_header, etc.) but - // will NOT verify the JWT signature against JWKS. + // [#next-major-version: consider removing or gating behind explicit opt-in] // // .. warning:: // - // This mode does not verify JWT authenticity. Use only in scenarios where: - // - // - JWTs come from a trusted source (e.g., internal service mesh) - // - Signature verification is performed elsewhere in the request path - // - You are in a testing period and the token issuer doesn't support JWKS yet + // SECURITY WARNING: This mode does NOT verify JWT signatures. Any party + // can forge a JWT with arbitrary claims, and those claims will be extracted + // and forwarded as HTTP headers. Headers set by this mode are + // INDISTINGUISHABLE from headers set by fully validated JWTs unless the + // ``verification_status_header`` is checked by downstream filters + // (set to ``false`` by default on all extract-only requests). // - // This mode will: + // DO NOT use this mode if: + // - RBAC policies match on JWT-derived headers + // - ext_authz services trust JWT-derived headers + // - Backend services use JWT-derived headers for authorization + // - The JWT source is not cryptographically authenticated by other means // - // * Decode the JWT header and payload - // * Extract claims and forward them as headers - // * Always return success (Status::Ok) regardless of JWT validity - // * Log when extraction occurs + // Use only when signature verification is PROVABLY performed elsewhere + // in the request path (e.g., by an upstream mTLS-authenticated service). // - // This mode will NOT: - // - // * Verify the JWT signature - // * Validate the (issuer) claim - // * Validate the (audience) claim - // * Check not-before time (nbf claim) ExtractOnlyWithoutValidation extract_only_without_validation = 7; } } +// Configuration for extract-only mode without JWT signature validation. +// +// When this mode is active and a JWT is present in the request but fails +// signature verification, a verification status header is set on the request +// to signal to downstream filters (RBAC, ext_authz) that the JWT claims were +// NOT cryptographically verified. The header is not set when the JWT is valid +// or when no JWT is present. +// message ExtractOnlyWithoutValidation { - // Reserved for future extensions (e.g., claim filtering, logging options) + // Name of the HTTP header set to "false" when a JWT is present but fails + // signature verification. The header is NOT set when: + // + // - The JWT is valid (verification succeeded), or + // - No JWT is present in the request. + // + // This means the header's presence is a meaningful signal to downstream + // filters: if set, the JWT was present but could not be verified, and any + // extracted claim headers should not be trusted for authorization. + // + // Downstream filters (RBAC, ext_authz) SHOULD check for the absence of this + // header (or its non-"false" value) before trusting JWT-derived claim headers + // for authorization decisions. + // + // Default (unset or empty): ``x-jwt-signature-verified``. + // + // Custom value: uses the specified header name. + // + // The header-setting behavior is guarded by the + // ``envoy.reloadable_features.jwt_authn_add_verification_status_header`` + // runtime flag (default on). If removal is needed downstream, use header + // mutation in a subsequent filter. + // + // Example: when a JWT is present in the request but fails signature + // verification, the request will carry: + // + // .. code-block:: yaml + // + // x-jwt-signature-verified: false + // + string verification_status_header = 1; } // This message specifies a list of RequiredProvider. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index bbb438b4cca8b..0fc8514884f33 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -128,5 +128,15 @@ new_features: in the composite HTTP filter. Now users could specify the matcher inline in the filter configuration instead of using the :ref:`ExtensionWithMatcher ` filter. +- area: jwt_authn + change: | + Added :ref:`verification_status_header + ` + to the ``ExtractOnlyWithoutValidation`` requirement. When a JWT is present in the request but fails + signature verification, the named request header (default ``x-jwt-signature-verified``) is set to + ``false`` so downstream filters (RBAC, ext_authz) can distinguish forwarded-but-unverified claims + from validated ones. The header is not set on a successfully verified JWT or when no JWT is present. + This behavior can be reverted by setting the runtime guard + ``envoy.reloadable_features.jwt_authn_add_verification_status_header`` to ``false``. deprecated: diff --git a/docs/root/configuration/http/http_filters/_include/jwt-authn-extract-only-rbac.yaml b/docs/root/configuration/http/http_filters/_include/jwt-authn-extract-only-rbac.yaml new file mode 100644 index 0000000000000..8108544de3bd8 --- /dev/null +++ b/docs/root/configuration/http/http_filters/_include/jwt-authn-extract-only-rbac.yaml @@ -0,0 +1,91 @@ +# Example Envoy bootstrap config showing how to use +# extract_only_without_validation with an RBAC policy that checks +# the verification status header before trusting JWT-derived headers. +# +# The RBAC policy only allows requests where: +# 1. The JWT claim 'role' equals 'admin', AND +# 2. The x-jwt-signature-verified header is NOT 'false' +# This prevents forged JWTs from satisfying the admin role check. +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: default + domains: + - "*" + routes: + - match: + prefix: "/admin" + direct_response: + status: 200 + body: + inline_string: "admin OK" + http_filters: + - name: envoy.filters.http.jwt_authn + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication + providers: + example_provider: + issuer: https://example.com + claim_to_headers: + - header_name: x-jwt-claim-role + claim_name: role + # extract_only_without_validation does not verify signatures, + # but the JWKS must still parse and contain at least one valid + # public key. This dummy RSA key satisfies that requirement + # without being used to verify anything. + local_jwks: + inline_string: >- + {"keys": [ + {"kty":"RSA", + "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86z + wu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc + 5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8K + JZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh + 6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKn + qDKgw", + "e":"AQAB", + "alg":"RS256", + "kid":"dummy"}]} + rules: + - match: + prefix: "/admin" + requires: + extract_only_without_validation: {} + - name: envoy.filters.http.rbac + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC + rules: + action: ALLOW + policies: + admin_verified_only: + permissions: + - any: true + principals: + - and_ids: + ids: + - header: + name: x-jwt-claim-role + string_match: + exact: admin + - not_id: + header: + name: x-jwt-signature-verified + string_match: + exact: "false" + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router diff --git a/docs/root/configuration/http/http_filters/jwt_authn_filter.rst b/docs/root/configuration/http/http_filters/jwt_authn_filter.rst index 6d896e1c50151..5ddc90e47b636 100644 --- a/docs/root/configuration/http/http_filters/jwt_authn_filter.rst +++ b/docs/root/configuration/http/http_filters/jwt_authn_filter.rst @@ -235,3 +235,55 @@ comes from the owning HTTP connection manager. jwks_fetch_failed, Counter, Total failed JWKS remote fetch attempts jwt_cache_hit, Counter, Total JWT cache hits where a previously validated token was reused jwt_cache_miss, Counter, Total JWT cache misses requiring full token validation + + +.. _config_jwt_authn_extract_only_security: + +Extract-Only Mode Security Considerations +------------------------------------------ + +.. warning:: + + **SECURITY WARNING**: The ``extract_only_without_validation`` + requirement type does **NOT** verify JWT signatures. Any party can craft + a JWT with arbitrary claims, and those claims will be extracted and + forwarded as HTTP headers. + + **Verification status header (default on):** + + When this mode is active, Envoy sets a verification status header on + requests whose JWT is present but fails signature verification: + + .. code-block:: yaml + + x-jwt-signature-verified: false + + The header is NOT set when the JWT is valid or when no JWT is present. + This means the header's presence is a meaningful signal: if set, the + JWT failed verification and downstream filters should not trust any + JWT-derived claim headers. + + **RBAC integration:** + + If RBAC policies match on JWT-derived claim headers (e.g., + ``x-jwt-claim-role``), add a corresponding principal check for the + verification status header. Example RBAC policy that only allows + verified admin claims: + + .. literalinclude:: _include/jwt-authn-extract-only-rbac.yaml + :language: yaml + :lines: 58-73 + :lineno-start: 58 + :linenos: + :caption: :download:`jwt-authn-extract-only-rbac.yaml <_include/jwt-authn-extract-only-rbac.yaml>` + + **Configuration:** + + The header name is configurable via ``verification_status_header`` in + ``ExtractOnlyWithoutValidation``. The header is set by default, but this behavior can be + disabled via the ``envoy.reloadable_features.jwt_authn_add_verification_status_header`` + runtime flag. + + **Recommended alternative:** Use ``provider_name`` with ``remote_jwks`` + or ``local_jwks`` for full signature verification. + diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index a97925ea9e82e..571928da38fa0 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -68,6 +68,7 @@ RUNTIME_GUARD(envoy_reloadable_features_http2_discard_host_header); RUNTIME_GUARD(envoy_reloadable_features_http_async_client_retry_respect_buffer_limits); // Delay deprecation and decommission until UHV is enabled. RUNTIME_GUARD(envoy_reloadable_features_http_reject_path_with_fragment); +RUNTIME_GUARD(envoy_reloadable_features_jwt_authn_add_verification_status_header); RUNTIME_GUARD(envoy_reloadable_features_map_http_stream_reset_to_tcp_rst); RUNTIME_GUARD(envoy_reloadable_features_mcp_filter_use_new_metadata_namespace); RUNTIME_GUARD(envoy_reloadable_features_mobile_use_network_observer_registry); diff --git a/source/extensions/filters/http/jwt_authn/verifier.cc b/source/extensions/filters/http/jwt_authn/verifier.cc index 0ad18b517f883..24d7e7a1e2e4d 100644 --- a/source/extensions/filters/http/jwt_authn/verifier.cc +++ b/source/extensions/filters/http/jwt_authn/verifier.cc @@ -3,6 +3,7 @@ #include "envoy/extensions/filters/http/jwt_authn/v3/config.pb.h" #include "source/common/jwt/check_audience.h" +#include "source/common/runtime/runtime_features.h" using envoy::extensions::filters::http::jwt_authn::v3::JwtProvider; using envoy::extensions::filters::http::jwt_authn::v3::JwtRequirement; @@ -368,25 +369,41 @@ JwtProviderList getAllProvidersAsList(const Protobuf::Map(*context); - // Set allow_failed=true and allow_missing=true to bypass validation - // The key difference is we're telling the authenticator to extract claims - // even when signature validation would fail + + // Use allow_failed=false so the authenticator surfaces the original + // verification status (e.g. JwtExpired) instead of collapsing it to Ok. + // allow_missing=true keeps "no token" reported as Ok via the missing + // path. The verifier itself collapses any remaining failure into Ok + // below, since extract-only mode never fails the request. auto auth = auth_factory_.create(nullptr, absl::nullopt, - /*=allow failed*/ true, + /*=allow failed*/ false, /*=allow missing*/ true); extractor_->sanitizeHeaders(ctximpl.headers()); @@ -396,10 +413,17 @@ class ExtractOnlyWithoutValidationVerifierImpl : public BaseVerifierImpl { ctximpl.addExtractedData(name, extracted_data); }, [this, &ctximpl](const Status& status) { - // Always treat as success for extract-only mode - // This ensures claims are forwarded even if signature validation failed ENVOY_LOG(debug, "JWT extraction completed with status: {}, treating as success", static_cast(status)); + // Status::Ok means verification succeeded; Status::JwtMissed means + // no token was present (collapsed by allow_missing). Any other + // status is a real verification failure — signal downstream that + // the forwarded claims are unverified. + if (status != Status::Ok && status != Status::JwtMissed && + Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.jwt_authn_add_verification_status_header")) { + ctximpl.headers().setCopy(verification_status_header_, kVerificationStatusValue); + } onComplete(Status::Ok, ctximpl); }, [&ctximpl]() { ctximpl.callback()->clearRouteCache(); }); @@ -414,6 +438,7 @@ class ExtractOnlyWithoutValidationVerifierImpl : public BaseVerifierImpl { private: const AuthFactory& auth_factory_; const ExtractorConstPtr extractor_; + Http::LowerCaseString verification_status_header_; }; VerifierConstPtr innerCreate(const JwtRequirement& requirement, @@ -445,7 +470,8 @@ VerifierConstPtr innerCreate(const JwtRequirement& requirement, parent); case JwtRequirement::RequiresTypeCase::kExtractOnlyWithoutValidation: return std::make_unique( - factory, getAllProvidersAsList(providers), parent); + factory, getAllProvidersAsList(providers), requirement.extract_only_without_validation(), + parent); case JwtRequirement::RequiresTypeCase::REQUIRES_TYPE_NOT_SET: return std::make_unique(parent); } diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index bcfa2eb2c4832..f7896e43afe15 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -221,6 +221,7 @@ envoy_extension_cc_test( "//source/extensions/filters/http/jwt_authn:filter_config_lib", "//source/extensions/filters/http/jwt_authn:matchers_lib", "//test/mocks/server:factory_context_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/jwt_authn/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/jwt_authn/all_verifier_test.cc b/test/extensions/filters/http/jwt_authn/all_verifier_test.cc index 7a57300a5a1c0..9d76e57a71c5a 100644 --- a/test/extensions/filters/http/jwt_authn/all_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/all_verifier_test.cc @@ -6,6 +6,7 @@ #include "test/extensions/filters/http/jwt_authn/mock.h" #include "test/extensions/filters/http/jwt_authn/test_common.h" #include "test/mocks/server/factory_context.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "absl/strings/string_view.h" @@ -662,6 +663,75 @@ TEST_F(ExtractOnlyWithoutValidationInSingleRequirementTest, TwoGoodJwts) { EXPECT_THAT(headers, JwtOutputSuccess(kOtherHeader)); } +// Test: Verification status header is NOT set when JWT is valid. +TEST_F(ExtractOnlyWithoutValidationInSingleRequirementTest, + VerificationStatusHeaderNotSetOnGoodJwt) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)); + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + // Good JWT verified successfully — no "unverified" header should be set. + EXPECT_TRUE(headers.get_("x-jwt-signature-verified").empty()); +} + +// Test: Verification status header IS set when JWT fails verification. +TEST_F(ExtractOnlyWithoutValidationInSingleRequirementTest, VerificationStatusHeaderSetOnBadJwt) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)); + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, ExpiredToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + // Expired JWT failed verification — header should be set to "false". + EXPECT_EQ(headers.get_("x-jwt-signature-verified"), "false"); +} + +// Test: Verification status header is NOT set when no JWT is present. +TEST_F(ExtractOnlyWithoutValidationInSingleRequirementTest, + VerificationStatusHeaderNotSetOnMissingJwt) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)); + auto headers = Http::TestRequestHeaderMapImpl{}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + // No JWT present — not a failure, header should not be set. + EXPECT_TRUE(headers.get_("x-jwt-signature-verified").empty()); +} + +// Test: Verification status header is NOT set when the runtime guard is disabled, +// even if a JWT is present and fails verification. +TEST_F(ExtractOnlyWithoutValidationInSingleRequirementTest, + VerificationStatusHeaderNotSetWhenRuntimeFlagDisabled) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.jwt_authn_add_verification_status_header", "false"}}); + + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)); + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, ExpiredToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + // Runtime feature disabled — header must not be set even though the JWT failed. + EXPECT_TRUE(headers.get_("x-jwt-signature-verified").empty()); +} + +// Test: Verification status header uses custom name when configured +// and JWT verification fails. +TEST_F(ExtractOnlyWithoutValidationInSingleRequirementTest, VerificationStatusHeaderCustomName) { + // Reconfigure with a custom verification_status_header. + proto_config_.mutable_rules(0) + ->mutable_requires_() + ->mutable_extract_only_without_validation() + ->set_verification_status_header("x-custom-verified"); + createVerifier(); + + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)); + // Use ExpiredToken to trigger verification failure. + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, ExpiredToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + // Custom header should be set (verification failed). + EXPECT_EQ(headers.get_("x-custom-verified"), "false"); + // Default header should NOT be set. + EXPECT_TRUE(headers.get_("x-jwt-signature-verified").empty()); +} + TEST_F(ExtractOnlyWithoutValidationInSingleRequirementTest, GoodAndBadJwts) { EXPECT_CALL(mock_cb_, onComplete(Status::Ok)); auto headers = diff --git a/test/extensions/filters/http/jwt_authn/authenticator_test.cc b/test/extensions/filters/http/jwt_authn/authenticator_test.cc index 27180360c9888..e648d8f62982a 100644 --- a/test/extensions/filters/http/jwt_authn/authenticator_test.cc +++ b/test/extensions/filters/http/jwt_authn/authenticator_test.cc @@ -1194,6 +1194,22 @@ TEST_F(AuthenticatorJwtCacheTest, TestCacheHit) { EXPECT_TRUE(TestUtility::protoEqual(out_extracted_data_, expected_payload)); } +// Test: ExtractOnlyWithoutValidation config can be set and cleared. +TEST_F(AuthenticatorTest, ExtractOnlyVerificationHeaderConfig) { + envoy::extensions::filters::http::jwt_authn::v3::ExtractOnlyWithoutValidation config; + + // By default, the header name is empty, signaling the filter to use the default. + EXPECT_TRUE(config.verification_status_header().empty()); + + // A custom header name can be set. + config.set_verification_status_header("x-custom-jwt-status"); + EXPECT_EQ(config.verification_status_header(), "x-custom-jwt-status"); + + // Clearing reverts to default behavior. + config.clear_verification_status_header(); + EXPECT_TRUE(config.verification_status_header().empty()); +} + } // namespace } // namespace JwtAuthn } // namespace HttpFilters From 8c08501217f16fac62f1d50494bacc6442864a1d Mon Sep 17 00:00:00 2001 From: danzh Date: Mon, 4 May 2026 10:06:31 -0400 Subject: [PATCH 206/750] mobile: support handling requests on worker thread (#44295) Commit Message: add a config knob `threading_model` in ApiListenerManager. And add an interface `enableWorkerThread()` in C++ and python EngineBuilder to allow choosing a threading model for mobile engine. In addition to the exiting `MAIN_THREAD_ONLY` model mostly used for mobile deployment, this PR introduces a `STANDALONE_WORKER_THREAD` model which will drive http requests in a standalone worker thread. This allows E-M to be more like Envoy which also run HTTP listeners and xDS processing on different threads so that network events won't be blocked by xDS config updates. Note that in `STANDALONE_WORKER_THREAD ` mode certificates validation offloading and system proxy setting is NOT supported as this threading model is not meant to be used by mobile applications. Risk Level: low, behind knobs Testing: existing integration tests pass Docs Changes: N/A Release Notes: N/A --------- Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- api/envoy/config/bootstrap/v3/bootstrap.proto | 10 ++ mobile/.bazelrc | 1 + mobile/library/cc/engine_builder.cc | 35 ++++- mobile/library/cc/engine_builder.h | 9 +- mobile/library/common/BUILD | 1 + mobile/library/common/engine_common.cc | 6 +- mobile/library/common/engine_common.h | 23 ++- .../platform_bridge_cert_validator.cc | 2 + .../api_listener_manager/BUILD | 3 + .../api_listener_manager.cc | 136 +++++++++++++++++- .../api_listener_manager.h | 75 ++++++++-- mobile/library/common/http/client.cc | 11 +- mobile/library/common/http/client.h | 10 +- mobile/library/common/internal_engine.cc | 126 +++++++++------- mobile/library/common/internal_engine.h | 19 ++- .../common/network/apple_proxy_resolver.cc | 1 + .../common/network/connectivity_manager.cc | 1 + mobile/library/python/module_definition.cc | 6 + mobile/test/common/http/client_test.cc | 2 + .../integration/client_integration_test.cc | 85 ++++++++--- .../integration/rtds_integration_test.cc | 13 +- mobile/test/python/fetch_test.py | 1 + 22 files changed, 479 insertions(+), 97 deletions(-) diff --git a/api/envoy/config/bootstrap/v3/bootstrap.proto b/api/envoy/config/bootstrap/v3/bootstrap.proto index f3fe408f76c4b..a34d18b14458e 100644 --- a/api/envoy/config/bootstrap/v3/bootstrap.proto +++ b/api/envoy/config/bootstrap/v3/bootstrap.proto @@ -830,4 +830,14 @@ message ValidationListenerManager { // Listener Manager via the bootstrap's :ref:`listener_manager `. // [#not-implemented-hide:] message ApiListenerManager { + enum ThreadingModel { + // Handle HTTP requests on the main Envoy thread which also processes platform-raised events and runs xDS clients. + MAIN_THREAD_ONLY = 0; + + // Handle HTTP requests on a standalone worker thread. + STANDALONE_WORKER_THREAD = 1; + } + + // Default to MainThreadOnly. + ThreadingModel threading_model = 1; } diff --git a/mobile/.bazelrc b/mobile/.bazelrc index 9a6a3fc443045..b6e2d32b40646 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -154,6 +154,7 @@ build:mobile-tsan --config=tsan build:mobile-tsan --config=mobile-clang build:mobile-tsan --build_tests_only test:mobile-tsan --test_env=ENVOY_IP_TEST_VERSIONS=v4only +build:mobile-tsan --test_timeout=240,1200,3000,9600 ############################################################################# diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index 7fe8ce1a346d3..6df864a6eab88 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -327,6 +327,10 @@ EngineBuilder& EngineBuilder::setMaxConcurrentStreams(int max_concurrent_streams EngineBuilder& EngineBuilder::enablePlatformCertificatesValidation(bool platform_certificates_validation_on) { + if (use_worker_thread_) { + // Platform certificate validation is not supported with worker thread. + return *this; + } platform_certificates_validation_on_ = platform_certificates_validation_on; return *this; } @@ -381,6 +385,19 @@ std::string EngineBuilder::nativeNameToConfig(absl::string_view name) { #endif } +EngineBuilder& EngineBuilder::enableWorkerThread(bool use_worker_thread) { + use_worker_thread_ = use_worker_thread; + if (use_worker_thread_) { + // Platform certificate validation and system proxy settings are not supported with worker + // thread. + platform_certificates_validation_on_ = false; +#ifdef __APPLE__ + respect_system_proxy_settings_ = false; +#endif + } + return *this; +} + EngineBuilder& EngineBuilder::addPlatformFilter(const std::string& name) { addNativeFilter("envoy.filters.http.platform_bridge", nativeNameToConfig(name)); return *this; @@ -446,6 +463,10 @@ EngineBuilder::setMaxTimeOnNonDefaultNetworkSeconds(int max_time_on_non_default_ #if defined(__APPLE__) EngineBuilder& EngineBuilder::respectSystemProxySettings(bool value, int refresh_interval_secs) { + if (use_worker_thread_) { + // System proxy settings are not supported with worker thread. + return *this; + } respect_system_proxy_settings_ = value; if (refresh_interval_secs > 0) { proxy_settings_refresh_interval_secs_ = refresh_interval_secs; @@ -1143,6 +1164,12 @@ std::unique_ptr EngineBuilder::generate #endif // ENVOY_MOBILE_XDS envoy::config::bootstrap::v3::ApiListenerManager api; + if (!use_worker_thread_) { + api.set_threading_model(envoy::config::bootstrap::v3::ApiListenerManager::MAIN_THREAD_ONLY); + } else { + api.set_threading_model( + envoy::config::bootstrap::v3::ApiListenerManager::STANDALONE_WORKER_THREAD); + } auto* listener_manager = bootstrap->mutable_listener_manager(); listener_manager->mutable_typed_config()->PackFrom(api); listener_manager->set_name("envoy.listener_manager_impl.api"); @@ -1151,10 +1178,10 @@ std::unique_ptr EngineBuilder::generate } EngineSharedPtr EngineBuilder::build() { - InternalEngine* envoy_engine = absl::IgnoreLeak( - new InternalEngine(std::move(callbacks_), std::move(logger_), std::move(event_tracker_), - network_thread_priority_, high_watermark_, - disable_dns_refresh_on_network_change_, enable_logger_)); + InternalEngine* envoy_engine = absl::IgnoreLeak(new InternalEngine( + std::move(callbacks_), std::move(logger_), std::move(event_tracker_), + network_thread_priority_, high_watermark_, disable_dns_refresh_on_network_change_, + enable_logger_, use_worker_thread_)); for (const auto& [name, store] : key_value_stores_) { // TODO(goaway): This leaks, but it's tied to the life of the engine. diff --git a/mobile/library/cc/engine_builder.h b/mobile/library/cc/engine_builder.h index 3f9ceedcc3c1e..cb989918ef4bb 100644 --- a/mobile/library/cc/engine_builder.h +++ b/mobile/library/cc/engine_builder.h @@ -157,6 +157,11 @@ class EngineBuilder { EngineBuilder& enableBrotliDecompression(bool brotli_decompression_on); EngineBuilder& enableSocketTagging(bool socket_tagging_on); EngineBuilder& enableHttp3(bool http3_on); + // If true, all HTTP requests are handled on a dedicated worker thread instead of on the Envoy + // main thread which also handles all xDS requests. + // Note: Engine in worker thread model doesn't support platform certificate validation and system + // proxy settings. And these settings will be ignored if worker thread model is enabled. + EngineBuilder& enableWorkerThread(bool use_worker_thread); EngineBuilder& enableEarlyData(bool early_data_on); EngineBuilder& enableScone(bool enable); EngineBuilder& addQuicConnectionOption(std::string option); @@ -377,7 +382,9 @@ class EngineBuilder { absl::optional node_locality_ = absl::nullopt; absl::optional node_metadata_ = absl::nullopt; bool enable_stats_collection_ = true; - bool enable_network_change_monitor_ = false; + bool use_worker_thread_{false}; + bool enable_network_change_monitor_{false}; + #ifdef ENVOY_MOBILE_XDS absl::optional xds_builder_ = absl::nullopt; #endif // ENVOY_MOBILE_XDS diff --git a/mobile/library/common/BUILD b/mobile/library/common/BUILD index 54e0243d760d5..ce6bb011ddc60 100644 --- a/mobile/library/common/BUILD +++ b/mobile/library/common/BUILD @@ -28,6 +28,7 @@ envoy_cc_library( ":mobile_process_wide_lib", "//library/common/bridge:utility_lib", "//library/common/event:provisional_dispatcher_lib", + "//library/common/extensions/listener_managers/api_listener_manager:api_listener_manager_lib", "//library/common/http:client_lib", "//library/common/http:header_utility_lib", "//library/common/logger:logger_delegate_lib", diff --git a/mobile/library/common/engine_common.cc b/mobile/library/common/engine_common.cc index 03f0e1719e819..d3ee363d8debd 100644 --- a/mobile/library/common/engine_common.cc +++ b/mobile/library/common/engine_common.cc @@ -81,7 +81,9 @@ class ServerLite : public Server::InstanceBase { } }; -EngineCommon::EngineCommon(std::shared_ptr options) : options_(options) { +EngineCommon::EngineCommon(std::shared_ptr options, + std::function on_workers_started) + : options_(options), mobile_listener_hooks_(on_workers_started) { #if !defined(ENVOY_ENABLE_FULL_PROTOS) registerMobileProtoDescriptors(); @@ -106,7 +108,7 @@ EngineCommon::EngineCommon(std::shared_ptr options) : op auto random_generator = std::make_unique(); base_ = std::make_unique(*options_, prod_component_factory_, std::make_unique(), *random_generator); - base_->init(real_time_system_, default_listener_hooks_, std::move(random_generator), nullptr, + base_->init(real_time_system_, mobile_listener_hooks_, std::move(random_generator), nullptr, create_instance); // Disabling signal handling in the options makes it so that the server's event dispatcher _does // not_ listen for termination signals such as SIGTERM, SIGINT, etc diff --git a/mobile/library/common/engine_common.h b/mobile/library/common/engine_common.h index 0b860b6173178..147de047e2880 100644 --- a/mobile/library/common/engine_common.h +++ b/mobile/library/common/engine_common.h @@ -14,19 +14,38 @@ #include "source/exe/terminate_handler.h" #endif +#include + namespace Envoy { // If Envoy is built with lite protos, this will register Envoy-Mobile specific // descriptors for reflection. void registerMobileProtoDescriptors(); +class MobileListenerHooks : public ListenerHooks { +public: + MobileListenerHooks(std::function on_workers_started) + : on_workers_started_(on_workers_started) {} + void onWorkerListenerAdded() override {} + void onWorkerListenerRemoved() override {} + void onWorkersStarted() override { + if (on_workers_started_) { + on_workers_started_(); + } + } + +private: + std::function on_workers_started_; +}; + /** * This class is used instead of Envoy::MainCommon to customize logic for the Envoy Mobile setting. * It largely leverages Envoy::StrippedMainBase. */ class EngineCommon { public: - EngineCommon(std::shared_ptr options); + EngineCommon(std::shared_ptr options, + std::function on_workers_started = nullptr); bool run() { base_->runServer(); return true; @@ -47,7 +66,7 @@ class EngineCommon { #endif std::shared_ptr options_; Event::RealTimeSystem real_time_system_; // NO_CHECK_FORMAT(real_time) - DefaultListenerHooks default_listener_hooks_; + MobileListenerHooks mobile_listener_hooks_; ProdComponentFactory prod_component_factory_; std::shared_ptr base_; }; diff --git a/mobile/library/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator.cc b/mobile/library/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator.cc index e3151f4a3564c..2b7a0a0158585 100644 --- a/mobile/library/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator.cc +++ b/mobile/library/common/extensions/cert_validator/platform_bridge/platform_bridge_cert_validator.cc @@ -109,6 +109,8 @@ ValidationResults PlatformBridgeCertValidator::doVerifyCertChain( ValidationJob job; job.result_callback_ = std::move(callback); Event::Dispatcher& dispatcher = job.result_callback_->dispatcher(); + ASSERT(Thread::MainThread::isMainOrTestThread(), + "Certification validation must happen on the main thread."); Thread::Options thread_options; thread_options.priority_ = thread_priority_; job.validation_thread_ = thread_factory_->createThread( diff --git a/mobile/library/common/extensions/listener_managers/api_listener_manager/BUILD b/mobile/library/common/extensions/listener_managers/api_listener_manager/BUILD index 96514ed28e2a2..28c33d90b286d 100644 --- a/mobile/library/common/extensions/listener_managers/api_listener_manager/BUILD +++ b/mobile/library/common/extensions/listener_managers/api_listener_manager/BUILD @@ -18,9 +18,12 @@ envoy_cc_extension( ], repository = "@envoy", deps = [ + "//library/common/event:provisional_dispatcher_lib", + "//library/common/http:client_lib", "@envoy//envoy/server:api_listener_interface", "@envoy//envoy/server:instance_interface", "@envoy//envoy/server:listener_manager_interface", + "@envoy//envoy/thread:thread_interface", "@envoy//source/extensions/api_listeners/default_api_listener:api_listener_lib", "@envoy//source/server:listener_manager_factory_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", diff --git a/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.cc b/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.cc index c851f89d4aabe..897d925e5bc44 100644 --- a/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.cc +++ b/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.cc @@ -7,11 +7,104 @@ #include "source/common/common/assert.h" #include "source/common/common/fmt.h" #include "source/common/config/utility.h" +#include "absl/synchronization/notification.h" namespace Envoy { namespace Server { -ApiListenerManagerImpl::ApiListenerManagerImpl(Instance& server) : server_(server) {} +ApiListenerWorker::ApiListenerWorker(Instance& server) + : server_(server), dispatcher_(server.api().allocateDispatcher("api_listener_worker")), + provisional_dispatcher_(std::make_unique()) { + server_.threadLocal().registerThread(*dispatcher_, false); + provisional_dispatcher_->drain(*dispatcher_); +} + +ApiListenerWorker::~ApiListenerWorker() {} + +void ApiListenerWorker::addListener(ApiListenerOptRef api_listener, std::function cb) { + dispatcher_->post([this, api_listener, cb]() { + api_listener_ = api_listener; + if (api_listener_.has_value()) { + auto api_listener_impl = api_listener_->get().createHttpApiListener(*dispatcher_); + ASSERT(api_listener_impl != nullptr); + http_client_ = std::make_unique( + std::move(api_listener_impl), *provisional_dispatcher_, + server_.serverFactoryContext().scope(), server_.api().randomGenerator()); + ENVOY_LOG_MISC(info, "Created API listener."); + if (shutdown_notification_.HasBeenNotified()) { + ENVOY_LOG_MISC(info, "Shutting down API listener."); + http_client_->shutdownApiListener(); + } + } + if (cb) { + server_.dispatcher().post([cb]() { cb(); }); + } + }); +} + +void ApiListenerWorker::start(OptRef guard_dog, const std::function& cb) { + ASSERT(!thread_); + + Thread::Options options{absl::StrCat("wrk:", dispatcher_->name())}; + thread_ = server_.api().threadFactory().createThread( + [this, guard_dog, cb]() -> void { threadRoutine(guard_dog, cb); }, options); +} + +void ApiListenerWorker::initializeStats(Stats::Scope& scope) { + if (dispatcher_) { + dispatcher_->initializeStats(scope); + } +} + +void ApiListenerWorker::stop() { + if (thread_) { + shutdown_notification_.WaitForNotification(); + ENVOY_LOG_MISC(info, "Listener has been shutdown, exiting the event loop."); + dispatcher_->exit(); + thread_->join(); + thread_.reset(); + } +} + +void ApiListenerWorker::stopListener() { + dispatcher_->post([this]() { + if (http_client_) { + http_client_->shutdownApiListener(); + } + shutdown_notification_.Notify(); + }); +} + +void ApiListenerWorker::threadRoutine(OptRef guard_dog, const std::function& cb) { + if (cb) { + dispatcher_->post(cb); + } + if (guard_dog.has_value()) { + dispatcher_->post([this, guard_dog]() { + watch_dog_ = guard_dog->createWatchDog(server_.api().threadFactory().currentThreadId(), + dispatcher_->name(), *dispatcher_); + }); + } + + dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + dispatcher_->shutdown(); + ENVOY_LOG_MISC(info, "ApiListenerWorker dispatcher exited"); + if (guard_dog.has_value() && watch_dog_) { + guard_dog->stopWatching(watch_dog_); + } + + watch_dog_.reset(); +} + +ApiListenerManagerImpl::ApiListenerManagerImpl(Instance& server, bool use_worker_thread) + : server_(server), + worker_(use_worker_thread ? std::make_unique(server) : nullptr) {} + +ApiListenerManagerImpl::~ApiListenerManagerImpl() { + if (worker_) { + stopWorkers(); + } +} absl::StatusOr ApiListenerManagerImpl::addOrUpdateListener(const envoy::config::listener::v3::Listener& config, @@ -56,6 +149,47 @@ ApiListenerManagerImpl::addOrUpdateListener(const envoy::config::listener::v3::L return false; } +absl::Status ApiListenerManagerImpl::startWorkers(OptRef guard_dog, + std::function callback) { + if (!worker_) { + if (callback) { + callback(); + } + return absl::OkStatus(); + } + + if (worker_started_.load()) { + return absl::OkStatus(); + } + + ASSERT(api_listener_ != nullptr, "api_listener_ is null in startWorkers!"); + worker_->addListener(api_listener_ ? ApiListenerOptRef(std::ref(*api_listener_)) : absl::nullopt, + callback); + absl::Notification worker_started_notification; + worker_->start(guard_dog, [&worker_started_notification, this]() { + worker_started_.store(true); + worker_started_notification.Notify(); + }); + worker_started_notification.WaitForNotification(); + ENVOY_LOG_MISC(info, "Worker thread has been started"); + + // The client should be available at this point. + worker_->httpClient(); + + return absl::OkStatus(); +} + +void ApiListenerManagerImpl::stopListeners(StopListenersType, + const Network::ExtraShutdownListenerOptions&) { + worker_->stopListener(); +} + +void ApiListenerManagerImpl::stopWorkers() { + if (worker_started_.load()) { + worker_->stop(); + } +} + REGISTER_FACTORY(ApiListenerManagerFactoryImpl, ListenerManagerFactory); } // namespace Server diff --git a/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.h b/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.h index bffdbc8776cc5..56cf66378abb3 100644 --- a/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.h +++ b/mobile/library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.h @@ -2,22 +2,61 @@ #include +#include "absl/synchronization/notification.h" + #include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/server/instance.h" #include "envoy/server/listener_manager.h" +#include "envoy/config/bootstrap/v3/bootstrap.pb.validate.h" #include "source/server/listener_manager_factory.h" +#include "envoy/thread/thread.h" +#include "library/common/event/provisional_dispatcher.h" +#include "library/common/http/client.h" + namespace Envoy { namespace Server { +class ApiListenerWorker { +public: + ApiListenerWorker(Instance& server); + ~ApiListenerWorker(); + + void addListener(ApiListenerOptRef api_listener, std::function cb); + void start(OptRef guard_dog, const std::function& cb); + void initializeStats(Stats::Scope& scope); + void stop(); + void stopListener(); + + Http::Client& httpClient() { + RELEASE_ASSERT(http_client_ != nullptr, "http_client_ is null!"); + return *http_client_; + } + Event::Dispatcher& dispatcher() { return *dispatcher_; } + +private: + void threadRoutine(OptRef guard_dog, const std::function& cb); + + Instance& server_; + ApiListenerOptRef api_listener_; + Thread::ThreadPtr thread_; + Event::DispatcherPtr dispatcher_; + // Wraps above dispatcher_ for the http client. + std::unique_ptr provisional_dispatcher_; + std::unique_ptr http_client_; + WatchDogSharedPtr watch_dog_; + absl::Notification shutdown_notification_; +}; + /** * Implementation of a lightweight ListenerManager for Envoy Mobile. * This does not handle downstream TCP / UDP connections but only the API listener. */ class ApiListenerManagerImpl : public ListenerManager, Logger::Loggable { public: - explicit ApiListenerManagerImpl(Instance& server); + explicit ApiListenerManagerImpl(Instance& server, bool use_worker_thread = false); + ~ApiListenerManagerImpl() override; // Server::ListenerManager absl::StatusOr addOrUpdateListener(const envoy::config::listener::v3::Listener& config, @@ -30,15 +69,12 @@ class ApiListenerManagerImpl : public ListenerManager, Logger::Loggable, std::function callback) override { - callback(); - return absl::OkStatus(); - } - void stopListeners(StopListenersType, const Network::ExtraShutdownListenerOptions&) override {} - void stopWorkers() override {} + absl::Status startWorkers(OptRef guard_dog, std::function callback) override; + void stopListeners(StopListenersType, const Network::ExtraShutdownListenerOptions&) override; + void stopWorkers() override; void beginListenerUpdate() override {} void endListenerUpdate(FailureStates&&) override {} - bool isWorkerStarted() override { return true; } + bool isWorkerStarted() override { return worker_started_.load(); } Http::Context& httpContext() { return server_.httpContext(); } ApiListenerOptRef apiListener() override { return api_listener_ ? ApiListenerOptRef(std::ref(*api_listener_)) : absl::nullopt; @@ -47,19 +83,38 @@ class ApiListenerManagerImpl : public ListenerManager, Logger::Loggable(); } + Http::Client& httpClient() { + ASSERT(worker_ != nullptr, "httpClient() called when worker_ is null!"); + return worker_->httpClient(); + } + + Event::Dispatcher& httpClientDispatcher() { + ASSERT(worker_ != nullptr, "httpClientDispatcher() called when worker_ is null!"); + return worker_->dispatcher(); + } + private: struct ListenerUpdateCallbacksNopHandle : public ListenerUpdateCallbacksHandle {}; Instance& server_; ApiListenerPtr api_listener_; + + std::atomic worker_started_{false}; + std::unique_ptr worker_; }; class ApiListenerManagerFactoryImpl : public ListenerManagerFactory { public: std::unique_ptr - createListenerManager(const Protobuf::Message&, Instance& server, + createListenerManager(const Protobuf::Message& config, Instance& server, std::unique_ptr&&, WorkerFactory&, bool, Quic::QuicStatNames&) override { - return std::make_unique(server); + const auto& api_config = + MessageUtil::downcastAndValidate( + config, server.messageValidationContext().staticValidationVisitor()); + bool use_worker_thread = + (api_config.threading_model() == + envoy::config::bootstrap::v3::ApiListenerManager::STANDALONE_WORKER_THREAD); + return std::make_unique(server, use_worker_thread); } std::string name() const override { return "envoy.listener_manager_impl.api"; } ProtobufTypes::MessagePtr createEmptyConfigProto() override { diff --git a/mobile/library/common/http/client.cc b/mobile/library/common/http/client.cc index 01bc31277d2f6..90288efa2cafe 100644 --- a/mobile/library/common/http/client.cc +++ b/mobile/library/common/http/client.cc @@ -304,7 +304,7 @@ void Client::DirectStreamCallbacks::onError() { // error occurs (e.g., timeout)? if (explicit_flow_control_ && (hasDataToSend() || response_trailers_.get())) { - ENVOY_LOG(debug, "[S{}] defering remote reset stream due to explicit flow control", + ENVOY_LOG(debug, "[S{}] deferring remote reset stream due to explicit flow control", direct_stream_.stream_handle_); if (direct_stream_.parent_.getStream(direct_stream_.stream_handle_, GetStreamFilters::AllowOnlyForOpenStreams)) { @@ -472,7 +472,7 @@ void Client::DirectStreamCallbacks::latchError() { !resp_code_details.empty()) { error_msg_details.push_back(absl::StrCat("det: ", std::move(resp_code_details))); } - // The format of the error message propogated to callbacks is: + // The format of the error message propagated to callbacks is: // rc: {value}|ec: {value}|rsp_flags: {value}|http: {value}|det: {value} // // Where envoy_rc is the HTTP response code from StreamInfo::responseCode(). @@ -547,6 +547,13 @@ void Client::DirectStream::dumpState(std::ostream&, int indent_level) const { void Client::startStream(envoy_stream_t new_stream_handle, EnvoyStreamCallbacks&& stream_callbacks, bool explicit_flow_control) { ASSERT(dispatcher_.isThreadSafe()); + + if (!api_listener_) { + ENVOY_LOG(debug, "[S{}] stream can't be started after shutdown.", new_stream_handle); + stream_callbacks.on_cancel_({}, {}); + return; + } + Client::DirectStreamSharedPtr direct_stream{new DirectStream(new_stream_handle, *this)}; direct_stream->explicit_flow_control_ = explicit_flow_control; direct_stream->callbacks_ = diff --git a/mobile/library/common/http/client.h b/mobile/library/common/http/client.h index b8e996d38458a..1e02678520b96 100644 --- a/mobile/library/common/http/client.h +++ b/mobile/library/common/http/client.h @@ -65,6 +65,11 @@ class Client : public Logger::Loggable { // and having local data of 2M + kernel-buffer-limit for HTTP/1.1 random_(random), high_watermark_(high_watermark.value_or(2 * 1024 * 1024)) {} + ~Client() { + ASSERT(api_listener_ == nullptr, + "shutdownApiListener must be called before Client is destructed"); + } + /** * Attempts to open a new stream to the remote. Note that this function is asynchronous and * opening a stream may fail. The returned handle is immediately valid for use with this API, but @@ -139,7 +144,10 @@ class Client : public Logger::Loggable { CONSTRUCT_ON_FIRST_USE(std::string, "client_cancelled_stream"); } - void shutdownApiListener() { api_listener_.reset(); } + void shutdownApiListener() { + ENVOY_LOG(debug, "shutdownApiListener called"); + api_listener_.reset(); + } private: class DirectStream; diff --git a/mobile/library/common/internal_engine.cc b/mobile/library/common/internal_engine.cc index 370a5d8448237..43be67845adba 100644 --- a/mobile/library/common/internal_engine.cc +++ b/mobile/library/common/internal_engine.cc @@ -15,6 +15,7 @@ #include "library/common/network/network_types.h" #include "library/common/network/proxy_api.h" #include "library/common/stats/utility.h" +#include "library/common/extensions/listener_managers/api_listener_manager/api_listener_manager.h" namespace Envoy { namespace { @@ -78,19 +79,18 @@ bool areIpAddressesDifferent(const Network::Address::InstanceConstSharedPtr& add static std::atomic current_stream_handle_{0}; -InternalEngine::InternalEngine(std::unique_ptr callbacks, - std::unique_ptr logger, - std::unique_ptr event_tracker, - absl::optional thread_priority, - absl::optional high_watermark, - bool disable_dns_refresh_on_network_change, - Thread::PosixThreadFactoryPtr thread_factory, bool enable_logger) +InternalEngine::InternalEngine( + std::unique_ptr callbacks, std::unique_ptr logger, + std::unique_ptr event_tracker, absl::optional thread_priority, + absl::optional high_watermark, bool disable_dns_refresh_on_network_change, + Thread::PosixThreadFactoryPtr thread_factory, bool enable_logger, bool use_worker_thread) : thread_factory_(std::move(thread_factory)), callbacks_(std::move(callbacks)), logger_(std::move(logger)), event_tracker_(std::move(event_tracker)), thread_priority_(thread_priority), high_watermark_(high_watermark), - dispatcher_(std::make_unique()), + main_dispatcher_(std::make_unique()), disable_dns_refresh_on_network_change_(disable_dns_refresh_on_network_change), - enable_logger_(enable_logger) { + enable_logger_(enable_logger), use_worker_thread_(use_worker_thread), + request_dispatcher_(std::make_unique()) { ExtensionRegistry::registerFactories(); Api::External::registerApi(std::string(ENVOY_EVENT_TRACKER_API_NAME), &event_tracker_); @@ -101,52 +101,52 @@ InternalEngine::InternalEngine(std::unique_ptr callbacks, std::unique_ptr event_tracker, absl::optional thread_priority, absl::optional high_watermark, - bool disable_dns_refresh_on_network_change, bool enable_logger) + bool disable_dns_refresh_on_network_change, bool enable_logger, + bool use_worker_thread) : InternalEngine(std::move(callbacks), std::move(logger), std::move(event_tracker), thread_priority, high_watermark, disable_dns_refresh_on_network_change, - Thread::PosixThreadFactory::create(), enable_logger) {} + Thread::PosixThreadFactory::create(), enable_logger, use_worker_thread) {} envoy_stream_t InternalEngine::initStream() { return current_stream_handle_++; } envoy_status_t InternalEngine::startStream(envoy_stream_t stream, EnvoyStreamCallbacks&& stream_callbacks, bool explicit_flow_control) { - return dispatcher_->post( - [&, stream, stream_callbacks = std::move(stream_callbacks), explicit_flow_control]() mutable { - http_client_->startStream(stream, std::move(stream_callbacks), explicit_flow_control); - }); + return requestDispatcher().post([this, stream, stream_callbacks = std::move(stream_callbacks), + explicit_flow_control]() mutable { + http_client_handle_->startStream(stream, std::move(stream_callbacks), explicit_flow_control); + }); } envoy_status_t InternalEngine::sendHeaders(envoy_stream_t stream, Http::RequestHeaderMapPtr headers, bool end_stream, bool idempotent) { - return dispatcher_->post( + return requestDispatcher().post( [this, stream, headers = std::move(headers), end_stream, idempotent]() mutable { - http_client_->sendHeaders(stream, std::move(headers), end_stream, idempotent); + http_client_handle_->sendHeaders(stream, std::move(headers), end_stream, idempotent); }); - return ENVOY_SUCCESS; } envoy_status_t InternalEngine::readData(envoy_stream_t stream, size_t bytes_to_read) { - return dispatcher_->post( - [&, stream, bytes_to_read]() { http_client_->readData(stream, bytes_to_read); }); + return requestDispatcher().post( + [this, stream, bytes_to_read]() { http_client_handle_->readData(stream, bytes_to_read); }); } envoy_status_t InternalEngine::sendData(envoy_stream_t stream, Buffer::InstancePtr buffer, bool end_stream) { - return dispatcher_->post([&, stream, buffer = std::move(buffer), end_stream]() mutable { - http_client_->sendData(stream, std::move(buffer), end_stream); + return requestDispatcher().post([this, stream, buffer = std::move(buffer), end_stream]() mutable { + http_client_handle_->sendData(stream, std::move(buffer), end_stream); }); } envoy_status_t InternalEngine::sendTrailers(envoy_stream_t stream, Http::RequestTrailerMapPtr trailers) { - return dispatcher_->post([&, stream, trailers = std::move(trailers)]() mutable { - http_client_->sendTrailers(stream, std::move(trailers)); + return requestDispatcher().post([this, stream, trailers = std::move(trailers)]() mutable { + http_client_handle_->sendTrailers(stream, std::move(trailers)); }); } envoy_status_t InternalEngine::cancelStream(envoy_stream_t stream) { - return dispatcher_->post([&, stream]() { http_client_->cancelStream(stream); }); + return requestDispatcher().post([this, stream]() { http_client_handle_->cancelStream(stream); }); } // This function takes a `std::shared_ptr` instead of `std::unique_ptr` because `std::function` is a @@ -193,7 +193,17 @@ envoy_status_t InternalEngine::main(std::shared_ptr options) { } } - main_common = std::make_unique(options); + main_common = std::make_unique(options, [this]() { + ASSERT(Thread::MainThread::isMainOrTestThread()); + if (use_worker_thread_) { + ENVOY_LOG_MISC(info, "Worker thread has started."); + auto* api_mgr = + dynamic_cast(&server_->listenerManager()); + ASSERT(api_mgr != nullptr); + http_client_handle_ = &api_mgr->httpClient(); + request_dispatcher_->drain(api_mgr->httpClientDispatcher()); + } + }); server_ = main_common->server(); event_dispatcher_ = &server_->dispatcher(); @@ -227,7 +237,7 @@ envoy_status_t InternalEngine::main(std::shared_ptr options) { connectivity_manager_ = Network::ConnectivityManagerFactory{generic_context}.get(); Network::DefaultNetworkChangeCallback cb = [this](envoy_netconf_t current_configuration_key) { - dispatcher_->post([this, current_configuration_key]() { + main_dispatcher_->post([this, current_configuration_key]() { if (connectivity_manager_->getConfigurationKey() != current_configuration_key) { // The default network has changed to a different one. return; @@ -261,13 +271,17 @@ envoy_status_t InternalEngine::main(std::shared_ptr options) { // on-the-fly without risking contention on system with lots of threads. // It also comes with ease of programming. stat_name_set_ = client_scope_->symbolTable().makeSet("pulse"); - auto api_listener = server_->listenerManager().apiListener()->get().createHttpApiListener( - server_->dispatcher()); - ASSERT(api_listener != nullptr); - http_client_ = std::make_unique( - std::move(api_listener), *dispatcher_, server_->serverFactoryContext().scope(), - server_->api().randomGenerator(), high_watermark_); - dispatcher_->drain(server_->dispatcher()); + if (!use_worker_thread_) { + auto api_listener = + server_->listenerManager().apiListener()->get().createHttpApiListener( + server_->dispatcher()); + ASSERT(api_listener != nullptr); + http_client_ = std::make_unique( + std::move(api_listener), *main_dispatcher_, server_->serverFactoryContext().scope(), + server_->api().randomGenerator()); + http_client_handle_ = http_client_.get(); + } + main_dispatcher_->drain(server_->dispatcher()); engine_running_.Notify(); callbacks_->on_engine_running_(); }); @@ -279,6 +293,10 @@ envoy_status_t InternalEngine::main(std::shared_ptr options) { // Ensure destructors run on Envoy's main thread. postinit_callback_handler_.reset(nullptr); + if (http_client_) { + http_client_.reset(); + http_client_handle_ = nullptr; + } connectivity_manager_.reset(); client_scope_.reset(); stat_name_set_.reset(); @@ -321,17 +339,21 @@ envoy_status_t InternalEngine::terminate() { } ASSERT(event_dispatcher_); - ASSERT(dispatcher_); - - // We must destroy the Http::ApiListener in the main thread. - dispatcher_->post([this]() { http_client_->shutdownApiListener(); }); + ASSERT(main_dispatcher_); + if (!use_worker_thread_) { + main_dispatcher_->post([this]() { http_client_->shutdownApiListener(); }); + } // Exit the event loop and finish up in Engine::run(...) if (thread_factory_->currentPthreadId() == main_thread_->pthreadId()) { // TODO(goaway): figure out some way to support this. PANIC("Terminating the engine from its own main thread is currently unsupported."); } else { - dispatcher_->terminate(); + if (use_worker_thread_) { + server_->listenerManager().stopListeners(Server::ListenerManager::StopListenersType::All, + Network::ExtraShutdownListenerOptions{}); + } + main_dispatcher_->terminate(); } } // lock(_mutex) @@ -351,13 +373,16 @@ InternalEngine::~InternalEngine() { } envoy_status_t InternalEngine::setProxySettings(absl::string_view hostname, const uint16_t port) { - return dispatcher_->post([&, host = std::string(hostname), port]() -> void { - connectivity_manager_->setProxySettings(Network::ProxySettings::parseHostAndPort(host, port)); + return main_dispatcher_->post([&, host = std::string(hostname), port]() -> void { + if (!use_worker_thread_) { + // Proxy settings are not supported when using worker thread. + connectivity_manager_->setProxySettings(Network::ProxySettings::parseHostAndPort(host, port)); + } }); } envoy_status_t InternalEngine::resetConnectivityState() { - return dispatcher_->post([&]() -> void { connectivity_manager_->resetConnectivityState(); }); + return main_dispatcher_->post([&]() -> void { connectivity_manager_->resetConnectivityState(); }); } void InternalEngine::onDefaultNetworkAvailable() { @@ -368,7 +393,7 @@ void InternalEngine::onDefaultNetworkAvailable() { void InternalEngine::onDefaultNetworkChangeEvent(const int network_type) { ENVOY_LOG_MISC(trace, "Calling the default network change event callback"); - dispatcher_->post([&, network_type]() -> void { + main_dispatcher_->post([&, network_type]() -> void { Network::Address::InstanceConstSharedPtr local_addr = probeAndGetLocalAddr(AF_INET6); const bool has_ipv6_connectivity = local_addr != nullptr; if (local_addr == nullptr) { @@ -395,7 +420,7 @@ void InternalEngine::onDefaultNetworkChangeEvent(const int network_type) { // default. void InternalEngine::onDefaultNetworkChanged(int network) { ENVOY_LOG_MISC(trace, "Calling the default network changed callback"); - dispatcher_->post([&, network]() -> void { + main_dispatcher_->post([&, network]() -> void { handleNetworkChange(network, probeAndGetLocalAddr(AF_INET6) != nullptr); }); } @@ -427,7 +452,7 @@ void InternalEngine::purgeActiveNetworkListAndroid(const std::vector& a void InternalEngine::onDefaultNetworkUnavailable() { ENVOY_LOG_MISC(trace, "Calling the default network unavailable callback"); - dispatcher_->post([&]() -> void { connectivity_manager_->dnsCache()->stop(); }); + main_dispatcher_->post([&]() -> void { connectivity_manager_->dnsCache()->stop(); }); } void InternalEngine::handleNetworkChange(const int network_type, const bool has_ipv6_connectivity) { @@ -480,7 +505,7 @@ void InternalEngine::resetHttpPropertiesAndDrainHosts(bool has_ipv6_connectivity envoy_status_t InternalEngine::recordCounterInc(absl::string_view elements, envoy_stats_tags tags, uint64_t count) { - return dispatcher_->post( + return main_dispatcher_->post( [&, name = Stats::Utility::sanitizeStatsName(elements), tags, count]() -> void { ENVOY_LOG(trace, "[pulse.{}] recordCounterInc", name); Stats::StatNameTagVector tags_vctr = @@ -490,7 +515,7 @@ envoy_status_t InternalEngine::recordCounterInc(absl::string_view elements, envo }); } -Event::ProvisionalDispatcher& InternalEngine::dispatcher() const { return *dispatcher_; } +Event::ProvisionalDispatcher& InternalEngine::dispatcher() const { return *main_dispatcher_; } Thread::PosixThreadFactory& InternalEngine::threadFactory() const { return *thread_factory_; } @@ -540,7 +565,7 @@ std::string InternalEngine::dumpStats() { std::string stats; absl::Notification stats_received; - if (dispatcher_->post([&]() -> void { + if (main_dispatcher_->post([&]() -> void { Envoy::Buffer::OwnedImpl instance; handlerStats(server_->stats(), instance); stats = instance.toString(); @@ -553,13 +578,14 @@ std::string InternalEngine::dumpStats() { } Upstream::ClusterManager& InternalEngine::getClusterManager() { - ASSERT(dispatcher_->isThreadSafe(), + ASSERT(main_dispatcher_->isThreadSafe(), "getClusterManager must be called from the dispatcher's context"); return server_->clusterManager(); } Stats::Store& InternalEngine::getStatsStore() { - ASSERT(dispatcher_->isThreadSafe(), "getStatsStore must be called from the dispatcher's context"); + ASSERT(main_dispatcher_->isThreadSafe(), + "getStatsStore must be called from the dispatcher's context"); return server_->stats(); } diff --git a/mobile/library/common/internal_engine.h b/mobile/library/common/internal_engine.h index 6550d42aee917..826c639e9d1c2 100644 --- a/mobile/library/common/internal_engine.h +++ b/mobile/library/common/internal_engine.h @@ -36,7 +36,8 @@ class InternalEngine : public Logger::Loggable { std::unique_ptr event_tracker, absl::optional thread_priority = absl::nullopt, absl::optional high_watermark = absl::nullopt, - bool disable_dns_refresh_on_network_change = false, bool enable_logger = true); + bool disable_dns_refresh_on_network_change = false, bool enable_logger = true, + bool use_worker_thread = false); /** * InternalEngine destructor. @@ -212,7 +213,8 @@ class InternalEngine : public Logger::Loggable { std::unique_ptr event_tracker, absl::optional thread_priority, absl::optional high_watermark, bool disable_dns_refresh_on_network_change, - Thread::PosixThreadFactoryPtr thread_factory, bool enable_logger = true); + Thread::PosixThreadFactoryPtr thread_factory, bool enable_logger = true, + bool use_worker_thread = false); envoy_status_t main(std::shared_ptr options); static void logInterfaces(absl::string_view event, @@ -234,6 +236,10 @@ class InternalEngine : public Logger::Loggable { // Called when it's been determined that the default network has changed. void resetHttpPropertiesAndDrainHosts(bool has_ipv6_connectivity); + Event::ProvisionalDispatcher& requestDispatcher() const { + return use_worker_thread_ ? *request_dispatcher_ : *main_dispatcher_; + } + Thread::PosixThreadFactoryPtr thread_factory_; Event::Dispatcher* event_dispatcher_{}; Stats::ScopeSharedPtr client_scope_; @@ -249,7 +255,7 @@ class InternalEngine : public Logger::Loggable { Thread::CondVar cv_; Http::ClientPtr http_client_; Network::ConnectivityManagerImplSharedPtr connectivity_manager_; - Event::ProvisionalDispatcherPtr dispatcher_; + Event::ProvisionalDispatcherPtr main_dispatcher_; // Used by the cerr logger to ensure logs don't overwrite each other. absl::Mutex log_mutex_; Logger::EventTrackingDelegatePtr log_delegate_ptr_{}; @@ -264,6 +270,13 @@ class InternalEngine : public Logger::Loggable { int prev_network_type_{0}; Network::Address::InstanceConstSharedPtr prev_local_addr_{nullptr}; bool enable_logger_{true}; + bool use_worker_thread_{false}; + // If use_worker_thread_ is true, http_client_handle_ will point to the http_client_ in the + // ApiListenerWorker. Otherwise, it will point to http_client_. + Http::Client* http_client_handle_{nullptr}; + // If use_worker_thread_ is true, request_dispatcher_ will point to the dispatcher_ in the + // ApiListenerWorker. Otherwise, it will be null. + Event::ProvisionalDispatcherPtr request_dispatcher_; }; } // namespace Envoy diff --git a/mobile/library/common/network/apple_proxy_resolver.cc b/mobile/library/common/network/apple_proxy_resolver.cc index 2c36eba5e6c07..6a8d7cb514c55 100644 --- a/mobile/library/common/network/apple_proxy_resolver.cc +++ b/mobile/library/common/network/apple_proxy_resolver.cc @@ -42,6 +42,7 @@ AppleProxyResolver::resolveProxy(const std::string& target_url_string, ProxySettingsResolvedCallback proxy_resolution_completed) { ASSERT(started_, "AppleProxyResolver not started."); ASSERT(dispatcher_ != nullptr, "Dispatcher not set on the AppleProxyResolver."); + ASSERT(dispatcher_->isThreadSafe(), "Proxy resolution must happen on the main thread."); std::string pac_file_url; { diff --git a/mobile/library/common/network/connectivity_manager.cc b/mobile/library/common/network/connectivity_manager.cc index 9f87acaf053f2..75b3c6441c405 100644 --- a/mobile/library/common/network/connectivity_manager.cc +++ b/mobile/library/common/network/connectivity_manager.cc @@ -637,6 +637,7 @@ ConnectivityManagerImplSharedPtr ConnectivityManagerFactory::get() { SINGLETON_MANAGER_REGISTERED_NAME(connectivity_manager), [this] { Envoy::Extensions::Common::DynamicForwardProxy::DnsCacheManagerFactoryImpl cache_manager_factory{context_}; + return std::make_shared( context_.serverFactoryContext().clusterManager(), cache_manager_factory.get()); }); diff --git a/mobile/library/python/module_definition.cc b/mobile/library/python/module_definition.cc index 575067524b6a7..466f84045101a 100644 --- a/mobile/library/python/module_definition.cc +++ b/mobile/library/python/module_definition.cc @@ -378,6 +378,12 @@ PYBIND11_MODULE(envoy_engine, m) { return self.enableStatsCollection(on); }, py::arg("stats_collection_on"), py::return_value_policy::reference) + .def( + "enable_worker_thread", + [](Envoy::Platform::EngineBuilder& self, bool on) -> Envoy::Platform::EngineBuilder& { + return self.enableWorkerThread(on); + }, + py::arg("use_worker_thread_on"), py::return_value_policy::reference) .def("build", &Envoy::Platform::EngineBuilder::build, py::call_guard()); } diff --git a/mobile/test/common/http/client_test.cc b/mobile/test/common/http/client_test.cc index f05b653db01cf..b13fe3f75a5fc 100644 --- a/mobile/test/common/http/client_test.cc +++ b/mobile/test/common/http/client_test.cc @@ -84,6 +84,8 @@ class ClientTest : public testing::TestWithParam { }); } + void TearDown() override { http_client_.shutdownApiListener(); } + struct StreamCallbacksCalled { uint32_t on_headers_calls_{0}; uint32_t on_data_calls_{0}; diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index ab6b12c4caa5f..d50db5e5bbceb 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -61,14 +61,15 @@ class TestKeyValueStore : public Envoy::Platform::KeyValueStore { std::string value_; }; -class ClientIntegrationTest - : public BaseClientIntegrationTest, - public testing::TestWithParam> { +class ClientIntegrationTest : public BaseClientIntegrationTest, + public testing::TestWithParam< + std::tuple> { public: static void SetUpTestCase() { test_key_value_store_ = std::make_shared(); } static void TearDownTestCase() { test_key_value_store_.reset(); } Http::CodecType getCodecType() { return std::get<1>(GetParam()); } + bool getUseWorkerThread() { return std::get<2>(GetParam()); } ClientIntegrationTest() : BaseClientIntegrationTest(/*ip_version=*/std::get<0>(GetParam())) { // For server TLS @@ -86,6 +87,12 @@ class ClientIntegrationTest } void initialize() override { + builder_.enableWorkerThread(getUseWorkerThread()); + if (getUseWorkerThread()) { + // Platform cert validation is disabled when using worker thread. The engine will use the + // built-in root certs which doesn't have the CA cert for our fake upstream. + builder_.enforceTrustChainVerification(false); + } // Integration test starts upstreams before Envoy which can cause a data race. builder_.enableLogger(false); builder_.setLogLevel(Logger::Logger::trace); @@ -194,13 +201,14 @@ class ClientIntegrationTest } static std::string testParamsToString( - const testing::TestParamInfo> + const testing::TestParamInfo> params) { return fmt::format( - "{}_{}", + "{}_{}_{}", TestUtility::ipTestParamsToString(testing::TestParamInfo( std::get<0>(params.param), params.index)), - protocolToString(std::get<1>(params.param))); + protocolToString(std::get<1>(params.param)), + std::get<2>(params.param) ? "WorkerThreadOn" : "WorkerThreadOff"); } protected: @@ -218,14 +226,17 @@ INSTANTIATE_TEST_SUITE_P( IpVersions, ClientIntegrationTest, testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), testing::ValuesIn({Http::CodecType::HTTP1, Http::CodecType::HTTP2, - Http::CodecType::HTTP3})), + Http::CodecType::HTTP3}), + testing::Bool()), ClientIntegrationTest::testParamsToString); void ClientIntegrationTest::basicTest() { if (getCodecType() != Http::CodecType::HTTP1) { EXPECT_CALL(helper_handle_->mock_helper(), isCleartextPermitted(_)).Times(0); - EXPECT_CALL(helper_handle_->mock_helper(), validateCertificateChain(_, _)); - EXPECT_CALL(helper_handle_->mock_helper(), cleanupAfterCertificateValidation()); + if (!getUseWorkerThread()) { + EXPECT_CALL(helper_handle_->mock_helper(), validateCertificateChain(_, _)); + EXPECT_CALL(helper_handle_->mock_helper(), cleanupAfterCertificateValidation()); + } } Buffer::OwnedImpl request_data = Buffer::OwnedImpl("request body"); default_request_headers_.addCopy(AutonomousStream::EXPECT_REQUEST_SIZE_BYTES, @@ -417,6 +428,7 @@ TEST_P(ClientIntegrationTest, HandleNetworkChangeEventsAndroid) { } TEST_P(ClientIntegrationTest, Http3IdleConnectionClosedUponNetworkChangeEventsAndroid) { + expect_data_streams_ = false; builder_.enableQuicConnectionMigration(true); builder_.addRuntimeGuard("decouple_explicit_drain_pools_and_dns_refresh", true); builder_.addRuntimeGuard("mobile_use_network_observer_registry", true); @@ -502,6 +514,7 @@ TEST_P(ClientIntegrationTest, Http3ConnectionMigrationUponNetworkChangeEventsAnd builder_.enableQuicConnectionMigration(true); builder_.addRuntimeGuard("decouple_explicit_drain_pools_and_dns_refresh", true); builder_.addRuntimeGuard("mobile_use_network_observer_registry", true); + builder_.setMigrateIdleQuicConnection(true); initialize(); if (getCodecType() != Http::CodecType::HTTP3 || version_ != Network::Address::IpVersion::v4) { @@ -960,8 +973,10 @@ TEST_P(ClientIntegrationTest, ClearTextNotPermitted) { TEST_P(ClientIntegrationTest, BasicHttps) { EXPECT_CALL(helper_handle_->mock_helper(), isCleartextPermitted(_)).Times(0); - EXPECT_CALL(helper_handle_->mock_helper(), validateCertificateChain(_, _)); - EXPECT_CALL(helper_handle_->mock_helper(), cleanupAfterCertificateValidation()); + if (!getUseWorkerThread()) { + EXPECT_CALL(helper_handle_->mock_helper(), validateCertificateChain(_, _)); + EXPECT_CALL(helper_handle_->mock_helper(), cleanupAfterCertificateValidation()); + } builder_.enablePlatformCertificatesValidation(true); @@ -1109,6 +1124,7 @@ TEST_P(ClientIntegrationTest, InvalidDomainReresolveWithNoAddresses) { } TEST_P(ClientIntegrationTest, ReresolveAndDrain) { + expect_data_streams_ = false; // 0-RTT request might skip some fields in final stream intel. builder_.enableDrainPostDnsRefresh(true); // 0-RTT requests will not populate some of the final stream intel fields, so skip the validation. expect_data_streams_ = false; @@ -1684,7 +1700,7 @@ TEST_P(ClientIntegrationTest, ResetWithBidiTrafficExplicitData) { } TEST_P(ClientIntegrationTest, Proxying) { - if (getCodecType() != Http::CodecType::HTTP1) { + if (getCodecType() != Http::CodecType::HTTP1 || getUseWorkerThread()) { return; } initialize(); @@ -1734,6 +1750,9 @@ TEST_P(ClientIntegrationTest, TestStats) { #if defined(__APPLE__) TEST_P(ClientIntegrationTest, TestProxyResolutionApi) { + if (getUseWorkerThread()) { + return; + } builder_.respectSystemProxySettings(true); initialize(); ASSERT_TRUE(Envoy::Api::External::retrieveApi("envoy_proxy_resolver") != nullptr); @@ -1744,6 +1763,9 @@ TEST_P(ClientIntegrationTest, TestProxyResolutionApi) { // doesn't crash. It doesn't really test the actual network change event. TEST_P(ClientIntegrationTest, OnNetworkChanged) { builder_.addRuntimeGuard("dns_cache_set_ip_version_to_remove", true); + if (getCodecType() == Http::CodecType::HTTP3) { + builder_.setDisableDnsRefreshOnNetworkChange(true); + } initialize(); internalEngine()->onDefaultNetworkChanged(1); basicTest(); @@ -1756,6 +1778,9 @@ TEST_P(ClientIntegrationTest, OnNetworkChanged) { // doesn't crash. It doesn't really test the actual network change event. TEST_P(ClientIntegrationTest, OnNetworkChangeEvent) { builder_.addRuntimeGuard("dns_cache_set_ip_version_to_remove", true); + if (getCodecType() == Http::CodecType::HTTP3) { + builder_.setDisableDnsRefreshOnNetworkChange(true); + } initialize(); int network = 0; network |= static_cast(NetworkType::WWAN); @@ -1767,15 +1792,36 @@ TEST_P(ClientIntegrationTest, OnNetworkChangeEvent) { } } +// A thread-safe implementation which can fake SOCKET_ERROR_NOBUFS send errors. class MockSendOsSysCalls : public Api::OsSysCallsImpl { public: + MockSendOsSysCalls() { + ON_CALL(*this, send(_, _, _, _)) + .WillByDefault([this](os_fd_t socket, void* buffer, size_t length, int flags) { + int target = target_fd_.load(); + if (fail_send_.load() && (target == -1 || socket == target)) { + bool expected = true; + if (fail_send_.compare_exchange_strong(expected, false)) { + return Api::SysCallSizeResult{-1, SOCKET_ERROR_NOBUFS}; + } + } + return Api::OsSysCallsImpl::send(socket, buffer, length, flags); + }); + } + MOCK_METHOD(Api::SysCallSizeResult, send, (os_fd_t socket, void* buffer, size_t length, int flags), (override)); + + std::atomic fail_send_{false}; + std::atomic target_fd_{-1}; }; // Tests that a transient write error due to no space available on the socket // does not cause the stream to error out when using HTTP/3. TEST_P(ClientIntegrationTest, NoSpaceAvailableWriteErrorSwallowed) { + testing::NiceMock sys_calls; + TestThreadsafeSingletonInjector injector(&sys_calls); + initialize(); if (upstreamProtocol() != Http::CodecType::HTTP3) { return; @@ -1799,13 +1845,8 @@ TEST_P(ClientIntegrationTest, NoSpaceAvailableWriteErrorSwallowed) { // Wait for the upstream connection to be created and introduce a transient SOCKET_ERROR_NOBUFS // write error. ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_cx_http3_total", 1)); - MockSendOsSysCalls sys_calls; - TestThreadsafeSingletonInjector injector(&sys_calls); - EXPECT_CALL(sys_calls, send(fd, _, _, _)) - .WillOnce(Return(Api::SysCallSizeResult{-1, SOCKET_ERROR_NOBUFS})) - .WillRepeatedly(Invoke([&](os_fd_t socket, void* buffer, size_t length, int flags) { - return injector.latched().send(socket, buffer, length, flags); - })); + sys_calls.target_fd_.store(fd); + sys_calls.fail_send_.store(true); // Complete the request. stream_->close(Http::Utility::createRequestTrailerMapPtr()); @@ -1815,6 +1856,9 @@ TEST_P(ClientIntegrationTest, NoSpaceAvailableWriteErrorSwallowed) { ASSERT_EQ(cc_.on_error_calls_, 0); ASSERT_EQ(cc_.on_complete_calls_, 1); EXPECT_EQ(cc_.status_, "200"); + + // Join background threads before local injector and mocks are destroyed to avoid TSAN data races. + TearDown(); } TEST_P(ClientIntegrationTest, HttpsWithEarlyData) { @@ -1855,11 +1899,12 @@ TEST_P(ClientIntegrationTest, HttpsWithEarlyData) { // Wait for session ticket to be received (QUIC sends it after handshake) timeSystem().realSleepDoNotUseWithoutScrutiny(std::chrono::milliseconds(500)); + int old_upstream_cx_destroy = getCounterValue("cluster.base.upstream_cx_destroy"); // Close connection to force reconnect and use session ticket ASSERT_TRUE(upstream_connection_->close()); ASSERT_TRUE(upstream_connection_->waitForDisconnect()); upstream_connection_.reset(); - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_cx_destroy", 1)); + ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_cx_destroy", old_upstream_cx_destroy + 1)); // Reset terminal callback for the second request. ConditionalInitializer terminal_callback; diff --git a/mobile/test/common/integration/rtds_integration_test.cc b/mobile/test/common/integration/rtds_integration_test.cc index 1c9063618ea89..20f5e64489ca0 100644 --- a/mobile/test/common/integration/rtds_integration_test.cc +++ b/mobile/test/common/integration/rtds_integration_test.cc @@ -5,6 +5,8 @@ #include "test/common/integration/xds_integration_test.h" #include "test/test_common/environment.h" +#include "extension_registry.h" +#include "source/common/tls/server_context_impl.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" @@ -34,12 +36,16 @@ class RtdsIntegrationTest : public XdsIntegrationTest { xds_builder.addRuntimeDiscoveryService("some_rtds_resource", /*timeout_in_seconds=*/1) .setSslRootCerts(getUpstreamCert()); builder_.setXds(std::move(xds_builder)); + builder_.setLogLevel(Logger::Logger::trace); + builder_.enforceTrustChainVerification(false); XdsIntegrationTest::createEnvoy(); } - void SetUp() override { initialize(); } + void SetUp() override {} void runReloadTest() { + initialize(); + stream_ = createNewStream(createDefaultStreamCallbacks()); // Send a request on the data plane. stream_->sendHeaders(std::make_unique(default_request_headers_), @@ -113,5 +119,10 @@ TEST_P(RtdsIntegrationTest, RtdsReloadWithoutDfpMixedScheme) { runReloadTest(); } +TEST_P(RtdsIntegrationTest, RtdsReloadWithWorkerThread) { + builder_.enableWorkerThread(true); + runReloadTest(); +} + } // namespace } // namespace Envoy diff --git a/mobile/test/python/fetch_test.py b/mobile/test/python/fetch_test.py index 0a6209c692448..17010c98252fc 100644 --- a/mobile/test/python/fetch_test.py +++ b/mobile/test/python/fetch_test.py @@ -39,6 +39,7 @@ def _build_engine(self): .set_log_level(LogLevel.trace) .add_runtime_guard("dns_cache_set_ip_version_to_remove", True) .set_on_engine_running(lambda: engine_running.set()) + .enable_worker_thread(True) .build() ) self.assertTrue(engine_running.wait(timeout=30), "Engine did not start within timeout") From 0f60cd0a9c7836cacbba0d346cc9caa9c8222d66 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 4 May 2026 10:56:44 -0400 Subject: [PATCH 207/750] cache_v2: add passthrough test for upstream reset handling with null headers (#44834) Commit Message: cache_v2: add passthrough test for upstream reset handling with null headers Additional Description: Adding test coverage for https://github.com/envoyproxy/envoy/pull/44636 Risk Level: Low/None (only test changes) Testing: unit test Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A --------- Signed-off-by: Ryan Daniels --- .../http/cache_v2/cache_sessions_test.cc | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/extensions/filters/http/cache_v2/cache_sessions_test.cc b/test/extensions/filters/http/cache_v2/cache_sessions_test.cc index ea80ad1d85796..b9b89e73dd6c0 100644 --- a/test/extensions/filters/http/cache_v2/cache_sessions_test.cc +++ b/test/extensions/filters/http/cache_v2/cache_sessions_test.cc @@ -841,6 +841,48 @@ TEST_F(CacheSessionsTest, RangeRequestWhenLengthIsUnknownReturnsNotSatisfiable) Mock::VerifyAndClearExpectations(&headers_callback1); } +TEST_F(CacheSessionsTest, PassthroughWithUpstreamResetCallsGetHeadersCallbackWithNullPointer) { + Mock::VerifyAndClearExpectations(mock_cacheable_response_checker_.get()); + EXPECT_CALL(*mock_cacheable_response_checker_, isCacheableResponse) + .Times(testing::AnyNumber()) + .WillOnce(testing::Return(false)); + EXPECT_CALL(*mock_http_cache_, lookup(LookupHasPath("/a"), _)); + EXPECT_CALL(*mock_http_cache_, touch(KeyHasPath("/a"), _)).Times(2); + + ActiveLookupResultPtr result1; + cache_sessions_->lookup(testLookupRequest("/a"), + [&result1](ActiveLookupResultPtr r) { result1 = std::move(r); }); + pumpDispatcher(); + consumeCallback(captured_lookup_callbacks_[0])(LookupResult{}); + pumpDispatcher(); + // Upstream request should have been sent. + ASSERT_THAT(fake_upstreams_.size(), Eq(1)); + ASSERT_THAT(fake_upstream_get_headers_callbacks_.size(), Eq(1)); + consumeCallback(fake_upstream_get_headers_callbacks_[0])(uncacheableResponseHeaders(), + EndStream::End); + pumpDispatcher(); + ASSERT_THAT(result1, NotNull()); + EXPECT_THAT(result1->status_, Eq(CacheEntryStatus::Uncacheable)); + + ActiveLookupResultPtr result2; + cache_sessions_->lookup(testLookupRequest("/a"), + [&result2](ActiveLookupResultPtr r) { result2 = std::move(r); }); + pumpDispatcher(); + ASSERT_THAT(result2, NotNull()); + EXPECT_THAT(result2->status_, Eq(CacheEntryStatus::Uncacheable)); + ASSERT_THAT(fake_upstreams_.size(), Eq(2)); + ASSERT_THAT(fake_upstream_get_headers_callbacks_.size(), Eq(2)); + EXPECT_THAT(fake_upstream_get_headers_callbacks_[1], IsNull()); + Http::ResponseHeaderMapPtr headers2; + result2->http_source_->getHeaders( + [&headers2](Http::ResponseHeaderMapPtr h, EndStream) { headers2 = std::move(h); }); + ASSERT_THAT(fake_upstream_get_headers_callbacks_[1], NotNull()); + // Simulate an upstream reset by calling the get headers callback with a nullptr and reset end + // stream. + consumeCallback(fake_upstream_get_headers_callbacks_[1])(nullptr, EndStream::Reset); + EXPECT_THAT(headers2, IsNull()); +} + // TODO: UpdateHeadersSkipSpecificHeaders // TODO: Vary From 488faa202b67cc241ae41cc51ffa6c475613dea2 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Mon, 4 May 2026 14:09:40 -0400 Subject: [PATCH 208/750] ci: add opt-in Slack notification for CI job failures (#44350) Add a reusable mechanism for CI jobs to send Slack notifications on failure. Jobs opt in by specifying a `slack-channel` in their matrix entry (e.g., `slack-channel: '#envoy-ci'`). The notification is sent via the Slack API using the existing SLACK_BOT_TOKEN secret. --------- Signed-off-by: Jonh Wendell --- .github/workflows/_check_build.yml | 5 +++++ .github/workflows/_run.yml | 24 +++++++++++++++++++++++- .github/workflows/envoy-checks.yml | 2 ++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/_check_build.yml b/.github/workflows/_check_build.yml index ab1076cd161de..af654964d7d5f 100644 --- a/.github/workflows/_check_build.yml +++ b/.github/workflows/_check_build.yml @@ -5,6 +5,8 @@ permissions: on: workflow_call: + secrets: + slack-bot-token: inputs: request: type: string @@ -20,6 +22,8 @@ concurrency: jobs: build: + secrets: + slack-bot-token: ${{ secrets.slack-bot-token }} permissions: actions: read contents: read @@ -39,6 +43,7 @@ jobs: rbe: true request: ${{ inputs.request }} skip: ${{ matrix.skip != false && true || false }} + slack-channel: ${{ matrix.slack-channel || '' }} target: ${{ matrix.target }} timeout-minutes: 180 trusted: ${{ inputs.trusted }} diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index fd9ac09eaa628..a0f01c72f9c54 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -10,6 +10,7 @@ on: app-key: gpg-key: gpg-key-password: + slack-bot-token: ssh-key: ssh-key-extra: inputs: @@ -138,6 +139,10 @@ on: skip: type: boolean default: false + slack-channel: + type: string + default: '' + description: Slack channel for failure notifications (opt-in, e.g. '#envoy-ci'). Leave empty to disable. source: type: string summary-post: @@ -387,7 +392,7 @@ jobs: BAZELRC_CONTENT: ${{ vars.ENVOY_CI_BAZELRC }} # NOTE: This is where untrusted code can be run!!! - # It MUST be the last step in the workflow + # Only post-failure notification steps (which don't use any repo code) should follow this step. - uses: envoyproxy/toolshed/actions/github/run@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: @@ -443,3 +448,20 @@ jobs: ENVOY_COMMIT: ${{ fromJSON(inputs.request).request.sha }} ENVOY_REPO: ${{ github.repository }} ENVOY_PUBLISH_DRY_RUN: ${{ (fromJSON(inputs.request).request.version.dev || ! inputs.trusted) && 1 || '' }} + + - name: Notify Slack on failure + if: ${{ failure() && inputs.slack-channel != '' }} + uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 + env: + JOB_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + method: chat.postMessage + token: ${{ secrets.slack-bot-token }} + payload: | + channel: "${{ inputs.slack-channel }}" + text: "CI job failed: ${{ inputs.target }}" + blocks: + - type: section + text: + type: mrkdwn + text: ":x: *CI job failed:* `${{ inputs.target }}`\n<${{ env.JOB_URL }}|View workflow run>" diff --git a/.github/workflows/envoy-checks.yml b/.github/workflows/envoy-checks.yml index 8ba40a47499bd..78277cccd1185 100644 --- a/.github/workflows/envoy-checks.yml +++ b/.github/workflows/envoy-checks.yml @@ -46,6 +46,8 @@ jobs: # head-sha: ${{ github.sha }} build: + secrets: + slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }} permissions: actions: read contents: read From af83ce2f87b9d0d0611fb077dff302aab1daf6c4 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Mon, 4 May 2026 14:34:30 -0400 Subject: [PATCH 209/750] ci: enable Slack notification for OpenSSL failures (#44838) Signed-off-by: Jonh Wendell --- .github/workflows/_check_build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/_check_build.yml b/.github/workflows/_check_build.yml index af654964d7d5f..a5ab7e25b3951 100644 --- a/.github/workflows/_check_build.yml +++ b/.github/workflows/_check_build.yml @@ -61,3 +61,4 @@ jobs: - target: openssl name: OpenSSL skip: ${{ ! fromJSON(inputs.request).run.check-build-openssl }} + slack-channel: '#envoy-openssl' From 7743c62ae7675f9f68f185f38506c6934236677a Mon Sep 17 00:00:00 2001 From: Yilin Guo Date: Mon, 4 May 2026 18:01:49 -0700 Subject: [PATCH 210/750] mcp_transcoder: add req/res body size limit in the bridge filter. (#44748) Commit Message: mcp: add req/res body size limit in the bridge filter. Additional Description: This PR introduces `max_request_body_size` and `max_response_body_size` configuration fields to the `McpJsonRestBridge` filter to prevent unbounded buffering. By default, both are set to 64KB (65536 bytes). If the request or response body exceeds the configured limit, the filter now rejects it and returns a JSON-RPC error response with code `-32000` and message "Request/Response body too large". Risk Level: Low. This actually reduces the risk of memory exhaustion by introducing sensible defaults to bound payload buffering. Testing: Added unit tests (`RequestBodyExceedsLimitReturnsError`, `ResponseBodyExceedsLimitReturnsError`) and integration tests (`ToolsCallRequestBodyExceedsLimit`, `ToolsCallResponseBodyExceedsLimit`) to verify the new size limits. Docs Changes: Inline documentation added to the `mcp_json_rest_bridge.proto` configuration fields. Release Notes: mcp_json_rest_bridge: Added `max_request_body_size` and `max_response_body_size` config limits to prevent unbounded buffering (defaults to 64KB). Platform Specific Features: N/A --------- Signed-off-by: Yilin Guo --- .../v3/mcp_json_rest_bridge.proto | 20 +++ .../mcp_json_rest_bridge_filter.cc | 42 ++++++- .../mcp_json_rest_bridge_filter.h | 5 + .../mcp_json_rest_bridge_filter_test.cc | 104 ++++++++++++++- .../mcp_json_rest_bridge_integration_test.cc | 118 ++++++++++++++++++ 5 files changed, 284 insertions(+), 5 deletions(-) diff --git a/api/envoy/extensions/filters/http/mcp_json_rest_bridge/v3/mcp_json_rest_bridge.proto b/api/envoy/extensions/filters/http/mcp_json_rest_bridge/v3/mcp_json_rest_bridge.proto index 11df0c8b690af..a70e4cb4b4f8d 100644 --- a/api/envoy/extensions/filters/http/mcp_json_rest_bridge/v3/mcp_json_rest_bridge.proto +++ b/api/envoy/extensions/filters/http/mcp_json_rest_bridge/v3/mcp_json_rest_bridge.proto @@ -96,6 +96,26 @@ message McpJsonRestBridge { // Configuration for the MCP tools. ServerToolConfig tool_config = 2; + + // Maximum size of the request body to buffer for transcoding and validation. + // If the request body exceeds this size, the request is rejected with ``413 Payload Too Large``. + // This limit applies to prevent unbounded buffering. + // + // It defaults to 64KB (65536 bytes) as the MCP calls (tools, resources, or prompts) + // only pass small arguments or identifiers. + // + // Setting it to 0 would disable the limit. It is not recommended to do so in production. + google.protobuf.UInt32Value max_request_body_size = 3; + + // Maximum size of the response body to buffer for transcoding. + // If the response body exceeds this size, the response is rejected with an appropriate error. + // This limit applies to prevent unbounded buffering. + // + // It defaults to 1MB (1048576 bytes) to prevent transcoding failures on large payloads like + // file reads, while aligning with Envoy's standard default connection buffer limit. + // + // Setting it to 0 would disable the limit. It is not recommended to do so in production. + google.protobuf.UInt32Value max_response_body_size = 4; } // Configuration for the server metadata. diff --git a/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.cc b/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.cc index 770b1f28ea40a..4d7c03d3f5139 100644 --- a/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.cc +++ b/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.cc @@ -30,6 +30,9 @@ namespace { using ::nlohmann::json; namespace McpConstants = Envoy::Extensions::Filters::Common::Mcp::McpConstants; +constexpr uint32_t DEFAULT_MAX_REQUEST_BODY_SIZE = 1024 * 64; // 64KB +constexpr uint32_t DEFAULT_MAX_RESPONSE_BODY_SIZE = 1024 * 1024; // 1MB + bool isMcpProtocolVersionSupported(absl::string_view protocol_version) { static const absl::NoDestructor> supported_mcp_versions({ McpConstants::LATEST_SUPPORTED_MCP_VERSION, @@ -133,7 +136,11 @@ McpJsonRestBridgeFilterConfig::McpJsonRestBridgeFilterConfig( proto_config) : proto_config_(proto_config), fallback_protocol_version_(PROTOBUF_GET_WRAPPED_OR_DEFAULT( proto_config_.server_info(), fallback_protocol_version, - std::string(McpConstants::FALLBACK_PROTOCOL_VERSION))) { + std::string(McpConstants::FALLBACK_PROTOCOL_VERSION))), + max_request_body_size_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(proto_config_, max_request_body_size, + DEFAULT_MAX_REQUEST_BODY_SIZE)), + max_response_body_size_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(proto_config_, max_response_body_size, + DEFAULT_MAX_RESPONSE_BODY_SIZE)) { for (const auto& tool : proto_config.tool_config().tools()) { tool_to_http_rule_[tool.name()] = tool.http_rule(); } @@ -197,7 +204,16 @@ Http::FilterDataStatus McpJsonRestBridgeFilter::decodeData(Buffer::Instance& dat return Http::FilterDataStatus::Continue; } - // TODO(guoyilin42): Add hard limit for the buffer size and flow control if possible. + const uint32_t max_request_body_size = config_->maxRequestBodySize(); + if (max_request_body_size > 0 && + (request_body_.length() + data.length()) > max_request_body_size) { + ENVOY_STREAM_LOG(error, "Request body exceeds limit. Size: {}, Limit: {}", *decoder_callbacks_, + request_body_.length() + data.length(), max_request_body_size); + sendErrorResponse(Http::Code::PayloadTooLarge, "mcp_json_rest_bridge_filter_request_too_large", + generateErrorJsonResponse(-32000, "Request body too large").dump()); + return Http::FilterDataStatus::StopIterationNoBuffer; + } + request_body_.move(data); if (!end_stream) { @@ -225,6 +241,7 @@ Http::FilterDataStatus McpJsonRestBridgeFilter::decodeData(Buffer::Instance& dat if (mcp_operation_ == McpOperation::Initialization || mcp_operation_ == McpOperation::InitializationAck || mcp_operation_ == McpOperation::OperationFailed) { + // sendLocalReply was called in handleMcpMethod for these operations. return Http::FilterDataStatus::StopIterationNoBuffer; } @@ -262,6 +279,27 @@ Http::FilterDataStatus McpJsonRestBridgeFilter::encodeData(Buffer::Instance& dat mcp_operation_ == McpOperation::InitializationAck) { return Http::FilterDataStatus::Continue; } + + const uint32_t max_response_body_size = config_->maxResponseBodySize(); + if (max_response_body_size > 0 && + (response_body_.length() + data.length()) > max_response_body_size) { + ENVOY_STREAM_LOG(error, "Response body exceeds limit. Size: {}, Limit: {}", *encoder_callbacks_, + response_body_.length() + data.length(), max_response_body_size); + json error_json = { + {McpConstants::JSONRPC_FIELD, McpConstants::JSONRPC_VERSION}, + // If the ID is missing in the request, the ID in the response should be null. + {McpConstants::ID_FIELD, session_id_.has_value() ? *session_id_ : json(nullptr)}, + {McpConstants::ERROR_FIELD, generateErrorJsonResponse(-32000, "Response body too large")}}; + encoder_callbacks_->sendLocalReply( + Http::Code::InternalServerError, error_json.dump(), + [](Http::ResponseHeaderMap& headers) { + headers.setContentType(Http::Headers::get().ContentTypeValues.Json); + }, + Grpc::Status::WellKnownGrpcStatus::Internal, + "mcp_json_rest_bridge_filter_response_too_large"); + return Http::FilterDataStatus::StopIterationNoBuffer; + } + response_body_.move(data); if (!end_stream) { diff --git a/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.h b/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.h index 5b7604a75de0b..3a7efeb8bc1f1 100644 --- a/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.h +++ b/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.h @@ -40,12 +40,17 @@ class McpJsonRestBridgeFilterConfig : public Logger::Loggable tool_to_http_rule_; envoy::extensions::filters::http::mcp_json_rest_bridge::v3::McpJsonRestBridge proto_config_; std::string fallback_protocol_version_; + uint32_t max_request_body_size_; + uint32_t max_response_body_size_; }; using McpJsonRestBridgeFilterConfigSharedPtr = std::shared_ptr; diff --git a/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter_test.cc b/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter_test.cc index a11aa46aadae6..57a4e37ed0028 100644 --- a/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter_test.cc +++ b/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter_test.cc @@ -687,10 +687,10 @@ TEST_F(McpJsonRestBridgeFilterTest, BackendErrorReturnsToolCallError) { Http::FilterHeadersStatus::StopIteration); Buffer::OwnedImpl request_body( - R"json({"jsonrpc":"2.0","id":123,"method":"tools/call","params":{"name":"create_api_key","arguments":{"parent":"projects/cloudesf-codelab","key":{"displayName":"display-key"}}}})json"); + R"json({"jsonrpc":"2.0","id":123,"method":"tools/call","params":{"name":"create_api_key","arguments":{"parent":"projects/test-codelab","key":{"displayName":"display-key"}}}})json"); EXPECT_EQ(filter_->decodeData(request_body, /*end_stream=*/true), Http::FilterDataStatus::Continue); - EXPECT_THAT(request_headers_.getPathValue(), StrEq("/v1/projects/cloudesf-codelab/apiKeys")); + EXPECT_THAT(request_headers_.getPathValue(), StrEq("/v1/projects/test-codelab/apiKeys")); EXPECT_THAT(request_headers_.getMethodValue(), StrEq("POST")); EXPECT_EQ(nlohmann::json::parse(request_body.toString()), nlohmann::json::parse(R"json({"displayName":"display-key"})json")); @@ -717,7 +717,7 @@ TEST_F(McpJsonRestBridgeFilterTest, RejectInvalidUtf8BackendResponse) { EXPECT_EQ(filter_->decodeHeaders(request_headers_, /*end_stream=*/false), Http::FilterHeadersStatus::StopIteration); Buffer::OwnedImpl request_body( - R"json({"jsonrpc":"2.0","id":123,"method":"tools/call","params":{"name":"create_api_key","arguments":{"parent":"projects/cloudesf-codelab","key":{"displayName":"display-key"}}}})json"); + R"json({"jsonrpc":"2.0","id":123,"method":"tools/call","params":{"name":"create_api_key","arguments":{"parent":"projects/test-codelab","key":{"displayName":"display-key"}}}})json"); EXPECT_EQ(filter_->decodeData(request_body, /*end_stream=*/true), Http::FilterDataStatus::Continue); response_headers_ = { @@ -771,6 +771,38 @@ TEST_F(McpJsonRestBridgeFilterTest, ToolListRewritePathForRequestAndTranslateRes R"json({"jsonrpc":"2.0","id":12,"result":{"tools":[{"name":"google.api.CreateApiKey"}]}})json")); } +TEST_F(McpJsonRestBridgeFilterTest, EncodeDataReturnsStopIterationNoBufferWhenNotEndOfStream) { + request_headers_ = {{":method", "POST"}, {":path", "/mcp"}, {"content-type", "application/json"}}; + Buffer::OwnedImpl request_body( + R"json({"jsonrpc":"2.0","id":123,"method":"tools/call","params":{"name":"create_api_key","arguments":{"parent":"projects/test-codelab","key":{"displayName":"display-key"}}}})json"); + request_headers_.setContentLength(request_body.toString().size()); + + EXPECT_CALL(decoder_callbacks_.downstream_callbacks_, clearRouteCache()); + EXPECT_EQ(filter_->decodeHeaders(request_headers_, /*end_stream=*/false), + Http::FilterHeadersStatus::StopIteration); + EXPECT_EQ(filter_->decodeData(request_body, /*end_stream=*/true), + Http::FilterDataStatus::Continue); + + response_headers_ = { + {":status", "200"}, {"content-type", "text/plain"}, {"content-length", "123456"}}; + EXPECT_EQ(filter_->encodeHeaders(response_headers_, /*end_stream=*/false), + Http::FilterHeadersStatus::StopIteration); + Buffer::OwnedImpl first_chunk("part1"); + EXPECT_EQ(filter_->encodeData(first_chunk, /*end_stream=*/false), + Http::FilterDataStatus::StopIterationNoBuffer); + EXPECT_TRUE(first_chunk.toString().empty()); + Buffer::OwnedImpl second_chunk("part2"); + EXPECT_EQ(filter_->encodeData(second_chunk, /*end_stream=*/true), + Http::FilterDataStatus::Continue); + EXPECT_THAT(response_headers_.getContentTypeValue(), StrEq("application/json")); + EXPECT_THAT(response_headers_.getContentLengthValue(), + StrEq(std::to_string(second_chunk.length()))); + EXPECT_EQ( + nlohmann::json::parse(second_chunk.toString()), + nlohmann::json::parse( + R"json({"jsonrpc":"2.0","id":123,"result":{"content":[{"text":"part1part2","type":"text"}],"isError":false}})json")); +} + TEST_F(McpJsonRestBridgeFilterTest, ToolListWithNumericStringId) { request_headers_ = {{":method", "POST"}, {":path", "/mcp"}}; EXPECT_EQ(filter_->decodeHeaders(request_headers_, /*end_stream=*/false), @@ -1131,6 +1163,72 @@ TEST_F(McpJsonRestBridgeFilterTest, InitializeRequestIgnoreProtocolVersionHeader Http::FilterDataStatus::Continue); } +TEST_F(McpJsonRestBridgeFilterTest, RequestBodyExceedsLimitReturnsError) { + envoy::extensions::filters::http::mcp_json_rest_bridge::v3::McpJsonRestBridge proto_config = + ParseTextProtoOrDie(R"pb( + max_request_body_size { value: 10 } + )pb"); + config_ = std::make_shared(proto_config); + filter_ = std::make_unique(config_); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + filter_->setEncoderFilterCallbacks(encoder_callbacks_); + + request_headers_ = {{":method", "POST"}, {":path", "/mcp"}}; + + EXPECT_EQ(filter_->decodeHeaders(request_headers_, /*end_stream=*/false), + Http::FilterHeadersStatus::StopIteration); + EXPECT_CALL(decoder_callbacks_, + sendLocalReply(Eq(Http::Code::PayloadTooLarge), _, _, _, + StrEq("mcp_json_rest_bridge_filter_request_too_large"))); + Buffer::OwnedImpl body("12345678901"); // 11 bytes + EXPECT_EQ(filter_->decodeData(body, /*end_stream=*/true), + Http::FilterDataStatus::StopIterationNoBuffer); +} + +TEST_F(McpJsonRestBridgeFilterTest, ResponseBodyExceedsLimitReturnsError) { + envoy::extensions::filters::http::mcp_json_rest_bridge::v3::McpJsonRestBridge proto_config = + ParseTextProtoOrDie(R"pb( + max_response_body_size { value: 10 } + tool_config { + tools { + name: "create_api_key" + http_rule: { + post: "/v1/{parent=projects/*}/apiKeys" + body: "key" + } + } + } + )pb"); + config_ = std::make_shared(proto_config); + filter_ = std::make_unique(config_); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + filter_->setEncoderFilterCallbacks(encoder_callbacks_); + + request_headers_ = {{":method", "POST"}, {":path", "/mcp"}, {"content-type", "application/json"}}; + Buffer::OwnedImpl request_body( + R"json({"jsonrpc":"2.0","id":123,"method":"tools/call","params":{"name":"create_api_key","arguments":{"parent":"projects/test","key":{"displayName":"display-key"}}}})json"); + request_headers_.setContentLength(request_body.toString().size()); + + EXPECT_CALL(decoder_callbacks_.downstream_callbacks_, clearRouteCache()); + EXPECT_EQ(filter_->decodeHeaders(request_headers_, /*end_stream=*/false), + Http::FilterHeadersStatus::StopIteration); + EXPECT_EQ(filter_->decodeData(request_body, /*end_stream=*/true), + Http::FilterDataStatus::Continue); + + EXPECT_EQ(filter_->encodeHeaders(response_headers_, /*end_stream=*/false), + Http::FilterHeadersStatus::StopIteration); + EXPECT_CALL( + encoder_callbacks_, + sendLocalReply( + Eq(Http::Code::InternalServerError), + StrEq( + R"json({"error":{"code":-32000,"message":"Response body too large"},"id":123,"jsonrpc":"2.0"})json"), + _, _, StrEq("mcp_json_rest_bridge_filter_response_too_large"))); + Buffer::OwnedImpl body("12345678901"); // 11 bytes + EXPECT_EQ(filter_->encodeData(body, /*end_stream=*/true), + Http::FilterDataStatus::StopIterationNoBuffer); +} + class McpHttpMethodFilterTest : public testing::TestWithParam { public: void SetUp() override { diff --git a/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_integration_test.cc b/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_integration_test.cc index ac81d9ef0098f..f79be0c43454b 100644 --- a/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_integration_test.cc +++ b/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_integration_test.cc @@ -197,6 +197,124 @@ TEST_P(McpJsonRestBridgeIntegrationTest, ToolsCallTranscoding) { EXPECT_EQ(nlohmann::json::parse(response->body()), nlohmann::json::parse(expected_rpc_response)); } +TEST_P(McpJsonRestBridgeIntegrationTest, ToolsCallRequestBodyExceedsLimit) { + const std::string config = R"EOF( + name: envoy.filters.http.mcp_json_rest_bridge + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_json_rest_bridge.v3.McpJsonRestBridge + max_request_body_size: 10 + tool_config: + tools: + - name: "create_api_key" + http_rule: + post: "/v1/{parent=projects/*}/keys" + body: "key" + )EOF"; + + initializeFilter(config); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + const std::string request_body = R"({ + "jsonrpc": "2.0", + "id": 321, + "method": "tools/call", + "params": { + "name": "create_api_key", + "arguments": { + "parent": "projects/foo", + "key": { + "displayName": "bar" + } + } + } + })"; + + auto response = codec_client_->makeRequestWithBody( + Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/mcp"}, + {":scheme", "http"}, + {":authority", "host"}, + {"content-type", "application/json"}}, + request_body); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_THAT(response->headers().getStatusValue(), StrEq("413")); + EXPECT_THAT(response->headers().getContentTypeValue(), StrEq("application/json")); + EXPECT_THAT(response->headers().getContentLengthValue(), + StrEq(std::to_string(response->body().size()))); + EXPECT_EQ( + nlohmann::json::parse(response->body()), + nlohmann::json::parse( + R"json({"jsonrpc":"2.0","id":null,"error":{"code":-32000,"message":"Request body too large"}})json")); +} + +TEST_P(McpJsonRestBridgeIntegrationTest, ToolsCallResponseBodyExceedsLimit) { + const std::string config = R"EOF( + name: envoy.filters.http.mcp_json_rest_bridge + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_json_rest_bridge.v3.McpJsonRestBridge + max_response_body_size: 10 + tool_config: + tools: + - name: "create_api_key" + http_rule: + post: "/v1/{parent=projects/*}/keys" + body: "key" + )EOF"; + + initializeFilter(config); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + const std::string request_body = R"({ + "jsonrpc": "2.0", + "id": 321, + "method": "tools/call", + "params": { + "name": "create_api_key", + "arguments": { + "parent": "projects/foo", + "key": { + "displayName": "bar" + } + } + } + })"; + + auto response = codec_client_->makeRequestWithBody( + Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/mcp"}, + {":scheme", "http"}, + {":authority", "host"}, + {"content-type", "application/json"}}, + request_body); + + waitForNextUpstreamRequest(); + EXPECT_THAT(upstream_request_->headers().getMethodValue(), StrEq("POST")); + EXPECT_THAT(upstream_request_->headers().getPathValue(), StrEq("/v1/projects/foo/keys")); + + Http::TestResponseHeaderMapImpl response_headers; + response_headers.setStatus(200); + response_headers.setContentType(Http::Headers::get().ContentTypeValues.Json); + + upstream_request_->encodeHeaders(response_headers, false); + + Buffer::OwnedImpl response_data; + response_data.add("12345678901"); // 11 bytes, exceeds 10 + upstream_request_->encodeData(response_data, true); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_THAT(response->headers().getStatusValue(), StrEq("500")); + EXPECT_THAT(response->headers().getContentTypeValue(), StrEq("application/json")); + EXPECT_THAT(response->headers().getContentLengthValue(), + StrEq(std::to_string(response->body().size()))); + EXPECT_EQ( + nlohmann::json::parse(response->body()), + nlohmann::json::parse( + R"json({"jsonrpc":"2.0","id":321,"error":{"code":-32000,"message":"Response body too large"}})json")); +} + TEST_P(McpJsonRestBridgeIntegrationTest, ToolsListTranscoding) { const std::string config = R"EOF( name: envoy.filters.http.mcp_json_rest_bridge From 101c66708129f75110c5f61483f28ca954ae34e3 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Tue, 5 May 2026 05:52:53 +0200 Subject: [PATCH 211/750] dynamic_modules: fix TOCTOU UAF in filter scheduler commit() across modules (#44841) ## Description The HTTP, network, and listener filter schedulers expose a thread-safe `commit()` that the module may invoke from a foreign thread (e.g., a tokio worker / OS thread it owns). The pre-fix implementation read `decoder_callbacks_`, `read_callbacks_`, `callbacks_` from the foreign thread, then dereferenced `->dispatcher()` on the result. Those callback objects have lifetime independent of the filter's `shared_ptr` which is a worker-thread teardown of the FilterManager can free the callback object while the filter `shared_ptr` is still live, producing a UAF on `->dispatcher().post(...)`. This change publishes the worker dispatcher pointer through a `std::atomic cached_dispatcher_` member, set with release ordering at callback-init time and cleared with release ordering on filter teardown. `commit()` now reads only the `weak_ptr` and the atomic dispatcher pointer and it never touches the FilterManager-owned callback object from a foreign thread. The dispatcher object itself is owned by `Worker::dispatcher_` and lives for the worker-thread lifetime, so the cached pointer is stable for any subsequent post. --- **Commit Message:** dynamic_modules: fix TOCTOU UAF in filter scheduler commit() across modules **Additional Description:** Fixes TOCTOU UAF in filter scheduler commit() across modules **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** Added **Release Notes:** N/A Signed-off-by: Rohit Agrawal --- .../filters/http/dynamic_modules/filter.cc | 2 + .../filters/http/dynamic_modules/filter.h | 32 ++++++-- .../listener/dynamic_modules/filter.cc | 4 + .../filters/listener/dynamic_modules/filter.h | 21 +++-- .../filters/network/dynamic_modules/filter.cc | 4 + .../filters/network/dynamic_modules/filter.h | 20 +++-- .../dynamic_modules/http/abi_impl_test.cc | 52 ++++++++++++- .../dynamic_modules/listener/abi_impl_test.cc | 76 ++++++++++++------- .../dynamic_modules/network/abi_impl_test.cc | 68 +++++++++++------ 9 files changed, 203 insertions(+), 76 deletions(-) diff --git a/source/extensions/filters/http/dynamic_modules/filter.cc b/source/extensions/filters/http/dynamic_modules/filter.cc index 5d466d1ac1302..693ba77bfe834 100644 --- a/source/extensions/filters/http/dynamic_modules/filter.cc +++ b/source/extensions/filters/http/dynamic_modules/filter.cc @@ -33,6 +33,8 @@ void DynamicModuleHttpFilter::onStreamComplete() { void DynamicModuleHttpFilter::onDestroy() { destroyed_ = true; + // Clear the cached dispatcher so any concurrent foreign-thread `commit()` short-circuits. + cached_dispatcher_.store(nullptr, std::memory_order_release); // Pair with the register in maybeRegisterDownstreamWatermarkCallbacks(); the underlying // removeDownstreamWatermarkCallbacks() asserts the callback was previously added. if (decoder_callbacks_ != nullptr && downstream_watermark_callbacks_registered_) { diff --git a/source/extensions/filters/http/dynamic_modules/filter.h b/source/extensions/filters/http/dynamic_modules/filter.h index 236c80a829c0d..ed0aa45856331 100644 --- a/source/extensions/filters/http/dynamic_modules/filter.h +++ b/source/extensions/filters/http/dynamic_modules/filter.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "source/common/tracing/null_span_impl.h" #include "source/extensions/dynamic_modules/dynamic_modules.h" #include "source/extensions/filters/http/common/pass_through_filter.h" @@ -42,6 +44,8 @@ class DynamicModuleHttpFilter : public Http::StreamFilter, FilterMetadataStatus decodeMetadata(MetadataMap&) override; void setDecoderFilterCallbacks(StreamDecoderFilterCallbacks& callbacks) override { decoder_callbacks_ = &callbacks; + // Publish the worker dispatcher for cross-thread `commit()`; see `dispatcher()`. + cached_dispatcher_.store(&callbacks.dispatcher(), std::memory_order_release); // Registration is deferred until the in-module filter exists: the factory wires callbacks // before initializeInModuleFilter(), and addDownstreamWatermarkCallbacks() synchronously // replays any pending onAboveWriteBufferHighWatermark() into the newly registered callback. @@ -62,6 +66,12 @@ class DynamicModuleHttpFilter : public Http::StreamFilter, bool isDestroyed() const { return destroyed_; } + /** + * Returns the worker dispatcher this filter is running on; safe to call from any thread. + * Returns nullptr until callbacks are wired and after `onDestroy()`. + */ + Event::Dispatcher* dispatcher() { return cached_dispatcher_.load(std::memory_order_acquire); } + // ---------- Http::DownstreamWatermarkCallbacks ---------- void onAboveWriteBufferHighWatermark() override; void onBelowWriteBufferLowWatermark() override; @@ -71,7 +81,8 @@ class DynamicModuleHttpFilter : public Http::StreamFilter, const absl::optional grpc_status, absl::string_view details); - // The callbacks for the filter. They are only valid until onDestroy() is called. + // The callbacks for the filter. Worker-thread only; foreign threads must use `dispatcher()`. + // They are only valid until onDestroy() is called. StreamDecoderFilterCallbacks* decoder_callbacks_ = nullptr; StreamEncoderFilterCallbacks* encoder_callbacks_ = nullptr; bool destroyed_ = false; @@ -270,6 +281,9 @@ class DynamicModuleHttpFilter : public Http::StreamFilter, // asserts that the callback was previously added. bool downstream_watermark_callbacks_registered_ = false; + // Worker dispatcher published at callback-init, cleared on destroy. Read via `dispatcher()`. + std::atomic cached_dispatcher_{nullptr}; + /** * This implementation of the AsyncClient::Callbacks is used to handle the response from the HTTP * callout from the parent HTTP filter. @@ -404,15 +418,19 @@ class DynamicModuleHttpFilterScheduler { explicit DynamicModuleHttpFilterScheduler(DynamicModuleHttpFilterWeakPtr filter) : filter_(std::move(filter)) {} + // Safe to call from any thread. Reads only the weak_ptr and the atomic dispatcher cache (see + // `DynamicModuleHttpFilter::dispatcher()`); it never dereferences `decoder_callbacks_` from a + // foreign thread. void commit(uint64_t event_id) { - // Lock the filter so the dispatcher reference obtained via its callbacks stays valid across - // `post`. - auto filter_shared = filter_.lock(); - if (!filter_shared || filter_shared->isDestroyed() || - filter_shared->decoder_callbacks_ == nullptr) { + DynamicModuleHttpFilterSharedPtr filter_shared = filter_.lock(); + if (!filter_shared) { + return; + } + Event::Dispatcher* dispatcher = filter_shared->dispatcher(); + if (dispatcher == nullptr) { return; } - filter_shared->decoder_callbacks_->dispatcher().post([filter = filter_, event_id]() { + dispatcher->post([filter = filter_, event_id]() { if (DynamicModuleHttpFilterSharedPtr fs = filter.lock()) { fs->onScheduled(event_id); } diff --git a/source/extensions/filters/listener/dynamic_modules/filter.cc b/source/extensions/filters/listener/dynamic_modules/filter.cc index 910481ce85210..33e0631eb2e83 100644 --- a/source/extensions/filters/listener/dynamic_modules/filter.cc +++ b/source/extensions/filters/listener/dynamic_modules/filter.cc @@ -43,11 +43,15 @@ void DynamicModuleListenerFilter::destroy() { config_->on_listener_filter_destroy_(in_module_filter_); in_module_filter_ = nullptr; } + // Clear the cached dispatcher so any concurrent foreign-thread `commit()` short-circuits. + cached_dispatcher_.store(nullptr, std::memory_order_release); destroyed_ = true; } Network::FilterStatus DynamicModuleListenerFilter::onAccept(Network::ListenerFilterCallbacks& cb) { callbacks_ = &cb; + // Publish the worker dispatcher for cross-thread `commit()`; see `dispatcher()`. + cached_dispatcher_.store(&cb.dispatcher(), std::memory_order_release); const std::string& worker_name = cb.dispatcher().name(); auto pos = worker_name.find_first_of('_'); diff --git a/source/extensions/filters/listener/dynamic_modules/filter.h b/source/extensions/filters/listener/dynamic_modules/filter.h index 68d0cead21ad9..06ead4af1b82f 100644 --- a/source/extensions/filters/listener/dynamic_modules/filter.h +++ b/source/extensions/filters/listener/dynamic_modules/filter.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "envoy/http/async_client.h" #include "envoy/network/filter.h" #include "envoy/network/listener_filter_buffer.h" @@ -66,12 +68,10 @@ class DynamicModuleListenerFilter void onScheduled(uint64_t event_id); /** - * Get the dispatcher for the worker thread this filter is running on. - * Returns nullptr if callbacks are not set. + * Returns the worker dispatcher this filter is running on; safe to call from any thread. + * Returns nullptr until callbacks are wired and after the filter is destroyed. */ - Event::Dispatcher* dispatcher() { - return callbacks_ != nullptr ? &callbacks_->dispatcher() : nullptr; - } + Event::Dispatcher* dispatcher() { return cached_dispatcher_.load(std::memory_order_acquire); } /** * Returns the worker index assigned to this filter. @@ -99,6 +99,7 @@ class DynamicModuleListenerFilter const DynamicModuleListenerFilterConfigSharedPtr config_; envoy_dynamic_module_type_listener_filter_module_ptr in_module_filter_ = nullptr; + // Worker-thread only; foreign threads must use `dispatcher()`. Network::ListenerFilterCallbacks* callbacks_ = nullptr; // Current buffer, only valid during onData callback. @@ -109,6 +110,9 @@ class DynamicModuleListenerFilter bool destroyed_ = false; + // Worker dispatcher published at callback-init, cleared on destroy. Read via `dispatcher()`. + std::atomic cached_dispatcher_{nullptr}; + uint32_t worker_index_; /** @@ -155,10 +159,11 @@ class DynamicModuleListenerFilterScheduler { explicit DynamicModuleListenerFilterScheduler(DynamicModuleListenerFilterWeakPtr filter) : filter_(std::move(filter)) {} + // Safe to call from any thread. Reads only the weak_ptr and the atomic dispatcher cache (see + // `DynamicModuleListenerFilter::dispatcher()`); it never dereferences `callbacks_` from a + // foreign thread. void commit(uint64_t event_id) { - // Lock the filter so the dispatcher reference obtained via its callbacks stays valid across - // `post`. - auto filter_shared = filter_.lock(); + DynamicModuleListenerFilterSharedPtr filter_shared = filter_.lock(); if (!filter_shared) { return; } diff --git a/source/extensions/filters/network/dynamic_modules/filter.cc b/source/extensions/filters/network/dynamic_modules/filter.cc index 3a415fb536983..2eddab9bd5941 100644 --- a/source/extensions/filters/network/dynamic_modules/filter.cc +++ b/source/extensions/filters/network/dynamic_modules/filter.cc @@ -58,12 +58,16 @@ void DynamicModuleNetworkFilter::destroy() { config_->on_network_filter_destroy_(in_module_filter_); in_module_filter_ = nullptr; } + // Clear the cached dispatcher so any concurrent foreign-thread `commit()` short-circuits. + cached_dispatcher_.store(nullptr, std::memory_order_release); destroyed_ = true; } void DynamicModuleNetworkFilter::initializeReadFilterCallbacks( Network::ReadFilterCallbacks& callbacks) { read_callbacks_ = &callbacks; + // Publish the worker dispatcher for cross-thread `commit()`; see `dispatcher()`. + cached_dispatcher_.store(&callbacks.connection().dispatcher(), std::memory_order_release); const std::string& worker_name = callbacks.connection().dispatcher().name(); auto pos = worker_name.find_first_of('_'); diff --git a/source/extensions/filters/network/dynamic_modules/filter.h b/source/extensions/filters/network/dynamic_modules/filter.h index c4251ab9125c2..e763b2cd0f3bc 100644 --- a/source/extensions/filters/network/dynamic_modules/filter.h +++ b/source/extensions/filters/network/dynamic_modules/filter.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -145,12 +146,10 @@ class DynamicModuleNetworkFilter : public Network::Filter, void onScheduled(uint64_t event_id); /** - * Get the dispatcher for the worker thread this filter is running on. - * Returns nullptr if callbacks are not set. + * Returns the worker dispatcher this filter is running on; safe to call from any thread. + * Returns nullptr until callbacks are wired and after the filter is destroyed. */ - Event::Dispatcher* dispatcher() { - return read_callbacks_ != nullptr ? &read_callbacks_->connection().dispatcher() : nullptr; - } + Event::Dispatcher* dispatcher() { return cached_dispatcher_.load(std::memory_order_acquire); } /** * Returns the worker index assigned to this filter. @@ -177,6 +176,7 @@ class DynamicModuleNetworkFilter : public Network::Filter, const DynamicModuleNetworkFilterConfigSharedPtr config_; envoy_dynamic_module_type_network_filter_module_ptr in_module_filter_ = nullptr; + // Worker-thread only; foreign threads must use `dispatcher()`. Network::ReadFilterCallbacks* read_callbacks_ = nullptr; Network::WriteFilterCallbacks* write_callbacks_ = nullptr; @@ -187,6 +187,9 @@ class DynamicModuleNetworkFilter : public Network::Filter, bool destroyed_ = false; + // Worker dispatcher published at callback-init, cleared on destroy. Read via `dispatcher()`. + std::atomic cached_dispatcher_{nullptr}; + uint32_t worker_index_; /** @@ -244,10 +247,11 @@ class DynamicModuleNetworkFilterScheduler { explicit DynamicModuleNetworkFilterScheduler(DynamicModuleNetworkFilterWeakPtr filter) : filter_(std::move(filter)) {} + // Safe to call from any thread. Reads only the weak_ptr and the atomic dispatcher cache (see + // `DynamicModuleNetworkFilter::dispatcher()`); it never dereferences `read_callbacks_` from a + // foreign thread. void commit(uint64_t event_id) { - // Lock the filter so the dispatcher reference obtained via its callbacks stays valid across - // `post`. - auto filter_shared = filter_.lock(); + DynamicModuleNetworkFilterSharedPtr filter_shared = filter_.lock(); if (!filter_shared) { return; } diff --git a/test/extensions/dynamic_modules/http/abi_impl_test.cc b/test/extensions/dynamic_modules/http/abi_impl_test.cc index 9dd29a93a9afa..e0e032b74b4ec 100644 --- a/test/extensions/dynamic_modules/http/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/http/abi_impl_test.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include "envoy/registry/registry.h" @@ -3523,7 +3524,8 @@ class DynamicModuleHttpFilterSchedulerTest : public testing::Test { std::shared_ptr filter_; }; -// Covers the happy path: `commit` posts to the dispatcher resolved via decoder callbacks. +// Covers the happy path: `commit` posts to the dispatcher cached when decoder callbacks were +// wired. TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitPostsToWorkerDispatcher) { filter_->setDecoderFilterCallbacks(decoder_callbacks_); @@ -3556,7 +3558,7 @@ TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitAfterFilte envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); } -// Covers the `filter_shared->isDestroyed()` early return. +// Covers the `dispatcher == nullptr` early return after `onDestroy()` clears the cache. TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitAfterOnDestroyIsNoOp) { filter_->setDecoderFilterCallbacks(decoder_callbacks_); @@ -3571,7 +3573,8 @@ TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitAfterOnDes envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); } -// Covers the `decoder_callbacks_ == nullptr` early return. +// Covers the `dispatcher == nullptr` early return when decoder callbacks have not been wired +// (so the cached dispatcher has not been published). TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitWithoutDecoderCallbacksIsNoOp) { auto* scheduler = envoy_dynamic_module_callback_http_filter_scheduler_new(filterPtr()); @@ -3614,6 +3617,49 @@ TEST_F(DynamicModuleHttpFilterSchedulerTest, envoy_dynamic_module_callback_http_filter_config_scheduler_delete(scheduler); } +// Verifies `commit()` from a foreign thread after `onDestroy()` is a safe no-op. Run under +// `--config=tsan` to confirm no data race on `decoder_callbacks_`. +TEST_F(DynamicModuleHttpFilterSchedulerTest, + HttpFilterSchedulerCommitFromForeignThreadAfterDestroyIsNoOp) { + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + + auto* scheduler = envoy_dynamic_module_callback_http_filter_scheduler_new(filterPtr()); + ASSERT_NE(nullptr, scheduler); + + filter_->onDestroy(); + + EXPECT_CALL(worker_thread_dispatcher_, post(testing::_)).Times(0); + + std::thread foreign( + [&]() { envoy_dynamic_module_callback_http_filter_scheduler_commit(scheduler, 999); }); + foreign.join(); + + envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); +} + +// Verifies `commit()` from a foreign thread before destroy posts via the cached dispatcher and +// that the lambda re-locks the weak_ptr correctly. +TEST_F(DynamicModuleHttpFilterSchedulerTest, + HttpFilterSchedulerCommitFromForeignThreadBeforeDestroyPosts) { + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + + auto* scheduler = envoy_dynamic_module_callback_http_filter_scheduler_new(filterPtr()); + ASSERT_NE(nullptr, scheduler); + + Event::PostCb captured_cb; + EXPECT_CALL(worker_thread_dispatcher_, post(testing::_)) + .WillOnce(testing::Invoke([&](Event::PostCb cb) { captured_cb = std::move(cb); })); + + std::thread foreign( + [&]() { envoy_dynamic_module_callback_http_filter_scheduler_commit(scheduler, 42); }); + foreign.join(); + + ASSERT_TRUE(captured_cb); + captured_cb(); + + envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); +} + } // namespace HttpFilters } // namespace DynamicModules } // namespace Extensions diff --git a/test/extensions/dynamic_modules/listener/abi_impl_test.cc b/test/extensions/dynamic_modules/listener/abi_impl_test.cc index bdf4ff843c944..a20d40a0a14a8 100644 --- a/test/extensions/dynamic_modules/listener/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/listener/abi_impl_test.cc @@ -1,4 +1,5 @@ #include +#include #include #include "source/common/http/message_impl.h" @@ -1954,10 +1955,6 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, MaxReadBytes) { // ============================================================================= TEST_F(DynamicModuleListenerFilterAbiCallbackTest, ListenerFilterSchedulerNewDelete) { - // Set up the dispatcher for the filter. - NiceMock worker_dispatcher; - EXPECT_CALL(callbacks_, dispatcher()).WillRepeatedly(testing::ReturnRef(worker_dispatcher)); - auto* scheduler = envoy_dynamic_module_callback_listener_filter_scheduler_new(filterPtr()); EXPECT_NE(nullptr, scheduler); @@ -1965,19 +1962,14 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, ListenerFilterSchedulerNewDel } TEST_F(DynamicModuleListenerFilterAbiCallbackTest, ListenerFilterSchedulerCommit) { - // Set up the dispatcher for the filter. - NiceMock worker_dispatcher; - EXPECT_CALL(callbacks_, dispatcher()).WillRepeatedly(testing::ReturnRef(worker_dispatcher)); - auto* scheduler = envoy_dynamic_module_callback_listener_filter_scheduler_new(filterPtr()); EXPECT_NE(nullptr, scheduler); - // Expect the callback to be posted. - EXPECT_CALL(worker_dispatcher, post(_)); + // The dispatcher cached during `onAccept` (in SetUp) is `worker_thread_dispatcher_`. + EXPECT_CALL(worker_thread_dispatcher_, post(_)); envoy_dynamic_module_callback_listener_filter_scheduler_commit(scheduler, 123); - // Clean up. envoy_dynamic_module_callback_listener_filter_scheduler_delete(scheduler); } @@ -2005,16 +1997,12 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, ListenerFilterConfigScheduler TEST_F(DynamicModuleListenerFilterAbiCallbackTest, ListenerFilterSchedulerCommitInvokesOnScheduled) { - // Set up the dispatcher for the filter. - NiceMock worker_dispatcher; - EXPECT_CALL(callbacks_, dispatcher()).WillRepeatedly(testing::ReturnRef(worker_dispatcher)); - auto* scheduler = envoy_dynamic_module_callback_listener_filter_scheduler_new(filterPtr()); EXPECT_NE(nullptr, scheduler); // Capture the posted callback and invoke it to verify onScheduled is called. Event::PostCb captured_cb; - EXPECT_CALL(worker_dispatcher, post(_)).WillOnce(testing::Invoke([&](Event::PostCb cb) { + EXPECT_CALL(worker_thread_dispatcher_, post(_)).WillOnce(testing::Invoke([&](Event::PostCb cb) { captured_cb = std::move(cb); })); @@ -2025,7 +2013,6 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, // Since the no_op module's on_scheduled is a no-op, we just verify it doesn't crash. captured_cb(); - // Clean up. envoy_dynamic_module_callback_listener_filter_scheduler_delete(scheduler); } @@ -2055,16 +2042,12 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, TEST_F(DynamicModuleListenerFilterAbiCallbackTest, ListenerFilterSchedulerCommitAfterFilterDestroyedDoesNotCrash) { - // Set up the dispatcher for the filter. - NiceMock worker_dispatcher; - EXPECT_CALL(callbacks_, dispatcher()).WillRepeatedly(testing::ReturnRef(worker_dispatcher)); - auto* scheduler = envoy_dynamic_module_callback_listener_filter_scheduler_new(filterPtr()); EXPECT_NE(nullptr, scheduler); // Capture the posted callback. Event::PostCb captured_cb; - EXPECT_CALL(worker_dispatcher, post(_)).WillOnce(testing::Invoke([&](Event::PostCb cb) { + EXPECT_CALL(worker_thread_dispatcher_, post(_)).WillOnce(testing::Invoke([&](Event::PostCb cb) { captured_cb = std::move(cb); })); @@ -2076,7 +2059,6 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, // Invoke the captured callback - should not crash because the scheduler holds a weak_ptr. captured_cb(); - // Clean up. envoy_dynamic_module_callback_listener_filter_scheduler_delete(scheduler); } @@ -2105,16 +2087,56 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, envoy_dynamic_module_callback_listener_filter_config_scheduler_delete(scheduler); } -// Covers the `dispatcher == nullptr` early return of the listener filter scheduler. +// Covers the `dispatcher == nullptr` early return of the listener filter scheduler when the +// filter has not yet received `onAccept` (so the cached dispatcher has not been published). +TEST_F(DynamicModuleListenerFilterAbiCallbackTest, + ListenerFilterSchedulerCommitBeforeOnAcceptIsNoOp) { + auto fresh_filter = std::make_shared(filter_config_); + auto* scheduler = envoy_dynamic_module_callback_listener_filter_scheduler_new( + static_cast(fresh_filter.get())); + ASSERT_NE(nullptr, scheduler); + + EXPECT_CALL(worker_thread_dispatcher_, post(_)).Times(0); + envoy_dynamic_module_callback_listener_filter_scheduler_commit(scheduler, 123); + + envoy_dynamic_module_callback_listener_filter_scheduler_delete(scheduler); +} + +// Verifies `commit()` from a foreign thread before destroy posts via the cached dispatcher. +// Run under `--config=tsan` to confirm no data race on `callbacks_`. TEST_F(DynamicModuleListenerFilterAbiCallbackTest, - ListenerFilterSchedulerCommitWithoutCallbacksIsNoOp) { + ListenerFilterSchedulerCommitFromForeignThreadBeforeDestroyPosts) { auto* scheduler = envoy_dynamic_module_callback_listener_filter_scheduler_new(filterPtr()); ASSERT_NE(nullptr, scheduler); - filter_->setCallbacksForTest(nullptr); + Event::PostCb captured_cb; + EXPECT_CALL(worker_thread_dispatcher_, post(_)).WillOnce(testing::Invoke([&](Event::PostCb cb) { + captured_cb = std::move(cb); + })); + + std::thread foreign( + [&]() { envoy_dynamic_module_callback_listener_filter_scheduler_commit(scheduler, 42); }); + foreign.join(); + + ASSERT_TRUE(captured_cb); + captured_cb(); + + envoy_dynamic_module_callback_listener_filter_scheduler_delete(scheduler); +} + +// Verifies `commit()` from a foreign thread after the filter is destroyed is a safe no-op. +TEST_F(DynamicModuleListenerFilterAbiCallbackTest, + ListenerFilterSchedulerCommitFromForeignThreadAfterDestroyIsNoOp) { + auto* scheduler = envoy_dynamic_module_callback_listener_filter_scheduler_new(filterPtr()); + ASSERT_NE(nullptr, scheduler); + + filter_.reset(); EXPECT_CALL(worker_thread_dispatcher_, post(_)).Times(0); - envoy_dynamic_module_callback_listener_filter_scheduler_commit(scheduler, 123); + + std::thread foreign( + [&]() { envoy_dynamic_module_callback_listener_filter_scheduler_commit(scheduler, 999); }); + foreign.join(); envoy_dynamic_module_callback_listener_filter_scheduler_delete(scheduler); } diff --git a/test/extensions/dynamic_modules/network/abi_impl_test.cc b/test/extensions/dynamic_modules/network/abi_impl_test.cc index c1c42bd8ec1d4..c1bc90fb8e6b3 100644 --- a/test/extensions/dynamic_modules/network/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/network/abi_impl_test.cc @@ -1,3 +1,4 @@ +#include #include #include "envoy/registry/registry.h" @@ -2254,10 +2255,6 @@ TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, StartUpstreamSecureTransportNu // ============================================================================= TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, NetworkFilterSchedulerNewDelete) { - // Set up the dispatcher for the filter via connection. - NiceMock worker_dispatcher; - EXPECT_CALL(connection_, dispatcher()).WillRepeatedly(testing::ReturnRef(worker_dispatcher)); - auto scheduler = envoy_dynamic_module_callback_network_filter_scheduler_new(filterPtr()); EXPECT_NE(nullptr, scheduler); @@ -2265,19 +2262,15 @@ TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, NetworkFilterSchedulerNewDelet } TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, NetworkFilterSchedulerCommit) { - // Set up the dispatcher for the filter via connection. - NiceMock worker_dispatcher; - EXPECT_CALL(connection_, dispatcher()).WillRepeatedly(testing::ReturnRef(worker_dispatcher)); - auto scheduler = envoy_dynamic_module_callback_network_filter_scheduler_new(filterPtr()); EXPECT_NE(nullptr, scheduler); - // Expect the callback to be posted. - EXPECT_CALL(worker_dispatcher, post(_)); + // The dispatcher cached during `initializeReadFilterCallbacks` (in SetUp) is + // `worker_thread_dispatcher_`. + EXPECT_CALL(worker_thread_dispatcher_, post(_)); envoy_dynamic_module_callback_network_filter_scheduler_commit(scheduler, 12345); - // Clean up. envoy_dynamic_module_callback_network_filter_scheduler_delete(scheduler); } @@ -2304,16 +2297,12 @@ TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, NetworkFilterConfigSchedulerCo } TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, NetworkFilterSchedulerCommitInvokesOnScheduled) { - // Set up the dispatcher for the filter via connection. - NiceMock worker_dispatcher; - EXPECT_CALL(connection_, dispatcher()).WillRepeatedly(testing::ReturnRef(worker_dispatcher)); - auto scheduler = envoy_dynamic_module_callback_network_filter_scheduler_new(filterPtr()); EXPECT_NE(nullptr, scheduler); // Capture the posted callback and invoke it to verify onScheduled is called. Event::PostCb captured_cb; - EXPECT_CALL(worker_dispatcher, post(_)).WillOnce(testing::Invoke([&](Event::PostCb cb) { + EXPECT_CALL(worker_thread_dispatcher_, post(_)).WillOnce(testing::Invoke([&](Event::PostCb cb) { captured_cb = std::move(cb); })); @@ -2324,7 +2313,6 @@ TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, NetworkFilterSchedulerCommitIn // Since the no_op module's on_scheduled is a no-op, we just verify it doesn't crash. captured_cb(); - // Clean up. envoy_dynamic_module_callback_network_filter_scheduler_delete(scheduler); } @@ -2354,16 +2342,12 @@ TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, NetworkFilterSchedulerCommitAfterFilterDestroyedDoesNotCrash) { - // Set up the dispatcher for the filter via connection. - NiceMock worker_dispatcher; - EXPECT_CALL(connection_, dispatcher()).WillRepeatedly(testing::ReturnRef(worker_dispatcher)); - auto scheduler = envoy_dynamic_module_callback_network_filter_scheduler_new(filterPtr()); EXPECT_NE(nullptr, scheduler); // Capture the posted callback. Event::PostCb captured_cb; - EXPECT_CALL(worker_dispatcher, post(_)).WillOnce(testing::Invoke([&](Event::PostCb cb) { + EXPECT_CALL(worker_thread_dispatcher_, post(_)).WillOnce(testing::Invoke([&](Event::PostCb cb) { captured_cb = std::move(cb); })); @@ -2375,7 +2359,45 @@ TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, // The callback should not crash even though the filter is destroyed. captured_cb(); - // Clean up. + envoy_dynamic_module_callback_network_filter_scheduler_delete(scheduler); +} + +// Verifies `commit()` from a foreign thread before destroy posts via the cached dispatcher. +// Run under `--config=tsan` to confirm no data race on `read_callbacks_`. +TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, + NetworkFilterSchedulerCommitFromForeignThreadBeforeDestroyPosts) { + auto* scheduler = envoy_dynamic_module_callback_network_filter_scheduler_new(filterPtr()); + ASSERT_NE(nullptr, scheduler); + + Event::PostCb captured_cb; + EXPECT_CALL(worker_thread_dispatcher_, post(_)).WillOnce(testing::Invoke([&](Event::PostCb cb) { + captured_cb = std::move(cb); + })); + + std::thread foreign( + [&]() { envoy_dynamic_module_callback_network_filter_scheduler_commit(scheduler, 42); }); + foreign.join(); + + ASSERT_TRUE(captured_cb); + captured_cb(); + + envoy_dynamic_module_callback_network_filter_scheduler_delete(scheduler); +} + +// Verifies `commit()` from a foreign thread after the filter is destroyed is a safe no-op. +TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, + NetworkFilterSchedulerCommitFromForeignThreadAfterDestroyIsNoOp) { + auto* scheduler = envoy_dynamic_module_callback_network_filter_scheduler_new(filterPtr()); + ASSERT_NE(nullptr, scheduler); + + filter_.reset(); + + EXPECT_CALL(worker_thread_dispatcher_, post(_)).Times(0); + + std::thread foreign( + [&]() { envoy_dynamic_module_callback_network_filter_scheduler_commit(scheduler, 999); }); + foreign.join(); + envoy_dynamic_module_callback_network_filter_scheduler_delete(scheduler); } From 2b19ff75b578b0d3dc04582ed3306a6c8ed9e5a5 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Tue, 5 May 2026 06:34:19 +0200 Subject: [PATCH 212/750] dynamic_modules: fix StatNamePool data race in metric increment callbacks (#44840) ## Description The metric increment callbacks across the LB, tracer, cluster, and bootstrap dynamic-module ABIs mutated a per-config `Stats::StatNamePool` on every labeled metric increment. `StatNamePool::add` does an unsynchronized `std::vector::emplace_back` and the same pool is shared across every Envoy worker thread, so concurrent `increment_counter_vec`, `set_gauge_vec`, `record_histogram_value` calls would race the vector reallocation in the slow path and **SIGSEGV**. This PR fixes it by building a per-request tag vectors using a stack-local `Stats::StatNameDynamicPool`. The shared pool is no longer mutated from the request path, eliminating both the data race and an unbounded `storage_vector_` growth that previously leaked one entry per label per increment. --- **Commit Message:** dynamic_modules: fix StatNamePool data race in metric increment callbacks **Additional Description:** Fixed StatNamePool data race in metric increment callbacks. **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** Added **Release Notes:** N/A Signed-off-by: Rohit Agrawal --- .../dynamic_modules/abi_impl.cc | 9 + .../dynamic_modules/access_log_config.cc | 1 + .../dynamic_modules/access_log_config.h | 3 + .../bootstrap/dynamic_modules/abi_impl.cc | 31 +++- .../dynamic_modules/extension_config.cc | 2 + .../dynamic_modules/extension_config.h | 5 + .../clusters/dynamic_modules/abi_impl.cc | 37 +++-- .../clusters/dynamic_modules/cluster.cc | 2 + .../clusters/dynamic_modules/cluster.h | 5 + .../listener/dynamic_modules/abi_impl.cc | 9 + .../listener/dynamic_modules/filter_config.cc | 1 + .../listener/dynamic_modules/filter_config.h | 3 + .../network/dynamic_modules/abi_impl.cc | 9 + .../network/dynamic_modules/filter_config.cc | 1 + .../network/dynamic_modules/filter_config.h | 3 + .../filters/udp/dynamic_modules/abi_impl.cc | 9 + .../udp/dynamic_modules/filter_config.cc | 1 + .../udp/dynamic_modules/filter_config.h | 3 + .../dynamic_modules/abi_impl.cc | 39 +++-- .../dynamic_modules/lb_config.cc | 2 + .../dynamic_modules/lb_config.h | 4 + .../tracers/dynamic_modules/abi_impl.cc | 27 ++- .../tracers/dynamic_modules/tracer_config.cc | 1 + .../tracers/dynamic_modules/tracer_config.h | 5 + .../dynamic_modules/access_log_test.cc | 18 ++ .../clusters/dynamic_modules/cluster_test.cc | 103 ++++++++++++ .../bootstrap/abi_impl_test.cc | 154 ++++++++++++------ .../dynamic_modules/http/abi_impl_test.cc | 15 +- .../dynamic_modules/listener/abi_impl_test.cc | 36 +++- .../dynamic_modules/listener/filter_test.cc | 2 + .../dynamic_modules/network/abi_impl_test.cc | 20 +++ .../dynamic_modules/network/filter_test.cc | 2 + .../dynamic_modules/udp/abi_impl_test.cc | 18 ++ .../dynamic_modules/udp/filter_test.cc | 2 + .../dynamic_modules/config_test.cc | 141 ++++++++++++++++ .../tracers/dynamic_modules/abi_impl_test.cc | 80 +++++++++ 36 files changed, 702 insertions(+), 101 deletions(-) diff --git a/source/extensions/access_loggers/dynamic_modules/abi_impl.cc b/source/extensions/access_loggers/dynamic_modules/abi_impl.cc index c3992181c7fb8..7345d5c9e869b 100644 --- a/source/extensions/access_loggers/dynamic_modules/abi_impl.cc +++ b/source/extensions/access_loggers/dynamic_modules/abi_impl.cc @@ -1542,6 +1542,9 @@ envoy_dynamic_module_callback_access_logger_config_define_counter( envoy_dynamic_module_type_access_logger_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* counter_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Stats::Counter& c = Stats::Utility::counterFromStatNames(*config->stats_scope_, {main_stat_name}); @@ -1567,6 +1570,9 @@ envoy_dynamic_module_callback_access_logger_config_define_gauge( envoy_dynamic_module_type_access_logger_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* gauge_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Stats::Gauge& g = Stats::Utility::gaugeFromStatNames(*config->stats_scope_, {main_stat_name}, @@ -1618,6 +1624,9 @@ envoy_dynamic_module_callback_access_logger_config_define_histogram( envoy_dynamic_module_type_access_logger_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* histogram_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Stats::Histogram& h = Stats::Utility::histogramFromStatNames( diff --git a/source/extensions/access_loggers/dynamic_modules/access_log_config.cc b/source/extensions/access_loggers/dynamic_modules/access_log_config.cc index 92bd845aed9d2..7e8efc80da5fd 100644 --- a/source/extensions/access_loggers/dynamic_modules/access_log_config.cc +++ b/source/extensions/access_loggers/dynamic_modules/access_log_config.cc @@ -77,6 +77,7 @@ absl::StatusOr newDynamicModuleAccessLogC if (config->in_module_config_ == nullptr) { return absl::InvalidArgumentError("Failed to initialize dynamic module access logger config"); } + config->stat_creation_frozen_ = true; return config; } diff --git a/source/extensions/access_loggers/dynamic_modules/access_log_config.h b/source/extensions/access_loggers/dynamic_modules/access_log_config.h index af619b48d00e4..7460b873d294b 100644 --- a/source/extensions/access_loggers/dynamic_modules/access_log_config.h +++ b/source/extensions/access_loggers/dynamic_modules/access_log_config.h @@ -143,6 +143,9 @@ class DynamicModuleAccessLogConfig { // Stats scope for metric creation. const Stats::ScopeSharedPtr stats_scope_; Stats::StatNamePool stat_name_pool_; + // We only allow the module to create stats during the in-module config_new, and not later from + // worker threads, so that we don't have to wrap stat_name_pool_ in a lock. + bool stat_creation_frozen_ = false; private: // Allow the factory function to access private members for initialization. diff --git a/source/extensions/bootstrap/dynamic_modules/abi_impl.cc b/source/extensions/bootstrap/dynamic_modules/abi_impl.cc index 9aa38248a1e72..a751421c2f753 100644 --- a/source/extensions/bootstrap/dynamic_modules/abi_impl.cc +++ b/source/extensions/bootstrap/dynamic_modules/abi_impl.cc @@ -196,16 +196,17 @@ void envoy_dynamic_module_callback_bootstrap_extension_iterate_gauges( namespace { -// Helper to build a StatNameTagVector from label names and label values. +// Builds the tag vector using a caller-owned stack-local pool so the shared `stat_name_pool_` +// is not mutated from worker threads. Returned tags borrow storage from `dynamic_pool`. Envoy::Stats::StatNameTagVector buildTagsForBootstrapMetric( - DynamicModuleBootstrapExtensionConfig& config, const Envoy::Stats::StatNameVec& label_names, + Envoy::Stats::StatNameDynamicPool& dynamic_pool, const Envoy::Stats::StatNameVec& label_names, envoy_dynamic_module_type_module_buffer* label_values, size_t label_values_length) { ASSERT(label_values_length == label_names.size()); Envoy::Stats::StatNameTagVector tags; tags.reserve(label_values_length); for (size_t i = 0; i < label_values_length; i++) { absl::string_view label_value_view(label_values[i].ptr, label_values[i].length); - auto label_value = config.stat_name_pool_.add(label_value_view); + auto label_value = dynamic_pool.add(label_value_view); tags.push_back(Envoy::Stats::StatNameTag(label_names[i], label_value)); } return tags; @@ -222,6 +223,9 @@ envoy_dynamic_module_callback_bootstrap_extension_config_define_counter( envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* counter_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(name_view); @@ -266,7 +270,8 @@ envoy_dynamic_module_callback_bootstrap_extension_config_increment_counter( if (label_values_length != counter->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = buildTagsForBootstrapMetric(*config, counter->getLabelNames(), label_values, + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForBootstrapMetric(dynamic_pool, counter->getLabelNames(), label_values, label_values_length); counter->add(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; @@ -279,6 +284,9 @@ envoy_dynamic_module_callback_bootstrap_extension_config_define_gauge( envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* gauge_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(name_view); Envoy::Stats::Gauge::ImportMode import_mode = Envoy::Stats::Gauge::ImportMode::Accumulate; @@ -322,7 +330,8 @@ envoy_dynamic_module_callback_bootstrap_extension_config_set_gauge( if (label_values_length != gauge->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = buildTagsForBootstrapMetric(*config, gauge->getLabelNames(), label_values, + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForBootstrapMetric(dynamic_pool, gauge->getLabelNames(), label_values, label_values_length); gauge->set(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; @@ -350,7 +359,8 @@ envoy_dynamic_module_callback_bootstrap_extension_config_increment_gauge( if (label_values_length != gauge->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = buildTagsForBootstrapMetric(*config, gauge->getLabelNames(), label_values, + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForBootstrapMetric(dynamic_pool, gauge->getLabelNames(), label_values, label_values_length); gauge->add(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; @@ -378,7 +388,8 @@ envoy_dynamic_module_callback_bootstrap_extension_config_decrement_gauge( if (label_values_length != gauge->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = buildTagsForBootstrapMetric(*config, gauge->getLabelNames(), label_values, + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForBootstrapMetric(dynamic_pool, gauge->getLabelNames(), label_values, label_values_length); gauge->sub(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; @@ -391,6 +402,9 @@ envoy_dynamic_module_callback_bootstrap_extension_config_define_histogram( envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* histogram_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(name_view); Envoy::Stats::Histogram::Unit unit = Envoy::Stats::Histogram::Unit::Unspecified; @@ -434,7 +448,8 @@ envoy_dynamic_module_callback_bootstrap_extension_config_record_histogram_value( if (label_values_length != histogram->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = buildTagsForBootstrapMetric(*config, histogram->getLabelNames(), label_values, + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForBootstrapMetric(dynamic_pool, histogram->getLabelNames(), label_values, label_values_length); histogram->recordValue(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; diff --git a/source/extensions/bootstrap/dynamic_modules/extension_config.cc b/source/extensions/bootstrap/dynamic_modules/extension_config.cc index 51ef95991b431..c5735cfcb91ef 100644 --- a/source/extensions/bootstrap/dynamic_modules/extension_config.cc +++ b/source/extensions/bootstrap/dynamic_modules/extension_config.cc @@ -378,6 +378,8 @@ newDynamicModuleBootstrapExtensionConfig( config->on_bootstrap_extension_listener_add_or_update_ = on_listener_add_or_update.value(); config->on_bootstrap_extension_listener_removal_ = on_listener_removal.value(); + config->stat_creation_frozen_ = true; + return config; } diff --git a/source/extensions/bootstrap/dynamic_modules/extension_config.h b/source/extensions/bootstrap/dynamic_modules/extension_config.h index 25a6389f0ddda..8ffa2aee9e131 100644 --- a/source/extensions/bootstrap/dynamic_modules/extension_config.h +++ b/source/extensions/bootstrap/dynamic_modules/extension_config.h @@ -395,6 +395,11 @@ class DynamicModuleBootstrapExtensionConfig // Stats scope for metric creation. const Stats::ScopeSharedPtr stats_scope_; Stats::StatNamePool stat_name_pool_; + // We only allow the module to create stats during on_bootstrap_extension_config_new, and not + // later from worker threads, so that we don't have to wrap stat_name_pool_ in a lock. + // Per-request label values use a stack-local Stats::StatNameDynamicPool in the increment + // callbacks (see abi_impl.cc). + bool stat_creation_frozen_ = false; // Temporary storage for the admin response body. Set by the // envoy_dynamic_module_callback_bootstrap_extension_admin_set_response callback during diff --git a/source/extensions/clusters/dynamic_modules/abi_impl.cc b/source/extensions/clusters/dynamic_modules/abi_impl.cc index c574efa68b0c7..483c3a9072134 100644 --- a/source/extensions/clusters/dynamic_modules/abi_impl.cc +++ b/source/extensions/clusters/dynamic_modules/abi_impl.cc @@ -64,16 +64,17 @@ getClusterHostMetadataValue(envoy_dynamic_module_type_cluster_lb_envoy_ptr lb_en return &field_it->second; } +// Builds the tag vector using a caller-owned stack-local pool so the shared `stat_name_pool_` +// is not mutated from worker threads. Returned tags borrow storage from `dynamic_pool`. Envoy::Stats::StatNameTagVector buildTagsForClusterMetric( - Envoy::Extensions::Clusters::DynamicModules::DynamicModuleClusterConfig& config, - const Envoy::Stats::StatNameVec& label_names, + Envoy::Stats::StatNameDynamicPool& dynamic_pool, const Envoy::Stats::StatNameVec& label_names, envoy_dynamic_module_type_module_buffer* label_values, size_t label_values_length) { ASSERT(label_values_length == label_names.size()); Envoy::Stats::StatNameTagVector tags; tags.reserve(label_values_length); for (size_t i = 0; i < label_values_length; i++) { absl::string_view label_value_view(label_values[i].ptr, label_values[i].length); - auto label_value = config.stat_name_pool_.add(label_value_view); + auto label_value = dynamic_pool.add(label_value_view); tags.push_back(Envoy::Stats::StatNameTag(label_names[i], label_value)); } return tags; @@ -839,6 +840,9 @@ envoy_dynamic_module_callback_cluster_config_define_counter( envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* counter_id_ptr) { auto* config = getConfig(cluster_config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(name_view); @@ -885,7 +889,8 @@ envoy_dynamic_module_callback_cluster_config_increment_counter( if (label_values_length != counter->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = buildTagsForClusterMetric(*config, counter->getLabelNames(), label_values, + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForClusterMetric(dynamic_pool, counter->getLabelNames(), label_values, label_values_length); counter->add(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; @@ -897,6 +902,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_cluster_c envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* gauge_id_ptr) { auto* config = getConfig(cluster_config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(name_view); Envoy::Stats::Gauge::ImportMode import_mode = Envoy::Stats::Gauge::ImportMode::Accumulate; @@ -943,8 +951,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_cluster_c if (label_values_length != gauge->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = - buildTagsForClusterMetric(*config, gauge->getLabelNames(), label_values, label_values_length); + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForClusterMetric(dynamic_pool, gauge->getLabelNames(), label_values, + label_values_length); gauge->set(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; } @@ -975,8 +984,9 @@ envoy_dynamic_module_callback_cluster_config_increment_gauge( if (label_values_length != gauge->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = - buildTagsForClusterMetric(*config, gauge->getLabelNames(), label_values, label_values_length); + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForClusterMetric(dynamic_pool, gauge->getLabelNames(), label_values, + label_values_length); gauge->add(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; } @@ -1007,8 +1017,9 @@ envoy_dynamic_module_callback_cluster_config_decrement_gauge( if (label_values_length != gauge->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = - buildTagsForClusterMetric(*config, gauge->getLabelNames(), label_values, label_values_length); + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForClusterMetric(dynamic_pool, gauge->getLabelNames(), label_values, + label_values_length); gauge->sub(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; } @@ -1020,6 +1031,9 @@ envoy_dynamic_module_callback_cluster_config_define_histogram( envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* histogram_id_ptr) { auto* config = getConfig(cluster_config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(name_view); Envoy::Stats::Histogram::Unit unit = Envoy::Stats::Histogram::Unit::Unspecified; @@ -1067,7 +1081,8 @@ envoy_dynamic_module_callback_cluster_config_record_histogram_value( if (label_values_length != histogram->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = buildTagsForClusterMetric(*config, histogram->getLabelNames(), label_values, + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForClusterMetric(dynamic_pool, histogram->getLabelNames(), label_values, label_values_length); histogram->recordValue(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; diff --git a/source/extensions/clusters/dynamic_modules/cluster.cc b/source/extensions/clusters/dynamic_modules/cluster.cc index d9dbeb177bda0..7dc23d99df010 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.cc +++ b/source/extensions/clusters/dynamic_modules/cluster.cc @@ -135,6 +135,8 @@ absl::StatusOr> DynamicModuleCluster return absl::InvalidArgumentError("Failed to create in-module cluster configuration"); } + config->stat_creation_frozen_ = true; + return config; } diff --git a/source/extensions/clusters/dynamic_modules/cluster.h b/source/extensions/clusters/dynamic_modules/cluster.h index f428ec58db04f..6a4a30a5096bb 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.h +++ b/source/extensions/clusters/dynamic_modules/cluster.h @@ -261,6 +261,11 @@ class DynamicModuleClusterConfig { const Stats::ScopeSharedPtr stats_scope_; Stats::StatNamePool stat_name_pool_; + // We only allow the module to create stats during on_cluster_config_new, and not later from + // worker threads, so that we don't have to wrap stat_name_pool_ in a lock. Per-request label + // values use a stack-local Stats::StatNameDynamicPool in the increment callbacks (see + // abi_impl.cc). + bool stat_creation_frozen_ = false; private: DynamicModuleClusterConfig(const std::string& cluster_name, const std::string& cluster_config, diff --git a/source/extensions/filters/listener/dynamic_modules/abi_impl.cc b/source/extensions/filters/listener/dynamic_modules/abi_impl.cc index 5aff1e248ebea..fefae732d891c 100644 --- a/source/extensions/filters/listener/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/listener/dynamic_modules/abi_impl.cc @@ -980,6 +980,9 @@ envoy_dynamic_module_callback_listener_filter_config_define_counter( envoy_dynamic_module_type_listener_filter_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* counter_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Stats::Counter& c = Stats::Utility::counterFromStatNames(*config->stats_scope_, {main_stat_name}); @@ -1005,6 +1008,9 @@ envoy_dynamic_module_callback_listener_filter_config_define_gauge( envoy_dynamic_module_type_listener_filter_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* gauge_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Stats::Gauge& g = Stats::Utility::gaugeFromStatNames(*config->stats_scope_, {main_stat_name}, @@ -1056,6 +1062,9 @@ envoy_dynamic_module_callback_listener_filter_config_define_histogram( envoy_dynamic_module_type_listener_filter_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* histogram_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Stats::Histogram& h = Stats::Utility::histogramFromStatNames( diff --git a/source/extensions/filters/listener/dynamic_modules/filter_config.cc b/source/extensions/filters/listener/dynamic_modules/filter_config.cc index 0d69ee1bc20cd..7135d2b045d65 100644 --- a/source/extensions/filters/listener/dynamic_modules/filter_config.cc +++ b/source/extensions/filters/listener/dynamic_modules/filter_config.cc @@ -119,6 +119,7 @@ absl::StatusOr newDynamicModuleListe if (config->in_module_config_ == nullptr) { return absl::InvalidArgumentError("Failed to initialize dynamic module listener filter config"); } + config->stat_creation_frozen_ = true; return config; } diff --git a/source/extensions/filters/listener/dynamic_modules/filter_config.h b/source/extensions/filters/listener/dynamic_modules/filter_config.h index 2e19edfea1d4a..455ba2b459bfe 100644 --- a/source/extensions/filters/listener/dynamic_modules/filter_config.h +++ b/source/extensions/filters/listener/dynamic_modules/filter_config.h @@ -181,6 +181,9 @@ class DynamicModuleListenerFilterConfig // Stats scope for metric creation. const Stats::ScopeSharedPtr stats_scope_; Stats::StatNamePool stat_name_pool_; + // We only allow the module to create stats during the in-module config_new, and not later from + // worker threads, so that we don't have to wrap stat_name_pool_ in a lock. + bool stat_creation_frozen_ = false; private: // Allow the factory function to access private members for initialization. diff --git a/source/extensions/filters/network/dynamic_modules/abi_impl.cc b/source/extensions/filters/network/dynamic_modules/abi_impl.cc index 469a74eb6a23f..24a97cdc89e0e 100644 --- a/source/extensions/filters/network/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/network/dynamic_modules/abi_impl.cc @@ -837,6 +837,9 @@ envoy_dynamic_module_callback_network_filter_config_define_counter( envoy_dynamic_module_type_network_filter_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* counter_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Stats::Counter& c = Stats::Utility::counterFromStatNames(*config->stats_scope_, {main_stat_name}); @@ -862,6 +865,9 @@ envoy_dynamic_module_callback_network_filter_config_define_gauge( envoy_dynamic_module_type_network_filter_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* gauge_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Stats::Gauge& g = Stats::Utility::gaugeFromStatNames(*config->stats_scope_, {main_stat_name}, @@ -913,6 +919,9 @@ envoy_dynamic_module_callback_network_filter_config_define_histogram( envoy_dynamic_module_type_network_filter_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* histogram_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Stats::Histogram& h = Stats::Utility::histogramFromStatNames( diff --git a/source/extensions/filters/network/dynamic_modules/filter_config.cc b/source/extensions/filters/network/dynamic_modules/filter_config.cc index 9086f65f59858..107b3b7fa4709 100644 --- a/source/extensions/filters/network/dynamic_modules/filter_config.cc +++ b/source/extensions/filters/network/dynamic_modules/filter_config.cc @@ -129,6 +129,7 @@ absl::StatusOr newDynamicModuleNetwor if (config->in_module_config_ == nullptr) { return absl::InvalidArgumentError("Failed to initialize dynamic module network filter config"); } + config->stat_creation_frozen_ = true; return config; } diff --git a/source/extensions/filters/network/dynamic_modules/filter_config.h b/source/extensions/filters/network/dynamic_modules/filter_config.h index ce2154c524a3b..60d1d14d1701f 100644 --- a/source/extensions/filters/network/dynamic_modules/filter_config.h +++ b/source/extensions/filters/network/dynamic_modules/filter_config.h @@ -186,6 +186,9 @@ class DynamicModuleNetworkFilterConfig // Stats scope for metric creation. const Stats::ScopeSharedPtr stats_scope_; Stats::StatNamePool stat_name_pool_; + // We only allow the module to create stats during the in-module config_new, and not later from + // worker threads, so that we don't have to wrap stat_name_pool_ in a lock. + bool stat_creation_frozen_ = false; private: // Allow the factory function to access private members for initialization. diff --git a/source/extensions/filters/udp/dynamic_modules/abi_impl.cc b/source/extensions/filters/udp/dynamic_modules/abi_impl.cc index 2c1f6df4f6e3f..ebc26a6de7dfe 100644 --- a/source/extensions/filters/udp/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/udp/dynamic_modules/abi_impl.cc @@ -179,6 +179,9 @@ envoy_dynamic_module_callback_udp_listener_filter_config_define_counter( envoy_dynamic_module_type_udp_listener_filter_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* counter_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Envoy::Stats::Counter& c = @@ -205,6 +208,9 @@ envoy_dynamic_module_callback_udp_listener_filter_config_define_gauge( envoy_dynamic_module_type_udp_listener_filter_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* gauge_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Envoy::Stats::Gauge& g = Envoy::Stats::Utility::gaugeFromStatNames( @@ -257,6 +263,9 @@ envoy_dynamic_module_callback_udp_listener_filter_config_define_histogram( envoy_dynamic_module_type_udp_listener_filter_config_envoy_ptr config_envoy_ptr, envoy_dynamic_module_type_module_buffer name, size_t* histogram_id_ptr) { auto* config = static_cast(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(absl::string_view(name.ptr, name.length)); Envoy::Stats::Histogram& h = Envoy::Stats::Utility::histogramFromStatNames( diff --git a/source/extensions/filters/udp/dynamic_modules/filter_config.cc b/source/extensions/filters/udp/dynamic_modules/filter_config.cc index 4b8b1e245c472..d74e7cf467945 100644 --- a/source/extensions/filters/udp/dynamic_modules/filter_config.cc +++ b/source/extensions/filters/udp/dynamic_modules/filter_config.cc @@ -67,6 +67,7 @@ DynamicModuleUdpListenerFilterConfig::DynamicModuleUdpListenerFilterConfig( in_module_config_ = on_filter_config_new_(static_cast(this), {filter_name_.c_str(), filter_name_.size()}, {filter_config_.data(), filter_config_.size()}); + stat_creation_frozen_ = true; } DynamicModuleUdpListenerFilterConfig::~DynamicModuleUdpListenerFilterConfig() { diff --git a/source/extensions/filters/udp/dynamic_modules/filter_config.h b/source/extensions/filters/udp/dynamic_modules/filter_config.h index d76617cc9ceb6..09e522d4d4079 100644 --- a/source/extensions/filters/udp/dynamic_modules/filter_config.h +++ b/source/extensions/filters/udp/dynamic_modules/filter_config.h @@ -120,6 +120,9 @@ class DynamicModuleUdpListenerFilterConfig { // Stats scope for metric creation. const Stats::ScopeSharedPtr stats_scope_; Stats::StatNamePool stat_name_pool_; + // We only allow the module to create stats during the in-module config_new, and not later from + // worker threads, so that we don't have to wrap stat_name_pool_ in a lock. + bool stat_creation_frozen_ = false; private: // Metric storage. diff --git a/source/extensions/load_balancing_policies/dynamic_modules/abi_impl.cc b/source/extensions/load_balancing_policies/dynamic_modules/abi_impl.cc index 429dac8c87549..a896c24cf6f5c 100644 --- a/source/extensions/load_balancing_policies/dynamic_modules/abi_impl.cc +++ b/source/extensions/load_balancing_policies/dynamic_modules/abi_impl.cc @@ -642,16 +642,17 @@ envoy_dynamic_module_callback_lb_get_host_stat(envoy_dynamic_module_type_lb_envo namespace { -Envoy::Stats::StatNameTagVector -buildTagsForLbMetric(DynamicModuleLbConfig& config, const Envoy::Stats::StatNameVec& label_names, - envoy_dynamic_module_type_module_buffer* label_values, - size_t label_values_length) { +// Builds the tag vector using a caller-owned stack-local pool so the shared `stat_name_pool_` +// is not mutated from worker threads. Returned tags borrow storage from `dynamic_pool`. +Envoy::Stats::StatNameTagVector buildTagsForLbMetric( + Envoy::Stats::StatNameDynamicPool& dynamic_pool, const Envoy::Stats::StatNameVec& label_names, + envoy_dynamic_module_type_module_buffer* label_values, size_t label_values_length) { ASSERT(label_values_length == label_names.size()); Envoy::Stats::StatNameTagVector tags; tags.reserve(label_values_length); for (size_t i = 0; i < label_values_length; i++) { absl::string_view label_value_view(label_values[i].ptr, label_values[i].length); - auto label_value = config.stat_name_pool_.add(label_value_view); + auto label_value = dynamic_pool.add(label_value_view); tags.push_back(Envoy::Stats::StatNameTag(label_names[i], label_value)); } return tags; @@ -667,6 +668,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_lb_config envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* counter_id_ptr) { auto* config = static_cast(lb_config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(name_view); @@ -713,8 +717,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_lb_config if (label_values_length != counter->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = - buildTagsForLbMetric(*config, counter->getLabelNames(), label_values, label_values_length); + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForLbMetric(dynamic_pool, counter->getLabelNames(), label_values, + label_values_length); counter->add(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; } @@ -725,6 +730,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_lb_config envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* gauge_id_ptr) { auto* config = static_cast(lb_config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(name_view); Envoy::Stats::Gauge::ImportMode import_mode = Envoy::Stats::Gauge::ImportMode::Accumulate; @@ -771,8 +779,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_lb_config if (label_values_length != gauge->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); auto tags = - buildTagsForLbMetric(*config, gauge->getLabelNames(), label_values, label_values_length); + buildTagsForLbMetric(dynamic_pool, gauge->getLabelNames(), label_values, label_values_length); gauge->set(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; } @@ -802,8 +811,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_lb_config if (label_values_length != gauge->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); auto tags = - buildTagsForLbMetric(*config, gauge->getLabelNames(), label_values, label_values_length); + buildTagsForLbMetric(dynamic_pool, gauge->getLabelNames(), label_values, label_values_length); gauge->add(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; } @@ -833,8 +843,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_lb_config if (label_values_length != gauge->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); auto tags = - buildTagsForLbMetric(*config, gauge->getLabelNames(), label_values, label_values_length); + buildTagsForLbMetric(dynamic_pool, gauge->getLabelNames(), label_values, label_values_length); gauge->sub(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; } @@ -845,6 +856,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_lb_config envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* histogram_id_ptr) { auto* config = static_cast(lb_config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); Envoy::Stats::StatName main_stat_name = config->stat_name_pool_.add(name_view); Envoy::Stats::Histogram::Unit unit = Envoy::Stats::Histogram::Unit::Unspecified; @@ -892,8 +906,9 @@ envoy_dynamic_module_callback_lb_config_record_histogram_value( if (label_values_length != histogram->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = - buildTagsForLbMetric(*config, histogram->getLabelNames(), label_values, label_values_length); + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForLbMetric(dynamic_pool, histogram->getLabelNames(), label_values, + label_values_length); histogram->recordValue(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; } diff --git a/source/extensions/load_balancing_policies/dynamic_modules/lb_config.cc b/source/extensions/load_balancing_policies/dynamic_modules/lb_config.cc index ca26c47fb884f..d95e35395c7b7 100644 --- a/source/extensions/load_balancing_policies/dynamic_modules/lb_config.cc +++ b/source/extensions/load_balancing_policies/dynamic_modules/lb_config.cc @@ -48,6 +48,8 @@ DynamicModuleLbConfig::create(const std::string& lb_policy_name, const std::stri return absl::InvalidArgumentError("failed to create in-module load balancer configuration"); } + config->stat_creation_frozen_ = true; + return config; } diff --git a/source/extensions/load_balancing_policies/dynamic_modules/lb_config.h b/source/extensions/load_balancing_policies/dynamic_modules/lb_config.h index a4a428fe3ba49..9c2bc41ce63a7 100644 --- a/source/extensions/load_balancing_policies/dynamic_modules/lb_config.h +++ b/source/extensions/load_balancing_policies/dynamic_modules/lb_config.h @@ -234,6 +234,10 @@ class DynamicModuleLbConfig : public Logger::Loggablestat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); auto stat_name = config->stat_name_pool_.add(name_view); @@ -188,6 +192,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_tracer_de envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* gauge_id_ptr) { auto* config = getConfig(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); auto stat_name = config->stat_name_pool_.add(name_view); auto import_mode = Envoy::Stats::Gauge::ImportMode::NeverImport; @@ -217,6 +224,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_tracer_de envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* histogram_id_ptr) { auto* config = getConfig(config_envoy_ptr); + if (config->stat_creation_frozen_) { + return envoy_dynamic_module_type_metrics_result_Frozen; + } absl::string_view name_view(name.ptr, name.length); auto stat_name = config->stat_name_pool_.add(name_view); auto unit = Envoy::Stats::Histogram::Unit::Unspecified; @@ -262,7 +272,8 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_tracer_in if (label_values_length != counter->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = buildTagsForTracerMetric(*config, counter->getLabelNames(), label_values, + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForTracerMetric(dynamic_pool, counter->getLabelNames(), label_values, label_values_length); counter->add(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; @@ -291,7 +302,8 @@ envoy_dynamic_module_callback_tracer_record_histogram_value( if (label_values_length != histogram->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = buildTagsForTracerMetric(*config, histogram->getLabelNames(), label_values, + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForTracerMetric(dynamic_pool, histogram->getLabelNames(), label_values, label_values_length); histogram->recordValue(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; @@ -319,8 +331,9 @@ envoy_dynamic_module_type_metrics_result envoy_dynamic_module_callback_tracer_se if (label_values_length != gauge->getLabelNames().size()) { return envoy_dynamic_module_type_metrics_result_InvalidLabels; } - auto tags = - buildTagsForTracerMetric(*config, gauge->getLabelNames(), label_values, label_values_length); + Envoy::Stats::StatNameDynamicPool dynamic_pool(config->stats_scope_->symbolTable()); + auto tags = buildTagsForTracerMetric(dynamic_pool, gauge->getLabelNames(), label_values, + label_values_length); gauge->set(*config->stats_scope_, tags, value); return envoy_dynamic_module_type_metrics_result_Success; } diff --git a/source/extensions/tracers/dynamic_modules/tracer_config.cc b/source/extensions/tracers/dynamic_modules/tracer_config.cc index 5f9d1e0b6862d..a8027c7c99e64 100644 --- a/source/extensions/tracers/dynamic_modules/tracer_config.cc +++ b/source/extensions/tracers/dynamic_modules/tracer_config.cc @@ -101,6 +101,7 @@ absl::StatusOr newDynamicModuleTracerConfig( if (config->in_module_config_ == nullptr) { return absl::InvalidArgumentError("Failed to initialize dynamic module tracer config"); } + config->stat_creation_frozen_ = true; return config; } diff --git a/source/extensions/tracers/dynamic_modules/tracer_config.h b/source/extensions/tracers/dynamic_modules/tracer_config.h index 71408591cb5b9..a83a0d33cb209 100644 --- a/source/extensions/tracers/dynamic_modules/tracer_config.h +++ b/source/extensions/tracers/dynamic_modules/tracer_config.h @@ -237,6 +237,11 @@ class DynamicModuleTracerConfig { const Stats::ScopeSharedPtr stats_scope_; Stats::StatNamePool stat_name_pool_; + // We only allow the module to create stats during on_tracer_config_new, and not later from + // worker threads, so that we don't have to wrap stat_name_pool_ in a lock. Per-request label + // values use a stack-local Stats::StatNameDynamicPool in the increment callbacks (see + // abi_impl.cc). + bool stat_creation_frozen_ = false; private: friend absl::StatusOr> newDynamicModuleTracerConfig( diff --git a/test/extensions/access_loggers/dynamic_modules/access_log_test.cc b/test/extensions/access_loggers/dynamic_modules/access_log_test.cc index f293ec7df6b96..15b69474d4e2c 100644 --- a/test/extensions/access_loggers/dynamic_modules/access_log_test.cc +++ b/test/extensions/access_loggers/dynamic_modules/access_log_test.cc @@ -39,6 +39,8 @@ class DynamicModuleAccessLogTest : public testing::Test { std::move(dynamic_module.value()), *stats_.rootScope()); EXPECT_TRUE(config.ok()) << config.status().message(); config_ = std::move(config.value()); + // Re-open stat creation so tests can call `define_*` from the test thread. + config_->stat_creation_frozen_ = false; } Stats::IsolatedStoreImpl stats_; @@ -328,6 +330,22 @@ TEST_F(DynamicModuleAccessLogTest, MetricsInvalidId) { static_cast(config_.get()), 999, 1)); } +// Verifies the factory auto-freezes stat creation so `define_*` returns `Frozen` after init. +TEST_F(DynamicModuleAccessLogTest, MetricsFrozenAfterInit) { + config_->stat_creation_frozen_ = true; + envoy_dynamic_module_type_module_buffer name = {.ptr = "frozen_counter", .length = 14}; + size_t out_id = 0; + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_access_logger_config_define_counter( + static_cast(config_.get()), name, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_access_logger_config_define_gauge( + static_cast(config_.get()), name, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_access_logger_config_define_histogram( + static_cast(config_.get()), name, &out_id)); +} + } // namespace } // namespace DynamicModules } // namespace AccessLoggers diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index f0ea6b5404291..c453eac87eb14 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -1,3 +1,6 @@ +#include +#include + #include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/extensions/clusters/dynamic_modules/v3/cluster.pb.h" @@ -18,6 +21,7 @@ #include "test/test_common/thread_factory_for_test.h" #include "test/test_common/utility.h" +#include "absl/strings/str_cat.h" #include "gtest/gtest.h" namespace Envoy { @@ -67,6 +71,11 @@ class DynamicModuleClusterTest : public testing::Test { return factory.create(cluster_config, factory_context); } + // Re-opens stat creation so tests can call `define_*` from the test thread. + static void unfreezeStatCreation(DynamicModuleClusterConfig& config) { + config.stat_creation_frozen_ = false; + } + std::string makeYamlConfig(const std::string& module_name, const std::string& cluster_name = "test") { return fmt::format(R"EOF( @@ -1336,6 +1345,7 @@ TEST_F(DynamicModuleClusterTest, MetricsDefineAndIncrementCounter) { auto cluster = std::dynamic_pointer_cast(result->first); auto* config = cluster->config().get(); + unfreezeStatCreation(*config); // Define a scalar counter. size_t counter_id = 0; @@ -1359,6 +1369,7 @@ TEST_F(DynamicModuleClusterTest, MetricsDefineAndUseGauge) { auto cluster = std::dynamic_pointer_cast(result->first); auto* config = cluster->config().get(); + unfreezeStatCreation(*config); // Define a scalar gauge. size_t gauge_id = 0; @@ -1388,6 +1399,7 @@ TEST_F(DynamicModuleClusterTest, MetricsDefineAndRecordHistogram) { auto cluster = std::dynamic_pointer_cast(result->first); auto* config = cluster->config().get(); + unfreezeStatCreation(*config); // Define a scalar histogram. size_t histogram_id = 0; @@ -1411,6 +1423,7 @@ TEST_F(DynamicModuleClusterTest, MetricsNotFoundForInvalidId) { auto cluster = std::dynamic_pointer_cast(result->first); auto* config = cluster->config().get(); + unfreezeStatCreation(*config); // Increment a counter that was never defined. EXPECT_EQ( @@ -1444,6 +1457,7 @@ TEST_F(DynamicModuleClusterTest, MetricsCounterVecWithLabels) { auto cluster = std::dynamic_pointer_cast(result->first); auto* config = cluster->config().get(); + unfreezeStatCreation(*config); // Define a counter vec with two labels. size_t counter_id = 0; @@ -1488,6 +1502,7 @@ TEST_F(DynamicModuleClusterTest, MetricsGaugeVecWithLabels) { auto cluster = std::dynamic_pointer_cast(result->first); auto* config = cluster->config().get(); + unfreezeStatCreation(*config); // Define a gauge vec. size_t gauge_id = 0; @@ -1523,6 +1538,7 @@ TEST_F(DynamicModuleClusterTest, MetricsHistogramVecWithLabels) { auto cluster = std::dynamic_pointer_cast(result->first); auto* config = cluster->config().get(); + unfreezeStatCreation(*config); // Define a histogram vec. size_t histogram_id = 0; @@ -1554,6 +1570,7 @@ TEST_F(DynamicModuleClusterTest, MetricsVecScalarIdConflictErrors) { auto cluster = std::dynamic_pointer_cast(result->first); auto* config = cluster->config().get(); + unfreezeStatCreation(*config); envoy_dynamic_module_type_module_buffer label_name = {const_cast("lbl"), strlen("lbl")}; @@ -1608,6 +1625,7 @@ TEST_F(DynamicModuleClusterTest, MetricsVecWrongLabelCount) { auto cluster = std::dynamic_pointer_cast(result->first); auto* config = cluster->config().get(); + unfreezeStatCreation(*config); // Define a gauge vec with one label. envoy_dynamic_module_type_module_buffer gauge_name = {const_cast("gwl"), strlen("gwl")}; @@ -1652,6 +1670,7 @@ TEST_F(DynamicModuleClusterTest, MetricsVecNotFoundWithLabels) { auto cluster = std::dynamic_pointer_cast(result->first); auto* config = cluster->config().get(); + unfreezeStatCreation(*config); envoy_dynamic_module_type_module_buffer label_val = {const_cast("val"), strlen("val")}; @@ -3252,6 +3271,90 @@ TEST_F(DynamicModuleClusterTest, LbRepeatedConstructTeardownWithUpdates) { } } +// Verifies the factory auto-freezes stat creation so `define_*` returns `Frozen` after init. +TEST_F(DynamicModuleClusterTest, MetricsFrozenAfterInit) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()) << result.status().message(); + + auto cluster = std::dynamic_pointer_cast(result->first); + auto* config = cluster->config().get(); + EXPECT_TRUE(config->stat_creation_frozen_); + + envoy_dynamic_module_type_module_buffer name = {const_cast("frozen_counter"), 14}; + envoy_dynamic_module_type_module_buffer label = {const_cast("label"), 5}; + size_t out_id = 0; + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_cluster_config_define_counter(config, name, nullptr, 0, + &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_cluster_config_define_counter(config, name, &label, 1, + &out_id)); + EXPECT_EQ( + envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_cluster_config_define_gauge(config, name, nullptr, 0, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_cluster_config_define_histogram(config, name, nullptr, 0, + &out_id)); +} + +// Drives concurrent labeled increments from multiple threads to verify no data race in the +// shared `stat_name_pool_`. Run under `--config=tsan` to verify. +TEST_F(DynamicModuleClusterTest, MetricsConcurrentIncrementCounterVecNoRace) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()) << result.status().message(); + auto cluster = std::dynamic_pointer_cast(result->first); + auto* config = cluster->config().get(); + unfreezeStatCreation(*config); + + envoy_dynamic_module_type_module_buffer name = {const_cast("race_counter"), 12}; + envoy_dynamic_module_type_module_buffer label_names = {const_cast("status"), 6}; + size_t counter_id = 0; + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_cluster_config_define_counter(config, name, &label_names, + 1, &counter_id)); + + constexpr int kNumThreads = 8; + constexpr int kIncrementsPerThread = 2000; + + // Pre-warm the test scope's counter cache so workers only hit the cache. `TestScope` uses an + // unsynchronized map for counter caching that would otherwise race independently of the path + // under test. + for (int t = 0; t < kNumThreads; ++t) { + const std::string label_value_str = absl::StrCat("worker_", t); + envoy_dynamic_module_type_module_buffer label_value = { + const_cast(label_value_str.data()), label_value_str.size()}; + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_cluster_config_increment_counter(config, counter_id, + &label_value, 1, 0)); + } + + std::vector threads; + threads.reserve(kNumThreads); + std::atomic ready{0}; + std::atomic go{false}; + for (int t = 0; t < kNumThreads; ++t) { + threads.emplace_back([&, t]() { + const std::string label_value_str = absl::StrCat("worker_", t); + envoy_dynamic_module_type_module_buffer label_value = { + const_cast(label_value_str.data()), label_value_str.size()}; + ready.fetch_add(1, std::memory_order_relaxed); + while (!go.load(std::memory_order_acquire)) { + } + for (int i = 0; i < kIncrementsPerThread; ++i) { + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_cluster_config_increment_counter( + config, counter_id, &label_value, 1, 1)); + } + }); + } + while (ready.load(std::memory_order_acquire) < kNumThreads) { + } + go.store(true, std::memory_order_release); + for (auto& th : threads) { + th.join(); + } +} + } // namespace } // namespace DynamicModules } // namespace Clusters diff --git a/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc b/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc index 61e5521bcf6e4..dd2403c07b3c1 100644 --- a/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc @@ -1,3 +1,6 @@ +#include +#include + #include "source/extensions/bootstrap/dynamic_modules/extension.h" #include "source/extensions/bootstrap/dynamic_modules/extension_config.h" #include "source/extensions/dynamic_modules/abi/abi.h" @@ -17,6 +20,7 @@ #include "test/test_common/environment.h" #include "test/test_common/utility.h" +#include "absl/strings/str_cat.h" #include "gtest/gtest.h" namespace Envoy { @@ -30,6 +34,11 @@ class BootstrapAbiImplTest : public testing::Test { return TestEnvironment::runfilesPath("test/extensions/dynamic_modules/test_data/c"); } + // Re-opens stat creation so tests can call `define_*` from the test thread. + static void unfreezeStatCreation(DynamicModuleBootstrapExtensionConfig& config) { + config.stat_creation_frozen_ = false; + } + testing::NiceMock dispatcher_; testing::NiceMock context_; }; @@ -44,7 +53,6 @@ TEST_F(BootstrapAbiImplTest, SchedulerLifecycle) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Create a scheduler via the ABI callback. auto* scheduler_ptr = envoy_dynamic_module_callback_bootstrap_extension_config_scheduler_new( config.value()->thisAsVoidPtr()); @@ -64,7 +72,6 @@ TEST_F(BootstrapAbiImplTest, SchedulerCommit) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Create a scheduler via the ABI callback. auto* scheduler_ptr = envoy_dynamic_module_callback_bootstrap_extension_config_scheduler_new( config.value()->thisAsVoidPtr()); @@ -96,7 +103,6 @@ TEST_F(BootstrapAbiImplTest, OnScheduledCallback) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Create a scheduler via the ABI callback. auto* scheduler_ptr = envoy_dynamic_module_callback_bootstrap_extension_config_scheduler_new( config.value()->thisAsVoidPtr()); @@ -131,7 +137,6 @@ TEST_F(BootstrapAbiImplTest, OnScheduledAfterConfigDestroyed) { "test", "config", DefaultMetricsNamespace, std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Create a scheduler via the ABI callback. auto* scheduler_ptr = envoy_dynamic_module_callback_bootstrap_extension_config_scheduler_new( config.value()->thisAsVoidPtr()); @@ -168,7 +173,6 @@ TEST_F(BootstrapAbiImplTest, CommitAfterConfigDestroyedDoesNotTouchDispatcher) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - auto* scheduler_ptr = envoy_dynamic_module_callback_bootstrap_extension_config_scheduler_new( config.value()->thisAsVoidPtr()); EXPECT_NE(scheduler_ptr, nullptr); @@ -192,7 +196,6 @@ TEST_F(BootstrapAbiImplTest, OnScheduledDirect) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Call onScheduled directly - this should call the in-module hook. config.value()->onScheduled(789); } @@ -215,7 +218,6 @@ TEST_F(BootstrapAbiImplTest, InitTargetAutoRegisteredAndSignal) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // The C no-op module already called signal_init_complete during config creation. // Calling it again verifies that duplicate calls are safe. envoy_dynamic_module_callback_bootstrap_extension_config_signal_init_complete( @@ -236,7 +238,6 @@ TEST_F(BootstrapAbiImplTest, HttpCalloutClusterNotFound) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Setup mock to return nullptr for the cluster lookup. EXPECT_CALL(context_.cluster_manager_, getThreadLocalCluster("nonexistent_cluster")) .WillOnce(testing::Return(nullptr)); @@ -269,7 +270,6 @@ TEST_F(BootstrapAbiImplTest, HttpCalloutMissingHeaders) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Headers missing :method, :path, and host. std::vector headers = { {"x-custom", 8, "value", 5}, @@ -296,7 +296,6 @@ TEST_F(BootstrapAbiImplTest, HttpCalloutSuccess) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Setup mock cluster manager to return a valid cluster. testing::NiceMock thread_local_cluster; EXPECT_CALL(context_.cluster_manager_, getThreadLocalCluster("test_cluster")) @@ -355,7 +354,6 @@ TEST_F(BootstrapAbiImplTest, HttpCalloutFailureReset) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Setup mock cluster manager to return a valid cluster. testing::NiceMock thread_local_cluster; EXPECT_CALL(context_.cluster_manager_, getThreadLocalCluster("test_cluster")) @@ -404,7 +402,6 @@ TEST_F(BootstrapAbiImplTest, HttpCalloutFailureExceedBufferLimit) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Setup mock cluster manager to return a valid cluster. testing::NiceMock thread_local_cluster; EXPECT_CALL(context_.cluster_manager_, getThreadLocalCluster("test_cluster")) @@ -454,7 +451,6 @@ TEST_F(BootstrapAbiImplTest, HttpCalloutCannotCreateRequest) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Setup mock cluster manager to return a valid cluster. testing::NiceMock thread_local_cluster; EXPECT_CALL(context_.cluster_manager_, getThreadLocalCluster("test_cluster")) @@ -492,7 +488,6 @@ TEST_F(BootstrapAbiImplTest, HttpCalloutSuccessAfterInModuleConfigCleared) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Setup mock cluster manager to return a valid cluster. testing::NiceMock thread_local_cluster; EXPECT_CALL(context_.cluster_manager_, getThreadLocalCluster("test_cluster")) @@ -549,7 +544,6 @@ TEST_F(BootstrapAbiImplTest, HttpCalloutFailureAfterInModuleConfigCleared) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Setup mock cluster manager to return a valid cluster. testing::NiceMock thread_local_cluster; EXPECT_CALL(context_.cluster_manager_, getThreadLocalCluster("test_cluster")) @@ -601,7 +595,6 @@ TEST_F(BootstrapAbiImplTest, HttpCalloutWithBody) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Setup mock cluster manager to return a valid cluster. testing::NiceMock thread_local_cluster; EXPECT_CALL(context_.cluster_manager_, getThreadLocalCluster("test_cluster")) @@ -666,7 +659,6 @@ TEST_F(BootstrapAbiImplTest, GetCounterValueExisting) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - auto extension = std::make_unique(config.value()); extension->initializeInModuleExtension(); @@ -690,7 +682,6 @@ TEST_F(BootstrapAbiImplTest, GetCounterValueNonExistent) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - auto extension = std::make_unique(config.value()); extension->initializeInModuleExtension(); @@ -716,7 +707,6 @@ TEST_F(BootstrapAbiImplTest, GetGaugeValueExisting) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - auto extension = std::make_unique(config.value()); extension->initializeInModuleExtension(); @@ -740,7 +730,6 @@ TEST_F(BootstrapAbiImplTest, GetGaugeValueNonExistent) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - auto extension = std::make_unique(config.value()); extension->initializeInModuleExtension(); @@ -768,7 +757,6 @@ TEST_F(BootstrapAbiImplTest, IterateCounters) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - auto extension = std::make_unique(config.value()); extension->initializeInModuleExtension(); @@ -805,7 +793,6 @@ TEST_F(BootstrapAbiImplTest, IterateGauges) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - auto extension = std::make_unique(config.value()); extension->initializeInModuleExtension(); @@ -842,6 +829,7 @@ TEST_F(BootstrapAbiImplTest, DefineAndIncrementCounter) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); + unfreezeStatCreation(*config.value()); // Define a counter without labels. size_t counter_id = 0; @@ -876,7 +864,6 @@ TEST_F(BootstrapAbiImplTest, IncrementCounterInvalidId) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Try to increment a counter with an invalid ID. auto result = envoy_dynamic_module_callback_bootstrap_extension_config_increment_counter( config.value()->thisAsVoidPtr(), 999, nullptr, 0, 1); @@ -893,6 +880,7 @@ TEST_F(BootstrapAbiImplTest, DefineAndManipulateGauge) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); + unfreezeStatCreation(*config.value()); // Define a gauge without labels. size_t gauge_id = 0; @@ -932,7 +920,6 @@ TEST_F(BootstrapAbiImplTest, GaugeOperationsInvalidId) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Try to set, increment, and decrement a gauge with an invalid ID. EXPECT_EQ(envoy_dynamic_module_callback_bootstrap_extension_config_set_gauge( config.value()->thisAsVoidPtr(), 999, nullptr, 0, 1), @@ -955,6 +942,7 @@ TEST_F(BootstrapAbiImplTest, DefineAndRecordHistogram) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); + unfreezeStatCreation(*config.value()); // Define a histogram without labels. size_t histogram_id = 0; @@ -985,7 +973,6 @@ TEST_F(BootstrapAbiImplTest, RecordHistogramInvalidId) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Try to record a histogram value with an invalid ID. auto result = envoy_dynamic_module_callback_bootstrap_extension_config_record_histogram_value( config.value()->thisAsVoidPtr(), 999, nullptr, 0, 42); @@ -1002,6 +989,7 @@ TEST_F(BootstrapAbiImplTest, DefineMultipleMetrics) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); + unfreezeStatCreation(*config.value()); // Define multiple counters. size_t counter_id_0 = 0; @@ -1060,6 +1048,7 @@ TEST_F(BootstrapAbiImplTest, DefineAndIncrementCounterVec) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); + unfreezeStatCreation(*config.value()); // Define a counter vec with labels. size_t counter_vec_id = 0; @@ -1091,6 +1080,7 @@ TEST_F(BootstrapAbiImplTest, IncrementCounterVecInvalidLabels) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); + unfreezeStatCreation(*config.value()); // Define a counter vec with 2 labels. size_t counter_vec_id = 0; @@ -1117,6 +1107,7 @@ TEST_F(BootstrapAbiImplTest, DefineAndManipulateGaugeVec) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); + unfreezeStatCreation(*config.value()); // Define a gauge vec with labels. size_t gauge_vec_id = 0; @@ -1155,6 +1146,7 @@ TEST_F(BootstrapAbiImplTest, DefineAndRecordHistogramVec) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); + unfreezeStatCreation(*config.value()); // Define a histogram vec with labels. size_t histogram_vec_id = 0; @@ -1186,6 +1178,7 @@ TEST_F(BootstrapAbiImplTest, VecMetricsInvalidIdAndLabels) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); + unfreezeStatCreation(*config.value()); // Define vec metrics with a single label each. size_t counter_vec_id = 0; @@ -1262,7 +1255,6 @@ TEST_F(BootstrapAbiImplTest, TimerLifecycle) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Create a timer via the ABI callback. auto* timer_ptr = envoy_dynamic_module_callback_bootstrap_extension_timer_new(config.value()->thisAsVoidPtr()); @@ -1296,7 +1288,6 @@ TEST_F(BootstrapAbiImplTest, TimerFired) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Create a timer via the ABI callback. This will use the MockTimer we set up. auto* timer_ptr = envoy_dynamic_module_callback_bootstrap_extension_timer_new(config.value()->thisAsVoidPtr()); @@ -1335,7 +1326,6 @@ TEST_F(BootstrapAbiImplTest, TimerFiredAfterConfigDestroyed) { "test", "config", DefaultMetricsNamespace, std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Create a timer via the ABI callback. timer_ptr = envoy_dynamic_module_callback_bootstrap_extension_timer_new( config.value()->thisAsVoidPtr()); @@ -1372,7 +1362,6 @@ TEST_F(BootstrapAbiImplTest, FileWatcherAddWatch) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Add a watch for a specific path and events. envoy_dynamic_module_type_module_buffer path_buf = {"/tmp/test_file", 14}; bool added = envoy_dynamic_module_callback_bootstrap_extension_file_watcher_add_watch( @@ -1401,7 +1390,6 @@ TEST_F(BootstrapAbiImplTest, FileWatcherFired) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Add a watch to capture the callback. envoy_dynamic_module_type_module_buffer path_buf = {"/tmp/test_file", 14}; bool added = envoy_dynamic_module_callback_bootstrap_extension_file_watcher_add_watch( @@ -1439,7 +1427,6 @@ TEST_F(BootstrapAbiImplTest, FileWatcherFiredAfterConfigDestroyed) { "test", "config", DefaultMetricsNamespace, std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Add a watch to capture the callback. envoy_dynamic_module_type_module_buffer path_buf = {"/tmp/test_file", 14}; bool added = envoy_dynamic_module_callback_bootstrap_extension_file_watcher_add_watch( @@ -1471,7 +1458,6 @@ TEST_F(BootstrapAbiImplTest, FileWatcherAddWatchFails) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Add a watch - should fail and return false. envoy_dynamic_module_type_module_buffer path_buf = {"/tmp/test_file", 14}; bool added = envoy_dynamic_module_callback_bootstrap_extension_file_watcher_add_watch( @@ -1493,7 +1479,6 @@ TEST_F(BootstrapAbiImplTest, RegisterAdminHandler) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Expect the admin handler to be registered. EXPECT_CALL(context_.admin_, addHandler("/test_prefix", "Test help text", _, true, false, _)) .WillOnce(testing::Return(true)); @@ -1515,7 +1500,6 @@ TEST_F(BootstrapAbiImplTest, RegisterAdminHandlerFails) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Expect the admin handler registration to fail. EXPECT_CALL(context_.admin_, addHandler("/duplicate", "Duplicate handler", _, false, true, _)) .WillOnce(testing::Return(false)); @@ -1540,7 +1524,6 @@ TEST_F(BootstrapAbiImplTest, RegisterAdminHandlerNoAdmin) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - envoy_dynamic_module_type_module_buffer path_prefix = {"/no_admin", 9}; envoy_dynamic_module_type_module_buffer help_text = {"No admin", 8}; bool result = envoy_dynamic_module_callback_bootstrap_extension_register_admin_handler( @@ -1558,7 +1541,6 @@ TEST_F(BootstrapAbiImplTest, RemoveAdminHandler) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - EXPECT_CALL(context_.admin_, removeHandler("/test_prefix")).WillOnce(testing::Return(true)); envoy_dynamic_module_type_module_buffer path_prefix = {"/test_prefix", 12}; @@ -1577,7 +1559,6 @@ TEST_F(BootstrapAbiImplTest, RemoveAdminHandlerNotFound) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - EXPECT_CALL(context_.admin_, removeHandler("/nonexistent")).WillOnce(testing::Return(false)); envoy_dynamic_module_type_module_buffer path_prefix = {"/nonexistent", 12}; @@ -1599,7 +1580,6 @@ TEST_F(BootstrapAbiImplTest, RemoveAdminHandlerNoAdmin) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - envoy_dynamic_module_type_module_buffer path_prefix = {"/no_admin", 9}; bool result = envoy_dynamic_module_callback_bootstrap_extension_remove_admin_handler( config.value()->thisAsVoidPtr(), path_prefix); @@ -1616,7 +1596,6 @@ TEST_F(BootstrapAbiImplTest, AdminHandlerCallbackInvokesEventHook) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Capture the handler callback when addHandler is called. Server::Admin::HandlerCb captured_handler; EXPECT_CALL(context_.admin_, addHandler("/test_admin", "Test admin handler", _, true, false, _)) @@ -1664,7 +1643,6 @@ TEST_F(BootstrapAbiImplTest, TimerReEnable) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Create a timer via the ABI callback. auto* timer_ptr = envoy_dynamic_module_callback_bootstrap_extension_timer_new(config.value()->thisAsVoidPtr()); @@ -1692,7 +1670,6 @@ TEST_F(BootstrapAbiImplTest, EnableClusterLifecycle) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Expect the callback registration to go through ClusterManager. EXPECT_CALL(context_.cluster_manager_, addThreadLocalClusterUpdateCallbacks_(_)) .WillOnce(testing::ReturnNew()); @@ -1717,7 +1694,6 @@ TEST_F(BootstrapAbiImplTest, ClusterAddOrUpdateCallback) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Invoke onClusterAddOrUpdate directly on the config to test the callback forwarding. Upstream::ThreadLocalClusterCommand get_cluster = []() -> Upstream::ThreadLocalCluster& { PANIC("should not be called"); @@ -1735,7 +1711,6 @@ TEST_F(BootstrapAbiImplTest, ClusterRemovalCallback) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Invoke onClusterRemoval directly on the config. config.value()->onClusterRemoval("test_cluster"); } @@ -1750,7 +1725,6 @@ TEST_F(BootstrapAbiImplTest, EnableListenerLifecycle) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Simulate server initialization by setting the listener manager. testing::NiceMock listener_manager; config.value()->setListenerManager(listener_manager); @@ -1778,7 +1752,6 @@ TEST_F(BootstrapAbiImplTest, EnableListenerLifecycleBeforeServerInit) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Do not set listener manager - simulate calling before server init. EXPECT_LOG_CONTAINS("error", "cannot enable listener lifecycle before server is initialized", { bool result = envoy_dynamic_module_callback_bootstrap_extension_enable_listener_lifecycle( @@ -1797,7 +1770,6 @@ TEST_F(BootstrapAbiImplTest, ListenerAddOrUpdateCallback) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Invoke onListenerAddOrUpdate directly on the config to test the callback forwarding. NiceMock mock_listener_config; config.value()->onListenerAddOrUpdate("test_listener", mock_listener_config); @@ -1813,11 +1785,99 @@ TEST_F(BootstrapAbiImplTest, ListenerRemovalCallback) { std::move(dynamic_module.value()), dispatcher_, context_, context_.store_); ASSERT_TRUE(config.ok()) << config.status(); - // Invoke onListenerRemoval directly on the config. config.value()->onListenerRemoval("test_listener"); } +// Verifies the factory auto-freezes stat creation so `define_*` returns `Frozen` after init. +TEST_F(BootstrapAbiImplTest, MetricsFrozenAfterInit) { + auto dynamic_module = + Extensions::DynamicModules::newDynamicModule(testDataDir() + "/libbootstrap_no_op.so", false); + ASSERT_TRUE(dynamic_module.ok()) << dynamic_module.status(); + auto config = newDynamicModuleBootstrapExtensionConfig("test", "config", DefaultMetricsNamespace, + std::move(dynamic_module.value()), + dispatcher_, context_, context_.store_); + ASSERT_TRUE(config.ok()) << config.status(); + EXPECT_TRUE(config.value()->stat_creation_frozen_); + + envoy_dynamic_module_type_module_buffer name = {"frozen_counter", 14}; + envoy_dynamic_module_type_module_buffer label = {"label", 5}; + size_t out_id = 0; + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_bootstrap_extension_config_define_counter( + config.value()->thisAsVoidPtr(), name, nullptr, 0, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_bootstrap_extension_config_define_counter( + config.value()->thisAsVoidPtr(), name, &label, 1, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_bootstrap_extension_config_define_gauge( + config.value()->thisAsVoidPtr(), name, nullptr, 0, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_bootstrap_extension_config_define_histogram( + config.value()->thisAsVoidPtr(), name, nullptr, 0, &out_id)); +} + +// Drives concurrent labeled increments from multiple threads to verify no data race in the +// shared `stat_name_pool_`. Run under `--config=tsan` to verify. +TEST_F(BootstrapAbiImplTest, MetricsConcurrentIncrementCounterVecNoRace) { + auto dynamic_module = + Extensions::DynamicModules::newDynamicModule(testDataDir() + "/libbootstrap_no_op.so", false); + ASSERT_TRUE(dynamic_module.ok()) << dynamic_module.status(); + auto config = newDynamicModuleBootstrapExtensionConfig("test", "config", DefaultMetricsNamespace, + std::move(dynamic_module.value()), + dispatcher_, context_, context_.store_); + ASSERT_TRUE(config.ok()) << config.status(); + unfreezeStatCreation(*config.value()); + + envoy_dynamic_module_type_module_buffer name = {"race_counter", 12}; + envoy_dynamic_module_type_module_buffer label_names = {"status", 6}; + size_t counter_id = 0; + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_bootstrap_extension_config_define_counter( + config.value()->thisAsVoidPtr(), name, &label_names, 1, &counter_id)); + + constexpr int kNumThreads = 8; + constexpr int kIncrementsPerThread = 2000; + + // Pre-warm the test scope's counter cache so workers only hit the cache. `TestScope` uses an + // unsynchronized map for counter caching that would otherwise race independently of the path + // under test. + for (int t = 0; t < kNumThreads; ++t) { + const std::string label_value_str = absl::StrCat("worker_", t); + envoy_dynamic_module_type_module_buffer label_value = { + const_cast(label_value_str.data()), label_value_str.size()}; + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_bootstrap_extension_config_increment_counter( + config.value()->thisAsVoidPtr(), counter_id, &label_value, 1, 0)); + } + + std::vector threads; + threads.reserve(kNumThreads); + std::atomic ready{0}; + std::atomic go{false}; + for (int t = 0; t < kNumThreads; ++t) { + threads.emplace_back([&, t]() { + const std::string label_value_str = absl::StrCat("worker_", t); + envoy_dynamic_module_type_module_buffer label_value = { + const_cast(label_value_str.data()), label_value_str.size()}; + ready.fetch_add(1, std::memory_order_relaxed); + while (!go.load(std::memory_order_acquire)) { + } + for (int i = 0; i < kIncrementsPerThread; ++i) { + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_bootstrap_extension_config_increment_counter( + config.value()->thisAsVoidPtr(), counter_id, &label_value, 1, 1)); + } + }); + } + while (ready.load(std::memory_order_acquire) < kNumThreads) { + } + go.store(true, std::memory_order_release); + for (auto& th : threads) { + th.join(); + } +} + } // namespace DynamicModules } // namespace Bootstrap } // namespace Extensions diff --git a/test/extensions/dynamic_modules/http/abi_impl_test.cc b/test/extensions/dynamic_modules/http/abi_impl_test.cc index e0e032b74b4ec..2cd7200241ba0 100644 --- a/test/extensions/dynamic_modules/http/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/http/abi_impl_test.cc @@ -3524,8 +3524,7 @@ class DynamicModuleHttpFilterSchedulerTest : public testing::Test { std::shared_ptr filter_; }; -// Covers the happy path: `commit` posts to the dispatcher cached when decoder callbacks were -// wired. +// Verifies that `commit` posts to the dispatcher cached when decoder callbacks are wired. TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitPostsToWorkerDispatcher) { filter_->setDecoderFilterCallbacks(decoder_callbacks_); @@ -3543,7 +3542,7 @@ TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitPostsToWor envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); } -// Covers the `!filter_shared` early return. +// Verifies that `commit` is a no-op when the filter weak_ptr can no longer be locked. TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitAfterFilterDestroyedIsNoOp) { filter_->setDecoderFilterCallbacks(decoder_callbacks_); @@ -3558,7 +3557,7 @@ TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitAfterFilte envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); } -// Covers the `dispatcher == nullptr` early return after `onDestroy()` clears the cache. +// Verifies that `commit` is a no-op after `onDestroy()` clears the cached dispatcher. TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitAfterOnDestroyIsNoOp) { filter_->setDecoderFilterCallbacks(decoder_callbacks_); @@ -3573,8 +3572,8 @@ TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitAfterOnDes envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); } -// Covers the `dispatcher == nullptr` early return when decoder callbacks have not been wired -// (so the cached dispatcher has not been published). +// Verifies that `commit` is a no-op when decoder callbacks have not been wired and no dispatcher +// has been cached. TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterSchedulerCommitWithoutDecoderCallbacksIsNoOp) { auto* scheduler = envoy_dynamic_module_callback_http_filter_scheduler_new(filterPtr()); @@ -3586,7 +3585,7 @@ TEST_F(DynamicModuleHttpFilterSchedulerTest, envoy_dynamic_module_callback_http_filter_scheduler_delete(scheduler); } -// Covers the happy path for the config scheduler: `commit` posts to the main thread dispatcher. +// Verifies that the config scheduler `commit` posts to the main thread dispatcher. TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterConfigSchedulerCommitPostsToMainDispatcher) { auto* scheduler = envoy_dynamic_module_callback_http_filter_config_scheduler_new(configPtr()); ASSERT_NE(nullptr, scheduler); @@ -3602,7 +3601,7 @@ TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterConfigSchedulerCommitPost envoy_dynamic_module_callback_http_filter_config_scheduler_delete(scheduler); } -// Covers the `!config_shared` early return of the config scheduler. +// Verifies that the config scheduler `commit` is a no-op after the config has been destroyed. TEST_F(DynamicModuleHttpFilterSchedulerTest, HttpFilterConfigSchedulerCommitAfterConfigDestroyedIsNoOp) { auto* scheduler = envoy_dynamic_module_callback_http_filter_config_scheduler_new(configPtr()); diff --git a/test/extensions/dynamic_modules/listener/abi_impl_test.cc b/test/extensions/dynamic_modules/listener/abi_impl_test.cc index a20d40a0a14a8..ffa26586ff59c 100644 --- a/test/extensions/dynamic_modules/listener/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/listener/abi_impl_test.cc @@ -73,6 +73,8 @@ class DynamicModuleListenerFilterAbiCallbackTest : public testing::Test { cluster_manager_, *stats_.rootScope(), main_thread_dispatcher_); EXPECT_TRUE(filter_config_or_status.ok()) << filter_config_or_status.status().message(); filter_config_ = filter_config_or_status.value(); + // Re-open stat creation so tests can call `define_*` from the test thread. + filter_config_->stat_creation_frozen_ = false; ON_CALL(callbacks_, dispatcher()).WillByDefault(testing::ReturnRef(worker_thread_dispatcher_)); @@ -2087,8 +2089,8 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, envoy_dynamic_module_callback_listener_filter_config_scheduler_delete(scheduler); } -// Covers the `dispatcher == nullptr` early return of the listener filter scheduler when the -// filter has not yet received `onAccept` (so the cached dispatcher has not been published). +// Verifies that `commit` is a no-op when `onAccept` has not yet wired callbacks and no dispatcher +// has been cached. TEST_F(DynamicModuleListenerFilterAbiCallbackTest, ListenerFilterSchedulerCommitBeforeOnAcceptIsNoOp) { auto fresh_filter = std::make_shared(filter_config_); @@ -2367,6 +2369,8 @@ class DynamicModuleListenerFilterHttpCalloutTest : public testing::Test { cluster_manager_, *stats_.rootScope(), main_thread_dispatcher_); EXPECT_TRUE(filter_config_or_status.ok()) << filter_config_or_status.status().message(); filter_config_ = filter_config_or_status.value(); + // Re-open stat creation so tests can call `define_*` from the test thread. + filter_config_->stat_creation_frozen_ = false; ON_CALL(callbacks_, dispatcher()).WillByDefault(testing::ReturnRef(worker_thread_dispatcher_)); @@ -2661,7 +2665,7 @@ TEST_F(DynamicModuleListenerFilterHttpCalloutTest, FilterDestructionCancelsPendi filter_.reset(); } -// Covers the `on_listener_filter_http_callout_done_ == nullptr` branch of `onSuccess`. +// Verifies that `onSuccess` is a safe no-op when `on_listener_filter_http_callout_done_` is null. TEST_F(DynamicModuleListenerFilterHttpCalloutTest, HttpCalloutOnSuccessWithoutCalloutDoneHook) { NiceMock cluster; NiceMock async_client; @@ -2699,8 +2703,8 @@ TEST_F(DynamicModuleListenerFilterHttpCalloutTest, HttpCalloutOnSuccessWithoutCa ->onSuccess(request, std::move(response)); } -// Covers the `on_listener_filter_http_callout_done_ == nullptr` branch of `onFailure` along with -// the `ExceedResponseBufferLimit` path of the reason switch. +// Verifies that `onFailure` is a safe no-op when `on_listener_filter_http_callout_done_` is null, +// exercising the `ExceedResponseBufferLimit` path of the reason switch. TEST_F(DynamicModuleListenerFilterHttpCalloutTest, HttpCalloutOnFailureWithoutCalloutDoneHook) { NiceMock cluster; NiceMock async_client; @@ -2735,21 +2739,37 @@ TEST_F(DynamicModuleListenerFilterHttpCalloutTest, HttpCalloutOnFailureWithoutCa ->onFailure(request, Http::AsyncClient::FailureReason::ExceedResponseBufferLimit); } -// Covers `DynamicModuleListenerFilter::onScheduled` when `in_module_filter_` is null. +// Verifies that `onScheduled` is a safe no-op when `in_module_filter_` is null. TEST_F(DynamicModuleListenerFilterAbiCallbackTest, ListenerFilterOnScheduledWithoutInModuleFilterIsNoOp) { auto filter = std::make_shared(filter_config_); filter->onScheduled(42); } -// Covers the `on_listener_filter_config_scheduled_ == nullptr` branch of -// `DynamicModuleListenerFilterConfig::onScheduled`. +// Verifies that `DynamicModuleListenerFilterConfig::onScheduled` is a safe no-op when +// `on_listener_filter_config_scheduled_` is null. TEST_F(DynamicModuleListenerFilterAbiCallbackTest, ListenerFilterConfigOnScheduledWithoutHookIsNoOp) { filter_config_->on_listener_filter_config_scheduled_ = nullptr; filter_config_->onScheduled(42); } +// Verifies the factory auto-freezes stat creation so `define_*` returns `Frozen` after init. +TEST_F(DynamicModuleListenerFilterAbiCallbackTest, MetricsFrozenAfterInit) { + filter_config_->stat_creation_frozen_ = true; + envoy_dynamic_module_type_module_buffer name = {const_cast("frozen_counter"), 14}; + size_t out_id = 0; + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_listener_filter_config_define_counter( + static_cast(filter_config_.get()), name, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_listener_filter_config_define_gauge( + static_cast(filter_config_.get()), name, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_listener_filter_config_define_histogram( + static_cast(filter_config_.get()), name, &out_id)); +} + } // namespace ListenerFilters } // namespace DynamicModules } // namespace Extensions diff --git a/test/extensions/dynamic_modules/listener/filter_test.cc b/test/extensions/dynamic_modules/listener/filter_test.cc index 1abd0c2f8e5ae..299f45ebf6f99 100644 --- a/test/extensions/dynamic_modules/listener/filter_test.cc +++ b/test/extensions/dynamic_modules/listener/filter_test.cc @@ -51,6 +51,8 @@ class DynamicModuleListenerFilterTest : public testing::Test { cluster_manager_, *stats_.rootScope(), main_thread_dispatcher_); EXPECT_TRUE(filter_config_or_status.ok()) << filter_config_or_status.status().message(); filter_config_ = filter_config_or_status.value(); + // Re-open stat creation so tests can call `define_*` from the test thread. + filter_config_->stat_creation_frozen_ = false; ON_CALL(callbacks_, dispatcher()).WillByDefault(testing::ReturnRef(dispatcher)); } diff --git a/test/extensions/dynamic_modules/network/abi_impl_test.cc b/test/extensions/dynamic_modules/network/abi_impl_test.cc index c1bc90fb8e6b3..7e70fe41419eb 100644 --- a/test/extensions/dynamic_modules/network/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/network/abi_impl_test.cc @@ -95,6 +95,8 @@ class DynamicModuleNetworkFilterAbiCallbackTest : public testing::Test { cluster_manager_, *stats_.rootScope(), main_thread_dispatcher_); EXPECT_TRUE(filter_config_or_status.ok()) << filter_config_or_status.status().message(); filter_config_ = filter_config_or_status.value(); + // Re-open stat creation so tests can call `define_*` from the test thread. + filter_config_->stat_creation_frozen_ = false; filter_ = std::make_shared(filter_config_); @@ -1597,6 +1599,8 @@ class DynamicModuleNetworkFilterHttpCalloutTest : public testing::Test { cluster_manager_, *stats_.rootScope(), main_thread_dispatcher_); EXPECT_TRUE(filter_config_or_status.ok()) << filter_config_or_status.status().message(); filter_config_ = filter_config_or_status.value(); + // Re-open stat creation so tests can call `define_*` from the test thread. + filter_config_->stat_creation_frozen_ = false; filter_ = std::make_shared(filter_config_); @@ -2526,6 +2530,22 @@ TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, AboveHighWatermark) { EXPECT_FALSE(envoy_dynamic_module_callback_network_filter_above_high_watermark(filterPtr())); } +// Verifies the factory auto-freezes stat creation so `define_*` returns `Frozen` after init. +TEST_F(DynamicModuleNetworkFilterAbiCallbackTest, MetricsFrozenAfterInit) { + filter_config_->stat_creation_frozen_ = true; + envoy_dynamic_module_type_module_buffer name = {const_cast("frozen_counter"), 14}; + size_t out_id = 0; + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_network_filter_config_define_counter( + static_cast(filter_config_.get()), name, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_network_filter_config_define_gauge( + static_cast(filter_config_.get()), name, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_network_filter_config_define_histogram( + static_cast(filter_config_.get()), name, &out_id)); +} + } // namespace NetworkFilters } // namespace DynamicModules } // namespace Extensions diff --git a/test/extensions/dynamic_modules/network/filter_test.cc b/test/extensions/dynamic_modules/network/filter_test.cc index eaf6b0a344c5c..1499cc03bac11 100644 --- a/test/extensions/dynamic_modules/network/filter_test.cc +++ b/test/extensions/dynamic_modules/network/filter_test.cc @@ -26,6 +26,8 @@ class DynamicModuleNetworkFilterTest : public testing::Test { cluster_manager_, *stats_.rootScope(), main_thread_dispatcher_); EXPECT_TRUE(filter_config_or_status.ok()) << filter_config_or_status.status().message(); filter_config_ = filter_config_or_status.value(); + // Re-open stat creation so tests can call `define_*` from the test thread. + filter_config_->stat_creation_frozen_ = false; ON_CALL(connection_, dispatcher()).WillByDefault(testing::ReturnRef(worker_thread_dispatcher_)); ON_CALL(read_callbacks_, connection()).WillByDefault(testing::ReturnRef(connection_)); diff --git a/test/extensions/dynamic_modules/udp/abi_impl_test.cc b/test/extensions/dynamic_modules/udp/abi_impl_test.cc index 6478884bccfea..9ab706b35a614 100644 --- a/test/extensions/dynamic_modules/udp/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/udp/abi_impl_test.cc @@ -28,6 +28,8 @@ class DynamicModuleUdpListenerFilterAbiCallbackTest : public testing::Test { filter_config_ = std::make_shared( proto_config, std::move(dynamic_module.value()), *stats_.rootScope()); + // Re-open stat creation so tests can call `define_*` from the test thread. + filter_config_->stat_creation_frozen_ = false; filter_ = std::make_shared(callbacks_, filter_config_, 1); } @@ -584,6 +586,22 @@ TEST_F(DynamicModuleUdpListenerFilterAbiCallbackTest, GetWorkerIndex) { EXPECT_EQ(1u, worker_index); } +// Verifies the constructor auto-freezes stat creation so `define_*` returns `Frozen` after init. +TEST_F(DynamicModuleUdpListenerFilterAbiCallbackTest, MetricsFrozenAfterInit) { + filter_config_->stat_creation_frozen_ = true; + envoy_dynamic_module_type_module_buffer name = {const_cast("frozen_counter"), 14}; + size_t out_id = 0; + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_udp_listener_filter_config_define_counter( + static_cast(filter_config_.get()), name, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_udp_listener_filter_config_define_gauge( + static_cast(filter_config_.get()), name, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_udp_listener_filter_config_define_histogram( + static_cast(filter_config_.get()), name, &out_id)); +} + } // namespace DynamicModules } // namespace UdpFilters } // namespace Extensions diff --git a/test/extensions/dynamic_modules/udp/filter_test.cc b/test/extensions/dynamic_modules/udp/filter_test.cc index 0b4273355c335..04c271d094589 100644 --- a/test/extensions/dynamic_modules/udp/filter_test.cc +++ b/test/extensions/dynamic_modules/udp/filter_test.cc @@ -25,6 +25,8 @@ class DynamicModuleUdpListenerFilterTest : public testing::Test { filter_config_ = std::make_shared( proto_config, std::move(dynamic_module.value()), *stats_.rootScope()); + // Re-open stat creation so tests can call `define_*` from the test thread. + filter_config_->stat_creation_frozen_ = false; } Stats::IsolatedStoreImpl stats_; diff --git a/test/extensions/load_balancing_policies/dynamic_modules/config_test.cc b/test/extensions/load_balancing_policies/dynamic_modules/config_test.cc index fde93312414cb..65bc98434e964 100644 --- a/test/extensions/load_balancing_policies/dynamic_modules/config_test.cc +++ b/test/extensions/load_balancing_policies/dynamic_modules/config_test.cc @@ -1,3 +1,6 @@ +#include +#include + #include "envoy/extensions/load_balancing_policies/dynamic_modules/v3/dynamic_modules.pb.h" #include "source/common/upstream/upstream_impl.h" @@ -14,6 +17,7 @@ #include "test/test_common/environment.h" #include "test/test_common/utility.h" +#include "absl/strings/str_cat.h" #include "gtest/gtest.h" namespace Envoy { @@ -217,6 +221,11 @@ class DynamicModulesLoadBalancerTest : public testing::Test { .WillByDefault(ReturnRef(priority_set_.host_sets_)); } + // Re-opens stat creation so tests can call `define_*` from the test thread. + static void unfreezeStatCreation(DynamicModuleLbConfig& config) { + config.stat_creation_frozen_ = false; + } + NiceMock factory_context_; NiceMock cluster_info_; NiceMock priority_set_; @@ -1748,6 +1757,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsCounterDefineAndIncrement) { ASSERT_NE(typed_config, nullptr); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Define a counter (no labels). envoy_dynamic_module_type_module_buffer name = {.ptr = "test_counter", .length = 12}; @@ -1789,6 +1799,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsGaugeDefineAndManipulate) { dynamic_cast(lb_config_or_error.value().get()); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Define a gauge (no labels). envoy_dynamic_module_type_module_buffer name = {.ptr = "test_gauge", .length = 10}; @@ -1833,6 +1844,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsHistogramDefineAndRecord) { dynamic_cast(lb_config_or_error.value().get()); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Define a histogram (no labels). envoy_dynamic_module_type_module_buffer name = {.ptr = "test_histogram", .length = 14}; @@ -1862,6 +1874,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsInvalidId) { dynamic_cast(lb_config_or_error.value().get()); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Using invalid IDs should return MetricNotFound (no labels). EXPECT_EQ( @@ -1915,6 +1928,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsMultipleCounters) { dynamic_cast(lb_config_or_error.value().get()); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Define two counters. envoy_dynamic_module_type_module_buffer name1 = {.ptr = "counter_a", .length = 9}; @@ -1957,6 +1971,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsCounterVecWithLabels) { dynamic_cast(lb_config_or_error.value().get()); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Define a counter vec with two labels. envoy_dynamic_module_type_module_buffer name = {.ptr = "req_total", .length = 9}; @@ -2003,6 +2018,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsGaugeVecWithLabels) { dynamic_cast(lb_config_or_error.value().get()); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Define a gauge vec with one label. envoy_dynamic_module_type_module_buffer name = {.ptr = "active_conns", .length = 12}; @@ -2045,6 +2061,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsHistogramVecWithLabels) { dynamic_cast(lb_config_or_error.value().get()); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Define a histogram vec with one label. envoy_dynamic_module_type_module_buffer name = {.ptr = "latency", .length = 7}; @@ -2088,6 +2105,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsVecScalarIdConflictErrors) { dynamic_cast(lb_config_or_error.value().get()); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Define a counter vec (ID 1 in vec space). envoy_dynamic_module_type_module_buffer counter_name = {.ptr = "cv", .length = 2}; @@ -2148,6 +2166,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsVecWrongLabelCount) { dynamic_cast(lb_config_or_error.value().get()); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Define a gauge vec with one label. envoy_dynamic_module_type_module_buffer gauge_name = {.ptr = "gwl", .length = 3}; @@ -2185,6 +2204,7 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsVecNotFoundWithLabels) { dynamic_cast(lb_config_or_error.value().get()); auto lb_config = typed_config->config(); auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); // Using non-existent vec IDs with labels should return MetricNotFound. envoy_dynamic_module_type_module_buffer label_val = {.ptr = "val", .length = 3}; @@ -2196,6 +2216,127 @@ TEST_F(DynamicModulesLoadBalancerTest, MetricsVecNotFoundWithLabels) { envoy_dynamic_module_callback_lb_config_decrement_gauge(config_ptr, 999, &label_val, 1, 5)); } +// Verifies the factory auto-freezes stat creation so `define_*` returns `Frozen` after init. +TEST_F(DynamicModulesLoadBalancerTest, MetricsFrozenAfterInit) { + envoy::extensions::load_balancing_policies::dynamic_modules::v3::DynamicModulesLoadBalancerConfig + config; + config.mutable_dynamic_module_config()->set_name("lb_round_robin"); + config.set_lb_policy_name("test_lb"); + + Factory factory; + auto lb_config_or_error = factory.loadConfig(factory_context_, config); + ASSERT_TRUE(lb_config_or_error.ok()); + + auto* typed_config = + dynamic_cast(lb_config_or_error.value().get()); + ASSERT_NE(typed_config, nullptr); + auto lb_config = typed_config->config(); + auto* config_ptr = static_cast(lb_config.get()); + EXPECT_TRUE(lb_config->stat_creation_frozen_); + + envoy_dynamic_module_type_module_buffer name = {.ptr = "frozen_counter", .length = 14}; + envoy_dynamic_module_type_module_buffer label_name = {.ptr = "label", .length = 5}; + size_t out_id = 0; + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_lb_config_define_counter(config_ptr, name, nullptr, 0, + &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_lb_config_define_counter(config_ptr, name, &label_name, 1, + &out_id)); + EXPECT_EQ( + envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_lb_config_define_gauge(config_ptr, name, nullptr, 0, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_lb_config_define_gauge(config_ptr, name, &label_name, 1, + &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_lb_config_define_histogram(config_ptr, name, nullptr, 0, + &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_lb_config_define_histogram(config_ptr, name, &label_name, + 1, &out_id)); +} + +// Drives concurrent labeled increments from multiple threads to verify no data race in the +// shared `stat_name_pool_`. Run under `--config=tsan` to verify. +TEST_F(DynamicModulesLoadBalancerTest, MetricsConcurrentIncrementCounterVecNoRace) { + envoy::extensions::load_balancing_policies::dynamic_modules::v3::DynamicModulesLoadBalancerConfig + config; + config.mutable_dynamic_module_config()->set_name("lb_round_robin"); + config.set_lb_policy_name("test_lb"); + + Factory factory; + auto lb_config_or_error = factory.loadConfig(factory_context_, config); + ASSERT_TRUE(lb_config_or_error.ok()); + + auto* typed_config = + dynamic_cast(lb_config_or_error.value().get()); + ASSERT_NE(typed_config, nullptr); + auto lb_config = typed_config->config(); + auto* config_ptr = static_cast(lb_config.get()); + unfreezeStatCreation(*lb_config); + + envoy_dynamic_module_type_module_buffer name = {.ptr = "race_counter", .length = 12}; + std::string label_name_str = "status"; + envoy_dynamic_module_type_module_buffer label_names[1] = { + {.ptr = const_cast(label_name_str.data()), .length = label_name_str.size()}}; + size_t counter_id = 0; + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_lb_config_define_counter(config_ptr, name, label_names, 1, + &counter_id)); + + constexpr int kNumThreads = 8; + constexpr int kIncrementsPerThread = 2000; + + // Pre-warm the test scope's counter cache so workers only hit the cache. `TestScope` uses an + // unsynchronized map for counter caching that would otherwise race independently of the path + // under test. + for (int t = 0; t < kNumThreads; ++t) { + const std::string label_value_str = absl::StrCat("worker_", t); + envoy_dynamic_module_type_module_buffer label_value = { + .ptr = const_cast(label_value_str.data()), .length = label_value_str.size()}; + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_lb_config_increment_counter(config_ptr, counter_id, + &label_value, 1, 0)); + } + + std::vector threads; + threads.reserve(kNumThreads); + std::atomic ready{0}; + std::atomic go{false}; + for (int t = 0; t < kNumThreads; ++t) { + threads.emplace_back([&, t]() { + const std::string label_value_str = absl::StrCat("worker_", t); + envoy_dynamic_module_type_module_buffer label_value = { + .ptr = const_cast(label_value_str.data()), .length = label_value_str.size()}; + ready.fetch_add(1, std::memory_order_relaxed); + while (!go.load(std::memory_order_acquire)) { + } + for (int i = 0; i < kIncrementsPerThread; ++i) { + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_lb_config_increment_counter(config_ptr, counter_id, + &label_value, 1, 1)); + } + }); + } + while (ready.load(std::memory_order_acquire) < kNumThreads) { + } + go.store(true, std::memory_order_release); + for (auto& th : threads) { + th.join(); + } + + uint64_t total = 0; + for (int t = 0; t < kNumThreads; ++t) { + auto counter = TestUtility::findCounter( + factory_context_.store_, + absl::StrCat("dynamicmodulescustom.race_counter.status.worker_", t)); + ASSERT_NE(nullptr, counter) << "missing counter for worker_" << t; + total += counter->value(); + } + EXPECT_EQ(static_cast(kNumThreads) * kIncrementsPerThread, total); +} + } // namespace } // namespace DynamicModules } // namespace LoadBalancingPolicies diff --git a/test/extensions/tracers/dynamic_modules/abi_impl_test.cc b/test/extensions/tracers/dynamic_modules/abi_impl_test.cc index 34be2064665b4..f3505d802e1ca 100644 --- a/test/extensions/tracers/dynamic_modules/abi_impl_test.cc +++ b/test/extensions/tracers/dynamic_modules/abi_impl_test.cc @@ -1,3 +1,6 @@ +#include +#include + #include "source/extensions/dynamic_modules/abi/abi.h" #include "source/extensions/tracers/dynamic_modules/tracer_config.h" @@ -6,6 +9,7 @@ #include "test/test_common/environment.h" #include "test/test_common/utility.h" +#include "absl/strings/str_cat.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -35,6 +39,8 @@ class AbiImplTest : public ::testing::Test { std::move(module.value()), *store_.rootScope()); EXPECT_TRUE(config_or.ok()); config_ = config_or.value(); + // Re-open stat creation so tests can call `define_*` from the test thread. + config_->stat_creation_frozen_ = false; driver_ = std::make_shared(config_); } @@ -518,6 +524,80 @@ TEST_F(AbiImplTest, RecordHistogramVecNotFound) { EXPECT_EQ(result, envoy_dynamic_module_type_metrics_result_MetricNotFound); } +// Verifies the factory auto-freezes stat creation so `define_*` returns `Frozen` after init. +TEST_F(AbiImplTest, MetricsFrozenAfterInit) { + config_->stat_creation_frozen_ = true; + envoy_dynamic_module_type_module_buffer name = {.ptr = "frozen_counter", .length = 14}; + envoy_dynamic_module_type_module_buffer label_name = {.ptr = "label", .length = 5}; + size_t out_id = 0; + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_tracer_define_counter(static_cast(config_.get()), + name, nullptr, 0, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_tracer_define_counter(static_cast(config_.get()), + name, &label_name, 1, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_tracer_define_gauge(static_cast(config_.get()), + name, nullptr, 0, &out_id)); + EXPECT_EQ(envoy_dynamic_module_type_metrics_result_Frozen, + envoy_dynamic_module_callback_tracer_define_histogram(static_cast(config_.get()), + name, nullptr, 0, &out_id)); +} + +// Drives concurrent labeled increments from multiple threads to verify no data race in the +// shared `stat_name_pool_`. Run under `--config=tsan` to verify. +TEST_F(AbiImplTest, MetricsConcurrentIncrementCounterVecNoRace) { + envoy_dynamic_module_type_module_buffer name = {.ptr = "race_counter", .length = 12}; + std::string label_name_str = "status"; + envoy_dynamic_module_type_module_buffer label_names[1] = { + {.ptr = const_cast(label_name_str.data()), .length = label_name_str.size()}}; + size_t counter_id = 0; + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_tracer_define_counter(static_cast(config_.get()), + name, label_names, 1, &counter_id)); + + constexpr int kNumThreads = 8; + constexpr int kIncrementsPerThread = 2000; + + // Pre-warm the test scope's counter cache so workers only hit the cache. `TestScope` uses an + // unsynchronized map for counter caching that would otherwise race independently of the path + // under test. + for (int t = 0; t < kNumThreads; ++t) { + const std::string label_value_str = absl::StrCat("worker_", t); + envoy_dynamic_module_type_module_buffer label_value = { + .ptr = const_cast(label_value_str.data()), .length = label_value_str.size()}; + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_tracer_increment_counter( + static_cast(config_.get()), counter_id, &label_value, 1, 0)); + } + + std::vector threads; + threads.reserve(kNumThreads); + std::atomic ready{0}; + std::atomic go{false}; + for (int t = 0; t < kNumThreads; ++t) { + threads.emplace_back([&, t]() { + const std::string label_value_str = absl::StrCat("worker_", t); + envoy_dynamic_module_type_module_buffer label_value = { + .ptr = const_cast(label_value_str.data()), .length = label_value_str.size()}; + ready.fetch_add(1, std::memory_order_relaxed); + while (!go.load(std::memory_order_acquire)) { + } + for (int i = 0; i < kIncrementsPerThread; ++i) { + ASSERT_EQ(envoy_dynamic_module_type_metrics_result_Success, + envoy_dynamic_module_callback_tracer_increment_counter( + static_cast(config_.get()), counter_id, &label_value, 1, 1)); + } + }); + } + while (ready.load(std::memory_order_acquire) < kNumThreads) { + } + go.store(true, std::memory_order_release); + for (auto& th : threads) { + th.join(); + } +} + } // namespace } // namespace DynamicModules } // namespace Tracers From 02a2fe4fa9cf9b088f41c8a50f0379b0161ee9c1 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Tue, 5 May 2026 15:11:50 +0200 Subject: [PATCH 213/750] dynamic_modules: replace debug-only ASSERTs with release-mode thread guards on ABI entries (#44843) --- .../bootstrap/dynamic_modules/abi_impl.cc | 14 +-- .../clusters/dynamic_modules/abi_impl.cc | 24 +++++ source/extensions/dynamic_modules/abi_impl.cc | 24 ++++- .../clusters/dynamic_modules/cluster_test.cc | 88 +++++++++++++++++++ .../dynamic_modules/abi_impl_test.cc | 56 ++++++++++++ .../bootstrap/abi_impl_test.cc | 14 +++ 6 files changed, 213 insertions(+), 7 deletions(-) diff --git a/source/extensions/bootstrap/dynamic_modules/abi_impl.cc b/source/extensions/bootstrap/dynamic_modules/abi_impl.cc index a751421c2f753..19f5e477186a4 100644 --- a/source/extensions/bootstrap/dynamic_modules/abi_impl.cc +++ b/source/extensions/bootstrap/dynamic_modules/abi_impl.cc @@ -507,11 +507,15 @@ bool envoy_dynamic_module_callback_bootstrap_extension_timer_enabled( void envoy_dynamic_module_callback_bootstrap_extension_timer_delete( envoy_dynamic_module_type_bootstrap_extension_timer_module_ptr timer_ptr) { using namespace Envoy; - // The underlying `Event::Timer` `deregisters` from the dispatcher's timer list in its - // destructor, which is only safe on the dispatcher thread. Callers that hold the timer handle - // from a context that may be dropped off the main thread must route deletion through the - // scheduler ABI. - ASSERT_IS_MAIN_OR_TEST_THREAD(); + // The underlying `Event::Timer` is removed from the dispatcher's timer list in its destructor, + // which is only safe on the dispatcher thread. The previous `ASSERT_IS_MAIN_OR_TEST_THREAD` is + // compiled out under NDEBUG, so guard explicitly and skip the deletion to avoid corrupting the + // timer list. + if (!Thread::MainThread::isMainOrTestThread()) { + IS_ENVOY_BUG("envoy_dynamic_module_callback_bootstrap_extension_timer_delete must be called " + "on the main thread"); + return; + } delete static_cast(timer_ptr); } diff --git a/source/extensions/clusters/dynamic_modules/abi_impl.cc b/source/extensions/clusters/dynamic_modules/abi_impl.cc index 483c3a9072134..13bc58819c8ca 100644 --- a/source/extensions/clusters/dynamic_modules/abi_impl.cc +++ b/source/extensions/clusters/dynamic_modules/abi_impl.cc @@ -3,6 +3,7 @@ // This file provides host-side implementations for the cluster dynamic module ABI callbacks. #include "source/common/common/assert.h" +#include "source/common/common/thread.h" #include "source/common/http/message_impl.h" #include "source/extensions/clusters/dynamic_modules/cluster.h" #include "source/extensions/dynamic_modules/abi/abi.h" @@ -92,6 +93,14 @@ bool envoy_dynamic_module_callback_cluster_add_hosts( const envoy_dynamic_module_type_module_buffer* sub_zones, const envoy_dynamic_module_type_module_buffer* metadata_pairs, size_t metadata_pairs_per_host, size_t count, envoy_dynamic_module_type_cluster_host_envoy_ptr* result_host_ptrs) { + // `cluster_add_hosts` mutates `priority_set_` and runs member-update callbacks; both are + // main-thread-only. The previous `ASSERT_IS_MAIN_OR_TEST_THREAD` is compiled out under NDEBUG, + // so guard explicitly and fail closed. + if (!Envoy::Thread::MainThread::isMainOrTestThread()) { + IS_ENVOY_BUG( + "envoy_dynamic_module_callback_cluster_add_hosts must be called on the main thread"); + return false; + } auto* cluster = getCluster(cluster_envoy_ptr); std::vector address_strings; address_strings.reserve(count); @@ -140,6 +149,11 @@ bool envoy_dynamic_module_callback_cluster_add_hosts( size_t envoy_dynamic_module_callback_cluster_remove_hosts( envoy_dynamic_module_type_cluster_envoy_ptr cluster_envoy_ptr, const envoy_dynamic_module_type_cluster_host_envoy_ptr* host_envoy_ptrs, size_t count) { + if (!Envoy::Thread::MainThread::isMainOrTestThread()) { + IS_ENVOY_BUG( + "envoy_dynamic_module_callback_cluster_remove_hosts must be called on the main thread"); + return 0; + } auto* cluster = getCluster(cluster_envoy_ptr); std::vector hosts; hosts.reserve(count); @@ -153,6 +167,11 @@ bool envoy_dynamic_module_callback_cluster_update_host_health( envoy_dynamic_module_type_cluster_envoy_ptr cluster_envoy_ptr, envoy_dynamic_module_type_cluster_host_envoy_ptr host_envoy_ptr, envoy_dynamic_module_type_host_health health_status) { + if (!Envoy::Thread::MainThread::isMainOrTestThread()) { + IS_ENVOY_BUG("envoy_dynamic_module_callback_cluster_update_host_health must be called on the " + "main thread"); + return false; + } auto* cluster = getCluster(cluster_envoy_ptr); auto host = cluster->findHost(host_envoy_ptr); return cluster->updateHostHealth(std::move(host), health_status); @@ -173,6 +192,11 @@ envoy_dynamic_module_callback_cluster_find_host_by_address( void envoy_dynamic_module_callback_cluster_pre_init_complete( envoy_dynamic_module_type_cluster_envoy_ptr cluster_envoy_ptr) { + if (!Envoy::Thread::MainThread::isMainOrTestThread()) { + IS_ENVOY_BUG("envoy_dynamic_module_callback_cluster_pre_init_complete must be called on the " + "main thread"); + return; + } getCluster(cluster_envoy_ptr)->preInitComplete(); } diff --git a/source/extensions/dynamic_modules/abi_impl.cc b/source/extensions/dynamic_modules/abi_impl.cc index 23abb8e6a2b5a..062c8c8c1d15e 100644 --- a/source/extensions/dynamic_modules/abi_impl.cc +++ b/source/extensions/dynamic_modules/abi_impl.cc @@ -69,15 +69,35 @@ void envoy_dynamic_module_callback_log(envoy_dynamic_module_type_log_level level uint32_t envoy_dynamic_module_callback_get_concurrency() { using namespace Envoy; - ASSERT_IS_MAIN_OR_TEST_THREAD(); + // The previous `ASSERT_IS_MAIN_OR_TEST_THREAD` is compiled out under NDEBUG and the + // `getExisting()` thread-local lookup returns nullptr off the main thread, so guard explicitly + // and fail closed. + if (!Thread::MainThread::isMainOrTestThread()) { + IS_ENVOY_BUG("envoy_dynamic_module_callback_get_concurrency must be called on the main thread"); + return 0; + } auto context = Server::Configuration::ServerFactoryContextInstance::getExisting(); + if (context == nullptr) { + IS_ENVOY_BUG("envoy_dynamic_module_callback_get_concurrency called before the server context " + "was initialized"); + return 0; + } return context->options().concurrency(); } bool envoy_dynamic_module_callback_is_validation_mode() { using namespace Envoy; - ASSERT_IS_MAIN_OR_TEST_THREAD(); + if (!Thread::MainThread::isMainOrTestThread()) { + IS_ENVOY_BUG( + "envoy_dynamic_module_callback_is_validation_mode must be called on the main thread"); + return false; + } auto context = Server::Configuration::ServerFactoryContextInstance::getExisting(); + if (context == nullptr) { + IS_ENVOY_BUG("envoy_dynamic_module_callback_is_validation_mode called before the server " + "context was initialized"); + return false; + } return context->options().mode() == Server::Mode::Validate; } diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index c453eac87eb14..a8e66e37d3cfb 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -3271,6 +3271,94 @@ TEST_F(DynamicModuleClusterTest, LbRepeatedConstructTeardownWithUpdates) { } } +// ============================================================================= +// Off-main-thread guard tests for cluster ABI callbacks. +// ============================================================================= + +// Verifies that `cluster_add_hosts` is fail-closed when called off the main thread. +TEST_F(DynamicModuleClusterTest, AddHostsOffMainThreadFailsClosed) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()) << result.status().message(); + auto cluster = std::dynamic_pointer_cast(result->first); + ASSERT_NE(nullptr, cluster); + + envoy_dynamic_module_type_module_buffer addr = {"127.0.0.1:10001", 15}; + uint32_t weight = 1; + envoy_dynamic_module_type_module_buffer empty = {"", 0}; + envoy_dynamic_module_type_cluster_host_envoy_ptr host_ptr = nullptr; + void* cluster_ptr = cluster.get(); + + EXPECT_ENVOY_BUG( + { + std::thread t([&] { + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_add_hosts( + cluster_ptr, 0, &addr, &weight, &empty, &empty, &empty, nullptr, 0, 1, &host_ptr)); + }); + t.join(); + }, + "envoy_dynamic_module_callback_cluster_add_hosts must be called on the main thread"); +} + +// Verifies that `cluster_remove_hosts` is fail-closed when called off the main thread. +TEST_F(DynamicModuleClusterTest, RemoveHostsOffMainThreadFailsClosed) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()) << result.status().message(); + auto cluster = std::dynamic_pointer_cast(result->first); + ASSERT_NE(nullptr, cluster); + + envoy_dynamic_module_type_cluster_host_envoy_ptr host_ptrs[1] = {nullptr}; + void* cluster_ptr = cluster.get(); + + EXPECT_ENVOY_BUG( + { + std::thread t([&] { + EXPECT_EQ(0u, + envoy_dynamic_module_callback_cluster_remove_hosts(cluster_ptr, host_ptrs, 1)); + }); + t.join(); + }, + "envoy_dynamic_module_callback_cluster_remove_hosts must be called on the main thread"); +} + +// Verifies that `cluster_update_host_health` is fail-closed when called off the main thread. +TEST_F(DynamicModuleClusterTest, UpdateHostHealthOffMainThreadFailsClosed) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()) << result.status().message(); + auto cluster = std::dynamic_pointer_cast(result->first); + ASSERT_NE(nullptr, cluster); + + void* cluster_ptr = cluster.get(); + + EXPECT_ENVOY_BUG( + { + std::thread t([&] { + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_update_host_health( + cluster_ptr, nullptr, envoy_dynamic_module_type_host_health_Healthy)); + }); + t.join(); + }, + "envoy_dynamic_module_callback_cluster_update_host_health must be called on the main " + "thread"); +} + +// Verifies that `cluster_pre_init_complete` is a safe no-op when called off the main thread. +TEST_F(DynamicModuleClusterTest, PreInitCompleteOffMainThreadFailsClosed) { + auto result = createCluster(makeYamlConfig("cluster_no_op")); + ASSERT_TRUE(result.ok()) << result.status().message(); + auto cluster = std::dynamic_pointer_cast(result->first); + ASSERT_NE(nullptr, cluster); + + void* cluster_ptr = cluster.get(); + + EXPECT_ENVOY_BUG( + { + std::thread t( + [&] { envoy_dynamic_module_callback_cluster_pre_init_complete(cluster_ptr); }); + t.join(); + }, + "envoy_dynamic_module_callback_cluster_pre_init_complete must be called on the main thread"); +} + // Verifies the factory auto-freezes stat creation so `define_*` returns `Frozen` after init. TEST_F(DynamicModuleClusterTest, MetricsFrozenAfterInit) { auto result = createCluster(makeYamlConfig("cluster_no_op")); diff --git a/test/extensions/dynamic_modules/abi_impl_test.cc b/test/extensions/dynamic_modules/abi_impl_test.cc index 4f94d6cbe08d5..7e0ad7658498c 100644 --- a/test/extensions/dynamic_modules/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/abi_impl_test.cc @@ -1,3 +1,5 @@ +#include + #include "source/extensions/dynamic_modules/abi/abi.h" #include "test/mocks/server/server_factory_context.h" @@ -40,6 +42,60 @@ TEST(CommonAbiImplTest, IsValidationModeReturnsFalseInInitOnlyMode) { EXPECT_FALSE(envoy_dynamic_module_callback_is_validation_mode()); } +// Verifies that `is_validation_mode` is fail-closed when called off the main thread. +TEST(CommonAbiImplTest, IsValidationModeOffMainThreadFailsClosed) { + testing::NiceMock context; + ON_CALL(context.options_, mode()).WillByDefault(Return(Server::Mode::Validate)); + ScopedThreadLocalServerContextSetter setter(context); + EXPECT_ENVOY_BUG( + { + std::thread t([] { EXPECT_FALSE(envoy_dynamic_module_callback_is_validation_mode()); }); + t.join(); + }, + "envoy_dynamic_module_callback_is_validation_mode must be called on the main thread"); +} + +// Verifies that `is_validation_mode` is fail-closed when called before the server context is +// installed. +TEST(CommonAbiImplTest, IsValidationModeBeforeServerContextFailsClosed) { + EXPECT_ENVOY_BUG(EXPECT_FALSE(envoy_dynamic_module_callback_is_validation_mode()), + "envoy_dynamic_module_callback_is_validation_mode called before the server " + "context was initialized"); +} + +// ============================================================================= +// Concurrency Tests +// ============================================================================= + +TEST(CommonAbiImplTest, GetConcurrencyReturnsConfiguredValue) { + testing::NiceMock context; + ON_CALL(context.options_, concurrency()).WillByDefault(Return(4)); + + ScopedThreadLocalServerContextSetter setter(context); + EXPECT_EQ(4u, envoy_dynamic_module_callback_get_concurrency()); +} + +// Verifies that `get_concurrency` is fail-closed when called off the main thread. +TEST(CommonAbiImplTest, GetConcurrencyOffMainThreadFailsClosed) { + testing::NiceMock context; + ON_CALL(context.options_, concurrency()).WillByDefault(Return(4)); + ScopedThreadLocalServerContextSetter setter(context); + EXPECT_ENVOY_BUG( + { + std::thread t([] { EXPECT_EQ(0u, envoy_dynamic_module_callback_get_concurrency()); }); + t.join(); + }, + "envoy_dynamic_module_callback_get_concurrency must be called on the main thread"); +} + +// Verifies that `get_concurrency` is fail-closed when called before the server context is +// installed. +TEST(CommonAbiImplTest, GetConcurrencyBeforeServerContextFailsClosed) { + EXPECT_ENVOY_BUG(EXPECT_EQ(0u, envoy_dynamic_module_callback_get_concurrency()), + "envoy_dynamic_module_callback_get_concurrency called before the server " + "context was initialized"); +} + // ============================================================================= // Function Registry Tests // ============================================================================= diff --git a/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc b/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc index dd2403c07b3c1..13fa468d6b3f3 100644 --- a/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/bootstrap/abi_impl_test.cc @@ -1304,6 +1304,20 @@ TEST_F(BootstrapAbiImplTest, TimerFired) { envoy_dynamic_module_callback_bootstrap_extension_timer_delete(timer_ptr); } +// Verifies that `bootstrap_extension_timer_delete` is a safe no-op when called off the main +// thread. The guard short-circuits before any pointer dereference, so passing a null timer is +// safe here. +TEST_F(BootstrapAbiImplTest, TimerDeleteOffMainThreadFailsClosed) { + EXPECT_ENVOY_BUG( + { + std::thread t( + [] { envoy_dynamic_module_callback_bootstrap_extension_timer_delete(nullptr); }); + t.join(); + }, + "envoy_dynamic_module_callback_bootstrap_extension_timer_delete must be called on the main " + "thread"); +} + // Test that the timer callback safely handles a destroyed config via weak_ptr. TEST_F(BootstrapAbiImplTest, TimerFiredAfterConfigDestroyed) { Event::TimerCb captured_timer_cb; From 217271b658685006c136c3d62fe6abd29f08054b Mon Sep 17 00:00:00 2001 From: Guy Daich Date: Tue, 5 May 2026 11:44:50 -0500 Subject: [PATCH 214/750] feat(access-log): support upstream_server_name (#44798) Added ``%UPSTREAM_SERVER_NAME%`` access log formatter and ``upstream.server_name`` attribute returning the SNI from the established upstream TLS connection. Risk Level: low Testing: unit Docs Changes: yes Release Notes: yes Platform Specific Features: No Fixes #44797 Signed-off-by: Guy Daich --- changelogs/current.yaml | 8 ++++ .../advanced/substitution_formatter.rst | 8 ++++ .../arch_overview/advanced/attributes.rst | 1 + .../common/formatter/stream_info_formatter.cc | 19 +++++++++ .../extensions/filters/common/expr/context.cc | 12 +++++- .../extensions/filters/common/expr/context.h | 1 + .../formatter/substitution_formatter_test.cc | 35 +++++++++++++++++ .../filters/common/expr/context_test.cc | 39 +++++++++++++++++++ 8 files changed, 122 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 0fc8514884f33..9444449debe6f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -138,5 +138,13 @@ new_features: from validated ones. The header is not set on a successfully verified JWT or when no JWT is present. This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.jwt_authn_add_verification_status_header`` to ``false``. +- area: access_log + change: | + Added ``%UPSTREAM_SERVER_NAME%`` access log formatter returning the SNI from the established upstream + TLS connection. +- area: attributes + change: | + Added ``upstream.server_name`` CEL attribute returning the SNI from the established upstream + TLS connection. deprecated: diff --git a/docs/root/configuration/advanced/substitution_formatter.rst b/docs/root/configuration/advanced/substitution_formatter.rst index 89ad4f8b0ff06..b5b7243d83810 100644 --- a/docs/root/configuration/advanced/substitution_formatter.rst +++ b/docs/root/configuration/advanced/substitution_formatter.rst @@ -1452,6 +1452,14 @@ Current supported substitution commands include: UDP Not implemented. It will appear as ``"-"`` in the access logs. +.. _config_access_log_format_upstream_server_name: + +``%UPSTREAM_SERVER_NAME%`` + HTTP/TCP/THRIFT + The TLS SNI value used to establish the upstream TLS connection. + UDP + Not implemented. It will appear as ``"-"`` in the access logs. + ``%UPSTREAM_PEER_CERT%`` HTTP/TCP/THRIFT The server certificate in the URL-encoded PEM format used to establish the upstream TLS connection. diff --git a/docs/root/intro/arch_overview/advanced/attributes.rst b/docs/root/intro/arch_overview/advanced/attributes.rst index 286caa3ea1549..27a28b4db07ea 100644 --- a/docs/root/intro/arch_overview/advanced/attributes.rst +++ b/docs/root/intro/arch_overview/advanced/attributes.rst @@ -160,6 +160,7 @@ The following attributes are available once the upstream connection is establish upstream.request_attempt_count, uint, The count of upstream request attempts. A value of ‘0’ indicates that the request was never attempted upstream upstream.cx_pool_ready_duration, duration, Total duration from when the upstream request was created to when the upstream connection pool is ready upstream.locality, :ref:`Locality`, Locality information of upstream host + upstream.server_name, string, The SNI used for the upstream TLS connection Metadata and filter state ------------------------- diff --git a/source/common/formatter/stream_info_formatter.cc b/source/common/formatter/stream_info_formatter.cc index 09f3c6ce56a9d..d12d794d5deb9 100644 --- a/source/common/formatter/stream_info_formatter.cc +++ b/source/common/formatter/stream_info_formatter.cc @@ -1664,6 +1664,25 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide return connection_info.sessionId(); }); }}}, + {"UPSTREAM_SERVER_NAME", + {CommandSyntaxChecker::COMMAND_ONLY, + [](absl::string_view, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) + -> absl::optional { + if (stream_info.upstreamInfo() && + stream_info.upstreamInfo()->upstreamSslConnection() != + nullptr) { + auto sni = stream_info.upstreamInfo() + ->upstreamSslConnection() + ->sni(); + if (!sni.empty()) { + return std::string(sni); + } + } + return absl::nullopt; + }); + }}}, {"UPSTREAM_PEER_ISSUER", {CommandSyntaxChecker::COMMAND_ONLY, [](absl::string_view, absl::optional) { diff --git a/source/extensions/filters/common/expr/context.cc b/source/extensions/filters/common/expr/context.cc index e5e8d851ee2e0..37fad4180b363 100644 --- a/source/extensions/filters/common/expr/context.cc +++ b/source/extensions/filters/common/expr/context.cc @@ -422,12 +422,22 @@ const UpstreamLookupValues& UpstreamLookupValues::get() { [](const UpstreamWrapper& wrapper) -> absl::optional { return CelValue::CreateUint64(wrapper.info_.attemptCount().value_or(0)); }}, - {UpstreamNumEndpoints, [](const UpstreamWrapper& wrapper) -> absl::optional { + {UpstreamNumEndpoints, + [](const UpstreamWrapper& wrapper) -> absl::optional { if (const auto cluster_info = wrapper.info_.upstreamClusterInfo()) { return CelValue::CreateUint64( cluster_info->endpointStats().membership_total_.value()); } return {}; + }}, + {UpstreamServerName, [](const UpstreamWrapper& wrapper) -> absl::optional { + if (wrapper.info_.upstreamInfo().has_value()) { + auto ssl_info = wrapper.info_.upstreamInfo().value().get().upstreamSslConnection(); + if (ssl_info != nullptr && !ssl_info->sni().empty()) { + return CelValue::CreateString(&ssl_info->sni()); + } + } + return {}; }}}); } diff --git a/source/extensions/filters/common/expr/context.h b/source/extensions/filters/common/expr/context.h index dbe3f05f3b846..bca62b0f78254 100644 --- a/source/extensions/filters/common/expr/context.h +++ b/source/extensions/filters/common/expr/context.h @@ -90,6 +90,7 @@ constexpr absl::string_view UpstreamTransportFailureReason = "transport_failure_ constexpr absl::string_view UpstreamRequestAttemptCount = "request_attempt_count"; constexpr absl::string_view UpstreamConnectionPoolReadyDuration = "cx_pool_ready_duration"; constexpr absl::string_view UpstreamNumEndpoints = "num_endpoints"; +constexpr absl::string_view UpstreamServerName = "server_name"; // xDS configuration context properties constexpr absl::string_view XDS = "xds"; diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index 85d7a98386209..ead015dd71568 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -2720,6 +2720,41 @@ TEST(SubstitutionFormatterTest, streamInfoFormatterWithSsl) { EXPECT_EQ(absl::nullopt, upstream_format.format({}, stream_info)); EXPECT_THAT(upstream_format.formatValue({}, stream_info), ProtoEq(ValueUtil::nullValue())); } + { + NiceMock stream_info; + StreamInfoFormatter upstream_format("UPSTREAM_SERVER_NAME"); + EXPECT_CALL(stream_info, upstreamInfo()).WillRepeatedly(Return(nullptr)); + + EXPECT_EQ(absl::nullopt, upstream_format.format({}, stream_info)); + EXPECT_THAT(upstream_format.formatValue({}, stream_info), ProtoEq(ValueUtil::nullValue())); + } + { + NiceMock stream_info; + stream_info.upstreamInfo()->setUpstreamSslConnection(nullptr); + StreamInfoFormatter upstream_format("UPSTREAM_SERVER_NAME"); + EXPECT_EQ(absl::nullopt, upstream_format.format({}, stream_info)); + EXPECT_THAT(upstream_format.formatValue({}, stream_info), ProtoEq(ValueUtil::nullValue())); + } + { + NiceMock stream_info; + StreamInfoFormatter upstream_format("UPSTREAM_SERVER_NAME"); + auto connection_info = std::make_shared(); + const std::string sni = "example.com"; + EXPECT_CALL(*connection_info, sni()).WillRepeatedly(ReturnRef(sni)); + stream_info.upstreamInfo()->setUpstreamSslConnection(connection_info); + EXPECT_EQ("example.com", upstream_format.format({}, stream_info)); + EXPECT_THAT(upstream_format.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("example.com"))); + } + { + NiceMock stream_info; + StreamInfoFormatter upstream_format("UPSTREAM_SERVER_NAME"); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, sni()).WillRepeatedly(ReturnRef(EMPTY_STRING)); + stream_info.upstreamInfo()->setUpstreamSslConnection(connection_info); + EXPECT_EQ(absl::nullopt, upstream_format.format({}, stream_info)); + EXPECT_THAT(upstream_format.formatValue({}, stream_info), ProtoEq(ValueUtil::nullValue())); + } { NiceMock stream_info; StreamInfoFormatter upstream_format("UPSTREAM_TLS_CIPHER"); diff --git a/test/extensions/filters/common/expr/context_test.cc b/test/extensions/filters/common/expr/context_test.cc index 9c4fabd786933..5734afd8cbe23 100644 --- a/test/extensions/filters/common/expr/context_test.cc +++ b/test/extensions/filters/common/expr/context_test.cc @@ -1297,6 +1297,45 @@ TEST(Context, UpstreamEdgeCases) { } } +TEST(Context, UpstreamServerName) { + Protobuf::Arena arena; + + { + // TLS connection with SNI set — returns actual SNI from connection. + NiceMock info; + auto ssl_info = std::make_shared>(); + const std::string sni = "tls.example.com"; + EXPECT_CALL(*ssl_info, sni()).WillRepeatedly(ReturnRef(sni)); + info.upstreamInfo()->setUpstreamSslConnection(ssl_info); + + UpstreamWrapper upstream(arena, info); + const auto value = upstream[CelValue::CreateStringView(UpstreamServerName)]; + ASSERT_TRUE(value.has_value()); + EXPECT_EQ("tls.example.com", value->StringOrDie().value()); + } + + { + // No TLS connection — returns empty. + NiceMock info; + info.upstreamInfo()->setUpstreamSslConnection(nullptr); + + UpstreamWrapper upstream(arena, info); + const auto value = upstream[CelValue::CreateStringView(UpstreamServerName)]; + EXPECT_FALSE(value.has_value()); + } + + { + // No upstream info — returns empty. + NiceMock info; + EXPECT_CALL(info, upstreamInfo()) + .WillRepeatedly(Return(std::shared_ptr(nullptr))); + + UpstreamWrapper upstream(arena, info); + const auto value = upstream[CelValue::CreateStringView(UpstreamServerName)]; + EXPECT_FALSE(value.has_value()); + } +} + TEST(Context, ExtractSslInfoEmptyValues) { NiceMock info; std::shared_ptr> upstream_host( From b0a71793e1e8cf2b4c65ac29969884023c6c4cca Mon Sep 17 00:00:00 2001 From: Raven Black Date: Tue, 5 May 2026 14:10:37 -0400 Subject: [PATCH 215/750] Fix stream_rate_limiter stats callback (#44862) Commit Message: Fix stream_rate_limiter stats callback Additional Description: Missed this in #44145 - the function type being initialized changed an arg from bool to uint64, but the stored function arg remained of type bool. Surprised this wasn't a compile-time error. This fixes that omission and adds a test that verifies it's working as expected now (which it did not without the fix). Risk Level: Negligible since the old behavior was treating that arg as a bool anyway. Testing: Added. Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Raven Black --- .../filters/http/common/stream_rate_limiter.h | 2 +- .../http/common/stream_rate_limiter_test.cc | 38 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/source/extensions/filters/http/common/stream_rate_limiter.h b/source/extensions/filters/http/common/stream_rate_limiter.h index 983d4aed7a2f5..df4b9e7b44c34 100644 --- a/source/extensions/filters/http/common/stream_rate_limiter.h +++ b/source/extensions/filters/http/common/stream_rate_limiter.h @@ -87,7 +87,7 @@ class StreamRateLimiter : Logger::Loggable { const std::chrono::milliseconds fill_interval_; const std::function write_data_cb_; const std::function continue_cb_; - const std::function write_stats_cb_; + const std::function write_stats_cb_; const ScopeTrackedObject& scope_; std::shared_ptr token_bucket_; Event::TimerPtr token_timer_; diff --git a/test/extensions/filters/http/common/stream_rate_limiter_test.cc b/test/extensions/filters/http/common/stream_rate_limiter_test.cc index cc8dbbe5214a2..759ecb2ea9972 100644 --- a/test/extensions/filters/http/common/stream_rate_limiter_test.cc +++ b/test/extensions/filters/http/common/stream_rate_limiter_test.cc @@ -1,3 +1,6 @@ +#include +#include + #include "envoy/event/dispatcher.h" #include "source/common/buffer/buffer_impl.h" @@ -16,6 +19,7 @@ using testing::_; using testing::AnyNumber; +using testing::ElementsAre; using testing::NiceMock; using testing::Return; @@ -26,7 +30,11 @@ namespace Common { class StreamRateLimiterTest : public testing::Test { public: - void setUpTest(uint16_t limit_kbps) { + using WriteStatsCb = std::function; + + void setUpTest( + uint16_t limit_kbps, + WriteStatsCb write_stats_cb = [](uint64_t, uint64_t, std::chrono::milliseconds) {}) { EXPECT_CALL(decoder_callbacks_.dispatcher_, pushTrackedObject(_)).Times(AnyNumber()); EXPECT_CALL(decoder_callbacks_.dispatcher_, popTrackedObject(_)).Times(AnyNumber()); @@ -37,10 +45,7 @@ class StreamRateLimiterTest : public testing::Test { [this](Buffer::Instance& data, bool end_stream) { decoder_callbacks_.injectDecodedDataToFilterChain(data, end_stream); }, - [this] { decoder_callbacks_.continueDecoding(); }, - [](uint64_t /*len*/, uint64_t /*buffered*/, std::chrono::milliseconds) { - // config->stats().decode_allowed_size_.set(len); - }, + [this] { decoder_callbacks_.continueDecoding(); }, std::move(write_stats_cb), decoder_callbacks_.dispatcher_, decoder_callbacks_.scope(), StreamRateLimiter::simpleTokenBucket(limit_kbps, time_system_)); } @@ -126,6 +131,29 @@ TEST_F(StreamRateLimiterTest, RateLimitOnSingleStream) { EXPECT_EQ(limiter_->destroyed(), true); } +TEST_F(StreamRateLimiterTest, ReportsBufferedBytesInStatsCallback) { + ON_CALL(decoder_callbacks_, bufferLimit()).WillByDefault(Return(1100)); + Event::MockTimer* token_timer = new NiceMock(&decoder_callbacks_.dispatcher_); + std::vector buffered_bytes; + setUpTest(1, [&buffered_bytes](uint64_t, uint64_t buffered, std::chrono::milliseconds) { + buffered_bytes.push_back(buffered); + }); + + time_system_.advanceTimeWait(std::chrono::seconds(1)); + + EXPECT_CALL(decoder_callbacks_, onDecoderFilterAboveWriteBufferHighWatermark()); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0), _)); + Buffer::OwnedImpl data(std::string(1126, 'a')); + limiter_->writeData(data, false); + + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); + EXPECT_CALL(decoder_callbacks_, onDecoderFilterBelowWriteBufferLowWatermark()); + token_timer->invokeCallback(); + + EXPECT_THAT(buffered_bytes, ElementsAre(102)); + limiter_->destroy(); +} + } // namespace Common } // namespace HttpFilters } // namespace Extensions From 0ec3ed5763fb077dbedb431e90a3fce04baacf9a Mon Sep 17 00:00:00 2001 From: Frank Meier <40453138+meierfra-ergon@users.noreply.github.com> Date: Tue, 5 May 2026 20:43:06 +0200 Subject: [PATCH 216/750] substitution formatter: Add UP/DOWNSTREAM_TLS_GROUP command operators (#44347) to discern if a TLS connection used a post quantum safe key exchange, the TLS group may be logged via the command operators UPSTREAM_TLS_GROUP and DOWNSTREAM_TLS_GROUP. Signed-off-by: Frank Meier --- changelogs/current.yaml | 5 +++ .../advanced/substitution_formatter.rst | 12 ++++++ envoy/ssl/connection.h | 12 ++++++ .../common/formatter/stream_info_formatter.cc | 20 +++++++++ .../common/tls/connection_info_impl_base.cc | 11 +++++ source/common/tls/connection_info_impl_base.h | 2 + .../formatter/substitution_formatter_test.cc | 41 +++++++++++++++++++ test/common/tls/ssl_socket_test.cc | 17 ++++++++ test/mocks/ssl/mocks.h | 2 + 9 files changed, 122 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9444449debe6f..025342d8f06b5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -147,4 +147,9 @@ new_features: Added ``upstream.server_name`` CEL attribute returning the SNI from the established upstream TLS connection. +- area: tls + change: | + Added substitution commands ``%DOWNSTREAM_TLS_GROUP%`` and ``%UPSTREAM_TLS_GROUP%``. The TLS group may be used + to discern if a TLS connection used a post quantum safe key exchange (e.g. X25519MLKEM768). + deprecated: diff --git a/docs/root/configuration/advanced/substitution_formatter.rst b/docs/root/configuration/advanced/substitution_formatter.rst index b5b7243d83810..294c540597f2b 100644 --- a/docs/root/configuration/advanced/substitution_formatter.rst +++ b/docs/root/configuration/advanced/substitution_formatter.rst @@ -1304,6 +1304,12 @@ Current supported substitution commands include: UDP Not implemented. It will appear as ``"-"`` in the access logs. +``%DOWNSTREAM_TLS_GROUP%`` + HTTP/TCP/THRIFT + The name of the TLS group used for the key agreement to establish the downstream TLS connection. + UDP + Not implemented. It will appear as ``"-"`` in the access logs. + ``%DOWNSTREAM_TLS_VERSION%`` HTTP/TCP/THRIFT The TLS version (e.g., ``TLSv1.2``, ``TLSv1.3``) used to establish the downstream TLS connection. @@ -1446,6 +1452,12 @@ Current supported substitution commands include: UDP Not implemented. It will appear as ``"-"`` in the access logs. +``%UPSTREAM_TLS_GROUP%`` + HTTP/TCP/THRIFT + The name of the TLS group used for the key agreement to establish the upstream TLS connection. + UDP + Not implemented. It will appear as ``"-"`` in the access logs. + ``%UPSTREAM_TLS_VERSION%`` HTTP/TCP/THRIFT The TLS version (e.g., ``TLSv1.2``, ``TLSv1.3``) used to establish the upstream TLS connection. diff --git a/envoy/ssl/connection.h b/envoy/ssl/connection.h index 3d364b1fd5d3b..2c8d0b19b8920 100644 --- a/envoy/ssl/connection.h +++ b/envoy/ssl/connection.h @@ -251,6 +251,18 @@ class ConnectionInfo { **/ virtual std::string ciphersuiteString() const PURE; + /** + * @return uint16_t the OpenSSL id of the group that was used for the key agreement of the + * established TLS connection. Returns 0 if there is no group. + **/ + virtual uint16_t tlsGroupId() const PURE; + + /** + * @return absl::string_view the OpenSSL name of the group that was used for the key agreement of + * the established TLS connection. Returns "" if there is no group. + **/ + virtual absl::string_view tlsGroupString() const PURE; + /** * @return std::string the TLS version (e.g., TLSv1.2, TLSv1.3) used in the established TLS * connection. diff --git a/source/common/formatter/stream_info_formatter.cc b/source/common/formatter/stream_info_formatter.cc index d12d794d5deb9..4d22be1567434 100644 --- a/source/common/formatter/stream_info_formatter.cc +++ b/source/common/formatter/stream_info_formatter.cc @@ -1646,6 +1646,16 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide return connection_info.ciphersuiteString(); }); }}}, + {"UPSTREAM_TLS_GROUP", + {CommandSyntaxChecker::COMMAND_ONLY, + [](absl::string_view, absl::optional) { + return std::make_unique< + StreamInfoUpstreamSslConnectionInfoFormatterProvider>( + [](const Ssl::ConnectionInfo& connection_info) { + return absl::make_optional( + connection_info.tlsGroupString()); + }); + }}}, {"UPSTREAM_TLS_VERSION", {CommandSyntaxChecker::COMMAND_ONLY, [](absl::string_view, absl::optional) { @@ -2107,6 +2117,16 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide return connection_info.ciphersuiteString(); }); }}}, + {"DOWNSTREAM_TLS_GROUP", + {CommandSyntaxChecker::COMMAND_ONLY, + [](absl::string_view, absl::optional) { + return std::make_unique< + StreamInfoSslConnectionInfoFormatterProvider>( + [](const Ssl::ConnectionInfo& connection_info) { + return absl::make_optional( + connection_info.tlsGroupString()); + }); + }}}, {"DOWNSTREAM_TLS_VERSION", {CommandSyntaxChecker::COMMAND_ONLY, [](absl::string_view, absl::optional) { diff --git a/source/common/tls/connection_info_impl_base.cc b/source/common/tls/connection_info_impl_base.cc index a05794f4248ca..effa8f73c3038 100644 --- a/source/common/tls/connection_info_impl_base.cc +++ b/source/common/tls/connection_info_impl_base.cc @@ -351,6 +351,17 @@ std::string ConnectionInfoImplBase::ciphersuiteString() const { return SSL_CIPHER_get_name(cipher); } +uint16_t ConnectionInfoImplBase::tlsGroupId() const { return SSL_get_group_id(ssl()); } + +absl::string_view ConnectionInfoImplBase::tlsGroupString() const { + const char* group = SSL_get_group_name(tlsGroupId()); + if (group == nullptr) { + return {}; + } + + return group; +} + const std::string& ConnectionInfoImplBase::tlsVersion() const { return getCachedValueOrCreate( CachedValueTag::TlsVersion, [](SSL* ssl) { return std::string(SSL_get_version(ssl)); }); diff --git a/source/common/tls/connection_info_impl_base.h b/source/common/tls/connection_info_impl_base.h index 42218958772e2..7df94dd86cc00 100644 --- a/source/common/tls/connection_info_impl_base.h +++ b/source/common/tls/connection_info_impl_base.h @@ -54,6 +54,8 @@ class ConnectionInfoImplBase : public Ssl::ConnectionInfo { const std::string& sessionId() const override; uint16_t ciphersuiteId() const override; std::string ciphersuiteString() const override; + uint16_t tlsGroupId() const override; + absl::string_view tlsGroupString() const override; const std::string& tlsVersion() const override; const std::string& alpn() const override; const std::string& sni() const override; diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index ead015dd71568..fb49f1f9bee55 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -2315,6 +2315,23 @@ TEST(SubstitutionFormatterTest, streamInfoFormatterWithSsl) { stream_info.downstream_connection_info_provider_->setSslConnection(connection_info); EXPECT_EQ("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", upstream_format.format({}, stream_info)); } + { + NiceMock stream_info; + StreamInfoFormatter upstream_format("DOWNSTREAM_TLS_GROUP"); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, tlsGroupString).WillRepeatedly(Return("X25519MLKEM768")); + stream_info.downstream_connection_info_provider_->setSslConnection(connection_info); + EXPECT_EQ("X25519MLKEM768", upstream_format.format({}, stream_info)); + } + { + NiceMock stream_info; + StreamInfoFormatter upstream_format("DOWNSTREAM_TLS_GROUP"); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, tlsGroupString).WillRepeatedly(Return("")); + stream_info.downstream_connection_info_provider_->setSslConnection(connection_info); + EXPECT_EQ(absl::nullopt, upstream_format.format({}, stream_info)); + EXPECT_THAT(upstream_format.formatValue({}, stream_info), ProtoEq(ValueUtil::nullValue())); + } { NiceMock stream_info; StreamInfoFormatter upstream_format("DOWNSTREAM_TLS_CIPHER"); @@ -2788,6 +2805,30 @@ TEST(SubstitutionFormatterTest, streamInfoFormatterWithSsl) { EXPECT_EQ(absl::nullopt, upstream_format.format({}, stream_info)); EXPECT_THAT(upstream_format.formatValue({}, stream_info), ProtoEq(ValueUtil::nullValue())); } + { + NiceMock stream_info; + stream_info.upstreamInfo()->setUpstreamSslConnection(nullptr); + StreamInfoFormatter upstream_format("UPSTREAM_TLS_GROUP"); + EXPECT_EQ(absl::nullopt, upstream_format.format({}, stream_info)); + EXPECT_THAT(upstream_format.formatValue({}, stream_info), ProtoEq(ValueUtil::nullValue())); + } + { + NiceMock stream_info; + StreamInfoFormatter upstream_format("UPSTREAM_TLS_GROUP"); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, tlsGroupString).WillRepeatedly(Return("X25519MLKEM768")); + stream_info.upstreamInfo()->setUpstreamSslConnection(connection_info); + EXPECT_EQ("X25519MLKEM768", upstream_format.format({}, stream_info)); + } + { + NiceMock stream_info; + StreamInfoFormatter upstream_format("UPSTREAM_TLS_GROUP"); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, tlsGroupString).WillRepeatedly(Return("")); + stream_info.upstreamInfo()->setUpstreamSslConnection(connection_info); + EXPECT_EQ(absl::nullopt, upstream_format.format({}, stream_info)); + EXPECT_THAT(upstream_format.formatValue({}, stream_info), ProtoEq(ValueUtil::nullValue())); + } { NiceMock stream_info; StreamInfoFormatter upstream_format("UPSTREAM_TLS_VERSION"); diff --git a/test/common/tls/ssl_socket_test.cc b/test/common/tls/ssl_socket_test.cc index cb59f7de4bd2f..86412b775fb06 100644 --- a/test/common/tls/ssl_socket_test.cc +++ b/test/common/tls/ssl_socket_test.cc @@ -935,6 +935,13 @@ class TestUtilOptionsV2 : public TestUtilOptionsBase { const std::string& expectedCiphersuite() const { return expected_cipher_suite_; } + TestUtilOptionsV2& setExpectedTlsGroup(const std::string& expected_tls_group) { + expected_tls_group_ = expected_tls_group; + return *this; + } + + const std::string& expectedTlsGroup() const { return expected_tls_group_; } + TestUtilOptionsV2& setExpectedServerCertDigest(const std::string& expected_server_cert_digest) { expected_server_cert_digest_ = expected_server_cert_digest; return *this; @@ -990,6 +997,7 @@ class TestUtilOptionsV2 : public TestUtilOptionsBase { std::string client_session_; std::string expected_cipher_suite_; + std::string expected_tls_group_; std::string expected_protocol_version_; std::string expected_server_cert_digest_; std::string expected_requested_server_name_; @@ -1136,6 +1144,14 @@ void testUtilV2(const TestUtilOptionsV2& options) { EXPECT_NE(nullptr, cipher); EXPECT_EQ(options.expectedCiphersuite(), SSL_CIPHER_get_name(cipher)); } + if (!options.expectedTlsGroup().empty()) { + EXPECT_EQ(options.expectedTlsGroup(), client_connection->ssl()->tlsGroupString()); + uint16_t group_id = client_connection->ssl()->tlsGroupId(); + EXPECT_NE(0, group_id); + const char* group_name = SSL_get_group_name(group_id); + EXPECT_NE(nullptr, group_name); + EXPECT_EQ(options.expectedTlsGroup(), group_name); + } absl::optional server_ssl_requested_server_name; const SslHandshakerImpl* server_ssl_socket = @@ -5999,6 +6015,7 @@ TEST_P(SslSocketTest, EcdhCurves) { TestUtilOptionsV2 ecdh_curves_test_options(listener, client, true, version_); std::string stats = "ssl.curves.X25519"; ecdh_curves_test_options.setExpectedServerStats(stats).setExpectedClientStats(stats); + ecdh_curves_test_options.setExpectedTlsGroup("X25519"); testUtilV2(ecdh_curves_test_options); client_params->clear_ecdh_curves(); server_params->clear_ecdh_curves(); diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index 7b73c859c2a2e..9ad28703e8c01 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -77,6 +77,8 @@ class MockConnectionInfo : public ConnectionInfo { MOCK_METHOD(const std::string&, sessionId, (), (const)); MOCK_METHOD(uint16_t, ciphersuiteId, (), (const)); MOCK_METHOD(std::string, ciphersuiteString, (), (const)); + MOCK_METHOD(uint16_t, tlsGroupId, (), (const)); + MOCK_METHOD(absl::string_view, tlsGroupString, (), (const)); MOCK_METHOD(const std::string&, tlsVersion, (), (const)); MOCK_METHOD(const std::string&, alpn, (), (const)); MOCK_METHOD(const std::string&, sni, (), (const)); From ce3f6355eb64df75c82aee8127fab37ea0204719 Mon Sep 17 00:00:00 2001 From: Raven Black Date: Tue, 5 May 2026 17:08:36 -0400 Subject: [PATCH 217/750] More flexible and simpler buffer string matcher (#44817) Commit Message: More flexible and simpler buffer string matcher Additional Description: There's a matcher `BufferStringEqual` that can be easily replaced with a better matcher that takes a matcher argument rather than a string argument - this way other conditions can be performed, such as `BufferString(HasSubstr(...))`, and it's also less code. Added also `BufferPtrString(...)`, for the same thing when it's a pointer (this could be composed as `Pointee(BufferString(...))` but since buffer pointers are quite common it seems reasonable to have a convenience shortcut). Also removes the local matcher `BufferString` (which was actually `BufferPtrString`) from `ext_authz`, now that it can use a shared, common one. Risk Level: None, test-only. Testing: yes it is. Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a --------- Signed-off-by: Raven Black --- mobile/test/common/http/client_test.cc | 6 +- test/common/http/async_client_impl_test.cc | 12 +- test/common/http/conn_manager_impl_test_3.cc | 16 +-- test/common/http/http1/codec_impl_test.cc | 34 +++--- test/common/network/connection_impl_test.cc | 82 ++++++------- .../network/filter_manager_impl_test.cc | 111 +++++++++--------- .../quic/envoy_quic_client_stream_test.cc | 6 +- .../quic/envoy_quic_server_stream_test.cc | 2 +- .../common/quic/http_datagram_handler_test.cc | 5 +- test/common/router/router_test.cc | 18 +-- test/common/tcp/async_tcp_client_impl_test.cc | 2 +- test/common/tcp_proxy/tcp_proxy_test.cc | 9 +- test/common/tcp_proxy/upstream_test.cc | 18 +-- test/common/tls/ssl_socket_test.cc | 10 +- .../async_file_handle_thread_pool_test.cc | 10 +- .../http/bandwidth_limit/filter_test.cc | 62 +++++----- .../http/cache_v2/cache_filter_test.cc | 27 +++-- .../http/cache_v2/cache_sessions_test.cc | 14 +-- .../http_cache_implementation_test_common.cc | 4 +- .../http/cache_v2/upstream_request_test.cc | 26 ++-- .../http/common/stream_rate_limiter_test.cc | 13 +- test/extensions/filters/http/ext_authz/BUILD | 1 + .../filters/http/ext_authz/ext_authz_test.cc | 14 +-- .../filters/http/fault/fault_filter_test.cc | 12 +- .../filters/http/file_server/filter_test.cc | 8 +- .../http/file_system_buffer/filter_test.cc | 2 +- .../http/file_system_buffer/fragment_test.cc | 6 +- .../network/generic_proxy/proxy_test.cc | 26 ++-- .../tcp_bandwidth_limit/filter_test.cc | 4 +- .../stats_sinks/common/statsd/statsd_test.cc | 26 ++-- .../http_11_proxy/connect_test.cc | 14 +-- .../proxy_protocol/proxy_protocol_test.cc | 48 ++++---- .../http/tcp/upstream_request_test.cc | 14 +-- .../http/udp/upstream_request_test.cc | 2 +- .../api_listener_integration_test.cc | 4 +- test/mocks/buffer/mocks.h | 13 +- 36 files changed, 334 insertions(+), 347 deletions(-) diff --git a/mobile/test/common/http/client_test.cc b/mobile/test/common/http/client_test.cc index b13fe3f75a5fc..404707a1606f6 100644 --- a/mobile/test/common/http/client_test.cc +++ b/mobile/test/common/http/client_test.cc @@ -273,7 +273,7 @@ TEST_P(ClientTest, BasicStreamData) { // test data functionality. EXPECT_CALL(dispatcher_, pushTrackedObject(_)); EXPECT_CALL(dispatcher_, popTrackedObject(_)); - EXPECT_CALL(*request_decoder_, decodeData(BufferStringEqual("request body"), true)); + EXPECT_CALL(*request_decoder_, decodeData(BufferString("request body"), true)); resumeDataIfEarlyResume(20); // Resume before data arrives. http_client_.sendData(stream_, std::move(request_data), true); resumeDataIfLateResume(20); // Resume after data arrives. @@ -358,7 +358,7 @@ TEST_P(ClientTest, MultipleDataStream) { // Send request data. EXPECT_CALL(dispatcher_, pushTrackedObject(_)); EXPECT_CALL(dispatcher_, popTrackedObject(_)); - EXPECT_CALL(*request_decoder_, decodeData(BufferStringEqual("request body1"), false)); + EXPECT_CALL(*request_decoder_, decodeData(BufferString("request body1"), false)); http_client_.sendData(stream_, std::move(request_data1), false); EXPECT_EQ(callbacks_called.on_send_window_available_calls_, 0); if (explicit_flow_control_) { @@ -371,7 +371,7 @@ TEST_P(ClientTest, MultipleDataStream) { // Send second request data. EXPECT_CALL(dispatcher_, pushTrackedObject(_)); EXPECT_CALL(dispatcher_, popTrackedObject(_)); - EXPECT_CALL(*request_decoder_, decodeData(BufferStringEqual("request body2"), true)); + EXPECT_CALL(*request_decoder_, decodeData(BufferString("request body2"), true)); http_client_.sendData(stream_, std::move(request_data2), true); // The stream is done: no further on_send_window_available calls should happen. EXPECT_EQ(callbacks_called.on_send_window_available_calls_, explicit_flow_control_ ? 1 : 0); diff --git a/test/common/http/async_client_impl_test.cc b/test/common/http/async_client_impl_test.cc index c2e964d64da31..7e7e4f5dfa25f 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -521,7 +521,7 @@ TEST_F(AsyncClientImplTest, OngoingRequestWithWatermarking) { EXPECT_CALL(watermark_callbacks, onSidestreamAboveHighWatermark()); request->setWatermarkCallbacks(watermark_callbacks); - EXPECT_CALL(stream_encoder_, encodeData(BufferStringEqual(""), true)); + EXPECT_CALL(stream_encoder_, encodeData(BufferString(""), true)); Buffer::OwnedImpl empty; request->sendData(empty, true); @@ -1161,7 +1161,7 @@ TEST_F(AsyncClientImplTest, RetryWithStream) { TestRequestHeaderMapImpl headers; HttpTestUtility::addDefaultHeaders(headers); EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&headers), false)); - EXPECT_CALL(stream_encoder_, encodeData(BufferStringEqual("test body"), true)); + EXPECT_CALL(stream_encoder_, encodeData(BufferString("test body"), true)); headers.setReferenceEnvoyRetryOn(Headers::get().EnvoyRetryOnValues._5xx); AsyncClient::Stream* stream = client_.start(stream_callbacks_, {}); @@ -1185,7 +1185,7 @@ TEST_F(AsyncClientImplTest, RetryWithStream) { })); EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&headers), false)); - EXPECT_CALL(stream_encoder_, encodeData(BufferStringEqual("test body"), true)); + EXPECT_CALL(stream_encoder_, encodeData(BufferString("test body"), true)); timer_->invokeCallback(); // Normal response. @@ -1220,7 +1220,7 @@ TEST_F(AsyncClientImplTest, DataBufferForRetryOverflow) { const std::string body_str((1 << 16) + 1, 'a'); Buffer::InstancePtr large_body{new Buffer::OwnedImpl(body_str)}; - EXPECT_CALL(stream_encoder_, encodeData(BufferStringEqual(body_str), true)); + EXPECT_CALL(stream_encoder_, encodeData(BufferString(body_str), true)); headers.setReferenceEnvoyRetryOn(Headers::get().EnvoyRetryOnValues._5xx); AsyncClient::Stream* stream = client_.start(stream_callbacks_, {}); @@ -1266,7 +1266,7 @@ TEST_F(AsyncClientImplTest, DataBufferForRetryWithLargerBufferLimit) { const std::string body_str((1 << 16) + 1, 'a'); Buffer::InstancePtr large_body{new Buffer::OwnedImpl(body_str)}; - EXPECT_CALL(stream_encoder_, encodeData(BufferStringEqual(body_str), true)); + EXPECT_CALL(stream_encoder_, encodeData(BufferString(body_str), true)); headers.setReferenceEnvoyRetryOn(Headers::get().EnvoyRetryOnValues._5xx); // Set buffer limit to larger than body size. @@ -1292,7 +1292,7 @@ TEST_F(AsyncClientImplTest, DataBufferForRetryWithLargerBufferLimit) { })); EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&headers), false)); - EXPECT_CALL(stream_encoder_, encodeData(BufferStringEqual(body_str), true)); + EXPECT_CALL(stream_encoder_, encodeData(BufferString(body_str), true)); timer_->invokeCallback(); // Normal response. diff --git a/test/common/http/conn_manager_impl_test_3.cc b/test/common/http/conn_manager_impl_test_3.cc index fdd43cf61168a..2e5affa1f9969 100644 --- a/test/common/http/conn_manager_impl_test_3.cc +++ b/test/common/http/conn_manager_impl_test_3.cc @@ -470,11 +470,11 @@ TEST_F(HttpConnectionManagerImplTest, FilterDirectDecodeEncodeDataNoTrailers) { Buffer::OwnedImpl decoded_data_to_forward; decoded_data_to_forward.move(decode_buffer, 2); - EXPECT_CALL(*decoder_filters_[1], decodeData(BufferStringEqual("he"), false)) + EXPECT_CALL(*decoder_filters_[1], decodeData(BufferString("he"), false)) .WillOnce(Return(FilterDataStatus::StopIterationNoBuffer)); decoder_filters_[0]->callbacks_->injectDecodedDataToFilterChain(decoded_data_to_forward, false); - EXPECT_CALL(*decoder_filters_[1], decodeData(BufferStringEqual("llo"), true)) + EXPECT_CALL(*decoder_filters_[1], decodeData(BufferString("llo"), true)) .WillOnce(Return(FilterDataStatus::StopIterationNoBuffer)); EXPECT_CALL(*decoder_filters_[1], decodeComplete()); decoder_filters_[0]->callbacks_->injectDecodedDataToFilterChain(decode_buffer, true); @@ -502,11 +502,11 @@ TEST_F(HttpConnectionManagerImplTest, FilterDirectDecodeEncodeDataNoTrailers) { Buffer::OwnedImpl encoded_data_to_forward; encoded_data_to_forward.move(encoder_buffer, 3); - EXPECT_CALL(*encoder_filters_[0], encodeData(BufferStringEqual("res"), false)); + EXPECT_CALL(*encoder_filters_[0], encodeData(BufferString("res"), false)); EXPECT_CALL(response_encoder_, encodeData(_, false)); encoder_filters_[1]->callbacks_->injectEncodedDataToFilterChain(encoded_data_to_forward, false); - EXPECT_CALL(*encoder_filters_[0], encodeData(BufferStringEqual("ponse"), true)); + EXPECT_CALL(*encoder_filters_[0], encodeData(BufferString("ponse"), true)); EXPECT_CALL(*encoder_filters_[0], encodeComplete()); EXPECT_CALL(response_encoder_, encodeData(_, true)); expectOnDestroy(); @@ -556,11 +556,11 @@ TEST_F(HttpConnectionManagerImplTest, FilterDirectDecodeEncodeDataTrailers) { Buffer::OwnedImpl decoded_data_to_forward; decoded_data_to_forward.move(decode_buffer, 2); - EXPECT_CALL(*decoder_filters_[1], decodeData(BufferStringEqual("he"), false)) + EXPECT_CALL(*decoder_filters_[1], decodeData(BufferString("he"), false)) .WillOnce(Return(FilterDataStatus::StopIterationNoBuffer)); decoder_filters_[0]->callbacks_->injectDecodedDataToFilterChain(decoded_data_to_forward, false); - EXPECT_CALL(*decoder_filters_[1], decodeData(BufferStringEqual("llo"), false)) + EXPECT_CALL(*decoder_filters_[1], decodeData(BufferString("llo"), false)) .WillOnce(Return(FilterDataStatus::StopIterationNoBuffer)); decoder_filters_[0]->callbacks_->injectDecodedDataToFilterChain(decode_buffer, false); @@ -595,11 +595,11 @@ TEST_F(HttpConnectionManagerImplTest, FilterDirectDecodeEncodeDataTrailers) { Buffer::OwnedImpl encoded_data_to_forward; encoded_data_to_forward.move(encoder_buffer, 3); - EXPECT_CALL(*encoder_filters_[0], encodeData(BufferStringEqual("res"), false)); + EXPECT_CALL(*encoder_filters_[0], encodeData(BufferString("res"), false)); EXPECT_CALL(response_encoder_, encodeData(_, false)); encoder_filters_[1]->callbacks_->injectEncodedDataToFilterChain(encoded_data_to_forward, false); - EXPECT_CALL(*encoder_filters_[0], encodeData(BufferStringEqual("ponse"), false)); + EXPECT_CALL(*encoder_filters_[0], encodeData(BufferString("ponse"), false)); EXPECT_CALL(response_encoder_, encodeData(_, false)); encoder_filters_[1]->callbacks_->injectEncodedDataToFilterChain(encoder_buffer, false); diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index f2b015bd9ef8b..92833d1b109e5 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -4215,7 +4215,7 @@ TEST_F(Http1ClientConnectionImplTest, FirstReadEOF) { Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\n\r\nfoo"); EXPECT_CALL(decoder, decodeHeaders_(_, false)); - EXPECT_CALL(decoder, decodeData(BufferStringEqual("foo"), false)); + EXPECT_CALL(decoder, decodeData(BufferString("foo"), false)); status = codec_->dispatch(buffer); EXPECT_EQ(0, buffer.length()); EXPECT_TRUE(status.ok()); @@ -4282,7 +4282,7 @@ TEST_F(Http1ServerConnectionImplTest, EOFDuringChunkedBody) { })); EXPECT_CALL(decoder, decodeHeaders_(_, false)); - EXPECT_CALL(decoder, decodeData(BufferStringEqual("foo"), false)); + EXPECT_CALL(decoder, decodeData(BufferString("foo"), false)); Buffer::OwnedImpl buffer("POST / HTTP/1.1\r\n" "transfer-encoding: chunked\r\n\r\n" "9\r\n" @@ -4311,7 +4311,7 @@ TEST_F(Http1ClientConnectionImplTest, EOFDuringChunkedBody) { EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(decoder, decodeHeaders_(_, false)); - EXPECT_CALL(decoder, decodeData(BufferStringEqual("foo"), false)); + EXPECT_CALL(decoder, decodeData(BufferString("foo"), false)); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\n" "transfer-encoding: chunked\r\n\r\n" "9\r\n" @@ -4340,7 +4340,7 @@ TEST_F(Http1ServerConnectionImplTest, EOFDuringContentLengthBody) { })); EXPECT_CALL(decoder, decodeHeaders_(_, false)); - EXPECT_CALL(decoder, decodeData(BufferStringEqual("foo"), false)); + EXPECT_CALL(decoder, decodeData(BufferString("foo"), false)); Buffer::OwnedImpl buffer("POST / HTTP/1.1\r\n" "content-length: 9\r\n\r\n" "foo"); @@ -4368,7 +4368,7 @@ TEST_F(Http1ClientConnectionImplTest, EOFDuringContentLengthBody) { EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(decoder, decodeHeaders_(_, false)); - EXPECT_CALL(decoder, decodeData(BufferStringEqual("foo"), false)); + EXPECT_CALL(decoder, decodeData(BufferString("foo"), false)); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\n" "content-length: 9\r\n\r\n" "foo"); @@ -4422,11 +4422,11 @@ TEST_F(Http1ClientConnectionImplTest, NoContentLengthResponse) { EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(decoder, decodeHeaders_(_, false)); - EXPECT_CALL(decoder, decodeData(BufferStringEqual("foo"), false)); + EXPECT_CALL(decoder, decodeData(BufferString("foo"), false)); Buffer::OwnedImpl buffer(kResponseWithBody); auto status = codec_->dispatch(buffer); - EXPECT_CALL(decoder, decodeData(BufferStringEqual(""), true)); + EXPECT_CALL(decoder, decodeData(BufferString(""), true)); Buffer::OwnedImpl empty; status = codec_->dispatch(empty); ASSERT_TRUE(status.ok()); @@ -4439,14 +4439,14 @@ TEST_F(Http1ClientConnectionImplTest, NoContentLengthResponse) { EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(decoder, decodeHeaders_(_, false)); - EXPECT_CALL(decoder, decodeData(BufferStringEqual("foo"), false)); + EXPECT_CALL(decoder, decodeData(BufferString("foo"), false)); Buffer::OwnedImpl buffer(kResponseWithBody); auto status = codec_->dispatch(buffer); EXPECT_EQ(0, buffer.length()); ASSERT_TRUE(status.ok()); - EXPECT_CALL(decoder, decodeData(BufferStringEqual(""), true)); + EXPECT_CALL(decoder, decodeData(BufferString(""), true)); Buffer::OwnedImpl empty; status = codec_->dispatch(empty); EXPECT_TRUE(status.ok()); @@ -4720,7 +4720,7 @@ TEST_F(Http1ClientConnectionImplTest, MalformedTrailerLine) { TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); - EXPECT_CALL(response_decoder, decodeData(BufferStringEqual("foo"), false)); + EXPECT_CALL(response_decoder, decodeData(BufferString("foo"), false)); Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\n" "transfer-encoding: chunked\r\n" @@ -4744,7 +4744,7 @@ TEST_F(Http1ClientConnectionImplTest, InvalidCharacterInTrailerName) { EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); - EXPECT_CALL(response_decoder, decodeData(BufferStringEqual("foo"), false)); + EXPECT_CALL(response_decoder, decodeData(BufferString("foo"), false)); // SPELLCHECKER(off) Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\n" @@ -5051,8 +5051,8 @@ TEST_F(Http1ClientConnectionImplTest, FirstLineInvalidCR) { {"content-length", "5"}, }; EXPECT_CALL(response_decoder, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - EXPECT_CALL(response_decoder, decodeData(BufferStringEqual("hello"), false)); - EXPECT_CALL(response_decoder, decodeData(BufferStringEqual(""), true)); + EXPECT_CALL(response_decoder, decodeData(BufferString("hello"), false)); + EXPECT_CALL(response_decoder, decodeData(BufferString(""), true)); Buffer::OwnedImpl buffer("HTTP/1.1 200\rOK\r\ncontent-length: 5\r\n\r\n" "hello"); @@ -5213,8 +5213,8 @@ TEST_F(Http1ClientConnectionImplTest, RequestAfterConnectionClose) { EXPECT_TRUE(request_encoder.encodeHeaders(request_headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); - EXPECT_CALL(response_decoder, decodeData(BufferStringEqual("foo"), false)); - EXPECT_CALL(response_decoder, decodeData(BufferStringEqual(""), true)); + EXPECT_CALL(response_decoder, decodeData(BufferString("foo"), false)); + EXPECT_CALL(response_decoder, decodeData(BufferString(""), true)); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\n" "content-length: 3\r\n" @@ -5233,8 +5233,8 @@ TEST_F(Http1ClientConnectionImplTest, RequestAfterConnectionClose) { EXPECT_TRUE(request_encoder.encodeHeaders(request_headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); - EXPECT_CALL(response_decoder, decodeData(BufferStringEqual("bar"), false)); - EXPECT_CALL(response_decoder, decodeData(BufferStringEqual(""), true)); + EXPECT_CALL(response_decoder, decodeData(BufferString("bar"), false)); + EXPECT_CALL(response_decoder, decodeData(BufferString(""), true)); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\n" "content-length: 3\r\n" diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index 224bf75719f40..ccea8c2f29e9d 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -945,7 +945,7 @@ TEST_P(ConnectionImplTest, ReadEnableDispatches) { { Buffer::OwnedImpl buffer("data"); server_connection_->write(buffer, false); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("data"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("data"), false)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { dispatcher_->exit(); return FilterStatus::StopIteration; @@ -956,7 +956,7 @@ TEST_P(ConnectionImplTest, ReadEnableDispatches) { { EXPECT_EQ(Connection::ReadDisableStatus::TransitionedToReadDisabled, client_connection_->readDisable(true)); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("data"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("data"), false)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> FilterStatus { buffer.drain(buffer.length()); dispatcher_->exit(); @@ -983,7 +983,7 @@ TEST_P(ConnectionImplTest, KickUndone) { { Buffer::OwnedImpl buffer("data"); server_connection_->write(buffer, false); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("data"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("data"), false)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> FilterStatus { dispatcher_->exit(); connection_buffer = &buffer; @@ -1204,7 +1204,7 @@ TEST_P(ConnectionImplTest, HalfClose) { Buffer::OwnedImpl buffer("data"); server_connection_->write(buffer, false); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("data"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("data"), false)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> FilterStatus { buffer.drain(buffer.length()); dispatcher_->exit(); @@ -1215,7 +1215,7 @@ TEST_P(ConnectionImplTest, HalfClose) { EXPECT_CALL(server_callbacks_, onEvent(ConnectionEvent::LocalClose)); EXPECT_CALL(client_callbacks_, onEvent(ConnectionEvent::RemoteClose)); server_connection_->write(empty_buffer, true); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual(""), true)) + EXPECT_CALL(*client_read_filter, onData(BufferString(""), true)) .WillOnce(InvokeWithoutArgs([&]() -> FilterStatus { dispatcher_->exit(); return FilterStatus::StopIteration; @@ -1269,7 +1269,7 @@ TEST_P(ConnectionImplTest, HalfCloseThenNormallClose) { Buffer::OwnedImpl buffer("data"); server_connection_->write(buffer, false); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("data"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("data"), false)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> FilterStatus { buffer.drain(buffer.length()); dispatcher_->exit(); @@ -1315,7 +1315,7 @@ TEST_P(ConnectionImplTest, HalfCloseThenResetClose) { Buffer::OwnedImpl buffer("data"); server_connection_->write(buffer, false); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("data"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("data"), false)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> FilterStatus { buffer.drain(buffer.length()); dispatcher_->exit(); @@ -1366,7 +1366,7 @@ TEST_P(ConnectionImplTest, HalfCloseThenResetCloseThenWriteData) { Buffer::OwnedImpl buffer("data"); server_connection_->write(buffer, false); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("data"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("data"), false)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> FilterStatus { buffer.drain(buffer.length()); dispatcher_->exit(); @@ -1960,7 +1960,7 @@ TEST_P(ConnectionImplTest, FlushWriteCloseTest) { EXPECT_CALL(stats.delayed_close_timeouts_, inc()).Times(0); EXPECT_CALL(server_callbacks_, onEvent(ConnectionEvent::LocalClose)); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("data"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("data"), false)) .Times(1) .WillOnce(InvokeWithoutArgs([&]() -> FilterStatus { time_system_.setMonotonicTime(std::chrono::milliseconds(50)); @@ -1999,7 +1999,7 @@ TEST_P(ConnectionImplTest, FlushWriteAndDelayCloseTest) { Buffer::OwnedImpl data("Connection: Close"); server_connection_->write(data, false); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("Connection: Close"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("Connection: Close"), false)) .Times(1) .WillOnce(InvokeWithoutArgs([&]() -> FilterStatus { // Advance time by 50ms; delayed close timer should _not_ trigger. @@ -2054,7 +2054,7 @@ TEST_P(ConnectionImplTest, FlushWriteAndDelayRemoteCloseNotWithFilterManager) { Buffer::OwnedImpl data("Connection: Close"); server_connection_->write(data, false); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("Connection: Close"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("Connection: Close"), false)) .Times(1) .WillOnce(InvokeWithoutArgs([&]() -> FilterStatus { // Advance time by 1000ms; delayed close timer should _not_ trigger @@ -2105,7 +2105,7 @@ TEST_P(ConnectionImplTest, FlushWriteAndDelayCloseTimerTriggerTest) { // The client _will not_ close the connection. Instead, expect the delayed close timer to // trigger on the server connection. - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("Connection: Close"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("Connection: Close"), false)) .Times(1) .WillOnce(InvokeWithoutArgs([&]() -> FilterStatus { time_system_.setMonotonicTime(std::chrono::milliseconds(100)); @@ -2152,7 +2152,7 @@ TEST_P(ConnectionImplTest, FlushWriteAfterFlushWriteAndDelayWithPendingWrite) { // timer triggering. EXPECT_CALL(stats.delayed_close_timeouts_, inc()).Times(0); EXPECT_CALL(server_callbacks_, onEvent(ConnectionEvent::LocalClose)); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("Connection: Close"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("Connection: Close"), false)) .Times(1) .WillOnce(InvokeWithoutArgs([&]() -> FilterStatus { time_system_.setMonotonicTime(std::chrono::milliseconds(100)); @@ -2184,7 +2184,7 @@ TEST_P(ConnectionImplTest, FlushWriteAfterFlushWriteAndDelayWithoutPendingWrite) server_connection_->write(data, false); server_connection_->close(ConnectionCloseType::FlushWriteAndDelay); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("Connection: Close"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("Connection: Close"), false)) .Times(1) .WillOnce(InvokeWithoutArgs([&]() -> FilterStatus { dispatcher_->exit(); @@ -2265,7 +2265,7 @@ TEST_P(ConnectionImplTest, DelayedCloseTimerResetWithPendingWriteBufferFlushes) EXPECT_CALL(*mocks.file_event_, activate(Event::FileReadyType::Write)) .WillOnce(Invoke(*mocks.file_ready_cb_)); - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual("data"), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString("data"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> IoResult { // Do not drain the buffer and return 0 bytes processed to simulate backpressure. return IoResult{PostIoAction::KeepOpen, 0, false}; @@ -2278,7 +2278,7 @@ TEST_P(ConnectionImplTest, DelayedCloseTimerResetWithPendingWriteBufferFlushes) // The write ready event cb (ConnectionImpl::onWriteReady()) will reset the timer to its // original timeout value to avoid triggering while the write buffer is being actively flushed. - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual("data"), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString("data"), _)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> IoResult { // Partial flush. uint64_t bytes_drained = 1; @@ -2288,7 +2288,7 @@ TEST_P(ConnectionImplTest, DelayedCloseTimerResetWithPendingWriteBufferFlushes) EXPECT_CALL(*mocks.timer_, enableTimer(timeout, _)); EXPECT_TRUE((*mocks.file_ready_cb_)(Event::FileReadyType::Write).ok()); - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual("ata"), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString("ata"), _)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> IoResult { // Flush the entire buffer. uint64_t bytes_drained = buffer.length(); @@ -2326,7 +2326,7 @@ TEST_P(ConnectionImplTest, IgnoreSpuriousFdWriteEventsDuringFlushWriteAndDelay) EXPECT_CALL(*mocks.file_event_, activate(Event::FileReadyType::Write)) .WillOnce(Invoke(*mocks.file_ready_cb_)); - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual("data"), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString("data"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> IoResult { // Do not drain the buffer and return 0 bytes processed to simulate backpressure. return IoResult{PostIoAction::KeepOpen, 0, false}; @@ -2339,7 +2339,7 @@ TEST_P(ConnectionImplTest, IgnoreSpuriousFdWriteEventsDuringFlushWriteAndDelay) // The write ready event cb (ConnectionImpl::onWriteReady()) will reset the timer to its // original timeout value to avoid triggering while the write buffer is being actively flushed. - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual("data"), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString("data"), _)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> IoResult { // Partial flush. uint64_t bytes_drained = 1; @@ -2350,7 +2350,7 @@ TEST_P(ConnectionImplTest, IgnoreSpuriousFdWriteEventsDuringFlushWriteAndDelay) EXPECT_TRUE((*mocks.file_ready_cb_)(Event::FileReadyType::Write).ok()); // Handle a write event and drain 0 bytes from the buffer. Verify that the timer is not reset. - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual("ata"), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString("ata"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> IoResult { // Don't consume any bytes. return IoResult{PostIoAction::KeepOpen, 0, false}; @@ -2359,7 +2359,7 @@ TEST_P(ConnectionImplTest, IgnoreSpuriousFdWriteEventsDuringFlushWriteAndDelay) EXPECT_TRUE((*mocks.file_ready_cb_)(Event::FileReadyType::Write).ok()); // Handle a write event and drain the remainder of the buffer. Verify that the timer is reset. - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual("ata"), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString("ata"), _)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> IoResult { // Flush the entire buffer. ASSERT(buffer.length() > 0); @@ -2373,7 +2373,7 @@ TEST_P(ConnectionImplTest, IgnoreSpuriousFdWriteEventsDuringFlushWriteAndDelay) // Handle a write event after entering the half-closed state. Verify that the timer is not reset // because write consumed 0 bytes from the empty buffer. - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual(""), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString(""), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> IoResult { EXPECT_EQ(server_connection->state(), Connection::State::Closing); return IoResult{PostIoAction::KeepOpen, 0, false}; @@ -2383,7 +2383,7 @@ TEST_P(ConnectionImplTest, IgnoreSpuriousFdWriteEventsDuringFlushWriteAndDelay) // Handle a write event that somehow drains bytes from an empty output buffer. Since // some bytes were consumed, the timer is reset. - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual(""), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString(""), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> IoResult { EXPECT_EQ(server_connection->state(), Connection::State::Closing); return IoResult{PostIoAction::KeepOpen, 1, false}; @@ -2416,7 +2416,7 @@ TEST_P(ConnectionImplTest, DelayedCloseTimeoutDisableOnSocketClose) { .WillOnce(Invoke(*mocks.file_ready_cb_)); // The buffer must be drained when write() is called on the connection to allow the close() to // enable the timer. - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual("data"), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString("data"), _)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> IoResult { buffer.drain(buffer.length()); return IoResult{PostIoAction::KeepOpen, buffer.length(), false}; @@ -2456,7 +2456,7 @@ TEST_P(ConnectionImplTest, DelayedCloseTimeoutNullStats) { .WillOnce(Invoke(*mocks.file_ready_cb_)); // The buffer must be drained when write() is called on the connection to allow the close() to // enable the timer. - EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual("data"), _)) + EXPECT_CALL(*transport_socket, doWrite(BufferString("data"), _)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> IoResult { buffer.drain(buffer.length()); return IoResult{PostIoAction::KeepOpen, buffer.length(), false}; @@ -2976,7 +2976,7 @@ TEST_P(ConnectionImplCloseTest, LocalCloseThroughFilterManagerWithRawWrite) { EXPECT_TRUE(inject_data.length() == 0); // Verify that client can still get the data. - EXPECT_CALL(*client_read_filter_, onData(BufferStringEqual("test data"), _)) + EXPECT_CALL(*client_read_filter_, onData(BufferString("test data"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { dispatcher_->exit(); return FilterStatus::Continue; @@ -3762,7 +3762,7 @@ TEST_F(MockTransportConnectionImplTest, FullCloseWrite) { const std::string val("some data"); Buffer::OwnedImpl buffer(val); EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Write)).WillOnce(Invoke(file_ready_cb_)); - EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual(val), false)) + EXPECT_CALL(*transport_socket_, doWrite(BufferString(val), false)) .WillOnce(Invoke(simulateSuccessfulWrite)); connection_->write(buffer, false); } @@ -3776,7 +3776,7 @@ TEST_F(MockTransportConnectionImplTest, HalfCloseWrite) { const std::string val("some data"); Buffer::OwnedImpl buffer(val); - EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual(val), false)) + EXPECT_CALL(*transport_socket_, doWrite(BufferString(val), false)) .WillOnce(Invoke(simulateSuccessfulWrite)); connection_->write(buffer, false); @@ -3909,11 +3909,11 @@ TEST_F(MockTransportConnectionImplTest, ReadEndStreamStopIteration) { return {PostIoAction::KeepOpen, val.size(), true}; })); - EXPECT_CALL(*read_filter1, onData(BufferStringEqual(val), true)) + EXPECT_CALL(*read_filter1, onData(BufferString(val), true)) .WillOnce(Return(FilterStatus::StopIteration)); EXPECT_TRUE(file_ready_cb_(Event::FileReadyType::Read).ok()); - EXPECT_CALL(*read_filter2, onData(BufferStringEqual(val), true)) + EXPECT_CALL(*read_filter2, onData(BufferString(val), true)) .WillOnce(Return(FilterStatus::StopIteration)); read_filter1->callbacks_->continueReading(); } @@ -3929,14 +3929,14 @@ TEST_F(MockTransportConnectionImplTest, WriteEndStreamStopIteration) { connection_->addWriteFilter(write_filter2); connection_->addWriteFilter(write_filter1); - EXPECT_CALL(*write_filter1, onWrite(BufferStringEqual(val), true)) + EXPECT_CALL(*write_filter1, onWrite(BufferString(val), true)) .WillOnce(Return(FilterStatus::StopIteration)); Buffer::OwnedImpl buffer(val); connection_->write(buffer, true); - EXPECT_CALL(*write_filter1, onWrite(BufferStringEqual(val), true)) + EXPECT_CALL(*write_filter1, onWrite(BufferString(val), true)) .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(*write_filter2, onWrite(BufferStringEqual(val), true)) + EXPECT_CALL(*write_filter2, onWrite(BufferString(val), true)) .WillOnce(Return(FilterStatus::Continue)); EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Write)); connection_->write(buffer, true); @@ -3952,7 +3952,7 @@ TEST_F(MockTransportConnectionImplTest, WriteReadyOnConnected) { const std::string val("some data"); Buffer::OwnedImpl buffer(val); EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Write)).WillOnce(Invoke(file_ready_cb_)); - EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual(val), false)) + EXPECT_CALL(*transport_socket_, doWrite(BufferString(val), false)) .WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); connection_->write(buffer, false); @@ -3963,7 +3963,7 @@ TEST_F(MockTransportConnectionImplTest, WriteReadyOnConnected) { transport_socket_callbacks_->raiseEvent(Network::ConnectionEvent::Connected); return IoResult{PostIoAction::KeepOpen, 0, false}; })); - EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual(val), false)) + EXPECT_CALL(*transport_socket_, doWrite(BufferString(val), false)) .WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); EXPECT_TRUE(file_ready_cb_(Event::FileReadyType::Read).ok()); EXPECT_CALL(*transport_socket_, doWrite(_, true)) @@ -3979,7 +3979,7 @@ TEST_F(MockTransportConnectionImplTest, FlushWriteBufferAndRtt) { const std::string val("some data"); Buffer::OwnedImpl buffer(val); EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Write)).WillOnce(Invoke(file_ready_cb_)); - EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual(val), false)) + EXPECT_CALL(*transport_socket_, doWrite(BufferString(val), false)) .WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); connection_->write(buffer, false); @@ -3991,7 +3991,7 @@ TEST_F(MockTransportConnectionImplTest, FlushWriteBufferAndRtt) { transport_socket_callbacks_->flushWriteBuffer(); return IoResult{PostIoAction::KeepOpen, 0, false}; })); - EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual(val), false)) + EXPECT_CALL(*transport_socket_, doWrite(BufferString(val), false)) .WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); EXPECT_TRUE(file_ready_cb_(Event::FileReadyType::Read).ok()); EXPECT_CALL(*transport_socket_, doWrite(_, true)) @@ -4012,7 +4012,7 @@ TEST_F(MockTransportConnectionImplTest, BufferHighWatermarkTimeoutClosesConnecti EXPECT_CALL(*buffer_timer, enabled()).WillOnce(Return(false)); EXPECT_CALL(*buffer_timer, enableTimer(timeout, _)); EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Write)).WillOnce(Invoke(file_ready_cb_)); - EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual("data"), _)) + EXPECT_CALL(*transport_socket_, doWrite(BufferString("data"), _)) .WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); connection_->write(data, false); @@ -4034,7 +4034,7 @@ TEST_F(MockTransportConnectionImplTest, ZeroBufferHighWatermarkTimeoutDoesNotSch Buffer::OwnedImpl data("data"); EXPECT_CALL(dispatcher_, createTimer_(_)).Times(0); EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Write)).WillOnce(Invoke(file_ready_cb_)); - EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual("data"), _)) + EXPECT_CALL(*transport_socket_, doWrite(BufferString("data"), _)) .WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); EXPECT_CALL(*transport_socket_, doWrite(_, true)) .WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, true})); @@ -4055,11 +4055,11 @@ TEST_F(MockTransportConnectionImplTest, BufferHighWatermarkTimeoutCancelledOnDra EXPECT_CALL(*buffer_timer, enabled()).WillOnce(Return(false)); EXPECT_CALL(*buffer_timer, enableTimer(timeout, _)); EXPECT_CALL(*file_event_, activate(Event::FileReadyType::Write)).WillOnce(Invoke(file_ready_cb_)); - EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual("bytes"), _)) + EXPECT_CALL(*transport_socket_, doWrite(BufferString("bytes"), _)) .WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); connection_->write(data, false); - EXPECT_CALL(*transport_socket_, doWrite(BufferStringEqual("bytes"), _)) + EXPECT_CALL(*transport_socket_, doWrite(BufferString("bytes"), _)) .WillOnce(Invoke(&MockTransportConnectionImplTest::simulateSuccessfulWrite)); EXPECT_CALL(*buffer_timer, enabled()).WillOnce(Return(true)); EXPECT_CALL(*buffer_timer, disableTimer()); diff --git a/test/common/network/filter_manager_impl_test.cc b/test/common/network/filter_manager_impl_test.cc index d001617bc28a0..e156aa6d772e0 100644 --- a/test/common/network/filter_manager_impl_test.cc +++ b/test/common/network/filter_manager_impl_test.cc @@ -92,25 +92,25 @@ TEST_F(NetworkFilterManagerTest, All) { read_buffer_.add("hello"); read_end_stream_ = false; - EXPECT_CALL(*read_filter, onData(BufferStringEqual("hello"), false)) + EXPECT_CALL(*read_filter, onData(BufferString("hello"), false)) .WillOnce(Return(FilterStatus::StopIteration)); manager.onRead(); read_buffer_.add("world"); - EXPECT_CALL(*filter, onData(BufferStringEqual("helloworld"), false)) + EXPECT_CALL(*filter, onData(BufferString("helloworld"), false)) .WillOnce(Return(FilterStatus::Continue)); read_filter->callbacks_->continueReading(); write_buffer_.add("foo"); write_end_stream_ = false; - EXPECT_CALL(*filter, onWrite(BufferStringEqual("foo"), false)) + EXPECT_CALL(*filter, onWrite(BufferString("foo"), false)) .WillOnce(Return(FilterStatus::StopIteration)); manager.onWrite(); write_buffer_.add("bar"); - EXPECT_CALL(*filter, onWrite(BufferStringEqual("foobar"), false)) + EXPECT_CALL(*filter, onWrite(BufferString("foobar"), false)) .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(*write_filter, onWrite(BufferStringEqual("foobar"), false)) + EXPECT_CALL(*write_filter, onWrite(BufferString("foobar"), false)) .WillOnce(Return(FilterStatus::Continue)); manager.onWrite(); } @@ -159,7 +159,7 @@ TEST_F(NetworkFilterManagerTest, FilterReturnStopAndNoCallback) { read_buffer_.add("hello"); EXPECT_CALL(*read_filter, onNewConnection()).WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(*read_filter, onData(BufferStringEqual("hello"), _)) + EXPECT_CALL(*read_filter, onData(BufferString("hello"), _)) .WillOnce(Return(FilterStatus::StopIteration)); EXPECT_CALL(*filter, onNewConnection()).Times(0); EXPECT_CALL(*filter, onData(_, _)).Times(0); @@ -190,7 +190,7 @@ TEST_F(NetworkFilterManagerTest, ReadFilterCloseConnectionAndReturnContinue) { read_buffer_.add("hello"); EXPECT_CALL(connection_, state()).WillOnce(Return(Connection::State::Open)); - EXPECT_CALL(*read_filter, onData(BufferStringEqual("hello"), _)) + EXPECT_CALL(*read_filter, onData(BufferString("hello"), _)) .WillOnce(Return(FilterStatus::Continue)); EXPECT_CALL(connection_, state()).WillOnce(Return(Connection::State::Closing)); EXPECT_CALL(*filter, onData(_, _)).Times(0); @@ -222,19 +222,18 @@ TEST_F(NetworkFilterManagerTest, WriteFilterCloseConnectionAndReturnContinue) { EXPECT_EQ(manager.initializeReadFilters(), true); read_buffer_.add("hello"); - EXPECT_CALL(*read_filter, onData(BufferStringEqual("hello"), _)) + EXPECT_CALL(*read_filter, onData(BufferString("hello"), _)) .WillOnce(Return(FilterStatus::StopIteration)); manager.onRead(); read_buffer_.add("world"); - EXPECT_CALL(*filter, onData(BufferStringEqual("helloworld"), _)) + EXPECT_CALL(*filter, onData(BufferString("helloworld"), _)) .WillOnce(Return(FilterStatus::Continue)); read_filter->callbacks_->continueReading(); write_buffer_.add("foo"); EXPECT_CALL(connection_, state()).WillOnce(Return(Connection::State::Open)); - EXPECT_CALL(*filter, onWrite(BufferStringEqual("foo"), _)) - .WillOnce(Return(FilterStatus::Continue)); + EXPECT_CALL(*filter, onWrite(BufferString("foo"), _)).WillOnce(Return(FilterStatus::Continue)); EXPECT_CALL(connection_, state()).WillOnce(Return(Connection::State::Closing)); EXPECT_CALL(*write_filter, onWrite(_, _)).Times(0); manager.onWrite(); @@ -261,7 +260,7 @@ TEST_F(NetworkFilterManagerTest, ReadCloseConnectionReturnStopAndCallback) { EXPECT_EQ(manager.initializeReadFilters(), true); read_buffer_.add("hello"); - EXPECT_CALL(*read_filter, onData(BufferStringEqual("hello"), _)) + EXPECT_CALL(*read_filter, onData(BufferString("hello"), _)) .WillOnce(Return(FilterStatus::StopIteration)); manager.onRead(); @@ -295,15 +294,14 @@ TEST_F(NetworkFilterManagerTest, WriteCloseConnectionReturnStopAndCallback) { EXPECT_EQ(manager.initializeReadFilters(), true); read_buffer_.add("hello"); - EXPECT_CALL(*read_filter, onData(BufferStringEqual("hello"), _)) - .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(*filter, onData(BufferStringEqual("hello"), _)) + EXPECT_CALL(*read_filter, onData(BufferString("hello"), _)) .WillOnce(Return(FilterStatus::Continue)); + EXPECT_CALL(*filter, onData(BufferString("hello"), _)).WillOnce(Return(FilterStatus::Continue)); manager.onRead(); write_buffer_.add("foo"); EXPECT_CALL(connection_, state()).WillOnce(Return(Connection::State::Open)); - EXPECT_CALL(*filter, onWrite(BufferStringEqual("foo"), _)) + EXPECT_CALL(*filter, onWrite(BufferString("foo"), _)) .WillOnce(Return(FilterStatus::StopIteration)); manager.onWrite(); @@ -337,25 +335,25 @@ TEST_F(NetworkFilterManagerTest, EndStream) { read_buffer_.add("hello"); read_end_stream_ = true; - EXPECT_CALL(*read_filter, onData(BufferStringEqual("hello"), true)) + EXPECT_CALL(*read_filter, onData(BufferString("hello"), true)) .WillOnce(Return(FilterStatus::StopIteration)); manager.onRead(); read_buffer_.add("world"); - EXPECT_CALL(*filter, onData(BufferStringEqual("helloworld"), true)) + EXPECT_CALL(*filter, onData(BufferString("helloworld"), true)) .WillOnce(Return(FilterStatus::Continue)); read_filter->callbacks_->continueReading(); write_buffer_.add("foo"); write_end_stream_ = true; - EXPECT_CALL(*filter, onWrite(BufferStringEqual("foo"), true)) + EXPECT_CALL(*filter, onWrite(BufferString("foo"), true)) .WillOnce(Return(FilterStatus::StopIteration)); manager.onWrite(); write_buffer_.add("bar"); - EXPECT_CALL(*filter, onWrite(BufferStringEqual("foobar"), true)) + EXPECT_CALL(*filter, onWrite(BufferString("foobar"), true)) .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(*write_filter, onWrite(BufferStringEqual("foobar"), true)) + EXPECT_CALL(*write_filter, onWrite(BufferString("foobar"), true)) .WillOnce(Return(FilterStatus::Continue)); manager.onWrite(); } @@ -382,12 +380,12 @@ TEST_F(NetworkFilterManagerTest, InjectReadDataToFilterChain) { read_end_stream_ = true; Buffer::OwnedImpl injected_buffer("greetings"); - EXPECT_CALL(*filter, onData(BufferStringEqual("greetings"), false)) + EXPECT_CALL(*filter, onData(BufferString("greetings"), false)) .WillOnce(Return(FilterStatus::Continue)); read_filter->callbacks_->injectReadDataToFilterChain(injected_buffer, false); injected_buffer.add(" everyone"); - EXPECT_CALL(*filter, onData(BufferStringEqual("greetings everyone"), true)) + EXPECT_CALL(*filter, onData(BufferString("greetings everyone"), true)) .WillOnce(Return(FilterStatus::Continue)); read_filter->callbacks_->injectReadDataToFilterChain(injected_buffer, true); } @@ -405,15 +403,15 @@ TEST_F(NetworkFilterManagerTest, InjectWriteDataToFilterChain) { manager.addFilter(FilterSharedPtr{filter}); Buffer::OwnedImpl injected_buffer("greetings"); - EXPECT_CALL(*write_filter, onWrite(BufferStringEqual("greetings"), false)) + EXPECT_CALL(*write_filter, onWrite(BufferString("greetings"), false)) .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(connection_, rawWrite(BufferStringEqual("greetings"), false)); + EXPECT_CALL(connection_, rawWrite(BufferString("greetings"), false)); filter->write_callbacks_->injectWriteDataToFilterChain(injected_buffer, false); injected_buffer.add(" everyone!"); - EXPECT_CALL(*write_filter, onWrite(BufferStringEqual(" everyone!"), true)) + EXPECT_CALL(*write_filter, onWrite(BufferString(" everyone!"), true)) .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(connection_, rawWrite(BufferStringEqual(" everyone!"), true)); + EXPECT_CALL(connection_, rawWrite(BufferString(" everyone!"), true)); filter->write_callbacks_->injectWriteDataToFilterChain(injected_buffer, true); } @@ -459,7 +457,7 @@ TEST_F(NetworkFilterManagerTest, MultipleStopIterationAndDontCloseRead) { read_buffer_.add("hello world"); // First filter disableClose() and StopIteration. - EXPECT_CALL(*read_filter_1, onData(BufferStringEqual("hello world"), _)) + EXPECT_CALL(*read_filter_1, onData(BufferString("hello world"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter_1->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -473,7 +471,7 @@ TEST_F(NetworkFilterManagerTest, MultipleStopIterationAndDontCloseRead) { manager.onConnectionClose(remote_close_action_); // Continue from first filter. - EXPECT_CALL(*read_filter_2, onData(BufferStringEqual("hello world"), _)) + EXPECT_CALL(*read_filter_2, onData(BufferString("hello world"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter_2->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -508,7 +506,7 @@ TEST_F(NetworkFilterManagerTest, BothReadAndWriteFiltersHoldClose) { // Make both read and write filters hold a pending close. read_buffer_.add("read_data"); - EXPECT_CALL(*read_filter, onData(BufferStringEqual("read_data"), _)) + EXPECT_CALL(*read_filter, onData(BufferString("read_data"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -516,7 +514,7 @@ TEST_F(NetworkFilterManagerTest, BothReadAndWriteFiltersHoldClose) { manager.onRead(); write_buffer_.add("write_data"); - EXPECT_CALL(*filter, onWrite(BufferStringEqual("write_data"), _)) + EXPECT_CALL(*filter, onWrite(BufferString("write_data"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { filter->write_callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -552,7 +550,7 @@ TEST_F(NetworkFilterManagerTest, StopIterationAndDontCloseWithLocalClose) { // Set up read data. read_buffer_.add("test data"); - EXPECT_CALL(*read_filter, onData(BufferStringEqual("test data"), _)) + EXPECT_CALL(*read_filter, onData(BufferString("test data"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -584,7 +582,7 @@ TEST_F(NetworkFilterManagerTest, FinalizeCloseAfterFiltersComplete) { // Set up read data with disableClose and StopIteration. read_buffer_.add("data"); - EXPECT_CALL(*read_filter, onData(BufferStringEqual("data"), _)) + EXPECT_CALL(*read_filter, onData(BufferString("data"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -614,7 +612,7 @@ TEST_F(NetworkFilterManagerTest, LocalAndRemoteCloseRaceCondition) { EXPECT_EQ(manager.initializeReadFilters(), true); read_buffer_.add("data"); - EXPECT_CALL(*read_filter, onData(BufferStringEqual("data"), _)) + EXPECT_CALL(*read_filter, onData(BufferString("data"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -647,7 +645,7 @@ TEST_F(NetworkFilterManagerTest, LocalCloseSocketAndRemoteCloseRace) { EXPECT_EQ(manager.initializeReadFilters(), true); read_buffer_.add("data"); - EXPECT_CALL(*read_filter, onData(BufferStringEqual("data"), _)) + EXPECT_CALL(*read_filter, onData(BufferString("data"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -685,16 +683,16 @@ TEST_F(NetworkFilterManagerTest, MultipleFiltersWithDifferentStatusResponses) { // Setup data and filter responses. read_buffer_.add("test"); - EXPECT_CALL(*continue_filter, onData(BufferStringEqual("test"), _)) + EXPECT_CALL(*continue_filter, onData(BufferString("test"), _)) .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(*stop_filter, onData(BufferStringEqual("test"), _)) + EXPECT_CALL(*stop_filter, onData(BufferString("test"), _)) .WillOnce(Return(FilterStatus::StopIteration)); EXPECT_CALL(*stop_dont_close_filter, onData(_, _)).Times(0); manager.onRead(); // Continue from stop_filter. read_buffer_.add("more"); - EXPECT_CALL(*stop_dont_close_filter, onData(BufferStringEqual("testmore"), _)) + EXPECT_CALL(*stop_dont_close_filter, onData(BufferString("testmore"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { stop_dont_close_filter->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -730,7 +728,7 @@ TEST_F(NetworkFilterManagerTest, InjectReadDataWithStopIterationAndDontClose) { // First read filter disableClose(true). read_buffer_.add("original"); - EXPECT_CALL(*read_filter_1, onData(BufferStringEqual("original"), _)) + EXPECT_CALL(*read_filter_1, onData(BufferString("original"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter_1->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -741,18 +739,17 @@ TEST_F(NetworkFilterManagerTest, InjectReadDataWithStopIterationAndDontClose) { // Inject data through the stopped filter - should reach remaining filters. Buffer::OwnedImpl injected_data("injected"); - EXPECT_CALL(*read_filter_2, onData(BufferStringEqual("injected"), false)) + EXPECT_CALL(*read_filter_2, onData(BufferString("injected"), false)) .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(*filter, onData(BufferStringEqual("injected"), false)) + EXPECT_CALL(*filter, onData(BufferString("injected"), false)) .WillOnce(Return(FilterStatus::Continue)); read_filter_1->callbacks_->injectReadDataToFilterChain(injected_data, false); // Inject more data. Buffer::OwnedImpl more_data("more"); - EXPECT_CALL(*read_filter_2, onData(BufferStringEqual("more"), true)) - .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(*filter, onData(BufferStringEqual("more"), true)) + EXPECT_CALL(*read_filter_2, onData(BufferString("more"), true)) .WillOnce(Return(FilterStatus::Continue)); + EXPECT_CALL(*filter, onData(BufferString("more"), true)).WillOnce(Return(FilterStatus::Continue)); read_filter_1->callbacks_->injectReadDataToFilterChain(more_data, true); // Try to close the connection - should be held by filter. @@ -760,9 +757,9 @@ TEST_F(NetworkFilterManagerTest, InjectReadDataWithStopIterationAndDontClose) { manager.onConnectionClose(remote_close_action_); // Continue reading should not affect the close status. - EXPECT_CALL(*read_filter_2, onData(BufferStringEqual("original"), _)) + EXPECT_CALL(*read_filter_2, onData(BufferString("original"), _)) .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(*filter, onData(BufferStringEqual("original"), _)) + EXPECT_CALL(*filter, onData(BufferString("original"), _)) .WillOnce(Return(FilterStatus::Continue)); read_filter_1->callbacks_->continueReading(); @@ -789,7 +786,7 @@ TEST_F(NetworkFilterManagerTest, InjectWriteDataWithStopIterationAndDontClose) { // Write filter disableClose(true). write_buffer_.add("original"); - EXPECT_CALL(*write_filter_2, onWrite(BufferStringEqual("original"), _)) + EXPECT_CALL(*write_filter_2, onWrite(BufferString("original"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { write_filter_2->write_callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -803,16 +800,16 @@ TEST_F(NetworkFilterManagerTest, InjectWriteDataWithStopIterationAndDontClose) { // Inject write data should bypass stopped filter and reach connection. Buffer::OwnedImpl injected_data("injected"); - EXPECT_CALL(*write_filter_1, onWrite(BufferStringEqual("injected"), false)) + EXPECT_CALL(*write_filter_1, onWrite(BufferString("injected"), false)) .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(connection_, rawWrite(BufferStringEqual("injected"), false)); + EXPECT_CALL(connection_, rawWrite(BufferString("injected"), false)); write_filter_2->write_callbacks_->injectWriteDataToFilterChain(injected_data, false); // Inject more data with end_stream. Buffer::OwnedImpl more_data("more"); - EXPECT_CALL(*write_filter_1, onWrite(BufferStringEqual("more"), true)) + EXPECT_CALL(*write_filter_1, onWrite(BufferString("more"), true)) .WillOnce(Return(FilterStatus::Continue)); - EXPECT_CALL(connection_, rawWrite(BufferStringEqual("more"), true)); + EXPECT_CALL(connection_, rawWrite(BufferString("more"), true)); write_filter_2->write_callbacks_->injectWriteDataToFilterChain(more_data, true); // Connection should close after filter continues closing. @@ -840,7 +837,7 @@ TEST_F(NetworkFilterManagerTest, ChainedInjectsWithMixedFilterStatus) { // First read filter stops the chain with disableClose(true). read_buffer_.add("start"); - EXPECT_CALL(*read_filter_1, onData(BufferStringEqual("start"), _)) + EXPECT_CALL(*read_filter_1, onData(BufferString("start"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter_1->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -849,13 +846,13 @@ TEST_F(NetworkFilterManagerTest, ChainedInjectsWithMixedFilterStatus) { // Inject data and continue the chain - second filter returns StopIteration. Buffer::OwnedImpl data1("inject1"); - EXPECT_CALL(*read_filter_2, onData(BufferStringEqual("inject1"), false)) + EXPECT_CALL(*read_filter_2, onData(BufferString("inject1"), false)) .WillOnce(Return(FilterStatus::StopIteration)); read_filter_1->callbacks_->injectReadDataToFilterChain(data1, false); // Continue reading from second filter - filter returns Continue. Buffer::OwnedImpl data2("inject2"); - EXPECT_CALL(*filter, onData(BufferStringEqual("inject2"), true)) + EXPECT_CALL(*filter, onData(BufferString("inject2"), true)) .WillOnce(Return(FilterStatus::Continue)); read_filter_2->callbacks_->injectReadDataToFilterChain(data2, true); @@ -888,7 +885,7 @@ TEST_F(NetworkFilterManagerTest, MultipleInjectDataCallsFromDifferentFilters) { // First read filter StopIteration in sequence. read_buffer_.add("original"); - EXPECT_CALL(*read_filter_1, onData(BufferStringEqual("original"), _)) + EXPECT_CALL(*read_filter_1, onData(BufferString("original"), _)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter_1->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -898,7 +895,7 @@ TEST_F(NetworkFilterManagerTest, MultipleInjectDataCallsFromDifferentFilters) { // Inject data from first filter. Buffer::OwnedImpl data1("data1"); - EXPECT_CALL(*read_filter_2, onData(BufferStringEqual("data1"), false)) + EXPECT_CALL(*read_filter_2, onData(BufferString("data1"), false)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> FilterStatus { read_filter_2->callbacks_->disableClose(true); return FilterStatus::StopIteration; @@ -908,7 +905,7 @@ TEST_F(NetworkFilterManagerTest, MultipleInjectDataCallsFromDifferentFilters) { // Inject data from second filter. Buffer::OwnedImpl data2("data2"); - EXPECT_CALL(*filter, onData(BufferStringEqual("data2"), false)) + EXPECT_CALL(*filter, onData(BufferString("data2"), false)) .WillOnce(Return(FilterStatus::Continue)); read_filter_2->callbacks_->injectReadDataToFilterChain(data2, false); diff --git a/test/common/quic/envoy_quic_client_stream_test.cc b/test/common/quic/envoy_quic_client_stream_test.cc index 4522e5d788ff0..1c0e63b0633a7 100644 --- a/test/common/quic/envoy_quic_client_stream_test.cc +++ b/test/common/quic/envoy_quic_client_stream_test.cc @@ -216,7 +216,7 @@ TEST_F(EnvoyQuicClientStreamTest, GetRequestAndHeaderOnlyResponse) { .WillOnce(Invoke([](const Http::ResponseHeaderMapPtr& headers, bool) { EXPECT_EQ("200", headers->getStatusValue()); })); - EXPECT_CALL(stream_decoder_, decodeData(BufferStringEqual(""), /*end_stream=*/true)); + EXPECT_CALL(stream_decoder_, decodeData(BufferString(""), /*end_stream=*/true)); std::string payload = spdyHeaderToHttp3StreamPayload(spdy_response_headers_); quic::QuicStreamFrame frame(stream_id_, true, 0, payload); quic_stream_->OnStreamFrame(frame); @@ -627,7 +627,7 @@ TEST_F(EnvoyQuicClientStreamTest, ReadDisabledBeforeClose) { EXPECT_EQ("200", headers->getStatusValue()); quic_stream_->readDisable(true); })); - EXPECT_CALL(stream_decoder_, decodeData(BufferStringEqual(""), /*end_stream=*/true)); + EXPECT_CALL(stream_decoder_, decodeData(BufferString(""), /*end_stream=*/true)); std::string payload = spdyHeaderToHttp3StreamPayload(spdy_response_headers_); quic::QuicStreamFrame frame(stream_id_, true, 0, payload); quic_stream_->OnStreamFrame(frame); @@ -866,7 +866,7 @@ TEST_F(EnvoyQuicClientStreamTest, EncodeCapsule) { TEST_F(EnvoyQuicClientStreamTest, DecodeHttp3Datagram) { setUpCapsuleProtocol(true, false); - EXPECT_CALL(stream_decoder_, decodeData(BufferStringEqual(capsule_fragment_), _)); + EXPECT_CALL(stream_decoder_, decodeData(BufferString(capsule_fragment_), _)); quic_session_.OnDatagramReceived(datagram_fragment_); EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } diff --git a/test/common/quic/envoy_quic_server_stream_test.cc b/test/common/quic/envoy_quic_server_stream_test.cc index 91bea50096d6e..3cc39e3f7df98 100644 --- a/test/common/quic/envoy_quic_server_stream_test.cc +++ b/test/common/quic/envoy_quic_server_stream_test.cc @@ -949,7 +949,7 @@ TEST_F(EnvoyQuicServerStreamTest, EncodeCapsule) { TEST_F(EnvoyQuicServerStreamTest, DecodeHttp3Datagram) { setUpCapsuleProtocol(true, false); - EXPECT_CALL(stream_decoder_, decodeData(BufferStringEqual(capsule_fragment_), _)); + EXPECT_CALL(stream_decoder_, decodeData(BufferString(capsule_fragment_), _)); quic_session_.OnDatagramReceived(datagram_fragment_); } #endif diff --git a/test/common/quic/http_datagram_handler_test.cc b/test/common/quic/http_datagram_handler_test.cc index 2cc35df0fe493..ee7e0d0fce143 100644 --- a/test/common/quic/http_datagram_handler_test.cc +++ b/test/common/quic/http_datagram_handler_test.cc @@ -70,8 +70,7 @@ class HttpDatagramHandlerTest : public ::testing::Test { }; TEST_F(HttpDatagramHandlerTest, Http3DatagramToCapsule) { - EXPECT_CALL(stream_decoder_, - decodeData(BufferStringEqual(capsule_fragment_), /*end_stream=*/false)); + EXPECT_CALL(stream_decoder_, decodeData(BufferString(capsule_fragment_), /*end_stream=*/false)); http_datagram_handler_.OnHttp3Datagram(kStreamId, datagram_payload_); } @@ -87,7 +86,7 @@ TEST_F(HttpDatagramHandlerTest, CapsuleToHttp3Datagram) { TEST_F(HttpDatagramHandlerTest, ReceiveCapsuleWithUnknownType) { EXPECT_CALL(stream_decoder_, - decodeData(BufferStringEqual(unknown_capsule_fragment_), /*end_stream=*/false)); + decodeData(BufferString(unknown_capsule_fragment_), /*end_stream=*/false)); std::string payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); quiche::UnknownCapsule capsule{0x17u, payload}; http_datagram_handler_.OnUnknownCapsule(kStreamId, capsule); diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 4dbd98eeb9e06..0b4769e90ee46 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -3169,7 +3169,7 @@ TEST_F(RouterTest, RetryRequestBeforeBody) { // Complete request. Ensure original headers are present. const std::string body("body"); - EXPECT_CALL(encoder2, encodeData(BufferStringEqual(body), true)); + EXPECT_CALL(encoder2, encodeData(BufferString(body), true)); Buffer::OwnedImpl buf(body); router_->decodeData(buf, true); @@ -3215,7 +3215,7 @@ TEST_F(RouterTest, RetryRequestDuringBody) { expectNewStreamWithImmediateEncoder(encoder2, &response_decoder, Http::Protocol::Http10); EXPECT_CALL(encoder2, encodeHeaders(ContainsHeader("myheader", "present"), false)); - EXPECT_CALL(encoder2, encodeData(BufferStringEqual(body1), false)); + EXPECT_CALL(encoder2, encodeData(BufferString(body1), false)); router_->retry_state_->callback_(); EXPECT_EQ(2U, callbacks_.route_->virtual_host_->virtual_cluster_.stats().upstream_rq_total_.value()); @@ -3223,7 +3223,7 @@ TEST_F(RouterTest, RetryRequestDuringBody) { // Complete request. Ensure original headers are present. const std::string body2("body2"); - EXPECT_CALL(encoder2, encodeData(BufferStringEqual(body2), true)); + EXPECT_CALL(encoder2, encodeData(BufferString(body2), true)); Buffer::OwnedImpl buf2(body2); EXPECT_CALL(*router_->retry_state_, enabled()).WillOnce(Return(true)); router_->decodeData(buf2, true); @@ -3273,7 +3273,7 @@ TEST_F(RouterTest, RetryRequestDuringBodyDataBetweenAttemptsNotEndStream) { expectNewStreamWithImmediateEncoder(encoder2, &response_decoder, Http::Protocol::Http10); EXPECT_CALL(encoder2, encodeHeaders(ContainsHeader("myheader", "present"), false)); - EXPECT_CALL(encoder2, encodeData(BufferStringEqual(body1 + body2), false)); + EXPECT_CALL(encoder2, encodeData(BufferString(body1 + body2), false)); router_->retry_state_->callback_(); EXPECT_EQ(2U, callbacks_.route_->virtual_host_->virtual_cluster_.stats().upstream_rq_total_.value()); @@ -3281,7 +3281,7 @@ TEST_F(RouterTest, RetryRequestDuringBodyDataBetweenAttemptsNotEndStream) { // Complete request. Ensure original headers are present. const std::string body3("body3"); - EXPECT_CALL(encoder2, encodeData(BufferStringEqual(body3), true)); + EXPECT_CALL(encoder2, encodeData(BufferString(body3), true)); Buffer::OwnedImpl buf3(body3); router_->decodeData(buf3, true); @@ -3356,7 +3356,7 @@ TEST_F(RouterTest, RetryRequestDuringBodyCompleteBetweenAttempts) { expectNewStreamWithImmediateEncoder(encoder2, &response_decoder, Http::Protocol::Http10); EXPECT_CALL(encoder2, encodeHeaders(ContainsHeader("myheader", "present"), false)); - EXPECT_CALL(encoder2, encodeData(BufferStringEqual(body1 + body2), true)); + EXPECT_CALL(encoder2, encodeData(BufferString(body1 + body2), true)); router_->retry_state_->callback_(); EXPECT_EQ(2U, callbacks_.route_->virtual_host_->virtual_cluster_.stats().upstream_rq_total_.value()); @@ -3406,7 +3406,7 @@ TEST_F(RouterTest, RetryRequestDuringBodyTrailerBetweenAttempts) { expectNewStreamWithImmediateEncoder(encoder2, &response_decoder, Http::Protocol::Http10); EXPECT_CALL(encoder2, encodeHeaders(ContainsHeader("myheader", "present"), false)); - EXPECT_CALL(encoder2, encodeData(BufferStringEqual(body1), false)); + EXPECT_CALL(encoder2, encodeData(BufferString(body1), false)); EXPECT_CALL(encoder2, encodeTrailers(HeaderMapEqualRef(&trailers))); router_->retry_state_->callback_(); EXPECT_EQ(2U, @@ -5999,8 +5999,8 @@ TEST_P(RouterShadowingTest, StreamingShadow) { Buffer::InstancePtr body_data(new Buffer::OwnedImpl("hello")); EXPECT_CALL(callbacks_, addDecodedData(_, _)).Times(0); - EXPECT_CALL(foo_request, sendData(BufferStringEqual("hello"), false)); - EXPECT_CALL(fizz_request, sendData(BufferStringEqual("hello"), false)); + EXPECT_CALL(foo_request, sendData(BufferString("hello"), false)); + EXPECT_CALL(fizz_request, sendData(BufferString("hello"), false)); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, router_->decodeData(*body_data, false)); Http::TestRequestTrailerMapImpl trailers{{"some", "trailer"}}; diff --git a/test/common/tcp/async_tcp_client_impl_test.cc b/test/common/tcp/async_tcp_client_impl_test.cc index f1050ecc1da94..3199276de6f2f 100644 --- a/test/common/tcp/async_tcp_client_impl_test.cc +++ b/test/common/tcp/async_tcp_client_impl_test.cc @@ -76,7 +76,7 @@ TEST_F(AsyncTcpClientImplTest, BasicWrite) { setUpClient(); expectCreateConnection(); - EXPECT_CALL(*connection_, write(BufferStringEqual("test data"), _)); + EXPECT_CALL(*connection_, write(BufferString("test data"), _)); Buffer::OwnedImpl buff("test data"); client_->write(buff, false); diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 69db3b38f2659..3a26636f44bf1 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -856,7 +856,7 @@ TEST_P(TcpProxyTest, ReceiveBeforeConnectBuffersOnEarlyData) { filter_->onData(early_data_buffer, /*end_stream=*/false); // Now when upstream connection is established, early buffer will be sent. - EXPECT_CALL(*upstream_connections_.at(0), write(BufferStringEqual(early_data), false)); + EXPECT_CALL(*upstream_connections_.at(0), write(BufferString(early_data), false)); raiseEventUpstreamConnected(/*conn_index=*/0); // Any further communications between client and server can resume normally. @@ -880,8 +880,7 @@ TEST_P(TcpProxyTest, ReceiveBeforeConnectEarlyDataWithEndStream) { filter_->onData(early_data_buffer, /*end_stream=*/true); // Now when upstream connection is established, early buffer will be sent. - EXPECT_CALL(*upstream_connections_.at(0), - write(BufferStringEqual(early_data), /*end_stream*/ true)); + EXPECT_CALL(*upstream_connections_.at(0), write(BufferString(early_data), /*end_stream*/ true)); raiseEventUpstreamConnected(/*conn_index=*/0); // Any further communications between client and server can resume normally. @@ -903,7 +902,7 @@ TEST_P(TcpProxyTest, ReceiveBeforeConnectDownstreamClosesWithoutData) { // When upstream connection is established, the end_stream signal should be sent even though // the buffer is empty. This ensures the upstream connection is properly closed. - EXPECT_CALL(*upstream_connections_.at(0), write(BufferStringEqual(""), /*end_stream*/ true)); + EXPECT_CALL(*upstream_connections_.at(0), write(BufferString(""), /*end_stream*/ true)); raiseEventUpstreamConnected(/*conn_index=*/0); } @@ -918,7 +917,7 @@ TEST_P(TcpProxyTest, ReceiveBeforeConnectEmptyBufferWithEndStream) { filter_->onData(empty_buffer, /*end_stream=*/true); // When upstream connection is established, end_stream should be propagated. - EXPECT_CALL(*upstream_connections_.at(0), write(BufferStringEqual(""), /*end_stream*/ true)); + EXPECT_CALL(*upstream_connections_.at(0), write(BufferString(""), /*end_stream*/ true)); raiseEventUpstreamConnected(/*conn_index=*/0); // Upstream can still send data back. diff --git a/test/common/tcp_proxy/upstream_test.cc b/test/common/tcp_proxy/upstream_test.cc index 7d3287efc27da..b41f57eb1248e 100644 --- a/test/common/tcp_proxy/upstream_test.cc +++ b/test/common/tcp_proxy/upstream_test.cc @@ -73,11 +73,11 @@ INSTANTIATE_TEST_SUITE_P(H1H2H3Codecs, HttpUpstreamTest, TEST_P(HttpUpstreamTest, WriteUpstream) { this->setupUpstream(); - EXPECT_CALL(this->encoder_, encodeData(BufferStringEqual("foo"), false)); + EXPECT_CALL(this->encoder_, encodeData(BufferString("foo"), false)); Buffer::OwnedImpl buffer1("foo"); this->upstream_->encodeData(buffer1, false); - EXPECT_CALL(this->encoder_, encodeData(BufferStringEqual("bar"), true)); + EXPECT_CALL(this->encoder_, encodeData(BufferString("bar"), true)); Buffer::OwnedImpl buffer2("bar"); this->upstream_->encodeData(buffer2, true); @@ -89,11 +89,11 @@ TEST_P(HttpUpstreamTest, WriteUpstream) { TEST_P(HttpUpstreamTest, WriteDownstream) { this->setupUpstream(); - EXPECT_CALL(this->callbacks_, onUpstreamData(BufferStringEqual("foo"), false)); + EXPECT_CALL(this->callbacks_, onUpstreamData(BufferString("foo"), false)); Buffer::OwnedImpl buffer1("foo"); this->upstream_->responseDecoder().decodeData(buffer1, false); - EXPECT_CALL(this->callbacks_, onUpstreamData(BufferStringEqual("bar"), true)); + EXPECT_CALL(this->callbacks_, onUpstreamData(BufferString("bar"), true)); Buffer::OwnedImpl buffer2("bar"); this->upstream_->responseDecoder().decodeData(buffer2, true); } @@ -284,7 +284,7 @@ TEST_P(HttpUpstreamTest, UpstreamTrailersPropagateFinDownstream) { setupUpstream(); EXPECT_CALL(encoder_.stream_, resetStream(_)).Times(0); upstream_->doneWriting(); - EXPECT_CALL(callbacks_, onUpstreamData(BufferStringEqual(""), true)); + EXPECT_CALL(callbacks_, onUpstreamData(BufferString(""), true)); Http::ResponseTrailerMapPtr trailers{new Http::TestResponseTrailerMapImpl{{"key", "value"}}}; upstream_->responseDecoder().decodeTrailers(std::move(trailers)); } @@ -724,12 +724,12 @@ TEST_F(CombinedUpstreamTest, RouterFilterInterface) { TEST_F(CombinedUpstreamTest, WriteUpstream) { this->setup(); EXPECT_CALL(*this->mock_router_upstream_request_, - acceptDataFromRouter(BufferStringEqual("foo"), false /*end_stream*/)); + acceptDataFromRouter(BufferString("foo"), false /*end_stream*/)); Buffer::OwnedImpl buffer1("foo"); this->upstream_->encodeData(buffer1, false); EXPECT_CALL(*this->mock_router_upstream_request_, - acceptDataFromRouter(BufferStringEqual("bar"), true /*end_stream*/)); + acceptDataFromRouter(BufferString("bar"), true /*end_stream*/)); Buffer::OwnedImpl buffer2("bar"); this->upstream_->encodeData(buffer2, true); @@ -756,11 +756,11 @@ TEST_F(CombinedUpstreamTest, CombinedUpstreamGeneratesRequestIdWhenEnabled) { TEST_F(CombinedUpstreamTest, WriteDownstream) { this->setup(); - EXPECT_CALL(this->callbacks_, onUpstreamData(BufferStringEqual("foo"), false)); + EXPECT_CALL(this->callbacks_, onUpstreamData(BufferString("foo"), false)); Buffer::OwnedImpl buffer1("foo"); this->upstream_->responseDecoder().decodeData(buffer1, false); - EXPECT_CALL(this->callbacks_, onUpstreamData(BufferStringEqual("bar"), true)); + EXPECT_CALL(this->callbacks_, onUpstreamData(BufferString("bar"), true)); Buffer::OwnedImpl buffer2("bar"); this->upstream_->responseDecoder().decodeData(buffer2, true); } diff --git a/test/common/tls/ssl_socket_test.cc b/test/common/tls/ssl_socket_test.cc index 86412b775fb06..d57c631bd618d 100644 --- a/test/common/tls/ssl_socket_test.cc +++ b/test/common/tls/ssl_socket_test.cc @@ -4007,14 +4007,14 @@ TEST_P(SslSocketTest, HalfClose) { .WillOnce(Return(Network::FilterStatus::Continue)); EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), true)) + EXPECT_CALL(*client_read_filter, onData(BufferString("hello"), true)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> Network::FilterStatus { Buffer::OwnedImpl buffer("world"); client_connection->write(buffer, true); return Network::FilterStatus::Continue; })); EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); - EXPECT_CALL(*server_read_filter, onData(BufferStringEqual("world"), true)); + EXPECT_CALL(*server_read_filter, onData(BufferString("world"), true)); EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); })); @@ -4096,7 +4096,7 @@ TEST_P(SslSocketTest, ShutdownWithCloseNotify) { EXPECT_CALL(*client_read_filter, onNewConnection()) .WillOnce(Return(Network::FilterStatus::Continue)); EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), true)) + EXPECT_CALL(*client_read_filter, onData(BufferString("hello"), true)) .WillOnce(Invoke([&](Buffer::Instance& read_buffer, bool) -> Network::FilterStatus { read_buffer.drain(read_buffer.length()); client_connection->close(Network::ConnectionCloseType::NoFlush); @@ -4191,7 +4191,7 @@ TEST_P(SslSocketTest, ShutdownWithoutCloseNotify) { EXPECT_CALL(*client_read_filter, onNewConnection()) .WillOnce(Return(Network::FilterStatus::Continue)); EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), false)) + EXPECT_CALL(*client_read_filter, onData(BufferString("hello"), false)) .WillOnce(Invoke([&](Buffer::Instance& read_buffer, bool) -> Network::FilterStatus { read_buffer.drain(read_buffer.length()); // Close without sending close_notify alert. @@ -4205,7 +4205,7 @@ TEST_P(SslSocketTest, ShutdownWithoutCloseNotify) { EXPECT_CALL(*server_read_filter, onNewConnection()) .WillOnce(Return(Network::FilterStatus::Continue)); - EXPECT_CALL(*server_read_filter, onData(BufferStringEqual(""), true)) + EXPECT_CALL(*server_read_filter, onData(BufferString(""), true)) .WillOnce(Invoke([&](Buffer::Instance&, bool) -> Network::FilterStatus { // Close without sending close_notify alert. const SslHandshakerImpl* ssl_socket = diff --git a/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc b/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc index f6eb928ea5822..71fa10e5111e9 100644 --- a/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc +++ b/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc @@ -128,13 +128,13 @@ TEST_F(AsyncFileHandleTest, WriteReadClose) { })); resolveFileActions(); ASSERT_OK(read_status); - EXPECT_THAT(*read_status.value(), BufferStringEqual("help!")); + EXPECT_THAT(*read_status.value(), BufferString("help!")); ASSERT_OK(handle->read(dispatcher_.get(), 2, 3, [&](absl::StatusOr status) { second_read_status = std::move(status); })); resolveFileActions(); ASSERT_OK(second_read_status); - EXPECT_THAT(*second_read_status.value(), BufferStringEqual("lp!")); + EXPECT_THAT(*second_read_status.value(), BufferString("lp!")); close(handle); } @@ -295,7 +295,7 @@ TEST_F(AsyncFileHandleTest, OpenExistingReadWriteCanReadAndWrite) { })); resolveFileActions(); ASSERT_OK(read_status); - EXPECT_THAT(*read_status.value(), BufferStringEqual("help me!")); + EXPECT_THAT(*read_status.value(), BufferString("help me!")); close(handle); } @@ -318,7 +318,7 @@ TEST_F(AsyncFileHandleTest, TruncateReducesFileSize) { read_result = std::move(result); })); resolveFileActions(); - EXPECT_THAT(read_result, IsOkAndHolds(Pointee(BufferStringEqual("hello")))); + EXPECT_THAT(read_result, IsOkAndHolds(Pointee(BufferString("hello")))); close(handle); } @@ -357,7 +357,7 @@ TEST_F(AsyncFileHandleWithMockPosixTest, PartialReadReturnsPartialResult) { })); resolveFileActions(); EXPECT_OK(read_status); - EXPECT_THAT(*read_status.value(), BufferStringEqual("hel")); + EXPECT_THAT(*read_status.value(), BufferString("hel")); close(handle); } diff --git a/test/extensions/filters/http/bandwidth_limit/filter_test.cc b/test/extensions/filters/http/bandwidth_limit/filter_test.cc index f7f8a6f463e48..4eb2ab90a0639 100644 --- a/test/extensions/filters/http/bandwidth_limit/filter_test.cc +++ b/test/extensions/filters/http/bandwidth_limit/filter_test.cc @@ -123,7 +123,7 @@ TEST_F(FilterTest, LimitOnDecode) { EXPECT_EQ(5, findGauge("test.http_bandwidth_limit.request_incoming_size")); EXPECT_EQ(5, findCounter("test.http_bandwidth_limit.request_incoming_total_size")); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual("hello"), false)); + injectDecodedDataToFilterChain(BufferString("hello"), false)); token_timer->invokeCallback(); EXPECT_EQ(0, findCounter("test.http_bandwidth_limit.request_enforced")); EXPECT_EQ(5, findGauge("test.http_bandwidth_limit.request_allowed_size")); @@ -144,7 +144,7 @@ TEST_F(FilterTest, LimitOnDecode) { EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(decoder_filter_callbacks_, onDecoderFilterBelowWriteBufferLowWatermark()); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(1024, 'a')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(1024, 'a')), false)); token_timer->invokeCallback(); EXPECT_EQ(1, findCounter("test.http_bandwidth_limit.request_enforced")); EXPECT_EQ(1024, findGauge("test.http_bandwidth_limit.request_allowed_size")); @@ -156,7 +156,7 @@ TEST_F(FilterTest, LimitOnDecode) { time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'a')), false)); token_timer->invokeCallback(); EXPECT_EQ(2, findCounter("test.http_bandwidth_limit.request_enforced")); EXPECT_EQ(51, findGauge("test.http_bandwidth_limit.request_allowed_size")); @@ -175,7 +175,7 @@ TEST_F(FilterTest, LimitOnDecode) { time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'a')), false)); token_timer->invokeCallback(); EXPECT_EQ(3, findCounter("test.http_bandwidth_limit.request_enforced")); EXPECT_EQ(51, findGauge("test.http_bandwidth_limit.request_allowed_size")); @@ -185,7 +185,7 @@ TEST_F(FilterTest, LimitOnDecode) { // buffered. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'b')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'b')), false)); token_timer->invokeCallback(); EXPECT_EQ(3, findCounter("test.http_bandwidth_limit.request_enforced")); EXPECT_EQ(51, findGauge("test.http_bandwidth_limit.request_allowed_size")); @@ -202,7 +202,7 @@ TEST_F(FilterTest, LimitOnDecode) { EXPECT_EQ(1024, findGauge("test.http_bandwidth_limit.request_incoming_size")); EXPECT_EQ(2206, findCounter("test.http_bandwidth_limit.request_incoming_total_size")); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(1024, 'c')), true)); + injectDecodedDataToFilterChain(BufferString(std::string(1024, 'c')), true)); token_timer->invokeCallback(); EXPECT_EQ(3, findCounter("test.http_bandwidth_limit.request_enforced")); EXPECT_EQ(1024, findGauge("test.http_bandwidth_limit.request_allowed_size")); @@ -251,7 +251,7 @@ TEST_F(FilterTest, LimitOnEncode) { EXPECT_EQ(5, findGauge("test.http_bandwidth_limit.response_incoming_size")); EXPECT_EQ(5, findCounter("test.http_bandwidth_limit.response_incoming_total_size")); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual("hello"), false)); + injectEncodedDataToFilterChain(BufferString("hello"), false)); token_timer->invokeCallback(); EXPECT_EQ(0, findCounter("test.http_bandwidth_limit.response_enforced")); EXPECT_EQ(5, findGauge("test.http_bandwidth_limit.response_allowed_size")); @@ -271,7 +271,7 @@ TEST_F(FilterTest, LimitOnEncode) { EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, onEncoderFilterBelowWriteBufferLowWatermark()); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(1024, 'a')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(1024, 'a')), false)); token_timer->invokeCallback(); EXPECT_EQ(1, findGauge("test.http_bandwidth_limit.response_pending")); EXPECT_EQ(1, findCounter("test.http_bandwidth_limit.response_enforced")); @@ -284,7 +284,7 @@ TEST_F(FilterTest, LimitOnEncode) { time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'a')), false)); token_timer->invokeCallback(); EXPECT_EQ(2, findCounter("test.http_bandwidth_limit.response_enforced")); EXPECT_EQ(51, findGauge("test.http_bandwidth_limit.response_allowed_size")); @@ -298,7 +298,7 @@ TEST_F(FilterTest, LimitOnEncode) { time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'a')), false)); token_timer->invokeCallback(); EXPECT_EQ(3, findCounter("test.http_bandwidth_limit.response_enforced")); EXPECT_EQ(51, findGauge("test.http_bandwidth_limit.response_allowed_size")); @@ -308,7 +308,7 @@ TEST_F(FilterTest, LimitOnEncode) { // buffered. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'b')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'b')), false)); token_timer->invokeCallback(); EXPECT_EQ(3, findCounter("test.http_bandwidth_limit.response_enforced")); EXPECT_EQ(51, findGauge("test.http_bandwidth_limit.response_allowed_size")); @@ -325,7 +325,7 @@ TEST_F(FilterTest, LimitOnEncode) { EXPECT_EQ(1024, findGauge("test.http_bandwidth_limit.response_incoming_size")); EXPECT_EQ(2206, findCounter("test.http_bandwidth_limit.response_incoming_total_size")); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(1024, 'c')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(1024, 'c')), false)); token_timer->invokeCallback(); EXPECT_EQ(0, findGauge("test.http_bandwidth_limit.response_pending")); EXPECT_EQ(3, findCounter("test.http_bandwidth_limit.response_enforced")); @@ -377,14 +377,14 @@ TEST_F(FilterTest, LimitOnDecodeAndEncode) { EXPECT_CALL(*request_timer, enableTimer(std::chrono::milliseconds(0), _)); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(dec_data1, false)); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual("hello"), false)); + injectDecodedDataToFilterChain(BufferString("hello"), false)); request_timer->invokeCallback(); Buffer::OwnedImpl enc_data1("world!"); EXPECT_CALL(*response_timer, enableTimer(std::chrono::milliseconds(0), _)); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(enc_data1, false)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual("world!"), false)); + injectEncodedDataToFilterChain(BufferString("world!"), false)); response_timer->invokeCallback(); // Advance time by 1s which should refill all tokens. @@ -404,31 +404,31 @@ TEST_F(FilterTest, LimitOnDecodeAndEncode) { EXPECT_CALL(*request_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(decoder_filter_callbacks_, onDecoderFilterBelowWriteBufferLowWatermark()); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(1024, 'd')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(1024, 'd')), false)); request_timer->invokeCallback(); // Encoder will not be able to write any bytes due to insufficient tokens. EXPECT_CALL(*response_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string("")), false)); + injectEncodedDataToFilterChain(BufferString(std::string("")), false)); response_timer->invokeCallback(); // Fire timer, also advance time by 1 unit. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'd')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'd')), false)); request_timer->invokeCallback(); // Encoder will not be able to write any bytes due to insufficient tokens. EXPECT_CALL(*response_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string("")), false)); + injectEncodedDataToFilterChain(BufferString(std::string("")), false)); response_timer->invokeCallback(); // Fire timer, also advance time by 1 unit. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*response_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'e')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'e')), false)); response_timer->invokeCallback(); // Get new data with current data buffered, not end_stream. @@ -439,14 +439,14 @@ TEST_F(FilterTest, LimitOnDecodeAndEncode) { time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*response_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'e')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'e')), false)); response_timer->invokeCallback(); // Fire timer, also advance time. No time enable because there is nothing // buffered. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'b')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'b')), false)); response_timer->invokeCallback(); // Advance time by 1s for a full refill. @@ -461,9 +461,9 @@ TEST_F(FilterTest, LimitOnDecodeAndEncode) { EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(dec_data4, true)); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(enc_data4, true)); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'd')), true)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'd')), true)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(960, 'e')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(960, 'e')), false)); EXPECT_CALL(encoder_filter_callbacks_, continueEncoding()); request_timer->invokeCallback(); @@ -521,7 +521,7 @@ TEST_F(FilterTest, WithTrailers) { EXPECT_CALL(*request_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'd')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'd')), false)); EXPECT_EQ(Http::FilterTrailersStatus::StopIteration, filter_->decodeTrailers(request_trailers_)); request_timer->invokeCallback(); EXPECT_EQ(1, findGauge("test.http_bandwidth_limit.request_pending")); @@ -529,7 +529,7 @@ TEST_F(FilterTest, WithTrailers) { // Fire timer, also advance time by 1 unit. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'd')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'd')), false)); request_timer->invokeCallback(); EXPECT_EQ(0, findGauge("test.http_bandwidth_limit.request_pending")); @@ -537,14 +537,14 @@ TEST_F(FilterTest, WithTrailers) { time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*response_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'e')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'e')), false)); response_timer->invokeCallback(); EXPECT_EQ(Http::FilterTrailersStatus::StopIteration, filter_->encodeTrailers(response_trailers_)); EXPECT_EQ(1, findGauge("test.http_bandwidth_limit.response_pending")); time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(5, 'e')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(5, 'e')), false)); response_timer->invokeCallback(); EXPECT_EQ(0, findGauge("test.http_bandwidth_limit.response_pending")); // No delay triggers since enable_response_trailers is false by default @@ -597,13 +597,13 @@ TEST_F(FilterTest, WithTrailersNoEndStream) { EXPECT_CALL(*request_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'd')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'd')), false)); request_timer->invokeCallback(); // Fire timer, also advance time by 1 unit. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(decoder_filter_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'd')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'd')), false)); request_timer->invokeCallback(); EXPECT_EQ(1, findGauge("test.http_bandwidth_limit.request_pending")); EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_)); @@ -613,12 +613,12 @@ TEST_F(FilterTest, WithTrailersNoEndStream) { time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*response_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'e')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'e')), false)); response_timer->invokeCallback(); time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(5, 'e')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(5, 'e')), false)); response_timer->invokeCallback(); EXPECT_EQ(1, findGauge("test.http_bandwidth_limit.response_pending")); EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_)); diff --git a/test/extensions/filters/http/cache_v2/cache_filter_test.cc b/test/extensions/filters/http/cache_v2/cache_filter_test.cc index e1acd003e3d3f..464f8620fa465 100644 --- a/test/extensions/filters/http/cache_v2/cache_filter_test.cc +++ b/test/extensions/filters/http/cache_v2/cache_filter_test.cc @@ -405,8 +405,8 @@ TEST_F(CacheFilterTest, GetBodyAdvancesRequestRange) { captured_lookup_callback_(std::make_unique( ActiveLookupResult{std::move(mock_http_source_), CacheEntryStatus::Miss})); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(IsSupersetOfHeaders(response_headers_), false)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual("hello"), false)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual(" world!"), true)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString("hello"), false)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString(" world!"), true)); captured_get_headers_callback_(createHeaderMap(response_headers_), EndStream::More); captured_get_body_callback_(std::make_unique("hello"), EndStream::More); @@ -426,8 +426,8 @@ TEST_F(CacheFilterTest, GetBodyReturningNullBufferAndEndStreamCompletes) { captured_lookup_callback_(std::make_unique( ActiveLookupResult{std::move(mock_http_source_), CacheEntryStatus::Hit})); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(IsSupersetOfHeaders(response_headers_), false)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual("hello"), false)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual(""), true)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString("hello"), false)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString(""), true)); captured_get_headers_callback_(createHeaderMap(response_headers_), EndStream::More); captured_get_body_callback_(std::make_unique("hello"), EndStream::More); @@ -448,7 +448,7 @@ TEST_F(CacheFilterTest, GetBodyReturningNullBufferAndNoEndStreamGoesOnToTrailers captured_lookup_callback_(std::make_unique( ActiveLookupResult{std::move(mock_http_source_), CacheEntryStatus::Hit})); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(IsSupersetOfHeaders(response_headers_), false)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual("hello"), false)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString("hello"), false)); EXPECT_CALL(decoder_callbacks_, encodeTrailers_(IsSupersetOfHeaders(response_trailers_))); captured_get_headers_callback_(createHeaderMap(response_headers_), EndStream::More); @@ -494,7 +494,7 @@ TEST_F(CacheFilterTest, EndOfRequestedRangeEndsStreamWhenUpstreamDoesNot) { captured_lookup_callback_(std::make_unique( ActiveLookupResult{std::move(mock_http_source_), CacheEntryStatus::Hit})); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(IsSupersetOfHeaders(response_headers_), false)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual("hello"), true)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString("hello"), true)); captured_get_headers_callback_(createHeaderMap(response_headers_), EndStream::More); captured_get_body_callback_(std::make_unique("hello"), EndStream::More); @@ -515,7 +515,7 @@ TEST_F(CacheFilterTest, EndOfRequestedRangeEndsTraileredStreamWithoutSendingTrai captured_lookup_callback_(std::make_unique( ActiveLookupResult{std::move(mock_http_source_), CacheEntryStatus::Hit})); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(IsSupersetOfHeaders(response_headers_), false)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual("beep"), true)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString("beep"), true)); captured_get_headers_callback_(createHeaderMap(response_headers_), EndStream::More); // More here, at the end of the source data, indicates that trailers exist. @@ -534,8 +534,9 @@ TEST_F(CacheFilterTest, FilterDestroyedDuringEncodeDataPreventsFurtherRequests) captured_lookup_callback_(std::make_unique( ActiveLookupResult{std::move(mock_http_source_), CacheEntryStatus::Hit})); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(IsSupersetOfHeaders(response_headers_), false)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual("hello"), false)) - .WillOnce([&filter]() { filter->onDestroy(); }); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString("hello"), false)).WillOnce([&filter]() { + filter->onDestroy(); + }); captured_get_headers_callback_(createHeaderMap(response_headers_), EndStream::More); captured_get_body_callback_(std::make_unique("hello"), EndStream::More); @@ -554,7 +555,7 @@ TEST_F(CacheFilterTest, WatermarkDelaysUpstreamRequestingMore) { captured_lookup_callback_(std::make_unique( ActiveLookupResult{std::move(mock_http_source_), CacheEntryStatus::Hit})); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(IsSupersetOfHeaders(response_headers_), false)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual("hello"), false)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString("hello"), false)); captured_get_headers_callback_(createHeaderMap(response_headers_), EndStream::More); filter->onAboveWriteBufferHighWatermark(); @@ -573,7 +574,7 @@ TEST_F(CacheFilterTest, WatermarkDelaysUpstreamRequestingMore) { // Unwatermarking back to zero should release the request. filter->onBelowWriteBufferLowWatermark(); EXPECT_THAT(captured_get_body_callback_, NotNull()); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual("world"), true)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString("world"), true)); captured_get_body_callback_(std::make_unique("world"), EndStream::End); EXPECT_THAT(decoder_callbacks_.details(), Eq("cache.response_from_cache_filter")); } @@ -602,8 +603,8 @@ TEST_F(CacheFilterTest, DeepRecursionOfGetBodyDoesntOverflowStack) { captured_lookup_callback_(std::make_unique( ActiveLookupResult{std::move(mock_http_source_), CacheEntryStatus::Hit})); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(IsSupersetOfHeaders(response_headers_), false)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual("a"), false)).Times(max_depth - 1); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual("a"), true)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString("a"), false)).Times(max_depth - 1); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString("a"), true)); captured_get_headers_callback_(createHeaderMap(response_headers_), EndStream::More); EXPECT_THAT(decoder_callbacks_.details(), Eq("cache.response_from_cache_filter")); diff --git a/test/extensions/filters/http/cache_v2/cache_sessions_test.cc b/test/extensions/filters/http/cache_v2/cache_sessions_test.cc index b9b89e73dd6c0..a731c7d3f259d 100644 --- a/test/extensions/filters/http/cache_v2/cache_sessions_test.cc +++ b/test/extensions/filters/http/cache_v2/cache_sessions_test.cc @@ -485,9 +485,9 @@ TEST_F(CacheSessionsTest, .WillOnce([&](Event::Dispatcher&, AdjustedByteRange, GetBodyCallback&& cb) { cb(std::make_unique("abc"), EndStream::More); }); - EXPECT_CALL(body_callback1, Call(Pointee(BufferStringEqual("abc")), EndStream::More)); - EXPECT_CALL(body_callback2, Call(Pointee(BufferStringEqual("ab")), EndStream::More)); - EXPECT_CALL(body_callback3, Call(Pointee(BufferStringEqual("bc")), EndStream::More)); + EXPECT_CALL(body_callback1, Call(Pointee(BufferString("abc")), EndStream::More)); + EXPECT_CALL(body_callback2, Call(Pointee(BufferString("ab")), EndStream::More)); + EXPECT_CALL(body_callback3, Call(Pointee(BufferString("bc")), EndStream::More)); progress->onBodyInserted(AdjustedByteRange(0, 3), false); pumpDispatcher(); Mock::VerifyAndClearExpectations(mock_cache_reader); @@ -503,7 +503,7 @@ TEST_F(CacheSessionsTest, .WillOnce([&](Event::Dispatcher&, AdjustedByteRange, GetBodyCallback&& cb) { cb(std::make_unique("abc"), EndStream::More); }); - EXPECT_CALL(body_callback6, Call(Pointee(BufferStringEqual("abc")), EndStream::More)); + EXPECT_CALL(body_callback6, Call(Pointee(BufferString("abc")), EndStream::More)); result3->http_source_->getBody(AdjustedByteRange(0, 3), body_callback6.AsStdFunction()); pumpDispatcher(); Mock::VerifyAndClearExpectations(&body_callback6); @@ -513,8 +513,8 @@ TEST_F(CacheSessionsTest, .WillOnce([&](Event::Dispatcher&, AdjustedByteRange, GetBodyCallback&& cb) { cb(std::make_unique("de"), EndStream::More); }); - EXPECT_CALL(body_callback4, Call(Pointee(BufferStringEqual("de")), EndStream::More)); - EXPECT_CALL(body_callback5, Call(Pointee(BufferStringEqual("de")), EndStream::More)); + EXPECT_CALL(body_callback4, Call(Pointee(BufferString("de")), EndStream::More)); + EXPECT_CALL(body_callback5, Call(Pointee(BufferString("de")), EndStream::More)); progress->onBodyInserted(AdjustedByteRange(3, 5), false); pumpDispatcher(); Http::TestResponseTrailerMapImpl trailers{{"x-test", "yes"}}; @@ -561,7 +561,7 @@ TEST_F(CacheSessionsTest, CacheHitGoesDirectlyToCachedResponses) { Call(Pointee(IsSupersetOfHeaders(*response_headers)), EndStream::More)); result->http_source_->getHeaders(header_callback.AsStdFunction()); MockFunction body_callback1, body_callback2; - EXPECT_CALL(body_callback1, Call(Pointee(BufferStringEqual("abcde")), EndStream::More)); + EXPECT_CALL(body_callback1, Call(Pointee(BufferString("abcde")), EndStream::More)); EXPECT_CALL(*mock_cache_reader, getBody(_, RangeIs(0, 5), _)) .WillOnce([&](Event::Dispatcher&, AdjustedByteRange, GetBodyCallback cb) { cb(std::make_unique("abcde"), EndStream::More); diff --git a/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc b/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc index 5ff99d105d25b..fb237e7e33cd0 100644 --- a/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc +++ b/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc @@ -379,7 +379,7 @@ TEST_P(HttpCacheImplementationTest, ReadingFromBodyDuringInsert) { pumpDispatcher(); Mock::VerifyAndClearExpectations(mock_progress_receiver.get()); MockFunction mock_body_callback; - EXPECT_CALL(mock_body_callback, Call(Pointee(BufferStringEqual("Hello ")), EndStream::More)); + EXPECT_CALL(mock_body_callback, Call(Pointee(BufferString("Hello ")), EndStream::More)); cache_reader->getBody(dispatcher(), AdjustedByteRange(0, 6), mock_body_callback.AsStdFunction()); pumpDispatcher(); Mock::VerifyAndClearExpectations(&mock_body_callback); @@ -387,7 +387,7 @@ TEST_P(HttpCacheImplementationTest, ReadingFromBodyDuringInsert) { EXPECT_CALL(*mock_progress_receiver, onBodyInserted(RangeIs(6, 11), true)); get_body_2(std::make_unique("World"), EndStream::End); pumpDispatcher(); - EXPECT_CALL(mock_body_callback, Call(Pointee(BufferStringEqual("Hello World")), EndStream::More)); + EXPECT_CALL(mock_body_callback, Call(Pointee(BufferString("Hello World")), EndStream::More)); cache_reader->getBody(dispatcher(), AdjustedByteRange(0, 11), mock_body_callback.AsStdFunction()); pumpDispatcher(); Mock::VerifyAndClearExpectations(&mock_body_callback); diff --git a/test/extensions/filters/http/cache_v2/upstream_request_test.cc b/test/extensions/filters/http/cache_v2/upstream_request_test.cc index b97b4f0c569fa..ff4a120038e0f 100644 --- a/test/extensions/filters/http/cache_v2/upstream_request_test.cc +++ b/test/extensions/filters/http/cache_v2/upstream_request_test.cc @@ -112,7 +112,7 @@ TEST_F(UpstreamRequestTest, BodyRequestedThenArrivedDeliversBody) { Buffer::OwnedImpl data{"hello"}; MockFunction body_cb; upstream_request_->getBody(AdjustedByteRange{0, 5}, body_cb.AsStdFunction()); - EXPECT_CALL(body_cb, Call(Pointee(BufferStringEqual("hello")), EndStream::End)); + EXPECT_CALL(body_cb, Call(Pointee(BufferString("hello")), EndStream::End)); http_callbacks_->onData(data, true); http_callbacks_->onComplete(); } @@ -122,7 +122,7 @@ TEST_F(UpstreamRequestTest, BodyArrivedThenOversizedRequestedDeliversBody) { MockFunction body_cb; http_callbacks_->onData(data, true); http_callbacks_->onComplete(); - EXPECT_CALL(body_cb, Call(Pointee(BufferStringEqual("hello")), EndStream::End)); + EXPECT_CALL(body_cb, Call(Pointee(BufferString("hello")), EndStream::End)); upstream_request_->getBody(AdjustedByteRange{0, 99}, body_cb.AsStdFunction()); } @@ -132,9 +132,9 @@ TEST_F(UpstreamRequestTest, BodyArrivedThenRequestedInPiecesDeliversBody) { MockFunction body_cb2; http_callbacks_->onData(data, true); http_callbacks_->onComplete(); - EXPECT_CALL(body_cb1, Call(Pointee(BufferStringEqual("hel")), EndStream::More)); + EXPECT_CALL(body_cb1, Call(Pointee(BufferString("hel")), EndStream::More)); upstream_request_->getBody(AdjustedByteRange{0, 3}, body_cb1.AsStdFunction()); - EXPECT_CALL(body_cb2, Call(Pointee(BufferStringEqual("lo")), EndStream::End)); + EXPECT_CALL(body_cb2, Call(Pointee(BufferString("lo")), EndStream::End)); upstream_request_->getBody(AdjustedByteRange{3, 5}, body_cb2.AsStdFunction()); } @@ -143,10 +143,10 @@ TEST_F(UpstreamRequestTest, BodyAlternatingActionsDeliversBody) { MockFunction body_cb1; MockFunction body_cb2; upstream_request_->getBody(AdjustedByteRange{0, 3}, body_cb1.AsStdFunction()); - EXPECT_CALL(body_cb1, Call(Pointee(BufferStringEqual("hel")), EndStream::More)); + EXPECT_CALL(body_cb1, Call(Pointee(BufferString("hel")), EndStream::More)); http_callbacks_->onData(data, true); http_callbacks_->onComplete(); - EXPECT_CALL(body_cb2, Call(Pointee(BufferStringEqual("lo")), EndStream::End)); + EXPECT_CALL(body_cb2, Call(Pointee(BufferString("lo")), EndStream::End)); upstream_request_->getBody(AdjustedByteRange{3, 5}, body_cb2.AsStdFunction()); } @@ -157,12 +157,12 @@ TEST_F(UpstreamRequestTest, BodyInMultiplePiecesDeliversBody) { MockFunction body_cb1; MockFunction body_cb2; upstream_request_->getBody(AdjustedByteRange{0, 99}, body_cb1.AsStdFunction()); - EXPECT_CALL(body_cb1, Call(Pointee(BufferStringEqual("hello")), EndStream::More)); + EXPECT_CALL(body_cb1, Call(Pointee(BufferString("hello")), EndStream::More)); http_callbacks_->onData(data1, false); http_callbacks_->onData(data2, false); http_callbacks_->onData(data3, true); http_callbacks_->onComplete(); - EXPECT_CALL(body_cb2, Call(Pointee(BufferStringEqual("therebanana")), EndStream::End)); + EXPECT_CALL(body_cb2, Call(Pointee(BufferString("therebanana")), EndStream::End)); upstream_request_->getBody(AdjustedByteRange{5, 99}, body_cb2.AsStdFunction()); } @@ -178,7 +178,7 @@ TEST_F(UpstreamRequestTest, RequestingMoreBodyAfterCompletionReturnsNull) { MockFunction body_cb2; http_callbacks_->onData(data, true); http_callbacks_->onComplete(); - EXPECT_CALL(body_cb1, Call(Pointee(BufferStringEqual("hello")), EndStream::End)); + EXPECT_CALL(body_cb1, Call(Pointee(BufferString("hello")), EndStream::End)); upstream_request_->getBody(AdjustedByteRange{0, 99}, body_cb1.AsStdFunction()); EXPECT_CALL(body_cb2, Call(IsNull(), EndStream::End)); upstream_request_->getBody(AdjustedByteRange{5, 99}, body_cb2.AsStdFunction()); @@ -194,9 +194,9 @@ TEST_F(UpstreamRequestTest, RequestingMoreBodyAfterTrailersResumesAndEventuallyR http_callbacks_->onTrailers( std::make_unique(response_trailers_)); http_callbacks_->onComplete(); - EXPECT_CALL(body_cb1, Call(Pointee(BufferStringEqual("hel")), EndStream::More)); + EXPECT_CALL(body_cb1, Call(Pointee(BufferString("hel")), EndStream::More)); upstream_request_->getBody(AdjustedByteRange{0, 3}, body_cb1.AsStdFunction()); - EXPECT_CALL(body_cb2, Call(Pointee(BufferStringEqual("lo")), EndStream::More)); + EXPECT_CALL(body_cb2, Call(Pointee(BufferString("lo")), EndStream::More)); upstream_request_->getBody(AdjustedByteRange{3, 99}, body_cb2.AsStdFunction()); EXPECT_CALL(body_cb3, Call(IsNull(), EndStream::More)); upstream_request_->getBody(AdjustedByteRange{5, 99}, body_cb3.AsStdFunction()); @@ -270,7 +270,7 @@ TEST_F(UpstreamRequestWithRangeHeaderTest, RangeHeaderSkipsToExpectedStreamPos) Buffer::OwnedImpl data{"lo"}; MockFunction body_cb; upstream_request_->getBody(AdjustedByteRange{3, 5}, body_cb.AsStdFunction()); - EXPECT_CALL(body_cb, Call(Pointee(BufferStringEqual("lo")), EndStream::End)); + EXPECT_CALL(body_cb, Call(Pointee(BufferString("lo")), EndStream::End)); http_callbacks_->onData(data, true); http_callbacks_->onComplete(); } @@ -291,7 +291,7 @@ TEST_F(UpstreamRequestWithSmallBuffersTest, WatermarksPauseTheUpstream) { // TODO(ravenblack): validate that onBelowHighWatermark actions // are performed during onData, once it's possible to pause flow // from upstream. - EXPECT_CALL(body_cb, Call(Pointee(BufferStringEqual("hello")), EndStream::End)); + EXPECT_CALL(body_cb, Call(Pointee(BufferString("hello")), EndStream::End)); upstream_request_->getBody(AdjustedByteRange{0, 5}, body_cb.AsStdFunction()); } diff --git a/test/extensions/filters/http/common/stream_rate_limiter_test.cc b/test/extensions/filters/http/common/stream_rate_limiter_test.cc index 759ecb2ea9972..28341a1344a97 100644 --- a/test/extensions/filters/http/common/stream_rate_limiter_test.cc +++ b/test/extensions/filters/http/common/stream_rate_limiter_test.cc @@ -71,8 +71,7 @@ TEST_F(StreamRateLimiterTest, RateLimitOnSingleStream) { Buffer::OwnedImpl data1("hello"); EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0), _)); limiter_->writeData(data1, false); - EXPECT_CALL(decoder_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual("hello"), false)); + EXPECT_CALL(decoder_callbacks_, injectDecodedDataToFilterChain(BufferString("hello"), false)); token_timer->invokeCallback(); // Advance time by 1s which should refill all tokens. @@ -87,14 +86,14 @@ TEST_F(StreamRateLimiterTest, RateLimitOnSingleStream) { EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(decoder_callbacks_, onDecoderFilterBelowWriteBufferLowWatermark()); EXPECT_CALL(decoder_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(1024, 'a')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(1024, 'a')), false)); token_timer->invokeCallback(); // Fire timer, also advance time. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(decoder_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'a')), false)); token_timer->invokeCallback(); // Get new data with current data buffered, not end_stream. @@ -105,14 +104,14 @@ TEST_F(StreamRateLimiterTest, RateLimitOnSingleStream) { time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(decoder_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'a')), false)); token_timer->invokeCallback(); // Fire timer, also advance time. No timer enable because there is nothing // buffered. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(decoder_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(51, 'b')), false)); + injectDecodedDataToFilterChain(BufferString(std::string(51, 'b')), false)); token_timer->invokeCallback(); // Advance time by 1s for a full refill. @@ -124,7 +123,7 @@ TEST_F(StreamRateLimiterTest, RateLimitOnSingleStream) { Buffer::OwnedImpl data4(std::string(1024, 'c')); limiter_->writeData(data4, true); EXPECT_CALL(decoder_callbacks_, - injectDecodedDataToFilterChain(BufferStringEqual(std::string(1024, 'c')), true)); + injectDecodedDataToFilterChain(BufferString(std::string(1024, 'c')), true)); token_timer->invokeCallback(); limiter_->destroy(); diff --git a/test/extensions/filters/http/ext_authz/BUILD b/test/extensions/filters/http/ext_authz/BUILD index 18014166b846e..314eef098a656 100644 --- a/test/extensions/filters/http/ext_authz/BUILD +++ b/test/extensions/filters/http/ext_authz/BUILD @@ -32,6 +32,7 @@ envoy_extension_cc_test( "//source/extensions/filters/common/ext_authz:ext_authz_grpc_lib", "//source/extensions/filters/http/ext_authz", "//test/extensions/filters/common/ext_authz:ext_authz_mocks", + "//test/mocks/buffer:buffer_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:server_factory_context_mocks", diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index ea0bf9268010d..8ee5de2d7fdb9 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -20,6 +20,7 @@ #include "source/extensions/filters/http/ext_authz/ext_authz.h" #include "test/extensions/filters/common/ext_authz/mocks.h" +#include "test/mocks/buffer/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" @@ -51,11 +52,6 @@ namespace HttpFilters { namespace ExtAuthz { namespace { -// Matcher to convert a Buffer::Instance to its string representation for composition. -MATCHER_P(BufferString, m, "") { - return testing::ExplainMatchResult(m, arg->toString(), result_listener); -} - // Matcher to parse a buffer string into a CheckRequest proto. MATCHER_P(AsCheckRequest, m, "") { envoy::service::auth::v3::CheckRequest check_request; @@ -5671,10 +5667,10 @@ TEST_P(HttpFilterTestParam, PerRouteConfigurationIntegrationTest) { .WillOnce(Return(absl::StatusOr(mock_raw_grpc_client))); // Mock the sendRaw call with matcher-based validation for the gRPC authorization check. - EXPECT_CALL(*mock_raw_grpc_client, - sendRaw(_, _, - BufferString(AsCheckRequest(HasContextExtension("test_key", "test_value"))), - _, _, _)) + EXPECT_CALL( + *mock_raw_grpc_client, + sendRaw(_, _, BufferPtrString(AsCheckRequest(HasContextExtension("test_key", "test_value"))), + _, _, _)) .WillOnce([&](absl::string_view /*service_full_name*/, absl::string_view /*method_name*/, Buffer::InstancePtr&& /*request*/, Grpc::RawAsyncRequestCallbacks& callbacks, Tracing::Span& parent_span, diff --git a/test/extensions/filters/http/fault/fault_filter_test.cc b/test/extensions/filters/http/fault/fault_filter_test.cc index e1f0bc48da81b..f2d7d77a51666 100644 --- a/test/extensions/filters/http/fault/fault_filter_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_test.cc @@ -1420,7 +1420,7 @@ TEST_F(FaultFilterRateLimitTest, ResponseRateLimitEnabled) { EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0), _)); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(data1, false)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual("hello"), false)); + injectEncodedDataToFilterChain(BufferString("hello"), false)); token_timer->invokeCallback(); // Advance time by 1s which should refill all tokens. @@ -1435,14 +1435,14 @@ TEST_F(FaultFilterRateLimitTest, ResponseRateLimitEnabled) { EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, onEncoderFilterBelowWriteBufferLowWatermark()); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(1024, 'a')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(1024, 'a')), false)); token_timer->invokeCallback(); // Fire timer, also advance time. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'a')), false)); token_timer->invokeCallback(); // Get new data with current data buffered, not end_stream. @@ -1453,13 +1453,13 @@ TEST_F(FaultFilterRateLimitTest, ResponseRateLimitEnabled) { time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'a')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'a')), false)); token_timer->invokeCallback(); // Fire timer, also advance time. No time enable because there is nothing buffered. time_system_.advanceTimeWait(std::chrono::milliseconds(50)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(51, 'b')), false)); + injectEncodedDataToFilterChain(BufferString(std::string(51, 'b')), false)); token_timer->invokeCallback(); // Advance time by 1s for a full refill. @@ -1470,7 +1470,7 @@ TEST_F(FaultFilterRateLimitTest, ResponseRateLimitEnabled) { Buffer::OwnedImpl data4(std::string(1024, 'c')); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(data4, true)); EXPECT_CALL(encoder_filter_callbacks_, - injectEncodedDataToFilterChain(BufferStringEqual(std::string(1024, 'c')), true)); + injectEncodedDataToFilterChain(BufferString(std::string(1024, 'c')), true)); token_timer->invokeCallback(); filter_->onDestroy(); diff --git a/test/extensions/filters/http/file_server/filter_test.cc b/test/extensions/filters/http/file_server/filter_test.cc index f2f79d2b30920..3169ca20d10ee 100644 --- a/test/extensions/filters/http/file_server/filter_test.cc +++ b/test/extensions/filters/http/file_server/filter_test.cc @@ -447,9 +447,9 @@ TEST_F(FileServerFilterTest, GetRequestPausesWhenOverBufferLimit) { EXPECT_CALL(*mock_file_handle_, stat); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), false)); EXPECT_CALL(*mock_file_handle_, read(_, 0, chunk1.length(), _)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual(chunk1), false)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString(chunk1), false)); EXPECT_CALL(*mock_file_handle_, read(_, chunk1.length(), chunk2.length(), _)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual(chunk2), true)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString(chunk2), true)); } EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter->decodeHeaders(request_headers, true)); struct stat stat_result = {}; @@ -501,9 +501,9 @@ TEST_F(FileServerFilterTest, BufferLimitsDontPauseIfClearedBeforeActionCompletes EXPECT_CALL(*mock_file_handle_, stat); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), false)); EXPECT_CALL(*mock_file_handle_, read(_, 0, chunk1.length(), _)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual(chunk1), false)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString(chunk1), false)); EXPECT_CALL(*mock_file_handle_, read(_, chunk1.length(), chunk2.length(), _)); - EXPECT_CALL(decoder_callbacks_, encodeData(BufferStringEqual(chunk2), true)); + EXPECT_CALL(decoder_callbacks_, encodeData(BufferString(chunk2), true)); } EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter->decodeHeaders(request_headers, true)); struct stat stat_result = {}; diff --git a/test/extensions/filters/http/file_system_buffer/filter_test.cc b/test/extensions/filters/http/file_system_buffer/filter_test.cc index 7a0e3e1f52016..ec34383cbc903 100644 --- a/test/extensions/filters/http/file_system_buffer/filter_test.cc +++ b/test/extensions/filters/http/file_system_buffer/filter_test.cc @@ -57,7 +57,7 @@ class FileSystemBufferFilterTest : public testing::Test { } void expectWriteWithPosition(MockAsyncFileHandle handle, absl::string_view content, off_t offset) { - EXPECT_CALL(*handle, write(_, BufferStringEqual(std::string(content)), offset, _)); + EXPECT_CALL(*handle, write(_, BufferString(std::string(content)), offset, _)); } void completeWriteOfSize(size_t length) { mock_async_file_manager_->nextActionCompletes(absl::StatusOr{length}); diff --git a/test/extensions/filters/http/file_system_buffer/fragment_test.cc b/test/extensions/filters/http/file_system_buffer/fragment_test.cc index 59e1644fca733..c8726ba9c74ba 100644 --- a/test/extensions/filters/http/file_system_buffer/fragment_test.cc +++ b/test/extensions/filters/http/file_system_buffer/fragment_test.cc @@ -77,7 +77,7 @@ TEST_F(FileSystemBufferFilterFragmentTest, CreatesFragmentFromPartialBufferAndCo TEST_F(FileSystemBufferFilterFragmentTest, WritesAndReadsBack) { Buffer::OwnedImpl input("hello"); Fragment frag(input); - EXPECT_CALL(*handle_, write(_, BufferStringEqual("hello"), 123, _)) + EXPECT_CALL(*handle_, write(_, BufferString("hello"), 123, _)) .WillOnce([](Event::Dispatcher* dispatcher, Buffer::Instance&, off_t, absl::AnyInvocable)> callback) { dispatcher->post([callback = std::move(callback)]() mutable { std::move(callback)(5); }); @@ -124,7 +124,7 @@ TEST_F(FileSystemBufferFilterFragmentTest, WritesAndReadsBack) { TEST_F(FileSystemBufferFilterFragmentTest, ReturnsErrorOnWriteError) { Buffer::OwnedImpl input("hello"); Fragment frag(input); - EXPECT_CALL(*handle_, write(_, BufferStringEqual("hello"), 123, _)) + EXPECT_CALL(*handle_, write(_, BufferString("hello"), 123, _)) .WillOnce([](Event::Dispatcher* dispatcher, Buffer::Instance&, off_t, absl::AnyInvocable)> callback) { dispatcher->post([callback = std::move(callback)]() mutable { @@ -142,7 +142,7 @@ TEST_F(FileSystemBufferFilterFragmentTest, ReturnsErrorOnWriteError) { TEST_F(FileSystemBufferFilterFragmentTest, ReturnsErrorOnWriteIncomplete) { Buffer::OwnedImpl input("hello"); Fragment frag(input); - EXPECT_CALL(*handle_, write(_, BufferStringEqual("hello"), 123, _)) + EXPECT_CALL(*handle_, write(_, BufferString("hello"), 123, _)) .WillOnce([](Event::Dispatcher* dispatcher, Buffer::Instance&, off_t, absl::AnyInvocable)> callback) { dispatcher->post([callback = std::move(callback)]() mutable { std::move(callback)(2); }); diff --git a/test/extensions/filters/network/generic_proxy/proxy_test.cc b/test/extensions/filters/network/generic_proxy/proxy_test.cc index 5b1be9a26307a..d63341003559e 100644 --- a/test/extensions/filters/network/generic_proxy/proxy_test.cc +++ b/test/extensions/filters/network/generic_proxy/proxy_test.cc @@ -777,7 +777,7 @@ TEST_F(FilterTest, ActiveStreamSingleFrameFiltersContinueEncoding) { // Next filter is `mock_0`. EXPECT_EQ("mock_0", (*active_stream->nextEncoderHeaderFilterForTest())->filterConfigName()); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); EXPECT_CALL(*server_codec_, encode(_, _)) .WillOnce(Invoke([&](const StreamFrame&, EncodingContext&) { Buffer::OwnedImpl buffer; @@ -1006,7 +1006,7 @@ TEST_F(FilterTest, ActiveStreamMultipleFrameFiltersContinueEncoding) { .WillOnce(Return(CommonFilterStatus::StopIteration)) // StopIteration will be ignored. .WillOnce(Return(CommonFilterStatus::StopIteration)); // StopIteration will be ignored. - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); EXPECT_CALL(*server_codec_, encode(_, _)) .WillOnce(Invoke([&](const StreamFrame&, EncodingContext&) { Buffer::OwnedImpl buffer; @@ -1188,7 +1188,7 @@ TEST_F(FilterTest, SendLocalReplyAfterPreviousLocalReply) { return EncodingResult{4}; })); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); // The latest local reply will skip the filter chain processing and be sent to the downstream // directly. @@ -1247,7 +1247,7 @@ TEST_F(FilterTest, SendLocalReplyAfterPreviousUpstreamResponseHeaderIsSent) { return EncodingResult{4}; })); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); auto response = std::make_unique(); response->message_ = "anything"; @@ -1320,7 +1320,7 @@ TEST_F(FilterTest, ActiveStreamSendLocalReply) { return EncodingResult{4}; })); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); // Clean up the stream and log the access log. EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)); @@ -1407,7 +1407,7 @@ TEST_F(FilterTest, ActiveStreamSendLocalReplyWhenProcessingBody) { return EncodingResult{4}; })); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); // Clean up the stream and log the access log. EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)); @@ -1505,7 +1505,7 @@ TEST_F(FilterTest, ActiveStreamSendLocalReplyWhenTransferringBody) { return EncodingResult{4}; })); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); // Clean up the stream and log the access log. EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)); @@ -1600,7 +1600,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormally) { auto active_stream = filter_->activeStreamsForTest().begin()->get(); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); EXPECT_CALL(*server_codec_, encode(_, _)) .WillOnce(Invoke([&](const StreamFrame&, EncodingContext&) { @@ -1693,7 +1693,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithMultipleFrames) { .WillOnce(Return(false)); EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)).Times(2); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)).Times(2); EXPECT_CALL(*server_codec_, encode(_, _)) .Times(2) .WillRepeatedly(Invoke([&](const StreamFrame&, EncodingContext&) { @@ -1743,7 +1743,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithDrainClose) { auto active_stream = filter_->activeStreamsForTest().begin()->get(); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); EXPECT_CALL(*server_codec_, encode(_, _)) .WillOnce(Invoke([&](const StreamFrame&, EncodingContext&) { @@ -1790,7 +1790,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithStreamDrainClose) { auto active_stream = filter_->activeStreamsForTest().begin()->get(); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); EXPECT_CALL(*server_codec_, encode(_, _)) .WillOnce(Invoke([&](const StreamFrame&, EncodingContext&) { @@ -1859,7 +1859,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithTracing) { })); EXPECT_CALL(*span, finishSpan()); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); EXPECT_CALL(*server_codec_, encode(_, _)) .WillOnce(Invoke([&](const StreamFrame&, EncodingContext&) { @@ -1923,7 +1923,7 @@ TEST_F(FilterTest, NewStreamAndReplyNormallyWithTracingAndSamplingToTrue) { EXPECT_CALL(*span, setTag(_, _)).Times(testing::AnyNumber()); EXPECT_CALL(*span, finishSpan()); - EXPECT_CALL(filter_callbacks_.connection_, write(BufferStringEqual("test"), false)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferString("test"), false)); EXPECT_CALL(*server_codec_, encode(_, _)) .WillOnce(Invoke([&](const StreamFrame&, EncodingContext&) { diff --git a/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc b/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc index 7037445a519e9..9aacc3201b66c 100644 --- a/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc +++ b/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc @@ -889,7 +889,7 @@ TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedReadWithTokens) { time_source_.advanceTimeWait(std::chrono::milliseconds(100)); EXPECT_CALL(read_filter_callbacks_, - injectReadDataToFilterChain(BufferStringEqual("test data"), false)); + injectReadDataToFilterChain(BufferString("test data"), false)); filter_->onReadTokenTimer(); @@ -916,7 +916,7 @@ TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedWriteWithTokens) { time_source_.advanceTimeWait(std::chrono::milliseconds(100)); EXPECT_CALL(write_filter_callbacks_, - injectWriteDataToFilterChain(BufferStringEqual("test data"), false)); + injectWriteDataToFilterChain(BufferString("test data"), false)); EXPECT_CALL(write_filter_callbacks_, onBelowWriteBufferLowWatermark()); filter_->onWriteTokenTimer(); diff --git a/test/extensions/stats_sinks/common/statsd/statsd_test.cc b/test/extensions/stats_sinks/common/statsd/statsd_test.cc index 28d403747563a..546d6a636160b 100644 --- a/test/extensions/stats_sinks/common/statsd/statsd_test.cc +++ b/test/extensions/stats_sinks/common/statsd/statsd_test.cc @@ -67,7 +67,7 @@ class TcpStatsdSinkTest : public Event::TestUsingSimulatedTime, public testing:: TEST_F(TcpStatsdSinkTest, EmptyFlush) { InSequence s; expectCreateConnection(); - EXPECT_CALL(*connection_, write(BufferStringEqual(""), _)); + EXPECT_CALL(*connection_, write(BufferString(""), _)); sink_->flush(snapshot_); } @@ -98,10 +98,10 @@ TEST_F(TcpStatsdSinkTest, BasicFlow) { snapshot_.host_gauges_.push_back(host_gauge_snap); expectCreateConnection(); - EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.test_counter:1|c\n" - "envoy.test_host_counter:3|c\n" - "envoy.test_gauge:2|g\n" - "envoy.test_host_gauge:4|g\n"), + EXPECT_CALL(*connection_, write(BufferString("envoy.test_counter:1|c\n" + "envoy.test_host_counter:3|c\n" + "envoy.test_gauge:2|g\n" + "envoy.test_host_gauge:4|g\n"), _)); sink_->flush(snapshot_); @@ -115,7 +115,7 @@ TEST_F(TcpStatsdSinkTest, BasicFlow) { NiceMock timer; timer.name_ = "test_timer"; - EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.test_timer:5|ms\n"), _)); + EXPECT_CALL(*connection_, write(BufferString("envoy.test_timer:5|ms\n"), _)); sink_->onHistogramComplete(timer, 5); EXPECT_CALL(*connection_, close(Network::ConnectionCloseType::NoFlush)); @@ -130,28 +130,28 @@ TEST_F(TcpStatsdSinkTest, SiSuffix) { items.name_ = "items"; items.unit_ = Stats::Histogram::Unit::Unspecified; - EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.items:1|ms\n"), _)); + EXPECT_CALL(*connection_, write(BufferString("envoy.items:1|ms\n"), _)); sink_->onHistogramComplete(items, 1); NiceMock information; information.name_ = "information"; information.unit_ = Stats::Histogram::Unit::Bytes; - EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.information:2|ms\n"), _)); + EXPECT_CALL(*connection_, write(BufferString("envoy.information:2|ms\n"), _)); sink_->onHistogramComplete(information, 2); NiceMock duration_micro; duration_micro.name_ = "duration"; duration_micro.unit_ = Stats::Histogram::Unit::Microseconds; - EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.duration:3|ms\n"), _)); + EXPECT_CALL(*connection_, write(BufferString("envoy.duration:3|ms\n"), _)); sink_->onHistogramComplete(duration_micro, 3); NiceMock duration_milli; duration_milli.name_ = "duration"; duration_milli.unit_ = Stats::Histogram::Unit::Milliseconds; - EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.duration:4|ms\n"), _)); + EXPECT_CALL(*connection_, write(BufferString("envoy.duration:4|ms\n"), _)); sink_->onHistogramComplete(duration_milli, 4); EXPECT_CALL(*connection_, close(Network::ConnectionCloseType::NoFlush)); @@ -165,7 +165,7 @@ TEST_F(TcpStatsdSinkTest, ScaledPercent) { items.name_ = "items"; items.unit_ = Stats::Histogram::Unit::Percent; - EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.items:0.5|h\n"), _)); + EXPECT_CALL(*connection_, write(BufferString("envoy.items:0.5|h\n"), _)); sink_->onHistogramComplete(items, Stats::Histogram::PercentScale / 2); EXPECT_CALL(*connection_, close(Network::ConnectionCloseType::NoFlush)); @@ -205,7 +205,7 @@ TEST_F(TcpStatsdSinkTest, WithCustomPrefix) { snapshot_.counters_.push_back({1, counter}); expectCreateConnection(); - EXPECT_CALL(*connection_, write(BufferStringEqual("test_prefix.test_counter:1|c\n"), _)); + EXPECT_CALL(*connection_, write(BufferString("test_prefix.test_counter:1|c\n"), _)); sink_->flush(snapshot_); } @@ -252,7 +252,7 @@ TEST_F(TcpStatsdSinkTest, Overflow) { ->info_->trafficStats() ->upstream_cx_tx_bytes_buffered_.set(1024 * 1024 * 15); expectCreateConnection(); - EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.test_counter:1|c\n"), _)); + EXPECT_CALL(*connection_, write(BufferString("envoy.test_counter:1|c\n"), _)); sink_->flush(snapshot_); // Raise and make sure we don't write and kill connection. diff --git a/test/extensions/transport_sockets/http_11_proxy/connect_test.cc b/test/extensions/transport_sockets/http_11_proxy/connect_test.cc index 68f933d3487d6..39b11e04aea82 100644 --- a/test/extensions/transport_sockets/http_11_proxy/connect_test.cc +++ b/test/extensions/transport_sockets/http_11_proxy/connect_test.cc @@ -73,7 +73,7 @@ class Http11ConnectTest : public testing::TestWithParam Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -105,7 +105,7 @@ TEST_F(ProxyProtocolTest, BytesProcessedIncludesProxyProtocolHeader) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V1); initialize(config, nullptr); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -143,11 +143,11 @@ TEST_F(ProxyProtocolTest, ReturnsKeepOpenWhenWriteErrorIsAgain) { auto msg = Buffer::OwnedImpl("some data"); { InSequence s; - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance&) -> Api::IoCallUint64Result { return {0, Network::IoSocketError::getIoSocketEagainError()}; })); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -202,7 +202,7 @@ TEST_F(ProxyProtocolTest, V1IPV4LocalAddressWhenTransportOptionsAreNull) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V1); initialize(config, nullptr); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -227,7 +227,7 @@ TEST_F(ProxyProtocolTest, V1IPV4LocalAddressesWhenHeaderOptionsAreNull) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V1); initialize(config, std::make_shared()); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = 43; buffer.drain(length); @@ -252,7 +252,7 @@ TEST_F(ProxyProtocolTest, V1IPV6LocalAddressesWhenHeaderOptionsAreNull) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V1); initialize(config, std::make_shared()); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -286,7 +286,7 @@ TEST_F(ProxyProtocolTest, V1IPV4DownstreamAddresses) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V1); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -320,7 +320,7 @@ TEST_F(ProxyProtocolTest, V1IPV6DownstreamAddresses) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V1); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -345,7 +345,7 @@ TEST_F(ProxyProtocolTest, V2IPV4LocalCommandWhenTransportOptionsAreNull) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); initialize(config, nullptr); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -370,7 +370,7 @@ TEST_F(ProxyProtocolTest, V2IPV4LocalCommandWhenHeaderOptionsAreNull) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); initialize(config, std::make_shared()); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -404,7 +404,7 @@ TEST_F(ProxyProtocolTest, V2IPV4DownstreamAddresses) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -439,7 +439,7 @@ TEST_F(ProxyProtocolTest, V2IPV6DownstreamAddresses) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -502,7 +502,7 @@ TEST_F(ProxyProtocolTest, V2IPV4DownstreamAddressesAndTLVs) { pass_through_tlvs->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -543,7 +543,7 @@ TEST_F(ProxyProtocolTest, V2IPV4PassSpecificTLVs) { pass_through_tlvs->add_tlv_type(0x05); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -582,7 +582,7 @@ TEST_F(ProxyProtocolTest, V2IPV4PassEmptyTLVs) { config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -660,7 +660,7 @@ TEST_F(ProxyProtocolTest, V2IPV6DownstreamAddressesAndTLVs) { config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -698,7 +698,7 @@ TEST_F(ProxyProtocolTest, V2IPV6DownstreamAddressesAndTLVsWithoutPassConfig) { config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -742,7 +742,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromConfig) { host_added_tlvs->set_value("moredata"); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -803,7 +803,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { config_added_tlvs->set_value("moredata"); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -867,7 +867,7 @@ TEST_F(ProxyProtocolTest, V2CombinedPrecedenceHostConfigPassthrough) { config_added_tlvs->set_value("configValue"); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -1142,7 +1142,7 @@ TEST_F(ProxyProtocolTest, V2DuplicateTLVsInConfigAndMetadataHandledProperlyNoDup unique_tlv_entry->set_value("baz"); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -1198,7 +1198,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVMetadataInvalidFormat) { config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); @@ -1257,7 +1257,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVHostMetadataMissing) { config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); initialize(config, socket_options); - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + EXPECT_CALL(io_handle_, write(BufferString(expected_buff.toString()))) .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { auto length = buffer.length(); buffer.drain(length); diff --git a/test/extensions/upstreams/http/tcp/upstream_request_test.cc b/test/extensions/upstreams/http/tcp/upstream_request_test.cc index 898daf1024ca7..4b31b65f744fb 100644 --- a/test/extensions/upstreams/http/tcp/upstream_request_test.cc +++ b/test/extensions/upstreams/http/tcp/upstream_request_test.cc @@ -132,7 +132,7 @@ TEST_F(TcpUpstreamTest, Basic) { EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); // Proxy the data. - EXPECT_CALL(connection(), write(BufferStringEqual("foo"), false)); + EXPECT_CALL(connection(), write(BufferString("foo"), false)); Buffer::OwnedImpl buffer("foo"); tcp_upstream_->encodeData(buffer, false); @@ -142,12 +142,12 @@ TEST_F(TcpUpstreamTest, Basic) { // Forward data. Buffer::OwnedImpl response1("bar"); - EXPECT_CALL(mock_router_filter_, onUpstreamData(BufferStringEqual("bar"), _, false)); + EXPECT_CALL(mock_router_filter_, onUpstreamData(BufferString("bar"), _, false)); tcp_upstream_->onUpstreamData(response1, false); Buffer::OwnedImpl response2("eep"); EXPECT_CALL(mock_router_filter_, onUpstreamHeaders(_, _, _, _)).Times(0); - EXPECT_CALL(mock_router_filter_, onUpstreamData(BufferStringEqual("eep"), _, false)); + EXPECT_CALL(mock_router_filter_, onUpstreamData(BufferString("eep"), _, false)); tcp_upstream_->onUpstreamData(response2, false); } @@ -170,7 +170,7 @@ TEST_F(TcpUpstreamTest, V1Header) { EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); // Data is proxied as usual. - EXPECT_CALL(connection(), write(BufferStringEqual("foo"), false)); + EXPECT_CALL(connection(), write(BufferString("foo"), false)); Buffer::OwnedImpl buffer("foo"); tcp_upstream_->encodeData(buffer, false); } @@ -194,7 +194,7 @@ TEST_F(TcpUpstreamTest, V2Header) { EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); // Data is proxied as usual. - EXPECT_CALL(connection(), write(BufferStringEqual("foo"), false)); + EXPECT_CALL(connection(), write(BufferString("foo"), false)); Buffer::OwnedImpl buffer("foo"); tcp_upstream_->encodeData(buffer, false); } @@ -203,13 +203,13 @@ TEST_F(TcpUpstreamTest, TrailersEndStream) { // Swallow the headers. EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); - EXPECT_CALL(connection(), write(BufferStringEqual(""), true)); + EXPECT_CALL(connection(), write(BufferString(""), true)); Envoy::Http::TestRequestTrailerMapImpl trailers{{"foo", "bar"}}; tcp_upstream_->encodeTrailers(trailers); } TEST_F(TcpUpstreamTest, HeaderEndStreamHalfClose) { - EXPECT_CALL(connection(), write(BufferStringEqual(""), true)); + EXPECT_CALL(connection(), write(BufferString(""), true)); EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, true).ok()); } diff --git a/test/extensions/upstreams/http/udp/upstream_request_test.cc b/test/extensions/upstreams/http/udp/upstream_request_test.cc index 83f6c515ded75..24121817b1857 100644 --- a/test/extensions/upstreams/http/udp/upstream_request_test.cc +++ b/test/extensions/upstreams/http/udp/upstream_request_test.cc @@ -107,7 +107,7 @@ TEST_F(UdpUpstreamTest, ExchangeCapsules) { Buffer::InstancePtr received_data = std::make_unique(absl::HexStringToBytes("b1b2b3b4b5b6b7")); EXPECT_CALL(mock_upstream_to_downstream_, - decodeData(BufferStringEqual(decoded_capsule_fragment), false)); + decodeData(BufferString(decoded_capsule_fragment), false)); Envoy::MonotonicTime timestamp; udp_upstream_->processPacket(nullptr, nullptr, std::move(received_data), timestamp, /*tos=*/0, /*saved_cmsg=*/{}); diff --git a/test/integration/api_listener_integration_test.cc b/test/integration/api_listener_integration_test.cc index 44a0c14916454..3234bbf614b48 100644 --- a/test/integration/api_listener_integration_test.cc +++ b/test/integration/api_listener_integration_test.cc @@ -103,7 +103,7 @@ TEST_P(ApiListenerIntegrationTest, Basic) { Http::TestResponseHeaderMapImpl expected_response_headers{{":status", "200"}}; EXPECT_CALL(stream_encoder_, encodeHeaders(_, false)); EXPECT_CALL(stream_encoder_, encodeData(_, false)); - EXPECT_CALL(stream_encoder_, encodeData(BufferStringEqual(""), true)).WillOnce(Notify(&done)); + EXPECT_CALL(stream_encoder_, encodeData(BufferString(""), true)).WillOnce(Notify(&done)); // Send a headers-only request stream_decoder->get()->decodeHeaders( @@ -191,7 +191,7 @@ TEST_P(ApiListenerIntegrationTest, FromWorkerThread) { Http::TestResponseHeaderMapImpl expected_response_headers{{":status", "200"}}; EXPECT_CALL(stream_encoder_, encodeHeaders(_, false)); EXPECT_CALL(stream_encoder_, encodeData(_, false)); - EXPECT_CALL(stream_encoder_, encodeData(BufferStringEqual(""), true)).WillOnce(Notify(&done)); + EXPECT_CALL(stream_encoder_, encodeData(BufferString(""), true)).WillOnce(Notify(&done)); // Send a headers-only request stream_decoder->get()->decodeHeaders( diff --git a/test/mocks/buffer/mocks.h b/test/mocks/buffer/mocks.h index 768decce37ce0..100c77eb8d4ea 100644 --- a/test/mocks/buffer/mocks.h +++ b/test/mocks/buffer/mocks.h @@ -98,17 +98,12 @@ MATCHER_P(BufferEqual, rhs, testing::PrintToString(*rhs)) { return TestUtility::buffersEqual(arg, *rhs); } -MATCHER_P(BufferStringEqual, rhs, rhs) { - *result_listener << "\"" << arg.toString() << "\""; - - Buffer::OwnedImpl buffer(rhs); - return TestUtility::buffersEqual(arg, buffer); +MATCHER_P(BufferString, m, "") { + return testing::ExplainMatchResult(m, arg.toString(), result_listener); } -MATCHER_P(BufferStringContains, rhs, - std::string(negation ? "doesn't contain" : "contains") + " \"" + rhs + "\"") { - *result_listener << "\"" << arg.toString() << "\""; - return arg.toString().find(rhs) != std::string::npos; +MATCHER_P(BufferPtrString, m, "") { + return testing::ExplainMatchResult(m, arg->toString(), result_listener); } ACTION_P(AddBufferToString, target_string) { From 46718cbc1564e28875c94061ef11e8db40f09360 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Tue, 5 May 2026 14:44:23 -0700 Subject: [PATCH 218/750] Adds regression test for b701b03 & 1d37220 (#44279) Commit Message: Adds regression test for b701b03 & 1d37220 Additional Description: This add a regression test for b701b03 & 1d37220 that simulates the bug that we encountered before in production. b701b03 or 1d37220 fixes the underlying issue but didn't come with a test that exercises the code path that makes Wasm VM crash. We verified that this test fails without b701b03 and 1d37220 Risk Level: none Testing: only test Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a --------- Signed-off-by: Takeshi Yoneda --- test/extensions/filters/http/wasm/BUILD | 7 + .../filters/http/wasm/test_data/BUILD | 8 + .../http/wasm/test_data/deferred_data_rust.rs | 24 ++ .../http/wasm/wasm_filter_integration_test.cc | 253 ++++++++++++++++++ 4 files changed, 292 insertions(+) create mode 100644 test/extensions/filters/http/wasm/test_data/deferred_data_rust.rs diff --git a/test/extensions/filters/http/wasm/BUILD b/test/extensions/filters/http/wasm/BUILD index aeec85283107c..857cf1263379d 100644 --- a/test/extensions/filters/http/wasm/BUILD +++ b/test/extensions/filters/http/wasm/BUILD @@ -86,6 +86,8 @@ envoy_extension_cc_test( srcs = ["wasm_filter_integration_test.cc"], data = envoy_select_wasm_cpp_tests([ "//test/extensions/filters/http/wasm/test_data:test_cpp.wasm", + ]) + envoy_select_wasm_rust_tests([ + "//test/extensions/filters/http/wasm/test_data:deferred_data_rust.wasm", ]), extension_names = ["envoy.filters.http.wasm"], rbe_pool = "4core", @@ -97,13 +99,18 @@ envoy_extension_cc_test( "//source/common/crypto:utility_lib", "//source/common/http:message_lib", "//source/extensions/common/wasm:wasm_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", "//source/extensions/filters/http/wasm:config", "//test/extensions/common/wasm:wasm_runtime", + "//test/extensions/filters/http/common:empty_http_filter_config_lib", "//test/integration:http_integration_lib", + "//test/integration:http_protocol_integration_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", + "//test/test_common:registry_lib", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", + "@abseil-cpp//absl/synchronization", ], ) diff --git a/test/extensions/filters/http/wasm/test_data/BUILD b/test/extensions/filters/http/wasm/test_data/BUILD index 3fc28d3b23e8d..6c6e3ac2a9783 100644 --- a/test/extensions/filters/http/wasm/test_data/BUILD +++ b/test/extensions/filters/http/wasm/test_data/BUILD @@ -38,6 +38,14 @@ wasm_rust_binary( ], ) +wasm_rust_binary( + name = "deferred_data_rust.wasm", + srcs = ["deferred_data_rust.rs"], + deps = [ + "@proxy_wasm_rust_sdk//:proxy_wasm", + ], +) + wasm_rust_binary( name = "grpc_call_rust.wasm", srcs = ["grpc_call_rust.rs"], diff --git a/test/extensions/filters/http/wasm/test_data/deferred_data_rust.rs b/test/extensions/filters/http/wasm/test_data/deferred_data_rust.rs new file mode 100644 index 0000000000000..44a099485a416 --- /dev/null +++ b/test/extensions/filters/http/wasm/test_data/deferred_data_rust.rs @@ -0,0 +1,24 @@ +use proxy_wasm::traits::{Context, HttpContext}; +use proxy_wasm::types::*; + +proxy_wasm::main! {{ + proxy_wasm::set_http_context(|_, _| -> Box { Box::new(TestStream) }); +}} + +struct TestStream; + +impl Context for TestStream {} + +impl HttpContext for TestStream { + fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action { + Action::Continue + } + + fn on_http_request_body(&mut self, _: usize, _: bool) -> Action { + Action::Continue + } + + fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action { + Action::Continue + } +} diff --git a/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc b/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc index d9d69b6f4ad63..fed2c26af9449 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc @@ -1,8 +1,22 @@ +#include +#include + +#include "envoy/http/filter.h" +#include "envoy/network/connection.h" +#include "envoy/server/filter_config.h" + #include "source/extensions/common/wasm/wasm.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" #include "test/extensions/common/wasm/wasm_runtime.h" +#include "test/extensions/filters/http/common/empty_http_filter_config.h" #include "test/integration/http_integration.h" +#include "test/integration/http_protocol_integration.h" +#include "test/test_common/registry.h" +#include "test/test_common/utility.h" +#include "absl/synchronization/mutex.h" +#include "absl/synchronization/notification.h" #include "gtest/gtest.h" namespace Envoy { @@ -290,6 +304,245 @@ TEST_P(WasmFilterIntegrationTest, LargeRequestHitBufferLimit) { } } +struct WasmDeferredDataSetupFilterState { + absl::Mutex mu; + bool destroyed ABSL_GUARDED_BY(mu){false}; + bool decode_data_after_destroy ABSL_GUARDED_BY(mu){false}; + Http::StreamDecoderFilterCallbacks* callbacks ABSL_GUARDED_BY(mu){nullptr}; + Event::Dispatcher* dispatcher ABSL_GUARDED_BY(mu){nullptr}; + bool headers_processed ABSL_GUARDED_BY(mu){false}; +}; + +class WasmDeferredDataSetupFilter : public Http::PassThroughFilter { +public: + explicit WasmDeferredDataSetupFilter(std::shared_ptr state) + : state_(std::move(state)) {} + + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool) override { + bool first_request = false; + { + absl::MutexLock l(&state_->mu); + first_request = !state_->headers_processed; + } + // Only the first request triggers the read-disable that the bug reproduction + // depends on. Follow-up requests pass through normally so they reach the + // upstream and exercise the Wasm filter's reload path. + if (!first_request) { + return Http::FilterHeadersStatus::Continue; + } + decoder_callbacks_->onDecoderFilterAboveWriteBufferHighWatermark(); + { + absl::MutexLock l(&state_->mu); + state_->callbacks = decoder_callbacks_; + state_->dispatcher = &decoder_callbacks_->dispatcher(); + state_->headers_processed = true; + } + return Http::FilterHeadersStatus::Continue; + } + + Http::FilterDataStatus decodeData(Buffer::Instance&, bool) override { + absl::MutexLock l(&state_->mu); + if (state_->destroyed) { + state_->decode_data_after_destroy = true; + } + return Http::FilterDataStatus::Continue; + } + + void onDestroy() override { + absl::MutexLock l(&state_->mu); + state_->destroyed = true; + state_->callbacks = nullptr; + } + +private: + std::shared_ptr state_; +}; + +class WasmDeferredDataSetupFilterConfig + : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig { +public: + explicit WasmDeferredDataSetupFilterConfig( + std::shared_ptr state) + : EmptyHttpFilterConfig("setup-filter"), state_(std::move(state)) {} + + absl::StatusOr + createFilter(const std::string&, Server::Configuration::FactoryContext&) override { + return [state = state_](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter(std::make_shared(state)); + }; + } + +private: + std::shared_ptr state_; +}; + +class WasmDeferredDataTest : public HttpProtocolIntegrationTest {}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, WasmDeferredDataTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( + {Http::CodecType::HTTP2}, {Http::CodecType::HTTP1})), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +// Verifies that a deferred data-processing callback does not invoke +// proxy_on_request_body on a Wasm filter whose context has already been +// destroyed (via proxy_on_delete) during connection close. +// +// Filter chain: [Wasm passthrough filter] -> [setup filter] +// +// Scenario: +// 1. Setup filter calls readDisable(true) in decodeHeaders. +// 2. Client sends DATA + END_STREAM -> codec buffers the frame. +// 3. A posted callback calls readDisable(false) (schedules +// process_buffered_data_callback_) then closes the connection +// (destroys filter chain -> Wasm context is deleted via proxy_on_delete). +// 4. Without the fix, the deferred process_buffered_data_callback_ fires, +// calls decodeData on the Wasm filter with an already-deleted context_id, +// causing a Rust panic "invalid context_id" that disables the Wasm VM. +TEST_P(WasmDeferredDataTest, InvalidContextIdPanicOnDeferredData) { + // The bug is runtime-agnostic, but testing with v8 is sufficient. + auto runtimes = Extensions::Common::Wasm::wasmTestMatrix(false, false); + if (!absl::c_any_of(runtimes, [](const auto& p) { return std::get<0>(p) == "v8"; })) { + GTEST_SKIP() << "v8 runtime not available"; + } + + auto state = std::make_shared(); + + // Register the C++ setup filter. + WasmDeferredDataSetupFilterConfig filter_config(state); + Registry::InjectFactory registered( + filter_config); + + // Prepend the setup filter (will end up second in chain). + config_helper_.prependFilter(R"EOF( +name: setup-filter +)EOF"); + + // Prepend the Wasm filter (will end up first in chain — hit by deferred data). + // Use FAIL_RELOAD so that any Wasm panic during the test (i.e. the bug + // reproduction firing proxy_on_request_body on a destroyed context) puts the + // VM into RuntimeError; the next request through the filter then triggers a + // VM reload and bumps the wasm..vm_reload_* counters. With the fix in + // place, no panic occurs and these counters stay at 0. + const std::string wasm_filter = TestEnvironment::substitute(R"EOF( +name: envoy.filters.http.wasm +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + config: + name: deferred_data_regression + failure_policy: FAIL_RELOAD + reload_config: + backoff: + base_interval: 0.01s + vm_config: + runtime: envoy.wasm.runtime.v8 + code: + local: + filename: "{{ test_rundir }}/test/extensions/filters/http/wasm/test_data/deferred_data_rust.wasm" +)EOF"); + config_helper_.prependFilter(wasm_filter); + + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Send HEADERS only. The Wasm filter passes through; the setup filter + // calls readDisable(true) and sets headers_processed. + auto [request_encoder, response_decoder] = codec_client_->startRequest(default_request_headers_); + + // Wait until the server has processed decodeHeaders and readDisable is on. + { + absl::MutexLock l(&state->mu); + state->mu.Await(absl::Condition(&state->headers_processed)); + } + + // Record rx bytes before DATA so we can tell when it arrives. + uint64_t bytes_before_data = 0; + if (auto ctr = test_server_->counter("http.config_test.downstream_cx_rx_bytes_total")) { + bytes_before_data = ctr->value(); + } + + // Send DATA + END_STREAM. Because readDisable is on, the codec buffers + // the frame (body_buffered_ = true) instead of delivering to the filter chain. + codec_client_->sendData(request_encoder, 1024, true); + + // Wait until the DATA frame (1024 payload + 9-byte frame header) has been + // received by the server's HTTP/2 codec. + test_server_->waitForCounterGe("http.config_test.downstream_cx_rx_bytes_total", + bytes_before_data + 1033); + + // Grab the worker-thread dispatcher and callbacks. + Event::Dispatcher* conn_dispatcher; + Http::StreamDecoderFilterCallbacks* cbs; + { + absl::MutexLock l(&state->mu); + conn_dispatcher = state->dispatcher; + cbs = state->callbacks; + } + ASSERT_NE(conn_dispatcher, nullptr); + ASSERT_NE(cbs, nullptr); + + // Post a callback to the connection's worker-thread dispatcher: + // (a) readDisable(false) -> schedules process_buffered_data_callback_ + // (b) Close connection -> synchronously destroys filter chain + // After the callback returns, the event loop fires the deferred + // process_buffered_data_callback_. + absl::Notification first_callback_done; + conn_dispatcher->post([cbs, &first_callback_done]() { + cbs->onDecoderFilterBelowWriteBufferLowWatermark(); + auto conn_ref = cbs->connection(); + if (conn_ref.has_value()) { + const_cast(conn_ref.ref()) + .close(Network::ConnectionCloseType::NoFlush, "wasm-deferred-data-test"); + } + first_callback_done.Notify(); + }); + + // Wait for the client to see the disconnect. + ASSERT_TRUE(codec_client_->waitForDisconnect()); + first_callback_done.WaitForNotification(); + + // Post a fence callback: because process_buffered_data_callback_ was + // scheduled via scheduleCallbackCurrentIteration() before this post(), the + // event loop will fire it BEFORE this fence. Waiting guarantees that the + // deferred decodeData (and any resulting Wasm panic) has already happened. + absl::Notification event_loop_drained; + conn_dispatcher->post([&event_loop_drained]() { event_loop_drained.Notify(); }); + event_loop_drained.WaitForNotification(); + + // Check 1: the setup filter must not have seen decodeData after destroy. + { + absl::MutexLock l(&state->mu); + EXPECT_FALSE(state->decode_data_after_destroy) + << "decodeData was invoked after onDestroy — deferred processing " + "callback fired on the destroyed filter chain (use-after-free bug)"; + } + + // Check 2: send a normal follow-up request that traverses the Wasm filter on + // the same worker thread. With the bug present, the Wasm VM was put into + // RuntimeError by the deferred decodeData panic, and FAIL_RELOAD will trigger + // a reload here — incrementing one of the vm_reload_* counters. With the fix, + // the VM is healthy and no reload is attempted. + const uint64_t initial_rq_total = + test_server_->counter("http.config_test.downstream_rq_total")->value(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto follow_up_response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + test_server_->waitForCounterGe("http.config_test.downstream_rq_total", initial_rq_total + 1); + // The reload check (when the bug is present) runs on the worker thread as + // part of the Wasm filter's per-request setup; downstream_rq_total can tick + // before that work has finished. Post a fence to the same worker dispatcher + // and wait for it so any pending vm_reload_* increments have landed before + // we read the counters below. + absl::Notification follow_up_drained; + conn_dispatcher->post([&follow_up_drained]() { follow_up_drained.Notify(); }); + follow_up_drained.WaitForNotification(); + codec_client_->close(); + + constexpr absl::string_view stat_prefix = "wasm.deferred_data_regression."; + EXPECT_EQ(0, test_server_->counter(absl::StrCat(stat_prefix, "vm_reload_success"))->value()); + EXPECT_EQ(0, test_server_->counter(absl::StrCat(stat_prefix, "vm_reload_backoff"))->value()); + EXPECT_EQ(0, test_server_->counter(absl::StrCat(stat_prefix, "vm_reload_failure"))->value()); +} + } // namespace } // namespace Wasm } // namespace Extensions From 2343e0c0810dc2947245d1dea69064e38df56ccc Mon Sep 17 00:00:00 2001 From: aakugan <164153358+aakugan@users.noreply.github.com> Date: Wed, 6 May 2026 03:53:58 +0530 Subject: [PATCH 219/750] Refactor DownstreamReverseConnectionIOHandle close/shutdown (#44853) ## Commit Message Refactor DownstreamReverseConnectionIOHandle close/shutdown ## Additional Description We saw some crashes when doing lds on reverse connections to remove them one of the suspects is the double close of fds in the downstreamiohandle -> this causes badfs and assert failures elsewhere we currently do not know how to track it but the tests became more stable after this. Additionally removed some dead code which is not being used by anyone else. ## Testing Unit tests and local tests stablize after this. Signed-off-by: aakugan --- ...downstream_reverse_connection_io_handle.cc | 26 +---- .../downstream_reverse_connection_io_handle.h | 9 -- .../downstream_socket_interface/BUILD | 1 + ...tream_reverse_connection_io_handle_test.cc | 108 +++++++++++++----- 4 files changed, 84 insertions(+), 60 deletions(-) diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle.cc b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle.cc index 73e8dcceb6cba..0e392422d19b6 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle.cc +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle.cc @@ -28,6 +28,7 @@ DownstreamReverseConnectionIOHandle::~DownstreamReverseConnectionIOHandle() { debug, "DownstreamReverseConnectionIOHandle: destroying handle for FD: {} with connection key: {}", fd_, connection_key_); + SET_SOCKET_INVALID(fd_); } void DownstreamReverseConnectionIOHandle::onPingMessage() { @@ -48,16 +49,6 @@ Api::IoCallUint64Result DownstreamReverseConnectionIOHandle::close() { "DownstreamReverseConnectionIOHandle: closing handle for FD: {} with connection key: {}", fd_, connection_key_); - // If we're ignoring close calls during socket hand-off, just return success. - if (ignore_close_and_shutdown_) { - ENVOY_LOG( - debug, - "DownstreamReverseConnectionIOHandle: ignoring close() call during socket hand-off for " - "connection key: {}", - connection_key_); - return Api::ioCallUint64ResultNoError(); - } - // Prevent double-closing by checking if already closed. if (fd_ < 0) { ENVOY_LOG(debug, @@ -80,7 +71,8 @@ Api::IoCallUint64Result DownstreamReverseConnectionIOHandle::close() { if (owned_socket_) { owned_socket_.reset(); } - return IoSocketHandleImpl::close(); + SET_SOCKET_INVALID(fd_); + return Api::ioCallUint64ResultNoError(); } Api::SysCallIntResult DownstreamReverseConnectionIOHandle::shutdown(int how) { @@ -89,17 +81,11 @@ Api::SysCallIntResult DownstreamReverseConnectionIOHandle::shutdown(int how) { "key: {}", how, fd_, connection_key_); - // If shutdown is ignored during socket hand-off, return success. - if (ignore_close_and_shutdown_) { - ENVOY_LOG( - debug, - "DownstreamReverseConnectionIOHandle: ignoring shutdown() call during socket hand-off for " - "connection key: {}", - connection_key_); - return Api::SysCallIntResult{0, 0}; + if (owned_socket_) { + return owned_socket_->ioHandle().shutdown(how); } - return IoSocketHandleImpl::shutdown(how); + return Api::SysCallIntResult{0, 0}; } } // namespace ReverseConnection diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle.h b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle.h index 806d793bcf5c9..405b8a5e0ed6b 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle.h +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle.h @@ -42,13 +42,6 @@ class DownstreamReverseConnectionIOHandle : public RpingInterceptor { // Send the RPING response from here. void onPingMessage() override; - /** - * Tell this IO handle to ignore close() and shutdown() calls. - * This is called by the HTTP filter during socket hand-off to prevent - * the handed-off socket from being affected by connection cleanup. - */ - void ignoreCloseAndShutdown() { ignore_close_and_shutdown_ = true; } - /** * Get the owned socket for read-only access. */ @@ -61,8 +54,6 @@ class DownstreamReverseConnectionIOHandle : public RpingInterceptor { ReverseConnectionIOHandle* parent_; // Connection key for tracking this specific connection. std::string connection_key_; - // Flag to ignore close and shutdown calls during socket hand-off. - bool ignore_close_and_shutdown_{false}; }; } // namespace ReverseConnection diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD index cf198bf4f06a4..7396ea78be5b6 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD @@ -80,6 +80,7 @@ envoy_cc_test( "//test/mocks/server:factory_context_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:threadsafe_singleton_injector_lib", "@envoy_api//envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle_test.cc index eb872c8318373..77ee6f86e1cb1 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/downstream_reverse_connection_io_handle_test.cc @@ -8,6 +8,7 @@ #include "envoy/server/factory_context.h" #include "envoy/thread_local/thread_local.h" +#include "source/common/api/os_sys_calls_impl.h" #include "source/common/buffer/buffer_impl.h" #include "source/common/network/address_impl.h" #include "source/common/network/io_socket_error_impl.h" @@ -23,6 +24,7 @@ #include "test/mocks/server/factory_context.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" +#include "test/test_common/threadsafe_singleton_injector.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -223,37 +225,6 @@ TEST_F(DownstreamReverseConnectionIOHandleTest, GetSocket) { EXPECT_EQ(handle->fdDoNotUse(), 42); } -// Test ignoreCloseAndShutdown() functionality. -TEST_F(DownstreamReverseConnectionIOHandleTest, IgnoreCloseAndShutdown) { - auto handle = createHandle(io_handle_.get(), "test_key"); - - // Initially, close and shutdown should work normally - // Test shutdown before ignoring - we don't check the result since it depends on base - // implementation - handle->shutdown(SHUT_RDWR); - - // Now enable ignore mode - handle->ignoreCloseAndShutdown(); - - // Test that close() is ignored when flag is set - auto close_result = handle->close(); - EXPECT_EQ(close_result.err_, nullptr); // Should return success but do nothing - - // Test that shutdown() is ignored when flag is set - auto shutdown_result2 = handle->shutdown(SHUT_RDWR); - EXPECT_EQ(shutdown_result2.return_value_, 0); - EXPECT_EQ(shutdown_result2.errno_, 0); - - // Test different shutdown modes are all ignored - auto shutdown_rd = handle->shutdown(SHUT_RD); - EXPECT_EQ(shutdown_rd.return_value_, 0); - EXPECT_EQ(shutdown_rd.errno_, 0); - - auto shutdown_wr = handle->shutdown(SHUT_WR); - EXPECT_EQ(shutdown_wr.return_value_, 0); - EXPECT_EQ(shutdown_wr.errno_, 0); -} - TEST_F(DownstreamReverseConnectionIOHandleTest, OnPingMessageWritesRpingToSocket) { int fds[2]; ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0); @@ -567,6 +538,81 @@ TEST_F(DownstreamReverseConnectionIOHandleTest, ReadEchoDisabledAndErrorHandling } } +// Verify that close() + destructor results in exactly one FD close syscall. +TEST_F(DownstreamReverseConnectionIOHandleTest, CloseAndDestructorNoDoubleClose) { + int fds[2]; + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0); + os_fd_t target_fd = fds[0]; + + NiceMock mock_os_syscalls; + TestThreadsafeSingletonInjector injector(&mock_os_syscalls); + EXPECT_CALL(mock_os_syscalls, close(target_fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); + + auto mock_socket = std::make_unique>(); + mock_socket->io_handle_ = std::make_unique(target_fd); + EXPECT_CALL(*mock_socket, ioHandle()).WillRepeatedly(ReturnRef(*mock_socket->io_handle_)); + + auto handle = std::make_unique( + std::move(mock_socket), io_handle_.get(), "CloseAndDestructorNoDoubleClose"); + handle->close(); + handle.reset(); + + ::close(fds[1]); +} + +// Verify that calling close() twice results in exactly one FD close. +TEST_F(DownstreamReverseConnectionIOHandleTest, DoubleCloseCallNoDoubleClose) { + int fds[2]; + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0); + os_fd_t target_fd = fds[0]; + + NiceMock mock_os_syscalls; + TestThreadsafeSingletonInjector injector(&mock_os_syscalls); + EXPECT_CALL(mock_os_syscalls, close(target_fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); + + auto mock_socket = std::make_unique>(); + mock_socket->io_handle_ = std::make_unique(target_fd); + EXPECT_CALL(*mock_socket, ioHandle()).WillRepeatedly(ReturnRef(*mock_socket->io_handle_)); + + auto handle = std::make_unique( + std::move(mock_socket), io_handle_.get(), "CloseAndDestructorNoDoubleClose"); + handle->close(); + handle->close(); + handle.reset(); + + ::close(fds[1]); +} + +// Verify that calling close() twice results in exactly one FD close. +TEST_F(DownstreamReverseConnectionIOHandleTest, DoubleShutdownCallNoDoubleClose) { + int fds[2]; + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0); + os_fd_t target_fd = fds[0]; + + NiceMock mock_os_syscalls; + TestThreadsafeSingletonInjector injector(&mock_os_syscalls); + EXPECT_CALL(mock_os_syscalls, close(target_fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); + + auto mock_socket = std::make_unique>(); + mock_socket->io_handle_ = std::make_unique(target_fd); + EXPECT_CALL(*mock_socket, ioHandle()).WillRepeatedly(ReturnRef(*mock_socket->io_handle_)); + + auto handle = std::make_unique( + std::move(mock_socket), io_handle_.get(), "CloseAndDestructorNoDoubleClose"); + handle->shutdown(0); + handle->shutdown(0); + + // After close(), owned_socket_ is null so shutdown() returns {0,0}. + handle->close(); + auto result = handle->shutdown(0); + EXPECT_EQ(result.return_value_, 0); + EXPECT_EQ(result.errno_, 0); + + handle.reset(); + + ::close(fds[1]); +} + } // namespace ReverseConnection } // namespace Bootstrap } // namespace Extensions From ec1c5f32c2d52ec8677178deec7b9195a1e45158 Mon Sep 17 00:00:00 2001 From: Biren Roy Date: Tue, 5 May 2026 19:16:29 -0400 Subject: [PATCH 220/750] perf: Migrate thread local free slot storage from `std::list` to `std::vector`. (#44859) `std::list` is not a great choice for a performance critical utility, as it requires a heap operation on every mutation. Signed-off-by: Biren Roy --- source/common/thread_local/thread_local_impl.cc | 4 ++-- source/common/thread_local/thread_local_impl.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/common/thread_local/thread_local_impl.cc b/source/common/thread_local/thread_local_impl.cc index 2a49789a09ec2..7d1401991a0d8 100644 --- a/source/common/thread_local/thread_local_impl.cc +++ b/source/common/thread_local/thread_local_impl.cc @@ -33,8 +33,8 @@ SlotPtr InstanceImpl::allocateSlot() { slots_.push_back(slot.get()); return slot; } - const uint32_t idx = free_slot_indexes_.front(); - free_slot_indexes_.pop_front(); + const uint32_t idx = free_slot_indexes_.back(); + free_slot_indexes_.pop_back(); ASSERT(idx < slots_.size()); SlotPtr slot = std::make_unique(*this, idx); slots_[idx] = slot.get(); diff --git a/source/common/thread_local/thread_local_impl.h b/source/common/thread_local/thread_local_impl.h index 719418991e426..cb0558ff57f02 100644 --- a/source/common/thread_local/thread_local_impl.h +++ b/source/common/thread_local/thread_local_impl.h @@ -83,8 +83,8 @@ class InstanceImpl : Logger::Loggable, public NonCopyable, pub Thread::MainThread main_thread_; std::vector slots_; - // A list of index of freed slots. - std::list free_slot_indexes_; + // A collection of indices of freed slots. + std::vector free_slot_indexes_; std::list> registered_threads_; Event::Dispatcher* main_thread_dispatcher_{}; std::atomic shutdown_{}; From eb7a6079d721b68e2a92300d55088ecfc020324d Mon Sep 17 00:00:00 2001 From: code Date: Wed, 6 May 2026 09:45:27 +0800 Subject: [PATCH 221/750] router: no shadow once the request be rejected immediately (#44406) Commit Message: router: no shadow once the request be rejected immediately Additional Description: Now, even if the upstream request is rejected immediately, we will still try to send the mirror request which is typically unnecessary because: - mirror cluster will get incomplete request if the request is not headers only - mirror cluster will receive requests that main cluster not received. Wait #44323 first. Risk Level: low. Testing: unit. Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: wbpcode Signed-off-by: wbpcode/wangbaiping --- source/common/router/router.cc | 12 +++++++++-- source/common/router/router.h | 3 ++- test/common/router/router_test.cc | 36 +++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/source/common/router/router.cc b/source/common/router/router.cc index fe3290bccb89e..d37f14f90b742 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -882,6 +882,13 @@ bool Filter::continueDecodeHeaders(Upstream::ThreadLocalCluster* cluster, allow_multiplexed_upstream_half_close_ /*enable_half_close*/); LinkedList::moveIntoList(std::move(upstream_request), upstream_requests_); upstream_requests_.front()->acceptHeadersFromRouter(end_stream); + + // If the upstream HTTP filters dropped this request by sending a local reply, we should not + // start any shadow streams since the request won't be proxied upstream. + if (saw_local_reply_) { + active_shadow_policies.clear(); + } + // Start the shadow streams. const size_t num_shadow_policies = active_shadow_policies.size(); for (size_t i = 0; i < num_shadow_policies; ++i) { @@ -942,8 +949,9 @@ bool Filter::continueDecodeHeaders(Upstream::ThreadLocalCluster* cluster, onRequestComplete(); } - // If this was called due to asynchronous host selection, the caller should continueDecoding. - return true; + // If this was called due to asynchronous host selection, the caller should continueDecoding + // if and only if we did not send a local reply. + return !saw_local_reply_; } std::unique_ptr Filter::createConnPool(Upstream::ThreadLocalCluster& cluster, diff --git a/source/common/router/router.h b/source/common/router/router.h index 34e593b5f690f..a4823300c3579 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -322,7 +322,7 @@ class Filter : Logger::Loggable, host_selection_cancelable_->cancel(); host_selection_cancelable_.reset(); } - + saw_local_reply_ = true; // Clean up the upstream_requests_. resetAll(); return Http::LocalErrorStatus::Continue; @@ -701,6 +701,7 @@ class Filter : Logger::Loggable, // Cached runtime flag value for reject_early_connect_data to avoid evaluating it on every data // chunk. bool reject_early_connect_data_enabled_ : 1 = false; + bool saw_local_reply_ : 1 = false; }; class ProdFilter : public Filter { diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 0b4769e90ee46..e037f8ecb62d1 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -6034,6 +6034,42 @@ TEST_P(RouterShadowingTest, NoShadowForConnect) { router_->onDestroy(); } +TEST_P(RouterShadowingTest, NoShadowWhenRequestRejectedImmediately) { + ShadowPolicyPtr policy = makeShadowPolicy("foo", "", "bar"); + callbacks_.route_->route_entry_.shadow_policies_.push_back(policy); + ON_CALL(callbacks_, streamId()).WillByDefault(Return(43)); + + NiceMock encoder; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder, &response_decoder, Http::Protocol::Http10); + + EXPECT_CALL( + runtime_.snapshot_, + featureEnabled("bar", testing::Matcher(Percent(0)), + 43)) + .WillOnce(Return(true)); + + EXPECT_CALL(*shadow_writer_, streamingShadow_(_, _, _)).Times(0); + EXPECT_CALL(*shadow_writer_, shadow_(_, _, _)).Times(0); + EXPECT_CALL(callbacks_, sendLocalReply(Http::Code::ServiceUnavailable, _, _, _, _)) + .WillOnce(InvokeWithoutArgs([] {})); + EXPECT_CALL(encoder, encodeHeaders(_, true)) + .WillOnce(Invoke([this](const Http::RequestHeaderMap&, bool) -> Http::Status { + callbacks_.sendLocalReply(Http::Code::ServiceUnavailable, "", {}, absl::nullopt, + "rejected_immediately"); + Http::StreamFilterBase::LocalReplyData local_reply_data{ + Http::Code::ServiceUnavailable, absl::nullopt, "rejected_immediately", false}; + router_->onLocalReply(local_reply_data); + return Http::okStatus(); + })); + + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + router_->decodeHeaders(headers, true); + + router_->onDestroy(); +} + TEST_P(RouterShadowingTest, ShadowRequestCarriesParentContext) { ShadowPolicyPtr policy = makeShadowPolicy("foo", "", "bar"); callbacks_.route_->route_entry_.shadow_policies_.push_back(policy); From 08f7c15da7f2e4077404829402bd8465536864f2 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 May 2026 07:31:36 +0100 Subject: [PATCH 222/750] deps/rust: Bump hickory-proto/resolver -> 0.26.1 (#44858) Fix RUSTSEC-2026-0118 and RUSTSEC-2026-0119 Signed-off-by: Ryan Northey --- Cargo.Bazel.lock | 2602 +++++++++++----- Cargo.lock | 374 ++- bazel/tests/external/Cargo.Bazel.lock | 2614 ++++++++++++----- docs/Cargo.Bazel.lock | 2602 +++++++++++----- .../builtin_extensions/Cargo.toml | 2 +- .../builtin_extensions/hickory_dns.rs | 68 +- 6 files changed, 5799 insertions(+), 2463 deletions(-) diff --git a/Cargo.Bazel.lock b/Cargo.Bazel.lock index d1d5a91f5ad27..44d587659c902 100644 --- a/Cargo.Bazel.lock +++ b/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "9e9be21e7a4781243f0bd2c76b8dfcb754ff750302be2a13d401ef862b6b3297", + "checksum": "5b2732d6faf4872654fe43d84a7d3424c73c0bbd48835c02915ef6c4443adadc", "crates": { "aho-corasick 1.1.4": { "name": "aho-corasick", @@ -259,6 +259,45 @@ ], "license_file": "LICENSE-APACHE" }, + "autocfg 1.5.0": { + "name": "autocfg", + "version": "1.5.0", + "package_url": "https://github.com/cuviper/autocfg", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/autocfg/1.5.0/download", + "sha256": "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + } + }, + "targets": [ + { + "Library": { + "crate_name": "autocfg", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "autocfg", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "1.5.0" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "bindgen 0.70.1": { "name": "bindgen", "version": "0.70.1", @@ -667,6 +706,71 @@ ], "license_file": "LICENSE-APACHE" }, + "chacha20 0.10.0": { + "name": "chacha20", + "version": "0.10.0", + "package_url": "https://github.com/RustCrypto/stream-ciphers", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/chacha20/0.10.0/download", + "sha256": "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" + } + }, + "targets": [ + { + "Library": { + "crate_name": "chacha20", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "chacha20", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "rng" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + }, + { + "id": "rand_core 0.10.1", + "target": "rand_core" + } + ], + "selects": { + "cfg(any(target_arch = \"x86_64\", target_arch = \"x86\"))": [ + { + "id": "cpufeatures 0.3.0", + "target": "cpufeatures" + } + ] + } + }, + "edition": "2024", + "version": "0.10.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "clang-sys 1.8.1": { "name": "clang-sys", "version": "1.8.1", @@ -774,20 +878,20 @@ ], "license_file": "LICENSE.txt" }, - "critical-section 1.2.0": { - "name": "critical-section", - "version": "1.2.0", - "package_url": "https://github.com/rust-embedded/critical-section", + "combine 4.6.7": { + "name": "combine", + "version": "4.6.7", + "package_url": "https://github.com/Marwes/combine", "repository": { "Http": { - "url": "https://static.crates.io/crates/critical-section/1.2.0/download", - "sha256": "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + "url": "https://static.crates.io/crates/combine/4.6.7/download", + "sha256": "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" } }, "targets": [ { "Library": { - "crate_name": "critical_section", + "crate_name": "combine", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -798,35 +902,43 @@ } } ], - "library_target_name": "critical_section", + "library_target_name": "combine", "common_attrs": { "compile_data_glob": [ "**" ], + "deps": { + "common": [ + { + "id": "memchr 2.8.0", + "target": "memchr" + } + ], + "selects": {} + }, "edition": "2018", - "version": "1.2.0" + "version": "4.6.7" }, - "license": "MIT OR Apache-2.0", + "license": "MIT", "license_ids": [ - "Apache-2.0", "MIT" ], - "license_file": "LICENSE-APACHE" + "license_file": "LICENSE" }, - "crossbeam-channel 0.5.15": { - "name": "crossbeam-channel", - "version": "0.5.15", - "package_url": "https://github.com/crossbeam-rs/crossbeam", + "core-foundation 0.9.4": { + "name": "core-foundation", + "version": "0.9.4", + "package_url": "https://github.com/servo/core-foundation-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/crossbeam-channel/0.5.15/download", - "sha256": "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" + "url": "https://static.crates.io/crates/core-foundation/0.9.4/download", + "sha256": "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" } }, "targets": [ { "Library": { - "crate_name": "crossbeam_channel", + "crate_name": "core_foundation", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -837,7 +949,7 @@ } } ], - "library_target_name": "crossbeam_channel", + "library_target_name": "core_foundation", "common_attrs": { "compile_data_glob": [ "**" @@ -845,21 +957,25 @@ "crate_features": { "common": [ "default", - "std" + "link" ], "selects": {} }, "deps": { "common": [ { - "id": "crossbeam-utils 0.8.21", - "target": "crossbeam_utils" + "id": "core-foundation-sys 0.8.7", + "target": "core_foundation_sys" + }, + { + "id": "libc 0.2.183", + "target": "libc" } ], "selects": {} }, - "edition": "2021", - "version": "0.5.15" + "edition": "2018", + "version": "0.9.4" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -868,20 +984,20 @@ ], "license_file": "LICENSE-APACHE" }, - "crossbeam-epoch 0.9.18": { - "name": "crossbeam-epoch", - "version": "0.9.18", - "package_url": "https://github.com/crossbeam-rs/crossbeam", + "core-foundation-sys 0.8.7": { + "name": "core-foundation-sys", + "version": "0.8.7", + "package_url": "https://github.com/servo/core-foundation-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/crossbeam-epoch/0.9.18/download", - "sha256": "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" + "url": "https://static.crates.io/crates/core-foundation-sys/0.8.7/download", + "sha256": "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" } }, "targets": [ { "Library": { - "crate_name": "crossbeam_epoch", + "crate_name": "core_foundation_sys", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -892,30 +1008,20 @@ } } ], - "library_target_name": "crossbeam_epoch", + "library_target_name": "core_foundation_sys", "common_attrs": { "compile_data_glob": [ "**" ], "crate_features": { "common": [ - "alloc", "default", - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "crossbeam-utils 0.8.21", - "target": "crossbeam_utils" - } + "link" ], "selects": {} }, - "edition": "2021", - "version": "0.9.18" + "edition": "2018", + "version": "0.8.7" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -924,20 +1030,20 @@ ], "license_file": "LICENSE-APACHE" }, - "crossbeam-utils 0.8.21": { - "name": "crossbeam-utils", - "version": "0.8.21", - "package_url": "https://github.com/crossbeam-rs/crossbeam", + "cpufeatures 0.3.0": { + "name": "cpufeatures", + "version": "0.3.0", + "package_url": "https://github.com/RustCrypto/utils", "repository": { "Http": { - "url": "https://static.crates.io/crates/crossbeam-utils/0.8.21/download", - "sha256": "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + "url": "https://static.crates.io/crates/cpufeatures/0.3.0/download", + "sha256": "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" } }, "targets": [ { "Library": { - "crate_name": "crossbeam_utils", + "crate_name": "cpufeatures", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -946,54 +1052,44 @@ ] } } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } } ], - "library_target_name": "crossbeam_utils", + "library_target_name": "cpufeatures", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "default", - "std" - ], - "selects": {} - }, "deps": { - "common": [ - { - "id": "crossbeam-utils 0.8.21", - "target": "build_script_build" - } - ], - "selects": {} + "common": [], + "selects": { + "cfg(all(target_arch = \"aarch64\", target_os = \"android\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ], + "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ], + "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ], + "cfg(all(target_arch = \"loongarch64\", target_os = \"linux\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ] + } }, - "edition": "2021", - "version": "0.8.21" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ] + "edition": "2024", + "version": "0.3.0" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -1002,20 +1098,20 @@ ], "license_file": "LICENSE-APACHE" }, - "data-encoding 2.10.0": { - "name": "data-encoding", - "version": "2.10.0", - "package_url": "https://github.com/ia0/data-encoding", + "critical-section 1.2.0": { + "name": "critical-section", + "version": "1.2.0", + "package_url": "https://github.com/rust-embedded/critical-section", "repository": { "Http": { - "url": "https://static.crates.io/crates/data-encoding/2.10.0/download", - "sha256": "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + "url": "https://static.crates.io/crates/critical-section/1.2.0/download", + "sha256": "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" } }, "targets": [ { "Library": { - "crate_name": "data_encoding", + "crate_name": "critical_section", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -1026,31 +1122,259 @@ } } ], - "library_target_name": "data_encoding", + "library_target_name": "critical_section", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "alloc", - "std" - ], - "selects": {} - }, "edition": "2018", - "version": "2.10.0" + "version": "1.2.0" }, - "license": "MIT", + "license": "MIT OR Apache-2.0", "license_ids": [ + "Apache-2.0", "MIT" ], - "license_file": "LICENSE" + "license_file": "LICENSE-APACHE" }, - "deranged 0.5.8": { - "name": "deranged", - "version": "0.5.8", - "package_url": "https://github.com/jhpratt/deranged", + "crossbeam-channel 0.5.15": { + "name": "crossbeam-channel", + "version": "0.5.15", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-channel/0.5.15/download", + "sha256": "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_channel", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_channel", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "crossbeam_utils" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.5.15" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "crossbeam-epoch 0.9.18": { + "name": "crossbeam-epoch", + "version": "0.9.18", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-epoch/0.9.18/download", + "sha256": "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_epoch", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_epoch", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "crossbeam_utils" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.9.18" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "crossbeam-utils 0.8.21": { + "name": "crossbeam-utils", + "version": "0.8.21", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-utils/0.8.21/download", + "sha256": "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_utils", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_utils", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.21" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "data-encoding 2.10.0": { + "name": "data-encoding", + "version": "2.10.0", + "package_url": "https://github.com/ia0/data-encoding", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/data-encoding/2.10.0/download", + "sha256": "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + } + }, + "targets": [ + { + "Library": { + "crate_name": "data_encoding", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "data_encoding", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "2.10.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "deranged 0.5.8": { + "name": "deranged", + "version": "0.5.8", + "package_url": "https://github.com/jhpratt/deranged", "repository": { "Http": { "url": "https://static.crates.io/crates/deranged/0.5.8/download", @@ -1232,70 +1556,17 @@ "compile_data_glob": [ "**" ], - "edition": "2021", - "version": "1.15.0" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "enum-as-inner 0.6.1": { - "name": "enum-as-inner", - "version": "0.6.1", - "package_url": "https://github.com/bluejekyll/enum-as-inner", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/enum-as-inner/0.6.1/download", - "sha256": "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" - } - }, - "targets": [ - { - "ProcMacro": { - "crate_name": "enum_as_inner", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "enum_as_inner", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { + "crate_features": { "common": [ - { - "id": "heck 0.5.0", - "target": "heck" - }, - { - "id": "proc-macro2 1.0.106", - "target": "proc_macro2" - }, - { - "id": "quote 1.0.45", - "target": "quote" - }, - { - "id": "syn 2.0.117", - "target": "syn" - } + "default", + "std" ], "selects": {} }, - "edition": "2018", - "version": "0.6.1" + "edition": "2021", + "version": "1.15.0" }, - "license": "MIT/Apache-2.0", + "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" @@ -1316,7 +1587,7 @@ "deps": { "common": [ { - "id": "hickory-resolver 0.25.2", + "id": "hickory-resolver 0.26.1", "target": "hickory_resolver" }, { @@ -1831,6 +2102,62 @@ ], "license_file": "LICENSE-APACHE" }, + "futures-macro 0.3.32": { + "name": "futures-macro", + "version": "0.3.32", + "package_url": "https://github.com/rust-lang/futures-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/futures-macro/0.3.32/download", + "sha256": "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "futures_macro", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "futures_macro", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.106", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.45", + "target": "quote" + }, + { + "id": "syn 2.0.117", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.3.32" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "futures-sink 0.3.32": { "name": "futures-sink", "version": "0.3.32", @@ -1956,6 +2283,9 @@ "crate_features": { "common": [ "alloc", + "async-await", + "async-await-macro", + "futures-macro", "slab", "std" ], @@ -1979,72 +2309,20 @@ "id": "slab 0.4.12", "target": "slab" } - ], - "selects": {} - }, - "edition": "2018", - "version": "0.3.32" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "getrandom 0.2.17": { - "name": "getrandom", - "version": "0.2.17", - "package_url": "https://github.com/rust-random/getrandom", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/getrandom/0.2.17/download", - "sha256": "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" - } - }, - "targets": [ - { - "Library": { - "crate_name": "getrandom", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "getrandom", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { + ], + "selects": {} + }, + "edition": "2018", + "proc_macro_deps": { "common": [ { - "id": "cfg-if 1.0.4", - "target": "cfg_if" + "id": "futures-macro 0.3.32", + "target": "futures_macro" } ], - "selects": { - "cfg(target_os = \"wasi\")": [ - { - "id": "wasi 0.11.1+wasi-snapshot-preview1", - "target": "wasi" - } - ], - "cfg(unix)": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ] - } + "selects": {} }, - "edition": "2018", - "version": "0.2.17" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -2053,14 +2331,14 @@ ], "license_file": "LICENSE-APACHE" }, - "getrandom 0.3.4": { + "getrandom 0.2.17": { "name": "getrandom", - "version": "0.3.4", + "version": "0.2.17", "package_url": "https://github.com/rust-random/getrandom", "repository": { "Http": { - "url": "https://static.crates.io/crates/getrandom/0.3.4/download", - "sha256": "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" + "url": "https://static.crates.io/crates/getrandom/0.2.17/download", + "sha256": "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" } }, "targets": [ @@ -2075,18 +2353,6 @@ ] } } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } } ], "library_target_name": "getrandom", @@ -2094,79 +2360,21 @@ "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "std" - ], - "selects": {} - }, "deps": { "common": [ { "id": "cfg-if 1.0.4", "target": "cfg_if" - }, - { - "id": "getrandom 0.3.4", - "target": "build_script_build" } ], "selects": { - "cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(all(target_arch = \"wasm32\", target_os = \"wasi\", target_env = \"p2\"))": [ - { - "id": "wasip2 1.0.2+wasi-0.2.9", - "target": "wasip2" - } - ], - "cfg(all(target_os = \"uefi\", getrandom_backend = \"efi_rng\"))": [ - { - "id": "r-efi 5.3.0", - "target": "r_efi" - } - ], - "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"hurd\", target_os = \"illumos\", target_os = \"cygwin\", all(target_os = \"horizon\", target_arch = \"arm\")))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(any(target_os = \"haiku\", target_os = \"redox\", target_os = \"nto\", target_os = \"aix\"))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(any(target_os = \"ios\", target_os = \"visionos\", target_os = \"watchos\", target_os = \"tvos\"))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(target_os = \"netbsd\")": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(target_os = \"solaris\")": [ + "cfg(target_os = \"wasi\")": [ { - "id": "libc 0.2.183", - "target": "libc" + "id": "wasi 0.11.1+wasi-snapshot-preview1", + "target": "wasi" } ], - "cfg(target_os = \"vxworks\")": [ + "cfg(unix)": [ { "id": "libc 0.2.183", "target": "libc" @@ -2174,19 +2382,8 @@ ] } }, - "edition": "2021", - "version": "0.3.4" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ] + "edition": "2018", + "version": "0.2.17" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -2236,6 +2433,13 @@ "compile_data_glob": [ "**" ], + "crate_features": { + "common": [ + "std", + "sys_rng" + ], + "selects": {} + }, "deps": { "common": [ { @@ -2245,6 +2449,10 @@ { "id": "getrandom 0.4.2", "target": "build_script_build" + }, + { + "id": "rand_core 0.10.1", + "target": "rand_core" } ], "selects": { @@ -2601,20 +2809,20 @@ ], "license_file": "LICENSE-APACHE" }, - "hickory-proto 0.25.2": { - "name": "hickory-proto", - "version": "0.25.2", + "hickory-net 0.26.1": { + "name": "hickory-net", + "version": "0.26.1", "package_url": "https://github.com/hickory-dns/hickory-dns", "repository": { "Http": { - "url": "https://static.crates.io/crates/hickory-proto/0.25.2/download", - "sha256": "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" + "url": "https://static.crates.io/crates/hickory-net/0.26.1/download", + "sha256": "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" } }, "targets": [ { "Library": { - "crate_name": "hickory_proto", + "crate_name": "hickory_net", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -2625,7 +2833,7 @@ } } ], - "library_target_name": "hickory_proto", + "library_target_name": "hickory_net", "common_attrs": { "compile_data_glob": [ "**" @@ -2636,9 +2844,7 @@ "__https", "__tls", "dnssec-ring", - "futures-io", "https-ring", - "std", "tls-ring", "tokio", "webpki-roots" @@ -2679,6 +2885,10 @@ "id": "h2 0.4.13", "target": "h2" }, + { + "id": "hickory-proto 0.26.1", + "target": "hickory_proto" + }, { "id": "http 1.4.0", "target": "http" @@ -2692,11 +2902,15 @@ "target": "ipnet" }, { - "id": "once_cell 1.21.4", - "target": "once_cell" + "id": "lru-cache 0.1.2", + "target": "lru_cache" + }, + { + "id": "parking_lot 0.12.5", + "target": "parking_lot" }, { - "id": "rand 0.9.4", + "id": "rand 0.10.1", "target": "rand" }, { @@ -2740,11 +2954,18 @@ "target": "url" }, { - "id": "webpki-roots 0.26.11", + "id": "webpki-roots 1.0.6", "target": "webpki_roots" } ], - "selects": {} + "selects": { + "cfg(target_os = \"android\")": [ + { + "id": "jni 0.22.4", + "target": "jni" + } + ] + } }, "edition": "2021", "proc_macro_deps": { @@ -2752,15 +2973,127 @@ { "id": "async-trait 0.1.89", "target": "async_trait" + } + ], + "selects": {} + }, + "version": "0.26.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "hickory-proto 0.26.1": { + "name": "hickory-proto", + "version": "0.26.1", + "package_url": "https://github.com/hickory-dns/hickory-dns", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/hickory-proto/0.26.1/download", + "sha256": "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" + } + }, + "targets": [ + { + "Library": { + "crate_name": "hickory_proto", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "hickory_proto", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "__dnssec", + "access-control", + "dnssec-ring", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "bitflags 2.11.0", + "target": "bitflags" + }, + { + "id": "data-encoding 2.10.0", + "target": "data_encoding" + }, + { + "id": "idna 1.1.0", + "target": "idna" + }, + { + "id": "ipnet 2.12.0", + "target": "ipnet" + }, + { + "id": "once_cell 1.21.4", + "target": "once_cell" + }, + { + "id": "prefix-trie 0.8.3", + "target": "prefix_trie" + }, + { + "id": "rand 0.10.1", + "target": "rand" + }, + { + "id": "ring 0.17.14", + "target": "ring" + }, + { + "id": "rustls-pki-types 1.14.0", + "target": "rustls_pki_types" + }, + { + "id": "thiserror 2.0.18", + "target": "thiserror" + }, + { + "id": "time 0.3.47", + "target": "time" + }, + { + "id": "tinyvec 1.11.0", + "target": "tinyvec" + }, + { + "id": "tracing 0.1.44", + "target": "tracing" }, { - "id": "enum-as-inner 0.6.1", - "target": "enum_as_inner" + "id": "url 2.5.8", + "target": "url" } ], - "selects": {} + "selects": { + "cfg(target_os = \"android\")": [ + { + "id": "jni 0.22.4", + "target": "jni" + } + ] + } }, - "version": "0.25.2" + "edition": "2021", + "version": "0.26.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -2769,14 +3102,14 @@ ], "license_file": "LICENSE-APACHE" }, - "hickory-resolver 0.25.2": { + "hickory-resolver 0.26.1": { "name": "hickory-resolver", - "version": "0.25.2", + "version": "0.26.1", "package_url": "https://github.com/hickory-dns/hickory-dns", "repository": { "Http": { - "url": "https://static.crates.io/crates/hickory-resolver/0.25.2/download", - "sha256": "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" + "url": "https://static.crates.io/crates/hickory-resolver/0.26.1/download", + "sha256": "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" } }, "targets": [ @@ -2824,9 +3157,17 @@ "target": "futures_util" }, { - "id": "hickory-proto 0.25.2", + "id": "hickory-net 0.26.1", + "target": "hickory_net" + }, + { + "id": "hickory-proto 0.26.1", "target": "hickory_proto" }, + { + "id": "ipnet 2.12.0", + "target": "ipnet" + }, { "id": "moka 0.12.15", "target": "moka" @@ -2840,7 +3181,7 @@ "target": "parking_lot" }, { - "id": "rand 0.9.4", + "id": "rand 0.10.1", "target": "rand" }, { @@ -2872,11 +3213,17 @@ "target": "tracing" }, { - "id": "webpki-roots 0.26.11", + "id": "webpki-roots 1.0.6", "target": "webpki_roots" } ], "selects": { + "aarch64-apple-darwin": [ + { + "id": "system-configuration 0.7.0", + "target": "system_configuration" + } + ], "x86_64-pc-windows-msvc": [ { "id": "ipconfig 0.3.4", @@ -2886,7 +3233,7 @@ } }, "edition": "2021", - "version": "0.25.2" + "version": "0.26.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -3859,41 +4206,355 @@ } } ], - "library_target_name": "ipnet", + "library_target_name": "ipnet", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "serde", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "serde 1.0.228", + "target": "serde" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "2.12.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "itertools 0.13.0": { + "name": "itertools", + "version": "0.13.0", + "package_url": "https://github.com/rust-itertools/itertools", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/itertools/0.13.0/download", + "sha256": "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" + } + }, + "targets": [ + { + "Library": { + "crate_name": "itertools", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "itertools", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "either 1.15.0", + "target": "either" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.13.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "itoa 1.0.18": { + "name": "itoa", + "version": "1.0.18", + "package_url": "https://github.com/dtolnay/itoa", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/itoa/1.0.18/download", + "sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + } + }, + "targets": [ + { + "Library": { + "crate_name": "itoa", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "itoa", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "1.0.18" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "jni 0.22.4": { + "name": "jni", + "version": "0.22.4", + "package_url": "https://github.com/jni-rs/jni-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/jni/0.22.4/download", + "sha256": "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" + } + }, + "targets": [ + { + "Library": { + "crate_name": "jni", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "jni", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + }, + { + "id": "combine 4.6.7", + "target": "combine" + }, + { + "id": "jni 0.22.4", + "target": "build_script_build" + }, + { + "id": "jni-sys 0.4.1", + "target": "jni_sys" + }, + { + "id": "log 0.4.29", + "target": "log" + }, + { + "id": "simd_cesu8 1.1.1", + "target": "simd_cesu8" + }, + { + "id": "thiserror 2.0.18", + "target": "thiserror" + } + ], + "selects": { + "cfg(windows)": [ + { + "id": "windows-link 0.2.1", + "target": "windows_link" + } + ] + } + }, + "edition": "2024", + "proc_macro_deps": { + "common": [ + { + "id": "jni-macros 0.22.4", + "target": "jni_macros" + } + ], + "selects": {} + }, + "version": "0.22.4" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "walkdir 2.5.0", + "target": "walkdir" + } + ], + "selects": {} + } + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": null + }, + "jni-macros 0.22.4": { + "name": "jni-macros", + "version": "0.22.4", + "package_url": "https://github.com/jni-rs/jni-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/jni-macros/0.22.4/download", + "sha256": "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "jni_macros", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "jni_macros", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { + "deps": { "common": [ - "std" + { + "id": "jni-macros 0.22.4", + "target": "build_script_build" + }, + { + "id": "proc-macro2 1.0.106", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.45", + "target": "quote" + }, + { + "id": "simd_cesu8 1.1.1", + "target": "simd_cesu8" + }, + { + "id": "syn 2.0.117", + "target": "syn" + } ], "selects": {} }, - "edition": "2018", - "version": "2.12.0" + "edition": "2024", + "version": "0.22.4" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "rustc_version 0.4.1", + "target": "rustc_version" + } + ], + "selects": {} + } }, "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" ], - "license_file": "LICENSE-APACHE" + "license_file": null }, - "itertools 0.13.0": { - "name": "itertools", - "version": "0.13.0", - "package_url": "https://github.com/rust-itertools/itertools", + "jni-sys 0.4.1": { + "name": "jni-sys", + "version": "0.4.1", + "package_url": "https://github.com/jni-rs/jni-sys", "repository": { "Http": { - "url": "https://static.crates.io/crates/itertools/0.13.0/download", - "sha256": "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" + "url": "https://static.crates.io/crates/jni-sys/0.4.1/download", + "sha256": "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" } }, "targets": [ { "Library": { - "crate_name": "itertools", + "crate_name": "jni_sys", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -3904,22 +4565,22 @@ } } ], - "library_target_name": "itertools", + "library_target_name": "jni_sys", "common_attrs": { "compile_data_glob": [ "**" ], - "deps": { + "edition": "2021", + "proc_macro_deps": { "common": [ { - "id": "either 1.15.0", - "target": "either" + "id": "jni-sys-macros 0.4.1", + "target": "jni_sys_macros" } ], "selects": {} }, - "edition": "2018", - "version": "0.13.0" + "version": "0.4.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -3928,20 +4589,20 @@ ], "license_file": "LICENSE-APACHE" }, - "itoa 1.0.18": { - "name": "itoa", - "version": "1.0.18", - "package_url": "https://github.com/dtolnay/itoa", + "jni-sys-macros 0.4.1": { + "name": "jni-sys-macros", + "version": "0.4.1", + "package_url": "https://github.com/jni-rs/jni-sys", "repository": { "Http": { - "url": "https://static.crates.io/crates/itoa/1.0.18/download", - "sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + "url": "https://static.crates.io/crates/jni-sys-macros/0.4.1/download", + "sha256": "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" } }, "targets": [ { - "Library": { - "crate_name": "itoa", + "ProcMacro": { + "crate_name": "jni_sys_macros", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -3952,20 +4613,33 @@ } } ], - "library_target_name": "itoa", + "library_target_name": "jni_sys_macros", "common_attrs": { "compile_data_glob": [ "**" ], + "deps": { + "common": [ + { + "id": "quote 1.0.45", + "target": "quote" + }, + { + "id": "syn 2.0.117", + "target": "syn" + } + ], + "selects": {} + }, "edition": "2021", - "version": "1.0.18" + "version": "0.4.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" ], - "license_file": "LICENSE-APACHE" + "license_file": null }, "js-sys 0.3.91": { "name": "js-sys", @@ -4209,6 +4883,45 @@ ], "license_file": "LICENSE" }, + "linked-hash-map 0.5.6": { + "name": "linked-hash-map", + "version": "0.5.6", + "package_url": "https://github.com/contain-rs/linked-hash-map", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/linked-hash-map/0.5.6/download", + "sha256": "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + } + }, + "targets": [ + { + "Library": { + "crate_name": "linked_hash_map", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "linked_hash_map", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "0.5.6" + }, + "license": "MIT/Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "litemap 0.8.1": { "name": "litemap", "version": "0.8.1", @@ -4341,6 +5054,54 @@ ], "license_file": "LICENSE-APACHE" }, + "lru-cache 0.1.2": { + "name": "lru-cache", + "version": "0.1.2", + "package_url": "https://github.com/contain-rs/lru-cache", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/lru-cache/0.1.2/download", + "sha256": "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" + } + }, + "targets": [ + { + "Library": { + "crate_name": "lru_cache", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "lru_cache", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "linked-hash-map 0.5.6", + "target": "linked_hash_map" + } + ], + "selects": {} + }, + "edition": "2015", + "version": "0.1.2" + }, + "license": "MIT/Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "memchr 2.8.0": { "name": "memchr", "version": "2.8.0", @@ -4758,6 +5519,45 @@ ], "license_file": "LICENSE-APACHE" }, + "ndk-context 0.1.1": { + "name": "ndk-context", + "version": "0.1.1", + "package_url": "https://github.com/rust-windowing/android-ndk-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/ndk-context/0.1.1/download", + "sha256": "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "ndk_context", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "ndk_context", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.1.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": null + }, "nom 7.1.3": { "name": "nom", "version": "7.1.3", @@ -4845,15 +5645,102 @@ "compile_data_glob": [ "**" ], - "edition": "2021", - "version": "0.2.1" + "edition": "2021", + "version": "0.2.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-Apache" + }, + "num-traits 0.2.19": { + "name": "num-traits", + "version": "0.2.19", + "package_url": "https://github.com/rust-num/num-traits", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/num-traits/0.2.19/download", + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" + } + }, + "targets": [ + { + "Library": { + "crate_name": "num_traits", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "num_traits", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "num-traits 0.2.19", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.2.19" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "autocfg 1.5.0", + "target": "autocfg" + } + ], + "selects": {} + } }, "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" ], - "license_file": "LICENSE-Apache" + "license_file": "LICENSE-APACHE" }, "once_cell 1.21.4": { "name": "once_cell", @@ -5329,61 +6216,6 @@ ], "license_file": "LICENSE-Apache" }, - "ppv-lite86 0.2.21": { - "name": "ppv-lite86", - "version": "0.2.21", - "package_url": "https://github.com/cryptocorrosion/cryptocorrosion", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/ppv-lite86/0.2.21/download", - "sha256": "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" - } - }, - "targets": [ - { - "Library": { - "crate_name": "ppv_lite86", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "ppv_lite86", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "simd", - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "zerocopy 0.8.47", - "target": "zerocopy" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.2.21" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, "predicates 3.1.4": { "name": "predicates", "version": "3.1.4", @@ -5527,6 +6359,69 @@ ], "license_file": "LICENSE-APACHE" }, + "prefix-trie 0.8.3": { + "name": "prefix-trie", + "version": "0.8.3", + "package_url": "https://github.com/tiborschneider/prefix-trie", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/prefix-trie/0.8.3/download", + "sha256": "90f561214012d3fc240a1f9c817cc4d57f5310910d066069c1b093f766bb5966" + } + }, + "targets": [ + { + "Library": { + "crate_name": "prefix_trie", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "prefix_trie", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "ipnet" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "either 1.15.0", + "target": "either" + }, + { + "id": "ipnet 2.12.0", + "target": "ipnet" + }, + { + "id": "num-traits 0.2.19", + "target": "num_traits" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.3" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "prettyplease 0.2.37": { "name": "prettyplease", "version": "0.2.37", @@ -5777,46 +6672,6 @@ ], "license_file": "LICENSE-APACHE" }, - "r-efi 5.3.0": { - "name": "r-efi", - "version": "5.3.0", - "package_url": "https://github.com/r-efi/r-efi", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/r-efi/5.3.0/download", - "sha256": "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - } - }, - "targets": [ - { - "Library": { - "crate_name": "r_efi", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "r_efi", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "edition": "2018", - "version": "5.3.0" - }, - "license": "MIT OR Apache-2.0 OR LGPL-2.1-or-later", - "license_ids": [ - "Apache-2.0", - "LGPL-2.1", - "MIT" - ], - "license_file": null - }, "r-efi 6.0.0": { "name": "r-efi", "version": "6.0.0", @@ -5857,14 +6712,14 @@ ], "license_file": null }, - "rand 0.9.4": { + "rand 0.10.1": { "name": "rand", - "version": "0.9.4", + "version": "0.10.1", "package_url": "https://github.com/rust-random/rand", "repository": { "Http": { - "url": "https://static.crates.io/crates/rand/0.9.4/download", - "sha256": "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" + "url": "https://static.crates.io/crates/rand/0.10.1/download", + "sha256": "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" } }, "targets": [ @@ -5889,9 +6744,9 @@ "crate_features": { "common": [ "alloc", - "os_rng", "std", "std_rng", + "sys_rng", "thread_rng" ], "selects": {} @@ -5899,76 +6754,22 @@ "deps": { "common": [ { - "id": "rand_chacha 0.9.0", - "target": "rand_chacha" + "id": "chacha20 0.10.0", + "target": "chacha20" }, { - "id": "rand_core 0.9.5", - "target": "rand_core" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.9.4" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "rand_chacha 0.9.0": { - "name": "rand_chacha", - "version": "0.9.0", - "package_url": "https://github.com/rust-random/rand", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/rand_chacha/0.9.0/download", - "sha256": "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" - } - }, - "targets": [ - { - "Library": { - "crate_name": "rand_chacha", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "rand_chacha", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "ppv-lite86 0.2.21", - "target": "ppv_lite86" + "id": "getrandom 0.4.2", + "target": "getrandom" }, { - "id": "rand_core 0.9.5", + "id": "rand_core 0.10.1", "target": "rand_core" } ], "selects": {} }, - "edition": "2021", - "version": "0.9.0" + "edition": "2024", + "version": "0.10.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -5977,14 +6778,14 @@ ], "license_file": "LICENSE-APACHE" }, - "rand_core 0.9.5": { + "rand_core 0.10.1": { "name": "rand_core", - "version": "0.9.5", - "package_url": "https://github.com/rust-random/rand", + "version": "0.10.1", + "package_url": "https://github.com/rust-random/rand_core", "repository": { "Http": { - "url": "https://static.crates.io/crates/rand_core/0.9.5/download", - "sha256": "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + "url": "https://static.crates.io/crates/rand_core/0.10.1/download", + "sha256": "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" } }, "targets": [ @@ -6006,24 +6807,8 @@ "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "os_rng", - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "getrandom 0.3.4", - "target": "getrandom" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.9.5" + "edition": "2024", + "version": "0.10.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -6417,14 +7202,60 @@ "package_url": "https://github.com/rust-lang-nursery/rustc-hash", "repository": { "Http": { - "url": "https://static.crates.io/crates/rustc-hash/1.1.0/download", - "sha256": "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + "url": "https://static.crates.io/crates/rustc-hash/1.1.0/download", + "sha256": "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rustc_hash", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rustc_hash", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "edition": "2015", + "version": "1.1.0" + }, + "license": "Apache-2.0/MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rustc_version 0.4.1": { + "name": "rustc_version", + "version": "0.4.1", + "package_url": "https://github.com/djc/rustc-version-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rustc_version/0.4.1/download", + "sha256": "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" } }, "targets": [ { "Library": { - "crate_name": "rustc_hash", + "crate_name": "rustc_version", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -6435,22 +7266,24 @@ } } ], - "library_target_name": "rustc_hash", + "library_target_name": "rustc_version", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { + "deps": { "common": [ - "default", - "std" + { + "id": "semver 1.0.27", + "target": "semver" + } ], "selects": {} }, - "edition": "2015", - "version": "1.1.0" + "edition": "2018", + "version": "0.4.1" }, - "license": "Apache-2.0/MIT", + "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" @@ -6768,6 +7601,56 @@ ], "license_file": "LICENSE-APACHE" }, + "same-file 1.0.6": { + "name": "same-file", + "version": "1.0.6", + "package_url": "https://github.com/BurntSushi/same-file", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/same-file/1.0.6/download", + "sha256": "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" + } + }, + "targets": [ + { + "Library": { + "crate_name": "same_file", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "same_file", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [], + "selects": { + "cfg(windows)": [ + { + "id": "winapi-util 0.1.11", + "target": "winapi_util" + } + ] + } + }, + "edition": "2018", + "version": "1.0.6" + }, + "license": "Unlicense/MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, "scopeguard 1.2.0": { "name": "scopeguard", "version": "1.2.0", @@ -6836,6 +7719,13 @@ "compile_data_glob": [ "**" ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, "edition": "2018", "version": "1.0.27" }, @@ -7269,6 +8159,106 @@ ], "license_file": "LICENSE-APACHE" }, + "simd_cesu8 1.1.1": { + "name": "simd_cesu8", + "version": "1.1.1", + "package_url": "https://github.com/seancroach/simd_cesu8", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/simd_cesu8/1.1.1/download", + "sha256": "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" + } + }, + "targets": [ + { + "Library": { + "crate_name": "simd_cesu8", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "simd_cesu8", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "simdutf8 0.1.5", + "target": "simdutf8" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.1.1" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "simdutf8 0.1.5": { + "name": "simdutf8", + "version": "0.1.5", + "package_url": "https://github.com/rusticstuff/simdutf8", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/simdutf8/0.1.5/download", + "sha256": "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "simdutf8", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "simdutf8", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.1.5" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-Apache" + }, "slab 0.4.12": { "name": "slab", "version": "0.4.12", @@ -7626,11 +8616,146 @@ "edition": "2018", "version": "0.13.2" }, - "license": "MIT", + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "system-configuration 0.7.0": { + "name": "system-configuration", + "version": "0.7.0", + "package_url": "https://github.com/mullvad/system-configuration-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/system-configuration/0.7.0/download", + "sha256": "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "system_configuration", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "system_configuration", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "bitflags 2.11.0", + "target": "bitflags" + }, + { + "id": "core-foundation 0.9.4", + "target": "core_foundation" + }, + { + "id": "system-configuration-sys 0.6.0", + "target": "system_configuration_sys" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.7.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "system-configuration-sys 0.6.0": { + "name": "system-configuration-sys", + "version": "0.6.0", + "package_url": "https://github.com/mullvad/system-configuration-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/system-configuration-sys/0.6.0/download", + "sha256": "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" + } + }, + "targets": [ + { + "Library": { + "crate_name": "system_configuration_sys", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "system_configuration_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "core-foundation-sys 0.8.7", + "target": "core_foundation_sys" + }, + { + "id": "libc 0.2.183", + "target": "libc" + }, + { + "id": "system-configuration-sys 0.6.0", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.6.0" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", "license_ids": [ + "Apache-2.0", "MIT" ], - "license_file": "LICENSE" + "license_file": "LICENSE-APACHE" }, "tagptr 0.2.0": { "name": "tagptr", @@ -8884,6 +10009,61 @@ ], "license_file": "LICENSE-APACHE" }, + "walkdir 2.5.0": { + "name": "walkdir", + "version": "2.5.0", + "package_url": "https://github.com/BurntSushi/walkdir", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/walkdir/2.5.0/download", + "sha256": "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "walkdir", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "walkdir", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "same-file 1.0.6", + "target": "same_file" + } + ], + "selects": { + "cfg(windows)": [ + { + "id": "winapi-util 0.1.11", + "target": "winapi_util" + } + ] + } + }, + "edition": "2018", + "version": "2.5.0" + }, + "license": "Unlicense/MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, "wasi 0.11.1+wasi-snapshot-preview1": { "name": "wasi", "version": "0.11.1+wasi-snapshot-preview1", @@ -9520,14 +10700,14 @@ ], "license_file": null }, - "webpki-roots 0.26.11": { + "webpki-roots 1.0.6": { "name": "webpki-roots", - "version": "0.26.11", + "version": "1.0.6", "package_url": "https://github.com/rustls/webpki-roots", "repository": { "Http": { - "url": "https://static.crates.io/crates/webpki-roots/0.26.11/download", - "sha256": "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" + "url": "https://static.crates.io/crates/webpki-roots/1.0.6/download", + "sha256": "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" } }, "targets": [ @@ -9552,15 +10732,15 @@ "deps": { "common": [ { - "id": "webpki-roots 1.0.6", - "target": "webpki_roots", - "alias": "parent" + "id": "rustls-pki-types 1.14.0", + "target": "rustls_pki_types", + "alias": "pki_types" } ], "selects": {} }, "edition": "2021", - "version": "0.26.11" + "version": "1.0.6" }, "license": "CDLA-Permissive-2.0", "license_ids": [ @@ -9568,20 +10748,20 @@ ], "license_file": "LICENSE" }, - "webpki-roots 1.0.6": { - "name": "webpki-roots", - "version": "1.0.6", - "package_url": "https://github.com/rustls/webpki-roots", + "widestring 1.2.1": { + "name": "widestring", + "version": "1.2.1", + "package_url": "https://github.com/VoidStarKat/widestring-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/webpki-roots/1.0.6/download", - "sha256": "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" + "url": "https://static.crates.io/crates/widestring/1.2.1/download", + "sha256": "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" } }, "targets": [ { "Library": { - "crate_name": "webpki_roots", + "crate_name": "widestring", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -9592,44 +10772,43 @@ } } ], - "library_target_name": "webpki_roots", + "library_target_name": "widestring", "common_attrs": { "compile_data_glob": [ "**" ], - "deps": { + "crate_features": { "common": [ - { - "id": "rustls-pki-types 1.14.0", - "target": "rustls_pki_types", - "alias": "pki_types" - } + "alloc", + "default", + "std" ], "selects": {} }, "edition": "2021", - "version": "1.0.6" + "version": "1.2.1" }, - "license": "CDLA-Permissive-2.0", + "license": "MIT OR Apache-2.0", "license_ids": [ - "CDLA-Permissive-2.0" + "Apache-2.0", + "MIT" ], - "license_file": "LICENSE" + "license_file": "LICENSE-APACHE" }, - "widestring 1.2.1": { - "name": "widestring", - "version": "1.2.1", - "package_url": "https://github.com/VoidStarKat/widestring-rs", + "winapi-util 0.1.11": { + "name": "winapi-util", + "version": "0.1.11", + "package_url": "https://github.com/BurntSushi/winapi-util", "repository": { "Http": { - "url": "https://static.crates.io/crates/widestring/1.2.1/download", - "sha256": "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + "url": "https://static.crates.io/crates/winapi-util/0.1.11/download", + "sha256": "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" } }, "targets": [ { "Library": { - "crate_name": "widestring", + "crate_name": "winapi_util", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -9640,28 +10819,31 @@ } } ], - "library_target_name": "widestring", + "library_target_name": "winapi_util", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "alloc", - "default", - "std" - ], - "selects": {} + "deps": { + "common": [], + "selects": { + "cfg(windows)": [ + { + "id": "windows-sys 0.61.2", + "target": "windows_sys" + } + ] + } }, "edition": "2021", - "version": "1.2.1" + "version": "0.1.11" }, - "license": "MIT OR Apache-2.0", + "license": "Unlicense OR MIT", "license_ids": [ - "Apache-2.0", - "MIT" + "MIT", + "Unlicense" ], - "license_file": "LICENSE-APACHE" + "license_file": "LICENSE-MIT" }, "windows-link 0.2.1": { "name": "windows-link", @@ -11358,152 +12540,6 @@ ], "license_file": "LICENSE" }, - "zerocopy 0.8.47": { - "name": "zerocopy", - "version": "0.8.47", - "package_url": "https://github.com/google/zerocopy", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/zerocopy/0.8.47/download", - "sha256": "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" - } - }, - "targets": [ - { - "Library": { - "crate_name": "zerocopy", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "zerocopy", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "simd" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "zerocopy 0.8.47", - "target": "build_script_build" - } - ], - "selects": {} - }, - "edition": "2021", - "proc_macro_deps": { - "common": [], - "selects": { - "cfg(any())": [ - { - "id": "zerocopy-derive 0.8.47", - "target": "zerocopy_derive" - } - ] - } - }, - "version": "0.8.47" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ] - }, - "license": "BSD-2-Clause OR Apache-2.0 OR MIT", - "license_ids": [ - "Apache-2.0", - "BSD-2-Clause", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "zerocopy-derive 0.8.47": { - "name": "zerocopy-derive", - "version": "0.8.47", - "package_url": "https://github.com/google/zerocopy", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/zerocopy-derive/0.8.47/download", - "sha256": "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" - } - }, - "targets": [ - { - "ProcMacro": { - "crate_name": "zerocopy_derive", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "zerocopy_derive", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { - "common": [ - { - "id": "proc-macro2 1.0.106", - "target": "proc_macro2" - }, - { - "id": "quote 1.0.45", - "target": "quote" - }, - { - "id": "syn 2.0.117", - "target": "syn" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.8.47" - }, - "license": "BSD-2-Clause OR Apache-2.0 OR MIT", - "license_ids": [ - "Apache-2.0", - "BSD-2-Clause", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, "zerofrom 0.1.6": { "name": "zerofrom", "version": "0.1.6", @@ -11952,6 +12988,14 @@ "x86_64-unknown-nixos-gnu" ], "cfg(all(target_arch = \"aarch64\", target_env = \"msvc\", not(windows_raw_dylib)))": [], + "cfg(all(target_arch = \"aarch64\", target_os = \"android\"))": [], + "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))": [ + "aarch64-unknown-linux-gnu" + ], + "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))": [ + "aarch64-apple-darwin" + ], + "cfg(all(target_arch = \"loongarch64\", target_os = \"linux\"))": [], "cfg(all(target_arch = \"wasm32\", target_os = \"wasi\", target_env = \"p2\"))": [], "cfg(all(target_arch = \"wasm32\", target_os = \"wasi\", target_env = \"p3\"))": [], "cfg(all(target_arch = \"x86\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))": [], @@ -11962,6 +13006,11 @@ ], "cfg(all(target_os = \"uefi\", getrandom_backend = \"efi_rng\"))": [], "cfg(any())": [], + "cfg(any(target_arch = \"x86_64\", target_arch = \"x86\"))": [ + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-nixos-gnu" + ], "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"hurd\", target_os = \"illumos\", target_os = \"cygwin\", all(target_os = \"horizon\", target_arch = \"arm\")))": [], "cfg(any(target_os = \"haiku\", target_os = \"redox\", target_os = \"nto\", target_os = \"aix\"))": [], "cfg(any(target_os = \"ios\", target_os = \"visionos\", target_os = \"watchos\", target_os = \"tvos\"))": [], @@ -11975,6 +13024,7 @@ "x86_64-unknown-linux-gnu", "x86_64-unknown-nixos-gnu" ], + "cfg(target_os = \"android\")": [], "cfg(target_os = \"hermit\")": [], "cfg(target_os = \"netbsd\")": [], "cfg(target_os = \"redox\")": [], @@ -12013,7 +13063,7 @@ }, "direct_deps": [ "bindgen 0.70.1", - "hickory-resolver 0.25.2", + "hickory-resolver 0.26.1", "mockall 0.13.1", "serde 1.0.228", "serde_json 1.0.149", diff --git a/Cargo.lock b/Cargo.lock index 87d71e4286d23..2aec1fc9de04b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "bindgen" version = "0.70.1" @@ -103,6 +109,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -114,6 +131,41 @@ dependencies = [ "libloading", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -182,18 +234,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "envoy-dynamic-modules-builtin-extensions" version = "0.1.0" @@ -275,6 +315,17 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.32" @@ -294,6 +345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project-lite", "slab", @@ -310,18 +362,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - [[package]] name = "getrandom" version = "0.4.2" @@ -330,7 +370,8 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi 6.0.0", + "r-efi", + "rand_core", "wasip2", "wasip3", ] @@ -382,25 +423,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hickory-proto" -version = "0.25.2" +name = "hickory-net" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" dependencies = [ "async-trait", "bitflags", "bytes", "cfg-if", "data-encoding", - "enum-as-inner", "futures-channel", "futures-io", "futures-util", "h2", + "hickory-proto", "http", "idna", "ipnet", - "once_cell", + "jni", + "lru-cache", + "parking_lot", "rand", "ring", "rustls", @@ -412,31 +455,59 @@ dependencies = [ "tokio-rustls", "tracing", "url", - "webpki-roots 0.26.11", + "webpki-roots", +] + +[[package]] +name = "hickory-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +dependencies = [ + "bitflags", + "data-encoding", + "idna", + "ipnet", + "jni", + "once_cell", + "prefix-trie", + "rand", + "ring", + "rustls-pki-types", + "thiserror", + "time", + "tinyvec", + "tracing", + "url", ] [[package]] name = "hickory-resolver" -version = "0.25.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" dependencies = [ "cfg-if", "futures-util", + "hickory-net", "hickory-proto", "ipconfig", + "ipnet", + "jni", "moka", + "ndk-context", "once_cell", "parking_lot", "rand", "resolv-conf", "rustls", "smallvec", + "system-configuration", "thiserror", "tokio", "tokio-rustls", "tracing", - "webpki-roots 0.26.11", + "webpki-roots", ] [[package]] @@ -587,6 +658,9 @@ name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +dependencies = [ + "serde", +] [[package]] name = "itertools" @@ -603,6 +677,55 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "js-sys" version = "0.3.91" @@ -635,6 +758,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "litemap" version = "0.8.1" @@ -656,6 +785,15 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "memchr" version = "2.8.0" @@ -722,6 +860,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "nom" version = "7.1.3" @@ -738,6 +882,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -804,15 +957,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "predicates" version = "3.1.4" @@ -839,6 +983,17 @@ dependencies = [ "termtree", ] +[[package]] +name = "prefix-trie" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f561214012d3fc240a1f9c817cc4d57f5310910d066069c1b093f766bb5966" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -867,12 +1022,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" @@ -881,32 +1030,20 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", + "chacha20", + "getrandom 0.4.2", "rand_core", ] [[package]] name = "rand_core" -version = "0.9.5" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "redox_syscall" @@ -972,6 +1109,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustls" version = "0.23.37" @@ -1013,6 +1159,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1074,6 +1229,22 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -1130,6 +1301,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -1328,6 +1520,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1431,15 +1633,6 @@ dependencies = [ "semver", ] -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.6", -] - [[package]] name = "webpki-roots" version = "1.0.6" @@ -1455,6 +1648,15 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -1689,26 +1891,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zerofrom" version = "0.1.6" diff --git a/bazel/tests/external/Cargo.Bazel.lock b/bazel/tests/external/Cargo.Bazel.lock index 7369c489c847e..b94a9991e883e 100644 --- a/bazel/tests/external/Cargo.Bazel.lock +++ b/bazel/tests/external/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "6c229299d001117048a532a667e66e1943ca53f008d36013a5fd3f15ebd71189", + "checksum": "ab1bf27b6172ab6ff094cc63e1db922d26015dc64095d0fb29443ca48168cf20", "crates": { "aho-corasick 1.1.4": { "name": "aho-corasick", @@ -259,6 +259,45 @@ ], "license_file": "LICENSE-APACHE" }, + "autocfg 1.5.0": { + "name": "autocfg", + "version": "1.5.0", + "package_url": "https://github.com/cuviper/autocfg", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/autocfg/1.5.0/download", + "sha256": "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + } + }, + "targets": [ + { + "Library": { + "crate_name": "autocfg", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "autocfg", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "1.5.0" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "bindgen 0.70.1": { "name": "bindgen", "version": "0.70.1", @@ -667,6 +706,71 @@ ], "license_file": "LICENSE-APACHE" }, + "chacha20 0.10.0": { + "name": "chacha20", + "version": "0.10.0", + "package_url": "https://github.com/RustCrypto/stream-ciphers", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/chacha20/0.10.0/download", + "sha256": "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" + } + }, + "targets": [ + { + "Library": { + "crate_name": "chacha20", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "chacha20", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "rng" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + }, + { + "id": "rand_core 0.10.1", + "target": "rand_core" + } + ], + "selects": { + "cfg(any(target_arch = \"x86_64\", target_arch = \"x86\"))": [ + { + "id": "cpufeatures 0.3.0", + "target": "cpufeatures" + } + ] + } + }, + "edition": "2024", + "version": "0.10.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "clang-sys 1.8.1": { "name": "clang-sys", "version": "1.8.1", @@ -774,20 +878,20 @@ ], "license_file": "LICENSE.txt" }, - "critical-section 1.2.0": { - "name": "critical-section", - "version": "1.2.0", - "package_url": "https://github.com/rust-embedded/critical-section", + "combine 4.6.7": { + "name": "combine", + "version": "4.6.7", + "package_url": "https://github.com/Marwes/combine", "repository": { "Http": { - "url": "https://static.crates.io/crates/critical-section/1.2.0/download", - "sha256": "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + "url": "https://static.crates.io/crates/combine/4.6.7/download", + "sha256": "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" } }, "targets": [ { "Library": { - "crate_name": "critical_section", + "crate_name": "combine", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -798,35 +902,43 @@ } } ], - "library_target_name": "critical_section", + "library_target_name": "combine", "common_attrs": { "compile_data_glob": [ "**" ], + "deps": { + "common": [ + { + "id": "memchr 2.8.0", + "target": "memchr" + } + ], + "selects": {} + }, "edition": "2018", - "version": "1.2.0" + "version": "4.6.7" }, - "license": "MIT OR Apache-2.0", + "license": "MIT", "license_ids": [ - "Apache-2.0", "MIT" ], - "license_file": "LICENSE-APACHE" + "license_file": "LICENSE" }, - "crossbeam-channel 0.5.15": { - "name": "crossbeam-channel", - "version": "0.5.15", - "package_url": "https://github.com/crossbeam-rs/crossbeam", + "core-foundation 0.9.4": { + "name": "core-foundation", + "version": "0.9.4", + "package_url": "https://github.com/servo/core-foundation-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/crossbeam-channel/0.5.15/download", - "sha256": "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" + "url": "https://static.crates.io/crates/core-foundation/0.9.4/download", + "sha256": "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" } }, "targets": [ { "Library": { - "crate_name": "crossbeam_channel", + "crate_name": "core_foundation", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -837,7 +949,7 @@ } } ], - "library_target_name": "crossbeam_channel", + "library_target_name": "core_foundation", "common_attrs": { "compile_data_glob": [ "**" @@ -845,21 +957,25 @@ "crate_features": { "common": [ "default", - "std" + "link" ], "selects": {} }, "deps": { "common": [ { - "id": "crossbeam-utils 0.8.21", - "target": "crossbeam_utils" + "id": "core-foundation-sys 0.8.7", + "target": "core_foundation_sys" + }, + { + "id": "libc 0.2.183", + "target": "libc" } ], "selects": {} }, - "edition": "2021", - "version": "0.5.15" + "edition": "2018", + "version": "0.9.4" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -868,20 +984,20 @@ ], "license_file": "LICENSE-APACHE" }, - "crossbeam-epoch 0.9.18": { - "name": "crossbeam-epoch", - "version": "0.9.18", - "package_url": "https://github.com/crossbeam-rs/crossbeam", + "core-foundation-sys 0.8.7": { + "name": "core-foundation-sys", + "version": "0.8.7", + "package_url": "https://github.com/servo/core-foundation-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/crossbeam-epoch/0.9.18/download", - "sha256": "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" + "url": "https://static.crates.io/crates/core-foundation-sys/0.8.7/download", + "sha256": "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" } }, "targets": [ { "Library": { - "crate_name": "crossbeam_epoch", + "crate_name": "core_foundation_sys", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -892,30 +1008,20 @@ } } ], - "library_target_name": "crossbeam_epoch", + "library_target_name": "core_foundation_sys", "common_attrs": { "compile_data_glob": [ "**" ], "crate_features": { "common": [ - "alloc", "default", - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "crossbeam-utils 0.8.21", - "target": "crossbeam_utils" - } + "link" ], "selects": {} }, - "edition": "2021", - "version": "0.9.18" + "edition": "2018", + "version": "0.8.7" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -924,20 +1030,20 @@ ], "license_file": "LICENSE-APACHE" }, - "crossbeam-utils 0.8.21": { - "name": "crossbeam-utils", - "version": "0.8.21", - "package_url": "https://github.com/crossbeam-rs/crossbeam", + "cpufeatures 0.3.0": { + "name": "cpufeatures", + "version": "0.3.0", + "package_url": "https://github.com/RustCrypto/utils", "repository": { "Http": { - "url": "https://static.crates.io/crates/crossbeam-utils/0.8.21/download", - "sha256": "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + "url": "https://static.crates.io/crates/cpufeatures/0.3.0/download", + "sha256": "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" } }, "targets": [ { "Library": { - "crate_name": "crossbeam_utils", + "crate_name": "cpufeatures", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -946,54 +1052,44 @@ ] } } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } } ], - "library_target_name": "crossbeam_utils", + "library_target_name": "cpufeatures", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "default", - "std" - ], - "selects": {} - }, "deps": { - "common": [ - { - "id": "crossbeam-utils 0.8.21", - "target": "build_script_build" - } - ], - "selects": {} + "common": [], + "selects": { + "cfg(all(target_arch = \"aarch64\", target_os = \"android\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ], + "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ], + "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ], + "cfg(all(target_arch = \"loongarch64\", target_os = \"linux\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ] + } }, - "edition": "2021", - "version": "0.8.21" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ] + "edition": "2024", + "version": "0.3.0" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -1002,20 +1098,20 @@ ], "license_file": "LICENSE-APACHE" }, - "data-encoding 2.10.0": { - "name": "data-encoding", - "version": "2.10.0", - "package_url": "https://github.com/ia0/data-encoding", + "critical-section 1.2.0": { + "name": "critical-section", + "version": "1.2.0", + "package_url": "https://github.com/rust-embedded/critical-section", "repository": { "Http": { - "url": "https://static.crates.io/crates/data-encoding/2.10.0/download", - "sha256": "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + "url": "https://static.crates.io/crates/critical-section/1.2.0/download", + "sha256": "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" } }, "targets": [ { "Library": { - "crate_name": "data_encoding", + "crate_name": "critical_section", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -1026,31 +1122,259 @@ } } ], - "library_target_name": "data_encoding", + "library_target_name": "critical_section", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "alloc", - "std" - ], - "selects": {} - }, "edition": "2018", - "version": "2.10.0" + "version": "1.2.0" }, - "license": "MIT", + "license": "MIT OR Apache-2.0", "license_ids": [ + "Apache-2.0", "MIT" ], - "license_file": "LICENSE" + "license_file": "LICENSE-APACHE" }, - "deranged 0.5.8": { - "name": "deranged", - "version": "0.5.8", - "package_url": "https://github.com/jhpratt/deranged", + "crossbeam-channel 0.5.15": { + "name": "crossbeam-channel", + "version": "0.5.15", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-channel/0.5.15/download", + "sha256": "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_channel", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_channel", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "crossbeam_utils" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.5.15" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "crossbeam-epoch 0.9.18": { + "name": "crossbeam-epoch", + "version": "0.9.18", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-epoch/0.9.18/download", + "sha256": "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_epoch", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_epoch", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "crossbeam_utils" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.9.18" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "crossbeam-utils 0.8.21": { + "name": "crossbeam-utils", + "version": "0.8.21", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-utils/0.8.21/download", + "sha256": "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_utils", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_utils", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.21" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "data-encoding 2.10.0": { + "name": "data-encoding", + "version": "2.10.0", + "package_url": "https://github.com/ia0/data-encoding", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/data-encoding/2.10.0/download", + "sha256": "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + } + }, + "targets": [ + { + "Library": { + "crate_name": "data_encoding", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "data_encoding", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "2.10.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "deranged 0.5.8": { + "name": "deranged", + "version": "0.5.8", + "package_url": "https://github.com/jhpratt/deranged", "repository": { "Http": { "url": "https://static.crates.io/crates/deranged/0.5.8/download", @@ -1232,70 +1556,17 @@ "compile_data_glob": [ "**" ], - "edition": "2021", - "version": "1.15.0" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "enum-as-inner 0.6.1": { - "name": "enum-as-inner", - "version": "0.6.1", - "package_url": "https://github.com/bluejekyll/enum-as-inner", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/enum-as-inner/0.6.1/download", - "sha256": "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" - } - }, - "targets": [ - { - "ProcMacro": { - "crate_name": "enum_as_inner", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "enum_as_inner", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { + "crate_features": { "common": [ - { - "id": "heck 0.5.0", - "target": "heck" - }, - { - "id": "proc-macro2 1.0.106", - "target": "proc_macro2" - }, - { - "id": "quote 1.0.45", - "target": "quote" - }, - { - "id": "syn 2.0.117", - "target": "syn" - } + "default", + "std" ], "selects": {} }, - "edition": "2018", - "version": "0.6.1" + "edition": "2021", + "version": "1.15.0" }, - "license": "MIT/Apache-2.0", + "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" @@ -1316,7 +1587,7 @@ "deps": { "common": [ { - "id": "hickory-resolver 0.25.2", + "id": "hickory-resolver 0.26.1", "target": "hickory_resolver" }, { @@ -1831,6 +2102,62 @@ ], "license_file": "LICENSE-APACHE" }, + "futures-macro 0.3.32": { + "name": "futures-macro", + "version": "0.3.32", + "package_url": "https://github.com/rust-lang/futures-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/futures-macro/0.3.32/download", + "sha256": "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "futures_macro", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "futures_macro", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.106", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.45", + "target": "quote" + }, + { + "id": "syn 2.0.117", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.3.32" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "futures-sink 0.3.32": { "name": "futures-sink", "version": "0.3.32", @@ -1956,6 +2283,9 @@ "crate_features": { "common": [ "alloc", + "async-await", + "async-await-macro", + "futures-macro", "slab", "std" ], @@ -1979,72 +2309,20 @@ "id": "slab 0.4.12", "target": "slab" } - ], - "selects": {} - }, - "edition": "2018", - "version": "0.3.32" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "getrandom 0.2.17": { - "name": "getrandom", - "version": "0.2.17", - "package_url": "https://github.com/rust-random/getrandom", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/getrandom/0.2.17/download", - "sha256": "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" - } - }, - "targets": [ - { - "Library": { - "crate_name": "getrandom", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "getrandom", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { + ], + "selects": {} + }, + "edition": "2018", + "proc_macro_deps": { "common": [ { - "id": "cfg-if 1.0.4", - "target": "cfg_if" + "id": "futures-macro 0.3.32", + "target": "futures_macro" } ], - "selects": { - "cfg(target_os = \"wasi\")": [ - { - "id": "wasi 0.11.1+wasi-snapshot-preview1", - "target": "wasi" - } - ], - "cfg(unix)": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ] - } + "selects": {} }, - "edition": "2018", - "version": "0.2.17" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -2053,14 +2331,14 @@ ], "license_file": "LICENSE-APACHE" }, - "getrandom 0.3.4": { + "getrandom 0.2.17": { "name": "getrandom", - "version": "0.3.4", + "version": "0.2.17", "package_url": "https://github.com/rust-random/getrandom", "repository": { "Http": { - "url": "https://static.crates.io/crates/getrandom/0.3.4/download", - "sha256": "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" + "url": "https://static.crates.io/crates/getrandom/0.2.17/download", + "sha256": "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" } }, "targets": [ @@ -2075,18 +2353,6 @@ ] } } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } } ], "library_target_name": "getrandom", @@ -2094,79 +2360,21 @@ "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "std" - ], - "selects": {} - }, "deps": { "common": [ { "id": "cfg-if 1.0.4", "target": "cfg_if" - }, - { - "id": "getrandom 0.3.4", - "target": "build_script_build" } ], "selects": { - "cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(all(target_arch = \"wasm32\", target_os = \"wasi\", target_env = \"p2\"))": [ - { - "id": "wasip2 1.0.2+wasi-0.2.9", - "target": "wasip2" - } - ], - "cfg(all(target_os = \"uefi\", getrandom_backend = \"efi_rng\"))": [ - { - "id": "r-efi 5.3.0", - "target": "r_efi" - } - ], - "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"hurd\", target_os = \"illumos\", target_os = \"cygwin\", all(target_os = \"horizon\", target_arch = \"arm\")))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(any(target_os = \"haiku\", target_os = \"redox\", target_os = \"nto\", target_os = \"aix\"))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(any(target_os = \"ios\", target_os = \"visionos\", target_os = \"watchos\", target_os = \"tvos\"))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(target_os = \"netbsd\")": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(target_os = \"solaris\")": [ + "cfg(target_os = \"wasi\")": [ { - "id": "libc 0.2.183", - "target": "libc" + "id": "wasi 0.11.1+wasi-snapshot-preview1", + "target": "wasi" } ], - "cfg(target_os = \"vxworks\")": [ + "cfg(unix)": [ { "id": "libc 0.2.183", "target": "libc" @@ -2174,19 +2382,8 @@ ] } }, - "edition": "2021", - "version": "0.3.4" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ] + "edition": "2018", + "version": "0.2.17" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -2236,6 +2433,13 @@ "compile_data_glob": [ "**" ], + "crate_features": { + "common": [ + "std", + "sys_rng" + ], + "selects": {} + }, "deps": { "common": [ { @@ -2245,6 +2449,10 @@ { "id": "getrandom 0.4.2", "target": "build_script_build" + }, + { + "id": "rand_core 0.10.1", + "target": "rand_core" } ], "selects": { @@ -2601,20 +2809,20 @@ ], "license_file": "LICENSE-APACHE" }, - "hickory-proto 0.25.2": { - "name": "hickory-proto", - "version": "0.25.2", + "hickory-net 0.26.1": { + "name": "hickory-net", + "version": "0.26.1", "package_url": "https://github.com/hickory-dns/hickory-dns", "repository": { "Http": { - "url": "https://static.crates.io/crates/hickory-proto/0.25.2/download", - "sha256": "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" + "url": "https://static.crates.io/crates/hickory-net/0.26.1/download", + "sha256": "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" } }, "targets": [ { "Library": { - "crate_name": "hickory_proto", + "crate_name": "hickory_net", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -2625,7 +2833,7 @@ } } ], - "library_target_name": "hickory_proto", + "library_target_name": "hickory_net", "common_attrs": { "compile_data_glob": [ "**" @@ -2636,9 +2844,7 @@ "__https", "__tls", "dnssec-ring", - "futures-io", "https-ring", - "std", "tls-ring", "tokio", "webpki-roots" @@ -2679,6 +2885,10 @@ "id": "h2 0.4.13", "target": "h2" }, + { + "id": "hickory-proto 0.26.1", + "target": "hickory_proto" + }, { "id": "http 1.4.0", "target": "http" @@ -2692,11 +2902,15 @@ "target": "ipnet" }, { - "id": "once_cell 1.21.4", - "target": "once_cell" + "id": "lru-cache 0.1.2", + "target": "lru_cache" + }, + { + "id": "parking_lot 0.12.5", + "target": "parking_lot" }, { - "id": "rand 0.9.4", + "id": "rand 0.10.1", "target": "rand" }, { @@ -2740,11 +2954,18 @@ "target": "url" }, { - "id": "webpki-roots 0.26.11", + "id": "webpki-roots 1.0.6", "target": "webpki_roots" } ], - "selects": {} + "selects": { + "cfg(target_os = \"android\")": [ + { + "id": "jni 0.22.4", + "target": "jni" + } + ] + } }, "edition": "2021", "proc_macro_deps": { @@ -2752,15 +2973,127 @@ { "id": "async-trait 0.1.89", "target": "async_trait" + } + ], + "selects": {} + }, + "version": "0.26.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "hickory-proto 0.26.1": { + "name": "hickory-proto", + "version": "0.26.1", + "package_url": "https://github.com/hickory-dns/hickory-dns", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/hickory-proto/0.26.1/download", + "sha256": "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" + } + }, + "targets": [ + { + "Library": { + "crate_name": "hickory_proto", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "hickory_proto", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "__dnssec", + "access-control", + "dnssec-ring", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "bitflags 2.11.0", + "target": "bitflags" + }, + { + "id": "data-encoding 2.10.0", + "target": "data_encoding" + }, + { + "id": "idna 1.1.0", + "target": "idna" + }, + { + "id": "ipnet 2.12.0", + "target": "ipnet" + }, + { + "id": "once_cell 1.21.4", + "target": "once_cell" + }, + { + "id": "prefix-trie 0.8.3", + "target": "prefix_trie" + }, + { + "id": "rand 0.10.1", + "target": "rand" + }, + { + "id": "ring 0.17.14", + "target": "ring" + }, + { + "id": "rustls-pki-types 1.14.0", + "target": "rustls_pki_types" + }, + { + "id": "thiserror 2.0.18", + "target": "thiserror" + }, + { + "id": "time 0.3.47", + "target": "time" + }, + { + "id": "tinyvec 1.11.0", + "target": "tinyvec" + }, + { + "id": "tracing 0.1.44", + "target": "tracing" }, { - "id": "enum-as-inner 0.6.1", - "target": "enum_as_inner" + "id": "url 2.5.8", + "target": "url" } ], - "selects": {} + "selects": { + "cfg(target_os = \"android\")": [ + { + "id": "jni 0.22.4", + "target": "jni" + } + ] + } }, - "version": "0.25.2" + "edition": "2021", + "version": "0.26.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -2769,14 +3102,14 @@ ], "license_file": "LICENSE-APACHE" }, - "hickory-resolver 0.25.2": { + "hickory-resolver 0.26.1": { "name": "hickory-resolver", - "version": "0.25.2", + "version": "0.26.1", "package_url": "https://github.com/hickory-dns/hickory-dns", "repository": { "Http": { - "url": "https://static.crates.io/crates/hickory-resolver/0.25.2/download", - "sha256": "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" + "url": "https://static.crates.io/crates/hickory-resolver/0.26.1/download", + "sha256": "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" } }, "targets": [ @@ -2824,9 +3157,17 @@ "target": "futures_util" }, { - "id": "hickory-proto 0.25.2", + "id": "hickory-net 0.26.1", + "target": "hickory_net" + }, + { + "id": "hickory-proto 0.26.1", "target": "hickory_proto" }, + { + "id": "ipnet 2.12.0", + "target": "ipnet" + }, { "id": "moka 0.12.15", "target": "moka" @@ -2840,7 +3181,7 @@ "target": "parking_lot" }, { - "id": "rand 0.9.4", + "id": "rand 0.10.1", "target": "rand" }, { @@ -2872,11 +3213,17 @@ "target": "tracing" }, { - "id": "webpki-roots 0.26.11", + "id": "webpki-roots 1.0.6", "target": "webpki_roots" } ], "selects": { + "aarch64-apple-darwin": [ + { + "id": "system-configuration 0.7.0", + "target": "system_configuration" + } + ], "x86_64-pc-windows-msvc": [ { "id": "ipconfig 0.3.4", @@ -2886,7 +3233,7 @@ } }, "edition": "2021", - "version": "0.25.2" + "version": "0.26.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -3859,41 +4206,355 @@ } } ], - "library_target_name": "ipnet", + "library_target_name": "ipnet", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "serde", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "serde 1.0.228", + "target": "serde" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "2.12.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "itertools 0.13.0": { + "name": "itertools", + "version": "0.13.0", + "package_url": "https://github.com/rust-itertools/itertools", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/itertools/0.13.0/download", + "sha256": "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" + } + }, + "targets": [ + { + "Library": { + "crate_name": "itertools", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "itertools", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "either 1.15.0", + "target": "either" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.13.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "itoa 1.0.18": { + "name": "itoa", + "version": "1.0.18", + "package_url": "https://github.com/dtolnay/itoa", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/itoa/1.0.18/download", + "sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + } + }, + "targets": [ + { + "Library": { + "crate_name": "itoa", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "itoa", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "1.0.18" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "jni 0.22.4": { + "name": "jni", + "version": "0.22.4", + "package_url": "https://github.com/jni-rs/jni-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/jni/0.22.4/download", + "sha256": "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" + } + }, + "targets": [ + { + "Library": { + "crate_name": "jni", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "jni", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + }, + { + "id": "combine 4.6.7", + "target": "combine" + }, + { + "id": "jni 0.22.4", + "target": "build_script_build" + }, + { + "id": "jni-sys 0.4.1", + "target": "jni_sys" + }, + { + "id": "log 0.4.29", + "target": "log" + }, + { + "id": "simd_cesu8 1.1.1", + "target": "simd_cesu8" + }, + { + "id": "thiserror 2.0.18", + "target": "thiserror" + } + ], + "selects": { + "cfg(windows)": [ + { + "id": "windows-link 0.2.1", + "target": "windows_link" + } + ] + } + }, + "edition": "2024", + "proc_macro_deps": { + "common": [ + { + "id": "jni-macros 0.22.4", + "target": "jni_macros" + } + ], + "selects": {} + }, + "version": "0.22.4" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "walkdir 2.5.0", + "target": "walkdir" + } + ], + "selects": {} + } + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": null + }, + "jni-macros 0.22.4": { + "name": "jni-macros", + "version": "0.22.4", + "package_url": "https://github.com/jni-rs/jni-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/jni-macros/0.22.4/download", + "sha256": "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "jni_macros", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "jni_macros", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { + "deps": { "common": [ - "std" + { + "id": "jni-macros 0.22.4", + "target": "build_script_build" + }, + { + "id": "proc-macro2 1.0.106", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.45", + "target": "quote" + }, + { + "id": "simd_cesu8 1.1.1", + "target": "simd_cesu8" + }, + { + "id": "syn 2.0.117", + "target": "syn" + } ], "selects": {} }, - "edition": "2018", - "version": "2.12.0" + "edition": "2024", + "version": "0.22.4" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "rustc_version 0.4.1", + "target": "rustc_version" + } + ], + "selects": {} + } }, "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" ], - "license_file": "LICENSE-APACHE" + "license_file": null }, - "itertools 0.13.0": { - "name": "itertools", - "version": "0.13.0", - "package_url": "https://github.com/rust-itertools/itertools", + "jni-sys 0.4.1": { + "name": "jni-sys", + "version": "0.4.1", + "package_url": "https://github.com/jni-rs/jni-sys", "repository": { "Http": { - "url": "https://static.crates.io/crates/itertools/0.13.0/download", - "sha256": "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" + "url": "https://static.crates.io/crates/jni-sys/0.4.1/download", + "sha256": "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" } }, "targets": [ { "Library": { - "crate_name": "itertools", + "crate_name": "jni_sys", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -3904,22 +4565,22 @@ } } ], - "library_target_name": "itertools", + "library_target_name": "jni_sys", "common_attrs": { "compile_data_glob": [ "**" ], - "deps": { + "edition": "2021", + "proc_macro_deps": { "common": [ { - "id": "either 1.15.0", - "target": "either" + "id": "jni-sys-macros 0.4.1", + "target": "jni_sys_macros" } ], "selects": {} }, - "edition": "2018", - "version": "0.13.0" + "version": "0.4.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -3928,20 +4589,20 @@ ], "license_file": "LICENSE-APACHE" }, - "itoa 1.0.18": { - "name": "itoa", - "version": "1.0.18", - "package_url": "https://github.com/dtolnay/itoa", + "jni-sys-macros 0.4.1": { + "name": "jni-sys-macros", + "version": "0.4.1", + "package_url": "https://github.com/jni-rs/jni-sys", "repository": { "Http": { - "url": "https://static.crates.io/crates/itoa/1.0.18/download", - "sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + "url": "https://static.crates.io/crates/jni-sys-macros/0.4.1/download", + "sha256": "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" } }, "targets": [ { - "Library": { - "crate_name": "itoa", + "ProcMacro": { + "crate_name": "jni_sys_macros", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -3952,20 +4613,33 @@ } } ], - "library_target_name": "itoa", + "library_target_name": "jni_sys_macros", "common_attrs": { "compile_data_glob": [ "**" ], + "deps": { + "common": [ + { + "id": "quote 1.0.45", + "target": "quote" + }, + { + "id": "syn 2.0.117", + "target": "syn" + } + ], + "selects": {} + }, "edition": "2021", - "version": "1.0.18" + "version": "0.4.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" ], - "license_file": "LICENSE-APACHE" + "license_file": null }, "js-sys 0.3.91": { "name": "js-sys", @@ -4209,6 +4883,45 @@ ], "license_file": "LICENSE" }, + "linked-hash-map 0.5.6": { + "name": "linked-hash-map", + "version": "0.5.6", + "package_url": "https://github.com/contain-rs/linked-hash-map", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/linked-hash-map/0.5.6/download", + "sha256": "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + } + }, + "targets": [ + { + "Library": { + "crate_name": "linked_hash_map", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "linked_hash_map", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "0.5.6" + }, + "license": "MIT/Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "litemap 0.8.1": { "name": "litemap", "version": "0.8.1", @@ -4341,6 +5054,54 @@ ], "license_file": "LICENSE-APACHE" }, + "lru-cache 0.1.2": { + "name": "lru-cache", + "version": "0.1.2", + "package_url": "https://github.com/contain-rs/lru-cache", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/lru-cache/0.1.2/download", + "sha256": "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" + } + }, + "targets": [ + { + "Library": { + "crate_name": "lru_cache", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "lru_cache", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "linked-hash-map 0.5.6", + "target": "linked_hash_map" + } + ], + "selects": {} + }, + "edition": "2015", + "version": "0.1.2" + }, + "license": "MIT/Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "memchr 2.8.0": { "name": "memchr", "version": "2.8.0", @@ -4758,6 +5519,45 @@ ], "license_file": "LICENSE-APACHE" }, + "ndk-context 0.1.1": { + "name": "ndk-context", + "version": "0.1.1", + "package_url": "https://github.com/rust-windowing/android-ndk-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/ndk-context/0.1.1/download", + "sha256": "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "ndk_context", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "ndk_context", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.1.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": null + }, "nom 7.1.3": { "name": "nom", "version": "7.1.3", @@ -4845,15 +5645,102 @@ "compile_data_glob": [ "**" ], - "edition": "2021", - "version": "0.2.1" + "edition": "2021", + "version": "0.2.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-Apache" + }, + "num-traits 0.2.19": { + "name": "num-traits", + "version": "0.2.19", + "package_url": "https://github.com/rust-num/num-traits", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/num-traits/0.2.19/download", + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" + } + }, + "targets": [ + { + "Library": { + "crate_name": "num_traits", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "num_traits", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "num-traits 0.2.19", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.2.19" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "autocfg 1.5.0", + "target": "autocfg" + } + ], + "selects": {} + } }, "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" ], - "license_file": "LICENSE-Apache" + "license_file": "LICENSE-APACHE" }, "once_cell 1.21.4": { "name": "once_cell", @@ -5329,61 +6216,6 @@ ], "license_file": "LICENSE-Apache" }, - "ppv-lite86 0.2.21": { - "name": "ppv-lite86", - "version": "0.2.21", - "package_url": "https://github.com/cryptocorrosion/cryptocorrosion", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/ppv-lite86/0.2.21/download", - "sha256": "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" - } - }, - "targets": [ - { - "Library": { - "crate_name": "ppv_lite86", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "ppv_lite86", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "simd", - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "zerocopy 0.8.47", - "target": "zerocopy" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.2.21" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, "predicates 3.1.4": { "name": "predicates", "version": "3.1.4", @@ -5527,6 +6359,69 @@ ], "license_file": "LICENSE-APACHE" }, + "prefix-trie 0.8.3": { + "name": "prefix-trie", + "version": "0.8.3", + "package_url": "https://github.com/tiborschneider/prefix-trie", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/prefix-trie/0.8.3/download", + "sha256": "90f561214012d3fc240a1f9c817cc4d57f5310910d066069c1b093f766bb5966" + } + }, + "targets": [ + { + "Library": { + "crate_name": "prefix_trie", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "prefix_trie", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "ipnet" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "either 1.15.0", + "target": "either" + }, + { + "id": "ipnet 2.12.0", + "target": "ipnet" + }, + { + "id": "num-traits 0.2.19", + "target": "num_traits" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.3" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "prettyplease 0.2.37": { "name": "prettyplease", "version": "0.2.37", @@ -5777,46 +6672,6 @@ ], "license_file": "LICENSE-APACHE" }, - "r-efi 5.3.0": { - "name": "r-efi", - "version": "5.3.0", - "package_url": "https://github.com/r-efi/r-efi", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/r-efi/5.3.0/download", - "sha256": "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - } - }, - "targets": [ - { - "Library": { - "crate_name": "r_efi", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "r_efi", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "edition": "2018", - "version": "5.3.0" - }, - "license": "MIT OR Apache-2.0 OR LGPL-2.1-or-later", - "license_ids": [ - "Apache-2.0", - "LGPL-2.1", - "MIT" - ], - "license_file": null - }, "r-efi 6.0.0": { "name": "r-efi", "version": "6.0.0", @@ -5857,14 +6712,14 @@ ], "license_file": null }, - "rand 0.9.4": { + "rand 0.10.1": { "name": "rand", - "version": "0.9.4", + "version": "0.10.1", "package_url": "https://github.com/rust-random/rand", "repository": { "Http": { - "url": "https://static.crates.io/crates/rand/0.9.4/download", - "sha256": "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" + "url": "https://static.crates.io/crates/rand/0.10.1/download", + "sha256": "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" } }, "targets": [ @@ -5889,9 +6744,9 @@ "crate_features": { "common": [ "alloc", - "os_rng", "std", "std_rng", + "sys_rng", "thread_rng" ], "selects": {} @@ -5899,76 +6754,22 @@ "deps": { "common": [ { - "id": "rand_chacha 0.9.0", - "target": "rand_chacha" + "id": "chacha20 0.10.0", + "target": "chacha20" }, { - "id": "rand_core 0.9.5", - "target": "rand_core" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.9.4" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "rand_chacha 0.9.0": { - "name": "rand_chacha", - "version": "0.9.0", - "package_url": "https://github.com/rust-random/rand", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/rand_chacha/0.9.0/download", - "sha256": "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" - } - }, - "targets": [ - { - "Library": { - "crate_name": "rand_chacha", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "rand_chacha", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "ppv-lite86 0.2.21", - "target": "ppv_lite86" + "id": "getrandom 0.4.2", + "target": "getrandom" }, { - "id": "rand_core 0.9.5", + "id": "rand_core 0.10.1", "target": "rand_core" } ], "selects": {} }, - "edition": "2021", - "version": "0.9.0" + "edition": "2024", + "version": "0.10.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -5977,14 +6778,14 @@ ], "license_file": "LICENSE-APACHE" }, - "rand_core 0.9.5": { + "rand_core 0.10.1": { "name": "rand_core", - "version": "0.9.5", - "package_url": "https://github.com/rust-random/rand", + "version": "0.10.1", + "package_url": "https://github.com/rust-random/rand_core", "repository": { "Http": { - "url": "https://static.crates.io/crates/rand_core/0.9.5/download", - "sha256": "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + "url": "https://static.crates.io/crates/rand_core/0.10.1/download", + "sha256": "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" } }, "targets": [ @@ -6006,24 +6807,8 @@ "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "os_rng", - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "getrandom 0.3.4", - "target": "getrandom" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.9.5" + "edition": "2024", + "version": "0.10.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -6417,14 +7202,60 @@ "package_url": "https://github.com/rust-lang-nursery/rustc-hash", "repository": { "Http": { - "url": "https://static.crates.io/crates/rustc-hash/1.1.0/download", - "sha256": "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + "url": "https://static.crates.io/crates/rustc-hash/1.1.0/download", + "sha256": "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rustc_hash", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rustc_hash", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "edition": "2015", + "version": "1.1.0" + }, + "license": "Apache-2.0/MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rustc_version 0.4.1": { + "name": "rustc_version", + "version": "0.4.1", + "package_url": "https://github.com/djc/rustc-version-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rustc_version/0.4.1/download", + "sha256": "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" } }, "targets": [ { "Library": { - "crate_name": "rustc_hash", + "crate_name": "rustc_version", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -6435,22 +7266,24 @@ } } ], - "library_target_name": "rustc_hash", + "library_target_name": "rustc_version", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { + "deps": { "common": [ - "default", - "std" + { + "id": "semver 1.0.27", + "target": "semver" + } ], "selects": {} }, - "edition": "2015", - "version": "1.1.0" + "edition": "2018", + "version": "0.4.1" }, - "license": "Apache-2.0/MIT", + "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" @@ -6532,7 +7365,7 @@ "alias": "pki_types" }, { - "id": "rustls-webpki 0.103.12", + "id": "rustls-webpki 0.103.13", "target": "webpki" }, { @@ -6633,14 +7466,14 @@ ], "license_file": "LICENSE-APACHE" }, - "rustls-webpki 0.103.12": { + "rustls-webpki 0.103.13": { "name": "rustls-webpki", - "version": "0.103.12", + "version": "0.103.13", "package_url": "https://github.com/rustls/webpki", "repository": { "Http": { - "url": "https://static.crates.io/crates/rustls-webpki/0.103.12/download", - "sha256": "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" + "url": "https://static.crates.io/crates/rustls-webpki/0.103.13/download", + "sha256": "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" } }, "targets": [ @@ -6689,7 +7522,7 @@ "selects": {} }, "edition": "2021", - "version": "0.103.12" + "version": "0.103.13" }, "license": "ISC", "license_ids": [ @@ -6768,6 +7601,56 @@ ], "license_file": "LICENSE-APACHE" }, + "same-file 1.0.6": { + "name": "same-file", + "version": "1.0.6", + "package_url": "https://github.com/BurntSushi/same-file", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/same-file/1.0.6/download", + "sha256": "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" + } + }, + "targets": [ + { + "Library": { + "crate_name": "same_file", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "same_file", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [], + "selects": { + "cfg(windows)": [ + { + "id": "winapi-util 0.1.11", + "target": "winapi_util" + } + ] + } + }, + "edition": "2018", + "version": "1.0.6" + }, + "license": "Unlicense/MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, "scopeguard 1.2.0": { "name": "scopeguard", "version": "1.2.0", @@ -6836,6 +7719,13 @@ "compile_data_glob": [ "**" ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, "edition": "2018", "version": "1.0.27" }, @@ -7269,6 +8159,106 @@ ], "license_file": "LICENSE-APACHE" }, + "simd_cesu8 1.1.1": { + "name": "simd_cesu8", + "version": "1.1.1", + "package_url": "https://github.com/seancroach/simd_cesu8", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/simd_cesu8/1.1.1/download", + "sha256": "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" + } + }, + "targets": [ + { + "Library": { + "crate_name": "simd_cesu8", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "simd_cesu8", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "simdutf8 0.1.5", + "target": "simdutf8" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.1.1" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "simdutf8 0.1.5": { + "name": "simdutf8", + "version": "0.1.5", + "package_url": "https://github.com/rusticstuff/simdutf8", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/simdutf8/0.1.5/download", + "sha256": "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "simdutf8", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "simdutf8", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.1.5" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-Apache" + }, "slab 0.4.12": { "name": "slab", "version": "0.4.12", @@ -7626,11 +8616,146 @@ "edition": "2018", "version": "0.13.2" }, - "license": "MIT", + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "system-configuration 0.7.0": { + "name": "system-configuration", + "version": "0.7.0", + "package_url": "https://github.com/mullvad/system-configuration-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/system-configuration/0.7.0/download", + "sha256": "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "system_configuration", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "system_configuration", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "bitflags 2.11.0", + "target": "bitflags" + }, + { + "id": "core-foundation 0.9.4", + "target": "core_foundation" + }, + { + "id": "system-configuration-sys 0.6.0", + "target": "system_configuration_sys" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.7.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "system-configuration-sys 0.6.0": { + "name": "system-configuration-sys", + "version": "0.6.0", + "package_url": "https://github.com/mullvad/system-configuration-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/system-configuration-sys/0.6.0/download", + "sha256": "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" + } + }, + "targets": [ + { + "Library": { + "crate_name": "system_configuration_sys", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "system_configuration_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "core-foundation-sys 0.8.7", + "target": "core_foundation_sys" + }, + { + "id": "libc 0.2.183", + "target": "libc" + }, + { + "id": "system-configuration-sys 0.6.0", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.6.0" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", "license_ids": [ + "Apache-2.0", "MIT" ], - "license_file": "LICENSE" + "license_file": "LICENSE-APACHE" }, "tagptr 0.2.0": { "name": "tagptr", @@ -8884,6 +10009,61 @@ ], "license_file": "LICENSE-APACHE" }, + "walkdir 2.5.0": { + "name": "walkdir", + "version": "2.5.0", + "package_url": "https://github.com/BurntSushi/walkdir", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/walkdir/2.5.0/download", + "sha256": "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "walkdir", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "walkdir", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "same-file 1.0.6", + "target": "same_file" + } + ], + "selects": { + "cfg(windows)": [ + { + "id": "winapi-util 0.1.11", + "target": "winapi_util" + } + ] + } + }, + "edition": "2018", + "version": "2.5.0" + }, + "license": "Unlicense/MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, "wasi 0.11.1+wasi-snapshot-preview1": { "name": "wasi", "version": "0.11.1+wasi-snapshot-preview1", @@ -9520,14 +10700,14 @@ ], "license_file": null }, - "webpki-roots 0.26.11": { + "webpki-roots 1.0.6": { "name": "webpki-roots", - "version": "0.26.11", + "version": "1.0.6", "package_url": "https://github.com/rustls/webpki-roots", "repository": { "Http": { - "url": "https://static.crates.io/crates/webpki-roots/0.26.11/download", - "sha256": "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" + "url": "https://static.crates.io/crates/webpki-roots/1.0.6/download", + "sha256": "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" } }, "targets": [ @@ -9552,15 +10732,15 @@ "deps": { "common": [ { - "id": "webpki-roots 1.0.6", - "target": "webpki_roots", - "alias": "parent" + "id": "rustls-pki-types 1.14.0", + "target": "rustls_pki_types", + "alias": "pki_types" } ], "selects": {} }, "edition": "2021", - "version": "0.26.11" + "version": "1.0.6" }, "license": "CDLA-Permissive-2.0", "license_ids": [ @@ -9568,20 +10748,20 @@ ], "license_file": "LICENSE" }, - "webpki-roots 1.0.6": { - "name": "webpki-roots", - "version": "1.0.6", - "package_url": "https://github.com/rustls/webpki-roots", + "widestring 1.2.1": { + "name": "widestring", + "version": "1.2.1", + "package_url": "https://github.com/VoidStarKat/widestring-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/webpki-roots/1.0.6/download", - "sha256": "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" + "url": "https://static.crates.io/crates/widestring/1.2.1/download", + "sha256": "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" } }, "targets": [ { "Library": { - "crate_name": "webpki_roots", + "crate_name": "widestring", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -9592,44 +10772,43 @@ } } ], - "library_target_name": "webpki_roots", + "library_target_name": "widestring", "common_attrs": { "compile_data_glob": [ "**" ], - "deps": { + "crate_features": { "common": [ - { - "id": "rustls-pki-types 1.14.0", - "target": "rustls_pki_types", - "alias": "pki_types" - } + "alloc", + "default", + "std" ], "selects": {} }, "edition": "2021", - "version": "1.0.6" + "version": "1.2.1" }, - "license": "CDLA-Permissive-2.0", + "license": "MIT OR Apache-2.0", "license_ids": [ - "CDLA-Permissive-2.0" + "Apache-2.0", + "MIT" ], - "license_file": "LICENSE" + "license_file": "LICENSE-APACHE" }, - "widestring 1.2.1": { - "name": "widestring", - "version": "1.2.1", - "package_url": "https://github.com/VoidStarKat/widestring-rs", + "winapi-util 0.1.11": { + "name": "winapi-util", + "version": "0.1.11", + "package_url": "https://github.com/BurntSushi/winapi-util", "repository": { "Http": { - "url": "https://static.crates.io/crates/widestring/1.2.1/download", - "sha256": "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + "url": "https://static.crates.io/crates/winapi-util/0.1.11/download", + "sha256": "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" } }, "targets": [ { "Library": { - "crate_name": "widestring", + "crate_name": "winapi_util", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -9640,28 +10819,31 @@ } } ], - "library_target_name": "widestring", + "library_target_name": "winapi_util", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "alloc", - "default", - "std" - ], - "selects": {} + "deps": { + "common": [], + "selects": { + "cfg(windows)": [ + { + "id": "windows-sys 0.61.2", + "target": "windows_sys" + } + ] + } }, "edition": "2021", - "version": "1.2.1" + "version": "0.1.11" }, - "license": "MIT OR Apache-2.0", + "license": "Unlicense OR MIT", "license_ids": [ - "Apache-2.0", - "MIT" + "MIT", + "Unlicense" ], - "license_file": "LICENSE-APACHE" + "license_file": "LICENSE-MIT" }, "windows-link 0.2.1": { "name": "windows-link", @@ -11358,152 +12540,6 @@ ], "license_file": "LICENSE" }, - "zerocopy 0.8.47": { - "name": "zerocopy", - "version": "0.8.47", - "package_url": "https://github.com/google/zerocopy", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/zerocopy/0.8.47/download", - "sha256": "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" - } - }, - "targets": [ - { - "Library": { - "crate_name": "zerocopy", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "zerocopy", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "simd" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "zerocopy 0.8.47", - "target": "build_script_build" - } - ], - "selects": {} - }, - "edition": "2021", - "proc_macro_deps": { - "common": [], - "selects": { - "cfg(any())": [ - { - "id": "zerocopy-derive 0.8.47", - "target": "zerocopy_derive" - } - ] - } - }, - "version": "0.8.47" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ] - }, - "license": "BSD-2-Clause OR Apache-2.0 OR MIT", - "license_ids": [ - "Apache-2.0", - "BSD-2-Clause", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "zerocopy-derive 0.8.47": { - "name": "zerocopy-derive", - "version": "0.8.47", - "package_url": "https://github.com/google/zerocopy", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/zerocopy-derive/0.8.47/download", - "sha256": "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" - } - }, - "targets": [ - { - "ProcMacro": { - "crate_name": "zerocopy_derive", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "zerocopy_derive", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { - "common": [ - { - "id": "proc-macro2 1.0.106", - "target": "proc_macro2" - }, - { - "id": "quote 1.0.45", - "target": "quote" - }, - { - "id": "syn 2.0.117", - "target": "syn" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.8.47" - }, - "license": "BSD-2-Clause OR Apache-2.0 OR MIT", - "license_ids": [ - "Apache-2.0", - "BSD-2-Clause", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, "zerofrom 0.1.6": { "name": "zerofrom", "version": "0.1.6", @@ -11952,6 +12988,14 @@ "x86_64-unknown-nixos-gnu" ], "cfg(all(target_arch = \"aarch64\", target_env = \"msvc\", not(windows_raw_dylib)))": [], + "cfg(all(target_arch = \"aarch64\", target_os = \"android\"))": [], + "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))": [ + "aarch64-unknown-linux-gnu" + ], + "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))": [ + "aarch64-apple-darwin" + ], + "cfg(all(target_arch = \"loongarch64\", target_os = \"linux\"))": [], "cfg(all(target_arch = \"wasm32\", target_os = \"wasi\", target_env = \"p2\"))": [], "cfg(all(target_arch = \"wasm32\", target_os = \"wasi\", target_env = \"p3\"))": [], "cfg(all(target_arch = \"x86\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))": [], @@ -11962,6 +13006,11 @@ ], "cfg(all(target_os = \"uefi\", getrandom_backend = \"efi_rng\"))": [], "cfg(any())": [], + "cfg(any(target_arch = \"x86_64\", target_arch = \"x86\"))": [ + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-nixos-gnu" + ], "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"hurd\", target_os = \"illumos\", target_os = \"cygwin\", all(target_os = \"horizon\", target_arch = \"arm\")))": [], "cfg(any(target_os = \"haiku\", target_os = \"redox\", target_os = \"nto\", target_os = \"aix\"))": [], "cfg(any(target_os = \"ios\", target_os = \"visionos\", target_os = \"watchos\", target_os = \"tvos\"))": [], @@ -11975,6 +13024,7 @@ "x86_64-unknown-linux-gnu", "x86_64-unknown-nixos-gnu" ], + "cfg(target_os = \"android\")": [], "cfg(target_os = \"hermit\")": [], "cfg(target_os = \"netbsd\")": [], "cfg(target_os = \"redox\")": [], @@ -12013,7 +13063,7 @@ }, "direct_deps": [ "bindgen 0.70.1", - "hickory-resolver 0.25.2", + "hickory-resolver 0.26.1", "mockall 0.13.1", "serde 1.0.228", "serde_json 1.0.149", diff --git a/docs/Cargo.Bazel.lock b/docs/Cargo.Bazel.lock index 6890f21b2c208..b94a9991e883e 100644 --- a/docs/Cargo.Bazel.lock +++ b/docs/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "928a2ef1c0edb34383ab8517c21ab5ccf9e21205b5abc240a3a2309094117da0", + "checksum": "ab1bf27b6172ab6ff094cc63e1db922d26015dc64095d0fb29443ca48168cf20", "crates": { "aho-corasick 1.1.4": { "name": "aho-corasick", @@ -259,6 +259,45 @@ ], "license_file": "LICENSE-APACHE" }, + "autocfg 1.5.0": { + "name": "autocfg", + "version": "1.5.0", + "package_url": "https://github.com/cuviper/autocfg", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/autocfg/1.5.0/download", + "sha256": "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + } + }, + "targets": [ + { + "Library": { + "crate_name": "autocfg", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "autocfg", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "1.5.0" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "bindgen 0.70.1": { "name": "bindgen", "version": "0.70.1", @@ -667,6 +706,71 @@ ], "license_file": "LICENSE-APACHE" }, + "chacha20 0.10.0": { + "name": "chacha20", + "version": "0.10.0", + "package_url": "https://github.com/RustCrypto/stream-ciphers", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/chacha20/0.10.0/download", + "sha256": "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" + } + }, + "targets": [ + { + "Library": { + "crate_name": "chacha20", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "chacha20", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "rng" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + }, + { + "id": "rand_core 0.10.1", + "target": "rand_core" + } + ], + "selects": { + "cfg(any(target_arch = \"x86_64\", target_arch = \"x86\"))": [ + { + "id": "cpufeatures 0.3.0", + "target": "cpufeatures" + } + ] + } + }, + "edition": "2024", + "version": "0.10.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "clang-sys 1.8.1": { "name": "clang-sys", "version": "1.8.1", @@ -774,20 +878,20 @@ ], "license_file": "LICENSE.txt" }, - "critical-section 1.2.0": { - "name": "critical-section", - "version": "1.2.0", - "package_url": "https://github.com/rust-embedded/critical-section", + "combine 4.6.7": { + "name": "combine", + "version": "4.6.7", + "package_url": "https://github.com/Marwes/combine", "repository": { "Http": { - "url": "https://static.crates.io/crates/critical-section/1.2.0/download", - "sha256": "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + "url": "https://static.crates.io/crates/combine/4.6.7/download", + "sha256": "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" } }, "targets": [ { "Library": { - "crate_name": "critical_section", + "crate_name": "combine", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -798,35 +902,43 @@ } } ], - "library_target_name": "critical_section", + "library_target_name": "combine", "common_attrs": { "compile_data_glob": [ "**" ], + "deps": { + "common": [ + { + "id": "memchr 2.8.0", + "target": "memchr" + } + ], + "selects": {} + }, "edition": "2018", - "version": "1.2.0" + "version": "4.6.7" }, - "license": "MIT OR Apache-2.0", + "license": "MIT", "license_ids": [ - "Apache-2.0", "MIT" ], - "license_file": "LICENSE-APACHE" + "license_file": "LICENSE" }, - "crossbeam-channel 0.5.15": { - "name": "crossbeam-channel", - "version": "0.5.15", - "package_url": "https://github.com/crossbeam-rs/crossbeam", + "core-foundation 0.9.4": { + "name": "core-foundation", + "version": "0.9.4", + "package_url": "https://github.com/servo/core-foundation-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/crossbeam-channel/0.5.15/download", - "sha256": "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" + "url": "https://static.crates.io/crates/core-foundation/0.9.4/download", + "sha256": "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" } }, "targets": [ { "Library": { - "crate_name": "crossbeam_channel", + "crate_name": "core_foundation", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -837,7 +949,7 @@ } } ], - "library_target_name": "crossbeam_channel", + "library_target_name": "core_foundation", "common_attrs": { "compile_data_glob": [ "**" @@ -845,21 +957,25 @@ "crate_features": { "common": [ "default", - "std" + "link" ], "selects": {} }, "deps": { "common": [ { - "id": "crossbeam-utils 0.8.21", - "target": "crossbeam_utils" + "id": "core-foundation-sys 0.8.7", + "target": "core_foundation_sys" + }, + { + "id": "libc 0.2.183", + "target": "libc" } ], "selects": {} }, - "edition": "2021", - "version": "0.5.15" + "edition": "2018", + "version": "0.9.4" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -868,20 +984,20 @@ ], "license_file": "LICENSE-APACHE" }, - "crossbeam-epoch 0.9.18": { - "name": "crossbeam-epoch", - "version": "0.9.18", - "package_url": "https://github.com/crossbeam-rs/crossbeam", + "core-foundation-sys 0.8.7": { + "name": "core-foundation-sys", + "version": "0.8.7", + "package_url": "https://github.com/servo/core-foundation-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/crossbeam-epoch/0.9.18/download", - "sha256": "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" + "url": "https://static.crates.io/crates/core-foundation-sys/0.8.7/download", + "sha256": "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" } }, "targets": [ { "Library": { - "crate_name": "crossbeam_epoch", + "crate_name": "core_foundation_sys", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -892,30 +1008,20 @@ } } ], - "library_target_name": "crossbeam_epoch", + "library_target_name": "core_foundation_sys", "common_attrs": { "compile_data_glob": [ "**" ], "crate_features": { "common": [ - "alloc", "default", - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "crossbeam-utils 0.8.21", - "target": "crossbeam_utils" - } + "link" ], "selects": {} }, - "edition": "2021", - "version": "0.9.18" + "edition": "2018", + "version": "0.8.7" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -924,20 +1030,20 @@ ], "license_file": "LICENSE-APACHE" }, - "crossbeam-utils 0.8.21": { - "name": "crossbeam-utils", - "version": "0.8.21", - "package_url": "https://github.com/crossbeam-rs/crossbeam", + "cpufeatures 0.3.0": { + "name": "cpufeatures", + "version": "0.3.0", + "package_url": "https://github.com/RustCrypto/utils", "repository": { "Http": { - "url": "https://static.crates.io/crates/crossbeam-utils/0.8.21/download", - "sha256": "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + "url": "https://static.crates.io/crates/cpufeatures/0.3.0/download", + "sha256": "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" } }, "targets": [ { "Library": { - "crate_name": "crossbeam_utils", + "crate_name": "cpufeatures", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -946,54 +1052,44 @@ ] } } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } } ], - "library_target_name": "crossbeam_utils", + "library_target_name": "cpufeatures", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "default", - "std" - ], - "selects": {} - }, "deps": { - "common": [ - { - "id": "crossbeam-utils 0.8.21", - "target": "build_script_build" - } - ], - "selects": {} + "common": [], + "selects": { + "cfg(all(target_arch = \"aarch64\", target_os = \"android\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ], + "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ], + "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ], + "cfg(all(target_arch = \"loongarch64\", target_os = \"linux\"))": [ + { + "id": "libc 0.2.183", + "target": "libc" + } + ] + } }, - "edition": "2021", - "version": "0.8.21" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ] + "edition": "2024", + "version": "0.3.0" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -1002,20 +1098,20 @@ ], "license_file": "LICENSE-APACHE" }, - "data-encoding 2.10.0": { - "name": "data-encoding", - "version": "2.10.0", - "package_url": "https://github.com/ia0/data-encoding", + "critical-section 1.2.0": { + "name": "critical-section", + "version": "1.2.0", + "package_url": "https://github.com/rust-embedded/critical-section", "repository": { "Http": { - "url": "https://static.crates.io/crates/data-encoding/2.10.0/download", - "sha256": "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + "url": "https://static.crates.io/crates/critical-section/1.2.0/download", + "sha256": "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" } }, "targets": [ { "Library": { - "crate_name": "data_encoding", + "crate_name": "critical_section", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -1026,31 +1122,259 @@ } } ], - "library_target_name": "data_encoding", + "library_target_name": "critical_section", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "alloc", - "std" - ], - "selects": {} - }, "edition": "2018", - "version": "2.10.0" + "version": "1.2.0" }, - "license": "MIT", + "license": "MIT OR Apache-2.0", "license_ids": [ + "Apache-2.0", "MIT" ], - "license_file": "LICENSE" + "license_file": "LICENSE-APACHE" }, - "deranged 0.5.8": { - "name": "deranged", - "version": "0.5.8", - "package_url": "https://github.com/jhpratt/deranged", + "crossbeam-channel 0.5.15": { + "name": "crossbeam-channel", + "version": "0.5.15", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-channel/0.5.15/download", + "sha256": "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_channel", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_channel", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "crossbeam_utils" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.5.15" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "crossbeam-epoch 0.9.18": { + "name": "crossbeam-epoch", + "version": "0.9.18", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-epoch/0.9.18/download", + "sha256": "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_epoch", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_epoch", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "crossbeam_utils" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.9.18" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "crossbeam-utils 0.8.21": { + "name": "crossbeam-utils", + "version": "0.8.21", + "package_url": "https://github.com/crossbeam-rs/crossbeam", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/crossbeam-utils/0.8.21/download", + "sha256": "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + } + }, + "targets": [ + { + "Library": { + "crate_name": "crossbeam_utils", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "crossbeam_utils", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "crossbeam-utils 0.8.21", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.21" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "data-encoding 2.10.0": { + "name": "data-encoding", + "version": "2.10.0", + "package_url": "https://github.com/ia0/data-encoding", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/data-encoding/2.10.0/download", + "sha256": "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + } + }, + "targets": [ + { + "Library": { + "crate_name": "data_encoding", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "data_encoding", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "2.10.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "deranged 0.5.8": { + "name": "deranged", + "version": "0.5.8", + "package_url": "https://github.com/jhpratt/deranged", "repository": { "Http": { "url": "https://static.crates.io/crates/deranged/0.5.8/download", @@ -1232,70 +1556,17 @@ "compile_data_glob": [ "**" ], - "edition": "2021", - "version": "1.15.0" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "enum-as-inner 0.6.1": { - "name": "enum-as-inner", - "version": "0.6.1", - "package_url": "https://github.com/bluejekyll/enum-as-inner", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/enum-as-inner/0.6.1/download", - "sha256": "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" - } - }, - "targets": [ - { - "ProcMacro": { - "crate_name": "enum_as_inner", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "enum_as_inner", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { + "crate_features": { "common": [ - { - "id": "heck 0.5.0", - "target": "heck" - }, - { - "id": "proc-macro2 1.0.106", - "target": "proc_macro2" - }, - { - "id": "quote 1.0.45", - "target": "quote" - }, - { - "id": "syn 2.0.117", - "target": "syn" - } + "default", + "std" ], "selects": {} }, - "edition": "2018", - "version": "0.6.1" + "edition": "2021", + "version": "1.15.0" }, - "license": "MIT/Apache-2.0", + "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" @@ -1316,7 +1587,7 @@ "deps": { "common": [ { - "id": "hickory-resolver 0.25.2", + "id": "hickory-resolver 0.26.1", "target": "hickory_resolver" }, { @@ -1831,6 +2102,62 @@ ], "license_file": "LICENSE-APACHE" }, + "futures-macro 0.3.32": { + "name": "futures-macro", + "version": "0.3.32", + "package_url": "https://github.com/rust-lang/futures-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/futures-macro/0.3.32/download", + "sha256": "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "futures_macro", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "futures_macro", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.106", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.45", + "target": "quote" + }, + { + "id": "syn 2.0.117", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.3.32" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "futures-sink 0.3.32": { "name": "futures-sink", "version": "0.3.32", @@ -1956,6 +2283,9 @@ "crate_features": { "common": [ "alloc", + "async-await", + "async-await-macro", + "futures-macro", "slab", "std" ], @@ -1979,72 +2309,20 @@ "id": "slab 0.4.12", "target": "slab" } - ], - "selects": {} - }, - "edition": "2018", - "version": "0.3.32" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "getrandom 0.2.17": { - "name": "getrandom", - "version": "0.2.17", - "package_url": "https://github.com/rust-random/getrandom", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/getrandom/0.2.17/download", - "sha256": "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" - } - }, - "targets": [ - { - "Library": { - "crate_name": "getrandom", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "getrandom", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { + ], + "selects": {} + }, + "edition": "2018", + "proc_macro_deps": { "common": [ { - "id": "cfg-if 1.0.4", - "target": "cfg_if" + "id": "futures-macro 0.3.32", + "target": "futures_macro" } ], - "selects": { - "cfg(target_os = \"wasi\")": [ - { - "id": "wasi 0.11.1+wasi-snapshot-preview1", - "target": "wasi" - } - ], - "cfg(unix)": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ] - } + "selects": {} }, - "edition": "2018", - "version": "0.2.17" + "version": "0.3.32" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -2053,14 +2331,14 @@ ], "license_file": "LICENSE-APACHE" }, - "getrandom 0.3.4": { + "getrandom 0.2.17": { "name": "getrandom", - "version": "0.3.4", + "version": "0.2.17", "package_url": "https://github.com/rust-random/getrandom", "repository": { "Http": { - "url": "https://static.crates.io/crates/getrandom/0.3.4/download", - "sha256": "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" + "url": "https://static.crates.io/crates/getrandom/0.2.17/download", + "sha256": "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" } }, "targets": [ @@ -2075,18 +2353,6 @@ ] } } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } } ], "library_target_name": "getrandom", @@ -2094,79 +2360,21 @@ "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "std" - ], - "selects": {} - }, "deps": { "common": [ { "id": "cfg-if 1.0.4", "target": "cfg_if" - }, - { - "id": "getrandom 0.3.4", - "target": "build_script_build" } ], "selects": { - "cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(all(target_arch = \"wasm32\", target_os = \"wasi\", target_env = \"p2\"))": [ - { - "id": "wasip2 1.0.2+wasi-0.2.9", - "target": "wasip2" - } - ], - "cfg(all(target_os = \"uefi\", getrandom_backend = \"efi_rng\"))": [ - { - "id": "r-efi 5.3.0", - "target": "r_efi" - } - ], - "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"hurd\", target_os = \"illumos\", target_os = \"cygwin\", all(target_os = \"horizon\", target_arch = \"arm\")))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(any(target_os = \"haiku\", target_os = \"redox\", target_os = \"nto\", target_os = \"aix\"))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(any(target_os = \"ios\", target_os = \"visionos\", target_os = \"watchos\", target_os = \"tvos\"))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(target_os = \"netbsd\")": [ - { - "id": "libc 0.2.183", - "target": "libc" - } - ], - "cfg(target_os = \"solaris\")": [ + "cfg(target_os = \"wasi\")": [ { - "id": "libc 0.2.183", - "target": "libc" + "id": "wasi 0.11.1+wasi-snapshot-preview1", + "target": "wasi" } ], - "cfg(target_os = \"vxworks\")": [ + "cfg(unix)": [ { "id": "libc 0.2.183", "target": "libc" @@ -2174,19 +2382,8 @@ ] } }, - "edition": "2021", - "version": "0.3.4" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ] + "edition": "2018", + "version": "0.2.17" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -2236,6 +2433,13 @@ "compile_data_glob": [ "**" ], + "crate_features": { + "common": [ + "std", + "sys_rng" + ], + "selects": {} + }, "deps": { "common": [ { @@ -2245,6 +2449,10 @@ { "id": "getrandom 0.4.2", "target": "build_script_build" + }, + { + "id": "rand_core 0.10.1", + "target": "rand_core" } ], "selects": { @@ -2601,20 +2809,20 @@ ], "license_file": "LICENSE-APACHE" }, - "hickory-proto 0.25.2": { - "name": "hickory-proto", - "version": "0.25.2", + "hickory-net 0.26.1": { + "name": "hickory-net", + "version": "0.26.1", "package_url": "https://github.com/hickory-dns/hickory-dns", "repository": { "Http": { - "url": "https://static.crates.io/crates/hickory-proto/0.25.2/download", - "sha256": "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" + "url": "https://static.crates.io/crates/hickory-net/0.26.1/download", + "sha256": "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" } }, "targets": [ { "Library": { - "crate_name": "hickory_proto", + "crate_name": "hickory_net", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -2625,7 +2833,7 @@ } } ], - "library_target_name": "hickory_proto", + "library_target_name": "hickory_net", "common_attrs": { "compile_data_glob": [ "**" @@ -2636,9 +2844,7 @@ "__https", "__tls", "dnssec-ring", - "futures-io", "https-ring", - "std", "tls-ring", "tokio", "webpki-roots" @@ -2679,6 +2885,10 @@ "id": "h2 0.4.13", "target": "h2" }, + { + "id": "hickory-proto 0.26.1", + "target": "hickory_proto" + }, { "id": "http 1.4.0", "target": "http" @@ -2692,11 +2902,15 @@ "target": "ipnet" }, { - "id": "once_cell 1.21.4", - "target": "once_cell" + "id": "lru-cache 0.1.2", + "target": "lru_cache" + }, + { + "id": "parking_lot 0.12.5", + "target": "parking_lot" }, { - "id": "rand 0.9.4", + "id": "rand 0.10.1", "target": "rand" }, { @@ -2740,11 +2954,18 @@ "target": "url" }, { - "id": "webpki-roots 0.26.11", + "id": "webpki-roots 1.0.6", "target": "webpki_roots" } ], - "selects": {} + "selects": { + "cfg(target_os = \"android\")": [ + { + "id": "jni 0.22.4", + "target": "jni" + } + ] + } }, "edition": "2021", "proc_macro_deps": { @@ -2752,15 +2973,127 @@ { "id": "async-trait 0.1.89", "target": "async_trait" + } + ], + "selects": {} + }, + "version": "0.26.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "hickory-proto 0.26.1": { + "name": "hickory-proto", + "version": "0.26.1", + "package_url": "https://github.com/hickory-dns/hickory-dns", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/hickory-proto/0.26.1/download", + "sha256": "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" + } + }, + "targets": [ + { + "Library": { + "crate_name": "hickory_proto", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "hickory_proto", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "__dnssec", + "access-control", + "dnssec-ring", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "bitflags 2.11.0", + "target": "bitflags" + }, + { + "id": "data-encoding 2.10.0", + "target": "data_encoding" + }, + { + "id": "idna 1.1.0", + "target": "idna" + }, + { + "id": "ipnet 2.12.0", + "target": "ipnet" + }, + { + "id": "once_cell 1.21.4", + "target": "once_cell" + }, + { + "id": "prefix-trie 0.8.3", + "target": "prefix_trie" + }, + { + "id": "rand 0.10.1", + "target": "rand" + }, + { + "id": "ring 0.17.14", + "target": "ring" + }, + { + "id": "rustls-pki-types 1.14.0", + "target": "rustls_pki_types" + }, + { + "id": "thiserror 2.0.18", + "target": "thiserror" + }, + { + "id": "time 0.3.47", + "target": "time" + }, + { + "id": "tinyvec 1.11.0", + "target": "tinyvec" + }, + { + "id": "tracing 0.1.44", + "target": "tracing" }, { - "id": "enum-as-inner 0.6.1", - "target": "enum_as_inner" + "id": "url 2.5.8", + "target": "url" } ], - "selects": {} + "selects": { + "cfg(target_os = \"android\")": [ + { + "id": "jni 0.22.4", + "target": "jni" + } + ] + } }, - "version": "0.25.2" + "edition": "2021", + "version": "0.26.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -2769,14 +3102,14 @@ ], "license_file": "LICENSE-APACHE" }, - "hickory-resolver 0.25.2": { + "hickory-resolver 0.26.1": { "name": "hickory-resolver", - "version": "0.25.2", + "version": "0.26.1", "package_url": "https://github.com/hickory-dns/hickory-dns", "repository": { "Http": { - "url": "https://static.crates.io/crates/hickory-resolver/0.25.2/download", - "sha256": "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" + "url": "https://static.crates.io/crates/hickory-resolver/0.26.1/download", + "sha256": "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" } }, "targets": [ @@ -2824,9 +3157,17 @@ "target": "futures_util" }, { - "id": "hickory-proto 0.25.2", + "id": "hickory-net 0.26.1", + "target": "hickory_net" + }, + { + "id": "hickory-proto 0.26.1", "target": "hickory_proto" }, + { + "id": "ipnet 2.12.0", + "target": "ipnet" + }, { "id": "moka 0.12.15", "target": "moka" @@ -2840,7 +3181,7 @@ "target": "parking_lot" }, { - "id": "rand 0.9.4", + "id": "rand 0.10.1", "target": "rand" }, { @@ -2872,11 +3213,17 @@ "target": "tracing" }, { - "id": "webpki-roots 0.26.11", + "id": "webpki-roots 1.0.6", "target": "webpki_roots" } ], "selects": { + "aarch64-apple-darwin": [ + { + "id": "system-configuration 0.7.0", + "target": "system_configuration" + } + ], "x86_64-pc-windows-msvc": [ { "id": "ipconfig 0.3.4", @@ -2886,7 +3233,7 @@ } }, "edition": "2021", - "version": "0.25.2" + "version": "0.26.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -3859,41 +4206,355 @@ } } ], - "library_target_name": "ipnet", + "library_target_name": "ipnet", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "serde", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "serde 1.0.228", + "target": "serde" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "2.12.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "itertools 0.13.0": { + "name": "itertools", + "version": "0.13.0", + "package_url": "https://github.com/rust-itertools/itertools", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/itertools/0.13.0/download", + "sha256": "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" + } + }, + "targets": [ + { + "Library": { + "crate_name": "itertools", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "itertools", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "either 1.15.0", + "target": "either" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.13.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "itoa 1.0.18": { + "name": "itoa", + "version": "1.0.18", + "package_url": "https://github.com/dtolnay/itoa", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/itoa/1.0.18/download", + "sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + } + }, + "targets": [ + { + "Library": { + "crate_name": "itoa", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "itoa", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "1.0.18" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "jni 0.22.4": { + "name": "jni", + "version": "0.22.4", + "package_url": "https://github.com/jni-rs/jni-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/jni/0.22.4/download", + "sha256": "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" + } + }, + "targets": [ + { + "Library": { + "crate_name": "jni", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "jni", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + }, + { + "id": "combine 4.6.7", + "target": "combine" + }, + { + "id": "jni 0.22.4", + "target": "build_script_build" + }, + { + "id": "jni-sys 0.4.1", + "target": "jni_sys" + }, + { + "id": "log 0.4.29", + "target": "log" + }, + { + "id": "simd_cesu8 1.1.1", + "target": "simd_cesu8" + }, + { + "id": "thiserror 2.0.18", + "target": "thiserror" + } + ], + "selects": { + "cfg(windows)": [ + { + "id": "windows-link 0.2.1", + "target": "windows_link" + } + ] + } + }, + "edition": "2024", + "proc_macro_deps": { + "common": [ + { + "id": "jni-macros 0.22.4", + "target": "jni_macros" + } + ], + "selects": {} + }, + "version": "0.22.4" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "walkdir 2.5.0", + "target": "walkdir" + } + ], + "selects": {} + } + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": null + }, + "jni-macros 0.22.4": { + "name": "jni-macros", + "version": "0.22.4", + "package_url": "https://github.com/jni-rs/jni-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/jni-macros/0.22.4/download", + "sha256": "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "jni_macros", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "jni_macros", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { + "deps": { "common": [ - "std" + { + "id": "jni-macros 0.22.4", + "target": "build_script_build" + }, + { + "id": "proc-macro2 1.0.106", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.45", + "target": "quote" + }, + { + "id": "simd_cesu8 1.1.1", + "target": "simd_cesu8" + }, + { + "id": "syn 2.0.117", + "target": "syn" + } ], "selects": {} }, - "edition": "2018", - "version": "2.12.0" + "edition": "2024", + "version": "0.22.4" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "rustc_version 0.4.1", + "target": "rustc_version" + } + ], + "selects": {} + } }, "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" ], - "license_file": "LICENSE-APACHE" + "license_file": null }, - "itertools 0.13.0": { - "name": "itertools", - "version": "0.13.0", - "package_url": "https://github.com/rust-itertools/itertools", + "jni-sys 0.4.1": { + "name": "jni-sys", + "version": "0.4.1", + "package_url": "https://github.com/jni-rs/jni-sys", "repository": { "Http": { - "url": "https://static.crates.io/crates/itertools/0.13.0/download", - "sha256": "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" + "url": "https://static.crates.io/crates/jni-sys/0.4.1/download", + "sha256": "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" } }, "targets": [ { "Library": { - "crate_name": "itertools", + "crate_name": "jni_sys", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -3904,22 +4565,22 @@ } } ], - "library_target_name": "itertools", + "library_target_name": "jni_sys", "common_attrs": { "compile_data_glob": [ "**" ], - "deps": { + "edition": "2021", + "proc_macro_deps": { "common": [ { - "id": "either 1.15.0", - "target": "either" + "id": "jni-sys-macros 0.4.1", + "target": "jni_sys_macros" } ], "selects": {} }, - "edition": "2018", - "version": "0.13.0" + "version": "0.4.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -3928,20 +4589,20 @@ ], "license_file": "LICENSE-APACHE" }, - "itoa 1.0.18": { - "name": "itoa", - "version": "1.0.18", - "package_url": "https://github.com/dtolnay/itoa", + "jni-sys-macros 0.4.1": { + "name": "jni-sys-macros", + "version": "0.4.1", + "package_url": "https://github.com/jni-rs/jni-sys", "repository": { "Http": { - "url": "https://static.crates.io/crates/itoa/1.0.18/download", - "sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + "url": "https://static.crates.io/crates/jni-sys-macros/0.4.1/download", + "sha256": "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" } }, "targets": [ { - "Library": { - "crate_name": "itoa", + "ProcMacro": { + "crate_name": "jni_sys_macros", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -3952,20 +4613,33 @@ } } ], - "library_target_name": "itoa", + "library_target_name": "jni_sys_macros", "common_attrs": { "compile_data_glob": [ "**" ], + "deps": { + "common": [ + { + "id": "quote 1.0.45", + "target": "quote" + }, + { + "id": "syn 2.0.117", + "target": "syn" + } + ], + "selects": {} + }, "edition": "2021", - "version": "1.0.18" + "version": "0.4.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" ], - "license_file": "LICENSE-APACHE" + "license_file": null }, "js-sys 0.3.91": { "name": "js-sys", @@ -4209,6 +4883,45 @@ ], "license_file": "LICENSE" }, + "linked-hash-map 0.5.6": { + "name": "linked-hash-map", + "version": "0.5.6", + "package_url": "https://github.com/contain-rs/linked-hash-map", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/linked-hash-map/0.5.6/download", + "sha256": "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + } + }, + "targets": [ + { + "Library": { + "crate_name": "linked_hash_map", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "linked_hash_map", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "0.5.6" + }, + "license": "MIT/Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "litemap 0.8.1": { "name": "litemap", "version": "0.8.1", @@ -4341,6 +5054,54 @@ ], "license_file": "LICENSE-APACHE" }, + "lru-cache 0.1.2": { + "name": "lru-cache", + "version": "0.1.2", + "package_url": "https://github.com/contain-rs/lru-cache", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/lru-cache/0.1.2/download", + "sha256": "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" + } + }, + "targets": [ + { + "Library": { + "crate_name": "lru_cache", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "lru_cache", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "linked-hash-map 0.5.6", + "target": "linked_hash_map" + } + ], + "selects": {} + }, + "edition": "2015", + "version": "0.1.2" + }, + "license": "MIT/Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "memchr 2.8.0": { "name": "memchr", "version": "2.8.0", @@ -4758,6 +5519,45 @@ ], "license_file": "LICENSE-APACHE" }, + "ndk-context 0.1.1": { + "name": "ndk-context", + "version": "0.1.1", + "package_url": "https://github.com/rust-windowing/android-ndk-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/ndk-context/0.1.1/download", + "sha256": "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "ndk_context", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "ndk_context", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.1.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": null + }, "nom 7.1.3": { "name": "nom", "version": "7.1.3", @@ -4845,15 +5645,102 @@ "compile_data_glob": [ "**" ], - "edition": "2021", - "version": "0.2.1" + "edition": "2021", + "version": "0.2.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-Apache" + }, + "num-traits 0.2.19": { + "name": "num-traits", + "version": "0.2.19", + "package_url": "https://github.com/rust-num/num-traits", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/num-traits/0.2.19/download", + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" + } + }, + "targets": [ + { + "Library": { + "crate_name": "num_traits", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "num_traits", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "num-traits 0.2.19", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.2.19" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "autocfg 1.5.0", + "target": "autocfg" + } + ], + "selects": {} + } }, "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" ], - "license_file": "LICENSE-Apache" + "license_file": "LICENSE-APACHE" }, "once_cell 1.21.4": { "name": "once_cell", @@ -5329,61 +6216,6 @@ ], "license_file": "LICENSE-Apache" }, - "ppv-lite86 0.2.21": { - "name": "ppv-lite86", - "version": "0.2.21", - "package_url": "https://github.com/cryptocorrosion/cryptocorrosion", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/ppv-lite86/0.2.21/download", - "sha256": "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" - } - }, - "targets": [ - { - "Library": { - "crate_name": "ppv_lite86", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "ppv_lite86", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "simd", - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "zerocopy 0.8.47", - "target": "zerocopy" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.2.21" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, "predicates 3.1.4": { "name": "predicates", "version": "3.1.4", @@ -5527,6 +6359,69 @@ ], "license_file": "LICENSE-APACHE" }, + "prefix-trie 0.8.3": { + "name": "prefix-trie", + "version": "0.8.3", + "package_url": "https://github.com/tiborschneider/prefix-trie", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/prefix-trie/0.8.3/download", + "sha256": "90f561214012d3fc240a1f9c817cc4d57f5310910d066069c1b093f766bb5966" + } + }, + "targets": [ + { + "Library": { + "crate_name": "prefix_trie", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "prefix_trie", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "ipnet" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "either 1.15.0", + "target": "either" + }, + { + "id": "ipnet 2.12.0", + "target": "ipnet" + }, + { + "id": "num-traits 0.2.19", + "target": "num_traits" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.3" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "prettyplease 0.2.37": { "name": "prettyplease", "version": "0.2.37", @@ -5777,46 +6672,6 @@ ], "license_file": "LICENSE-APACHE" }, - "r-efi 5.3.0": { - "name": "r-efi", - "version": "5.3.0", - "package_url": "https://github.com/r-efi/r-efi", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/r-efi/5.3.0/download", - "sha256": "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - } - }, - "targets": [ - { - "Library": { - "crate_name": "r_efi", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "r_efi", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "edition": "2018", - "version": "5.3.0" - }, - "license": "MIT OR Apache-2.0 OR LGPL-2.1-or-later", - "license_ids": [ - "Apache-2.0", - "LGPL-2.1", - "MIT" - ], - "license_file": null - }, "r-efi 6.0.0": { "name": "r-efi", "version": "6.0.0", @@ -5857,14 +6712,14 @@ ], "license_file": null }, - "rand 0.9.4": { + "rand 0.10.1": { "name": "rand", - "version": "0.9.4", + "version": "0.10.1", "package_url": "https://github.com/rust-random/rand", "repository": { "Http": { - "url": "https://static.crates.io/crates/rand/0.9.4/download", - "sha256": "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" + "url": "https://static.crates.io/crates/rand/0.10.1/download", + "sha256": "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" } }, "targets": [ @@ -5889,9 +6744,9 @@ "crate_features": { "common": [ "alloc", - "os_rng", "std", "std_rng", + "sys_rng", "thread_rng" ], "selects": {} @@ -5899,76 +6754,22 @@ "deps": { "common": [ { - "id": "rand_chacha 0.9.0", - "target": "rand_chacha" + "id": "chacha20 0.10.0", + "target": "chacha20" }, { - "id": "rand_core 0.9.5", - "target": "rand_core" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.9.4" - }, - "license": "MIT OR Apache-2.0", - "license_ids": [ - "Apache-2.0", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "rand_chacha 0.9.0": { - "name": "rand_chacha", - "version": "0.9.0", - "package_url": "https://github.com/rust-random/rand", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/rand_chacha/0.9.0/download", - "sha256": "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" - } - }, - "targets": [ - { - "Library": { - "crate_name": "rand_chacha", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "rand_chacha", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "ppv-lite86 0.2.21", - "target": "ppv_lite86" + "id": "getrandom 0.4.2", + "target": "getrandom" }, { - "id": "rand_core 0.9.5", + "id": "rand_core 0.10.1", "target": "rand_core" } ], "selects": {} }, - "edition": "2021", - "version": "0.9.0" + "edition": "2024", + "version": "0.10.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -5977,14 +6778,14 @@ ], "license_file": "LICENSE-APACHE" }, - "rand_core 0.9.5": { + "rand_core 0.10.1": { "name": "rand_core", - "version": "0.9.5", - "package_url": "https://github.com/rust-random/rand", + "version": "0.10.1", + "package_url": "https://github.com/rust-random/rand_core", "repository": { "Http": { - "url": "https://static.crates.io/crates/rand_core/0.9.5/download", - "sha256": "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + "url": "https://static.crates.io/crates/rand_core/0.10.1/download", + "sha256": "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" } }, "targets": [ @@ -6006,24 +6807,8 @@ "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "os_rng", - "std" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "getrandom 0.3.4", - "target": "getrandom" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.9.5" + "edition": "2024", + "version": "0.10.1" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -6417,14 +7202,60 @@ "package_url": "https://github.com/rust-lang-nursery/rustc-hash", "repository": { "Http": { - "url": "https://static.crates.io/crates/rustc-hash/1.1.0/download", - "sha256": "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + "url": "https://static.crates.io/crates/rustc-hash/1.1.0/download", + "sha256": "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rustc_hash", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rustc_hash", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "edition": "2015", + "version": "1.1.0" + }, + "license": "Apache-2.0/MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rustc_version 0.4.1": { + "name": "rustc_version", + "version": "0.4.1", + "package_url": "https://github.com/djc/rustc-version-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rustc_version/0.4.1/download", + "sha256": "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" } }, "targets": [ { "Library": { - "crate_name": "rustc_hash", + "crate_name": "rustc_version", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -6435,22 +7266,24 @@ } } ], - "library_target_name": "rustc_hash", + "library_target_name": "rustc_version", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { + "deps": { "common": [ - "default", - "std" + { + "id": "semver 1.0.27", + "target": "semver" + } ], "selects": {} }, - "edition": "2015", - "version": "1.1.0" + "edition": "2018", + "version": "0.4.1" }, - "license": "Apache-2.0/MIT", + "license": "MIT OR Apache-2.0", "license_ids": [ "Apache-2.0", "MIT" @@ -6768,6 +7601,56 @@ ], "license_file": "LICENSE-APACHE" }, + "same-file 1.0.6": { + "name": "same-file", + "version": "1.0.6", + "package_url": "https://github.com/BurntSushi/same-file", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/same-file/1.0.6/download", + "sha256": "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" + } + }, + "targets": [ + { + "Library": { + "crate_name": "same_file", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "same_file", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [], + "selects": { + "cfg(windows)": [ + { + "id": "winapi-util 0.1.11", + "target": "winapi_util" + } + ] + } + }, + "edition": "2018", + "version": "1.0.6" + }, + "license": "Unlicense/MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, "scopeguard 1.2.0": { "name": "scopeguard", "version": "1.2.0", @@ -6836,6 +7719,13 @@ "compile_data_glob": [ "**" ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, "edition": "2018", "version": "1.0.27" }, @@ -7269,6 +8159,106 @@ ], "license_file": "LICENSE-APACHE" }, + "simd_cesu8 1.1.1": { + "name": "simd_cesu8", + "version": "1.1.1", + "package_url": "https://github.com/seancroach/simd_cesu8", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/simd_cesu8/1.1.1/download", + "sha256": "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" + } + }, + "targets": [ + { + "Library": { + "crate_name": "simd_cesu8", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "simd_cesu8", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "simdutf8 0.1.5", + "target": "simdutf8" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.1.1" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "simdutf8 0.1.5": { + "name": "simdutf8", + "version": "0.1.5", + "package_url": "https://github.com/rusticstuff/simdutf8", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/simdutf8/0.1.5/download", + "sha256": "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "simdutf8", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "simdutf8", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.1.5" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-Apache" + }, "slab 0.4.12": { "name": "slab", "version": "0.4.12", @@ -7626,11 +8616,146 @@ "edition": "2018", "version": "0.13.2" }, - "license": "MIT", + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "system-configuration 0.7.0": { + "name": "system-configuration", + "version": "0.7.0", + "package_url": "https://github.com/mullvad/system-configuration-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/system-configuration/0.7.0/download", + "sha256": "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "system_configuration", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "system_configuration", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "bitflags 2.11.0", + "target": "bitflags" + }, + { + "id": "core-foundation 0.9.4", + "target": "core_foundation" + }, + { + "id": "system-configuration-sys 0.6.0", + "target": "system_configuration_sys" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.7.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "system-configuration-sys 0.6.0": { + "name": "system-configuration-sys", + "version": "0.6.0", + "package_url": "https://github.com/mullvad/system-configuration-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/system-configuration-sys/0.6.0/download", + "sha256": "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" + } + }, + "targets": [ + { + "Library": { + "crate_name": "system_configuration_sys", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "system_configuration_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "core-foundation-sys 0.8.7", + "target": "core_foundation_sys" + }, + { + "id": "libc 0.2.183", + "target": "libc" + }, + { + "id": "system-configuration-sys 0.6.0", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.6.0" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", "license_ids": [ + "Apache-2.0", "MIT" ], - "license_file": "LICENSE" + "license_file": "LICENSE-APACHE" }, "tagptr 0.2.0": { "name": "tagptr", @@ -8884,6 +10009,61 @@ ], "license_file": "LICENSE-APACHE" }, + "walkdir 2.5.0": { + "name": "walkdir", + "version": "2.5.0", + "package_url": "https://github.com/BurntSushi/walkdir", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/walkdir/2.5.0/download", + "sha256": "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "walkdir", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "walkdir", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "same-file 1.0.6", + "target": "same_file" + } + ], + "selects": { + "cfg(windows)": [ + { + "id": "winapi-util 0.1.11", + "target": "winapi_util" + } + ] + } + }, + "edition": "2018", + "version": "2.5.0" + }, + "license": "Unlicense/MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, "wasi 0.11.1+wasi-snapshot-preview1": { "name": "wasi", "version": "0.11.1+wasi-snapshot-preview1", @@ -9520,14 +10700,14 @@ ], "license_file": null }, - "webpki-roots 0.26.11": { + "webpki-roots 1.0.6": { "name": "webpki-roots", - "version": "0.26.11", + "version": "1.0.6", "package_url": "https://github.com/rustls/webpki-roots", "repository": { "Http": { - "url": "https://static.crates.io/crates/webpki-roots/0.26.11/download", - "sha256": "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" + "url": "https://static.crates.io/crates/webpki-roots/1.0.6/download", + "sha256": "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" } }, "targets": [ @@ -9552,15 +10732,15 @@ "deps": { "common": [ { - "id": "webpki-roots 1.0.6", - "target": "webpki_roots", - "alias": "parent" + "id": "rustls-pki-types 1.14.0", + "target": "rustls_pki_types", + "alias": "pki_types" } ], "selects": {} }, "edition": "2021", - "version": "0.26.11" + "version": "1.0.6" }, "license": "CDLA-Permissive-2.0", "license_ids": [ @@ -9568,20 +10748,20 @@ ], "license_file": "LICENSE" }, - "webpki-roots 1.0.6": { - "name": "webpki-roots", - "version": "1.0.6", - "package_url": "https://github.com/rustls/webpki-roots", + "widestring 1.2.1": { + "name": "widestring", + "version": "1.2.1", + "package_url": "https://github.com/VoidStarKat/widestring-rs", "repository": { "Http": { - "url": "https://static.crates.io/crates/webpki-roots/1.0.6/download", - "sha256": "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" + "url": "https://static.crates.io/crates/widestring/1.2.1/download", + "sha256": "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" } }, "targets": [ { "Library": { - "crate_name": "webpki_roots", + "crate_name": "widestring", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -9592,44 +10772,43 @@ } } ], - "library_target_name": "webpki_roots", + "library_target_name": "widestring", "common_attrs": { "compile_data_glob": [ "**" ], - "deps": { + "crate_features": { "common": [ - { - "id": "rustls-pki-types 1.14.0", - "target": "rustls_pki_types", - "alias": "pki_types" - } + "alloc", + "default", + "std" ], "selects": {} }, "edition": "2021", - "version": "1.0.6" + "version": "1.2.1" }, - "license": "CDLA-Permissive-2.0", + "license": "MIT OR Apache-2.0", "license_ids": [ - "CDLA-Permissive-2.0" + "Apache-2.0", + "MIT" ], - "license_file": "LICENSE" + "license_file": "LICENSE-APACHE" }, - "widestring 1.2.1": { - "name": "widestring", - "version": "1.2.1", - "package_url": "https://github.com/VoidStarKat/widestring-rs", + "winapi-util 0.1.11": { + "name": "winapi-util", + "version": "0.1.11", + "package_url": "https://github.com/BurntSushi/winapi-util", "repository": { "Http": { - "url": "https://static.crates.io/crates/widestring/1.2.1/download", - "sha256": "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + "url": "https://static.crates.io/crates/winapi-util/0.1.11/download", + "sha256": "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" } }, "targets": [ { "Library": { - "crate_name": "widestring", + "crate_name": "winapi_util", "crate_root": "src/lib.rs", "srcs": { "allow_empty": true, @@ -9640,28 +10819,31 @@ } } ], - "library_target_name": "widestring", + "library_target_name": "winapi_util", "common_attrs": { "compile_data_glob": [ "**" ], - "crate_features": { - "common": [ - "alloc", - "default", - "std" - ], - "selects": {} + "deps": { + "common": [], + "selects": { + "cfg(windows)": [ + { + "id": "windows-sys 0.61.2", + "target": "windows_sys" + } + ] + } }, "edition": "2021", - "version": "1.2.1" + "version": "0.1.11" }, - "license": "MIT OR Apache-2.0", + "license": "Unlicense OR MIT", "license_ids": [ - "Apache-2.0", - "MIT" + "MIT", + "Unlicense" ], - "license_file": "LICENSE-APACHE" + "license_file": "LICENSE-MIT" }, "windows-link 0.2.1": { "name": "windows-link", @@ -11358,152 +12540,6 @@ ], "license_file": "LICENSE" }, - "zerocopy 0.8.47": { - "name": "zerocopy", - "version": "0.8.47", - "package_url": "https://github.com/google/zerocopy", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/zerocopy/0.8.47/download", - "sha256": "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" - } - }, - "targets": [ - { - "Library": { - "crate_name": "zerocopy", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - }, - { - "BuildScript": { - "crate_name": "build_script_build", - "crate_root": "build.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "zerocopy", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "crate_features": { - "common": [ - "simd" - ], - "selects": {} - }, - "deps": { - "common": [ - { - "id": "zerocopy 0.8.47", - "target": "build_script_build" - } - ], - "selects": {} - }, - "edition": "2021", - "proc_macro_deps": { - "common": [], - "selects": { - "cfg(any())": [ - { - "id": "zerocopy-derive 0.8.47", - "target": "zerocopy_derive" - } - ] - } - }, - "version": "0.8.47" - }, - "build_script_attrs": { - "compile_data_glob": [ - "**" - ], - "compile_data_glob_excludes": [ - "**/*.rs" - ], - "data_glob": [ - "**" - ] - }, - "license": "BSD-2-Clause OR Apache-2.0 OR MIT", - "license_ids": [ - "Apache-2.0", - "BSD-2-Clause", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, - "zerocopy-derive 0.8.47": { - "name": "zerocopy-derive", - "version": "0.8.47", - "package_url": "https://github.com/google/zerocopy", - "repository": { - "Http": { - "url": "https://static.crates.io/crates/zerocopy-derive/0.8.47/download", - "sha256": "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" - } - }, - "targets": [ - { - "ProcMacro": { - "crate_name": "zerocopy_derive", - "crate_root": "src/lib.rs", - "srcs": { - "allow_empty": true, - "include": [ - "**/*.rs" - ] - } - } - } - ], - "library_target_name": "zerocopy_derive", - "common_attrs": { - "compile_data_glob": [ - "**" - ], - "deps": { - "common": [ - { - "id": "proc-macro2 1.0.106", - "target": "proc_macro2" - }, - { - "id": "quote 1.0.45", - "target": "quote" - }, - { - "id": "syn 2.0.117", - "target": "syn" - } - ], - "selects": {} - }, - "edition": "2021", - "version": "0.8.47" - }, - "license": "BSD-2-Clause OR Apache-2.0 OR MIT", - "license_ids": [ - "Apache-2.0", - "BSD-2-Clause", - "MIT" - ], - "license_file": "LICENSE-APACHE" - }, "zerofrom 0.1.6": { "name": "zerofrom", "version": "0.1.6", @@ -11952,6 +12988,14 @@ "x86_64-unknown-nixos-gnu" ], "cfg(all(target_arch = \"aarch64\", target_env = \"msvc\", not(windows_raw_dylib)))": [], + "cfg(all(target_arch = \"aarch64\", target_os = \"android\"))": [], + "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))": [ + "aarch64-unknown-linux-gnu" + ], + "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))": [ + "aarch64-apple-darwin" + ], + "cfg(all(target_arch = \"loongarch64\", target_os = \"linux\"))": [], "cfg(all(target_arch = \"wasm32\", target_os = \"wasi\", target_env = \"p2\"))": [], "cfg(all(target_arch = \"wasm32\", target_os = \"wasi\", target_env = \"p3\"))": [], "cfg(all(target_arch = \"x86\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))": [], @@ -11962,6 +13006,11 @@ ], "cfg(all(target_os = \"uefi\", getrandom_backend = \"efi_rng\"))": [], "cfg(any())": [], + "cfg(any(target_arch = \"x86_64\", target_arch = \"x86\"))": [ + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-nixos-gnu" + ], "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"hurd\", target_os = \"illumos\", target_os = \"cygwin\", all(target_os = \"horizon\", target_arch = \"arm\")))": [], "cfg(any(target_os = \"haiku\", target_os = \"redox\", target_os = \"nto\", target_os = \"aix\"))": [], "cfg(any(target_os = \"ios\", target_os = \"visionos\", target_os = \"watchos\", target_os = \"tvos\"))": [], @@ -11975,6 +13024,7 @@ "x86_64-unknown-linux-gnu", "x86_64-unknown-nixos-gnu" ], + "cfg(target_os = \"android\")": [], "cfg(target_os = \"hermit\")": [], "cfg(target_os = \"netbsd\")": [], "cfg(target_os = \"redox\")": [], @@ -12013,7 +13063,7 @@ }, "direct_deps": [ "bindgen 0.70.1", - "hickory-resolver 0.25.2", + "hickory-resolver 0.26.1", "mockall 0.13.1", "serde 1.0.228", "serde_json 1.0.149", diff --git a/source/extensions/dynamic_modules/builtin_extensions/Cargo.toml b/source/extensions/dynamic_modules/builtin_extensions/Cargo.toml index a07e8b7a90792..8bec28b7ac5d0 100644 --- a/source/extensions/dynamic_modules/builtin_extensions/Cargo.toml +++ b/source/extensions/dynamic_modules/builtin_extensions/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/envoyproxy/envoy" [dependencies] envoy-proxy-dynamic-modules-rust-sdk = { path = "../sdk/rust" } -hickory-resolver = { version = "0.25", features = ["system-config", "tokio", "tls-ring", "https-ring", "dnssec-ring", "webpki-roots"] } +hickory-resolver = { version = "0.26", features = ["system-config", "tokio", "tls-ring", "https-ring", "dnssec-ring", "webpki-roots"] } serde = { version = "1", features = ["derive"] } serde_json = "1" time = ">=0.3.47" diff --git a/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs b/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs index 77bfd3c40d00a..ea3fe1923d1cd 100644 --- a/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs +++ b/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs @@ -7,7 +7,7 @@ use envoy_proxy_dynamic_modules_rust_sdk::*; use std::fmt::Write; -use std::net::{SocketAddr, ToSocketAddrs}; +use std::net::ToSocketAddrs; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -80,8 +80,8 @@ struct DnsOverHttpsJsonConfig { } impl HickoryConfig { - fn effective_cache_size(&self) -> usize { - self.cache_size.unwrap_or(1024) as usize + fn effective_cache_size(&self) -> u64 { + self.cache_size.unwrap_or(1024) as u64 } fn effective_num_threads(&self) -> usize { @@ -146,7 +146,7 @@ impl DnsResolverConfig for HickoryDnsResolverConfigImpl { } type TokioResolver = - hickory_resolver::Resolver; + hickory_resolver::Resolver; // Compile-time verification that TokioResolver implements Send + Sync. // This lets the compiler auto-derive Send + Sync for SharedResolverState and @@ -209,24 +209,24 @@ impl HickoryDnsResolverImpl { fn build_resolver(config: &HickoryConfig) -> TokioResolver { use hickory_resolver::config::*; - use hickory_resolver::name_server::TokioConnectionProvider; - use hickory_resolver::proto::xfer::Protocol; let mut resolver_config = if config.should_use_system_config() { let (sys_config, _) = hickory_resolver::system_conf::read_system_conf() .unwrap_or_else(|_| (ResolverConfig::default(), ResolverOpts::default())); sys_config } else { - ResolverConfig::new() + ResolverConfig::default() }; for resolver_addr in &config.resolvers { if let Some(ref sa) = resolver_addr.socket_address { let port = sa.port_value.unwrap_or(53) as u16; if let Ok(ip) = sa.address.parse::() { - let socket_addr = SocketAddr::new(ip, port); - resolver_config.add_name_server(NameServerConfig::new(socket_addr, Protocol::Udp)); - resolver_config.add_name_server(NameServerConfig::new(socket_addr, Protocol::Tcp)); + let mut udp = ConnectionConfig::udp(); + udp.port = port; + let mut tcp = ConnectionConfig::tcp(); + tcp.port = port; + resolver_config.add_name_server(NameServerConfig::new(ip, true, vec![udp, tcp])); } } } @@ -236,10 +236,10 @@ fn build_resolver(config: &HickoryConfig) -> TokioResolver { if let Some(ref sa) = server.socket_address { let port = sa.port_value.unwrap_or(853) as u16; if let Ok(ip) = sa.address.parse::() { - let socket_addr = SocketAddr::new(ip, port); - let mut ns = NameServerConfig::new(socket_addr, Protocol::Tls); - ns.tls_dns_name = Some(dot_config.tls_server_name.clone()); - resolver_config.add_name_server(ns); + let mut tls_conn = + ConnectionConfig::tls(std::sync::Arc::from(dot_config.tls_server_name.as_str())); + tls_conn.port = port; + resolver_config.add_name_server(NameServerConfig::new(ip, true, vec![tls_conn])); } } } @@ -267,13 +267,15 @@ fn build_resolver(config: &HickoryConfig) -> TokioResolver { Err(_) => continue, } }; - let socket_addr = SocketAddr::new(ip, port); - let mut ns = NameServerConfig::new(socket_addr, Protocol::Https); - ns.tls_dns_name = Some(host.to_string()); - if path != "/" && !path.is_empty() { - ns.http_endpoint = Some(path.to_string()); - } - resolver_config.add_name_server(ns); + let path_opt = if path != "/" && !path.is_empty() { + Some(std::sync::Arc::from(path)) + } else { + None + }; + let mut https_conn = + ConnectionConfig::https(std::sync::Arc::from(host), path_opt); + https_conn.port = port; + resolver_config.add_name_server(NameServerConfig::new(ip, true, vec![https_conn])); } } } @@ -284,10 +286,10 @@ fn build_resolver(config: &HickoryConfig) -> TokioResolver { opts.cache_size = config.effective_cache_size(); opts.validate = config.enable_dnssec; - let provider = TokioConnectionProvider::default(); + let provider = hickory_resolver::net::runtime::TokioRuntimeProvider::default(); let mut builder = hickory_resolver::Resolver::builder_with_config(resolver_config, provider); *builder.options_mut() = opts; - builder.build() + builder.build().unwrap_or_else(|e| panic!("failed to build DNS resolver: {e}")) } impl DnsResolverInstance for HickoryDnsResolverImpl { @@ -411,17 +413,18 @@ async fn perform_lookup( /// Extract A records from a lookup result into the addresses vector. fn collect_a_records( - result: Result, + result: Result, addresses: &mut Vec, error_msg: &mut Option, ) { + use hickory_resolver::proto::rr::RData; match result { Ok(response) => { - for record in response.records() { - if let Some(a) = record.data().as_a() { + for record in response.answers() { + if let RData::A(a) = &record.data { addresses.push(DnsAddress { address: format_ipv4_address(a.0), - ttl_seconds: record.ttl(), + ttl_seconds: record.ttl, }); } } @@ -432,17 +435,18 @@ fn collect_a_records( /// Extract AAAA records from a lookup result into the addresses vector. fn collect_aaaa_records( - result: Result, + result: Result, addresses: &mut Vec, error_msg: &mut Option, ) { + use hickory_resolver::proto::rr::RData; match result { Ok(response) => { - for record in response.records() { - if let Some(aaaa) = record.data().as_aaaa() { + for record in response.answers() { + if let RData::AAAA(aaaa) = &record.data { addresses.push(DnsAddress { address: format_ipv6_address(aaaa.0), - ttl_seconds: record.ttl(), + ttl_seconds: record.ttl, }); } } @@ -454,7 +458,7 @@ fn collect_aaaa_records( fn append_lookup_error( error_msg: &mut Option, record_type: &str, - error: &hickory_resolver::ResolveError, + error: &hickory_resolver::net::NetError, ) { let msg = format!("{record_type} lookup failed: {error}"); match error_msg { From 021f437055f8ed91f03fe8d20eb762c39826ec8b Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 10:19:58 +0000 Subject: [PATCH 223/750] deps: Bump `envoy_toolshed` -> 0.3.34 (#44871) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- api/bazel/deps.yaml | 2 +- api/bazel/repository_locations.bzl | 4 ++-- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/bazel/deps.yaml b/api/bazel/deps.yaml index 4ba113c620d46..a9616c67f3130 100644 --- a/api/bazel/deps.yaml +++ b/api/bazel/deps.yaml @@ -73,7 +73,7 @@ envoy_toolshed: project_name: "envoy_toolshed" project_desc: "Tooling, libraries, runners and checkers for Envoy proxy's CI" project_url: "https://github.com/envoyproxy/toolshed" - release_date: "2026-04-20" + release_date: "2026-05-06" use_category: - build - controlplane diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 29910ab6953fd..4f3fd56efcecf 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -74,8 +74,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/google/cel-spec/archive/v{version}.tar.gz"], ), envoy_toolshed = dict( - version = "0.3.33", - sha256 = "0d35c07b06033cebf50c767b0acdd31f215035785da4ecb9fbc02c23f2d53805", + version = "0.3.34", + sha256 = "49bb8b3cff6b28d74f9cd18ca9b3607cf4a5b2e81ecb57a04a9bc3fe94ac7ac8", strip_prefix = "toolshed-bazel-v{version}", urls = ["https://github.com/envoyproxy/toolshed/releases/download/bazel-v{version}/toolshed-bazel-v{version}.tar.gz"], ), diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 6799e18a42e2e..9b3d9e8130c18 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -799,7 +799,7 @@ envoy_toolshed: project_name: "envoy_toolshed" project_desc: "Tooling, libraries, runners and checkers for Envoy proxy's CI" project_url: "https://github.com/envoyproxy/toolshed" - release_date: "2026-04-20" + release_date: "2026-05-06" use_category: - build - controlplane diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 0e6150efa0a77..2006b00826034 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -46,8 +46,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/bazelbuild/buildtools/archive/v{version}.tar.gz"], ), envoy_toolshed = dict( - version = "0.3.33", - sha256 = "0d35c07b06033cebf50c767b0acdd31f215035785da4ecb9fbc02c23f2d53805", + version = "0.3.34", + sha256 = "49bb8b3cff6b28d74f9cd18ca9b3607cf4a5b2e81ecb57a04a9bc3fe94ac7ac8", strip_prefix = "toolshed-bazel-v{version}", urls = ["https://github.com/envoyproxy/toolshed/releases/download/bazel-v{version}/toolshed-bazel-v{version}.tar.gz"], ), From 64660d96b8f61c268e7175efe8265431d826a448 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 May 2026 11:26:43 +0100 Subject: [PATCH 224/750] rust: Fix format in hickory file (#44876) Signed-off-by: Ryan Northey --- .../dynamic_modules/builtin_extensions/hickory_dns.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs b/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs index ea3fe1923d1cd..6ac4068a428c0 100644 --- a/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs +++ b/source/extensions/dynamic_modules/builtin_extensions/hickory_dns.rs @@ -272,8 +272,7 @@ fn build_resolver(config: &HickoryConfig) -> TokioResolver { } else { None }; - let mut https_conn = - ConnectionConfig::https(std::sync::Arc::from(host), path_opt); + let mut https_conn = ConnectionConfig::https(std::sync::Arc::from(host), path_opt); https_conn.port = port; resolver_config.add_name_server(NameServerConfig::new(ip, true, vec![https_conn])); } @@ -289,7 +288,9 @@ fn build_resolver(config: &HickoryConfig) -> TokioResolver { let provider = hickory_resolver::net::runtime::TokioRuntimeProvider::default(); let mut builder = hickory_resolver::Resolver::builder_with_config(resolver_config, provider); *builder.options_mut() = opts; - builder.build().unwrap_or_else(|e| panic!("failed to build DNS resolver: {e}")) + builder + .build() + .unwrap_or_else(|e| panic!("failed to build DNS resolver: {e}")) } impl DnsResolverInstance for HickoryDnsResolverImpl { From b9e9219941bac9dc419991ad20c1c2f0116566fc Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 11:16:10 +0000 Subject: [PATCH 225/750] deps: Bump `kafka_source` -> 3.9.2 (#44881) resolve: CVE-2026-33558 Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 9b3d9e8130c18..5e88dbe3fd097 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -1080,7 +1080,7 @@ kafka_source: project_name: "Kafka (source)" project_desc: "Open-source distributed event streaming platform" project_url: "https://kafka.apache.org" - release_date: "2025-05-12" + release_date: "2026-02-07" use_category: - dataplane_ext extensions: diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 2006b00826034..6424bfbe60f5d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -589,8 +589,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/google/re2/releases/download/{version}/re2-{version}.zip"], ), kafka_source = dict( - version = "3.9.1", - sha256 = "c15b82940cfb9f67fce909d8600dc8bcfc42d2795da2c26c149d03a627f85234", + version = "3.9.2", + sha256 = "085bbee8208fea6d1cb22498afa6ffc3835037516643208f705420564b49b80e", strip_prefix = "kafka-{version}/clients/src/main/resources/common/message", urls = ["https://github.com/apache/kafka/archive/{version}.zip"], ), From 627329f473473f5e3e3488228113841e5ac0b60b Mon Sep 17 00:00:00 2001 From: Rudrakh Panigrahi Date: Wed, 6 May 2026 17:05:02 +0530 Subject: [PATCH 226/750] Add resource monitor max_heap_size_bytes_runtime (#43624) Commit Message: add resource monitor max_heap_size_bytes_runtime Additional Description: This can be helpful to update max_heap_size_bytes at startup or runtime instead of relying on a static bootstrap value. Risk Level: Low Testing: Unit testing Docs Changes: N/A Release Notes: Yes Platform Specific Features: N/A Fixes #33950 --------- Signed-off-by: Rudrakh Panigrahi Signed-off-by: Kateryna Nezdolii Co-authored-by: Kateryna Nezdolii --- api/envoy/config/core/v3/base.proto | 9 ++ .../resource_monitors/fixed_heap/v3/BUILD | 5 +- .../fixed_heap/v3/fixed_heap.proto | 14 ++- changelogs/current.yaml | 5 ++ .../kv_store_xds_delegate_integration_test.cc | 16 ++-- envoy/runtime/runtime.h | 8 ++ envoy/server/resource_monitor_config.h | 6 ++ .../integration/client_integration_test.cc | 2 +- mobile/test/common/internal_engine_test.cc | 2 +- .../chromium/net/impl/CronvoyEngineTest.java | 2 +- source/common/runtime/runtime_impl.cc | 2 + source/common/runtime/runtime_impl.h | 1 + source/common/runtime/runtime_protos.h | 17 ++++ .../resource_monitors/fixed_heap/BUILD | 5 ++ .../resource_monitors/fixed_heap/config.cc | 26 +++++- .../fixed_heap/fixed_heap_monitor.cc | 27 ++++-- .../fixed_heap/fixed_heap_monitor.h | 10 ++- source/server/config_validation/server.cc | 30 ++++--- source/server/instance_impl.cc | 2 +- source/server/overload_manager_impl.cc | 8 +- source/server/overload_manager_impl.h | 5 +- source/server/resource_monitor_config_impl.h | 8 +- source/server/server.cc | 17 ++-- .../resource_monitors/cgroup_memory/BUILD | 1 + .../cgroup_memory/config_test.cc | 16 ++-- .../resource_monitors/cpu_utilization/BUILD | 2 + .../cpu_utilization/config_test.cc | 13 ++- .../linux_cpu_stats_reader_test.cc | 18 ++-- .../downstream_connections/BUILD | 1 + .../downstream_connections/config_test.cc | 10 ++- .../resource_monitors/fixed_heap/BUILD | 3 +- .../fixed_heap/config_test.cc | 89 ++++++++++++++++++- .../fixed_heap/fixed_heap_monitor_test.cc | 79 ++++++++++++---- .../resource_monitors/injected_resource/BUILD | 2 + .../injected_resource/config_test.cc | 4 +- .../injected_resource_monitor_test.cc | 4 +- test/mocks/runtime/mocks.cc | 1 + test/mocks/runtime/mocks.h | 1 + test/server/BUILD | 1 + test/server/overload_manager_impl_test.cc | 9 +- 40 files changed, 391 insertions(+), 90 deletions(-) diff --git a/api/envoy/config/core/v3/base.proto b/api/envoy/config/core/v3/base.proto index 978f365d5f9e6..78a74c3f433b8 100644 --- a/api/envoy/config/core/v3/base.proto +++ b/api/envoy/config/core/v3/base.proto @@ -269,6 +269,15 @@ message RuntimeUInt32 { string runtime_key = 3; } +// Runtime derived uint64 with a default when not specified. +message RuntimeUInt64 { + // Default value if runtime value is not available. + uint64 default_value = 2; + + // Runtime key to get value for comparison. This value is used if defined. + string runtime_key = 3; +} + // Runtime derived percentage with a default when not specified. message RuntimePercent { // Default value if runtime value is not available. diff --git a/api/envoy/extensions/resource_monitors/fixed_heap/v3/BUILD b/api/envoy/extensions/resource_monitors/fixed_heap/v3/BUILD index 5f552f08145ca..504c6c70514ac 100644 --- a/api/envoy/extensions/resource_monitors/fixed_heap/v3/BUILD +++ b/api/envoy/extensions/resource_monitors/fixed_heap/v3/BUILD @@ -5,5 +5,8 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") licenses(["notice"]) # Apache 2 api_proto_package( - deps = ["@xds//udpa/annotations:pkg"], + deps = [ + "//envoy/config/core/v3:pkg", + "@xds//udpa/annotations:pkg", + ], ) diff --git a/api/envoy/extensions/resource_monitors/fixed_heap/v3/fixed_heap.proto b/api/envoy/extensions/resource_monitors/fixed_heap/v3/fixed_heap.proto index 4a9f07d1001cf..1728ec57a9b53 100644 --- a/api/envoy/extensions/resource_monitors/fixed_heap/v3/fixed_heap.proto +++ b/api/envoy/extensions/resource_monitors/fixed_heap/v3/fixed_heap.proto @@ -2,9 +2,10 @@ syntax = "proto3"; package envoy.extensions.resource_monitors.fixed_heap.v3; +import "envoy/config/core/v3/base.proto"; + import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; -import "validate/validate.proto"; option java_package = "io.envoyproxy.envoy.extensions.resource_monitors.fixed_heap.v3"; option java_outer_classname = "FixedHeapProto"; @@ -22,5 +23,14 @@ message FixedHeapConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.config.resource_monitor.fixed_heap.v2alpha.FixedHeapConfig"; - uint64 max_heap_size_bytes = 1 [(validate.rules).uint64 = {gt: 0}]; + // Static value for max heap size in bytes set at startup. + // Exactly one of max_heap_size_bytes or max_heap_size_bytes_runtime must be set. + // If set, the expected value must be greater than ``0``, otherwise validation will fail. + uint64 max_heap_size_bytes = 1; + + // Runtime overlay for max heap size in bytes. When set, the value can be overridden + // at runtime during startup or later without restart. + // Exactly one of max_heap_size_bytes or max_heap_size_bytes_runtime must be set. + // If set, the expected value must be greater than ``0``, otherwise validation will fail. + config.core.v3.RuntimeUInt64 max_heap_size_bytes_runtime = 2; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 025342d8f06b5..caff0a0e7376a 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -128,6 +128,11 @@ new_features: in the composite HTTP filter. Now users could specify the matcher inline in the filter configuration instead of using the :ref:`ExtensionWithMatcher ` filter. +- area: resource_monitors + change: | + Overload manager fixed heap resource monitor now supports :ref:`max_heap_size_bytes_runtime + ` + for runtime-overridable max heap size (e.g. RTDS or ``/runtime_modify``). - area: jwt_authn change: | Added :ref:`verification_status_header diff --git a/contrib/config/test/kv_store_xds_delegate_integration_test.cc b/contrib/config/test/kv_store_xds_delegate_integration_test.cc index a383cf93a30d7..924e148225e88 100644 --- a/contrib/config/test/kv_store_xds_delegate_integration_test.cc +++ b/contrib/config/test/kv_store_xds_delegate_integration_test.cc @@ -341,7 +341,9 @@ TEST_P(KeyValueStoreXdsDelegateIntegrationTest, BasicSuccess) { // Wait until the discovery responses have been processed. test_server_->waitForCounterGe( "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); - test_server_->waitForCounterGe("runtime.load_success", 2); + // Initial snapshot load, post-listener-manager TLS refresh (onWorkerThreadsRegistered), and + // first RTDS response each increment runtime.load_success; wait until all have completed. + test_server_->waitForCounterGe("runtime.load_success", 3); // Verify that the xDS resources are used by Envoy. checkSecretExists(std::string(CLIENT_CERT_NAME), /*version_info=*/"1"); @@ -360,9 +362,11 @@ TEST_P(KeyValueStoreXdsDelegateIntegrationTest, BasicSuccess) { layer: baz: saz )EOF"); + const uint32_t runtime_loads_before_rtds_v2 = + test_server_->counter("runtime.load_success")->value(); sendSotwDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {rtds_resource}, "2", rtds_stream_.get()); - test_server_->waitForCounterGe("runtime.load_success", 3); + test_server_->waitForCounterGe("runtime.load_success", runtime_loads_before_rtds_v2 + 1); EXPECT_EQ("whatevs", getRuntimeKey("foo")); EXPECT_EQ("yar", getRuntimeKey("bar")); @@ -374,8 +378,8 @@ TEST_P(KeyValueStoreXdsDelegateIntegrationTest, BasicSuccess) { // Wait until SDS and RTDS have been loaded from the KV store and updated the Envoy instance. test_server_->waitForCounterGe( "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); - // Two runtime loads are expected, one for the admin layer and one for the RTDS layer. - test_server_->waitForCounterGe("runtime.load_success", 2); + // Expect initial snapshot load, post-listener-manager TLS refresh, and RTDS restored from KV. + test_server_->waitForCounterGe("runtime.load_success", 3); // Verify that the latest resource values in the KV store are used by Envoy. EXPECT_EQ(2, test_server_->counter("xds.kv_store.load_success")->value()); @@ -398,10 +402,12 @@ TEST_P(KeyValueStoreXdsDelegateIntegrationTest, BasicSuccess) { foo: zoo baz: jazz )EOF"); + const uint32_t runtime_loads_before_rtds_v3 = + test_server_->counter("runtime.load_success")->value(); sendSotwDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {rtds_resource_v2}, /*version=*/"3", rtds_stream_.get()); - test_server_->waitForCounterGe("runtime.load_success", 3); + test_server_->waitForCounterGe("runtime.load_success", runtime_loads_before_rtds_v3 + 1); // Verify that the values from the xDS response are used instead of from the persisted xDS once // connectivity is re-established. diff --git a/envoy/runtime/runtime.h b/envoy/runtime/runtime.h index c7ae8fcfd3a8c..689feab2466e3 100644 --- a/envoy/runtime/runtime.h +++ b/envoy/runtime/runtime.h @@ -231,6 +231,14 @@ class Loader { */ virtual absl::Status initialize(Upstream::ClusterManager& cm) PURE; + /** + * Called from server initialization once worker dispatchers are registered with ThreadLocal + * (ListenerManager construction), before those worker threads start running their event loops. + * Implementations may use this for any setup that depends on worker threads being registered + * (e.g. publishing thread-local state to all registered dispatchers). + */ + virtual absl::Status onWorkerThreadsRegistered() PURE; + /** * @return const Snapshot& the current snapshot. This reference is safe to use for the duration of * the calling routine, but may be overwritten on a future event loop cycle so should be diff --git a/envoy/server/resource_monitor_config.h b/envoy/server/resource_monitor_config.h index a7d9086c28f0d..7fa1ee705d433 100644 --- a/envoy/server/resource_monitor_config.h +++ b/envoy/server/resource_monitor_config.h @@ -5,6 +5,7 @@ #include "envoy/config/typed_config.h" #include "envoy/event/dispatcher.h" #include "envoy/protobuf/message_validator.h" +#include "envoy/runtime/runtime.h" #include "envoy/server/options.h" #include "envoy/server/proactive_resource_monitor.h" #include "envoy/server/resource_monitor.h" @@ -40,6 +41,11 @@ class ResourceMonitorFactoryContext { * messages. */ virtual ProtobufMessage::ValidationVisitor& messageValidationVisitor() PURE; + + /** + * @return Runtime::Loader& the runtime loader for runtime key overrides. + */ + virtual Runtime::Loader& runtime() PURE; }; /** diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index d50db5e5bbceb..03c2b51a118d2 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -1744,7 +1744,7 @@ TEST_P(ClientIntegrationTest, TestStats) { { absl::MutexLock l(engine_lock_); std::string stats = engine_->dumpStats(); - EXPECT_TRUE((absl::StrContains(stats, "runtime.load_success: 1"))) << stats; + EXPECT_TRUE((absl::StrContains(stats, "runtime.load_success: 2"))) << stats; } } diff --git a/mobile/test/common/internal_engine_test.cc b/mobile/test/common/internal_engine_test.cc index c796bdbca86b6..ac65ddb30c085 100644 --- a/mobile/test/common/internal_engine_test.cc +++ b/mobile/test/common/internal_engine_test.cc @@ -155,7 +155,7 @@ TEST_F(InternalEngineTest, AccessEngineAfterInitialization) { absl::Notification getClusterManagerInvoked; // Running engine functions should work because the engine is running - EXPECT_THAT(engine_->engine_->dumpStats(), testing::HasSubstr("runtime.load_success: 1\n")); + EXPECT_THAT(engine_->engine_->dumpStats(), testing::HasSubstr("runtime.load_success: 2\n")); engine_->terminate(); ASSERT_TRUE(engine_->isTerminated()); diff --git a/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java b/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java index 2877c10593686..132b0312fcaed 100644 --- a/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java +++ b/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java @@ -107,7 +107,7 @@ public void get_simple() throws Exception { assertThat(statsMap.containsKey("http.hcm.downstream_rq_2xx")); assertThat(statsMap.containsKey("http.hcm.downstream_total")); assertThat(statsMap.containsKey("runtime.load_success")); - assertThat(statsMap.get("runtime.load_success")).isEqualTo("1"); + assertThat(statsMap.get("runtime.load_success")).isEqualTo("2"); } @Test diff --git a/source/common/runtime/runtime_impl.cc b/source/common/runtime/runtime_impl.cc index a3c1ce1927713..312fe4ac76fad 100644 --- a/source/common/runtime/runtime_impl.cc +++ b/source/common/runtime/runtime_impl.cc @@ -542,6 +542,8 @@ absl::Status LoaderImpl::initialize(Upstream::ClusterManager& cm) { return absl::OkStatus(); } +absl::Status LoaderImpl::onWorkerThreadsRegistered() { return loadNewSnapshot(); } + void LoaderImpl::startRtdsSubscriptions(ReadyCallback on_done) { on_rtds_initialized_ = on_done; init_manager_.initialize(init_watcher_); diff --git a/source/common/runtime/runtime_impl.h b/source/common/runtime/runtime_impl.h index 0e66169ff409a..96fc2937e55ae 100644 --- a/source/common/runtime/runtime_impl.h +++ b/source/common/runtime/runtime_impl.h @@ -229,6 +229,7 @@ class LoaderImpl : public Loader, Logger::Loggable { void startRtdsSubscriptions(ReadyCallback on_done) override; Stats::Scope& getRootScope() override; void countDeprecatedFeatureUse() const override; + absl::Status onWorkerThreadsRegistered() override; private: friend RtdsSubscription; diff --git a/source/common/runtime/runtime_protos.h b/source/common/runtime/runtime_protos.h index a17f61519d67c..06f2c411f1a62 100644 --- a/source/common/runtime/runtime_protos.h +++ b/source/common/runtime/runtime_protos.h @@ -38,6 +38,23 @@ class UInt32 : Logger::Loggable { Runtime::Loader& runtime_; }; +// Helper class for runtime-derived uint64. +class UInt64 : Logger::Loggable { +public: + UInt64(const envoy::config::core::v3::RuntimeUInt64& uint64_proto, Runtime::Loader& runtime) + : runtime_key_(uint64_proto.runtime_key()), default_value_(uint64_proto.default_value()), + runtime_(runtime) {} + + const std::string& runtimeKey() const { return runtime_key_; } + + uint64_t value() const { return runtime_.snapshot().getInteger(runtime_key_, default_value_); } + +private: + const std::string runtime_key_; + const uint64_t default_value_; + Runtime::Loader& runtime_; +}; + // Helper class for runtime-derived boolean feature flags. class FeatureFlag { public: diff --git a/source/extensions/resource_monitors/fixed_heap/BUILD b/source/extensions/resource_monitors/fixed_heap/BUILD index 108be4816bbe7..81e200e01e36c 100644 --- a/source/extensions/resource_monitors/fixed_heap/BUILD +++ b/source/extensions/resource_monitors/fixed_heap/BUILD @@ -17,6 +17,8 @@ envoy_cc_library( "//envoy/server:resource_monitor_config_interface", "//source/common/common:assert_lib", "//source/common/memory:stats_lib", + "//source/common/runtime:runtime_protos_lib", + "@abseil-cpp//absl/types:variant", "@envoy_api//envoy/extensions/resource_monitors/fixed_heap/v3:pkg_cc_proto", ], ) @@ -27,8 +29,11 @@ envoy_cc_extension( hdrs = ["config.h"], deps = [ ":fixed_heap_monitor", + "//envoy/common:exception_lib", "//envoy/registry", "//source/common/common:assert_lib", + "//source/common/common:utility_lib", + "//source/common/runtime:runtime_protos_lib", "//source/extensions/resource_monitors/common:factory_base_lib", "@envoy_api//envoy/extensions/resource_monitors/fixed_heap/v3:pkg_cc_proto", ], diff --git a/source/extensions/resource_monitors/fixed_heap/config.cc b/source/extensions/resource_monitors/fixed_heap/config.cc index c622892e5d939..f13dd28441848 100644 --- a/source/extensions/resource_monitors/fixed_heap/config.cc +++ b/source/extensions/resource_monitors/fixed_heap/config.cc @@ -1,10 +1,12 @@ #include "source/extensions/resource_monitors/fixed_heap/config.h" +#include "envoy/common/exception.h" #include "envoy/extensions/resource_monitors/fixed_heap/v3/fixed_heap.pb.h" #include "envoy/extensions/resource_monitors/fixed_heap/v3/fixed_heap.pb.validate.h" #include "envoy/registry/registry.h" -#include "source/common/protobuf/utility.h" +#include "source/common/common/utility.h" +#include "source/common/runtime/runtime_protos.h" #include "source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h" namespace Envoy { @@ -14,8 +16,26 @@ namespace FixedHeapMonitor { Server::ResourceMonitorPtr FixedHeapMonitorFactory::createResourceMonitorFromProtoTyped( const envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig& config, - Server::Configuration::ResourceMonitorFactoryContext& /*unused_context*/) { - return std::make_unique(config); + Server::Configuration::ResourceMonitorFactoryContext& context) { + if ((config.max_heap_size_bytes() > 0) && config.has_max_heap_size_bytes_runtime()) { + throw EnvoyException("fixed_heap: exactly one of max_heap_size_bytes or " + "max_heap_size_bytes_runtime must be set"); + } + + if (config.has_max_heap_size_bytes_runtime()) { + Runtime::UInt64 runtime_uint64(config.max_heap_size_bytes_runtime(), context.runtime()); + if (runtime_uint64.value() == 0) { + throw EnvoyException("fixed_heap: max heap size must be greater than 0"); + } + return std::make_unique( + absl::variant(std::move(runtime_uint64))); + } else { + if (config.max_heap_size_bytes() == 0) { + throw EnvoyException("fixed_heap: max heap size must be greater than 0"); + } + return std::make_unique( + absl::variant(config.max_heap_size_bytes())); + } } /** diff --git a/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.cc b/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.cc index 7702cbcf6a1ad..26cc081a6f10c 100644 --- a/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.cc +++ b/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.cc @@ -21,14 +21,25 @@ uint64_t MemoryStatsReader::allocatedHeapBytes() { return Memory::Stats::totalCurrentlyAllocated(); } -FixedHeapMonitor::FixedHeapMonitor( - const envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig& config, - std::unique_ptr stats) - : max_heap_(config.max_heap_size_bytes()), stats_(std::move(stats)) { - ASSERT(max_heap_ > 0); -} +FixedHeapMonitor::FixedHeapMonitor(absl::variant max_heap_source, + std::unique_ptr stats) + : max_heap_source_(std::move(max_heap_source)), stats_(std::move(stats)) {} void FixedHeapMonitor::updateResourceUsage(Server::ResourceUpdateCallbacks& callbacks) { + const uint64_t max_heap = absl::visit( + [](const auto& source) -> uint64_t { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return source; + } else { + return source.value(); + } + }, + max_heap_source_); + if (max_heap == 0) { + callbacks.onFailure(EnvoyException("fixed_heap: max heap size must be greater than 0")); + return; + } size_t used = 0; if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.fixed_heap_use_allocated")) { @@ -42,9 +53,9 @@ void FixedHeapMonitor::updateResourceUsage(Server::ResourceUpdateCallbacks& call }; Server::ResourceUsage usage; - usage.resource_pressure_ = used / static_cast(max_heap_); + usage.resource_pressure_ = used / static_cast(max_heap); - ENVOY_LOG_MISC(trace, "FixedHeapMonitor: used={}, max_heap={}, pressure={}", used, max_heap_, + ENVOY_LOG_MISC(trace, "FixedHeapMonitor: used={}, max_heap={}, pressure={}", used, max_heap, usage.resource_pressure_); callbacks.onSuccess(usage); diff --git a/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h b/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h index 27454ff4590d7..7441522d0d79b 100644 --- a/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h +++ b/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h @@ -3,6 +3,10 @@ #include "envoy/extensions/resource_monitors/fixed_heap/v3/fixed_heap.pb.h" #include "envoy/server/resource_monitor.h" +#include "source/common/runtime/runtime_protos.h" + +#include "absl/types/variant.h" + namespace Envoy { namespace Extensions { namespace ResourceMonitors { @@ -27,18 +31,18 @@ class MemoryStatsReader { }; /** - * Heap memory monitor with a statically configured maximum. + * Heap memory monitor with a statically configured or runtime-derived maximum. */ class FixedHeapMonitor : public Server::ResourceMonitor { public: FixedHeapMonitor( - const envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig& config, + absl::variant max_heap_source, std::unique_ptr stats = std::make_unique()); void updateResourceUsage(Server::ResourceUpdateCallbacks& callbacks) override; private: - const uint64_t max_heap_; + const absl::variant max_heap_source_; std::unique_ptr stats_; }; diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc index babfd9b6e1d45..3f61ba31b897b 100644 --- a/source/server/config_validation/server.cc +++ b/source/server/config_validation/server.cc @@ -124,23 +124,12 @@ void ValidationInstance::initialize(const Options& options, stats().symbolTable(), bootstrap_.node(), bootstrap_.node_context_params(), local_address, options.serviceZone(), options.serviceClusterName(), options.serviceNodeName()); - overload_manager_ = THROW_OR_RETURN_VALUE( - OverloadManagerImpl::create( - dispatcher(), *stats().rootScope(), threadLocal(), bootstrap_.overload_manager(), - messageValidationContext().staticValidationVisitor(), *api_, options_), - std::unique_ptr); - null_overload_manager_ = std::make_unique(threadLocal(), false); absl::Status creation_status = absl::OkStatus(); Configuration::InitialImpl initial_config(bootstrap_, creation_status); THROW_IF_NOT_OK_REF(creation_status); AdminFactoryContext factory_context(*this, std::make_shared()); initial_config.initAdminAccessLog(bootstrap_, factory_context); admin_ = std::make_unique(initial_config.admin().address()); - auto& validation_factory = Config::Utility::getAndCheckFactoryByName( - Config::ServerExtensionValues::get().VALIDATION_LISTENER); - auto empty_config = validation_factory.createEmptyConfigProto(); - listener_manager_ = validation_factory.createListenerManager(*empty_config, *this, nullptr, *this, - false, quic_stat_names_); thread_local_.registerThread(*dispatcher_, true); // Create bootstrap extensions to validate their configs and register any providers @@ -160,6 +149,24 @@ void ValidationInstance::initialize(const Options& options, runtime_ = component_factory.createRuntime(*this, initial_config); ENVOY_BUG(runtime_ != nullptr, "Component factory should not return nullptr from createRuntime()"); + validation_context_.setRuntime(runtime()); + + overload_manager_ = THROW_OR_RETURN_VALUE( + OverloadManagerImpl::create( + dispatcher(), *stats().rootScope(), threadLocal(), bootstrap_.overload_manager(), + messageValidationContext().staticValidationVisitor(), *api_, options_, *runtime_), + std::unique_ptr); + null_overload_manager_ = std::make_unique(threadLocal(), false); + + auto& listener_manager_factory = + Config::Utility::getAndCheckFactoryByName( + Config::ServerExtensionValues::get().VALIDATION_LISTENER); + listener_manager_ = listener_manager_factory.createListenerManager( + *listener_manager_factory.createEmptyConfigProto(), *this, nullptr, *this, false, + quic_stat_names_); + + THROW_IF_NOT_OK(runtime().onWorkerThreadsRegistered()); + drain_manager_ = component_factory.createDrainManager(*this); ENVOY_BUG(drain_manager_ != nullptr, "Component factory should not return nullptr from createDrainManager()"); @@ -181,6 +188,7 @@ void ValidationInstance::initialize(const Options& options, quic_stat_names_); THROW_IF_NOT_OK(config_.initialize(bootstrap_, *this, *cluster_manager_factory_)); THROW_IF_NOT_OK(runtime().initialize(clusterManager())); + clusterManager().setInitializedCb([this]() -> void { init_manager_.initialize(init_watcher_); }); } diff --git a/source/server/instance_impl.cc b/source/server/instance_impl.cc index 2400fcdebc5b1..2d7ac3c901f60 100644 --- a/source/server/instance_impl.cc +++ b/source/server/instance_impl.cc @@ -15,7 +15,7 @@ void InstanceImpl::maybeCreateHeapShrinker() { absl::StatusOr> InstanceImpl::createOverloadManager() { return OverloadManagerImpl::create( dispatcher(), *stats().rootScope(), threadLocal(), bootstrap().overload_manager(), - messageValidationContext().staticValidationVisitor(), api(), options()); + messageValidationContext().staticValidationVisitor(), api(), options(), runtime()); } std::unique_ptr InstanceImpl::createNullOverloadManager() { diff --git a/source/server/overload_manager_impl.cc b/source/server/overload_manager_impl.cc index d83a738c7af44..fe6f70c2e18b2 100644 --- a/source/server/overload_manager_impl.cc +++ b/source/server/overload_manager_impl.cc @@ -411,11 +411,11 @@ OverloadManagerImpl::create(Event::Dispatcher& dispatcher, Stats::Scope& stats_s ThreadLocal::SlotAllocator& slot_allocator, const envoy::config::overload::v3::OverloadManager& config, ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api, - const Server::Options& options) { + const Server::Options& options, Runtime::Loader& runtime) { absl::Status creation_status = absl::OkStatus(); auto ret = std::unique_ptr( new OverloadManagerImpl(dispatcher, stats_scope, slot_allocator, config, validation_visitor, - api, options, creation_status)); + api, options, runtime, creation_status)); RETURN_IF_NOT_OK(creation_status); return ret; } @@ -424,7 +424,7 @@ OverloadManagerImpl::OverloadManagerImpl(Event::Dispatcher& dispatcher, Stats::S const envoy::config::overload::v3::OverloadManager& config, ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api, const Server::Options& options, - absl::Status& creation_status) + Runtime::Loader& runtime, absl::Status& creation_status) : dispatcher_(dispatcher), time_source_(api.timeSource()), tls_(slot_allocator), refresh_interval_( std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(config, refresh_interval, 1000))), @@ -434,7 +434,7 @@ OverloadManagerImpl::OverloadManagerImpl(Event::Dispatcher& dispatcher, Stats::S std::make_unique< absl::node_hash_map>()) { Configuration::ResourceMonitorFactoryContextImpl context(dispatcher, options, api, - validation_visitor); + validation_visitor, runtime); // We should hide impl details from users, for them there should be no distinction between // proactive and regular resource monitors in configuration API. But internally we will maintain // two distinct collections of proactive and regular resources. Proactive resources are not diff --git a/source/server/overload_manager_impl.h b/source/server/overload_manager_impl.h index 2530c1220d104..b632ea3ab0318 100644 --- a/source/server/overload_manager_impl.h +++ b/source/server/overload_manager_impl.h @@ -157,7 +157,7 @@ class OverloadManagerImpl : Logger::Loggable, public OverloadM ThreadLocal::SlotAllocator& slot_allocator, const envoy::config::overload::v3::OverloadManager& config, ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api, - const Server::Options& options); + const Server::Options& options, Runtime::Loader& runtime); // Server::OverloadManager void start() override; @@ -177,7 +177,8 @@ class OverloadManagerImpl : Logger::Loggable, public OverloadM ThreadLocal::SlotAllocator& slot_allocator, const envoy::config::overload::v3::OverloadManager& config, ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api, - const Server::Options& options, absl::Status& creation_status); + const Server::Options& options, Runtime::Loader& runtime, + absl::Status& creation_status); // Factory for timer managers. This allows test-only subclasses to inject a mock implementation. virtual Event::ScaledRangeTimerManagerPtr createScaledRangeTimerManager( diff --git a/source/server/resource_monitor_config_impl.h b/source/server/resource_monitor_config_impl.h index 5bfc8d521e69f..981c46c422a75 100644 --- a/source/server/resource_monitor_config_impl.h +++ b/source/server/resource_monitor_config_impl.h @@ -10,9 +10,10 @@ class ResourceMonitorFactoryContextImpl : public ResourceMonitorFactoryContext { public: ResourceMonitorFactoryContextImpl(Event::Dispatcher& dispatcher, const Server::Options& options, Api::Api& api, - ProtobufMessage::ValidationVisitor& validation_visitor) + ProtobufMessage::ValidationVisitor& validation_visitor, + Runtime::Loader& runtime) : dispatcher_(dispatcher), options_(options), api_(api), - validation_visitor_(validation_visitor) {} + validation_visitor_(validation_visitor), runtime_(runtime) {} Event::Dispatcher& mainThreadDispatcher() override { return dispatcher_; } @@ -24,11 +25,14 @@ class ResourceMonitorFactoryContextImpl : public ResourceMonitorFactoryContext { return validation_visitor_; } + Runtime::Loader& runtime() override { return runtime_; } + private: Event::Dispatcher& dispatcher_; const Server::Options& options_; Api::Api& api_; ProtobufMessage::ValidationVisitor& validation_visitor_; + Runtime::Loader& runtime_; }; } // namespace Configuration diff --git a/source/server/server.cc b/source/server/server.cc index e3d6484501896..58deefa97883c 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -665,6 +665,11 @@ absl::Status InstanceBase::initializeOrThrow(Network::Address::InstanceConstShar loadServerFlags(initial_config.flagsPath()); + // Runtime is initialized before the overload manager so resource monitor factories can use + // runtime keys (e.g. RuntimeUInt64). + runtime_ = component_factory.createRuntime(*this, initial_config); + validation_context_.setRuntime(runtime()); + // Initialize the overload manager early so other modules can register for actions. auto overload_manager_or_error = createOverloadManager(); RETURN_IF_NOT_OK(overload_manager_or_error.status()); @@ -734,6 +739,13 @@ absl::Status InstanceBase::initializeOrThrow(Network::Address::InstanceConstShar *listener_manager_config, *this, nullptr, worker_factory_, bootstrap_.enable_dispatcher_stats(), quic_stat_names_); + // Runtime is initialized before the overload manager so resource monitors can read runtime keys; + // that means the first runtime snapshot is published before workers register for thread-local + // updates. Refresh the snapshot now so worker threads receive a valid TLS snapshot. + if (runtime_) { + RETURN_IF_NOT_OK(runtime().onWorkerThreadsRegistered()); + } + // We can now initialize stats for threading. stats_store_.initializeThreading(*dispatcher_, thread_local_); @@ -757,11 +769,6 @@ absl::Status InstanceBase::initializeOrThrow(Network::Address::InstanceConstShar // provisioned through ADS then ADS must use primary cluster as well. This invariant is enforced // during RTDS initialization and invalid configuration will be rejected. - // Runtime gets initialized before the main configuration since during main configuration - // load things may grab a reference to the loader for later use. - runtime_ = component_factory.createRuntime(*this, initial_config); - validation_context_.setRuntime(runtime()); - #ifndef WIN32 // Envoy automatically raises soft file limits, but we do it here in order to allow // a runtime override to disable this feature. Once the feature defaults to always on, diff --git a/test/extensions/resource_monitors/cgroup_memory/BUILD b/test/extensions/resource_monitors/cgroup_memory/BUILD index 0b4eef6574cbc..7572103ed3138 100644 --- a/test/extensions/resource_monitors/cgroup_memory/BUILD +++ b/test/extensions/resource_monitors/cgroup_memory/BUILD @@ -21,6 +21,7 @@ envoy_extension_cc_test( "//source/extensions/resource_monitors/cgroup_memory:config", "//source/server:resource_monitor_config_lib", "//test/mocks/event:event_mocks", + "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_mocks", "//test/test_common:utility_lib", ], diff --git a/test/extensions/resource_monitors/cgroup_memory/config_test.cc b/test/extensions/resource_monitors/cgroup_memory/config_test.cc index 56e4794a5cad3..f8c30efb614ab 100644 --- a/test/extensions/resource_monitors/cgroup_memory/config_test.cc +++ b/test/extensions/resource_monitors/cgroup_memory/config_test.cc @@ -10,6 +10,7 @@ #include "test/mocks/api/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/filesystem/mocks.h" +#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/options.h" #include "gmock/gmock.h" @@ -57,8 +58,9 @@ TEST(CgroupMemoryConfigTest, CreateMonitorDefault) { Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); auto monitor = std::make_unique(config, mock_fs); EXPECT_NE(monitor, nullptr); } @@ -89,8 +91,9 @@ TEST(CgroupMemoryConfigTest, CreateMonitorWithLimit) { Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); auto monitor = std::make_unique(config, mock_fs); EXPECT_NE(monitor, nullptr); } @@ -121,8 +124,9 @@ TEST(CgroupMemoryConfigTest, ZeroMemoryLimit) { Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); auto monitor = std::make_unique(config, mock_fs); EXPECT_NE(monitor, nullptr); } @@ -158,13 +162,15 @@ TEST(CgroupMemoryConfigTest, CreateMonitorIgnoresContext) { // Create contexts with the mock API Event::MockDispatcher dispatcher1; Server::MockOptions options1; + testing::NiceMock runtime1; Server::Configuration::ResourceMonitorFactoryContextImpl context1( - dispatcher1, options1, mock_api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher1, options1, mock_api, ProtobufMessage::getStrictValidationVisitor(), runtime1); Event::MockDispatcher dispatcher2; Server::MockOptions options2; + testing::NiceMock runtime2; Server::Configuration::ResourceMonitorFactoryContextImpl context2( - dispatcher2, options2, mock_api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher2, options2, mock_api, ProtobufMessage::getStrictValidationVisitor(), runtime2); auto monitor1 = factory->createResourceMonitor(config, context1); auto monitor2 = factory->createResourceMonitor(config, context2); diff --git a/test/extensions/resource_monitors/cpu_utilization/BUILD b/test/extensions/resource_monitors/cpu_utilization/BUILD index 1a363500118ac..b21a5bef467c0 100644 --- a/test/extensions/resource_monitors/cpu_utilization/BUILD +++ b/test/extensions/resource_monitors/cpu_utilization/BUILD @@ -33,6 +33,7 @@ envoy_extension_cc_test( "//source/extensions/resource_monitors/cpu_utilization:linux_cpu_stats_reader", "//source/server:resource_monitor_config_lib", "//test/mocks/event:event_mocks", + "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:options_mocks", "//test/test_common:environment_lib", "@abseil-cpp//absl/types:optional", @@ -52,6 +53,7 @@ envoy_extension_cc_test( "//source/extensions/resource_monitors/cpu_utilization:linux_cpu_stats_reader", "//source/server:resource_monitor_config_lib", "//test/mocks/event:event_mocks", + "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:options_mocks", "@envoy_api//envoy/extensions/resource_monitors/cpu_utilization/v3:pkg_cc_proto", ], diff --git a/test/extensions/resource_monitors/cpu_utilization/config_test.cc b/test/extensions/resource_monitors/cpu_utilization/config_test.cc index 6309b325ed601..a053c29cb9bff 100644 --- a/test/extensions/resource_monitors/cpu_utilization/config_test.cc +++ b/test/extensions/resource_monitors/cpu_utilization/config_test.cc @@ -7,6 +7,7 @@ #include "source/server/resource_monitor_config_impl.h" #include "test/mocks/event/mocks.h" +#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/options.h" #include "absl/types/optional.h" @@ -53,8 +54,9 @@ TEST(CpuUtilizationMonitorFactoryTest, CreateMonitorDefault) { Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); auto monitor = factory->createResourceMonitor(config, context); EXPECT_NE(monitor, nullptr); } @@ -74,8 +76,9 @@ TEST(CpuUtilizationMonitorFactoryTest, CreateContainerCPUMonitor) { Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); #if defined(__linux__) // Skip the check if the system running the test does not support cgroup. @@ -105,8 +108,9 @@ TEST(CpuUtilizationMonitorFactoryTest, HostMonitorFunctional) { Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); auto monitor = factory->createResourceMonitor(config, context); ASSERT_NE(monitor, nullptr); @@ -130,8 +134,9 @@ TEST(CpuUtilizationMonitorFactoryTest, ContainerMonitorFunctional) { Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); // Skip the check if the system running the test does not support cgroup. TRY_ASSERT_MAIN_THREAD { diff --git a/test/extensions/resource_monitors/cpu_utilization/linux_cpu_stats_reader_test.cc b/test/extensions/resource_monitors/cpu_utilization/linux_cpu_stats_reader_test.cc index 5c30758610406..9a674e1d3a162 100644 --- a/test/extensions/resource_monitors/cpu_utilization/linux_cpu_stats_reader_test.cc +++ b/test/extensions/resource_monitors/cpu_utilization/linux_cpu_stats_reader_test.cc @@ -6,6 +6,7 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/filesystem/mocks.h" +#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/options.h" #include "test/test_common/environment.h" @@ -103,7 +104,8 @@ class LinuxContainerCpuStatsReaderTest : public testing::Test { public: LinuxContainerCpuStatsReaderTest() : api_(Api::createApiForTest()), - context_(dispatcher_, options_, *api_, ProtobufMessage::getStrictValidationVisitor()), + context_(dispatcher_, options_, *api_, ProtobufMessage::getStrictValidationVisitor(), + runtime_), cpu_allocated_path_(TestEnvironment::temporaryPath("cgroup_cpu_allocated_stats")), cpu_times_path_(TestEnvironment::temporaryPath("cgroup_cpu_times_stats")) { // Default sane values so tests only need to set what they care about @@ -129,6 +131,7 @@ class LinuxContainerCpuStatsReaderTest : public testing::Test { Event::MockDispatcher dispatcher_; Api::ApiPtr api_; Server::MockOptions options_; + testing::NiceMock runtime_; Server::Configuration::ResourceMonitorFactoryContextImpl context_; std::string cpu_allocated_path_; std::string cpu_times_path_; @@ -259,7 +262,8 @@ class LinuxContainerCpuStatsReaderV2Test : public testing::Test { public: LinuxContainerCpuStatsReaderV2Test() : api_(Api::createApiForTest()), - context_(dispatcher_, options_, *api_, ProtobufMessage::getStrictValidationVisitor()), + context_(dispatcher_, options_, *api_, ProtobufMessage::getStrictValidationVisitor(), + runtime_), v2_cpu_stat_path_(TestEnvironment::temporaryPath("cgroupv2_cpu_stat")), v2_cpu_max_path_(TestEnvironment::temporaryPath("cgroupv2_cpu_max")), v2_cpu_effective_path_(TestEnvironment::temporaryPath("cgroupv2_cpu_effective")) {} @@ -288,6 +292,7 @@ class LinuxContainerCpuStatsReaderV2Test : public testing::Test { Event::MockDispatcher dispatcher_; Api::ApiPtr api_; Server::MockOptions options_; + testing::NiceMock runtime_; Server::Configuration::ResourceMonitorFactoryContextImpl context_; std::string v2_cpu_stat_path_; std::string v2_cpu_max_path_; @@ -508,8 +513,9 @@ TEST(LinuxContainerCpuStatsReaderFactoryTest, CreatesV2ReaderWhenV2FilesExist) { Api::ApiPtr api = Api::createApiForTest(); Event::MockDispatcher dispatcher; Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); Filesystem::MockInstance mock_fs; EXPECT_CALL(mock_fs, fileExists("/sys/fs/cgroup/cpu.stat")).WillOnce(Return(true)); @@ -524,8 +530,9 @@ TEST(LinuxContainerCpuStatsReaderFactoryTest, CreatesV1ReaderWhenOnlyV1FilesExis Api::ApiPtr api = Api::createApiForTest(); Event::MockDispatcher dispatcher; Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); Filesystem::MockInstance mock_fs; @@ -550,8 +557,9 @@ TEST(LinuxContainerCpuStatsReaderFactoryTest, ThrowsWhenNoCgroupFilesExist) { Api::ApiPtr api = Api::createApiForTest(); Event::MockDispatcher dispatcher; Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); Filesystem::MockInstance mock_fs; diff --git a/test/extensions/resource_monitors/downstream_connections/BUILD b/test/extensions/resource_monitors/downstream_connections/BUILD index 0d5c1004ff073..d7f84b72ff623 100644 --- a/test/extensions/resource_monitors/downstream_connections/BUILD +++ b/test/extensions/resource_monitors/downstream_connections/BUILD @@ -34,6 +34,7 @@ envoy_extension_cc_test( "//source/extensions/resource_monitors/downstream_connections:config", "//source/server:resource_monitor_config_lib", "//test/mocks/event:event_mocks", + "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:options_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/resource_monitors/downstream_connections/v3:pkg_cc_proto", diff --git a/test/extensions/resource_monitors/downstream_connections/config_test.cc b/test/extensions/resource_monitors/downstream_connections/config_test.cc index af4f61cc06f40..1228969415df6 100644 --- a/test/extensions/resource_monitors/downstream_connections/config_test.cc +++ b/test/extensions/resource_monitors/downstream_connections/config_test.cc @@ -6,6 +6,7 @@ #include "source/server/resource_monitor_config_impl.h" #include "test/mocks/event/mocks.h" +#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/options.h" #include "test/test_common/utility.h" @@ -30,8 +31,9 @@ TEST(ActiveDownstreamConnectionsMonitorFactoryTest, CreateMonitorInvalidConfig) Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); EXPECT_THROW_WITH_REGEX(factory->createProactiveResourceMonitor(config, context), ProtoValidationException, "Proto constraint validation failed " @@ -52,8 +54,9 @@ TEST(ActiveDownstreamConnectionsMonitorFactoryTest, CreateCustomMonitor) { Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); auto monitor = factory->createProactiveResourceMonitor(config, context); EXPECT_NE(monitor, nullptr); } @@ -67,8 +70,9 @@ TEST(ActiveDownstreamConnectionsMonitorFactoryTest, CreateDefaultMonitor) { Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); auto config = factory->createEmptyConfigProto(); EXPECT_THROW_WITH_REGEX(factory->createProactiveResourceMonitor(*config, context), diff --git a/test/extensions/resource_monitors/fixed_heap/BUILD b/test/extensions/resource_monitors/fixed_heap/BUILD index bc7f429d7b9d1..4bfe537f1453e 100644 --- a/test/extensions/resource_monitors/fixed_heap/BUILD +++ b/test/extensions/resource_monitors/fixed_heap/BUILD @@ -18,9 +18,9 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/resource_monitors/fixed_heap:fixed_heap_monitor", + "//test/mocks/runtime:runtime_mocks", "//test/test_common:test_runtime_lib", "@abseil-cpp//absl/types:optional", - "@envoy_api//envoy/extensions/resource_monitors/fixed_heap/v3:pkg_cc_proto", ], ) @@ -35,6 +35,7 @@ envoy_extension_cc_test( "//source/extensions/resource_monitors/fixed_heap:config", "//source/server:resource_monitor_config_lib", "//test/mocks/event:event_mocks", + "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:options_mocks", "@envoy_api//envoy/extensions/resource_monitors/fixed_heap/v3:pkg_cc_proto", ], diff --git a/test/extensions/resource_monitors/fixed_heap/config_test.cc b/test/extensions/resource_monitors/fixed_heap/config_test.cc index c26aa094cf441..efc99e5132e06 100644 --- a/test/extensions/resource_monitors/fixed_heap/config_test.cc +++ b/test/extensions/resource_monitors/fixed_heap/config_test.cc @@ -6,6 +6,7 @@ #include "source/server/resource_monitor_config_impl.h" #include "test/mocks/event/mocks.h" +#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/options.h" #include "gtest/gtest.h" @@ -27,12 +28,98 @@ TEST(FixedHeapMonitorFactoryTest, CreateMonitor) { Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); auto monitor = factory->createResourceMonitor(config, context); EXPECT_NE(monitor, nullptr); } +TEST(FixedHeapMonitorFactoryTest, CreateMonitorWithRuntimeOverride) { + auto factory = + Registry::FactoryRegistry::getFactory( + "envoy.resource_monitors.fixed_heap"); + ASSERT_NE(factory, nullptr); + + envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig config; + config.mutable_max_heap_size_bytes_runtime()->set_default_value(2000); + config.mutable_max_heap_size_bytes_runtime()->set_runtime_key("fixed_heap.max_bytes"); + Event::MockDispatcher dispatcher; + Api::ApiPtr api = Api::createApiForTest(); + Server::MockOptions options; + testing::NiceMock runtime; + EXPECT_CALL(runtime, snapshot()).WillRepeatedly(testing::ReturnRef(runtime.snapshot_)); + EXPECT_CALL(runtime.snapshot_, getInteger("fixed_heap.max_bytes", 2000)) + .WillRepeatedly(testing::Return(2000)); + Server::Configuration::ResourceMonitorFactoryContextImpl context( + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); + auto monitor = factory->createResourceMonitor(config, context); + EXPECT_NE(monitor, nullptr); +} + +TEST(FixedHeapMonitorFactoryTest, RejectNeitherSet) { + auto factory = + Registry::FactoryRegistry::getFactory( + "envoy.resource_monitors.fixed_heap"); + ASSERT_NE(factory, nullptr); + + envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig config; + Event::MockDispatcher dispatcher; + Api::ApiPtr api = Api::createApiForTest(); + Server::MockOptions options; + testing::NiceMock runtime; + Server::Configuration::ResourceMonitorFactoryContextImpl context( + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); + EXPECT_THROW_WITH_REGEX(factory->createResourceMonitor(config, context), EnvoyException, + "max heap size must be greater than 0"); +} + +TEST(FixedHeapMonitorFactoryTest, RejectBothSet) { + auto factory = + Registry::FactoryRegistry::getFactory( + "envoy.resource_monitors.fixed_heap"); + ASSERT_NE(factory, nullptr); + + envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig config; + config.set_max_heap_size_bytes(1000); + config.mutable_max_heap_size_bytes_runtime()->set_default_value(2000); + config.mutable_max_heap_size_bytes_runtime()->set_runtime_key("fixed_heap.max_bytes"); + Event::MockDispatcher dispatcher; + Api::ApiPtr api = Api::createApiForTest(); + Server::MockOptions options; + testing::NiceMock runtime; + EXPECT_CALL(runtime, snapshot()).WillRepeatedly(testing::ReturnRef(runtime.snapshot_)); + EXPECT_CALL(runtime.snapshot_, getInteger("fixed_heap.max_bytes", 2000)) + .WillRepeatedly(testing::Return(2000)); + Server::Configuration::ResourceMonitorFactoryContextImpl context( + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); + EXPECT_THROW_WITH_REGEX(factory->createResourceMonitor(config, context), EnvoyException, + "exactly one of max_heap_size_bytes or max_heap_size_bytes_runtime must " + "be set"); +} + +TEST(FixedHeapMonitorFactoryTest, RejectRuntimeDefaultZero) { + auto factory = + Registry::FactoryRegistry::getFactory( + "envoy.resource_monitors.fixed_heap"); + ASSERT_NE(factory, nullptr); + + envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig config; + config.mutable_max_heap_size_bytes_runtime()->set_default_value(0); + config.mutable_max_heap_size_bytes_runtime()->set_runtime_key("fixed_heap.max_bytes"); + Event::MockDispatcher dispatcher; + Api::ApiPtr api = Api::createApiForTest(); + Server::MockOptions options; + testing::NiceMock runtime; + EXPECT_CALL(runtime, snapshot()).WillRepeatedly(testing::ReturnRef(runtime.snapshot_)); + EXPECT_CALL(runtime.snapshot_, getInteger("fixed_heap.max_bytes", 0)) + .WillRepeatedly(testing::Return(0)); + Server::Configuration::ResourceMonitorFactoryContextImpl context( + dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); + EXPECT_THROW_WITH_REGEX(factory->createResourceMonitor(config, context), EnvoyException, + "max heap size must be greater than 0"); +} + } // namespace } // namespace FixedHeapMonitor } // namespace ResourceMonitors diff --git a/test/extensions/resource_monitors/fixed_heap/fixed_heap_monitor_test.cc b/test/extensions/resource_monitors/fixed_heap/fixed_heap_monitor_test.cc index 007ab572b8122..0963bba304dae 100644 --- a/test/extensions/resource_monitors/fixed_heap/fixed_heap_monitor_test.cc +++ b/test/extensions/resource_monitors/fixed_heap/fixed_heap_monitor_test.cc @@ -1,7 +1,6 @@ -#include "envoy/extensions/resource_monitors/fixed_heap/v3/fixed_heap.pb.h" - #include "source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h" +#include "test/mocks/runtime/mocks.h" #include "test/test_common/test_runtime.h" #include "absl/types/optional.h" @@ -45,14 +44,12 @@ class ResourcePressure : public Server::ResourceUpdateCallbacks { }; TEST(FixedHeapMonitorTest, ComputesCorrectUsage) { - - envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig config; - config.set_max_heap_size_bytes(1000); auto stats_reader = std::make_unique(); EXPECT_CALL(*stats_reader, reservedHeapBytes()).WillOnce(Return(800)); EXPECT_CALL(*stats_reader, unmappedHeapBytes()).WillOnce(Return(100)); EXPECT_CALL(*stats_reader, freeMappedHeapBytes()).WillOnce(Return(200)); - auto monitor = std::make_unique(config, std::move(stats_reader)); + auto monitor = std::make_unique( + absl::variant(uint64_t(1000)), std::move(stats_reader)); ResourcePressure resource; monitor->updateResourceUsage(resource); @@ -65,11 +62,10 @@ TEST(FixedHeapMonitorTest, ComputesCorrectUsageRuntimeUseAllocated) { TestScopedRuntime scoped_runtime; scoped_runtime.mergeValues({{"envoy.reloadable_features.fixed_heap_use_allocated", "true"}}); - envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig config; - config.set_max_heap_size_bytes(1000); auto stats_reader = std::make_unique(); EXPECT_CALL(*stats_reader, allocatedHeapBytes()).WillOnce(Return(600)); - auto monitor = std::make_unique(config, std::move(stats_reader)); + auto monitor = std::make_unique( + absl::variant(uint64_t(1000)), std::move(stats_reader)); ResourcePressure resource; monitor->updateResourceUsage(resource); @@ -79,16 +75,14 @@ TEST(FixedHeapMonitorTest, ComputesCorrectUsageRuntimeUseAllocated) { } TEST(FixedHeapMonitorTest, ComputeUsageWithRealMemoryStats) { - - envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig config; const uint64_t max_heap = 1024 * 1024 * 1024; - config.set_max_heap_size_bytes(max_heap); auto stats_reader = std::make_unique(); const double expected_usage = (stats_reader->reservedHeapBytes() - stats_reader->unmappedHeapBytes() - stats_reader->freeMappedHeapBytes()) / static_cast(max_heap); - auto monitor = std::make_unique(config, std::move(stats_reader)); + auto monitor = std::make_unique( + absl::variant(max_heap), std::move(stats_reader)); ResourcePressure resource; monitor->updateResourceUsage(resource); @@ -99,19 +93,72 @@ TEST(FixedHeapMonitorTest, ComputesUsageRuntimeUseAllocatedWithRealMemoryStats) TestScopedRuntime scoped_runtime; scoped_runtime.mergeValues({{"envoy.reloadable_features.fixed_heap_use_allocated", "true"}}); - envoy::extensions::resource_monitors::fixed_heap::v3::FixedHeapConfig config; const uint64_t max_heap = 1024 * 1024 * 1024; - config.set_max_heap_size_bytes(max_heap); auto stats_reader = std::make_unique(); const double expected_pressure = stats_reader->allocatedHeapBytes() / static_cast(max_heap); - auto monitor = std::make_unique(config, std::move(stats_reader)); + auto monitor = std::make_unique( + absl::variant(max_heap), std::move(stats_reader)); ResourcePressure resource; monitor->updateResourceUsage(resource); EXPECT_NEAR(resource.pressure(), expected_pressure, 0.0005); } +TEST(FixedHeapMonitorTest, RuntimeMaxHeapReReadOnEachUpdate) { + testing::NiceMock runtime; + EXPECT_CALL(runtime, snapshot()).WillRepeatedly(testing::ReturnRef(runtime.snapshot_)); + + envoy::config::core::v3::RuntimeUInt64 runtime_proto; + runtime_proto.set_runtime_key("fixed_heap.max_bytes"); + runtime_proto.set_default_value(1000); + + EXPECT_CALL(runtime.snapshot_, getInteger("fixed_heap.max_bytes", 1000)) + .WillOnce(Return(1000)) // first updateResourceUsage + .WillOnce(Return(500)); // second updateResourceUsage (changed at runtime) + + auto stats_reader = std::make_unique(); + EXPECT_CALL(*stats_reader, reservedHeapBytes()).WillRepeatedly(Return(800)); + EXPECT_CALL(*stats_reader, unmappedHeapBytes()).WillRepeatedly(Return(100)); + EXPECT_CALL(*stats_reader, freeMappedHeapBytes()).WillRepeatedly(Return(200)); + + Runtime::UInt64 runtime_uint64(runtime_proto, runtime); + auto monitor = std::make_unique( + absl::variant(std::move(runtime_uint64)), std::move(stats_reader)); + + ResourcePressure resource1; + monitor->updateResourceUsage(resource1); + ASSERT_TRUE(resource1.hasPressure()); + EXPECT_EQ(resource1.pressure(), 0.5); // 500 / 1000 + + ResourcePressure resource2; + monitor->updateResourceUsage(resource2); + ASSERT_TRUE(resource2.hasPressure()); + EXPECT_EQ(resource2.pressure(), 1.0); // 500 / 500 +} + +TEST(FixedHeapMonitorTest, RuntimeMaxHeapZeroAtRuntime) { + testing::NiceMock runtime; + EXPECT_CALL(runtime, snapshot()).WillRepeatedly(testing::ReturnRef(runtime.snapshot_)); + + envoy::config::core::v3::RuntimeUInt64 runtime_proto; + runtime_proto.set_runtime_key("fixed_heap.max_bytes"); + runtime_proto.set_default_value(1000); + + EXPECT_CALL(runtime.snapshot_, getInteger("fixed_heap.max_bytes", 1000)) + .WillOnce(Return(0)); // updateResourceUsage returns 0 at runtime + + auto stats_reader = std::make_unique(); + Runtime::UInt64 runtime_uint64(runtime_proto, runtime); + auto monitor = std::make_unique( + absl::variant(std::move(runtime_uint64)), std::move(stats_reader)); + + ResourcePressure resource; + monitor->updateResourceUsage(resource); + ASSERT_TRUE(resource.hasError()); + ASSERT_FALSE(resource.hasPressure()); +} + } // namespace } // namespace FixedHeapMonitor } // namespace ResourceMonitors diff --git a/test/extensions/resource_monitors/injected_resource/BUILD b/test/extensions/resource_monitors/injected_resource/BUILD index c58b1a82512e9..55a42c97b617c 100644 --- a/test/extensions/resource_monitors/injected_resource/BUILD +++ b/test/extensions/resource_monitors/injected_resource/BUILD @@ -38,6 +38,7 @@ envoy_cc_test( "//source/common/stats:isolated_store_lib", "//source/extensions/resource_monitors/injected_resource:injected_resource_monitor", "//source/server:resource_monitor_config_lib", + "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:options_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", @@ -56,6 +57,7 @@ envoy_extension_cc_test( "//source/common/stats:isolated_store_lib", "//source/extensions/resource_monitors/injected_resource:config", "//source/server:resource_monitor_config_lib", + "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:options_mocks", "//test/test_common:environment_lib", "@envoy_api//envoy/extensions/resource_monitors/injected_resource/v3:pkg_cc_proto", diff --git a/test/extensions/resource_monitors/injected_resource/config_test.cc b/test/extensions/resource_monitors/injected_resource/config_test.cc index c0ffe5118cab1..7c17b0b925be9 100644 --- a/test/extensions/resource_monitors/injected_resource/config_test.cc +++ b/test/extensions/resource_monitors/injected_resource/config_test.cc @@ -6,6 +6,7 @@ #include "source/extensions/resource_monitors/injected_resource/config.h" #include "source/server/resource_monitor_config_impl.h" +#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/options.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" @@ -29,8 +30,9 @@ TEST(InjectedResourceMonitorFactoryTest, CreateMonitor) { Api::ApiPtr api = Api::createApiForTest(); Event::DispatcherPtr dispatcher(api->allocateDispatcher("test_thread")); Server::MockOptions options; + testing::NiceMock runtime; Server::Configuration::ResourceMonitorFactoryContextImpl context( - *dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor()); + *dispatcher, options, *api, ProtobufMessage::getStrictValidationVisitor(), runtime); Server::ResourceMonitorPtr monitor = factory->createResourceMonitor(config, context); EXPECT_NE(monitor, nullptr); } diff --git a/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_test.cc b/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_test.cc index 7010e02241b5c..32c333f3e9fbc 100644 --- a/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_test.cc +++ b/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_test.cc @@ -5,6 +5,7 @@ #include "source/extensions/resource_monitors/injected_resource/injected_resource_monitor.h" #include "source/server/resource_monitor_config_impl.h" +#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/options.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" @@ -63,13 +64,14 @@ class InjectedResourceMonitorTest : public testing::Test { envoy::extensions::resource_monitors::injected_resource::v3::InjectedResourceConfig config; config.set_filename(resource_filename_); Server::Configuration::ResourceMonitorFactoryContextImpl context( - *dispatcher_, options_, *api_, ProtobufMessage::getStrictValidationVisitor()); + *dispatcher_, options_, *api_, ProtobufMessage::getStrictValidationVisitor(), runtime_); return std::make_unique(config, context); } Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; Server::MockOptions options_; + testing::NiceMock runtime_; const std::string resource_filename_; AtomicFileUpdater file_updater_; MockedCallbacks cb_; diff --git a/test/mocks/runtime/mocks.cc b/test/mocks/runtime/mocks.cc index 01b09dbbedfcf..7cfa048d98a2c 100644 --- a/test/mocks/runtime/mocks.cc +++ b/test/mocks/runtime/mocks.cc @@ -20,6 +20,7 @@ MockSnapshot::MockSnapshot() { MockSnapshot::~MockSnapshot() = default; MockLoader::MockLoader() { + ON_CALL(*this, onWorkerThreadsRegistered()).WillByDefault(Return(absl::OkStatus())); ON_CALL(*this, threadsafeSnapshot()).WillByDefault(Return(threadsafe_snapshot_)); ON_CALL(*this, snapshot()).WillByDefault(ReturnRef(snapshot_)); ON_CALL(*this, getRootScope()).WillByDefault(ReturnRef(*store_.rootScope())); diff --git a/test/mocks/runtime/mocks.h b/test/mocks/runtime/mocks.h index f3de804126d2b..b6a06e02b6296 100644 --- a/test/mocks/runtime/mocks.h +++ b/test/mocks/runtime/mocks.h @@ -62,6 +62,7 @@ class MockLoader : public Loader { ~MockLoader() override; MOCK_METHOD(absl::Status, initialize, (Upstream::ClusterManager & cm)); + MOCK_METHOD(absl::Status, onWorkerThreadsRegistered, ()); MOCK_METHOD(const Snapshot&, snapshot, (), (const)); MOCK_METHOD(SnapshotConstSharedPtr, threadsafeSnapshot, ()); MOCK_METHOD(absl::Status, mergeValues, ((const absl::node_hash_map&))); diff --git a/test/server/BUILD b/test/server/BUILD index 23dcf07f66a43..aa0539636b11d 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -322,6 +322,7 @@ envoy_cc_test( "//source/server:overload_manager_lib", "//test/mocks/event:event_mocks", "//test/mocks/protobuf:protobuf_mocks", + "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:options_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:registry_lib", diff --git a/test/server/overload_manager_impl_test.cc b/test/server/overload_manager_impl_test.cc index acabea6bdebb7..60737d409ec65 100644 --- a/test/server/overload_manager_impl_test.cc +++ b/test/server/overload_manager_impl_test.cc @@ -16,6 +16,7 @@ #include "test/common/stats/stat_test_utility.h" #include "test/mocks/event/mocks.h" #include "test/mocks/protobuf/mocks.h" +#include "test/mocks/runtime/mocks.h" #include "test/mocks/server/options.h" #include "test/mocks/thread_local/mocks.h" #include "test/test_common/registry.h" @@ -177,9 +178,10 @@ class TestOverloadManager : public OverloadManagerImpl { ThreadLocal::SlotAllocator& slot_allocator, const envoy::config::overload::v3::OverloadManager& config, ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api, - const Server::MockOptions& options, absl::Status& creation_status) + const Server::MockOptions& options, Runtime::Loader& runtime, + absl::Status& creation_status) : OverloadManagerImpl(dispatcher, stats_scope, slot_allocator, config, validation_visitor, - api, options, creation_status) { + api, options, runtime, creation_status) { THROW_IF_NOT_OK_REF(creation_status); EXPECT_CALL(*this, createScaledRangeTimerManager) .Times(AnyNumber()) @@ -228,7 +230,7 @@ class OverloadManagerImplTest : public testing::Test { absl::Status creation_status = absl::OkStatus(); return std::make_unique(dispatcher_, *stats_.rootScope(), thread_local_, parseConfig(config), validation_visitor_, *api_, - options_, creation_status); + options_, runtime_, creation_status); } FakeResourceMonitorFactory factory1_; @@ -249,6 +251,7 @@ class OverloadManagerImplTest : public testing::Test { NiceMock validation_visitor_; Api::ApiPtr api_; Server::MockOptions options_; + NiceMock runtime_; }; constexpr char kRegularStateConfig[] = R"YAML( From 757df39ba03b77fa3f1e39194239f2667d3269f8 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 May 2026 12:47:21 +0100 Subject: [PATCH 227/750] bazel/ci: Ensure dates are updated when updating deps (#44872) Signed-off-by: Ryan Northey --- bazel/version_update_post.sh | 52 +++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/bazel/version_update_post.sh b/bazel/version_update_post.sh index 4306692481269..376ce59b5ce19 100644 --- a/bazel/version_update_post.sh +++ b/bazel/version_update_post.sh @@ -4,27 +4,47 @@ set -e set -o pipefail -EXISTING_DATE="$("${JQ}" -r ".${DEP}.release_date" "${DEP_DATA}")" -DATE_SEARCH="release_date = \"${EXISTING_DATE}\"," DEP_CHECK="${DEP_CHECK:-tools/dependency/check}" +# Derive the companion deps.yaml from VERSION_FILE. +# VERSION_FILE is always /repository_locations.bzl, so: +# bazel/repository_locations.bzl -> bazel/deps.yaml +# api/bazel/repository_locations.bzl -> api/bazel/deps.yaml +DEPS_YAML="${VERSION_FILE%/repository_locations.bzl}/deps.yaml" + +if [[ ! -f "$DEPS_YAML" ]]; then + echo "ERROR: Expected deps YAML not found: ${DEPS_YAML}" >&2 + exit 1 +fi + +# Read the current release_date directly from the dependency JSON data. +# shellcheck disable=SC2016 +EXISTING_DATE="$("$JQ" -r --arg dep "$DEP" '.[$dep].release_date' "$DEP_DATA")" + +if [[ -z "$EXISTING_DATE" || "$EXISTING_DATE" == "null" ]]; then + echo "ERROR: Could not find release_date for '${DEP}' in DEP_DATA" >&2 + exit 1 +fi + find_date_line () { - local match match_ln date_match_ln - # This needs to find the correct date to replace - match="$(\ - grep -n "${DEP_SEARCH}" "${VERSION_FILE}" \ - | cut -d: -f-2)" - match_ln="$(\ - echo "${match}" \ - | cut -d: -f1)" - match_ln="$((match_ln + 1))" + local dep_ln date_match_ln + # Find the line number of the dep's top-level YAML key (e.g. "bazel_gazelle:"). + dep_ln="$(grep -n "^${DEP}:$" "$DEPS_YAML" | head -n1 | cut -d: -f1)" + if [[ -z "$dep_ln" ]]; then + echo "ERROR: Could not find dep block '${DEP}:' in ${DEPS_YAML}" >&2 + exit 1 + fi + # From that line forward, find the first matching release_date line. date_match_ln="$(\ - tail -n "+${match_ln}" "${VERSION_FILE}" \ - | grep -n "${DATE_SEARCH}" \ + tail -n "+${dep_ln}" "$DEPS_YAML" \ + | grep -n "^ release_date: \"${EXISTING_DATE}\"$" \ | head -n1 \ | cut -d: -f1)" - date_match_ln="$((match_ln + date_match_ln - 1))" - printf '%s' "$date_match_ln" + if [[ -z "$date_match_ln" ]]; then + echo "ERROR: Could not find release_date line for '${DEP}' in ${DEPS_YAML}" >&2 + exit 1 + fi + printf '%s' "$((dep_ln + date_match_ln - 1))" } update_date () { @@ -33,7 +53,7 @@ update_date () { search="$2" replace="$3" echo "Updating date(${match_ln}): ${search} -> ${replace}" - sed -i "${match_ln}s/${search}/${replace}/" "$VERSION_FILE" + sed -i "${match_ln}s/${search}/${replace}/" "$DEPS_YAML" } get_new_date () { From 0b5564b72daa08436df29903cc65427a0d79fc74 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 May 2026 12:49:57 +0100 Subject: [PATCH 228/750] ci/engflow: Increase retry budgets (#44878) Signed-off-by: Ryan Northey --- .bazelrc | 4 ++-- bazel/tests/external/.bazelrc | 4 ++-- mobile/.bazelrc | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.bazelrc b/.bazelrc index 8c8a33920736d..653ade7a500e6 100644 --- a/.bazelrc +++ b/.bazelrc @@ -438,8 +438,8 @@ common:engflow-common --grpc_keepalive_time=60s common:engflow-common --grpc_keepalive_timeout=30s common:engflow-common --remote_cache_compression # Recover from transient gRPC failures (e.g. UNAVAILABLE: io exception) talking to engflow. -common:engflow-common --remote_retries=5 -common:engflow-common --remote_retry_max_delay=10s +common:engflow-common --remote_retries=10 +common:engflow-common --remote_retry_max_delay=60s # Recover when blobs are evicted from the CAS mid-build (common during patch releases). common:engflow-common --experimental_remote_cache_eviction_retries=5 diff --git a/bazel/tests/external/.bazelrc b/bazel/tests/external/.bazelrc index e187cd771cd12..76c6576862305 100644 --- a/bazel/tests/external/.bazelrc +++ b/bazel/tests/external/.bazelrc @@ -98,8 +98,8 @@ common:engflow-common --grpc_keepalive_time=60s common:engflow-common --grpc_keepalive_timeout=30s common:engflow-common --remote_cache_compression # Recover from transient gRPC failures (e.g. UNAVAILABLE: io exception) talking to engflow. -common:engflow-common --remote_retries=5 -common:engflow-common --remote_retry_max_delay=10s +common:engflow-common --remote_retries=10 +common:engflow-common --remote_retry_max_delay=60s common:engflow-common --experimental_remote_cache_eviction_retries=5 # Recover when blobs are evicted from the CAS mid-build (common during patch releases). common:engflow-common --experimental_remote_cache_eviction_retries=5 diff --git a/mobile/.bazelrc b/mobile/.bazelrc index b6e2d32b40646..43af1474b5cc5 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -130,8 +130,8 @@ build:mobile-rbe --spawn_strategy=remote,sandboxed,local build:mobile-rbe --experimental_ui_max_stdouterr_bytes=10485760 build:mobile-rbe --extra_execution_platforms=//bazel/platforms/rbe:linux_x64 # Recover from transient gRPC failures (e.g. UNAVAILABLE: io exception) talking to engflow. -build:mobile-rbe --remote_retries=5 -build:mobile-rbe --remote_retry_max_delay=10s +build:mobile-rbe --remote_retries=10 +build:mobile-rbe --remote_retry_max_delay=60s build:mobile-rbe --experimental_remote_cache_eviction_retries=5 From b0ab33e464403f67638ef2f7aeb56cac201b9c2a Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 11:52:44 +0000 Subject: [PATCH 229/750] deps: Bump `thrift` -> 0.23.0 (#44880) Fix #44724 Fix: - CVE-2025-48431 - CVE-2026-41602 - CVE-2026-41603 - CVE-2026-41604 - CVE-2026-41605 - CVE-2026-41606 - CVE-2026-41607 - CVE-2026-41636 Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 +-- bazel/thrift.patch | 54 ++++------------------------------ 3 files changed, 9 insertions(+), 51 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 5e88dbe3fd097..a42032abbc252 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -1397,7 +1397,7 @@ thrift: project_name: "Apache Thrift" project_desc: "Apache Thrift Python library" project_url: "https://thrift.apache.org/" - release_date: "2025-05-23" + release_date: "2026-04-27" use_category: - test_only cpe: "cpe:2.3:a:apache:thrift:*" diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 6424bfbe60f5d..b8844cfa74e6e 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -677,8 +677,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/maxmind/libmaxminddb/releases/download/{version}/libmaxminddb-{version}.tar.gz"], ), thrift = dict( - version = "0.22.0", - sha256 = "c4649c5879dd56c88f1e7a1c03e0fbfcc3b2a2872fb81616bffba5aa8a225a37", + version = "0.23.0", + sha256 = "087ba9517063c8252e9e7fb4e3891a9fd85e20af80e0309e2276eff16791d75c", strip_prefix = "thrift-{version}/lib/py/", urls = ["https://github.com/apache/thrift/archive/refs/tags/v{version}.tar.gz"], ), diff --git a/bazel/thrift.patch b/bazel/thrift.patch index a4871f01afe4a..5706dfb67456a 100644 --- a/bazel/thrift.patch +++ b/bazel/thrift.patch @@ -1,51 +1,9 @@ diff --git a/pyproject.toml b/pyproject.toml -new file mode 100644 -index 000000000..638dd9c54 ---- /dev/null +index b61373e..638dd9c 100644 +--- a/pyproject.toml +++ b/pyproject.toml -@@ -0,0 +1,3 @@ -+[build-system] +@@ -1,3 +1,3 @@ + [build-system] +-requires = ["setuptools>=61.0", "wheel"] +requires = ["setuptools>=61.0"] -+build-backend = "setuptools.build_meta" -diff --git a/setup.py b/setup.py -index 2a170a411..0f7cf0a5b 100644 ---- a/setup.py -+++ b/setup.py -@@ -20,13 +20,10 @@ - # - - import sys --try: -- from setuptools import setup, Extension --except Exception: -- from distutils.core import setup, Extension - --from distutils.command.build_ext import build_ext --from distutils.errors import CCompilerError, DistutilsExecError, DistutilsPlatformError -+from setuptools import Extension, setup -+from setuptools.command.build_ext import build_ext -+from setuptools.errors import CompileError, ExecError, PlatformError - - # Fix to build sdist under vagrant - import os -@@ -39,9 +36,9 @@ if 'vagrant' in str(os.environ): - include_dirs = ['src'] - if sys.platform == 'win32': - include_dirs.append('compat/win32') -- ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError) -+ ext_errors = (CompileError, ExecError, PlatformError, IOError) - else: -- ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError) -+ ext_errors = (CompileError, ExecError, PlatformError) - - - class BuildFailed(Exception): -@@ -52,7 +49,7 @@ class ve_build_ext(build_ext): - def run(self): - try: - build_ext.run(self) -- except DistutilsPlatformError: -+ except PlatformError: - raise BuildFailed() - - def build_extension(self, ext): + build-backend = "setuptools.build_meta" From 0aa1d818957b1a8dec47b4b9b5d5f408d47b0430 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 May 2026 13:28:03 +0100 Subject: [PATCH 230/750] ci/format: Fail on rustfmt diff (#44879) Signed-off-by: Ryan Northey --- ci/format_pre.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ci/format_pre.sh b/ci/format_pre.sh index a87c0ebba4c65..159bc9f8f07d3 100755 --- a/ci/format_pre.sh +++ b/ci/format_pre.sh @@ -76,7 +76,14 @@ CURRENT=spelling bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" //tools/spelling:check_spelling_pedantic -- --mark check --target_root="$PWD" CURRENT=rustfmt +RUSTFMT_PRE="$(git diff | md5sum)" bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" @rules_rust//:rustfmt +RUSTFMT_POST="$(git diff | md5sum)" +if [[ "$RUSTFMT_PRE" != "$RUSTFMT_POST" ]]; then + echo "ERROR: rustfmt produced changes — Rust code is unformatted." >&2 + echo "Run: bazel run @rules_rust//:rustfmt" >&2 + false +fi CURRENT=check_format bazel "${BAZEL_STARTUP_OPTIONS[@]}" run "${BAZEL_BUILD_OPTIONS[@]}" //tools/code_format:check_format -- fix --fail_on_diff From 463a07bb97b1ade3dfbddfde73481921090526f8 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Wed, 6 May 2026 08:28:32 -0400 Subject: [PATCH 231/750] Remove duplicate entries in bazelrc files (#44884) Signed-off-by: Jonh Wendell --- .bazelrc | 1 - bazel/tests/external/.bazelrc | 1 - 2 files changed, 2 deletions(-) diff --git a/.bazelrc b/.bazelrc index 653ade7a500e6..69fefca40a8bb 100644 --- a/.bazelrc +++ b/.bazelrc @@ -143,7 +143,6 @@ build:gcc --cxxopt=-Wno-nonnull-compare build:gcc --cxxopt=-Wno-trigraphs build:gcc --linkopt=-fuse-ld=gold --host_linkopt=-fuse-ld=gold build:gcc --host_platform=@envoy//bazel/rbe/toolchains:rbe_linux_gcc_platform -build:gcc --linkopt=-fuse-ld=gold --host_linkopt=-fuse-ld=gold build:gcc --action_env=BAZEL_LINKOPTS=-lm:-fuse-ld=gold # libc++ - default for clang diff --git a/bazel/tests/external/.bazelrc b/bazel/tests/external/.bazelrc index 76c6576862305..e9d344297f6a0 100644 --- a/bazel/tests/external/.bazelrc +++ b/bazel/tests/external/.bazelrc @@ -100,7 +100,6 @@ common:engflow-common --remote_cache_compression # Recover from transient gRPC failures (e.g. UNAVAILABLE: io exception) talking to engflow. common:engflow-common --remote_retries=10 common:engflow-common --remote_retry_max_delay=60s -common:engflow-common --experimental_remote_cache_eviction_retries=5 # Recover when blobs are evicted from the CAS mid-build (common during patch releases). common:engflow-common --experimental_remote_cache_eviction_retries=5 From bc7722a8639125f47e060a3f1237d5ef3aabb6f5 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 May 2026 13:43:45 +0100 Subject: [PATCH 232/750] ci: Fix CVE ignore filter (#44888) Signed-off-by: Ryan Northey --- tools/dependency/cve_utils.jq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dependency/cve_utils.jq b/tools/dependency/cve_utils.jq index bd8192ad4a295..c46cb65d1331e 100644 --- a/tools/dependency/cve_utils.jq +++ b/tools/dependency/cve_utils.jq @@ -129,7 +129,7 @@ def iterate_cves(cves; deps; ignored): . | [cves[] | . as $cve - | select(ignored | index($cve.id) | not) + | select(ignored | index($cve.cve.id) | not) | [.cve.configurations[]?.nodes[]?.cpeMatch[]? | parse_cve_cpe(.; $cve)] | matching_deps(.; deps) From fc3c4f5e3b9a621f3add595318b92e0bc53a367f Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 May 2026 13:52:09 +0100 Subject: [PATCH 233/750] tools/dependency: Prune stale ignored_cves entries from cve.yaml (#44889) Signed-off-by: Ryan Northey --- tools/dependency/cve.yaml | 48 --------------------------------------- 1 file changed, 48 deletions(-) diff --git a/tools/dependency/cve.yaml b/tools/dependency/cve.yaml index b555cee92dabf..0461ad651aeca 100644 --- a/tools/dependency/cve.yaml +++ b/tools/dependency/cve.yaml @@ -4,53 +4,5 @@ start_year: 2018 # These CVEs are false positives for the match heuristics. An explanation is # required when adding a new entry to this list as a comment. ignored_cves: -# Node.js issue unrelated to http-parser (napi_ API implementation). -- CVE-2020-8174 -# Node.js HTTP desync attack. Request smuggling due to CR and hyphen -# conflation in llhttp -# (https://github.com/nodejs/llhttp/commit/9d9da1d0f18599ceddd8f484df5a5ad694d23361). -# This was a result of using llparses toLowerUnsafe() for header keys. -# http-parser uses a TOKEN method that doesnt have the same issue for -# header fields. -- CVE-2020-8201 -# Node.js issue unrelated to http-parser. This is a DoS due to a lack of -# request/connection timeouts, see -# https://github.com/nodejs/node/commit/753f3b247a. -- CVE-2020-8251 -# Node.js issue unrelated to http-parser (libuv). -- CVE-2020-8252 -# Node.js issue rooted in a c-ares bug. Does not appear to affect -# http-parser or our use of c-ares, c-ares has been bumped regardless. -- CVE-2020-8277 -# Node.js issue unrelated to http-parser, see -# https://github.com/mhart/StringStream/issues/7. -- CVE-2018-21270 -# Node.js issue unrelated to http-parser (Node TLS). -- CVE-2020-8265 -# Node.js request smuggling. -# https://github.com/envoyproxy/envoy/pull/14686 validates that this does -# not apply to Envoy. -- CVE-2020-8287 -# Envoy is operating post Brotli 1.0.9 release, so not affected by this. -- CVE-2020-8927 -# Node.js issue unrelated to http-parser (*). -- CVE-2021-22883 -- CVE-2021-22884 -# Node.js issues unrelated to http-parser. -# See https://nvd.nist.gov/vuln/detail/CVE-2021-22918 -# See https://nvd.nist.gov/vuln/detail/CVE-2021-22921 -# See https://nvd.nist.gov/vuln/detail/CVE-2021-22931 -# See https://nvd.nist.gov/vuln/detail/CVE-2021-22939 -# See https://nvd.nist.gov/vuln/detail/CVE-2021-22940 -- CVE-2021-22918 -- CVE-2021-22921 -- CVE-2021-22930 -- CVE-2021-22931 -- CVE-2021-22939 -- CVE-2021-22940 -- CVE-2021-43803 -# This CVE assumes an untrusted client that can affect a shared connection between an intervening proxy and -# gRPC server, and does not affect Envoy directly -- CVE-2023-32732 # Patch applied https://github.com/envoyproxy/envoy/pull/44565 - CVE-2026-27135 From 68b5a0b3853e849dda7e9d9862a19e9adf8689e9 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 May 2026 14:47:14 +0100 Subject: [PATCH 234/750] ci/codeql: Fix scheduler to actually run daily and add dispatch (#44891) Signed-off-by: Ryan Northey --- .github/workflows/codeql-daily.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index aa42cc9cfa2bf..973147b8e50c1 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -5,7 +5,8 @@ permissions: on: schedule: - - cron: '0 12 * * 4' + - cron: '0 12 * * *' + workflow_dispatch: concurrency: group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} From a160c303ce447d6035a4565b7f505751b76e6d11 Mon Sep 17 00:00:00 2001 From: Ethan Truong Date: Wed, 6 May 2026 13:40:25 -0400 Subject: [PATCH 235/750] Fix double free in experimental uring (#44740) Added test DoubleFreeOnSocketCloseDuringInjectedCompletion to prevent regression of a double free. Signed-off-by: Ethan Truong --- source/common/io/io_uring_impl.cc | 9 +--- test/common/io/io_uring_impl_test.cc | 3 ++ test/common/io/io_uring_worker_impl_test.cc | 49 +++++++++++++++++++++ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/source/common/io/io_uring_impl.cc b/source/common/io/io_uring_impl.cc index 8096646c3fff7..411955c756ae9 100644 --- a/source/common/io/io_uring_impl.cc +++ b/source/common/io/io_uring_impl.cc @@ -79,14 +79,9 @@ void IoUringImpl::forEveryCompletion(const CompletionCb& completion_cb) { // long. // Iterate the injected completion. while (!injected_completions_.empty()) { - auto& completion = injected_completions_.front(); - completion_cb(completion.user_data_, completion.result_, true); - // The socket may closed in the completion_cb and all the related completions are - // removed. - if (injected_completions_.empty()) { - break; - } + auto completion = injected_completions_.front(); injected_completions_.pop_front(); + completion_cb(completion.user_data_, completion.result_, true); } } diff --git a/test/common/io/io_uring_impl_test.cc b/test/common/io/io_uring_impl_test.cc index 4e2880fb84725..64fe888494071 100644 --- a/test/common/io/io_uring_impl_test.cc +++ b/test/common/io/io_uring_impl_test.cc @@ -257,6 +257,9 @@ TEST_F(IoUringImplTest, NestRemoveInjectCompletion) { EXPECT_EQ(-11, res); } else { io_uring_->removeInjectedCompletion(fd2); + EXPECT_EQ(2, data2); + // Simulate deletion in IoUringWorkerImpl + delete user_data; EXPECT_EQ(-1, data2); } completions_nr++; diff --git a/test/common/io/io_uring_worker_impl_test.cc b/test/common/io/io_uring_worker_impl_test.cc index 37c8ac72c464a..6c10e8d70f535 100644 --- a/test/common/io/io_uring_worker_impl_test.cc +++ b/test/common/io/io_uring_worker_impl_test.cc @@ -693,6 +693,55 @@ TEST(IoUringWorkerImplTest, NoEnableReadOnConnectError) { delete static_cast(connect_req); } +class ReproSocket : public IoUringSocketEntry { +public: + ReproSocket(os_fd_t fd, IoUringWorkerImpl& parent) + : IoUringSocketEntry(fd, parent, [](uint32_t) { return absl::OkStatus(); }, false) {} + + void onRead(Request* req, int32_t result, bool injected) override { + IoUringSocketEntry::onRead(req, result, injected); + if (injected) { + cleanup(); + } + } + + void write(Buffer::Instance&) override {} + uint64_t write(const Buffer::RawSlice*, uint64_t) override { return 0; } + void shutdown(int) override {} +}; + +class IoUringWorkerRepro : public IoUringWorkerImpl { +public: + using IoUringWorkerImpl::IoUringWorkerImpl; + IoUringSocket& addReproSocket(os_fd_t fd) { + return addSocket(std::make_unique(fd, *this)); + } +}; + +TEST(IoUringWorkerImplTest, DoubleFreeOnSocketCloseDuringInjectedCompletion) { + if (!isIoUringSupported()) { + GTEST_SKIP() << "io_uring not supported on this kernel"; + } + + Event::MockDispatcher dispatcher; + auto io_uring = std::make_unique(64, false); + Event::FileReadyCb file_event_callback; + EXPECT_CALL(dispatcher, createFileEvent_(_, _, _, _)) + .WillOnce(testing::DoAll(testing::SaveArg<1>(&file_event_callback), + testing::ReturnNew>())); + + IoUringWorkerRepro worker(std::move(io_uring), 8192, 1000, dispatcher); + os_fd_t fd = 1; + auto& socket = worker.addReproSocket(fd); + + worker.injectCompletion(socket, Request::RequestType::Read, 0); + + EXPECT_CALL(dispatcher, deferredDelete_(testing::_)); + file_event_callback(Event::FileReadyType::Read).IgnoreError(); + + EXPECT_CALL(dispatcher, clearDeferredDeleteList()); +} + } // namespace } // namespace Io } // namespace Envoy From 32849ad9411fced2d8d1619c85fba7c25f7d69d3 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 May 2026 19:04:55 +0100 Subject: [PATCH 236/750] github/ci: Bump toolshed actions -> v0.4.12 (#44902) Signed-off-by: Ryan Northey --- .github/workflows/_check_coverage.yml | 4 +-- .github/workflows/_cve_fetch.yml | 2 +- .github/workflows/_cve_scan.yml | 2 +- .github/workflows/_finish.yml | 8 ++--- .github/workflows/_load.yml | 10 +++--- .github/workflows/_load_env.yml | 8 ++--- .github/workflows/_precheck_publish.yml | 2 +- .github/workflows/_publish_release.yml | 4 +-- .../workflows/_publish_release_container.yml | 6 ++-- .github/workflows/_publish_verify.yml | 2 +- .github/workflows/_request.yml | 24 ++++++------- .github/workflows/_request_cache_bazel.yml | 10 +++--- .github/workflows/_request_cache_docker.yml | 10 +++--- .github/workflows/_request_checks.yml | 10 +++--- .github/workflows/_run.yml | 28 +++++++-------- .github/workflows/_upload_gcs.yml | 6 ++-- .github/workflows/codeql-daily.yml | 4 +-- .github/workflows/codeql-push.yml | 4 +-- .github/workflows/command.yml | 6 ++-- .github/workflows/envoy-dependency.yml | 18 +++++----- .github/workflows/envoy-release.yml | 34 +++++++++---------- .github/workflows/envoy-sync.yml | 4 +-- .github/workflows/mobile-android_build.yml | 12 +++---- .github/workflows/mobile-ios_build.yml | 4 +-- 24 files changed, 111 insertions(+), 111 deletions(-) diff --git a/.github/workflows/_check_coverage.yml b/.github/workflows/_check_coverage.yml index c1c841ba5df4d..c722c73df1c6c 100644 --- a/.github/workflows/_check_coverage.yml +++ b/.github/workflows/_check_coverage.yml @@ -58,7 +58,7 @@ jobs: upload-name: coverage upload-path: generated/coverage/html steps-post: | - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: output-path: generated/coverage/html/gcs-metadata.json input-format: yaml @@ -79,7 +79,7 @@ jobs: - target: fuzz_coverage name: Fuzz coverage steps-post: | - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: output-path: generated/fuzz_coverage/html/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_cve_fetch.yml b/.github/workflows/_cve_fetch.yml index 73421a8f03d7c..13c95bb755b35 100644 --- a/.github/workflows/_cve_fetch.yml +++ b/.github/workflows/_cve_fetch.yml @@ -35,7 +35,7 @@ jobs: else echo "weekly_run=false" >> $GITHUB_OUTPUT fi - - uses: envoyproxy/toolshed/actions/gcp/setup@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_cve_scan.yml b/.github/workflows/_cve_scan.yml index d89d99f7b5032..f08900578d5ae 100644 --- a/.github/workflows/_cve_scan.yml +++ b/.github/workflows/_cve_scan.yml @@ -28,7 +28,7 @@ jobs: id: vars run: | echo "cve-data-path=${{ inputs.cve-data-path }}" > $GITHUB_OUTPUT - - uses: envoyproxy/toolshed/actions/gcp/setup@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_finish.yml b/.github/workflows/_finish.yml index ed5289310e577..8b6b4d81928ee 100644 --- a/.github/workflows/_finish.yml +++ b/.github/workflows/_finish.yml @@ -36,7 +36,7 @@ jobs: actions: read contents: read steps: - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Incoming data id: needs with: @@ -87,7 +87,7 @@ jobs: summary: "Check has finished", text: $text}}}} - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Print summary with: input: ${{ toJSON(steps.needs.outputs.value).summary-title }} @@ -95,13 +95,13 @@ jobs: "## \(.)" options: -Rr output-path: GITHUB_STEP_SUMMARY - - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Update check with: action: update diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index a38057a9d945e..d3ce020ab86de 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -102,7 +102,7 @@ jobs: # Handle any failure in triggering job # Remove any `checks` we dont care about # Prepare a check request - - uses: envoyproxy/toolshed/actions/github/env/load@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/env/load@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Load env id: data with: @@ -113,13 +113,13 @@ jobs: GH_TOKEN: ${{ github.token }} # Update the check - - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Update check if: ${{ fromJSON(steps.data.outputs.data).data.check.action == 'RUN' }} with: @@ -127,7 +127,7 @@ jobs: checks: ${{ toJSON(fromJSON(steps.data.outputs.data).checks) }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Print request summary with: input: | @@ -147,7 +147,7 @@ jobs: | $summary.summary as $summary | "${{ inputs.template-request-summary }}" - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: request-output name: Load request with: diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index 211f152d30184..a8036596341fa 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -62,18 +62,18 @@ jobs: request: ${{ steps.env.outputs.data }} trusted: true steps: - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout name: Checkout Envoy repository - name: Generate environment variables - uses: envoyproxy/toolshed/actions/envoy/ci/env@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/envoy/ci/env@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: env with: branch-name: ${{ inputs.branch-name }} @@ -85,7 +85,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/env/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} diff --git a/.github/workflows/_precheck_publish.yml b/.github/workflows/_precheck_publish.yml index 625028654837b..76429979f0f8b 100644 --- a/.github/workflows/_precheck_publish.yml +++ b/.github/workflows/_precheck_publish.yml @@ -85,7 +85,7 @@ jobs: upload-name: docs upload-path: generated/docs steps-post: | - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: output-path: generated/docs/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_publish_release.yml b/.github/workflows/_publish_release.yml index 1ee24b6c8ac90..ade83584a840f 100644 --- a/.github/workflows/_publish_release.yml +++ b/.github/workflows/_publish_release.yml @@ -137,12 +137,12 @@ jobs: needs: - release steps: - - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/actions/dispatch@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/dispatch@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: ref: main repository: ${{ fromJSON(inputs.request).request.version.dev && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} diff --git a/.github/workflows/_publish_release_container.yml b/.github/workflows/_publish_release_container.yml index b3d4afb63c615..6ac51cb5be5ba 100644 --- a/.github/workflows/_publish_release_container.yml +++ b/.github/workflows/_publish_release_container.yml @@ -56,7 +56,7 @@ jobs: - name: Generate manifest configuration (dev) id: dev-config if: ${{ inputs.dev && inputs.target-branch == 'main' }} - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: input-format: yaml filter: >- @@ -135,7 +135,7 @@ jobs: - tools-dev-${{ github.sha }} - name: Generate manifest configuration (release) - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: release-config if: ${{ ! inputs.dev || ! inputs.target-branch != 'main' }} with: @@ -225,7 +225,7 @@ jobs: - tools-v${{ inputs.version-major }}.${{ inputs.version-minor }}-latest - name: Collect and push OCI artifacts - uses: envoyproxy/toolshed/actions/oci/collector@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/oci/collector@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: artifacts-pattern: oci.* manifest-config: ${{ steps.dev-config.outputs.value || steps.release-config.outputs.value }} diff --git a/.github/workflows/_publish_verify.yml b/.github/workflows/_publish_verify.yml index 1e8450654c12e..b45e7adc5f712 100644 --- a/.github/workflows/_publish_verify.yml +++ b/.github/workflows/_publish_verify.yml @@ -108,7 +108,7 @@ jobs: export NO_BUILD_SETUP=1 steps-pre: | - id: version-support - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: input: | version_major: ${{ fromJSON(inputs.request).request.version.major }} diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index a27043eb4e850..8d293dee26424 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -55,14 +55,14 @@ jobs: caches: ${{ steps.caches.outputs.value }} config: ${{ steps.config.outputs.config }} steps: - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout name: Checkout Envoy repository (requested) with: @@ -76,7 +76,7 @@ jobs: # *ALL* variables collected should be treated as untrusted and should be sanitized before # use - name: Generate environment variables from commit - uses: envoyproxy/toolshed/actions/envoy/ci/request@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/envoy/ci/request@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: env with: branch-name: ${{ steps.checkout.outputs.branch-name }} @@ -87,7 +87,7 @@ jobs: vars: ${{ toJSON(vars) }} working-directory: requested - - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -95,7 +95,7 @@ jobs: config: | fetch-depth: 1 path: target - - uses: envoyproxy/toolshed/actions/hashfiles@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/hashfiles@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: bazel-cache-hash name: Bazel cache hash with: @@ -104,7 +104,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/env/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} @@ -120,28 +120,28 @@ jobs: target-branch: ${{ fromJSON(steps.env.outputs.data).request.target-branch }} - id: cache-id-bazel-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-arm64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-arm64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-docs-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-docs-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-external-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-external-x64 wf-path: .github/workflows/request.yml - name: Environment data - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: data with: input: | @@ -189,7 +189,7 @@ jobs: path: /tmp/cache key: ${{ fromJSON(steps.data.outputs.value).request.build-image.default }}-arm64 - name: Caches - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: caches with: input-format: yaml diff --git a/.github/workflows/_request_cache_bazel.yml b/.github/workflows/_request_cache_bazel.yml index 7dac1a693413c..6237eeb0091df 100644 --- a/.github/workflows/_request_cache_bazel.yml +++ b/.github/workflows/_request_cache_bazel.yml @@ -59,7 +59,7 @@ jobs: && ! fromJSON(inputs.caches).bazel[format('{0}-{1}', inputs.output-base, inputs.arch)]) }} steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: mounts: | - src: /mnt/workspace @@ -73,9 +73,9 @@ jobs: target: /build chown: "runner:docker" - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: inputs.arch == 'x64' && github.event.repository.private - - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -83,14 +83,14 @@ jobs: config: | fetch-depth: 1 - - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/cache/prime@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/cache/prime@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: bazel-cache name: Prime Bazel cache with: diff --git a/.github/workflows/_request_cache_docker.yml b/.github/workflows/_request_cache_docker.yml index f2701e7603434..3fd5228e9feb5 100644 --- a/.github/workflows/_request_cache_docker.yml +++ b/.github/workflows/_request_cache_docker.yml @@ -39,7 +39,7 @@ on: # For a job that does, you can restore with something like: # # steps: -# - uses: envoyproxy/toolshed/actions/docker/cache/restore@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 +# - uses: envoyproxy/toolshed/actions/docker/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 # with: # key: "${{ needs.env.outputs.build-image }}" # @@ -51,13 +51,13 @@ jobs: name: "[${{ inputs.arch }}] Prime Docker cache" if: ${{ ! fromJSON(inputs.caches).docker[inputs.arch] }} steps: - - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/docker/cache/prime@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/docker/cache/prime@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: docker name: Prime Docker cache (${{ inputs.image-tag }}${{ inputs.cache-suffix }}) with: @@ -65,7 +65,7 @@ jobs: key-suffix: ${{ inputs.cache-suffix }} lock-token: ${{ steps.appauth.outputs.token }} lock-repository: ${{ inputs.lock-repository }} - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: data name: Cache data with: @@ -73,7 +73,7 @@ jobs: input: | cached: ${{ steps.docker.outputs.cached }} key: ${{ inputs.image-tag }}${{ inputs.cache-suffix }} - - uses: envoyproxy/toolshed/actions/json/table@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/json/table@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Summary with: json: ${{ steps.data.outputs.value }} diff --git a/.github/workflows/_request_checks.yml b/.github/workflows/_request_checks.yml index 77d06ff1085ae..55e894fcd1764 100644 --- a/.github/workflows/_request_checks.yml +++ b/.github/workflows/_request_checks.yml @@ -55,7 +55,7 @@ jobs: runs-on: ${{ fromJSON(inputs.env).config.ci.agent-ubuntu }} name: Start checks steps: - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: check-config name: Prepare check data with: @@ -78,13 +78,13 @@ jobs: | .skipped.output.summary = "${{ inputs.skipped-summary }}" | .skipped.output.text = "" - - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Start checks id: checks with: @@ -95,7 +95,7 @@ jobs: ${{ fromJSON(inputs.env).summary.summary }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/json/table@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/json/table@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Summary with: collapse-open: true @@ -119,7 +119,7 @@ jobs: output-path: GITHUB_STEP_SUMMARY title: Checks started/skipped - - uses: envoyproxy/toolshed/actions/github/env/save@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/env/save@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Save env id: data with: diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index a0f01c72f9c54..64a8e75a55497 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -148,7 +148,7 @@ on: summary-post: type: string default: | - - uses: envoyproxy/toolshed/actions/envoy/run/summary@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/envoy/run/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: context: %{{ inputs.context }} steps-pre: @@ -217,7 +217,7 @@ jobs: name: ${{ inputs.target-suffix && format('[{0}] ', inputs.target-suffix) || '' }}${{ inputs.command }} ${{ inputs.target }} timeout-minutes: ${{ inputs.timeout-minutes }} steps: - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: started name: Create timestamp with: @@ -225,7 +225,7 @@ jobs: filter: | now # This controls which input vars are exposed to the run action (and related steps) - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Context id: context with: @@ -258,14 +258,14 @@ jobs: fi echo "mnt-available=$MNT_AVAILABLE" >> "$GITHUB_OUTPUT" id: disk - - uses: envoyproxy/toolshed/actions/github/remnt@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/remnt@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: steps.disk.outputs.should-remnt == 'true' - - uses: envoyproxy/toolshed/actions/bind-mounts@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: inputs.bind-mount && steps.disk.outputs.mnt-available == 'true' with: mounts: ${{ inputs.bind-mounts }} - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: inputs.diskspace-hack || steps.disk.outputs.mnt-available != 'true' with: to_remove: ${{ inputs.diskspace-hack-paths }} @@ -273,7 +273,7 @@ jobs: mount df -h - - uses: envoyproxy/toolshed/actions/bson@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/bson@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Configure Docker if: runner.os == 'Linux' with: @@ -289,7 +289,7 @@ jobs: | "${{ inputs.template-docker-configure }}" # Caches - - uses: envoyproxy/toolshed/actions/cache/restore@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: >- fromJSON(inputs.bazel-cache) name: >- @@ -330,12 +330,12 @@ jobs: key: ${{ inputs.cache-build-image }}${{ inputs.cache-build-image-key-suffix }} - if: ${{ inputs.cache-build-image && steps.cache-lookup.outputs.cache-hit == 'true' }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/actions/docker/cache/restore@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/docker/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: image-tag: ${{ inputs.cache-build-image }} key-suffix: ${{ inputs.cache-build-image-key-suffix }} - - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: appauth name: Appauth if: ${{ inputs.trusted }} @@ -346,7 +346,7 @@ jobs: # - the workaround is to allow the token to be passed through. token: ${{ github.token }} token-ok: true - - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout name: Checkout Envoy repository with: @@ -362,7 +362,7 @@ jobs: token: ${{ inputs.trusted && steps.appauth.outputs.token || github.token }} # This is currently only use by mobile-docs and can be removed once they are updated to the newer website - - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout-extra name: Checkout extra repository (for publishing) if: ${{ inputs.checkout-extra }} @@ -371,7 +371,7 @@ jobs: ssh-key: ${{ inputs.trusted && inputs.ssh-key-extra || '' }} - name: Import GPG key - uses: envoyproxy/toolshed/actions/gpg/import@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/gpg/import@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: ${{ inputs.import-gpg }} with: key: ${{ secrets.gpg-key }} @@ -393,7 +393,7 @@ jobs: # NOTE: This is where untrusted code can be run!!! # Only post-failure notification steps (which don't use any repo code) should follow this step. - - uses: envoyproxy/toolshed/actions/github/run@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: args: ${{ inputs.args != '--' && inputs.args || inputs.target }} diff --git a/.github/workflows/_upload_gcs.yml b/.github/workflows/_upload_gcs.yml index c35e4c1614039..556c3ec314567 100644 --- a/.github/workflows/_upload_gcs.yml +++ b/.github/workflows/_upload_gcs.yml @@ -29,17 +29,17 @@ jobs: with: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} - - uses: envoyproxy/toolshed/actions/jq@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: metadata with: input: ${{ matrix.artifact }}/gcs-metadata.json input-format: json-path - run: | rm -rf ${{ matrix.artifact }}/gcs-metadata.json - - uses: envoyproxy/toolshed/actions/gcp/setup@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: key: ${{ secrets.gcp-key }} - - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: bucket: ${{ fromJSON(steps.metadata.outputs.value).bucket }} path: ${{ matrix.artifact }} diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 973147b8e50c1..5baf6be36d540 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -28,7 +28,7 @@ jobs: steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: | ! github.event.repository.private with: @@ -43,7 +43,7 @@ jobs: if: | env.BUILD_TARGETS != '' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 7fcb79b280eb3..932cb66523f26 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-22.04 if: github.repository == 'envoyproxy/envoy' steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: ! github.event.repository.private with: mounts: | @@ -64,7 +64,7 @@ jobs: if: | env.CPP_CHANGED == 'true' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/command.yml b/.github/workflows/command.yml index 13e387799810c..bded5e0902aaa 100644 --- a/.github/workflows/command.yml +++ b/.github/workflows/command.yml @@ -28,7 +28,7 @@ jobs: && github.actor != 'dependabot[bot]' }} steps: - - uses: envoyproxy/toolshed/actions/github/command@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/command@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Parse command from comment id: command with: @@ -37,14 +37,14 @@ jobs: ^/(retest) # /retest - - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: ${{ steps.command.outputs.command == 'retest' }} id: appauth-retest name: Appauth (retest) with: key: ${{ secrets.ENVOY_CI_APP_KEY }} app_id: ${{ secrets.ENVOY_CI_APP_ID }} - - uses: envoyproxy/toolshed/actions/retest@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/retest@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: ${{ steps.command.outputs.command == 'retest' }} name: Retest with: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 19e40232f46de..a9aea749b90fe 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -53,16 +53,16 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/bson@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/bson@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: update name: Update dependency (${{ inputs.dependency }}) with: @@ -97,13 +97,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/actions/upload/diff@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/upload/diff@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.update.outputs.output }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/actions/github/pr@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: base: main body: | @@ -134,11 +134,11 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout name: Checkout Envoy repository with: @@ -184,7 +184,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/actions/docker/shas@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/docker/shas@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: images: | sha-ci: docker.io/envoyproxy/envoy-build:ci-${{ steps.build-tools.outputs.tag }} @@ -222,7 +222,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 3df9692d679bf..319eae3a9ce01 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -60,14 +60,14 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} @@ -88,10 +88,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/actions/email/validate@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/email/validate@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/actions/github/run@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Create release with: source: | @@ -116,7 +116,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: base: ${{ github.ref_name }} commit: false @@ -142,19 +142,19 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Re-open branch with: command: >- @@ -169,7 +169,7 @@ jobs: name: Dev version id: dev - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: base: ${{ github.ref_name }} commit: false @@ -193,20 +193,20 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Sync version histories with: command: >- @@ -216,7 +216,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: append-commit-message: true base: ${{ github.ref_name }} @@ -243,13 +243,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: config: | fetch-depth: 0 @@ -279,13 +279,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - name: Checkout repository - uses: envoyproxy/toolshed/actions/github/checkout@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index dcd5e701e06c9..887d1b2d94c1d 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -43,13 +43,13 @@ jobs: echo "skip=true" >> "$GITHUB_OUTPUT" fi - if: steps.tip.outputs.skip != 'true' - uses: envoyproxy/toolshed/actions/appauth@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - if: steps.tip.outputs.skip != 'true' - uses: envoyproxy/toolshed/actions/dispatch@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + uses: envoyproxy/toolshed/actions/dispatch@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index 230822d99d8cf..ffb3887aa9d43 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -103,9 +103,9 @@ jobs: target: kotlin-hello-world runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: apk: bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity @@ -146,7 +146,7 @@ jobs: target: ${{ matrix.target }} runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 steps-post: ${{ matrix.steps-post }} timeout-minutes: 50 trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} @@ -157,7 +157,7 @@ jobs: include: - name: java-hello-world steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: apk: bazel-bin/examples/java/hello_world/hello_envoy.apk app: io.envoyproxy.envoymobile.helloenvoy/.MainActivity @@ -180,7 +180,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/baseline:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: apk: bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity @@ -197,7 +197,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/experimental:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: apk: bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 4e7c71ef93928..ca964cf26616c 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -101,7 +101,7 @@ jobs: # source ./ci/mac_ci_setup.sh # bazel shutdown # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} @@ -146,7 +146,7 @@ jobs: # source: | # source ./ci/mac_ci_setup.sh # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@3c6fada5be38699cba49c9883d731ebe503d0b9d # v0.4.10 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} From f5de1e3b91e3b92a47df8e6637866e465f7f30c8 Mon Sep 17 00:00:00 2001 From: Ethan Truong Date: Wed, 6 May 2026 14:41:51 -0400 Subject: [PATCH 237/750] Update CONTRIBUTING.md (#44901) Forbid changing default behaviors on ext_* protocols Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Ethan Truong --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d3ce7e0018d63..4db90c55f8d05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -131,6 +131,7 @@ versioning guidelines: [envoy-announce](https://groups.google.com/forum/#!forum/envoy-announce) email list but by default it is expected the multi-phase warn-by-default/fail-by-default is sufficient to warn users to move away from deprecated features. +* Given their criticality, changing the default behavior of the ext_authz and ext_proc protocols is strictly forbidden. # Submitting a PR From 24b11c3d3575206f6ab3ddb0e7aea4d2c6701c67 Mon Sep 17 00:00:00 2001 From: aakugan <164153358+aakugan@users.noreply.github.com> Date: Thu, 7 May 2026 00:48:36 +0530 Subject: [PATCH 238/750] reverse_tunnels: skip async close in favor of connectionImpl's close (#44855) ## Commit Message Simplify RCConnectionWrapper::shutdown() ## Additional Description Skip the explicit async close via `connection_->close(FlushWrite)` and defer-delete the connection instead. The base `ConnectionImpl` destructor performs `close(NoFlush)`, which is sufficient. This prevents use-after-free when the HTTP/1 codec parses a response after the connection is already being torn down. ## Testing Unit tests. Signed-off-by: aakugan --- .../rc_connection_wrapper.cc | 19 +----- .../rc_connection_wrapper_test.cc | 62 ++++++++++++++----- .../reverse_connection_io_handle_test.cc | 3 + 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc index 985c5737027d7..c78c1bf86e8de 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc @@ -246,24 +246,7 @@ void RCConnectionWrapper::shutdown() { ENVOY_LOG(debug, "RCConnectionWrapper: Shutting down connection ID: {}, state: {}", connection_id, static_cast(state)); - // Remove connection callbacks first to prevent recursive calls during shutdown. - if (state != Network::Connection::State::Closed) { - connection_->removeConnectionCallbacks(*this); - ENVOY_LOG(debug, "Connection callbacks removed"); - } - - // Close the connection if it's still open. - state = connection_->state(); - if (state == Network::Connection::State::Open) { - ENVOY_LOG(debug, "Closing open connection gracefully"); - connection_->close(Network::ConnectionCloseType::FlushWrite); - } else if (state == Network::Connection::State::Closing) { - ENVOY_LOG(debug, "Connection already closing"); - } else { - ENVOY_LOG(debug, "Connection already closed"); - } - - // Clear the connection pointer after shutdown. + connection_->removeConnectionCallbacks(*this); connection_.reset(); ENVOY_LOG(debug, "RCConnectionWrapper: Connection cleared after shutdown"); ENVOY_LOG(debug, "RCConnectionWrapper: Shutdown completed"); diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc index a315fbd7c2395..06f07c7cd7fd8 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc @@ -32,6 +32,14 @@ namespace ReverseConnection { // RCConnectionWrapper Tests. +std::unique_ptr> +getDeletableConn(Event::Dispatcher& dispatcher) { + auto mock_connection = std::make_unique>(); + EXPECT_CALL(*mock_connection, dispatcher()).WillRepeatedly(ReturnRef(dispatcher)); + + return mock_connection; +} + class RCConnectionWrapperTest : public testing::Test { protected: void SetUp() override { @@ -52,6 +60,9 @@ class RCConnectionWrapperTest : public testing::Test { void TearDown() override { io_handle_.reset(); extension_.reset(); + while (dispatcher_.to_delete_.size()) { + dispatcher_.to_delete_.pop_front(); + } } void setupThreadLocalSlot() { @@ -127,7 +138,7 @@ class RCConnectionWrapperTest : public testing::Test { // Helper method to set up mock connection with proper socket expectations. std::unique_ptr> setupMockConnection() { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); // Create a mock socket for the connection. auto mock_socket_ptr = std::make_unique>(); @@ -179,7 +190,7 @@ class RCConnectionWrapperTest : public testing::Test { // Test RCConnectionWrapper::connect() method with HTTP/1.1 handshake success TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeSuccess) { // Create a mock connection. - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); // Set up connection expectations. EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); @@ -217,7 +228,7 @@ TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeSuccess) { // Test RCConnectionWrapper::connect() method with HTTP proxy (internal address) scenario. TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeWithHttpProxy) { // Create a mock connection. - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); // Set up connection expectations. EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); @@ -263,7 +274,7 @@ TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeWithHttpProxy) { // Test RCConnectionWrapper::connect() honors custom request paths. TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeWithCustomRequestPath) { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); EXPECT_CALL(*mock_connection, addReadFilter(_)); @@ -306,7 +317,7 @@ TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeWithCustomRequestPath) { // Test RCConnectionWrapper::connect() includes additional headers in the handshake request. TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeWithAdditionalHeaders) { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); EXPECT_CALL(*mock_connection, addReadFilter(_)); @@ -357,7 +368,7 @@ TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeWithAdditionalHeaders) { // Test that additional headers with OVERWRITE_IF_EXISTS_OR_ADD replace existing headers. TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeAdditionalHeadersOverwrite) { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); EXPECT_CALL(*mock_connection, addReadFilter(_)); @@ -406,7 +417,7 @@ TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeAdditionalHeadersOverwrite) // Test ADD_IF_ABSENT: header is added when absent, skipped when present. TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeAdditionalHeadersAddIfAbsent) { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); EXPECT_CALL(*mock_connection, addReadFilter(_)); @@ -463,7 +474,7 @@ TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeAdditionalHeadersAddIfAbsent // Test OVERWRITE_IF_EXISTS: overwrites existing header, does nothing for absent header. TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeAdditionalHeadersOverwriteIfExists) { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); EXPECT_CALL(*mock_connection, addReadFilter(_)); @@ -522,7 +533,7 @@ TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeAdditionalHeadersOverwriteIf // Test RCConnectionWrapper::connect() method with connection write failure. TEST_F(RCConnectionWrapperTest, ConnectHttpHandshakeWriteFailure) { // Create a mock connection that fails to write. - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); // Set up connection expectations. EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); @@ -1065,7 +1076,7 @@ TEST_F(RCConnectionWrapperTest, DecodeHeadersNonOk) { // Test dispatchHttp1 error path by initializing codec via connect() and // then feeding invalid bytes to the parser. TEST_F(RCConnectionWrapperTest, DispatchHttp1ErrorPath) { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); EXPECT_CALL(*mock_connection, addReadFilter(_)); @@ -1105,7 +1116,6 @@ TEST_F(RCConnectionWrapperTest, DestructorInvokesShutdown) { EXPECT_CALL(*mock_connection, removeConnectionCallbacks(_)); EXPECT_CALL(*mock_connection, state()).WillRepeatedly(Return(Network::Connection::State::Open)); - EXPECT_CALL(*mock_connection, close(Network::ConnectionCloseType::FlushWrite)); EXPECT_CALL(*mock_connection, id()).WillRepeatedly(Return(777)); { @@ -1206,7 +1216,6 @@ TEST_F(RCConnectionWrapperTest, Shutdown) { // Set up connection expectations for open connection. EXPECT_CALL(*mock_connection, removeConnectionCallbacks(_)); EXPECT_CALL(*mock_connection, state()).WillRepeatedly(Return(Network::Connection::State::Open)); - EXPECT_CALL(*mock_connection, close(Network::ConnectionCloseType::FlushWrite)); EXPECT_CALL(*mock_connection, id()).WillRepeatedly(Return(12345)); RCConnectionWrapper wrapper(*io_handle_, std::move(mock_connection), mock_host, "test-cluster"); @@ -1266,13 +1275,12 @@ TEST_F(RCConnectionWrapperTest, Shutdown) { } // Test 5: Multiple shutdown calls (should be safe) { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); auto mock_host = std::make_shared>(); // Set up connection expectations. EXPECT_CALL(*mock_connection, removeConnectionCallbacks(_)); EXPECT_CALL(*mock_connection, state()).WillRepeatedly(Return(Network::Connection::State::Open)); - EXPECT_CALL(*mock_connection, close(Network::ConnectionCloseType::FlushWrite)); EXPECT_CALL(*mock_connection, id()).WillRepeatedly(Return(12348)); RCConnectionWrapper wrapper(*io_handle_, std::move(mock_connection), mock_host, "test-cluster"); @@ -1304,11 +1312,14 @@ class SimpleConnReadFilterTest : public testing::Test { *stats_scope_); // Use the created scope } - void TearDown() override { io_handle_.reset(); } + void TearDown() override { + io_handle_.reset(); + dispatcher_.clearDeferredDeleteList(); + } // Helper to create a mock RCConnectionWrapper. std::unique_ptr createMockWrapper() { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(dispatcher_); auto mock_host = std::make_shared>(); return std::make_unique(*io_handle_, std::move(mock_connection), mock_host, "test-cluster"); @@ -1323,6 +1334,7 @@ class SimpleConnReadFilterTest : public testing::Test { Stats::IsolatedStoreImpl stats_store_; Stats::ScopeSharedPtr stats_scope_; std::unique_ptr io_handle_; + NiceMock dispatcher_{"worker_0"}; }; TEST_F(SimpleConnReadFilterTest, OnDataWithNullParent) { @@ -1466,6 +1478,24 @@ TEST_F(RCConnectionWrapperTest, NoOpMethods) { wrapper.onMaxStreamsChanged(100); } +// Verify shutdown with already-null connection doesn't crash. +TEST_F(RCConnectionWrapperTest, ShutdownIsIdempotent) { + auto mock_connection = getDeletableConn(dispatcher_); + auto mock_host = std::make_shared>(); + EXPECT_CALL(*mock_connection, id()).WillRepeatedly(Return(999)); + EXPECT_CALL(*mock_connection, removeConnectionCallbacks(_)); + auto wrapper = std::make_unique(*io_handle_, std::move(mock_connection), + mock_host, "test-cluster"); + + wrapper->shutdown(); + ASSERT_EQ(wrapper->getConnection(), nullptr); + + wrapper->shutdown(); + ASSERT_EQ(wrapper->getConnection(), nullptr); + + wrapper.reset(); +} + } // namespace ReverseConnection } // namespace Bootstrap } // namespace Extensions diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc index 0014534275cec..ed3763718b194 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc @@ -73,6 +73,9 @@ class ReverseConnectionIOHandleTest : public testing::Test { void TearDown() override { io_handle_.reset(); + while (!dispatcher_.to_delete_.empty()) { + dispatcher_.to_delete_.pop_front(); + } extension_.reset(); socket_interface_.reset(); } From 09edfe80938da3a080aeabb65d28ae9c9c3ae3d9 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Wed, 6 May 2026 16:38:15 -0400 Subject: [PATCH 239/750] ci: use catch-errors to detect CI failures for Slack notifications (#44905) Use the toolshed action's existing `catch-errors` input to suppress CI step failures without blocking the overall job. When enabled, the step exits successfully but the real exit code is still available via `steps.ci-run.outputs.exit-code`, allowing the Slack notification step to detect failures and notify the appropriate channel. Signed-off-by: Jonh Wendell --- .github/workflows/_check_build.yml | 1 + .github/workflows/_run.yml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/_check_build.yml b/.github/workflows/_check_build.yml index a5ab7e25b3951..b44cf039426b2 100644 --- a/.github/workflows/_check_build.yml +++ b/.github/workflows/_check_build.yml @@ -42,6 +42,7 @@ jobs: Error: rbe: true request: ${{ inputs.request }} + catch-errors: ${{ matrix.catch-errors || false }} skip: ${{ matrix.skip != false && true || false }} slack-channel: ${{ matrix.slack-channel || '' }} target: ${{ matrix.target }} diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 64a8e75a55497..45f8d0d0aba11 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -394,6 +394,7 @@ jobs: # NOTE: This is where untrusted code can be run!!! # Only post-failure notification steps (which don't use any repo code) should follow this step. - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + id: ci-run name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: args: ${{ inputs.args != '--' && inputs.args || inputs.target }} @@ -450,7 +451,7 @@ jobs: ENVOY_PUBLISH_DRY_RUN: ${{ (fromJSON(inputs.request).request.version.dev || ! inputs.trusted) && 1 || '' }} - name: Notify Slack on failure - if: ${{ failure() && inputs.slack-channel != '' }} + if: ${{ steps.ci-run.outputs.exit-code != 0 && inputs.slack-channel != '' }} uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 env: JOB_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} From 3a4dd528d9122e03d744b4f50b48c7dba1b1727d Mon Sep 17 00:00:00 2001 From: Paul Ogilby Date: Wed, 6 May 2026 16:43:57 -0400 Subject: [PATCH 240/750] mobile: Add EnvoyClient-based httpx Transport implementation (#44191) Commit Message: Add EnvoyClient-based HTTPX transport implementation. Additional Description: This allows EnvoyClient to be used easily by existing users of HTTPX libraries. Risk Level: None; no usage yet. Testing: unit Docs Changes: to come in future PR. AI Usage: Substantially generated by Gemini; I understand and stand by the changes. --------- Signed-off-by: Paul Ogilby --- mobile/library/python/BUILD | 6 +- .../library/python/envoy_mobile/__init__.py | 8 +- .../envoy_mobile/async_client/executor.py | 27 +- .../envoy_mobile/async_client_transport.py | 209 ++++++++ .../python/envoy_mobile/httpx_utils.py | 72 +++ .../envoy_mobile/sync_client_transport.py | 195 +++++++ .../python/envoy_mobile/transport_factory.py | 78 +++ mobile/test/python/BUILD | 21 + mobile/test/python/test_httpx_transport.py | 241 +++++++++ mobile/test/python/test_transport_factory.py | 74 +++ mobile/tools/python/BUILD | 15 + mobile/tools/python/requirements.in | 7 + mobile/tools/python/requirements.txt | 500 +++++++++++++++++- 13 files changed, 1443 insertions(+), 10 deletions(-) create mode 100644 mobile/library/python/envoy_mobile/async_client_transport.py create mode 100644 mobile/library/python/envoy_mobile/httpx_utils.py create mode 100644 mobile/library/python/envoy_mobile/sync_client_transport.py create mode 100644 mobile/library/python/envoy_mobile/transport_factory.py create mode 100644 mobile/test/python/test_httpx_transport.py create mode 100644 mobile/test/python/test_transport_factory.py diff --git a/mobile/library/python/BUILD b/mobile/library/python/BUILD index bea61dd4cdf46..9b21450cd4341 100644 --- a/mobile/library/python/BUILD +++ b/mobile/library/python/BUILD @@ -1,4 +1,5 @@ load("@bazel_skylib//rules:common_settings.bzl", "string_flag") +load("@mobile_pip3//:requirements.bzl", "requirement") load("@pybind11_bazel//:build_defs.bzl", "pybind_extension", "pybind_library") load("@python_abi//:abi.bzl", "abi_tag", "python_tag") load("@rules_python//python:defs.bzl", "py_library") @@ -93,7 +94,10 @@ py_library( ], imports = ["."], visibility = ["//visibility:public"], - deps = [":async_client"], + deps = [ + ":async_client", + requirement("httpx"), + ], ) py_package( diff --git a/mobile/library/python/envoy_mobile/__init__.py b/mobile/library/python/envoy_mobile/__init__.py index eb3b1e4321756..1852ce03a19c1 100644 --- a/mobile/library/python/envoy_mobile/__init__.py +++ b/mobile/library/python/envoy_mobile/__init__.py @@ -1,4 +1,3 @@ -from .async_client.client import AsyncClient from .envoy_engine import ( Engine, EngineBuilder, @@ -11,9 +10,16 @@ StreamIntel, StreamPrototype, ) +from .async_client.client import AsyncClient +from .async_client_transport import AsyncEnvoyClientTransport +from .sync_client_transport import EnvoyClientTransport +from .transport_factory import EnvoyTransportFactory __all__ = [ "AsyncClient", + "AsyncEnvoyClientTransport", + "EnvoyClientTransport", + "EnvoyTransportFactory", "Engine", "EngineBuilder", "EnvoyError", diff --git a/mobile/library/python/envoy_mobile/async_client/executor.py b/mobile/library/python/envoy_mobile/async_client/executor.py index 83f32aaac53d6..7ae98cefe7c06 100644 --- a/mobile/library/python/envoy_mobile/async_client/executor.py +++ b/mobile/library/python/envoy_mobile/async_client/executor.py @@ -17,18 +17,39 @@ def wrap(self, fn: Func) -> Func: class AsyncioExecutor(Executor): + """Executor that schedules callbacks on an asyncio event loop.""" + def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: # Allow an explicit loop to be supplied (useful for testing). # If none is provided, attempt to grab the currently running loop. if loop is None: - loop = asyncio.get_running_loop() + try: + loop = asyncio.get_running_loop() + except RuntimeError: + # Fallback for when no loop is running in the current thread. + # In such cases, the executor must be explicitly initialized + # with the correct loop. + pass self.loop = loop def wrap(self, fn: Func) -> Func: + """Return a version of ``fn`` that is safe to call from any thread.""" + @functools.wraps(fn) - def wrapper(*args: Any, **kwargs: Any) -> None: # type: ignore[return-value] + def wrapper(*args: Any, **kwargs: Any) -> Any: + if self.loop is None: + # If we don't have a loop, we can't schedule the callback. + # In a real-world scenario, this might happen if the loop + # was closed or if it was never correctly initialized. + raise RuntimeError("AsyncioExecutor must be initialized with a running loop.") + + if self.loop.is_closed(): + return None + # ``call_soon_threadsafe`` will schedule the callable on the same - # loop that constructed the executor, making every callback "safe" + # loop that constructed the executor, making every callback "safe". + # Note: call_soon_threadsafe doesn't return the result of fn. self.loop.call_soon_threadsafe(fn, *args, **kwargs) + return None return cast(Func, wrapper) diff --git a/mobile/library/python/envoy_mobile/async_client_transport.py b/mobile/library/python/envoy_mobile/async_client_transport.py new file mode 100644 index 0000000000000..356c10ba19f0b --- /dev/null +++ b/mobile/library/python/envoy_mobile/async_client_transport.py @@ -0,0 +1,209 @@ +"""Async httpx transport for Envoy Mobile.""" + +import asyncio +from typing import AsyncIterable, Dict, List, Optional, Union + +import httpx +from . import envoy_engine +from .async_client.executor import AsyncioExecutor +from .httpx_utils import get_envoy_headers, map_envoy_error + + +class AsyncEnvoyStream(httpx.AsyncByteStream): + """An asynchronous byte stream that reads data from an Envoy stream.""" + + def __init__( + self, + stream: envoy_engine.Stream, + queue: asyncio.Queue, + stream_complete: asyncio.Event, + executor: AsyncioExecutor, + ) -> None: + self._stream = stream + self._queue = queue + self._stream_complete = stream_complete + self._executor = executor + self._closed = False + + async def __aiter__(self) -> AsyncIterable[bytes]: + try: + while True: + # Use explicit flow control to request more data from Envoy. + # We request a chunk and then wait for the queue to populate. + if not self._stream_complete.is_set(): + # Request up to 64KB at a time + self._stream.read_data(65536) + + # Wait for data or completion + item = await self._queue.get() + if item is None: # EOF + break + if isinstance(item, Exception): + raise item + + yield item + finally: + await self.aclose() + + async def aclose(self) -> None: + if not self._closed: + if not self._stream_complete.is_set(): + self._stream.cancel() + self._closed = True + + +class AsyncResponseHandler: + """Handles callbacks from the Envoy engine and pushes data to asyncio queues.""" + + def __init__(self, executor: AsyncioExecutor) -> None: + self.executor = executor + self.headers_future: asyncio.Future = asyncio.Future() + self.data_queue: asyncio.Queue = asyncio.Queue() + self.stream_complete = asyncio.Event() + self.status_code: Optional[int] = None + self.headers: Dict[str, Union[str, List[str]]] = {} + self.trailers: Dict[str, Union[str, List[str]]] = {} + + def on_headers( + self, + headers: Dict[str, Union[str, List[str]]], + end_stream: bool, + intel: envoy_engine.StreamIntel, + ) -> None: + status = headers.get(":status") + if status is not None: + try: + self.status_code = int(status[0] if isinstance(status, list) else status) + except (ValueError, IndexError): + pass + + for key, value in headers.items(): + if not key.startswith(":"): + self.headers[key] = ( + value[0] if isinstance(value, list) and len(value) == 1 else value + ) + + if not self.headers_future.done(): + self.headers_future.set_result(True) + + if end_stream: + self.data_queue.put_nowait(None) + self.stream_complete.set() + + def on_data( + self, + data: bytes, + length: int, + end_stream: bool, + intel: envoy_engine.StreamIntel, + ) -> None: + self.data_queue.put_nowait(data) + if end_stream: + self.data_queue.put_nowait(None) + + def on_trailers( + self, + trailers: Dict[str, Union[str, List[str]]], + intel: envoy_engine.StreamIntel, + ) -> None: + for key, value in trailers.items(): + self.trailers[key] = value[0] if isinstance(value, list) and len(value) == 1 else value + self.data_queue.put_nowait(None) + + def on_complete( + self, intel: envoy_engine.StreamIntel, final_intel: envoy_engine.FinalStreamIntel + ) -> None: + if not self.stream_complete.is_set(): + self.data_queue.put_nowait(None) + self.stream_complete.set() + + def on_error( + self, + error: envoy_engine.EnvoyError, + intel: envoy_engine.StreamIntel, + final_intel: envoy_engine.FinalStreamIntel, + ) -> None: + exc = map_envoy_error(error.error_code, error.message) + if not self.headers_future.done(): + self.headers_future.set_exception(exc) + self.data_queue.put_nowait(exc) + self.stream_complete.set() + + def on_cancel( + self, intel: envoy_engine.StreamIntel, final_intel: envoy_engine.FinalStreamIntel + ) -> None: + exc = httpx.RequestError("Request cancelled") + if not self.headers_future.done(): + self.headers_future.set_exception(exc) + self.data_queue.put_nowait(exc) + self.stream_complete.set() + + +class AsyncEnvoyClientTransport(httpx.AsyncBaseTransport): + """An asynchronous transport for httpx that uses Envoy Mobile.""" + + def __init__(self, engine: envoy_engine.Engine) -> None: + self._engine = engine + self._executor = AsyncioExecutor() + + async def handle_async_request(self, request: httpx.Request) -> httpx.Response: + # Map headers + timeout = request.extensions.get("timeout", {}).get("read") + envoy_headers = get_envoy_headers(request, timeout=timeout) + + # Create handler + handler = AsyncResponseHandler(self._executor) + + # Start stream + proto = self._engine.stream_client().new_stream_prototype() + stream = proto.start( + on_headers=self._executor.wrap(handler.on_headers), + on_data=self._executor.wrap(handler.on_data), + on_trailers=self._executor.wrap(handler.on_trailers), + on_complete=self._executor.wrap(handler.on_complete), + on_error=self._executor.wrap(handler.on_error), + on_cancel=self._executor.wrap(handler.on_cancel), + explicit_flow_control=True, + ) + + # --- Send Request --- + # + # In httpx, the request body is accessed via `request.stream`, which provides + # an asynchronous iterator over the body chunks. This is crucial for: + # 1. Memory Efficiency: We don't load the entire body into memory, which + # is essential for large file uploads. + # 2. Support for Generators: If the user provides a generator as the + # request content, we consume it one chunk at a time. + # + # Simplified Request Logic: + # 1. Always send headers first with `end_stream=False`. + # 2. Stream all chunks from `request.stream` with `end_stream=False`. + # 3. Finalize by sending an empty string with `end_stream=True` (via `stream.close(b"")`). + + # Start by sending the request headers. + # Note: These stream operations are synchronous in Python because they are + # non-blocking calls that interface directly with the Envoy Mobile C++ engine. + stream.send_headers(envoy_headers, False) + + # Iterate through the request stream and send all data chunks. + async for chunk in request.stream: + stream.send_data(chunk, False) + + # Finalize the request. Sending an empty string with `stream.close()` + # signals `end_stream=True` to Envoy, completing the request side of the stream. + stream.close(b"") + + # Wait for headers + try: + await handler.headers_future + except Exception: + stream.cancel() + raise + + return httpx.Response( + status_code=handler.status_code or 0, + headers=handler.headers, + stream=AsyncEnvoyStream( + stream, handler.data_queue, handler.stream_complete, self._executor + ), + ) diff --git a/mobile/library/python/envoy_mobile/httpx_utils.py b/mobile/library/python/envoy_mobile/httpx_utils.py new file mode 100644 index 0000000000000..715bcd27f6dc9 --- /dev/null +++ b/mobile/library/python/envoy_mobile/httpx_utils.py @@ -0,0 +1,72 @@ +"""Utilities for mapping httpx requests and responses to Envoy Mobile.""" + +from typing import Dict, List, Optional, Union +from urllib.parse import urlparse +import httpx +from .async_client.utils import ( + normalize_method, + normalize_headers, + normalize_timeout_to_ms, +) + + +def get_envoy_headers( + request: httpx.Request, + timeout: Optional[Union[int, float]] = None, +) -> Dict[str, Union[str, List[str]]]: + """Map an httpx.Request to Envoy-compatible headers. + + Args: + request: The httpx.Request object. + timeout: Optional request timeout in seconds. + + Returns: + A dictionary of Envoy-compatible headers, including pseudo-headers. + """ + method = normalize_method(request.method) + url = str(request.url) + parsed = urlparse(url) + + # Convert httpx.Headers to the dict format expected by normalize_headers + # httpx.Headers.items() provides (key, value) pairs. + headers_dict: Dict[str, str] = dict(request.headers.items()) + norm_headers = normalize_headers(headers_dict) + + header_dict: Dict[str, Union[str, List[str]]] = { + ":method": method, + ":scheme": request.url.scheme, + ":authority": request.url.netloc.decode("ascii"), + ":path": request.url.raw_path.decode("ascii"), + } + + # Add timeout header if specified + timeout_ms = normalize_timeout_to_ms(timeout) + if timeout_ms > 0: + header_dict["x-envoy-upstream-rq-timeout-ms"] = str(timeout_ms) + + # Add normalized user headers + for key, values in norm_headers.items(): + # Avoid overriding pseudo-headers + if key.startswith(":"): + continue + header_dict[key] = values if len(values) > 1 else values[0] + + return header_dict + + +def map_envoy_error(error_code: int, message: str) -> Exception: + """Map an Envoy error code to an appropriate httpx exception.""" + # Envoy error codes are defined in library/python/module_definition.cc + # ENVOY_STREAM_RESET = 1 + # ENVOY_CONNECTION_FAILURE = 2 + # ENVOY_BUFFER_LIMIT_EXCEEDED = 3 + # ENVOY_REQUEST_TIMEOUT = 4 + + if error_code == 2: # ConnectionFailure + return httpx.ConnectError(message) + elif error_code == 4: # RequestTimeout + return httpx.ReadTimeout(message) + elif error_code == 1: # StreamReset + return httpx.RemoteProtocolError(message) + + return httpx.RequestError(message) diff --git a/mobile/library/python/envoy_mobile/sync_client_transport.py b/mobile/library/python/envoy_mobile/sync_client_transport.py new file mode 100644 index 0000000000000..1d3ed3baaa49f --- /dev/null +++ b/mobile/library/python/envoy_mobile/sync_client_transport.py @@ -0,0 +1,195 @@ +"""Synchronous httpx transport for Envoy Mobile.""" + +import queue +import threading +from typing import Dict, Iterable, List, Optional, Union + +import httpx +from . import envoy_engine +from .httpx_utils import get_envoy_headers, map_envoy_error + + +class SyncEnvoyStream(httpx.SyncByteStream): + def __init__( + self, + stream: envoy_engine.Stream, + data_queue: queue.Queue, + stream_complete: threading.Event, + ) -> None: + self._stream = stream + self._queue = data_queue + self._stream_complete = stream_complete + self._closed = False + + def __iter__(self) -> Iterable[bytes]: + try: + while True: + # Use explicit flow control to request more data from Envoy. + if not self._stream_complete.is_set(): + # Request up to 64KB at a time + self._stream.read_data(65536) + + # Wait for data or completion + # Blocking wait for data. Terminal states push None or Exception to the queue. + item = self._queue.get() + + if item is None: # EOF + break + if isinstance(item, Exception): + raise item + + yield item + finally: + self.close() + + def close(self) -> None: + if not self._closed: + if not self._stream_complete.is_set(): + self._stream.cancel() + self._closed = True + + +class SyncResponseHandler: + def __init__(self) -> None: + self.headers_event = threading.Event() + self.data_queue: queue.Queue = queue.Queue() + self.stream_complete = threading.Event() + self.status_code: Optional[int] = None + self.headers: Dict[str, Union[str, List[str]]] = {} + self.trailers: Dict[str, Union[str, List[str]]] = {} + self.exception: Optional[Exception] = None + + def on_headers( + self, + headers: Dict[str, Union[str, List[str]]], + end_stream: bool, + intel: envoy_engine.StreamIntel, + ) -> None: + status = headers.get(":status") + if status is not None: + try: + self.status_code = int(status[0] if isinstance(status, list) else status) + except (ValueError, IndexError): + pass + + for key, value in headers.items(): + if not key.startswith(":"): + self.headers[key] = ( + value[0] if isinstance(value, list) and len(value) == 1 else value + ) + + self.headers_event.set() + + if end_stream: + self.data_queue.put(None) + self.stream_complete.set() + + def on_data( + self, + data: bytes, + length: int, + end_stream: bool, + intel: envoy_engine.StreamIntel, + ) -> None: + self.data_queue.put(data) + if end_stream: + self.data_queue.put(None) + + def on_trailers( + self, + trailers: Dict[str, Union[str, List[str]]], + intel: envoy_engine.StreamIntel, + ) -> None: + for key, value in trailers.items(): + self.trailers[key] = value[0] if isinstance(value, list) and len(value) == 1 else value + self.data_queue.put(None) + + def on_complete( + self, intel: envoy_engine.StreamIntel, final_intel: envoy_engine.FinalStreamIntel + ) -> None: + if not self.stream_complete.is_set(): + self.data_queue.put(None) + self.stream_complete.set() + + def on_error( + self, + error: envoy_engine.EnvoyError, + intel: envoy_engine.StreamIntel, + final_intel: envoy_engine.FinalStreamIntel, + ) -> None: + exc = map_envoy_error(error.error_code, error.message) + self.exception = exc + self.data_queue.put(exc) + self.headers_event.set() + self.stream_complete.set() + + def on_cancel( + self, intel: envoy_engine.StreamIntel, final_intel: envoy_engine.FinalStreamIntel + ) -> None: + exc = httpx.RequestError("Request cancelled") + self.exception = exc + self.data_queue.put(exc) + self.headers_event.set() + self.stream_complete.set() + + +class EnvoyClientTransport(httpx.BaseTransport): + def __init__(self, engine: envoy_engine.Engine) -> None: + self._engine = engine + + def handle_request(self, request: httpx.Request) -> httpx.Response: + # Map headers + timeout = request.extensions.get("timeout", {}).get("read") + envoy_headers = get_envoy_headers(request, timeout=timeout) + + # Create handler + handler = SyncResponseHandler() + + # Start stream + proto = self._engine.stream_client().new_stream_prototype() + stream = proto.start( + on_headers=handler.on_headers, + on_data=handler.on_data, + on_trailers=handler.on_trailers, + on_complete=handler.on_complete, + on_error=handler.on_error, + on_cancel=handler.on_cancel, + explicit_flow_control=True, + ) + + # --- Send Request --- + # + # In httpx, the request body is accessed via `request.stream`, which provides + # an iterator over the body chunks. This is crucial for: + # 1. Memory Efficiency: We don't load the entire body into memory, which + # is essential for large file uploads. + # 2. Support for Generators: If the user provides a generator as the + # request content, we consume it one chunk at a time. + # + # Simplified Request Logic: + # 1. Always send headers first with `end_stream=False`. + # 2. Stream all chunks from `request.stream` with `end_stream=False`. + # 3. Finalize by sending an empty string with `end_stream=True` (via `stream.close(b"")`). + + # Start by sending the request headers. + stream.send_headers(envoy_headers, False) + + # Iterate through the request stream and send all data chunks. + for chunk in request.stream: + stream.send_data(chunk, False) + + # Finalize the request. Sending an empty string with `stream.close()` + # signals `end_stream=True` to Envoy, completing the request side of the stream. + stream.close(b"") + + # Wait for headers + handler.headers_event.wait() + if handler.exception: + stream.cancel() + raise handler.exception + + return httpx.Response( + status_code=handler.status_code or 0, + headers=handler.headers, + stream=SyncEnvoyStream(stream, handler.data_queue, handler.stream_complete), + ) diff --git a/mobile/library/python/envoy_mobile/transport_factory.py b/mobile/library/python/envoy_mobile/transport_factory.py new file mode 100644 index 0000000000000..eeb5d32d6e47c --- /dev/null +++ b/mobile/library/python/envoy_mobile/transport_factory.py @@ -0,0 +1,78 @@ +"""Factory for creating Envoy Mobile httpx transports with a shared engine.""" + +import threading +from typing import Optional + +from . import envoy_engine +from .async_client_transport import AsyncEnvoyClientTransport +from .sync_client_transport import EnvoyClientTransport + + +class EnvoyTransportFactory: + """Factory for managing a single, shared Envoy Engine. + + Envoy Engines are expensive to initialize and should typically live for the + entire life of the process. This factory ensures that only one Engine is + created and shared across all httpx transports. + """ + + _engine: Optional[envoy_engine.Engine] = None + _lock = threading.Lock() + + @classmethod + def get_shared_engine( + cls, builder: Optional[envoy_engine.EngineBuilder] = None + ) -> envoy_engine.Engine: + """Get the shared Envoy Engine instance, initializing it if necessary. + + Args: + builder: An optional EngineBuilder to configure the engine before + it is started. This is only used if the engine hasn't been + initialized yet. + + Returns: + The shared envoy_engine.Engine instance. + """ + if cls._engine is not None: + return cls._engine + + with cls._lock: + if cls._engine is None: + # Use default builder if none provided + if builder is None: + builder = envoy_engine.EngineBuilder() + + # Build and start the engine + cls._engine = builder.build() + + return cls._engine + + @classmethod + def get_async_transport( + cls, builder: Optional[envoy_engine.EngineBuilder] = None + ) -> AsyncEnvoyClientTransport: + """Create an AsyncEnvoyClientTransport using the shared engine. + + Args: + builder: Optional EngineBuilder for engine initialization. + + Returns: + An AsyncEnvoyClientTransport instance. + """ + engine = cls.get_shared_engine(builder) + return AsyncEnvoyClientTransport(engine) + + @classmethod + def get_sync_transport( + cls, builder: Optional[envoy_engine.EngineBuilder] = None + ) -> EnvoyClientTransport: + """Create an EnvoyClientTransport using the shared engine. + + Args: + builder: Optional EngineBuilder for engine initialization. + + Returns: + An EnvoyClientTransport instance. + """ + engine = cls.get_shared_engine(builder) + return EnvoyClientTransport(engine) diff --git a/mobile/test/python/BUILD b/mobile/test/python/BUILD index fbd9f6a515098..b98ae038b009e 100644 --- a/mobile/test/python/BUILD +++ b/mobile/test/python/BUILD @@ -1,3 +1,4 @@ +load("@mobile_pip3//:requirements.bzl", "requirement") load("@rules_python//python:defs.bzl", "py_library", "py_test") licenses(["notice"]) # Apache 2 @@ -26,3 +27,23 @@ py_test( "//library/python:envoy_mobile_lib", ], ) + +py_test( + name = "httpx_transport_test", + srcs = ["test_httpx_transport.py"], + main = "test_httpx_transport.py", + deps = [ + "//library/python:envoy_mobile_lib", + requirement("httpx"), + ], +) + +py_test( + name = "transport_factory_test", + srcs = ["test_transport_factory.py"], + main = "test_transport_factory.py", + deps = [ + "//library/python:envoy_mobile_lib", + requirement("httpx"), + ], +) diff --git a/mobile/test/python/test_httpx_transport.py b/mobile/test/python/test_httpx_transport.py new file mode 100644 index 0000000000000..dbb4399a01bd8 --- /dev/null +++ b/mobile/test/python/test_httpx_transport.py @@ -0,0 +1,241 @@ +"""Unit tests for Envoy Client httpx transports. + +These tests use unittest.mock to simulate the Envoy Mobile C++ engine (envoy_engine). +We verify that the transports correctly map httpx requests to Envoy headers, +handle streaming request/response bodies, and propagate errors appropriately. +""" + +import asyncio +import sys +import unittest +from unittest.mock import MagicMock, patch, call + +# Mock envoy_engine before any imports that might use it. +# This is necessary because envoy_engine is a C++ extension module that +# may not be available in all test environments. +mock_envoy_engine = MagicMock() +# The code does `from .. import envoy_engine` from inside envoy_mobile.async_client_transport +# So it looks for `envoy_mobile.envoy_engine` +sys.modules["envoy_mobile.envoy_engine"] = mock_envoy_engine + +import httpx +from envoy_mobile import ( + AsyncEnvoyClientTransport, + EnvoyClientTransport, +) + + +# Define these for use in tests since we mocked the module. +class MockEnvoyError: + def __init__(self): + self.error_code = 0 + self.message = "" + + +mock_envoy_engine.EnvoyError = MockEnvoyError + + +class TestEnvoyClientTransport(unittest.TestCase): + """Tests for the synchronous EnvoyClientTransport.""" + + def setUp(self): + self.mock_engine = MagicMock() + self.mock_stream_client = MagicMock() + self.mock_engine.stream_client.return_value = self.mock_stream_client + self.mock_prototype = MagicMock() + self.mock_stream_client.new_stream_prototype.return_value = self.mock_prototype + self.mock_stream = MagicMock() + self.mock_prototype.start.return_value = self.mock_stream + self.transport = EnvoyClientTransport(self.mock_engine) + + def test_handle_request_success(self): + """Verify a successful synchronous request/response cycle.""" + request = httpx.Request("GET", "https://example.com/path") + + def side_effect(*args, **kwargs): + # Simulate Envoy receiving headers and data. + on_headers = kwargs.get("on_headers") + on_headers({":status": "200", "content-type": ["application/json"]}, False, None) + on_data = kwargs.get("on_data") + on_data(b'{"key": "value"}', 16, True, None) + return self.mock_stream + + self.mock_prototype.start.side_effect = side_effect + + response = self.transport.handle_request(request) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.headers["content-type"], "application/json") + self.assertEqual(response.read(), b'{"key": "value"}') + + # Verify Envoy pseudo-headers were correctly mapped. + # Note: In the simplified logic, headers are always sent with end_stream=False. + self.mock_stream.send_headers.assert_called_once() + sent_headers = self.mock_stream.send_headers.call_args[0][0] + end_stream = self.mock_stream.send_headers.call_args[0][1] + self.assertEqual(sent_headers[":method"], "GET") + self.assertFalse(end_stream) + + # Verify the stream was closed with empty data. + self.mock_stream.close.assert_called_once_with(b"") + + def test_handle_request_error(self): + """Verify that Envoy errors are mapped to httpx exceptions.""" + request = httpx.Request("GET", "https://example.com") + + def side_effect(*args, **kwargs): + on_error = kwargs.get("on_error") + error = MockEnvoyError() + error.error_code = 2 # ConnectionFailure + error.message = "Connection failed" + on_error(error, None, None) + return self.mock_stream + + self.mock_prototype.start.side_effect = side_effect + + with self.assertRaises(httpx.ConnectError): + self.transport.handle_request(request) + + def test_handle_request_streaming(self): + """Verify that request bodies are streamed to Envoy chunk-by-chunk.""" + + def stream_generator(): + yield b"chunk1" + yield b"chunk2" + + request = httpx.Request("POST", "https://example.com/stream", content=stream_generator()) + + def side_effect(*args, **kwargs): + on_headers = kwargs.get("on_headers") + on_headers({":status": "200"}, False, None) + on_data = kwargs.get("on_data") + on_data(b"ok", 2, True, None) + return self.mock_stream + + self.mock_prototype.start.side_effect = side_effect + + response = self.transport.handle_request(request) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.read(), b"ok") + + # Verify call sequence: headers, chunk1, chunk2, close. + self.mock_stream.send_headers.assert_called_once_with(unittest.mock.ANY, False) + self.mock_stream.send_data.assert_has_calls( + [ + call(b"chunk1", False), + call(b"chunk2", False), + ] + ) + self.mock_stream.close.assert_called_once_with(b"") + + +class TestAsyncEnvoyClientTransport(unittest.IsolatedAsyncioTestCase): + """Tests for the asynchronous AsyncEnvoyClientTransport.""" + + async def asyncSetUp(self): + self.mock_engine = MagicMock() + self.mock_stream_client = MagicMock() + self.mock_engine.stream_client.return_value = self.mock_stream_client + self.mock_prototype = MagicMock() + self.mock_stream_client.new_stream_prototype.return_value = self.mock_prototype + self.mock_stream = MagicMock() + self.mock_prototype.start.return_value = self.mock_stream + self.transport = AsyncEnvoyClientTransport(self.mock_engine) + + async def test_handle_async_request_success(self): + """Verify a successful asynchronous request/response cycle.""" + request = httpx.Request("POST", "https://example.com/post", content=b"body") + + def side_effect(*args, **kwargs): + on_headers = kwargs.get("on_headers") + # Callbacks must be scheduled on the event loop to simulate the AsyncioExecutor. + loop = asyncio.get_running_loop() + loop.call_soon(on_headers, {":status": "201"}, False, None) + on_data = kwargs.get("on_data") + loop.call_soon(on_data, b"created", 7, True, None) + return self.mock_stream + + self.mock_prototype.start.side_effect = side_effect + + response = await self.transport.handle_async_request(request) + + self.assertEqual(response.status_code, 201) + self.assertEqual(await response.aread(), b"created") + + self.mock_stream.send_headers.assert_called_once_with(unittest.mock.ANY, False) + self.mock_stream.send_data.assert_called_once_with(b"body", False) + self.mock_stream.close.assert_called_with(b"") + + async def test_handle_async_request_error(self): + """Verify that Envoy errors are mapped to httpx exceptions asynchronously.""" + request = httpx.Request("GET", "https://example.com") + + def side_effect(*args, **kwargs): + on_error = kwargs.get("on_error") + error = MockEnvoyError() + error.error_code = 4 # RequestTimeout + error.message = "Timed out" + loop = asyncio.get_running_loop() + loop.call_soon(on_error, error, None, None) + return self.mock_stream + + self.mock_prototype.start.side_effect = side_effect + + with self.assertRaises(httpx.ReadTimeout): + await self.transport.handle_async_request(request) + + async def test_handle_async_request_streaming(self): + """Verify asynchronous request body streaming.""" + + async def stream_generator(): + yield b"chunk1" + yield b"chunk2" + + request = httpx.Request("POST", "https://example.com/stream", content=stream_generator()) + + def side_effect(*args, **kwargs): + on_headers = kwargs.get("on_headers") + loop = asyncio.get_running_loop() + loop.call_soon(on_headers, {":status": "200"}, False, None) + on_data = kwargs.get("on_data") + loop.call_soon(on_data, b"ok", 2, True, None) + return self.mock_stream + + self.mock_prototype.start.side_effect = side_effect + + response = await self.transport.handle_async_request(request) + self.assertEqual(response.status_code, 200) + self.assertEqual(await response.aread(), b"ok") + + self.mock_stream.send_headers.assert_called_once_with(unittest.mock.ANY, False) + self.mock_stream.send_data.assert_has_calls( + [ + call(b"chunk1", False), + call(b"chunk2", False), + ] + ) + self.mock_stream.close.assert_called_once_with(b"") + + +from envoy_mobile.async_client.executor import AsyncioExecutor + + +class TestAsyncioExecutor(unittest.TestCase): + """Tests for the AsyncioExecutor thread-safety wrapper.""" + + def test_strict_failure_mode(self): + """Ensure that the executor fails if used without an active loop.""" + # Instantiate without a running event loop. + executor = AsyncioExecutor(loop=None) + + def my_func(): + pass + + wrapped = executor.wrap(my_func) + # Invoking the wrapped function should raise RuntimeError to protect thread-safety. + with self.assertRaises(RuntimeError): + wrapped() + + +if __name__ == "__main__": + unittest.main() diff --git a/mobile/test/python/test_transport_factory.py b/mobile/test/python/test_transport_factory.py new file mode 100644 index 0000000000000..3948b93ff3c85 --- /dev/null +++ b/mobile/test/python/test_transport_factory.py @@ -0,0 +1,74 @@ +"""Unit tests for EnvoyTransportFactory.""" + +import sys +import unittest +from unittest.mock import MagicMock + +# Mock envoy_engine before any imports that might use it +mock_envoy_engine = MagicMock() +sys.modules["envoy_mobile.envoy_engine"] = mock_envoy_engine + +from envoy_mobile import ( + EnvoyTransportFactory, + AsyncEnvoyClientTransport, + EnvoyClientTransport, +) + + +class TestEnvoyTransportFactory(unittest.TestCase): + def setUp(self): + # Reset the shared engine state before each test + EnvoyTransportFactory._engine = None + mock_envoy_engine.reset_mock() + + def test_get_shared_engine_singleton(self): + # Setup mocks + mock_engine = MagicMock() + mock_builder = MagicMock() + mock_builder.build.return_value = mock_engine + + # Call factory multiple times + engine1 = EnvoyTransportFactory.get_shared_engine(mock_builder) + engine2 = EnvoyTransportFactory.get_shared_engine(mock_builder) + + # Verify same instance returned + self.assertEqual(engine1, engine2) + self.assertEqual(engine1, mock_engine) + + # Verify build was only called once + mock_builder.build.assert_called_once() + + def test_get_transports_use_shared_engine(self): + # Setup mocks + mock_engine = MagicMock() + mock_builder = MagicMock() + mock_builder.build.return_value = mock_engine + + # Get transports + async_transport = EnvoyTransportFactory.get_async_transport(mock_builder) + sync_transport = EnvoyTransportFactory.get_sync_transport(mock_builder) + + # Verify they use the same engine instance + self.assertEqual(async_transport._engine, mock_engine) + self.assertEqual(sync_transport._engine, mock_engine) + + # Verify build was only called once + mock_builder.build.assert_called_once() + + def test_default_builder_initialization(self): + # Setup mocks + mock_builder_instance = MagicMock() + mock_envoy_engine.EngineBuilder.return_value = mock_builder_instance + mock_engine = MagicMock() + mock_builder_instance.build.return_value = mock_engine + + # Call without explicit builder + engine = EnvoyTransportFactory.get_shared_engine() + + # Verify default builder was used + mock_envoy_engine.EngineBuilder.assert_called_once() + self.assertEqual(engine, mock_engine) + + +if __name__ == "__main__": + unittest.main() diff --git a/mobile/tools/python/BUILD b/mobile/tools/python/BUILD index 779d1695d3b7c..82bd9838ab7f9 100644 --- a/mobile/tools/python/BUILD +++ b/mobile/tools/python/BUILD @@ -1 +1,16 @@ +load("@rules_python//python:pip.bzl", "compile_pip_requirements") + licenses(["notice"]) # Apache 2 + +exports_files(glob(["**"])) + +compile_pip_requirements( + name = "requirements", + src = "requirements.in", + extra_args = [ + "--allow-unsafe", + "--generate-hashes", + "--reuse-hashes", + "--resolver=backtracking", + ], +) diff --git a/mobile/tools/python/requirements.in b/mobile/tools/python/requirements.in index 7e66a17d49cb8..4adcbe275396a 100644 --- a/mobile/tools/python/requirements.in +++ b/mobile/tools/python/requirements.in @@ -1 +1,8 @@ black +gevent +httpx +mypy +pytest +pytest-asyncio +pytest-benchmark +requests diff --git a/mobile/tools/python/requirements.txt b/mobile/tools/python/requirements.txt index 80b8f1d236fb4..fc491fcfc3266 100644 --- a/mobile/tools/python/requirements.txt +++ b/mobile/tools/python/requirements.txt @@ -2,8 +2,12 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --generate-hashes requirements.in +# bazel run //tools/python:requirements.update # +anyio==4.13.0 \ + --hash=sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708 \ + --hash=sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc + # via httpx black==26.3.1 \ --hash=sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c \ --hash=sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7 \ @@ -32,27 +36,457 @@ black==26.3.1 \ --hash=sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb \ --hash=sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56 \ --hash=sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568 - # via -r requirements.in + # via -r tools/python/requirements.in +certifi==2026.4.22 \ + --hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \ + --hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580 + # via + # httpcore + # httpx + # requests +charset-normalizer==3.4.7 \ + --hash=sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc \ + --hash=sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c \ + --hash=sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67 \ + --hash=sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4 \ + --hash=sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0 \ + --hash=sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c \ + --hash=sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5 \ + --hash=sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444 \ + --hash=sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153 \ + --hash=sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9 \ + --hash=sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01 \ + --hash=sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217 \ + --hash=sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b \ + --hash=sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c \ + --hash=sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a \ + --hash=sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83 \ + --hash=sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5 \ + --hash=sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7 \ + --hash=sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb \ + --hash=sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c \ + --hash=sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1 \ + --hash=sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42 \ + --hash=sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab \ + --hash=sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df \ + --hash=sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e \ + --hash=sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207 \ + --hash=sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18 \ + --hash=sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734 \ + --hash=sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38 \ + --hash=sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110 \ + --hash=sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18 \ + --hash=sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44 \ + --hash=sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d \ + --hash=sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48 \ + --hash=sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e \ + --hash=sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5 \ + --hash=sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d \ + --hash=sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53 \ + --hash=sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790 \ + --hash=sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c \ + --hash=sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b \ + --hash=sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116 \ + --hash=sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d \ + --hash=sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10 \ + --hash=sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6 \ + --hash=sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2 \ + --hash=sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776 \ + --hash=sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a \ + --hash=sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265 \ + --hash=sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008 \ + --hash=sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943 \ + --hash=sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374 \ + --hash=sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246 \ + --hash=sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e \ + --hash=sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5 \ + --hash=sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616 \ + --hash=sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15 \ + --hash=sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41 \ + --hash=sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960 \ + --hash=sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752 \ + --hash=sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e \ + --hash=sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72 \ + --hash=sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7 \ + --hash=sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8 \ + --hash=sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b \ + --hash=sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4 \ + --hash=sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545 \ + --hash=sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706 \ + --hash=sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366 \ + --hash=sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb \ + --hash=sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a \ + --hash=sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e \ + --hash=sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00 \ + --hash=sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f \ + --hash=sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a \ + --hash=sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1 \ + --hash=sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66 \ + --hash=sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356 \ + --hash=sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319 \ + --hash=sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4 \ + --hash=sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad \ + --hash=sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d \ + --hash=sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5 \ + --hash=sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7 \ + --hash=sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0 \ + --hash=sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686 \ + --hash=sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34 \ + --hash=sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49 \ + --hash=sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c \ + --hash=sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1 \ + --hash=sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e \ + --hash=sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60 \ + --hash=sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0 \ + --hash=sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274 \ + --hash=sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d \ + --hash=sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0 \ + --hash=sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae \ + --hash=sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f \ + --hash=sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d \ + --hash=sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe \ + --hash=sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3 \ + --hash=sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393 \ + --hash=sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1 \ + --hash=sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af \ + --hash=sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44 \ + --hash=sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00 \ + --hash=sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c \ + --hash=sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3 \ + --hash=sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7 \ + --hash=sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd \ + --hash=sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e \ + --hash=sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b \ + --hash=sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8 \ + --hash=sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259 \ + --hash=sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859 \ + --hash=sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46 \ + --hash=sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30 \ + --hash=sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b \ + --hash=sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46 \ + --hash=sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24 \ + --hash=sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a \ + --hash=sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24 \ + --hash=sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc \ + --hash=sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215 \ + --hash=sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063 \ + --hash=sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832 \ + --hash=sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6 \ + --hash=sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79 \ + --hash=sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464 + # via requests click==8.3.1 \ --hash=sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a \ --hash=sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6 # via black +gevent==26.4.0 \ + --hash=sha256:079dca3fe5e048714e93ab5ce7cbf2ec31709c860979feb117abbbf2b8ae6ad0 \ + --hash=sha256:1a18e543c830a1c07a2efeb33786a57ccac360af70cb42bbaf5a6f5f7ca49300 \ + --hash=sha256:1c737e6ac6ce1398df0e3f41c58d982e397c993cbe73ac05b7edbe39e128c9cb \ + --hash=sha256:1fe581d41c63cd1d8b12c69561ce53a48ad0d8763b254740d7bfea997335a38c \ + --hash=sha256:201323a5fb9a0646a0c7b384395ca55d60ee83200677919229df0648c4b78e6c \ + --hash=sha256:288d03addfccf0d1c67268358b6759b04392bf3bc35d26f3d9a45c82899c292d \ + --hash=sha256:297a361071dc6708115d4544859321e93b02a6cd5823ba02c0a909530a519d45 \ + --hash=sha256:29a225d2d4da37e20c7a246754a64442d0e43e4534b8cc764f89530bb22a4237 \ + --hash=sha256:318a0a73f664113e8d86d0cb0e328e7650e2d7d9c2e045418ab6fb1285831ad3 \ + --hash=sha256:35b037b415ed38369717800250fe5974249525953b46026bef9def20f946dfb0 \ + --hash=sha256:45d6010a6a981f5a2b3411c4e38fbe305a1b46e4b12db3b4914775927dea7ba4 \ + --hash=sha256:4a9a64c064457c1afaf93ee2815fe0f38be6ecbb92806a6a712f12afc3e26cf5 \ + --hash=sha256:4fbd3ff28a7babbfee750684c4f46ba6eedb3bce69365dd146726986b79fa6c1 \ + --hash=sha256:684256c29e3e5d4d0c4d06b772d00574d0dc859dfbb2fd13d318c512b16e1f89 \ + --hash=sha256:6d973735d2067607a32cd182893978755eee829a0dc268087592d3b715e63fad \ + --hash=sha256:73eafd06b158d511f1ec6e5902a45e0ae3b48e745f35e9df97d25f809f537d88 \ + --hash=sha256:74f1e3a460c43aefcb4ff9ef91aac15abc0b42e5233771e1956574d14ba9cac6 \ + --hash=sha256:7ab0f183a6fd2369eef619832eef14f1f2f69c605163c3f2dc41deb799af4a71 \ + --hash=sha256:7e5906860e632bf965e1966c57e6bfc19dcb79dc262f04fdb0a9d7c12147bf69 \ + --hash=sha256:7e74f59e5c9011afa2a9cb7106bb9a59f2a1f74c3d7b272c1b852eb0bc0b8f90 \ + --hash=sha256:7eef2ea508ce41795e20587a5fc868ae4919543097c81a40fbdfd65bc479f54f \ + --hash=sha256:82d68a60a4207826db295b4e80a204c9d392ce78ccc15679195faeb9e29d8388 \ + --hash=sha256:954258873ae0bcc97fb41e48db25284fb73454bfefe27db8ceb89225da5502fb \ + --hash=sha256:a1b897c952baefd72232efaeb3bdb1ca2fa7ae94cbfe68ac21201b03e843190a \ + --hash=sha256:c503b0c0a681e795255a13e5bb4e41615c3b020c1db93b8dfa04cfeb8f19d5a9 \ + --hash=sha256:c58c8e034f94329be4dc0979fba3301005a433dbab42cea0b2c33fd736946872 \ + --hash=sha256:ccaa0f002cfd69c03621ac05243c1a00ed77cee97b363d0108b0e36663e4ca33 \ + --hash=sha256:ce7aa033a3f68beb6732d1450a80c1af29e63e0c2d01abad7918cf2507f72fa6 \ + --hash=sha256:d00d8c4ca1afab90e478b79679dd53787082c6da8d2f4fdc7667a4440d1e1a7a \ + --hash=sha256:d1c08bc9bb6bd79732a26710a99588b5e9b67b668e165dd609704b876f41baab \ + --hash=sha256:d48e3ee13d7678c24c22f19d441ad6bc220a79f23662d03ff36fae0d62efdb59 \ + --hash=sha256:dc38137ba2f43794c488615aafa2eefd0cc142f484a8274d4c827ed7a031a1e2 \ + --hash=sha256:df208cd53c7382a4cc8470d39a92fc73b3cf2f0f3379d6c88bb556823a26ccb7 \ + --hash=sha256:f0a03650ca60c4c5774cbe21333905b95f2f5abd98ea5a3dbf28d93f2a7a5a84 \ + --hash=sha256:f7e12fdd28cc9f39a463d8df5172d698c64a8ed385a21d98e7092fd8308a139a + # via -r tools/python/requirements.in +greenlet==3.5.0 \ + --hash=sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846 \ + --hash=sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4 \ + --hash=sha256:0ff251e9a0279522e62f6176412869395a64ddf2b5c5f782ff609a8216a4e662 \ + --hash=sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce \ + --hash=sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2 \ + --hash=sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588 \ + --hash=sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13 \ + --hash=sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e \ + --hash=sha256:29ea813b2e1f45fa9649a17853b2b5465c4072fbcb072e5af6cd3a288216574a \ + --hash=sha256:362624e6a8e5bca3b8233e45eef33903a100e9539a2b995c364d595dbc4018b3 \ + --hash=sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b \ + --hash=sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033 \ + --hash=sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628 \ + --hash=sha256:41353ec2ecedf7aa8f682753a41919f8718031a6edac46b8d3dc7ed9e1ceb136 \ + --hash=sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b \ + --hash=sha256:4964101b8585c144cbda5532b1aa644255126c08a265dae90c16e7a0e63aaa9d \ + --hash=sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2 \ + --hash=sha256:4b28037cb07768933c54d81bfe47a85f9f402f57d7d69743b991a713b63954eb \ + --hash=sha256:4d0eadc7e4d9ffb2af4247b606cae307be8e448911e5a0d0b16d72fc3d224cfd \ + --hash=sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b \ + --hash=sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1 \ + --hash=sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16 \ + --hash=sha256:58c1c374fe2b3d852f9b6b11a7dff4c85404e51b9a596fd9e89cf904eb09866d \ + --hash=sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106 \ + --hash=sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba \ + --hash=sha256:5ecd83806b0f4c2f53b1018e0005cd82269ea01d42befc0368730028d850ed1c \ + --hash=sha256:64d6ac45f7271f48e45f67c95b54ef73534c52ec041fcda8edf520c6d811f4bc \ + --hash=sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7 \ + --hash=sha256:6c18dfb59c70f5a94acd271c72e90128c3c776e41e5f07767908c8c1b74ad339 \ + --hash=sha256:6d874e79afd41a96e11ff4c5d0bc90a80973e476fda1c2c64985667397df432b \ + --hash=sha256:7022615368890680e67b9965d33f5773aade330d5343bbe25560135aaa849eae \ + --hash=sha256:703cb211b820dbffbbc55a16bfc6e4583a6e6e990f33a119d2cc8b83211119c8 \ + --hash=sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2 \ + --hash=sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5 \ + --hash=sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf \ + --hash=sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f \ + --hash=sha256:804a70b328e706b785c6ef16187051c394a63dd1a906d89be24b6ad77759f13f \ + --hash=sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2 \ + --hash=sha256:884f649de075b84739713d41dd4dfd41e2b910bfb769c4a3ea02ec1da52cd9bb \ + --hash=sha256:8f1cc966c126639cd152fdaa52624d2655f492faa79e013fea161de3e6dda082 \ + --hash=sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7 \ + --hash=sha256:9c615f869163e14bb1ced20322d8038fb680b08236521ac3f30cd4c1288785a0 \ + --hash=sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c \ + --hash=sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853 \ + --hash=sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988 \ + --hash=sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3 \ + --hash=sha256:ba8f0bdc2fae6ce915dfd0c16d2d00bca7e4247c1eae4416e06430e522137858 \ + --hash=sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37 \ + --hash=sha256:cda05425526240807408156b6960a17a79a0c760b813573b67027823be760977 \ + --hash=sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4 \ + --hash=sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8 \ + --hash=sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86 \ + --hash=sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f \ + --hash=sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112 \ + --hash=sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e \ + --hash=sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2 \ + --hash=sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8 \ + --hash=sha256:f8c30c2225f40dd76c50790f0eb3b5c7c18431efb299e2782083e1981feed243 \ + --hash=sha256:fa94cb2288681e3a11645958f1871d48ee9211bd2f66628fdace505927d6e564 + # via gevent +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via httpcore +httpcore==1.0.9 \ + --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \ + --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8 + # via httpx +httpx==0.28.1 \ + --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ + --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad + # via -r tools/python/requirements.in +idna==3.13 \ + --hash=sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242 \ + --hash=sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3 + # via + # anyio + # httpx + # requests +iniconfig==2.3.0 \ + --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ + --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + # via pytest +librt==0.9.0 \ + --hash=sha256:07cf11f769831186eeac424376e6189f20ace4f7263e2134bdb9757340d84d4d \ + --hash=sha256:0a09c2f5869649101738653a9b7ab70cf045a1105ac66cbb8f4055e61df78f2d \ + --hash=sha256:0a1be03168b2691ba61927e299b352a6315189199ca18a57b733f86cb3cc8d38 \ + --hash=sha256:0b73e4266307e51c95e09c0750b7ec383c561d2e97d58e473f6f6a209952fbb8 \ + --hash=sha256:15cb151e52a044f06e54ac7f7b47adbfc89b5c8e2b63e1175a9d587c43e8942a \ + --hash=sha256:194fc1a32e1e21fe809d38b5faea66cc65eaa00217c8901fbdb99866938adbdb \ + --hash=sha256:1bf465d1e5b0a27713862441f6467b5ab76385f4ecf8f1f3a44f8aa3c695b4b6 \ + --hash=sha256:1c587494461ebd42229d0f1739f3aa34237dd9980623ecf1be8d3bcba79f4499 \ + --hash=sha256:224b9727eb8bc188bc3bcf29d969dba0cd61b01d9bac80c41575520cc4baabb2 \ + --hash=sha256:22a904cbdb678f7cb348c90d543d3c52f581663d687992fee47fd566dcbf5285 \ + --hash=sha256:2b8f5d00b49818f4e2b1667db994488b045835e0ac16fe2f924f3871bd2b8ac5 \ + --hash=sha256:2c3786f0f4490a5cd87f1ed6cefae833ad6b1060d52044ce0434a2e85893afd0 \ + --hash=sha256:2d03fa4fd277a7974c1978c92c374c57f44edeee163d147b477b143446ad1bf6 \ + --hash=sha256:2f8e12706dcb8ff6b3ed57514a19e45c49ad00bcd423e87b2b2e4b5f64578443 \ + --hash=sha256:3be322a15ee5e70b93b7a59cfd074614f22cc8c9ff18bd27f474e79137ea8d3b \ + --hash=sha256:3cc2917258e131ae5f958a4d872e07555b51cb7466a43433218061c74ef33745 \ + --hash=sha256:3f05d145df35dca5056a8bc3838e940efebd893a54b3e19b2dda39ceaa299bcb \ + --hash=sha256:3fe56e80badb66fdcde06bef81bbaa5bfcf6fbd7aefb86222d9e369c38c6b228 \ + --hash=sha256:42ff8a962554c350d4a83cf47d9b7b78b0e6ff7943e87df7cdfc97c07f3c016f \ + --hash=sha256:451daa98463b7695b0a30aa56bf637831ea559e7b8101ac2ef6382e8eb15e29c \ + --hash=sha256:465814ab157986acb9dfa5ccd7df944be5eefc0d08d31ec6e8d88bc71251d845 \ + --hash=sha256:4c4d0440a3a8e31d962340c3e1cc3fc9ee7febd34c8d8f770d06adb947779ea5 \ + --hash=sha256:4e3dda8345307fd7306db0ed0cb109a63a2c85ba780eb9dc2d09b2049a931f9c \ + --hash=sha256:5093043afb226ecfa1400120d1ebd4442b4f99977783e4f4f7248879009b227f \ + --hash=sha256:5112c2fb7c2eefefaeaf5c97fec81343ef44ee86a30dcfaa8223822fba6467b4 \ + --hash=sha256:527b5b820b47a09e09829051452bb0d1dd2122261254e2a6f674d12f1d793d54 \ + --hash=sha256:54d412e47c21b85865676ed0724e37a89e9593c2eee1e7367adf85bfad56ffb1 \ + --hash=sha256:56d65b583cf43b8cf4c8fbe1e1da20fa3076cc32a1149a141507af1062718236 \ + --hash=sha256:5ca8e133d799c948db2ab1afc081c333a825b5540475164726dcbf73537e5c2f \ + --hash=sha256:603138ee838ee1583f1b960b62d5d0007845c5c423feb68e44648b1359014e27 \ + --hash=sha256:63c12efcd160e1d14da11af0c46c0217473e1e0d2ae1acbccc83f561ea4c2a7b \ + --hash=sha256:657f8ba7b9eaaa82759a104137aed2a3ef7bc46ccfd43e0d89b04005b3e0a4cc \ + --hash=sha256:66b58fed90a545328e80d575467244de3741e088c1af928f0b489ebec3ef3858 \ + --hash=sha256:6788207daa0c19955d2b668f3294a368d19f67d9b5f274553fd073c1260cbb9f \ + --hash=sha256:703f4ae36d6240bfe24f542bac784c7e4194ec49c3ba5a994d02891649e2d85b \ + --hash=sha256:7202bdcac47d3a708271c4304a474a8605a4a9a4a709e954bf2d3241140aa938 \ + --hash=sha256:756775d25ec8345b837ab52effee3ad2f3b2dfd6bbee3e3f029c517bd5d8f05a \ + --hash=sha256:78042f6facfd98ecb25e9829c7e37cce23363d9d7c83bc5f72702c5059eb082b \ + --hash=sha256:789fff71757facc0738e8d89e3b84e4f0251c1c975e85e81b152cdaca927cc2d \ + --hash=sha256:7bc30ad339f4e1a01d4917d645e522a0bc0030644d8973f6346397c93ba1503f \ + --hash=sha256:7d429bdd4ac0ab17c8e4a8af0ed2a7440b16eba474909ab357131018fe8c7e71 \ + --hash=sha256:7d5c8a5929ac325729f6119802070b561f4db793dffc45e9ac750992a4ed4d22 \ + --hash=sha256:7e6274fd33fc5b2a14d41c9119629d3ff395849d8bcbc80cf637d9e8d2034da8 \ + --hash=sha256:80b25c7b570a86c03b5da69e665809deb39265476e8e21d96a9328f9762f9990 \ + --hash=sha256:81107843ed1836874b46b310f9b1816abcb89912af627868522461c3b7333c0f \ + --hash=sha256:8494cfc61e03542f2d381e71804990b3931175a29b9278fdb4a5459948778dc2 \ + --hash=sha256:850d6d03177e52700af605fd60db7f37dcb89782049a149674d1a9649c2138fd \ + --hash=sha256:8c6bc1384d9738781cfd41d09ad7f6e8af13cfea2c75ece6bd6d2566cdea2076 \ + --hash=sha256:90904fac73c478f4b83f4ed96c99c8208b75e6f9a8a1910548f69a00f1eaa671 \ + --hash=sha256:90e6d5420fc8a300518d4d2288154ff45005e920425c22cbbfe8330f3f754bd9 \ + --hash=sha256:928bd06eca2c2bbf4349e5b817f837509b0604342e65a502de1d50a7570afd15 \ + --hash=sha256:9b3e3bc363f71bda1639a4ee593cb78f7fbfeacc73411ec0d4c92f00730010a4 \ + --hash=sha256:9edcc35d1cae9fd5320171b1a838c7da8a5c968af31e82ecc3dff30b4be0957f \ + --hash=sha256:9fcb461fbf70654a52a7cc670e606f04449e2374c199b1825f754e16dacfedd8 \ + --hash=sha256:a0951822531e7aee6e0dfb556b30d5ee36bbe234faf60c20a16c01be3530869d \ + --hash=sha256:a361c9434a64d70a7dbb771d1de302c0cc9f13c0bffe1cf7e642152814b35265 \ + --hash=sha256:a4b25c6c25cac5d0d9d6d6da855195b254e0021e513e0249f0e3b444dc6e0e61 \ + --hash=sha256:a5af136bfba820d592f86c67affcef9b3ff4d4360ac3255e341e964489b48519 \ + --hash=sha256:a81eea9b999b985e4bacc650c4312805ea7008fd5e45e1bf221310176a7bcb3a \ + --hash=sha256:a9c63e04d003bc0fb6a03b348018b9a3002f98268200e22cc80f146beac5dc40 \ + --hash=sha256:aa95738a68cedd3a6f5492feddc513e2e166b50602958139e47bbdd82da0f5a7 \ + --hash=sha256:b0a2040f801406b93657a70b72fa12311063a319fee72ce98e1524da7200171f \ + --hash=sha256:b8bd70d5d816566a580d193326912f4a76ec2d28a97dc4cd4cc831c0af8e330e \ + --hash=sha256:b8da9f8035bb417770b1e1610526d87ad4fc58a2804dc4d79c53f6d2cf5a6eb9 \ + --hash=sha256:bc5518873822d2faa8ebdd2c1a4d7c8ef47b01a058495ab7924cb65bdbf5fc9a \ + --hash=sha256:c0d620e74897f8c2613b3c4e2e9c1e422eb46d2ddd07df540784d44117836af3 \ + --hash=sha256:c2640e23d2b7c98796f123ffd95cf2022c7777aa8a4a3b98b36c570d37e85eee \ + --hash=sha256:c81aef782380f0f13ead670aae01825eb653b44b046aa0e5ebbb79f76ed4aa11 \ + --hash=sha256:d4d16b608a1c43d7e33142099a75cd93af482dadce0bf82421e91cad077157f4 \ + --hash=sha256:d69fc39e627908f4c03297d5a88d9284b73f4d90b424461e32e8c2485e21c283 \ + --hash=sha256:d9da80e5b04acce03ced8ba6479a71c2a2edf535c2acc0d09c80d2f80f3bad15 \ + --hash=sha256:dd2c7e082b0b92e1baa4da28163a808672485617bc855cc22a2fd06978fa9084 \ + --hash=sha256:de7dac64e3eb832ffc7b840eb8f52f76420cde1b845be51b2a0f6b870890645e \ + --hash=sha256:e0785c2fb4a81e1aece366aa3e2e039f4a4d7d21aaaded5227d7f3c703427882 \ + --hash=sha256:e306d956cfa027fe041585f02a1602c32bfa6bb8ebea4899d373383295a6c62f \ + --hash=sha256:e78fb7419e07d98c2af4b8567b72b3eaf8cb05caad642e9963465569c8b2d87e \ + --hash=sha256:e9002e98dcb1c0a66723592520decd86238ddcef168b37ff6cfb559200b4b774 \ + --hash=sha256:e94cbc6ad9a6aeea46d775cbb11f361022f778a9cc8cc90af653d3a594b057ce \ + --hash=sha256:eea1b54943475f51698f85fa230c65ccac769f1e603b981be060ac5763d90927 \ + --hash=sha256:f100bfe2acf8a3689af9d0cc660d89f17286c9c795f9f18f7b62dd1a6b247ae6 \ + --hash=sha256:f162af66a2ed3f7d1d161a82ca584efd15acd9c1cff190a373458c32f7d42118 \ + --hash=sha256:f24b90b0e0c8cc9491fb1693ae91fe17cb7963153a1946395acdbdd5818429a4 \ + --hash=sha256:f29b68cd9714531672db62cc54f6e8ff981900f824d13fa0e00749189e13778e \ + --hash=sha256:f38bc489037eca88d6ebefc9c4d41a4e07c8e8b4de5188a9e6d290273ad7ebb1 \ + --hash=sha256:f3fd278f5e6bf7c75ccd6d12344eb686cc020712683363b66f46ac79d37c799f \ + --hash=sha256:f4003f70c56a5addd6aa0897f200dd59afd3bf7bcd5b3cce46dd21f925743bc2 \ + --hash=sha256:f48c963a76d71b9d7927eb817b543d0dccd52ab6648b99d37bd54f4cd475d856 \ + --hash=sha256:f819e0c6413e259a17a7c0d49f97f405abadd3c2a316a3b46c6440b7dbbedbb1 \ + --hash=sha256:fc5758e2b7a56532dc33e3c544d78cbaa9ecf0a0f2a2da2df882c1d6b99a317f \ + --hash=sha256:fcbdf2a9ca24e87bbebb47f1fe34e531ef06f104f98c9ccfc953a3f3344c567a + # via mypy +mypy==1.20.2 \ + --hash=sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb \ + --hash=sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98 \ + --hash=sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99 \ + --hash=sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100 \ + --hash=sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744 \ + --hash=sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f \ + --hash=sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609 \ + --hash=sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6 \ + --hash=sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c \ + --hash=sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30 \ + --hash=sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4 \ + --hash=sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b \ + --hash=sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558 \ + --hash=sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2 \ + --hash=sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102 \ + --hash=sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc \ + --hash=sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c \ + --hash=sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2 \ + --hash=sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517 \ + --hash=sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58 \ + --hash=sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997 \ + --hash=sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6 \ + --hash=sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c \ + --hash=sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8 \ + --hash=sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14 \ + --hash=sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec \ + --hash=sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee \ + --hash=sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066 \ + --hash=sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563 \ + --hash=sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330 \ + --hash=sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67 \ + --hash=sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15 \ + --hash=sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac \ + --hash=sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3 \ + --hash=sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254 \ + --hash=sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3 \ + --hash=sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4 \ + --hash=sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9 \ + --hash=sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382 \ + --hash=sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943 \ + --hash=sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924 \ + --hash=sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665 \ + --hash=sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026 \ + --hash=sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd + # via -r tools/python/requirements.in mypy-extensions==1.1.0 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 - # via black + # via + # black + # mypy packaging==26.0 \ --hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \ --hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529 - # via black + # via + # black + # pytest pathspec==1.0.4 \ --hash=sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645 \ --hash=sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723 - # via black + # via + # black + # mypy platformdirs==4.9.4 \ --hash=sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934 \ --hash=sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868 # via black +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +py-cpuinfo==9.0.0 \ + --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ + --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 + # via pytest-benchmark +pygments==2.20.0 \ + --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ + --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + # via pytest +pytest==9.0.3 \ + --hash=sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 \ + --hash=sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c + # via + # -r tools/python/requirements.in + # pytest-asyncio + # pytest-benchmark +pytest-asyncio==1.3.0 \ + --hash=sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5 \ + --hash=sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5 + # via -r tools/python/requirements.in +pytest-benchmark==5.2.3 \ + --hash=sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803 \ + --hash=sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779 + # via -r tools/python/requirements.in pytokens==0.4.1 \ --hash=sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1 \ --hash=sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009 \ @@ -97,3 +531,59 @@ pytokens==0.4.1 \ --hash=sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6 \ --hash=sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324 # via black +requests==2.33.1 \ + --hash=sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517 \ + --hash=sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a + # via -r tools/python/requirements.in +typing-extensions==4.15.0 \ + --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ + --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 + # via + # anyio + # mypy + # pytest-asyncio +urllib3==2.6.3 \ + --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \ + --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 + # via requests +zope-event==6.2 \ + --hash=sha256:5e755153ac4faf64c10a4b6dd3307680166a3edf65b38df22df592610f8fa874 \ + --hash=sha256:b97d5d6327067ee6b9dfcbdf606ade9ade70991e19c162e808ea39e5fcf0f8d3 + # via gevent +zope-interface==8.4 \ + --hash=sha256:049ba3c7b38cc400ae08e011617635706e0f442e1d075db1b015246fcbf6091e \ + --hash=sha256:04c2c9b58e9c177628715d85e94834efa807c1f9f0a2f57ae0f7b553e8266ac4 \ + --hash=sha256:0d88c1f106a4f06e074a3ada2d20f4a602e3f2871c4f55726ed5d91e94ec19b1 \ + --hash=sha256:265bad2df2ec070f23ff863249a89b408b11908fd4207662781fd18e3c6fc912 \ + --hash=sha256:29f09ec8bda65f7b30294328070070a2590b90f252f834ee0817cdb0e2c35f6a \ + --hash=sha256:2bc388cebcb753d21eaf2a0481fd6f0ce6840a47300a40dcec0b56bac27d0f97 \ + --hash=sha256:2e9e4aa33b76877af903d5532545e64d24ade0f6f80d9d1a31e6efcea76a60bc \ + --hash=sha256:36c575356732d59ffd3279ad67e302a6fe517e67db5b061b36b377ee0fa016c4 \ + --hash=sha256:376d0ef005a131b349e2088e302aa094fa23c826d2ec8a7db4b00fb33c71e0d9 \ + --hash=sha256:3e5866917ccb57d929e515a1136d729bd3fa4f367965fb16e38a4bc72cb05521 \ + --hash=sha256:415de524326ddd61a78f0816f65942fa1aa35dced19e72579ad30dd106ce523e \ + --hash=sha256:4713bf651ec36e7eea49d2ace4f0e89bec2b33a339674874b1121f2537edc62a \ + --hash=sha256:4ae6a1e111642dbf724f635424dcaf5a5c8abbde49eac3f452f5323ffaa10232 \ + --hash=sha256:5ec1a56b6cf9a757cbbce9da38284a01473b92b96c1517eabd99150f51f1bb69 \ + --hash=sha256:7cbb887fdbfaacb4c362dbb487033551646e28013ad5ffe72e96eb260003a1a1 \ + --hash=sha256:81ed23698bfb588c48b1756129814b890febac971ff6c8a414f82601773145bb \ + --hash=sha256:84064876ed96ddd0744e3ad5d37134c758d77885e54113567792671405a02bac \ + --hash=sha256:8544081e32b515bbaf1c6339eef06b23ed470cf4876ff2f575803f82a744cc43 \ + --hash=sha256:892b4b5350e58d6914858f58eb85d39fe9b992640ac6ece695f46c978046554d \ + --hash=sha256:8b302f955c36e924e1f4fe70dd9105ff06235857861c6ae72c3b10b016aeee99 \ + --hash=sha256:8b733af6e89a2b0b8edf5ff7a37988fe4e1788806e84e72127b88c47858f0da6 \ + --hash=sha256:8d683267a6243526869cb69677dcfc663416d5f87904c1576ddec6e420687d51 \ + --hash=sha256:9c4ac009c2c8e43283842f80387c4d4b41bcbc293391c3b9ab71532ae1ccc301 \ + --hash=sha256:9dbee7925a23aa6349738892c911019d4095a96cff487b743482073ecbc174a8 \ + --hash=sha256:a5638c6be715116d3453e6d099c299c6844d54810de7445ce116424e905ede06 \ + --hash=sha256:b8147b40bfcd53803870a9519e0879ff066aeecc2fcff8295663c1b17fc38dc2 \ + --hash=sha256:caffd033b27e311b45e15f01923cc9e73c6bfd8e843b4532e29b59ee432bf893 \ + --hash=sha256:cd55965d715413038774aead54851bc3dbdd74a69f3ce30252182a94407b9905 \ + --hash=sha256:d934497c4b72d5f528d2b5ebe9b8b5a7004b5877948ebd4ea00c2432fb27178f \ + --hash=sha256:e0b9d7e958657fad414f8272afcdf0b8a873fbbb2bb6a6287232d2f11a232bf8 \ + --hash=sha256:e195e76767847afb5379ffd67690c17d3c6efdab58dc0e477cf81ac94d5a5a15 \ + --hash=sha256:eef0a49e041f4dc4d2a6ab894b4fd0c5354e0e8037e731fb953531e59b0d3d33 \ + --hash=sha256:f00fd65343d2a241a2b17688a12f5e815aa704ed64f9ca375de5f9e0ae9c9bda \ + --hash=sha256:f1f854bef8bc137519e4413bcc1322d55faad28b20b3ca39f7bec49d2f1b26df \ + --hash=sha256:fa0a26d5767087170b3da9ff503221d535ea266bf61b522d0afa2590fd05db0a + # via gevent From 502f269be16e1d747bcbc793cbae442811a2b939 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 21:44:25 +0100 Subject: [PATCH 241/750] build(deps): bump gitpython from 3.1.47 to 3.1.50 in /tools/base (#44866) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 85f08be0687db..450b0062c2e56 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -767,9 +767,9 @@ gitdb==4.0.11 \ --hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \ --hash=sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b # via gitpython -gitpython==3.1.47 \ - --hash=sha256:489f590edfd6d20571b2c0e72c6a6ac6915ee8b8cd04572330e3842207a78905 \ - --hash=sha256:dba27f922bd2b42cb54c87a8ab3cb6beb6bf07f3d564e21ac848913a05a8a3cd +gitpython==3.1.50 \ + --hash=sha256:80da2d12504d52e1f998772dc5baf6e553f8d2fcfe1fcc226c9d9a2ee3372dcc \ + --hash=sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9 # via -r requirements.in humanfriendly==10.0 \ --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 \ From 81d1ffe873bc621517f6b685d4734d1ffdd144df Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Wed, 6 May 2026 17:04:55 -0400 Subject: [PATCH 242/750] ci: enable catch-errors and Slack notification for OpenSSL check (#44908) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable `catch-errors` for the OpenSSL CI target so that failures are captured without blocking the overall job. Remove the error masking in do_ci.sh — failures now propagate naturally and are handled at the workflow level, where the Slack notification step detects them via the exit code and notifies `#envoy-openssl`. Signed-off-by: Jonh Wendell --- .github/workflows/_check_build.yml | 1 + ci/do_ci.sh | 10 +--------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/_check_build.yml b/.github/workflows/_check_build.yml index b44cf039426b2..dc22bc92d5496 100644 --- a/.github/workflows/_check_build.yml +++ b/.github/workflows/_check_build.yml @@ -62,4 +62,5 @@ jobs: - target: openssl name: OpenSSL skip: ${{ ! fromJSON(inputs.request).run.check-build-openssl }} + catch-errors: true slack-channel: '#envoy-openssl' diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 5d9b12ec8eb63..196b332f9d949 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -822,15 +822,7 @@ case $CI_TARGET in ;; openssl) - # This whole boilerplate is to not fail the entire CI if there is an issue with the OpenSSL build or tests, - # as this is not a blocker for other work. - set +e - (set -e; build_openssl) - rc=$? - set -e - if [[ $rc -ne 0 ]]; then - echo "ERROR: OpenSSL build or test failed" >&2 - fi + build_openssl ;; publish) From 7e0f0c991feccf45fb7151b2539f9dd330c0b73c Mon Sep 17 00:00:00 2001 From: Zach Montoya Date: Wed, 6 May 2026 14:09:13 -0700 Subject: [PATCH 243/750] deps: bump dd-trace-cpp to v2.1.0 (#44906) This PR bumps up the version of `dd_trace_cpp` to v2.1.0. This adds new capabilities and bug fixes without any updates to the tracing APIs. **Reference:** For complete details on the v2.1.0 release: https://github.com/DataDog/dd-trace-cpp/releases/tag/v2.1.0 --- Commit Message: deps: bump `dd_trace_cpp` to v2.1.0 Additional Description: Bump the version of `dd_trace_cpp` to v2.1.0 Risk Level: Low Testing: CI Docs Changes: N/A Release Notes:N/A Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Zach Montoya --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index a42032abbc252..079e863ad5dd0 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -163,7 +163,7 @@ dd_trace_cpp: project_name: "Datadog C++ Tracing Library" project_desc: "Datadog distributed tracing for C++" project_url: "https://github.com/DataDog/dd-trace-cpp" - release_date: "2025-10-14" + release_date: "2026-05-06" use_category: - observability_ext extensions: diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index b8844cfa74e6e..4428cf94c6b5d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -290,8 +290,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/SkyAPM/cpp2sky/archive/v{version}.tar.gz"], ), dd_trace_cpp = dict( - version = "2.0.0", - sha256 = "e4a0dabc3e186ce99c71685178448f93c501577102cdc50ddbf12cbaaba54713", + version = "2.1.0", + sha256 = "8152fb69e61518a5b55ecb96edcbb19585f8950b6f070ac98ebd0b6dd6177492", strip_prefix = "dd-trace-cpp-{version}", urls = ["https://github.com/DataDog/dd-trace-cpp/archive/v{version}.tar.gz"], ), From 426db9172e2e03117603a96a158444265aefb60c Mon Sep 17 00:00:00 2001 From: Eric Bishop <60610299+ericdbishop@users.noreply.github.com> Date: Wed, 6 May 2026 17:10:33 -0400 Subject: [PATCH 244/750] build: use local llvm-objcopy when use_local_llvm is true (#44903) Signed-off-by: Eric Bishop --- .../dynamic_modules/dynamic_modules.bzl | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/source/extensions/dynamic_modules/dynamic_modules.bzl b/source/extensions/dynamic_modules/dynamic_modules.bzl index 7d548acbde7c2..d2f8ab3105855 100644 --- a/source/extensions/dynamic_modules/dynamic_modules.bzl +++ b/source/extensions/dynamic_modules/dynamic_modules.bzl @@ -1,5 +1,6 @@ """Bazel rules for building Envoy dynamic modules.""" +load("@envoy_repo//:compiler.bzl", "LLVM_PATH") load("@rules_cc//cc:defs.bzl", "cc_import") def envoy_dynamic_module_prefix_symbols(name, module_name, archive, tags = [], **kwargs): @@ -61,19 +62,30 @@ def envoy_dynamic_module_prefix_symbols(name, module_name, archive, tags = [], * # NOTE: The case statement is kept outside $() command substitution for # compatibility with bash 3.2 (macOS default), which cannot parse case # pattern delimiters inside $(). + archive_select_cmd = ( + "ARCH=\"\"; " + + "for f in $(SRCS); do case $$f in *.pic.a) continue;; *.a) ARCH=$$f; break;; esac; done; " + + "[ -z \"$$ARCH\" ] && " + + "for f in $(SRCS); do case $$f in *.a) ARCH=$$f; break;; esac; done; " + ) native.genrule( name = renamed_name, srcs = [archive, ":" + redefine_syms_name], outs = [name + "_renamed.a"], - cmd = ( - "ARCH=\"\"; " + - "for f in $(SRCS); do case $$f in *.pic.a) continue;; *.a) ARCH=$$f; break;; esac; done; " + - "[ -z \"$$ARCH\" ] && " + - "for f in $(SRCS); do case $$f in *.a) ARCH=$$f; break;; esac; done; " + - "$(location @llvm_toolchain_llvm//:objcopy) " + - "--redefine-syms=$(location :" + redefine_syms_name + ") $$ARCH $@" - ), - tools = ["@llvm_toolchain_llvm//:objcopy"], + cmd = archive_select_cmd + select({ + "@envoy_repo//:use_local_llvm": ( + "%s/bin/llvm-objcopy " % LLVM_PATH + + "--redefine-syms=$(location :" + redefine_syms_name + ") $$ARCH $@" + ), + "//conditions:default": ( + "$(location @llvm_toolchain_llvm//:objcopy) " + + "--redefine-syms=$(location :" + redefine_syms_name + ") $$ARCH $@" + ), + }), + tools = select({ + "@envoy_repo//:use_local_llvm": [], + "//conditions:default": ["@llvm_toolchain_llvm//:objcopy"], + }), tags = tags, ) From 8db119bd670936efcff09920b795da4f3fafe8e0 Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Wed, 6 May 2026 18:35:43 -0400 Subject: [PATCH 245/750] tls: refactor cipher_suites to be shared_ptr instead of copied strings (#44793) Refactoring the cipher_suites in the TLS `ContextConfigImpl` object, so that if there are multiple TLS objects with the same `cipher_suites` these will be shared instead of copied. The PR introduces a SharedObjectPool of the cipher_suites strings, that the `ContextConfigImpl` have shared-pointers to. Signed-off-by: Adi Suissa-Peleg --- source/common/tls/context_config_impl.cc | 19 +++++++++++++++++-- source/common/tls/context_config_impl.h | 5 +++-- test/common/tls/context_impl_test.cc | 24 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/source/common/tls/context_config_impl.cc b/source/common/tls/context_config_impl.cc index 7121a2da94298..be4993752aca7 100644 --- a/source/common/tls/context_config_impl.cc +++ b/source/common/tls/context_config_impl.cc @@ -13,6 +13,7 @@ #include "source/common/protobuf/message_validator_impl.h" #include "source/common/protobuf/utility.h" #include "source/common/secret/sds_api.h" +#include "source/common/shared_pool/shared_pool.h" #include "source/common/ssl/certificate_validation_context_config_impl.h" #include "source/common/tls/ssl_handshaker.h" @@ -24,6 +25,8 @@ namespace Extensions { namespace TransportSockets { namespace Tls { +SINGLETON_MANAGER_REGISTRATION(cipher_suites_pool); + namespace { std::string generateCertificateHash(const std::string& cert_data) { @@ -179,6 +182,14 @@ compliancePolicyFromProto( } } +std::shared_ptr> +getCipherSuitesPool(Singleton::Manager& singleton_manager, Event::Dispatcher& dispatcher) { + return singleton_manager.getTyped>( + SINGLETON_MANAGER_REGISTERED_NAME(cipher_suites_pool), [&dispatcher] { + return std::make_shared>(dispatcher); + }); +} + } // namespace ContextConfigImpl::ContextConfigImpl( @@ -194,8 +205,12 @@ ContextConfigImpl::ContextConfigImpl( lifecycle_notifier_(factory_context.serverFactoryContext().lifecycleNotifier()), auto_sni_san_match_(auto_sni_san_match), alpn_protocols_(RepeatedPtrUtil::join(config.alpn_protocols(), ",")), - cipher_suites_(StringUtil::nonEmptyStringOrDefault( - RepeatedPtrUtil::join(config.tls_params().cipher_suites(), ":"), default_cipher_suites)), + cipher_suites_( + getCipherSuitesPool(factory_context.serverFactoryContext().singletonManager(), + factory_context.serverFactoryContext().mainThreadDispatcher()) + ->getObject(StringUtil::nonEmptyStringOrDefault( + RepeatedPtrUtil::join(config.tls_params().cipher_suites(), ":"), + default_cipher_suites))), ecdh_curves_(StringUtil::nonEmptyStringOrDefault( RepeatedPtrUtil::join(config.tls_params().ecdh_curves(), ":"), default_curves)), signature_algorithms_(RepeatedPtrUtil::join(config.tls_params().signature_algorithms(), ":")), diff --git a/source/common/tls/context_config_impl.h b/source/common/tls/context_config_impl.h index 1dae26f6f3670..8686afb5c6fca 100644 --- a/source/common/tls/context_config_impl.h +++ b/source/common/tls/context_config_impl.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -35,7 +36,7 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig, public: // Ssl::ContextConfig const std::string& alpnProtocols() const override { return alpn_protocols_; } - const std::string& cipherSuites() const override { return cipher_suites_; } + const std::string& cipherSuites() const override { return *cipher_suites_; } const std::string& ecdhCurves() const override { return ecdh_curves_; } const std::string& signatureAlgorithms() const override { return signature_algorithms_; } absl::optional @@ -104,7 +105,7 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig, unsigned default_version); const std::string alpn_protocols_; - const std::string cipher_suites_; + const std::shared_ptr cipher_suites_; const std::string ecdh_curves_; const std::string signature_algorithms_; diff --git a/test/common/tls/context_impl_test.cc b/test/common/tls/context_impl_test.cc index 429005a45cf3d..c82924b456686 100644 --- a/test/common/tls/context_impl_test.cc +++ b/test/common/tls/context_impl_test.cc @@ -148,6 +148,30 @@ TEST_F(SslContextImplTest, TestCipherSuites) { "ciphers were rejected when tried individually: BOGUS1-SHA256, BOGUS2-SHA"); } +// Validates that multiple cipher-suites with the same contents are still equal +// after dedup. +TEST_F(SslContextImplTest, TestCipherSuitesDeduplication) { + const std::string yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256" + )EOF"; + + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context1; + TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), tls_context1); + auto cfg1 = *ClientContextConfigImpl::create(tls_context1, factory_context_); + + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context2; + TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), tls_context2); + auto cfg2 = *ClientContextConfigImpl::create(tls_context2, factory_context_); + + EXPECT_EQ(cfg1->cipherSuites(), "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256"); + EXPECT_EQ(cfg2->cipherSuites(), "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256"); + + EXPECT_TRUE(manager_.createSslClientContext(*store_.rootScope(), *cfg1).ok()); + EXPECT_TRUE(manager_.createSslClientContext(*store_.rootScope(), *cfg2).ok()); +} + // Envoy's default cipher preference is server's. TEST_F(SslContextImplTest, TestServerCipherPreference) { const std::string yaml = R"EOF( From 0fa085e9c2bf9c1cd2aaa37a24f227985c235f94 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Thu, 7 May 2026 01:05:14 +0200 Subject: [PATCH 246/750] dynamic_modules: wrap every Rust SDK FFI entry point in catch_unwind (#44846) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description The SDK's `pub (unsafe) extern "C" fn envoy_dynamic_module_*` entries are the C <-> Rust ABI boundary. On default cdylib `panic="unwind"` (Rust ≥1.81), letting a panic unwind across `extern "C"` is undefined behavior which can typically abort the process, but with no Envoy-side log of which module which entry point triggered it. `transport_socket.rs`, `dns_resolver.rs`, and `tracer.rs` already wrapped their entries but the rest of the modules does not. This change closes the gap. Every uncovered FFI entry across the 11 remaining SDK files including access_log, bootstrap, cert_validator, cluster, http, listener, load_balancer, matcher, network, udp_listener, upstream_http_tcp_bridge, etc. is now wrapped in `std::panic::catch_unwind` with a fail-closed sentinel chosen per the Envoy-side `abi_impl.cc` interpretation. --- **Commit Message:** dynamic_modules: wrap every Rust SDK FFI entry point in catch_unwind **Additional Description:** Wraps every Rust SDK FFI entry point in catch_unwind. **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** Added **Release Notes:** N/A Signed-off-by: Rohit Agrawal --- .../sdk/rust/src/access_log.rs | 116 ++- .../dynamic_modules/sdk/rust/src/bootstrap.rs | 499 +++++++----- .../sdk/rust/src/cert_validator.rs | 168 ++-- .../dynamic_modules/sdk/rust/src/cluster.rs | 323 +++++--- .../sdk/rust/src/dns_resolver.rs | 23 +- .../dynamic_modules/sdk/rust/src/http.rs | 737 ++++++++++++------ .../dynamic_modules/sdk/rust/src/lib.rs | 388 +++++---- .../dynamic_modules/sdk/rust/src/lib_test.rs | 69 ++ .../dynamic_modules/sdk/rust/src/listener.rs | 227 ++++-- .../sdk/rust/src/load_balancer.rs | 127 +-- .../dynamic_modules/sdk/rust/src/matcher.rs | 46 +- .../dynamic_modules/sdk/rust/src/network.rs | 287 ++++--- .../dynamic_modules/sdk/rust/src/tracer.rs | 71 +- .../sdk/rust/src/transport_socket.rs | 33 +- .../sdk/rust/src/udp_listener.rs | 101 ++- .../sdk/rust/src/upstream_http_tcp_bridge.rs | 169 ++-- 16 files changed, 2215 insertions(+), 1169 deletions(-) diff --git a/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs b/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs index 6a9d33a2ec796..66f7e7df71d44 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs @@ -9,6 +9,7 @@ use crate::{abi, EnvoyBuffer}; use std::ffi::c_void; +use std::panic::{catch_unwind, AssertUnwindSafe}; use std::ptr; // ----------------------------------------------------------------------------- @@ -1276,24 +1277,30 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_config_new( name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> *const c_void { - // The name and config originate from protobuf string/bytes fields, so they are valid - // UTF-8 and within bounds when received here. Mirrors the http filter FFI entry point. - let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )); - let config_bytes = std::slice::from_raw_parts(config.ptr as *const _, config.length); - let ctx = ConfigContext::new(config_envoy_ptr); - - envoy_dynamic_module_on_access_logger_config_new_impl( - &ctx, - name_str, - config_bytes, - crate::NEW_ACCESS_LOGGER_CONFIG_FUNCTION - .get() - .expect("NEW_ACCESS_LOGGER_CONFIG_FUNCTION must be set"), - config_envoy_ptr, - ) + catch_unwind(AssertUnwindSafe(|| { + // The name and config originate from protobuf string/bytes fields, so they are valid + // UTF-8 and within bounds when received here. Mirrors the http filter FFI entry point. + let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )); + let config_bytes = std::slice::from_raw_parts(config.ptr as *const _, config.length); + let ctx = ConfigContext::new(config_envoy_ptr); + + envoy_dynamic_module_on_access_logger_config_new_impl( + &ctx, + name_str, + config_bytes, + crate::NEW_ACCESS_LOGGER_CONFIG_FUNCTION + .get() + .expect("NEW_ACCESS_LOGGER_CONFIG_FUNCTION must be set"), + config_envoy_ptr, + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_access_logger_config_new", panic); + ptr::null() + }) } /// Testable wrapper for [`envoy_dynamic_module_on_access_logger_config_new`]. @@ -1325,7 +1332,15 @@ pub fn envoy_dynamic_module_on_access_logger_config_new_impl( pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_config_destroy( config_ptr: *const c_void, ) { - drop(Box::from_raw(config_ptr as *mut AccessLoggerConfigHandle)); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop(Box::from_raw(config_ptr as *mut AccessLoggerConfigHandle)); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_access_logger_config_destroy", + panic, + ); + }); } /// # Safety @@ -1337,10 +1352,16 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_new( config_ptr: *const c_void, logger_envoy_ptr: *mut c_void, ) -> *const c_void { - let handle = &*(config_ptr as *const AccessLoggerConfigHandle); - let metrics = MetricsContext::new(handle.config_envoy_ptr); - let logger: Box = handle.inner.create_logger(metrics, logger_envoy_ptr); - crate::wrap_into_c_void_ptr!(logger) + catch_unwind(AssertUnwindSafe(|| { + let handle = &*(config_ptr as *const AccessLoggerConfigHandle); + let metrics = MetricsContext::new(handle.config_envoy_ptr); + let logger: Box = handle.inner.create_logger(metrics, logger_envoy_ptr); + crate::wrap_into_c_void_ptr!(logger) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_access_logger_new", panic); + ptr::null() + }) } /// # Safety @@ -1353,10 +1374,15 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_log( logger_ptr: *mut c_void, log_type: abi::envoy_dynamic_module_type_access_log_type, ) { - let logger = &mut *(logger_ptr as *mut Box); - let access_log_type = AccessLogType::from_abi(log_type); - let ctx = LogContext::new(envoy_ptr, access_log_type); - logger.log(&ctx); + let _ = catch_unwind(AssertUnwindSafe(|| { + let logger = &mut *(logger_ptr as *mut Box); + let access_log_type = AccessLogType::from_abi(log_type); + let ctx = LogContext::new(envoy_ptr, access_log_type); + logger.log(&ctx); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_access_logger_log", panic); + }); } /// # Safety @@ -1365,7 +1391,12 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_log( /// by the Envoy dynamic module ABI. #[no_mangle] pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_destroy(logger_ptr: *mut c_void) { - crate::drop_wrapped_c_void_ptr!(logger_ptr, AccessLogger); + let _ = catch_unwind(AssertUnwindSafe(|| { + crate::drop_wrapped_c_void_ptr!(logger_ptr, AccessLogger); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_access_logger_destroy", panic); + }); } /// # Safety @@ -1374,8 +1405,13 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_destroy(logger_pt /// by the Envoy dynamic module ABI. #[no_mangle] pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_flush(logger_ptr: *mut c_void) { - let logger = &mut *(logger_ptr as *mut Box); - logger.flush(); + let _ = catch_unwind(AssertUnwindSafe(|| { + let logger = &mut *(logger_ptr as *mut Box); + logger.flush(); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_access_logger_flush", panic); + }); } /// Declare access-logger entry points for a single user-supplied config type. @@ -1458,12 +1494,20 @@ macro_rules! declare_access_logger { Err(_) => ::std::option::Option::None, } } - $crate::set_factory_once!( - $crate::NEW_ACCESS_LOGGER_CONFIG_FUNCTION, - __single_access_logger_factory as $crate::NewAccessLoggerConfigFunction, - "NEW_ACCESS_LOGGER_CONFIG_FUNCTION" - ); - $crate::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + $crate::set_factory_once!( + $crate::NEW_ACCESS_LOGGER_CONFIG_FUNCTION, + __single_access_logger_factory as $crate::NewAccessLoggerConfigFunction, + "NEW_ACCESS_LOGGER_CONFIG_FUNCTION" + ); + $crate::abi::envoy_dynamic_modules_abi_version.as_ptr() as *const ::std::os::raw::c_char + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, + } } }; } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs b/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs index 82380dba1f015..c639d7197c8ad 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs @@ -6,6 +6,7 @@ use crate::{ NewBootstrapExtensionConfigFunction, NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION, }; use mockall::*; +use std::panic::{catch_unwind, AssertUnwindSafe}; /// EnvoyBootstrapExtensionConfig is the Envoy-side bootstrap extension configuration. /// This is a handle to the Envoy configuration object. @@ -1311,23 +1312,32 @@ pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_config_new( name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr { - let mut envoy_extension_config = - EnvoyBootstrapExtensionConfigImpl::new(envoy_extension_config_ptr); - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) - }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; - init_bootstrap_extension_config( - &mut envoy_extension_config, - name_str, - config_slice, - NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION - .get() - .expect("NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION must be set"), - ) + catch_unwind(AssertUnwindSafe(|| { + let mut envoy_extension_config = + EnvoyBootstrapExtensionConfigImpl::new(envoy_extension_config_ptr); + let name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )) + }; + let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; + init_bootstrap_extension_config( + &mut envoy_extension_config, + name_str, + config_slice, + NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION + .get() + .expect("NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION must be set"), + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_config_new", + panic, + ); + std::ptr::null() + }) } pub(crate) fn init_bootstrap_extension_config( @@ -1351,7 +1361,15 @@ pub(crate) fn init_bootstrap_extension_config( pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_config_destroy( extension_config_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr, ) { - drop_wrapped_c_void_ptr!(extension_config_ptr, BootstrapExtensionConfig); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!(extension_config_ptr, BootstrapExtensionConfig); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_config_destroy", + panic, + ); + }); } /// # Safety @@ -1363,12 +1381,18 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_new( extension_config_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr, envoy_extension_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_envoy_ptr, ) -> abi::envoy_dynamic_module_type_bootstrap_extension_module_ptr { - let mut envoy_extension = EnvoyBootstrapExtensionImpl::new(envoy_extension_ptr); - let extension_config = { - let raw = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; - &**raw - }; - envoy_dynamic_module_on_bootstrap_extension_new_impl(&mut envoy_extension, extension_config) + catch_unwind(AssertUnwindSafe(|| { + let mut envoy_extension = EnvoyBootstrapExtensionImpl::new(envoy_extension_ptr); + let extension_config = { + let raw = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; + &**raw + }; + envoy_dynamic_module_on_bootstrap_extension_new_impl(&mut envoy_extension, extension_config) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_bootstrap_extension_new", panic); + std::ptr::null() + }) } pub(crate) fn envoy_dynamic_module_on_bootstrap_extension_new_impl( @@ -1384,9 +1408,17 @@ pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_server_initialized envoy_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_envoy_ptr, extension_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_module_ptr, ) { - let extension = extension_ptr as *mut Box; - let extension = unsafe { &mut *extension }; - extension.on_server_initialized(&mut EnvoyBootstrapExtensionImpl::new(envoy_ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension = extension_ptr as *mut Box; + let extension = unsafe { &mut *extension }; + extension.on_server_initialized(&mut EnvoyBootstrapExtensionImpl::new(envoy_ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_server_initialized", + panic, + ); + }); } #[no_mangle] @@ -1394,9 +1426,17 @@ pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_worker_thread_init envoy_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_envoy_ptr, extension_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_module_ptr, ) { - let extension = extension_ptr as *mut Box; - let extension = unsafe { &mut *extension }; - extension.on_worker_thread_initialized(&mut EnvoyBootstrapExtensionImpl::new(envoy_ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension = extension_ptr as *mut Box; + let extension = unsafe { &mut *extension }; + extension.on_worker_thread_initialized(&mut EnvoyBootstrapExtensionImpl::new(envoy_ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_worker_thread_initialized", + panic, + ); + }); } #[no_mangle] @@ -1404,9 +1444,17 @@ pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_drain_started( envoy_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_envoy_ptr, extension_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_module_ptr, ) { - let extension = extension_ptr as *mut Box; - let extension = unsafe { &mut *extension }; - extension.on_drain_started(&mut EnvoyBootstrapExtensionImpl::new(envoy_ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension = extension_ptr as *mut Box; + let extension = unsafe { &mut *extension }; + extension.on_drain_started(&mut EnvoyBootstrapExtensionImpl::new(envoy_ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_drain_started", + panic, + ); + }); } #[no_mangle] @@ -1416,17 +1464,30 @@ pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_shutdown( completion_callback: abi::envoy_dynamic_module_type_event_cb, completion_context: *mut std::os::raw::c_void, ) { - let extension = extension_ptr as *mut Box; - let extension = unsafe { &mut *extension }; - let completion = CompletionCallback::new(completion_callback, completion_context); - extension.on_shutdown(&mut EnvoyBootstrapExtensionImpl::new(envoy_ptr), completion); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension = extension_ptr as *mut Box; + let extension = unsafe { &mut *extension }; + let completion = CompletionCallback::new(completion_callback, completion_context); + extension.on_shutdown(&mut EnvoyBootstrapExtensionImpl::new(envoy_ptr), completion); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_shutdown", + panic, + ); + }); } #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_destroy( extension_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_module_ptr, ) { - let _ = unsafe { Box::from_raw(extension_ptr as *mut Box) }; + let _ = catch_unwind(AssertUnwindSafe(|| { + let _ = unsafe { Box::from_raw(extension_ptr as *mut Box) }; + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_bootstrap_extension_destroy", panic); + }); } #[no_mangle] @@ -1435,12 +1496,20 @@ pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_config_scheduled( extension_config_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr, event_id: u64, ) { - let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; - let extension_config = unsafe { &**extension_config }; - extension_config.on_scheduled( - &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - event_id, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; + let extension_config = unsafe { &**extension_config }; + extension_config.on_scheduled( + &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), + event_id, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_config_scheduled", + panic, + ); + }); } /// Event hook called by Envoy when an HTTP callout initiated by a bootstrap extension completes. @@ -1464,29 +1533,39 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_http_callou body_chunks: *const abi::envoy_dynamic_module_type_envoy_buffer, body_chunks_size: usize, ) { - let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; - let extension_config = unsafe { &**extension_config }; - - let headers = if headers_size > 0 { - Some(unsafe { - std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) - }) - } else { - None - }; - let body = if body_chunks_size > 0 { - Some(unsafe { std::slice::from_raw_parts(body_chunks as *const EnvoyBuffer, body_chunks_size) }) - } else { - None - }; - - extension_config.on_http_callout_done( - &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - callout_id, - result, - headers, - body, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; + let extension_config = unsafe { &**extension_config }; + + let headers = if headers_size > 0 { + Some(unsafe { + std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) + }) + } else { + None + }; + let body = if body_chunks_size > 0 { + Some(unsafe { + std::slice::from_raw_parts(body_chunks as *const EnvoyBuffer, body_chunks_size) + }) + } else { + None + }; + + extension_config.on_http_callout_done( + &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), + callout_id, + result, + headers, + body, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_http_callout_done", + panic, + ); + }); } /// Event hook called by Envoy when a timer created by a bootstrap extension fires. @@ -1496,16 +1575,24 @@ pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_timer_fired( extension_config_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr, timer_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_timer_module_ptr, ) { - let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; - let extension_config = unsafe { &**extension_config }; + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; + let extension_config = unsafe { &**extension_config }; - // Create a non-owning reference to the timer so the module can re-enable it. - let timer_ref = EnvoyBootstrapExtensionTimerRef { raw_ptr: timer_ptr }; + // Create a non-owning reference to the timer so the module can re-enable it. + let timer_ref = EnvoyBootstrapExtensionTimerRef { raw_ptr: timer_ptr }; - extension_config.on_timer_fired( - &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - &timer_ref, - ); + extension_config.on_timer_fired( + &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), + &timer_ref, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_timer_fired", + panic, + ); + }); } /// Event hook called by Envoy when a watched file changes for a bootstrap extension. @@ -1516,21 +1603,29 @@ pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_file_changed( path: abi::envoy_dynamic_module_type_envoy_buffer, events: u32, ) { - let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; - let extension_config = unsafe { &**extension_config }; - - let path_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - path.ptr as *const u8, - path.length, - )) - }; - - extension_config.on_file_changed( - &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - path_str, - events, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; + let extension_config = unsafe { &**extension_config }; + + let path_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + path.ptr as *const u8, + path.length, + )) + }; + + extension_config.on_file_changed( + &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), + path_str, + events, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_file_changed", + panic, + ); + }); } /// Event hook called by Envoy when an admin endpoint registered by a bootstrap extension is @@ -1552,44 +1647,54 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_admin_reque path: abi::envoy_dynamic_module_type_envoy_buffer, body: abi::envoy_dynamic_module_type_envoy_buffer, ) -> u32 { - let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; - let extension_config = unsafe { &**extension_config }; - - let method_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - method.ptr as *const u8, - method.length, - )) - }; - let path_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - path.ptr as *const u8, - path.length, - )) - }; - let body_slice = unsafe { std::slice::from_raw_parts(body.ptr as *const u8, body.length) }; - - let (status_code, response_str) = extension_config.on_admin_request( - &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - method_str, - path_str, - body_slice, - ); - - // Pass the response body to Envoy via the callback. Envoy copies the buffer immediately, - // so the string only needs to live until the call returns. - if !response_str.is_empty() { - let response_buf = abi::envoy_dynamic_module_type_module_buffer { - ptr: response_str.as_ptr() as *const _, - length: response_str.len(), + catch_unwind(AssertUnwindSafe(|| { + let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; + let extension_config = unsafe { &**extension_config }; + + let method_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + method.ptr as *const u8, + method.length, + )) + }; + let path_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + path.ptr as *const u8, + path.length, + )) }; - abi::envoy_dynamic_module_callback_bootstrap_extension_admin_set_response( - envoy_ptr, - response_buf, + let body_slice = unsafe { std::slice::from_raw_parts(body.ptr as *const u8, body.length) }; + + let (status_code, response_str) = extension_config.on_admin_request( + &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), + method_str, + path_str, + body_slice, ); - } - status_code + // Pass the response body to Envoy via the callback. Envoy copies the buffer immediately, + // so the string only needs to live until the call returns. + if !response_str.is_empty() { + let response_buf = abi::envoy_dynamic_module_type_module_buffer { + ptr: response_str.as_ptr() as *const _, + length: response_str.len(), + }; + abi::envoy_dynamic_module_callback_bootstrap_extension_admin_set_response( + envoy_ptr, + response_buf, + ); + } + + status_code + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_admin_request", + panic, + ); + // Fail-closed: 500 Internal Server Error. + 500 + }) } /// Event hook called by Envoy when a cluster is added to or updated in the ClusterManager. @@ -1604,20 +1709,28 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_cluster_add extension_config_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr, cluster_name: abi::envoy_dynamic_module_type_envoy_buffer, ) { - let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; - let extension_config = unsafe { &**extension_config }; - - let cluster_name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - cluster_name.ptr as *const u8, - cluster_name.length, - )) - }; - - extension_config.on_cluster_add_or_update( - &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - cluster_name_str, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; + let extension_config = unsafe { &**extension_config }; + + let cluster_name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + cluster_name.ptr as *const u8, + cluster_name.length, + )) + }; + + extension_config.on_cluster_add_or_update( + &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), + cluster_name_str, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_cluster_add_or_update", + panic, + ); + }); } /// Event hook called by Envoy when a cluster is removed from the ClusterManager. @@ -1632,20 +1745,28 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_cluster_rem extension_config_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr, cluster_name: abi::envoy_dynamic_module_type_envoy_buffer, ) { - let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; - let extension_config = unsafe { &**extension_config }; - - let cluster_name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - cluster_name.ptr as *const u8, - cluster_name.length, - )) - }; - - extension_config.on_cluster_removal( - &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - cluster_name_str, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; + let extension_config = unsafe { &**extension_config }; + + let cluster_name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + cluster_name.ptr as *const u8, + cluster_name.length, + )) + }; + + extension_config.on_cluster_removal( + &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), + cluster_name_str, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_cluster_removal", + panic, + ); + }); } /// Event hook called by Envoy when a listener is added to or updated in the ListenerManager. @@ -1660,20 +1781,28 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_listener_ad extension_config_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr, listener_name: abi::envoy_dynamic_module_type_envoy_buffer, ) { - let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; - let extension_config = unsafe { &**extension_config }; - - let listener_name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - listener_name.ptr as *const u8, - listener_name.length, - )) - }; - - extension_config.on_listener_add_or_update( - &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - listener_name_str, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; + let extension_config = unsafe { &**extension_config }; + + let listener_name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + listener_name.ptr as *const u8, + listener_name.length, + )) + }; + + extension_config.on_listener_add_or_update( + &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), + listener_name_str, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_listener_add_or_update", + panic, + ); + }); } /// Event hook called by Envoy when a listener is removed from the ListenerManager. @@ -1688,18 +1817,26 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_listener_re extension_config_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr, listener_name: abi::envoy_dynamic_module_type_envoy_buffer, ) { - let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; - let extension_config = unsafe { &**extension_config }; - - let listener_name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - listener_name.ptr as *const u8, - listener_name.length, - )) - }; - - extension_config.on_listener_removal( - &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - listener_name_str, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; + let extension_config = unsafe { &**extension_config }; + + let listener_name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + listener_name.ptr as *const u8, + listener_name.length, + )) + }; + + extension_config.on_listener_removal( + &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), + listener_name_str, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_bootstrap_extension_listener_removal", + panic, + ); + }); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs b/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs index 3c9a4975ac501..1eb0512f92db2 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs @@ -44,6 +44,7 @@ //! ``` use crate::{abi, bytes_to_module_buffer, EnvoyBuffer}; +use std::panic::{catch_unwind, AssertUnwindSafe}; /// Wrapper around the Envoy cert validator config pointer, providing access to /// Envoy-side operations such as filter state during certificate validation. @@ -253,18 +254,24 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_config_new( name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_cert_validator_config_module_ptr { - let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )); - let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length); - init_cert_validator_config( - name_str, - config_slice, - NEW_CERT_VALIDATOR_CONFIG_FUNCTION - .get() - .expect("NEW_CERT_VALIDATOR_CONFIG_FUNCTION must be set"), - ) + catch_unwind(AssertUnwindSafe(|| { + let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )); + let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length); + init_cert_validator_config( + name_str, + config_slice, + NEW_CERT_VALIDATOR_CONFIG_FUNCTION + .get() + .expect("NEW_CERT_VALIDATOR_CONFIG_FUNCTION must be set"), + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cert_validator_config_new", panic); + std::ptr::null() + }) } pub(crate) fn init_cert_validator_config( @@ -286,7 +293,15 @@ pub(crate) fn init_cert_validator_config( pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_config_destroy( config_ptr: abi::envoy_dynamic_module_type_cert_validator_config_module_ptr, ) { - drop_wrapped_c_void_ptr!(config_ptr, CertValidatorConfig); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!(config_ptr, CertValidatorConfig); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_cert_validator_config_destroy", + panic, + ); + }); } /// # Safety @@ -302,45 +317,57 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_do_verify_cert_c host_name: abi::envoy_dynamic_module_type_envoy_buffer, is_server: bool, ) -> abi::envoy_dynamic_module_type_cert_validator_validation_result { - let config = { - let raw = config_module_ptr as *const *const dyn CertValidatorConfig; - &**raw - }; - - let envoy_cert_validator = EnvoyCertValidator::new(config_envoy_ptr); + catch_unwind(AssertUnwindSafe(|| { + let config = { + let raw = config_module_ptr as *const *const dyn CertValidatorConfig; + &**raw + }; - let cert_buffers = std::slice::from_raw_parts(certs, certs_count); - let cert_slices: Vec<&[u8]> = cert_buffers - .iter() - .map(|buf| std::slice::from_raw_parts(buf.ptr as *const u8, buf.length)) - .collect(); + let envoy_cert_validator = EnvoyCertValidator::new(config_envoy_ptr); - let host_name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - host_name.ptr as *const _, - host_name.length, - )); + let cert_buffers = std::slice::from_raw_parts(certs, certs_count); + let cert_slices: Vec<&[u8]> = cert_buffers + .iter() + .map(|buf| std::slice::from_raw_parts(buf.ptr as *const u8, buf.length)) + .collect(); - let result = config.do_verify_cert_chain( - &envoy_cert_validator, - &cert_slices, - host_name_str, - is_server, - ); + let host_name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( + host_name.ptr as *const _, + host_name.length, + )); - // If the module provided error details, pass them to Envoy via the callback. - // Envoy copies the buffer immediately, so the string only needs to live until the call returns. - if let Some(ref error) = result.error_details { - let error_buf = abi::envoy_dynamic_module_type_module_buffer { - ptr: error.as_ptr() as *const _, - length: error.len(), - }; - abi::envoy_dynamic_module_callback_cert_validator_set_error_details( - config_envoy_ptr, - error_buf, + let result = config.do_verify_cert_chain( + &envoy_cert_validator, + &cert_slices, + host_name_str, + is_server, ); - } - abi::envoy_dynamic_module_type_cert_validator_validation_result::from(&result) + // If the module provided error details, pass them to Envoy via the callback. + // Envoy copies the buffer immediately, so the string only needs to live until the call returns. + if let Some(ref error) = result.error_details { + let error_buf = abi::envoy_dynamic_module_type_module_buffer { + ptr: error.as_ptr() as *const _, + length: error.len(), + }; + abi::envoy_dynamic_module_callback_cert_validator_set_error_details( + config_envoy_ptr, + error_buf, + ); + } + + abi::envoy_dynamic_module_type_cert_validator_validation_result::from(&result) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_cert_validator_do_verify_cert_chain", + panic, + ); + // Fail-closed: a panic during cert validation must not appear as success. + abi::envoy_dynamic_module_type_cert_validator_validation_result::from( + &ValidationResult::failed(ClientValidationStatus::Failed, None, None), + ) + }) } /// # Safety @@ -352,11 +379,22 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_get_ssl_verify_m config_module_ptr: abi::envoy_dynamic_module_type_cert_validator_config_module_ptr, handshaker_provides_certificates: bool, ) -> std::os::raw::c_int { - let config = { - let raw = config_module_ptr as *const *const dyn CertValidatorConfig; - &**raw - }; - config.get_ssl_verify_mode(handshaker_provides_certificates) + catch_unwind(AssertUnwindSafe(|| { + let config = { + let raw = config_module_ptr as *const *const dyn CertValidatorConfig; + &**raw + }; + config.get_ssl_verify_mode(handshaker_provides_certificates) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_cert_validator_get_ssl_verify_mode", + panic, + ); + // Fail-closed: SSL_VERIFY_PEER (0x01) | SSL_VERIFY_FAIL_IF_NO_PEER_CERT (0x02) = 0x03, + // the strictest mode. A panic must not silently degrade to SSL_VERIFY_NONE (0x00). + 0x03 + }) } /// # Safety @@ -368,11 +406,25 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_update_digest( config_module_ptr: abi::envoy_dynamic_module_type_cert_validator_config_module_ptr, out_data: *mut abi::envoy_dynamic_module_type_module_buffer, ) { - let config = { - let raw = config_module_ptr as *const *const dyn CertValidatorConfig; - &**raw - }; - let digest = config.update_digest(); - (*out_data).ptr = digest.as_ptr() as *const _; - (*out_data).length = digest.len(); + let _ = catch_unwind(AssertUnwindSafe(|| { + let config = { + let raw = config_module_ptr as *const *const dyn CertValidatorConfig; + &**raw + }; + let digest = config.update_digest(); + (*out_data).ptr = digest.as_ptr() as *const _; + (*out_data).length = digest.len(); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_cert_validator_update_digest", + panic, + ); + // On panic, leave `out_data` as an empty buffer so Envoy contributes nothing to the + // session context hash and avoids reading uninitialized memory. + if !out_data.is_null() { + (*out_data).ptr = std::ptr::null(); + (*out_data).length = 0; + } + }); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs b/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs index e6666fe4d0503..ed8d7ba7a173e 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs @@ -5,6 +5,7 @@ use crate::{ EnvoyHistogramId, EnvoyHistogramVecId, NEW_CLUSTER_CONFIG_FUNCTION, }; use mockall::*; +use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::Arc; /// The module-side cluster configuration. @@ -1892,22 +1893,28 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_config_new( name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_cluster_config_module_ptr { - // SAFETY: Envoy guarantees name and config are valid UTF-8 per the ABI contract. - let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )); - let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length); - let new_config_fn = NEW_CLUSTER_CONFIG_FUNCTION - .get() - .expect("NEW_CLUSTER_CONFIG_FUNCTION must be set"); - let envoy_cluster_metrics: Arc = Arc::new(EnvoyClusterMetricsImpl { - raw: config_envoy_ptr, - }); - match new_config_fn(name_str, config_slice, envoy_cluster_metrics) { - Some(config) => wrap_into_c_void_ptr!(config), - None => std::ptr::null(), - } + catch_unwind(AssertUnwindSafe(|| { + // SAFETY: Envoy guarantees name and config are valid UTF-8 per the ABI contract. + let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )); + let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length); + let new_config_fn = NEW_CLUSTER_CONFIG_FUNCTION + .get() + .expect("NEW_CLUSTER_CONFIG_FUNCTION must be set"); + let envoy_cluster_metrics: Arc = Arc::new(EnvoyClusterMetricsImpl { + raw: config_envoy_ptr, + }); + match new_config_fn(name_str, config_slice, envoy_cluster_metrics) { + Some(config) => wrap_into_c_void_ptr!(config), + None => std::ptr::null(), + } + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_config_new", panic); + std::ptr::null() + }) } /// # Safety @@ -1918,7 +1925,12 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_config_new( pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_config_destroy( config_module_ptr: abi::envoy_dynamic_module_type_cluster_config_module_ptr, ) { - drop_wrapped_c_void_ptr!(config_module_ptr, ClusterConfig); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!(config_module_ptr, ClusterConfig); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_config_destroy", panic); + }); } /// # Safety @@ -1930,11 +1942,17 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_new( config_module_ptr: abi::envoy_dynamic_module_type_cluster_config_module_ptr, cluster_envoy_ptr: abi::envoy_dynamic_module_type_cluster_envoy_ptr, ) -> abi::envoy_dynamic_module_type_cluster_module_ptr { - let config = config_module_ptr as *const *const dyn ClusterConfig; - let config = &**config; - let envoy_cluster = EnvoyClusterImpl::new(cluster_envoy_ptr); - let cluster = config.new_cluster(&envoy_cluster); - wrap_into_c_void_ptr!(cluster) + catch_unwind(AssertUnwindSafe(|| { + let config = config_module_ptr as *const *const dyn ClusterConfig; + let config = &**config; + let envoy_cluster = EnvoyClusterImpl::new(cluster_envoy_ptr); + let cluster = config.new_cluster(&envoy_cluster); + wrap_into_c_void_ptr!(cluster) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_new", panic); + std::ptr::null() + }) } /// # Safety @@ -1946,10 +1964,15 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_init( cluster_envoy_ptr: abi::envoy_dynamic_module_type_cluster_envoy_ptr, cluster_module_ptr: abi::envoy_dynamic_module_type_cluster_module_ptr, ) { - let cluster = cluster_module_ptr as *mut Box; - let cluster = &mut *cluster; - let envoy_cluster = EnvoyClusterImpl::new(cluster_envoy_ptr); - cluster.on_init(&envoy_cluster); + let _ = catch_unwind(AssertUnwindSafe(|| { + let cluster = cluster_module_ptr as *mut Box; + let cluster = &mut *cluster; + let envoy_cluster = EnvoyClusterImpl::new(cluster_envoy_ptr); + cluster.on_init(&envoy_cluster); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_init", panic); + }); } /// # Safety @@ -1960,7 +1983,12 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_init( pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_destroy( cluster_module_ptr: abi::envoy_dynamic_module_type_cluster_module_ptr, ) { - drop_wrapped_c_void_ptr!(cluster_module_ptr, Cluster); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!(cluster_module_ptr, Cluster); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_destroy", panic); + }); } /// Wrapper that pairs a module-side load balancer with the Envoy-side LB pointer. @@ -1980,12 +2008,18 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_lb_new( cluster_module_ptr: abi::envoy_dynamic_module_type_cluster_module_ptr, lb_envoy_ptr: abi::envoy_dynamic_module_type_cluster_lb_envoy_ptr, ) -> abi::envoy_dynamic_module_type_cluster_lb_module_ptr { - let cluster = cluster_module_ptr as *const *const dyn Cluster; - let cluster = &**cluster; - let envoy_lb = EnvoyClusterLoadBalancerImpl::new(lb_envoy_ptr); - let lb = cluster.new_load_balancer(&envoy_lb); - let wrapper = Box::new(ClusterLbWrapper { lb, lb_envoy_ptr }); - Box::into_raw(wrapper) as abi::envoy_dynamic_module_type_cluster_lb_module_ptr + catch_unwind(AssertUnwindSafe(|| { + let cluster = cluster_module_ptr as *const *const dyn Cluster; + let cluster = &**cluster; + let envoy_lb = EnvoyClusterLoadBalancerImpl::new(lb_envoy_ptr); + let lb = cluster.new_load_balancer(&envoy_lb); + let wrapper = Box::new(ClusterLbWrapper { lb, lb_envoy_ptr }); + Box::into_raw(wrapper) as abi::envoy_dynamic_module_type_cluster_lb_module_ptr + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_lb_new", panic); + std::ptr::null() + }) } /// # Safety @@ -1996,8 +2030,13 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_lb_new( pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_lb_destroy( lb_module_ptr: abi::envoy_dynamic_module_type_cluster_lb_module_ptr, ) { - let wrapper = lb_module_ptr as *mut ClusterLbWrapper; - let _ = Box::from_raw(wrapper); + let _ = catch_unwind(AssertUnwindSafe(|| { + let wrapper = lb_module_ptr as *mut ClusterLbWrapper; + let _ = Box::from_raw(wrapper); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_lb_destroy", panic); + }); } /// # Safety @@ -2011,41 +2050,54 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_lb_choose_host( host_out: *mut abi::envoy_dynamic_module_type_cluster_host_envoy_ptr, async_handle_out: *mut abi::envoy_dynamic_module_type_cluster_lb_async_handle_module_ptr, ) { - let wrapper = &mut *(lb_module_ptr as *mut ClusterLbWrapper); - let context = if context_envoy_ptr.is_null() { - None - } else { - Some(ClusterLbContextImpl::new( - context_envoy_ptr, - wrapper.lb_envoy_ptr, - )) - }; - - let async_completion = Box::new(EnvoyAsyncHostSelectionCompleteImpl { - raw_lb: wrapper.lb_envoy_ptr, - raw_context: context_envoy_ptr, - }); - - let result = wrapper.lb.choose_host( - context.as_ref().map(|c| c as &dyn ClusterLbContext), - async_completion, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let wrapper = &mut *(lb_module_ptr as *mut ClusterLbWrapper); + let context = if context_envoy_ptr.is_null() { + None + } else { + Some(ClusterLbContextImpl::new( + context_envoy_ptr, + wrapper.lb_envoy_ptr, + )) + }; - match result { - HostSelectionResult::Selected(host) => { - *host_out = host; - *async_handle_out = std::ptr::null_mut(); - }, - HostSelectionResult::NoHost => { + let async_completion = Box::new(EnvoyAsyncHostSelectionCompleteImpl { + raw_lb: wrapper.lb_envoy_ptr, + raw_context: context_envoy_ptr, + }); + + let result = wrapper.lb.choose_host( + context.as_ref().map(|c| c as &dyn ClusterLbContext), + async_completion, + ); + + match result { + HostSelectionResult::Selected(host) => { + *host_out = host; + *async_handle_out = std::ptr::null_mut(); + }, + HostSelectionResult::NoHost => { + *host_out = std::ptr::null_mut(); + *async_handle_out = std::ptr::null_mut(); + }, + HostSelectionResult::AsyncPending(handle) => { + *host_out = std::ptr::null_mut(); + *async_handle_out = Box::into_raw(Box::new(handle)) + as abi::envoy_dynamic_module_type_cluster_lb_async_handle_module_ptr; + }, + } + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_lb_choose_host", panic); + // Fail-closed: signal NoHost so Envoy returns 503 rather than dispatching to + // a stale or uninitialised pointer. + if !host_out.is_null() { *host_out = std::ptr::null_mut(); + } + if !async_handle_out.is_null() { *async_handle_out = std::ptr::null_mut(); - }, - HostSelectionResult::AsyncPending(handle) => { - *host_out = std::ptr::null_mut(); - *async_handle_out = Box::into_raw(Box::new(handle)) - as abi::envoy_dynamic_module_type_cluster_lb_async_handle_module_ptr; - }, - } + } + }); } /// # Safety @@ -2057,9 +2109,17 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_lb_cancel_host_selectio _lb_module_ptr: abi::envoy_dynamic_module_type_cluster_lb_module_ptr, async_handle_module_ptr: abi::envoy_dynamic_module_type_cluster_lb_async_handle_module_ptr, ) { - let handle = async_handle_module_ptr as *mut Box; - let mut handle = Box::from_raw(handle); - handle.cancel(); + let _ = catch_unwind(AssertUnwindSafe(|| { + let handle = async_handle_module_ptr as *mut Box; + let mut handle = Box::from_raw(handle); + handle.cancel(); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_cluster_lb_cancel_host_selection", + panic, + ); + }); } /// # Safety @@ -2073,11 +2133,19 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_lb_on_host_membership_u num_hosts_added: usize, num_hosts_removed: usize, ) { - let wrapper = &mut *(lb_module_ptr as *mut ClusterLbWrapper); - let envoy_lb = EnvoyClusterLoadBalancerImpl::new(lb_envoy_ptr); - wrapper - .lb - .on_host_membership_update(&envoy_lb, num_hosts_added, num_hosts_removed); + let _ = catch_unwind(AssertUnwindSafe(|| { + let wrapper = &mut *(lb_module_ptr as *mut ClusterLbWrapper); + let envoy_lb = EnvoyClusterLoadBalancerImpl::new(lb_envoy_ptr); + wrapper + .lb + .on_host_membership_update(&envoy_lb, num_hosts_added, num_hosts_removed); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_cluster_lb_on_host_membership_update", + panic, + ); + }); } /// # Safety @@ -2090,9 +2158,14 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_scheduled( cluster_module_ptr: abi::envoy_dynamic_module_type_cluster_module_ptr, event_id: u64, ) { - let cluster = cluster_module_ptr as *const *const dyn Cluster; - let cluster = &**cluster; - cluster.on_scheduled(&EnvoyClusterImpl::new(cluster_envoy_ptr), event_id); + let _ = catch_unwind(AssertUnwindSafe(|| { + let cluster = cluster_module_ptr as *const *const dyn Cluster; + let cluster = &**cluster; + cluster.on_scheduled(&EnvoyClusterImpl::new(cluster_envoy_ptr), event_id); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_scheduled", panic); + }); } /// # Safety @@ -2104,9 +2177,14 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_server_initialized( cluster_envoy_ptr: abi::envoy_dynamic_module_type_cluster_envoy_ptr, cluster_module_ptr: abi::envoy_dynamic_module_type_cluster_module_ptr, ) { - let cluster = cluster_module_ptr as *mut Box; - let cluster = &mut *cluster; - cluster.on_server_initialized(&EnvoyClusterImpl::new(cluster_envoy_ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let cluster = cluster_module_ptr as *mut Box; + let cluster = &mut *cluster; + cluster.on_server_initialized(&EnvoyClusterImpl::new(cluster_envoy_ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_server_initialized", panic); + }); } /// # Safety @@ -2118,9 +2196,14 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_drain_started( cluster_envoy_ptr: abi::envoy_dynamic_module_type_cluster_envoy_ptr, cluster_module_ptr: abi::envoy_dynamic_module_type_cluster_module_ptr, ) { - let cluster = cluster_module_ptr as *mut Box; - let cluster = &mut *cluster; - cluster.on_drain_started(&EnvoyClusterImpl::new(cluster_envoy_ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let cluster = cluster_module_ptr as *mut Box; + let cluster = &mut *cluster; + cluster.on_drain_started(&EnvoyClusterImpl::new(cluster_envoy_ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_drain_started", panic); + }); } /// # Safety @@ -2134,10 +2217,15 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_shutdown( completion_callback: abi::envoy_dynamic_module_type_event_cb, completion_context: *mut std::os::raw::c_void, ) { - let cluster = cluster_module_ptr as *mut Box; - let cluster = &mut *cluster; - let completion = CompletionCallback::new(completion_callback, completion_context); - cluster.on_shutdown(&EnvoyClusterImpl::new(cluster_envoy_ptr), completion); + let _ = catch_unwind(AssertUnwindSafe(|| { + let cluster = cluster_module_ptr as *mut Box; + let cluster = &mut *cluster; + let completion = CompletionCallback::new(completion_callback, completion_context); + cluster.on_shutdown(&EnvoyClusterImpl::new(cluster_envoy_ptr), completion); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_shutdown", panic); + }); } /// # Safety @@ -2155,31 +2243,36 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_http_callout_done( body_chunks: *const abi::envoy_dynamic_module_type_envoy_buffer, body_chunks_size: usize, ) { - let cluster = cluster_module_ptr as *mut Box; - let cluster = &mut *cluster; - - let headers = if headers_size > 0 { - Some(std::slice::from_raw_parts( - headers as *const (EnvoyBuffer, EnvoyBuffer), - headers_size, - )) - } else { - None - }; - let body = if body_chunks_size > 0 { - Some(std::slice::from_raw_parts( - body_chunks as *const EnvoyBuffer, - body_chunks_size, - )) - } else { - None - }; - - cluster.on_http_callout_done( - &EnvoyClusterImpl::new(cluster_envoy_ptr), - callout_id, - result, - headers, - body, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let cluster = cluster_module_ptr as *mut Box; + let cluster = &mut *cluster; + + let headers = if headers_size > 0 { + Some(std::slice::from_raw_parts( + headers as *const (EnvoyBuffer, EnvoyBuffer), + headers_size, + )) + } else { + None + }; + let body = if body_chunks_size > 0 { + Some(std::slice::from_raw_parts( + body_chunks as *const EnvoyBuffer, + body_chunks_size, + )) + } else { + None + }; + + cluster.on_http_callout_done( + &EnvoyClusterImpl::new(cluster_envoy_ptr), + callout_id, + result, + headers, + body, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_cluster_http_callout_done", panic); + }); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/dns_resolver.rs b/source/extensions/dynamic_modules/sdk/rust/src/dns_resolver.rs index a08f5f0b3ee2f..f7d0f25298c17 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/dns_resolver.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/dns_resolver.rs @@ -636,7 +636,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolver_config_new( } })) .unwrap_or_else(|panic| { - log_panic("envoy_dynamic_module_on_dns_resolver_config_new", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_dns_resolver_config_new", panic); std::ptr::null() }) } @@ -653,7 +653,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolver_config_destroy( drop_wrapped_c_void_ptr!(config_module_ptr, DnsResolverConfig); })) .map_err(|panic| { - log_panic("envoy_dynamic_module_on_dns_resolver_config_destroy", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_dns_resolver_config_destroy", panic); }); } @@ -676,7 +676,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolver_new( Box::into_raw(wrapper) as abi::envoy_dynamic_module_type_dns_resolver_module_ptr })) .unwrap_or_else(|panic| { - log_panic("envoy_dynamic_module_on_dns_resolver_new", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_dns_resolver_new", panic); std::ptr::null() }) } @@ -694,7 +694,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolver_destroy( let _ = unsafe { Box::from_raw(wrapper) }; })) .map_err(|panic| { - log_panic("envoy_dynamic_module_on_dns_resolver_destroy", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_dns_resolver_destroy", panic); }); } @@ -735,7 +735,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolve( } })) .unwrap_or_else(|panic| { - log_panic("envoy_dynamic_module_on_dns_resolve", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_dns_resolve", panic); std::ptr::null_mut() }) } @@ -755,7 +755,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolve_cancel( query.cancel(); })) .map_err(|panic| { - log_panic("envoy_dynamic_module_on_dns_resolve_cancel", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_dns_resolve_cancel", panic); }); } @@ -772,18 +772,9 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolver_reset_networking( wrapper.resolver.reset_networking(); })) .map_err(|panic| { - log_panic( + crate::log_ffi_panic( "envoy_dynamic_module_on_dns_resolver_reset_networking", panic, ); }); } - -/// Log a panic caught at an FFI boundary. -fn log_panic(function_name: &str, panic: Box) { - crate::envoy_log_error!( - "{}: caught panic: {}", - function_name, - crate::panic_payload_to_string(panic) - ); -} diff --git a/source/extensions/dynamic_modules/sdk/rust/src/http.rs b/source/extensions/dynamic_modules/sdk/rust/src/http.rs index 7262483cefa19..da003ef6956f0 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/http.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/http.rs @@ -9,6 +9,7 @@ use crate::{ }; use mockall::*; use std::any::Any; +use std::panic::{catch_unwind, AssertUnwindSafe}; /// The trait that represents the configuration for an Envoy Http filter configuration. /// This has one to one mapping with the [`EnvoyHttpFilterConfig`] object. @@ -3801,28 +3802,34 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_new( name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_http_filter_config_module_ptr { - // This assumes that the name is a valid UTF-8 string. Should we relax? At the moment, - // it is a String at protobuf level. - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) - }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; - - let mut envoy_filter_config = EnvoyHttpFilterConfigImpl { - raw_ptr: envoy_filter_config_ptr, - }; - - envoy_dynamic_module_on_http_filter_config_new_impl( - &mut envoy_filter_config, - name_str, - config_slice, - NEW_HTTP_FILTER_CONFIG_FUNCTION - .get() - .expect("NEW_HTTP_FILTER_CONFIG_FUNCTION must be set"), - ) + catch_unwind(AssertUnwindSafe(|| { + // This assumes that the name is a valid UTF-8 string. Should we relax? At the moment, + // it is a String at protobuf level. + let name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )) + }; + let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; + + let mut envoy_filter_config = EnvoyHttpFilterConfigImpl { + raw_ptr: envoy_filter_config_ptr, + }; + + envoy_dynamic_module_on_http_filter_config_new_impl( + &mut envoy_filter_config, + name_str, + config_slice, + NEW_HTTP_FILTER_CONFIG_FUNCTION + .get() + .expect("NEW_HTTP_FILTER_CONFIG_FUNCTION must be set"), + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_http_filter_config_new", panic); + std::ptr::null() + }) } pub fn envoy_dynamic_module_on_http_filter_config_new_impl( @@ -3846,7 +3853,12 @@ pub fn envoy_dynamic_module_on_http_filter_config_new_impl( pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_destroy( config_ptr: abi::envoy_dynamic_module_type_http_filter_config_module_ptr, ) { - crate::drop_wrapped_c_void_ptr!(config_ptr, HttpFilterConfig); + let _ = catch_unwind(AssertUnwindSafe(|| { + crate::drop_wrapped_c_void_ptr!(config_ptr, HttpFilterConfig); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_http_filter_config_destroy", panic); + }); } /// # Safety @@ -3859,9 +3871,17 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_scheduled( config_ptr: abi::envoy_dynamic_module_type_http_filter_config_module_ptr, event_id: u64, ) { - let config = config_ptr as *mut *mut dyn HttpFilterConfig; - let config = &**config; - config.on_scheduled(event_id); + let _ = catch_unwind(AssertUnwindSafe(|| { + let config = config_ptr as *mut *mut dyn HttpFilterConfig; + let config = &**config; + config.on_scheduled(event_id); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_config_scheduled", + panic, + ); + }); } /// # Safety @@ -3873,23 +3893,32 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_per_route_config_ne name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_http_filter_per_route_config_module_ptr { - // This assumes that the name is a valid UTF-8 string. Should we relax? At the moment, - // it is a String at protobuf level. - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) - }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; - - envoy_dynamic_module_on_http_filter_per_route_config_new_impl( - name_str, - config_slice, - NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION - .get() - .expect("NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION must be set"), - ) + catch_unwind(AssertUnwindSafe(|| { + // This assumes that the name is a valid UTF-8 string. Should we relax? At the moment, + // it is a String at protobuf level. + let name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )) + }; + let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; + + envoy_dynamic_module_on_http_filter_per_route_config_new_impl( + name_str, + config_slice, + NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION + .get() + .expect("NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION must be set"), + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_per_route_config_new", + panic, + ); + std::ptr::null() + }) } /// # Safety @@ -3900,8 +3929,16 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_per_route_config_ne pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_per_route_config_destroy( config_ptr: abi::envoy_dynamic_module_type_http_filter_per_route_config_module_ptr, ) { - let ptr = config_ptr as *mut std::sync::Arc; - std::mem::drop(Box::from_raw(ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let ptr = config_ptr as *mut std::sync::Arc; + std::mem::drop(Box::from_raw(ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_per_route_config_destroy", + panic, + ); + }); } pub fn envoy_dynamic_module_on_http_filter_per_route_config_new_impl( @@ -3927,14 +3964,20 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_new( filter_config_ptr: abi::envoy_dynamic_module_type_http_filter_config_module_ptr, filter_envoy_ptr: abi::envoy_dynamic_module_type_http_filter_envoy_ptr, ) -> abi::envoy_dynamic_module_type_http_filter_module_ptr { - let mut envoy_filter = EnvoyHttpFilterImpl { - raw_ptr: filter_envoy_ptr, - }; - let filter_config = { - let raw = filter_config_ptr as *const *const dyn HttpFilterConfig; - &**raw - }; - envoy_dynamic_module_on_http_filter_new_impl(&mut envoy_filter, filter_config) + catch_unwind(AssertUnwindSafe(|| { + let mut envoy_filter = EnvoyHttpFilterImpl { + raw_ptr: filter_envoy_ptr, + }; + let filter_config = { + let raw = filter_config_ptr as *const *const dyn HttpFilterConfig; + &**raw + }; + envoy_dynamic_module_on_http_filter_new_impl(&mut envoy_filter, filter_config) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_http_filter_new", panic); + std::ptr::null() + }) } pub fn envoy_dynamic_module_on_http_filter_new_impl( @@ -3953,7 +3996,12 @@ pub fn envoy_dynamic_module_on_http_filter_new_impl( pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_destroy( filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, ) { - crate::drop_wrapped_c_void_ptr!(filter_ptr, HttpFilter); + let _ = catch_unwind(AssertUnwindSafe(|| { + crate::drop_wrapped_c_void_ptr!(filter_ptr, HttpFilter); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_http_filter_destroy", panic); + }); } /// # Safety @@ -3965,9 +4013,14 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_stream_complete( envoy_ptr: abi::envoy_dynamic_module_type_http_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, ) { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_stream_complete(&mut EnvoyHttpFilterImpl::new(envoy_ptr)) + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_stream_complete(&mut EnvoyHttpFilterImpl::new(envoy_ptr)) + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_http_filter_stream_complete", panic); + }); } /// # Safety @@ -3980,9 +4033,16 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_request_headers( filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, end_of_stream: bool, ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_request_headers(&mut EnvoyHttpFilterImpl::new(envoy_ptr), end_of_stream) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_request_headers(&mut EnvoyHttpFilterImpl::new(envoy_ptr), end_of_stream) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_http_filter_request_headers", panic); + // Fail-closed: stop iteration so the request never reaches upstream after a panic. + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration + }) } /// # Safety @@ -3995,9 +4055,16 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_request_body( filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, end_of_stream: bool, ) -> abi::envoy_dynamic_module_type_on_http_filter_request_body_status { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_request_body(&mut EnvoyHttpFilterImpl::new(envoy_ptr), end_of_stream) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_request_body(&mut EnvoyHttpFilterImpl::new(envoy_ptr), end_of_stream) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_http_filter_request_body", panic); + // Fail-closed: stop iteration without buffering further data. + abi::envoy_dynamic_module_type_on_http_filter_request_body_status::StopIterationNoBuffer + }) } /// # Safety @@ -4009,9 +4076,18 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_request_trailers( envoy_ptr: abi::envoy_dynamic_module_type_http_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, ) -> abi::envoy_dynamic_module_type_on_http_filter_request_trailers_status { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_request_trailers(&mut EnvoyHttpFilterImpl::new(envoy_ptr)) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_request_trailers(&mut EnvoyHttpFilterImpl::new(envoy_ptr)) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_request_trailers", + panic, + ); + abi::envoy_dynamic_module_type_on_http_filter_request_trailers_status::StopIteration + }) } /// # Safety @@ -4024,9 +4100,18 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_response_headers( filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, end_of_stream: bool, ) -> abi::envoy_dynamic_module_type_on_http_filter_response_headers_status { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_response_headers(&mut EnvoyHttpFilterImpl::new(envoy_ptr), end_of_stream) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_response_headers(&mut EnvoyHttpFilterImpl::new(envoy_ptr), end_of_stream) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_response_headers", + panic, + ); + abi::envoy_dynamic_module_type_on_http_filter_response_headers_status::StopIteration + }) } /// # Safety @@ -4039,9 +4124,15 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_response_body( filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, end_of_stream: bool, ) -> abi::envoy_dynamic_module_type_on_http_filter_response_body_status { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_response_body(&mut EnvoyHttpFilterImpl::new(envoy_ptr), end_of_stream) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_response_body(&mut EnvoyHttpFilterImpl::new(envoy_ptr), end_of_stream) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_http_filter_response_body", panic); + abi::envoy_dynamic_module_type_on_http_filter_response_body_status::StopIterationNoBuffer + }) } /// # Safety @@ -4053,9 +4144,18 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_response_trailers( envoy_ptr: abi::envoy_dynamic_module_type_http_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, ) -> abi::envoy_dynamic_module_type_on_http_filter_response_trailers_status { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_response_trailers(&mut EnvoyHttpFilterImpl::new(envoy_ptr)) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_response_trailers(&mut EnvoyHttpFilterImpl::new(envoy_ptr)) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_response_trailers", + panic, + ); + abi::envoy_dynamic_module_type_on_http_filter_response_trailers_status::StopIteration + }) } /// # Safety @@ -4073,27 +4173,37 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_callout_done( body_chunks: *const abi::envoy_dynamic_module_type_envoy_buffer, body_chunks_size: usize, ) { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - let headers = if headers_size > 0 { - Some(unsafe { - std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) - }) - } else { - None - }; - let body = if body_chunks_size > 0 { - Some(unsafe { std::slice::from_raw_parts(body_chunks as *const EnvoyBuffer, body_chunks_size) }) - } else { - None - }; - filter.on_http_callout_done( - &mut EnvoyHttpFilterImpl::new(envoy_ptr), - callout_id, - result, - headers, - body, - ) + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + let headers = if headers_size > 0 { + Some(unsafe { + std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) + }) + } else { + None + }; + let body = if body_chunks_size > 0 { + Some(unsafe { + std::slice::from_raw_parts(body_chunks as *const EnvoyBuffer, body_chunks_size) + }) + } else { + None + }; + filter.on_http_callout_done( + &mut EnvoyHttpFilterImpl::new(envoy_ptr), + callout_id, + result, + headers, + body, + ) + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_http_callout_done", + panic, + ); + }); } /// # Safety @@ -4106,9 +4216,14 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_scheduled( filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, event_id: u64, ) { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_scheduled(&mut EnvoyHttpFilterImpl::new(envoy_ptr), event_id); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_scheduled(&mut EnvoyHttpFilterImpl::new(envoy_ptr), event_id); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_http_filter_scheduled", panic); + }); } /// # Safety @@ -4120,9 +4235,18 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_downstream_above_wr envoy_ptr: abi::envoy_dynamic_module_type_http_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, ) { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_downstream_above_write_buffer_high_watermark(&mut EnvoyHttpFilterImpl::new(envoy_ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter + .on_downstream_above_write_buffer_high_watermark(&mut EnvoyHttpFilterImpl::new(envoy_ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_downstream_above_write_buffer_high_watermark", + panic, + ); + }); } /// # Safety @@ -4134,9 +4258,17 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_downstream_below_wr envoy_ptr: abi::envoy_dynamic_module_type_http_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, ) { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_downstream_below_write_buffer_low_watermark(&mut EnvoyHttpFilterImpl::new(envoy_ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_downstream_below_write_buffer_low_watermark(&mut EnvoyHttpFilterImpl::new(envoy_ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_downstream_below_write_buffer_low_watermark", + panic, + ); + }); } /// # Safety @@ -4151,15 +4283,22 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_local_reply( details: abi::envoy_dynamic_module_type_envoy_buffer, reset_imminent: bool, ) -> abi::envoy_dynamic_module_type_on_http_filter_local_reply_status { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - let details_buffer = EnvoyBuffer::new_from_raw(details.ptr as *const u8, details.length); - filter.on_local_reply( - &mut EnvoyHttpFilterImpl::new(envoy_ptr), - response_code, - details_buffer, - reset_imminent, - ) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + let details_buffer = EnvoyBuffer::new_from_raw(details.ptr as *const u8, details.length); + filter.on_local_reply( + &mut EnvoyHttpFilterImpl::new(envoy_ptr), + response_code, + details_buffer, + reset_imminent, + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_http_filter_local_reply", panic); + // Continue with the planned local reply; do not escalate to a stream reset on panic. + abi::envoy_dynamic_module_type_on_http_filter_local_reply_status::Continue + }) } /// # Safety @@ -4175,21 +4314,29 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_stream_headers headers_size: usize, end_stream: bool, ) { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - let headers = if headers_size > 0 { - unsafe { - std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) - } - } else { - &[] - }; - filter.on_http_stream_headers( - &mut EnvoyHttpFilterImpl::new(envoy_ptr), - stream_handle, - headers, - end_stream, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + let headers = if headers_size > 0 { + unsafe { + std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) + } + } else { + &[] + }; + filter.on_http_stream_headers( + &mut EnvoyHttpFilterImpl::new(envoy_ptr), + stream_handle, + headers, + end_stream, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_http_stream_headers", + panic, + ); + }); } /// # Safety @@ -4205,19 +4352,27 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_stream_data( data_count: usize, end_stream: bool, ) { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - let data = if data_count > 0 { - unsafe { std::slice::from_raw_parts(data as *const EnvoyBuffer, data_count) } - } else { - &[] - }; - filter.on_http_stream_data( - &mut EnvoyHttpFilterImpl::new(envoy_ptr), - stream_handle, - data, - end_stream, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + let data = if data_count > 0 { + unsafe { std::slice::from_raw_parts(data as *const EnvoyBuffer, data_count) } + } else { + &[] + }; + filter.on_http_stream_data( + &mut EnvoyHttpFilterImpl::new(envoy_ptr), + stream_handle, + data, + end_stream, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_http_stream_data", + panic, + ); + }); } /// # Safety @@ -4232,20 +4387,28 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_stream_trailer trailers: *const abi::envoy_dynamic_module_type_envoy_http_header, trailers_size: usize, ) { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - let trailers = if trailers_size > 0 { - unsafe { - std::slice::from_raw_parts(trailers as *const (EnvoyBuffer, EnvoyBuffer), trailers_size) - } - } else { - &[] - }; - filter.on_http_stream_trailers( - &mut EnvoyHttpFilterImpl::new(envoy_ptr), - stream_handle, - trailers, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + let trailers = if trailers_size > 0 { + unsafe { + std::slice::from_raw_parts(trailers as *const (EnvoyBuffer, EnvoyBuffer), trailers_size) + } + } else { + &[] + }; + filter.on_http_stream_trailers( + &mut EnvoyHttpFilterImpl::new(envoy_ptr), + stream_handle, + trailers, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_http_stream_trailers", + panic, + ); + }); } /// # Safety @@ -4258,9 +4421,17 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_stream_complet filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr, stream_handle: u64, ) { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_http_stream_complete(&mut EnvoyHttpFilterImpl::new(envoy_ptr), stream_handle); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_http_stream_complete(&mut EnvoyHttpFilterImpl::new(envoy_ptr), stream_handle); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_http_stream_complete", + panic, + ); + }); } /// # Safety @@ -4274,13 +4445,21 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_stream_reset( stream_handle: u64, reset_reason: abi::envoy_dynamic_module_type_http_stream_reset_reason, ) { - let filter = filter_ptr as *mut *mut dyn HttpFilter; - let filter = &mut **filter; - filter.on_http_stream_reset( - &mut EnvoyHttpFilterImpl::new(envoy_ptr), - stream_handle, - reset_reason, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut *mut dyn HttpFilter; + let filter = &mut **filter; + filter.on_http_stream_reset( + &mut EnvoyHttpFilterImpl::new(envoy_ptr), + stream_handle, + reset_reason, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_http_stream_reset", + panic, + ); + }); } /// # Safety @@ -4298,29 +4477,39 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_http_callout body_chunks: *const abi::envoy_dynamic_module_type_envoy_buffer, body_chunks_size: usize, ) { - let config = config_ptr as *mut *mut dyn HttpFilterConfig; - let config = &**config; - let headers = if headers_size > 0 { - Some(unsafe { - std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) - }) - } else { - None - }; - let body = if body_chunks_size > 0 { - Some(unsafe { std::slice::from_raw_parts(body_chunks as *const EnvoyBuffer, body_chunks_size) }) - } else { - None - }; - config.on_http_callout_done( - &mut EnvoyHttpFilterConfigImpl { - raw_ptr: envoy_config_ptr, - }, - callout_id, - result, - headers, - body, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let config = config_ptr as *mut *mut dyn HttpFilterConfig; + let config = &**config; + let headers = if headers_size > 0 { + Some(unsafe { + std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) + }) + } else { + None + }; + let body = if body_chunks_size > 0 { + Some(unsafe { + std::slice::from_raw_parts(body_chunks as *const EnvoyBuffer, body_chunks_size) + }) + } else { + None + }; + config.on_http_callout_done( + &mut EnvoyHttpFilterConfigImpl { + raw_ptr: envoy_config_ptr, + }, + callout_id, + result, + headers, + body, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_config_http_callout_done", + panic, + ); + }); } /// # Safety @@ -4336,23 +4525,31 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_http_stream_ headers_size: usize, end_stream: bool, ) { - let config = config_ptr as *mut *mut dyn HttpFilterConfig; - let config = &**config; - let headers = if headers_size > 0 { - unsafe { - std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) - } - } else { - &[] - }; - config.on_http_stream_headers( - &mut EnvoyHttpFilterConfigImpl { - raw_ptr: envoy_config_ptr, - }, - stream_handle, - headers, - end_stream, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let config = config_ptr as *mut *mut dyn HttpFilterConfig; + let config = &**config; + let headers = if headers_size > 0 { + unsafe { + std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) + } + } else { + &[] + }; + config.on_http_stream_headers( + &mut EnvoyHttpFilterConfigImpl { + raw_ptr: envoy_config_ptr, + }, + stream_handle, + headers, + end_stream, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_config_http_stream_headers", + panic, + ); + }); } /// # Safety @@ -4368,21 +4565,29 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_http_stream_ data_count: usize, end_stream: bool, ) { - let config = config_ptr as *mut *mut dyn HttpFilterConfig; - let config = &**config; - let data = if data_count > 0 { - unsafe { std::slice::from_raw_parts(data as *const EnvoyBuffer, data_count) } - } else { - &[] - }; - config.on_http_stream_data( - &mut EnvoyHttpFilterConfigImpl { - raw_ptr: envoy_config_ptr, - }, - stream_handle, - data, - end_stream, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let config = config_ptr as *mut *mut dyn HttpFilterConfig; + let config = &**config; + let data = if data_count > 0 { + unsafe { std::slice::from_raw_parts(data as *const EnvoyBuffer, data_count) } + } else { + &[] + }; + config.on_http_stream_data( + &mut EnvoyHttpFilterConfigImpl { + raw_ptr: envoy_config_ptr, + }, + stream_handle, + data, + end_stream, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_config_http_stream_data", + panic, + ); + }); } /// # Safety @@ -4397,22 +4602,30 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_http_stream_ trailers: *const abi::envoy_dynamic_module_type_envoy_http_header, trailers_size: usize, ) { - let config = config_ptr as *mut *mut dyn HttpFilterConfig; - let config = &**config; - let trailers = if trailers_size > 0 { - unsafe { - std::slice::from_raw_parts(trailers as *const (EnvoyBuffer, EnvoyBuffer), trailers_size) - } - } else { - &[] - }; - config.on_http_stream_trailers( - &mut EnvoyHttpFilterConfigImpl { - raw_ptr: envoy_config_ptr, - }, - stream_handle, - trailers, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let config = config_ptr as *mut *mut dyn HttpFilterConfig; + let config = &**config; + let trailers = if trailers_size > 0 { + unsafe { + std::slice::from_raw_parts(trailers as *const (EnvoyBuffer, EnvoyBuffer), trailers_size) + } + } else { + &[] + }; + config.on_http_stream_trailers( + &mut EnvoyHttpFilterConfigImpl { + raw_ptr: envoy_config_ptr, + }, + stream_handle, + trailers, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_config_http_stream_trailers", + panic, + ); + }); } /// # Safety @@ -4425,14 +4638,22 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_http_stream_ config_ptr: abi::envoy_dynamic_module_type_http_filter_config_module_ptr, stream_handle: u64, ) { - let config = config_ptr as *mut *mut dyn HttpFilterConfig; - let config = &**config; - config.on_http_stream_complete( - &mut EnvoyHttpFilterConfigImpl { - raw_ptr: envoy_config_ptr, - }, - stream_handle, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let config = config_ptr as *mut *mut dyn HttpFilterConfig; + let config = &**config; + config.on_http_stream_complete( + &mut EnvoyHttpFilterConfigImpl { + raw_ptr: envoy_config_ptr, + }, + stream_handle, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_config_http_stream_complete", + panic, + ); + }); } /// # Safety @@ -4446,13 +4667,21 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_http_stream_ stream_handle: u64, reset_reason: abi::envoy_dynamic_module_type_http_stream_reset_reason, ) { - let config = config_ptr as *mut *mut dyn HttpFilterConfig; - let config = &**config; - config.on_http_stream_reset( - &mut EnvoyHttpFilterConfigImpl { - raw_ptr: envoy_config_ptr, - }, - stream_handle, - reset_reason, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let config = config_ptr as *mut *mut dyn HttpFilterConfig; + let config = &**config; + config.on_http_stream_reset( + &mut EnvoyHttpFilterConfigImpl { + raw_ptr: envoy_config_ptr, + }, + stream_handle, + reset_reason, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_http_filter_config_http_stream_reset", + panic, + ); + }); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs index 8cc9bb8b25355..eea14c0110adc 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs @@ -46,7 +46,11 @@ use crate::abi::envoy_dynamic_module_type_metrics_result; use std::any::Any; use std::sync::OnceLock; -pub(crate) fn panic_payload_to_string(payload: Box) -> String { +/// Convert a panic payload (as captured by [`std::panic::catch_unwind`]) into a printable +/// string. Public so that [`declare_matcher!`] and other macros can format the payload from +/// the consuming crate. +#[doc(hidden)] +pub fn panic_payload_to_string(payload: Box) -> String { match payload.downcast::() { Ok(s) => *s, Err(payload) => match payload.downcast::<&str>() { @@ -56,6 +60,23 @@ pub(crate) fn panic_payload_to_string(payload: Box) -> String { } } +/// Log a panic caught at an FFI boundary. Exposed via `#[doc(hidden)]` so SDK-provided macros +/// such as `declare_matcher!` and `declare_init_functions!` can call it from user crates after +/// expansion. +/// +/// Logging runs after `catch_unwind` has already captured the original panic, so a secondary +/// panic inside `format!` or `envoy_log_error!` would unwind into `libc::abort` rather than +/// across the FFI boundary. That is intentional: a recursive panic in the log path indicates +/// the process is too broken to continue safely. +#[doc(hidden)] +pub fn log_ffi_panic(function_name: &str, payload: Box) { + crate::envoy_log_error!( + "{}: caught panic at FFI boundary: {}", + function_name, + crate::panic_payload_to_string(payload) + ); +} + /// This module contains the generated bindings for the envoy dynamic modules ABI. /// /// This is not meant to be used directly. @@ -99,37 +120,53 @@ macro_rules! declare_init_functions { ($f:ident, $new_http_filter_config_fn:expr, $new_http_filter_per_route_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION, - $new_http_filter_config_fn, - "NEW_HTTP_FILTER_CONFIG_FUNCTION" - ); - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION, - $new_http_filter_per_route_config_fn, - "NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION, + $new_http_filter_config_fn, + "NEW_HTTP_FILTER_CONFIG_FUNCTION" + ); + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION, + $new_http_filter_per_route_config_fn, + "NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; ($f:ident, $new_http_filter_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION, - $new_http_filter_config_fn, - "NEW_HTTP_FILTER_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_HTTP_FILTER_CONFIG_FUNCTION, + $new_http_filter_config_fn, + "NEW_HTTP_FILTER_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; @@ -362,18 +399,16 @@ macro_rules! envoy_log { /// Guard macro that ensures each factory `OnceLock` is registered by exactly one module. /// -/// When the same module is re-initialized (e.g. static modules loaded multiple times via -/// `newDynamicModuleByName`, or per-route config triggering a second init), the function +/// When the same module is re-initialized (for example, static modules loaded multiple times +/// via `newDynamicModuleByName`, or per-route config triggering a second init), the function /// pointer will be identical and the re-registration is silently accepted (idempotent). /// /// If a *different* module (standalone `.so` or consolidated `.so`) tries to register a /// different factory function for the same slot, this macro logs a critical message via -/// `envoy_log_critical!` and panics. The panic is caught at the FFI boundary by Envoy, -/// which converts it into a null return from `envoy_dynamic_module_on_program_init`, -/// causing Envoy to refuse to start — the correct behaviour for a data-correctness issue. -/// -/// In contrast, the previous `get_or_init` approach silently ignored the second writer, -/// making the second module's factories unreachable at runtime with no diagnostic. +/// `envoy_log_critical!` and returns `null` from the surrounding `on_program_init`, causing +/// Envoy to refuse to start — the correct behaviour for a data-correctness issue. The macro +/// is only invoked inside `envoy_dynamic_module_on_program_init` (signature +/// `-> *const c_char`), where the null return is the documented "init failed" sentinel. #[macro_export] macro_rules! set_factory_once { ($static:expr, $fn:expr, $name:literal) => { @@ -385,12 +420,9 @@ macro_rules! set_factory_once { .so loads.", $name, ); - panic!( - "Duplicate factory registration for {}. A different module already registered this \ - factory. Check dynamic_module_config for conflicting standalone and consolidated \ - .so loads.", - $name, - ); + // Return the "init failed" sentinel rather than panicking; unwinding across the + // `extern "C"` boundary is undefined behavior on the default `panic="unwind"`. + return ::std::ptr::null(); } } }; @@ -569,16 +601,24 @@ macro_rules! declare_network_filter_init_functions { ($f:ident, $new_network_filter_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_NETWORK_FILTER_CONFIG_FUNCTION, - $new_network_filter_config_fn, - "NEW_NETWORK_FILTER_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_NETWORK_FILTER_CONFIG_FUNCTION, + $new_network_filter_config_fn, + "NEW_NETWORK_FILTER_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; @@ -697,14 +737,22 @@ macro_rules! declare_all_init_functions { ($f:ident, $($filter_type:ident : $filter_fn:expr),+ $(,)?) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - $( - declare_all_init_functions!(@register $filter_type : $filter_fn); - )+ - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + $( + declare_all_init_functions!(@register $filter_type : $filter_fn); + )+ + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; @@ -853,16 +901,24 @@ macro_rules! declare_listener_filter_init_functions { pub extern "C" fn envoy_dynamic_module_on_program_init( server_factory_context_ptr: abi::envoy_dynamic_module_type_server_factory_context_envoy_ptr, ) -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_LISTENER_FILTER_CONFIG_FUNCTION, - $new_listener_filter_config_fn, - "NEW_LISTENER_FILTER_CONFIG_FUNCTION" - ); - if ($f(server_factory_context_ptr)) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_LISTENER_FILTER_CONFIG_FUNCTION, + $new_listener_filter_config_fn, + "NEW_LISTENER_FILTER_CONFIG_FUNCTION" + ); + if ($f(server_factory_context_ptr)) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; @@ -911,16 +967,24 @@ macro_rules! declare_udp_listener_filter_init_functions { ($f:ident, $new_udp_listener_filter_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION, - $new_udp_listener_filter_config_fn, - "NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION, + $new_udp_listener_filter_config_fn, + "NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; @@ -1018,16 +1082,24 @@ macro_rules! declare_bootstrap_init_functions { ($f:ident, $new_bootstrap_extension_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION, - $new_bootstrap_extension_config_fn, - "NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION, + $new_bootstrap_extension_config_fn, + "NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; @@ -1152,16 +1224,24 @@ macro_rules! declare_cluster_init_functions { ($f:ident, $new_cluster_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_CLUSTER_CONFIG_FUNCTION, - $new_cluster_config_fn, - "NEW_CLUSTER_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_CLUSTER_CONFIG_FUNCTION, + $new_cluster_config_fn, + "NEW_CLUSTER_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; @@ -1256,16 +1336,24 @@ macro_rules! declare_load_balancer_init_functions { ($f:ident, $new_lb_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_LOAD_BALANCER_CONFIG_FUNCTION, - $new_lb_config_fn, - "NEW_LOAD_BALANCER_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_LOAD_BALANCER_CONFIG_FUNCTION, + $new_lb_config_fn, + "NEW_LOAD_BALANCER_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; @@ -1330,16 +1418,24 @@ macro_rules! declare_cert_validator_init_functions { ($f:ident, $new_cert_validator_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_CERT_VALIDATOR_CONFIG_FUNCTION, - $new_cert_validator_config_fn, - "NEW_CERT_VALIDATOR_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_CERT_VALIDATOR_CONFIG_FUNCTION, + $new_cert_validator_config_fn, + "NEW_CERT_VALIDATOR_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; @@ -1447,16 +1543,24 @@ macro_rules! declare_dns_resolver_init_functions { ($f:ident, $new_dns_resolver_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_DNS_RESOLVER_CONFIG_FUNCTION, - $new_dns_resolver_config_fn, - "NEW_DNS_RESOLVER_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_DNS_RESOLVER_CONFIG_FUNCTION, + $new_dns_resolver_config_fn, + "NEW_DNS_RESOLVER_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; @@ -1509,16 +1613,24 @@ macro_rules! declare_transport_socket_init_functions { ($f:ident, $new_transport_socket_factory_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION, - $new_transport_socket_factory_config_fn, - "NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION, + $new_transport_socket_factory_config_fn, + "NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs index 675d41b74b5ef..dd850459b8827 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs @@ -5666,3 +5666,72 @@ fn test_envoy_dynamic_module_on_access_logger_new_destroy() { } assert!(LOGGER_DROPPED.load(std::sync::atomic::Ordering::SeqCst)); } + +// ================================================================================================= +// FFI panic-handling helpers +// ================================================================================================= + +#[test] +fn test_panic_payload_to_string_handles_string_payload() { + let payload: Box = Box::new(String::from("formatted panic message")); + assert_eq!(panic_payload_to_string(payload), "formatted panic message"); +} + +#[test] +fn test_panic_payload_to_string_handles_str_payload() { + let payload: Box = Box::new("static panic message"); + assert_eq!(panic_payload_to_string(payload), "static panic message"); +} + +#[test] +fn test_panic_payload_to_string_falls_back_for_unknown_payload() { + let payload: Box = Box::new(42_i32); + assert_eq!( + panic_payload_to_string(payload), + "" + ); +} + +#[test] +fn test_log_ffi_panic_handles_unknown_payload_without_panicking() { + // The logging path must not itself panic when the payload type is not recognized; otherwise + // the secondary panic would unwind across the FFI boundary that the outer `catch_unwind` is + // there to prevent. + let payload: Box = Box::new(42_i32); + log_ffi_panic("test_entry_point", payload); +} + +#[test] +fn test_outer_ffi_pattern_returns_fail_closed_pointer_on_panic() { + // Mirrors the exact `catch_unwind` + `unwrap_or_else` shape used by every FFI entry that + // returns a pointer, exercised end-to-end with the SDK's `log_ffi_panic` helper. + let result: *const std::ffi::c_void = std::panic::catch_unwind(std::panic::AssertUnwindSafe( + || -> *const std::ffi::c_void { + panic!("simulated factory panic"); + }, + )) + .unwrap_or_else(|payload| { + log_ffi_panic( + "test_outer_ffi_pattern_returns_fail_closed_pointer_on_panic", + payload, + ); + std::ptr::null() + }); + assert!(result.is_null()); +} + +#[test] +fn test_outer_ffi_pattern_returns_fail_closed_bool_on_panic() { + // Mirrors the FFI shape used by entries that return a bool (e.g. `on_program_init`). + let result: bool = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| -> bool { + panic!("simulated factory panic"); + })) + .unwrap_or_else(|payload| { + log_ffi_panic( + "test_outer_ffi_pattern_returns_fail_closed_bool_on_panic", + payload, + ); + false + }); + assert!(!result); +} diff --git a/source/extensions/dynamic_modules/sdk/rust/src/listener.rs b/source/extensions/dynamic_modules/sdk/rust/src/listener.rs index da8cb17bd20e4..95b50b01ac279 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/listener.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/listener.rs @@ -5,6 +5,7 @@ use crate::{ NEW_LISTENER_FILTER_CONFIG_FUNCTION, }; use mockall::*; +use std::panic::{catch_unwind, AssertUnwindSafe}; /// The trait that represents the Envoy listener filter configuration. /// This is used in [`NewListenerFilterConfigFunction`] to pass the Envoy filter configuration @@ -1217,22 +1218,28 @@ pub extern "C" fn envoy_dynamic_module_on_listener_filter_config_new( name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_listener_filter_config_module_ptr { - let mut envoy_filter_config = EnvoyListenerFilterConfigImpl::new(envoy_filter_config_ptr); - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) - }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; - init_listener_filter_config( - &mut envoy_filter_config, - name_str, - config_slice, - NEW_LISTENER_FILTER_CONFIG_FUNCTION - .get() - .expect("NEW_LISTENER_FILTER_CONFIG_FUNCTION must be set"), - ) + catch_unwind(AssertUnwindSafe(|| { + let mut envoy_filter_config = EnvoyListenerFilterConfigImpl::new(envoy_filter_config_ptr); + let name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )) + }; + let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; + init_listener_filter_config( + &mut envoy_filter_config, + name_str, + config_slice, + NEW_LISTENER_FILTER_CONFIG_FUNCTION + .get() + .expect("NEW_LISTENER_FILTER_CONFIG_FUNCTION must be set"), + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_listener_filter_config_new", panic); + std::ptr::null() + }) } pub(crate) fn init_listener_filter_config< @@ -1259,10 +1266,18 @@ pub(crate) fn init_listener_filter_config< pub unsafe extern "C" fn envoy_dynamic_module_on_listener_filter_config_destroy( filter_config_ptr: abi::envoy_dynamic_module_type_listener_filter_config_module_ptr, ) { - drop_wrapped_c_void_ptr!( - filter_config_ptr, - ListenerFilterConfig - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!( + filter_config_ptr, + ListenerFilterConfig + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_listener_filter_config_destroy", + panic, + ); + }); } /// # Safety @@ -1274,12 +1289,19 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_listener_filter_new( filter_config_ptr: abi::envoy_dynamic_module_type_listener_filter_config_module_ptr, envoy_filter_ptr: abi::envoy_dynamic_module_type_listener_filter_envoy_ptr, ) -> abi::envoy_dynamic_module_type_listener_filter_module_ptr { - let mut envoy_filter = EnvoyListenerFilterImpl::new(envoy_filter_ptr); - let filter_config = { - let raw = filter_config_ptr as *const *const dyn ListenerFilterConfig; - &**raw - }; - envoy_dynamic_module_on_listener_filter_new_impl(&mut envoy_filter, filter_config) + catch_unwind(AssertUnwindSafe(|| { + let mut envoy_filter = EnvoyListenerFilterImpl::new(envoy_filter_ptr); + let filter_config = { + let raw = + filter_config_ptr as *const *const dyn ListenerFilterConfig; + &**raw + }; + envoy_dynamic_module_on_listener_filter_new_impl(&mut envoy_filter, filter_config) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_listener_filter_new", panic); + std::ptr::null() + }) } pub(crate) fn envoy_dynamic_module_on_listener_filter_new_impl( @@ -1295,9 +1317,15 @@ pub extern "C" fn envoy_dynamic_module_on_listener_filter_on_accept( envoy_ptr: abi::envoy_dynamic_module_type_listener_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_listener_filter_module_ptr, ) -> abi::envoy_dynamic_module_type_on_listener_filter_status { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_accept(&mut EnvoyListenerFilterImpl::new(envoy_ptr)) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_accept(&mut EnvoyListenerFilterImpl::new(envoy_ptr)) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_listener_filter_on_accept", panic); + abi::envoy_dynamic_module_type_on_listener_filter_status::StopIteration + }) } #[no_mangle] @@ -1305,9 +1333,15 @@ pub extern "C" fn envoy_dynamic_module_on_listener_filter_on_data( envoy_ptr: abi::envoy_dynamic_module_type_listener_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_listener_filter_module_ptr, ) -> abi::envoy_dynamic_module_type_on_listener_filter_status { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_data(&mut EnvoyListenerFilterImpl::new(envoy_ptr)) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_data(&mut EnvoyListenerFilterImpl::new(envoy_ptr)) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_listener_filter_on_data", panic); + abi::envoy_dynamic_module_type_on_listener_filter_status::StopIteration + }) } #[no_mangle] @@ -1315,17 +1349,27 @@ pub extern "C" fn envoy_dynamic_module_on_listener_filter_on_close( envoy_ptr: abi::envoy_dynamic_module_type_listener_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_listener_filter_module_ptr, ) { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_close(&mut EnvoyListenerFilterImpl::new(envoy_ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_close(&mut EnvoyListenerFilterImpl::new(envoy_ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_listener_filter_on_close", panic); + }); } #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_listener_filter_destroy( filter_ptr: abi::envoy_dynamic_module_type_listener_filter_module_ptr, ) { - let _ = - unsafe { Box::from_raw(filter_ptr as *mut Box>) }; + let _ = catch_unwind(AssertUnwindSafe(|| { + let _ = + unsafe { Box::from_raw(filter_ptr as *mut Box>) }; + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_listener_filter_destroy", panic); + }); } #[no_mangle] @@ -1334,9 +1378,14 @@ pub extern "C" fn envoy_dynamic_module_on_listener_filter_scheduled( filter_ptr: abi::envoy_dynamic_module_type_listener_filter_module_ptr, event_id: u64, ) { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_scheduled(&mut EnvoyListenerFilterImpl::new(envoy_ptr), event_id); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_scheduled(&mut EnvoyListenerFilterImpl::new(envoy_ptr), event_id); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_listener_filter_scheduled", panic); + }); } #[no_mangle] @@ -1345,10 +1394,18 @@ pub extern "C" fn envoy_dynamic_module_on_listener_filter_config_scheduled( filter_config_module_ptr: abi::envoy_dynamic_module_type_listener_filter_config_module_ptr, event_id: u64, ) { - let filter_config = - filter_config_module_ptr as *const *const dyn ListenerFilterConfig; - let filter_config = unsafe { &**filter_config }; - filter_config.on_config_scheduled(event_id); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter_config = + filter_config_module_ptr as *const *const dyn ListenerFilterConfig; + let filter_config = unsafe { &**filter_config }; + filter_config.on_config_scheduled(event_id); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_listener_filter_config_scheduled", + panic, + ); + }); } /// # Safety @@ -1366,41 +1423,49 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_listener_filter_http_callout_do body_chunks: *const abi::envoy_dynamic_module_type_envoy_buffer, body_chunks_size: usize, ) { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - - // Convert headers to Vec<(EnvoyBuffer, EnvoyBuffer)>. - let header_vec = if headers.is_null() || headers_size == 0 { - Vec::new() - } else { - let headers_slice = unsafe { std::slice::from_raw_parts(headers, headers_size) }; - headers_slice - .iter() - .map(|h| { - ( - unsafe { EnvoyBuffer::new_from_raw(h.key_ptr as *const _, h.key_length) }, - unsafe { EnvoyBuffer::new_from_raw(h.value_ptr as *const _, h.value_length) }, - ) - }) - .collect() - }; - - // Convert body chunks to Vec. - let body_vec = if body_chunks.is_null() || body_chunks_size == 0 { - Vec::new() - } else { - let chunks_slice = unsafe { std::slice::from_raw_parts(body_chunks, body_chunks_size) }; - chunks_slice - .iter() - .map(|c| unsafe { EnvoyBuffer::new_from_raw(c.ptr as *const _, c.length) }) - .collect() - }; - - filter.on_http_callout_done( - &mut EnvoyListenerFilterImpl::new(envoy_ptr), - callout_id, - result, - header_vec, - body_vec, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + + // Convert headers to Vec<(EnvoyBuffer, EnvoyBuffer)>. + let header_vec = if headers.is_null() || headers_size == 0 { + Vec::new() + } else { + let headers_slice = unsafe { std::slice::from_raw_parts(headers, headers_size) }; + headers_slice + .iter() + .map(|h| { + ( + unsafe { EnvoyBuffer::new_from_raw(h.key_ptr as *const _, h.key_length) }, + unsafe { EnvoyBuffer::new_from_raw(h.value_ptr as *const _, h.value_length) }, + ) + }) + .collect() + }; + + // Convert body chunks to Vec. + let body_vec = if body_chunks.is_null() || body_chunks_size == 0 { + Vec::new() + } else { + let chunks_slice = unsafe { std::slice::from_raw_parts(body_chunks, body_chunks_size) }; + chunks_slice + .iter() + .map(|c| unsafe { EnvoyBuffer::new_from_raw(c.ptr as *const _, c.length) }) + .collect() + }; + + filter.on_http_callout_done( + &mut EnvoyListenerFilterImpl::new(envoy_ptr), + callout_id, + result, + header_vec, + body_vec, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_listener_filter_http_callout_done", + panic, + ); + }); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/load_balancer.rs b/source/extensions/dynamic_modules/sdk/rust/src/load_balancer.rs index 06e2a91d01ff0..d47697490217c 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/load_balancer.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/load_balancer.rs @@ -4,6 +4,7 @@ use crate::{ EnvoyHistogramVecId, NEW_LOAD_BALANCER_CONFIG_FUNCTION, }; use mockall::*; +use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::Arc; /// Trait for interacting with the Envoy load balancer and its context. @@ -1308,21 +1309,27 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_lb_config_new( name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_lb_config_module_ptr { - let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )); - let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length); - let new_config_fn = NEW_LOAD_BALANCER_CONFIG_FUNCTION - .get() - .expect("NEW_LOAD_BALANCER_CONFIG_FUNCTION must be set"); - let envoy_lb_config: Arc = Arc::new(EnvoyLbConfigImpl { - raw: lb_config_envoy_ptr, - }); - match new_config_fn(name_str, config_slice, envoy_lb_config) { - Some(config) => wrap_into_c_void_ptr!(config), - None => std::ptr::null(), - } + catch_unwind(AssertUnwindSafe(|| { + let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )); + let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length); + let new_config_fn = NEW_LOAD_BALANCER_CONFIG_FUNCTION + .get() + .expect("NEW_LOAD_BALANCER_CONFIG_FUNCTION must be set"); + let envoy_lb_config: Arc = Arc::new(EnvoyLbConfigImpl { + raw: lb_config_envoy_ptr, + }); + match new_config_fn(name_str, config_slice, envoy_lb_config) { + Some(config) => wrap_into_c_void_ptr!(config), + None => std::ptr::null(), + } + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_lb_config_new", panic); + std::ptr::null() + }) } /// # Safety @@ -1333,7 +1340,12 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_lb_config_new( pub unsafe extern "C" fn envoy_dynamic_module_on_lb_config_destroy( config_ptr: abi::envoy_dynamic_module_type_lb_config_module_ptr, ) { - drop_wrapped_c_void_ptr!(config_ptr, LoadBalancerConfig); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!(config_ptr, LoadBalancerConfig); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_lb_config_destroy", panic); + }); } /// # Safety @@ -1345,14 +1357,20 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_lb_new( config_ptr: abi::envoy_dynamic_module_type_lb_config_module_ptr, lb_envoy_ptr: abi::envoy_dynamic_module_type_lb_envoy_ptr, ) -> abi::envoy_dynamic_module_type_lb_module_ptr { - // During new_load_balancer, context is not available. - let envoy_lb = EnvoyLoadBalancerImpl::new(lb_envoy_ptr, std::ptr::null_mut()); - let lb_config = { - let raw = config_ptr as *const *const dyn LoadBalancerConfig; - &**raw - }; - let lb = lb_config.new_load_balancer(&envoy_lb); - wrap_into_c_void_ptr!(lb) + catch_unwind(AssertUnwindSafe(|| { + // During new_load_balancer, context is not available. + let envoy_lb = EnvoyLoadBalancerImpl::new(lb_envoy_ptr, std::ptr::null_mut()); + let lb_config = { + let raw = config_ptr as *const *const dyn LoadBalancerConfig; + &**raw + }; + let lb = lb_config.new_load_balancer(&envoy_lb); + wrap_into_c_void_ptr!(lb) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_lb_new", panic); + std::ptr::null() + }) } /// # Safety @@ -1367,19 +1385,27 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_lb_choose_host( result_priority: *mut u32, result_index: *mut u32, ) -> bool { - let envoy_lb = EnvoyLoadBalancerImpl::new(lb_envoy_ptr, context_envoy_ptr); - let lb = { - let raw = lb_module_ptr as *mut *mut dyn LoadBalancer; - &mut **raw - }; - match lb.choose_host(&envoy_lb) { - Some(selection) => { - *result_priority = selection.priority; - *result_index = selection.index; - true - }, - None => false, - } + catch_unwind(AssertUnwindSafe(|| { + let envoy_lb = EnvoyLoadBalancerImpl::new(lb_envoy_ptr, context_envoy_ptr); + let lb = { + let raw = lb_module_ptr as *mut *mut dyn LoadBalancer; + &mut **raw + }; + match lb.choose_host(&envoy_lb) { + Some(selection) => { + *result_priority = selection.priority; + *result_index = selection.index; + true + }, + None => false, + } + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_lb_choose_host", panic); + // Fail-closed: return `false` so Envoy treats this as "no host selected" + // rather than reading the (uninitialised) out parameters. + false + }) } /// # Safety @@ -1393,12 +1419,20 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_lb_on_host_membership_update( num_hosts_added: usize, num_hosts_removed: usize, ) { - let envoy_lb = EnvoyLoadBalancerImpl::new(lb_envoy_ptr, std::ptr::null_mut()); - let lb = { - let raw = lb_module_ptr as *mut *mut dyn LoadBalancer; - &mut **raw - }; - lb.on_host_membership_update(&envoy_lb, num_hosts_added, num_hosts_removed); + let _ = catch_unwind(AssertUnwindSafe(|| { + let envoy_lb = EnvoyLoadBalancerImpl::new(lb_envoy_ptr, std::ptr::null_mut()); + let lb = { + let raw = lb_module_ptr as *mut *mut dyn LoadBalancer; + &mut **raw + }; + lb.on_host_membership_update(&envoy_lb, num_hosts_added, num_hosts_removed); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_lb_on_host_membership_update", + panic, + ); + }); } /// # Safety @@ -1409,5 +1443,10 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_lb_on_host_membership_update( pub unsafe extern "C" fn envoy_dynamic_module_on_lb_destroy( lb_module_ptr: abi::envoy_dynamic_module_type_lb_module_ptr, ) { - drop_wrapped_c_void_ptr!(lb_module_ptr, LoadBalancer); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!(lb_module_ptr, LoadBalancer); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_lb_destroy", panic); + }); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/matcher.rs b/source/extensions/dynamic_modules/sdk/rust/src/matcher.rs index ab36130c6b0d2..15d69a68b1c68 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/matcher.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/matcher.rs @@ -205,26 +205,35 @@ macro_rules! declare_matcher { name: $crate::abi::envoy_dynamic_module_type_envoy_buffer, config: $crate::abi::envoy_dynamic_module_type_envoy_buffer, ) -> *const ::std::ffi::c_void { - let name_str = unsafe { - let slice = ::std::slice::from_raw_parts(name.ptr as *const u8, name.length); - ::std::str::from_utf8(slice).unwrap_or("") - }; - let config_bytes = - unsafe { ::std::slice::from_raw_parts(config.ptr as *const u8, config.length) }; + ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + let name_str = unsafe { + let slice = ::std::slice::from_raw_parts(name.ptr as *const u8, name.length); + ::std::str::from_utf8(slice).unwrap_or("") + }; + let config_bytes = + unsafe { ::std::slice::from_raw_parts(config.ptr as *const u8, config.length) }; - match <$config_type as $crate::matcher::MatcherConfig>::new(name_str, config_bytes) { - Ok(c) => Box::into_raw(Box::new(c)) as *const ::std::ffi::c_void, - Err(_) => ::std::ptr::null(), - } + match <$config_type as $crate::matcher::MatcherConfig>::new(name_str, config_bytes) { + Ok(c) => Box::into_raw(Box::new(c)) as *const ::std::ffi::c_void, + Err(_) => ::std::ptr::null(), + } + })) + .unwrap_or_else(|panic| { + $crate::log_ffi_panic("envoy_dynamic_module_on_matcher_config_new", panic); + ::std::ptr::null() + }) } #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_matcher_config_destroy( config_ptr: *const ::std::ffi::c_void, ) { - unsafe { + let _ = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| unsafe { drop(Box::from_raw(config_ptr as *mut $config_type)); - } + })) + .map_err(|panic| { + $crate::log_ffi_panic("envoy_dynamic_module_on_matcher_config_destroy", panic); + }); } #[no_mangle] @@ -232,9 +241,16 @@ macro_rules! declare_matcher { config_ptr: *const ::std::ffi::c_void, matcher_input_envoy_ptr: *mut ::std::ffi::c_void, ) -> bool { - let config = unsafe { &*(config_ptr as *const $config_type) }; - let ctx = $crate::matcher::MatchContext::new(matcher_input_envoy_ptr); - config.on_matcher_match(&ctx) + ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + let config = unsafe { &*(config_ptr as *const $config_type) }; + let ctx = $crate::matcher::MatchContext::new(matcher_input_envoy_ptr); + config.on_matcher_match(&ctx) + })) + .unwrap_or_else(|panic| { + $crate::log_ffi_panic("envoy_dynamic_module_on_matcher_match", panic); + // Fail-closed: a panic during match evaluation must not look like "matched". + false + }) } }; } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/network.rs b/source/extensions/dynamic_modules/sdk/rust/src/network.rs index 0c5d284d0f4b1..109094e2b54fa 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/network.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/network.rs @@ -6,6 +6,7 @@ use crate::{ NEW_NETWORK_FILTER_CONFIG_FUNCTION, }; use mockall::*; +use std::panic::{catch_unwind, AssertUnwindSafe}; /// The trait that represents the Envoy network filter configuration. /// This is used in [`NewNetworkFilterConfigFunction`] to pass the Envoy filter configuration @@ -1653,22 +1654,28 @@ pub extern "C" fn envoy_dynamic_module_on_network_filter_config_new( name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_network_filter_config_module_ptr { - let mut envoy_filter_config = EnvoyNetworkFilterConfigImpl::new(envoy_filter_config_ptr); - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) - }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; - init_network_filter_config( - &mut envoy_filter_config, - name_str, - config_slice, - NEW_NETWORK_FILTER_CONFIG_FUNCTION - .get() - .expect("NEW_NETWORK_FILTER_CONFIG_FUNCTION must be set"), - ) + catch_unwind(AssertUnwindSafe(|| { + let mut envoy_filter_config = EnvoyNetworkFilterConfigImpl::new(envoy_filter_config_ptr); + let name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )) + }; + let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; + init_network_filter_config( + &mut envoy_filter_config, + name_str, + config_slice, + NEW_NETWORK_FILTER_CONFIG_FUNCTION + .get() + .expect("NEW_NETWORK_FILTER_CONFIG_FUNCTION must be set"), + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_network_filter_config_new", panic); + std::ptr::null() + }) } pub(crate) fn init_network_filter_config( @@ -1692,10 +1699,18 @@ pub(crate) fn init_network_filter_config - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!( + filter_config_ptr, + NetworkFilterConfig + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_network_filter_config_destroy", + panic, + ); + }); } /// # Safety @@ -1707,12 +1722,18 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_network_filter_new( filter_config_ptr: abi::envoy_dynamic_module_type_network_filter_config_module_ptr, envoy_filter_ptr: abi::envoy_dynamic_module_type_network_filter_envoy_ptr, ) -> abi::envoy_dynamic_module_type_network_filter_module_ptr { - let mut envoy_filter = EnvoyNetworkFilterImpl::new(envoy_filter_ptr); - let filter_config = { - let raw = filter_config_ptr as *const *const dyn NetworkFilterConfig; - &**raw - }; - envoy_dynamic_module_on_network_filter_new_impl(&mut envoy_filter, filter_config) + catch_unwind(AssertUnwindSafe(|| { + let mut envoy_filter = EnvoyNetworkFilterImpl::new(envoy_filter_ptr); + let filter_config = { + let raw = filter_config_ptr as *const *const dyn NetworkFilterConfig; + &**raw + }; + envoy_dynamic_module_on_network_filter_new_impl(&mut envoy_filter, filter_config) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_network_filter_new", panic); + std::ptr::null() + }) } pub(crate) fn envoy_dynamic_module_on_network_filter_new_impl( @@ -1728,9 +1749,18 @@ pub extern "C" fn envoy_dynamic_module_on_network_filter_new_connection( envoy_ptr: abi::envoy_dynamic_module_type_network_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_network_filter_module_ptr, ) -> abi::envoy_dynamic_module_type_on_network_filter_data_status { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_new_connection(&mut EnvoyNetworkFilterImpl::new(envoy_ptr)) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_new_connection(&mut EnvoyNetworkFilterImpl::new(envoy_ptr)) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_network_filter_new_connection", + panic, + ); + abi::envoy_dynamic_module_type_on_network_filter_data_status::StopIteration + }) } #[no_mangle] @@ -1740,13 +1770,19 @@ pub extern "C" fn envoy_dynamic_module_on_network_filter_read( data_length: usize, end_stream: bool, ) -> abi::envoy_dynamic_module_type_on_network_filter_data_status { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_read( - &mut EnvoyNetworkFilterImpl::new(envoy_ptr), - data_length, - end_stream, - ) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_read( + &mut EnvoyNetworkFilterImpl::new(envoy_ptr), + data_length, + end_stream, + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_network_filter_read", panic); + abi::envoy_dynamic_module_type_on_network_filter_data_status::StopIteration + }) } #[no_mangle] @@ -1756,13 +1792,19 @@ pub extern "C" fn envoy_dynamic_module_on_network_filter_write( data_length: usize, end_stream: bool, ) -> abi::envoy_dynamic_module_type_on_network_filter_data_status { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_write( - &mut EnvoyNetworkFilterImpl::new(envoy_ptr), - data_length, - end_stream, - ) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_write( + &mut EnvoyNetworkFilterImpl::new(envoy_ptr), + data_length, + end_stream, + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_network_filter_write", panic); + abi::envoy_dynamic_module_type_on_network_filter_data_status::StopIteration + }) } #[no_mangle] @@ -1771,17 +1813,27 @@ pub extern "C" fn envoy_dynamic_module_on_network_filter_event( filter_ptr: abi::envoy_dynamic_module_type_network_filter_module_ptr, event: abi::envoy_dynamic_module_type_network_connection_event, ) { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_event(&mut EnvoyNetworkFilterImpl::new(envoy_ptr), event); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_event(&mut EnvoyNetworkFilterImpl::new(envoy_ptr), event); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_network_filter_event", panic); + }); } #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_network_filter_destroy( filter_ptr: abi::envoy_dynamic_module_type_network_filter_module_ptr, ) { - let _ = - unsafe { Box::from_raw(filter_ptr as *mut Box>) }; + let _ = catch_unwind(AssertUnwindSafe(|| { + let _ = + unsafe { Box::from_raw(filter_ptr as *mut Box>) }; + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_network_filter_destroy", panic); + }); } #[no_mangle] @@ -1798,43 +1850,51 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_network_filter_http_callout_don body_chunks: *const abi::envoy_dynamic_module_type_envoy_buffer, body_chunks_size: usize, ) { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - - // Convert headers to Vec<(EnvoyBuffer, EnvoyBuffer)>. - let header_vec = if headers.is_null() || headers_size == 0 { - Vec::new() - } else { - let headers_slice = unsafe { std::slice::from_raw_parts(headers, headers_size) }; - headers_slice - .iter() - .map(|h| { - ( - unsafe { EnvoyBuffer::new_from_raw(h.key_ptr as *const _, h.key_length) }, - unsafe { EnvoyBuffer::new_from_raw(h.value_ptr as *const _, h.value_length) }, - ) - }) - .collect() - }; - - // Convert body chunks to Vec. - let body_vec = if body_chunks.is_null() || body_chunks_size == 0 { - Vec::new() - } else { - let chunks_slice = unsafe { std::slice::from_raw_parts(body_chunks, body_chunks_size) }; - chunks_slice - .iter() - .map(|c| unsafe { EnvoyBuffer::new_from_raw(c.ptr as *const _, c.length) }) - .collect() - }; - - filter.on_http_callout_done( - &mut EnvoyNetworkFilterImpl::new(envoy_ptr), - callout_id, - result, - header_vec, - body_vec, - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + + // Convert headers to Vec<(EnvoyBuffer, EnvoyBuffer)>. + let header_vec = if headers.is_null() || headers_size == 0 { + Vec::new() + } else { + let headers_slice = unsafe { std::slice::from_raw_parts(headers, headers_size) }; + headers_slice + .iter() + .map(|h| { + ( + unsafe { EnvoyBuffer::new_from_raw(h.key_ptr as *const _, h.key_length) }, + unsafe { EnvoyBuffer::new_from_raw(h.value_ptr as *const _, h.value_length) }, + ) + }) + .collect() + }; + + // Convert body chunks to Vec. + let body_vec = if body_chunks.is_null() || body_chunks_size == 0 { + Vec::new() + } else { + let chunks_slice = unsafe { std::slice::from_raw_parts(body_chunks, body_chunks_size) }; + chunks_slice + .iter() + .map(|c| unsafe { EnvoyBuffer::new_from_raw(c.ptr as *const _, c.length) }) + .collect() + }; + + filter.on_http_callout_done( + &mut EnvoyNetworkFilterImpl::new(envoy_ptr), + callout_id, + result, + header_vec, + body_vec, + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_network_filter_http_callout_done", + panic, + ); + }); } #[no_mangle] @@ -1843,9 +1903,14 @@ pub extern "C" fn envoy_dynamic_module_on_network_filter_scheduled( filter_ptr: abi::envoy_dynamic_module_type_network_filter_module_ptr, event_id: u64, ) { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_scheduled(&mut EnvoyNetworkFilterImpl::new(envoy_ptr), event_id); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_scheduled(&mut EnvoyNetworkFilterImpl::new(envoy_ptr), event_id); + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_network_filter_scheduled", panic); + }); } #[no_mangle] @@ -1853,11 +1918,19 @@ pub extern "C" fn envoy_dynamic_module_on_network_filter_config_scheduled( filter_config_ptr: abi::envoy_dynamic_module_type_network_filter_config_module_ptr, event_id: u64, ) { - let filter_config = { - let raw = filter_config_ptr as *const *const dyn NetworkFilterConfig; - unsafe { &**raw } - }; - filter_config.on_config_scheduled(event_id); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter_config = { + let raw = filter_config_ptr as *const *const dyn NetworkFilterConfig; + unsafe { &**raw } + }; + filter_config.on_config_scheduled(event_id); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_network_filter_config_scheduled", + panic, + ); + }); } #[no_mangle] @@ -1865,9 +1938,17 @@ pub extern "C" fn envoy_dynamic_module_on_network_filter_above_write_buffer_high envoy_ptr: abi::envoy_dynamic_module_type_network_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_network_filter_module_ptr, ) { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_above_write_buffer_high_watermark(&mut EnvoyNetworkFilterImpl::new(envoy_ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_above_write_buffer_high_watermark(&mut EnvoyNetworkFilterImpl::new(envoy_ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_network_filter_above_write_buffer_high_watermark", + panic, + ); + }); } #[no_mangle] @@ -1875,7 +1956,15 @@ pub extern "C" fn envoy_dynamic_module_on_network_filter_below_write_buffer_low_ envoy_ptr: abi::envoy_dynamic_module_type_network_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_network_filter_module_ptr, ) { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_below_write_buffer_low_watermark(&mut EnvoyNetworkFilterImpl::new(envoy_ptr)); + let _ = catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_below_write_buffer_low_watermark(&mut EnvoyNetworkFilterImpl::new(envoy_ptr)); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_network_filter_below_write_buffer_low_watermark", + panic, + ); + }); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs b/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs index 08fbd67890341..0490621f64005 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs @@ -593,10 +593,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_config_new( match result { Ok(ptr) => ptr, Err(panic) => { - crate::envoy_log_error!( - "on_tracer_config_new: caught panic: {}", - crate::panic_payload_to_string(panic) - ); + crate::log_ffi_panic("envoy_dynamic_module_on_tracer_config_new", panic); std::ptr::null() }, } @@ -610,11 +607,16 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_config_new( pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_config_destroy( config_module_ptr: abi::envoy_dynamic_module_type_tracer_config_module_ptr, ) { - let config = config_module_ptr as *mut *mut dyn TracerConfig; - unsafe { - let _outer = Box::from_raw(config); - let _inner = Box::from_raw(*config); - } + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let config = config_module_ptr as *mut *mut dyn TracerConfig; + unsafe { + let _outer = Box::from_raw(config); + let _inner = Box::from_raw(*config); + } + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_tracer_config_destroy", panic); + }); } /// # Safety @@ -645,10 +647,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_start_span( match result { Ok(ptr) => ptr, Err(panic) => { - crate::envoy_log_error!( - "on_tracer_start_span: caught panic: {}", - crate::panic_payload_to_string(panic) - ); + crate::log_ffi_panic("envoy_dynamic_module_on_tracer_start_span", panic); std::ptr::null() }, } @@ -759,10 +758,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_span_spawn_child( match result { Ok(ptr) => ptr, Err(panic) => { - crate::envoy_log_error!( - "on_tracer_span_spawn_child: caught panic: {}", - crate::panic_payload_to_string(panic) - ); + crate::log_ffi_panic("envoy_dynamic_module_on_tracer_span_spawn_child", panic); std::ptr::null() }, } @@ -937,10 +933,15 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_span_get_span_id( pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_span_destroy( span_module_ptr: abi::envoy_dynamic_module_type_tracer_span_module_ptr, ) { - let wrapper = span_module_ptr as *mut SpanWrapper; - unsafe { - let _ = Box::from_raw(wrapper); - } + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let wrapper = span_module_ptr as *mut SpanWrapper; + unsafe { + let _ = Box::from_raw(wrapper); + } + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_tracer_span_destroy", panic); + }); } /// Declare the init functions for the tracer dynamic module. @@ -952,16 +953,24 @@ macro_rules! declare_tracer_init_functions { ($f:ident, $new_tracer_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_TRACER_CONFIG_FUNCTION, - $new_tracer_config_fn, - "NEW_TRACER_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_TRACER_CONFIG_FUNCTION, + $new_tracer_config_fn, + "NEW_TRACER_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; diff --git a/source/extensions/dynamic_modules/sdk/rust/src/transport_socket.rs b/source/extensions/dynamic_modules/sdk/rust/src/transport_socket.rs index b880684e2700d..89fb18827bd16 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/transport_socket.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/transport_socket.rs @@ -430,7 +430,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_factory_config } })) .unwrap_or_else(|panic| { - log_panic( + crate::log_ffi_panic( "envoy_dynamic_module_on_transport_socket_factory_config_new", panic, ); @@ -453,7 +453,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_factory_config ); })) .map_err(|panic| { - log_panic( + crate::log_ffi_panic( "envoy_dynamic_module_on_transport_socket_factory_config_destroy", panic, ); @@ -479,7 +479,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_new( Box::into_raw(wrapper) as abi::envoy_dynamic_module_type_transport_socket_module_ptr })) .unwrap_or_else(|panic| { - log_panic("envoy_dynamic_module_on_transport_socket_new", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_transport_socket_new", panic); std::ptr::null() }) } @@ -497,7 +497,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_destroy( let _ = unsafe { Box::from_raw(wrapper) }; })) .map_err(|panic| { - log_panic("envoy_dynamic_module_on_transport_socket_destroy", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_transport_socket_destroy", panic); }); } @@ -516,7 +516,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_set_callbacks( wrapper.socket.on_set_callbacks(&mut envoy); })) .map_err(|panic| { - log_panic( + crate::log_ffi_panic( "envoy_dynamic_module_on_transport_socket_set_callbacks", panic, ); @@ -538,7 +538,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_on_connected( wrapper.socket.on_connected(&mut envoy); })) .map_err(|panic| { - log_panic( + crate::log_ffi_panic( "envoy_dynamic_module_on_transport_socket_on_connected", panic, ); @@ -561,7 +561,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_do_read( io.into() })) .unwrap_or_else(|panic| { - log_panic("envoy_dynamic_module_on_transport_socket_do_read", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_transport_socket_do_read", panic); IoResult::close(0, false).into() }) } @@ -584,7 +584,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_do_write( io.into() })) .unwrap_or_else(|panic| { - log_panic("envoy_dynamic_module_on_transport_socket_do_write", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_transport_socket_do_write", panic); IoResult::close(0, false).into() }) } @@ -605,7 +605,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_close( wrapper.socket.on_close(&mut envoy, event.into()); })) .map_err(|panic| { - log_panic("envoy_dynamic_module_on_transport_socket_close", panic); + crate::log_ffi_panic("envoy_dynamic_module_on_transport_socket_close", panic); }); } @@ -626,7 +626,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_get_protocol( fill_string_buffer_out(&GET_PROTOCOL_BUF, result, &s); })) .map_err(|panic| { - log_panic( + crate::log_ffi_panic( "envoy_dynamic_module_on_transport_socket_get_protocol", panic, ); @@ -650,7 +650,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_get_failure_re fill_string_buffer_out(&GET_FAILURE_REASON_BUF, result, &s); })) .map_err(|panic| { - log_panic( + crate::log_ffi_panic( "envoy_dynamic_module_on_transport_socket_get_failure_reason", panic, ); @@ -672,19 +672,10 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_can_flush_clos wrapper.socket.can_flush_close(&mut envoy) })) .unwrap_or_else(|panic| { - log_panic( + crate::log_ffi_panic( "envoy_dynamic_module_on_transport_socket_can_flush_close", panic, ); false }) } - -/// Log a panic caught at an FFI boundary. -fn log_panic(function_name: &str, panic: Box) { - crate::envoy_log_error!( - "{}: caught panic: {}", - function_name, - crate::panic_payload_to_string(panic) - ); -} diff --git a/source/extensions/dynamic_modules/sdk/rust/src/udp_listener.rs b/source/extensions/dynamic_modules/sdk/rust/src/udp_listener.rs index 5b79ed42639a2..dfeabd505d82c 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/udp_listener.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/udp_listener.rs @@ -5,6 +5,7 @@ use crate::{ NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION, }; use mockall::*; +use std::panic::{catch_unwind, AssertUnwindSafe}; /// The trait that represents the Envoy UDP listener filter configuration. /// This is used in [`NewUdpListenerFilterConfigFunction`] to pass the Envoy filter configuration @@ -398,22 +399,31 @@ pub extern "C" fn envoy_dynamic_module_on_udp_listener_filter_config_new( name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_udp_listener_filter_config_module_ptr { - let mut envoy_filter_config = EnvoyUdpListenerFilterConfigImpl::new(envoy_filter_config_ptr); - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) - }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; - init_udp_listener_filter_config( - &mut envoy_filter_config, - name_str, - config_slice, - NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION - .get() - .expect("NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION must be set"), - ) + catch_unwind(AssertUnwindSafe(|| { + let mut envoy_filter_config = EnvoyUdpListenerFilterConfigImpl::new(envoy_filter_config_ptr); + let name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )) + }; + let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; + init_udp_listener_filter_config( + &mut envoy_filter_config, + name_str, + config_slice, + NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION + .get() + .expect("NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION must be set"), + ) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_udp_listener_filter_config_new", + panic, + ); + std::ptr::null() + }) } pub(crate) fn init_udp_listener_filter_config< @@ -440,10 +450,18 @@ pub(crate) fn init_udp_listener_filter_config< pub unsafe extern "C" fn envoy_dynamic_module_on_udp_listener_filter_config_destroy( filter_config_ptr: abi::envoy_dynamic_module_type_udp_listener_filter_config_module_ptr, ) { - drop_wrapped_c_void_ptr!( - filter_config_ptr, - UdpListenerFilterConfig - ); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!( + filter_config_ptr, + UdpListenerFilterConfig + ); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_udp_listener_filter_config_destroy", + panic, + ); + }); } /// # Safety @@ -455,13 +473,19 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_udp_listener_filter_new( filter_config_ptr: abi::envoy_dynamic_module_type_udp_listener_filter_config_module_ptr, envoy_filter_ptr: abi::envoy_dynamic_module_type_udp_listener_filter_envoy_ptr, ) -> abi::envoy_dynamic_module_type_udp_listener_filter_module_ptr { - let mut envoy_filter = EnvoyUdpListenerFilterImpl::new(envoy_filter_ptr); - let filter_config = { - let raw = - filter_config_ptr as *const *const dyn UdpListenerFilterConfig; - &**raw - }; - envoy_dynamic_module_on_udp_listener_filter_new_impl(&mut envoy_filter, filter_config) + catch_unwind(AssertUnwindSafe(|| { + let mut envoy_filter = EnvoyUdpListenerFilterImpl::new(envoy_filter_ptr); + let filter_config = { + let raw = + filter_config_ptr as *const *const dyn UdpListenerFilterConfig; + &**raw + }; + envoy_dynamic_module_on_udp_listener_filter_new_impl(&mut envoy_filter, filter_config) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_udp_listener_filter_new", panic); + std::ptr::null() + }) } pub(crate) fn envoy_dynamic_module_on_udp_listener_filter_new_impl( @@ -477,16 +501,27 @@ pub extern "C" fn envoy_dynamic_module_on_udp_listener_filter_on_data( envoy_ptr: abi::envoy_dynamic_module_type_udp_listener_filter_envoy_ptr, filter_ptr: abi::envoy_dynamic_module_type_udp_listener_filter_module_ptr, ) -> abi::envoy_dynamic_module_type_on_udp_listener_filter_status { - let filter = filter_ptr as *mut Box>; - let filter = unsafe { &mut *filter }; - filter.on_data(&mut EnvoyUdpListenerFilterImpl::new(envoy_ptr)) + catch_unwind(AssertUnwindSafe(|| { + let filter = filter_ptr as *mut Box>; + let filter = unsafe { &mut *filter }; + filter.on_data(&mut EnvoyUdpListenerFilterImpl::new(envoy_ptr)) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_udp_listener_filter_on_data", panic); + abi::envoy_dynamic_module_type_on_udp_listener_filter_status::StopIteration + }) } #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_udp_listener_filter_destroy( filter_ptr: abi::envoy_dynamic_module_type_udp_listener_filter_module_ptr, ) { - let _ = unsafe { - Box::from_raw(filter_ptr as *mut Box>) - }; + let _ = catch_unwind(AssertUnwindSafe(|| { + let _ = unsafe { + Box::from_raw(filter_ptr as *mut Box>) + }; + })) + .map_err(|panic| { + crate::log_ffi_panic("envoy_dynamic_module_on_udp_listener_filter_destroy", panic); + }); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs b/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs index d4fc6f4bfc5a5..c2ce56916825a 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs @@ -3,6 +3,7 @@ use crate::{ NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION, }; use mockall::*; +use std::panic::{catch_unwind, AssertUnwindSafe}; /// The module-side bridge configuration. /// @@ -318,27 +319,44 @@ pub extern "C" fn envoy_dynamic_module_on_upstream_http_tcp_bridge_config_new( name: abi::envoy_dynamic_module_type_envoy_buffer, config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_config_module_ptr { - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) - }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; - let new_config_fn = NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION - .get() - .expect("NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION must be set"); - match new_config_fn(name_str, config_slice) { - Some(config) => wrap_into_c_void_ptr!(config), - None => std::ptr::null(), - } + catch_unwind(AssertUnwindSafe(|| { + let name_str = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + name.ptr as *const _, + name.length, + )) + }; + let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; + let new_config_fn = NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION + .get() + .expect("NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION must be set"); + match new_config_fn(name_str, config_slice) { + Some(config) => wrap_into_c_void_ptr!(config), + None => std::ptr::null(), + } + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_upstream_http_tcp_bridge_config_new", + panic, + ); + std::ptr::null() + }) } #[no_mangle] unsafe extern "C" fn envoy_dynamic_module_on_upstream_http_tcp_bridge_config_destroy( config_module_ptr: abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_config_module_ptr, ) { - drop_wrapped_c_void_ptr!(config_module_ptr, UpstreamHttpTcpBridgeConfig); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!(config_module_ptr, UpstreamHttpTcpBridgeConfig); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_upstream_http_tcp_bridge_config_destroy", + panic, + ); + }); } #[no_mangle] @@ -346,11 +364,20 @@ unsafe extern "C" fn envoy_dynamic_module_on_upstream_http_tcp_bridge_new( config_module_ptr: abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_config_module_ptr, bridge_envoy_ptr: abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_envoy_ptr, ) -> abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_module_ptr { - let config = config_module_ptr as *const *const dyn UpstreamHttpTcpBridgeConfig; - let config = &**config; - let envoy_bridge = EnvoyUpstreamHttpTcpBridgeImpl::new(bridge_envoy_ptr); - let bridge = config.new_bridge(&envoy_bridge); - wrap_into_c_void_ptr!(bridge) + catch_unwind(AssertUnwindSafe(|| { + let config = config_module_ptr as *const *const dyn UpstreamHttpTcpBridgeConfig; + let config = &**config; + let envoy_bridge = EnvoyUpstreamHttpTcpBridgeImpl::new(bridge_envoy_ptr); + let bridge = config.new_bridge(&envoy_bridge); + wrap_into_c_void_ptr!(bridge) + })) + .unwrap_or_else(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_upstream_http_tcp_bridge_new", + panic, + ); + std::ptr::null() + }) } #[no_mangle] @@ -359,10 +386,18 @@ pub extern "C" fn envoy_dynamic_module_on_upstream_http_tcp_bridge_encode_header bridge_module_ptr: abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_module_ptr, end_of_stream: bool, ) { - let bridge = bridge_module_ptr as *mut Box; - let bridge = unsafe { &mut *bridge }; - let envoy_bridge = EnvoyUpstreamHttpTcpBridgeImpl::new(bridge_envoy_ptr); - bridge.on_encode_headers(&envoy_bridge, end_of_stream); + let _ = catch_unwind(AssertUnwindSafe(|| { + let bridge = bridge_module_ptr as *mut Box; + let bridge = unsafe { &mut *bridge }; + let envoy_bridge = EnvoyUpstreamHttpTcpBridgeImpl::new(bridge_envoy_ptr); + bridge.on_encode_headers(&envoy_bridge, end_of_stream); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_upstream_http_tcp_bridge_encode_headers", + panic, + ); + }); } #[no_mangle] @@ -371,10 +406,18 @@ pub extern "C" fn envoy_dynamic_module_on_upstream_http_tcp_bridge_encode_data( bridge_module_ptr: abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_module_ptr, end_of_stream: bool, ) { - let bridge = bridge_module_ptr as *mut Box; - let bridge = unsafe { &mut *bridge }; - let envoy_bridge = EnvoyUpstreamHttpTcpBridgeImpl::new(bridge_envoy_ptr); - bridge.on_encode_data(&envoy_bridge, end_of_stream); + let _ = catch_unwind(AssertUnwindSafe(|| { + let bridge = bridge_module_ptr as *mut Box; + let bridge = unsafe { &mut *bridge }; + let envoy_bridge = EnvoyUpstreamHttpTcpBridgeImpl::new(bridge_envoy_ptr); + bridge.on_encode_data(&envoy_bridge, end_of_stream); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_upstream_http_tcp_bridge_encode_data", + panic, + ); + }); } #[no_mangle] @@ -382,10 +425,18 @@ pub extern "C" fn envoy_dynamic_module_on_upstream_http_tcp_bridge_encode_traile bridge_envoy_ptr: abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_envoy_ptr, bridge_module_ptr: abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_module_ptr, ) { - let bridge = bridge_module_ptr as *mut Box; - let bridge = unsafe { &mut *bridge }; - let envoy_bridge = EnvoyUpstreamHttpTcpBridgeImpl::new(bridge_envoy_ptr); - bridge.on_encode_trailers(&envoy_bridge); + let _ = catch_unwind(AssertUnwindSafe(|| { + let bridge = bridge_module_ptr as *mut Box; + let bridge = unsafe { &mut *bridge }; + let envoy_bridge = EnvoyUpstreamHttpTcpBridgeImpl::new(bridge_envoy_ptr); + bridge.on_encode_trailers(&envoy_bridge); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_upstream_http_tcp_bridge_encode_trailers", + panic, + ); + }); } #[no_mangle] @@ -394,17 +445,33 @@ pub extern "C" fn envoy_dynamic_module_on_upstream_http_tcp_bridge_on_upstream_d bridge_module_ptr: abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_module_ptr, end_of_stream: bool, ) { - let bridge = bridge_module_ptr as *mut Box; - let bridge = unsafe { &mut *bridge }; - let envoy_bridge = EnvoyUpstreamHttpTcpBridgeImpl::new(bridge_envoy_ptr); - bridge.on_upstream_data(&envoy_bridge, end_of_stream); + let _ = catch_unwind(AssertUnwindSafe(|| { + let bridge = bridge_module_ptr as *mut Box; + let bridge = unsafe { &mut *bridge }; + let envoy_bridge = EnvoyUpstreamHttpTcpBridgeImpl::new(bridge_envoy_ptr); + bridge.on_upstream_data(&envoy_bridge, end_of_stream); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_upstream_http_tcp_bridge_on_upstream_data", + panic, + ); + }); } #[no_mangle] unsafe extern "C" fn envoy_dynamic_module_on_upstream_http_tcp_bridge_destroy( bridge_module_ptr: abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_module_ptr, ) { - drop_wrapped_c_void_ptr!(bridge_module_ptr, UpstreamHttpTcpBridge); + let _ = catch_unwind(AssertUnwindSafe(|| { + drop_wrapped_c_void_ptr!(bridge_module_ptr, UpstreamHttpTcpBridge); + })) + .map_err(|panic| { + crate::log_ffi_panic( + "envoy_dynamic_module_on_upstream_http_tcp_bridge_destroy", + panic, + ); + }); } /// Declare the init functions for the upstream HTTP TCP bridge dynamic module. @@ -417,16 +484,24 @@ macro_rules! declare_upstream_http_tcp_bridge_init_functions { ($f:ident, $new_bridge_config_fn:expr) => { #[no_mangle] pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { - envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( - envoy_proxy_dynamic_modules_rust_sdk::NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION, - $new_bridge_config_fn, - "NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION" - ); - if ($f()) { - envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() - as *const ::std::os::raw::c_char - } else { - ::std::ptr::null() + match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { + envoy_proxy_dynamic_modules_rust_sdk::set_factory_once!( + envoy_proxy_dynamic_modules_rust_sdk::NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION, + $new_bridge_config_fn, + "NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION" + ); + if ($f()) { + envoy_proxy_dynamic_modules_rust_sdk::abi::envoy_dynamic_modules_abi_version.as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + })) { + ::std::result::Result::Ok(v) => v, + ::std::result::Result::Err(payload) => { + $crate::log_ffi_panic("envoy_dynamic_module_on_program_init", payload); + ::std::ptr::null() + }, } } }; From 1ba3ceca01eccbd8d584bceea0b9ceab9b37cea2 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 00:14:32 +0100 Subject: [PATCH 247/750] deps: Bump build images -> `99bbf42` (#44907) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- .github/config.yml | 18 +++++++++--------- test/config/integration/certs/certs.sh | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/config.yml b/.github/config.yml index d474f951dc07b..8c702d4acb57f 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -4,16 +4,16 @@ build-image: repo: docker.io/envoyproxy/envoy-build repo-gcr: gcr.io/envoy-ci/envoy-build # default ci caching (ci) - sha: 20656853fae51927cda557e7af80ccff175f5de6f84bd0f092cd8672b2a6e0fe - sha-ci: 20656853fae51927cda557e7af80ccff175f5de6f84bd0f092cd8672b2a6e0fe - sha-devtools: 6e7a82d4f1ba040f4ebef0c1aae00cdbd205ff7a1284c20cc20984fdfa4a91d8 - sha-docker: 85b6c3e76f093d9c9d10a968b5615cc8d82f38d7aef311100d542e4d640f5a74 - sha-gcc: 439e870260c1599646d05b8b5d3bf1b6dd585c2e3cdac78dcb9f4081564c27fd - sha-mobile: bd1338a8951376211e4f4f6ff3171675670c4c582b0966f1d247abd3ba6a8a67 - sha-worker: 25a68eff24b7414a346977d545687b87851d1c5746c466798050fa12fc5d0686 + sha: afe3addcdb7a5177bcca77d041f079acf4a6c5fb32701aadb2ab96fd0933e0b2 + sha-ci: afe3addcdb7a5177bcca77d041f079acf4a6c5fb32701aadb2ab96fd0933e0b2 + sha-devtools: 420d53d577ba85245db3791255c53423ebc8869d7ca9940c0f29d1decba0c00a + sha-docker: 5fbafb6f366bc7815e9941f813854301ad4341d5dae4efc783bd1e3d602d58ba + sha-gcc: b344f62c6160304d002290f73339ac648b735c3700e28a1c6ad05d9fc2815cec + sha-mobile: 25ef9a9e8977150fe1278e7358307cd039bc90b0ebc5c16fea1a5cd566af4e38 + sha-worker: 7189239fbbc3983b15840609b122a35ce228a1af83aa440b022b1c949adcc32a # TODO: remove this dupe (currently used by ci request handler) - mobile-sha: bd1338a8951376211e4f4f6ff3171675670c4c582b0966f1d247abd3ba6a8a67 - tag: 86873047235e9b8232df989a5999b9bebf9db69c + mobile-sha: 25ef9a9e8977150fe1278e7358307cd039bc90b0ebc5c16fea1a5cd566af4e38 + tag: 99bbf423a0d1ff5a7332fb1e23bd62b90754fcd2 config: envoy: diff --git a/test/config/integration/certs/certs.sh b/test/config/integration/certs/certs.sh index 994158fca79ca..5e3fc3ae156ef 100755 --- a/test/config/integration/certs/certs.sh +++ b/test/config/integration/certs/certs.sh @@ -40,7 +40,7 @@ generate_x509_cert() { generate_expired_x509_cert() { local days="${3:-730}" openssl req -new -key "${1}key.pem" -out "${1}cert.csr" -config "${1}cert.cfg" -batch -sha256 - docker run --rm -u root -v "$(pwd):/work" -w /work envoyproxy/envoy-build:gcc-86873047235e9b8232df989a5999b9bebf9db69c sh -c "apt-get update -y && apt-get install -y faketime && faketime '370 days ago' openssl x509 -req -days \"${days}\" -in \"${1}cert.csr\" -sha256 -CA \"${2}cert.pem\" -CAkey \"${2}key.pem\" -CAcreateserial -out \"${1}cert.pem\" -extensions v3_ca -extfile \"${1}cert.cfg\"" + docker run --rm -u root -v "$(pwd):/work" -w /work envoyproxy/envoy-build:gcc-99bbf423a0d1ff5a7332fb1e23bd62b90754fcd2 sh -c "apt-get update -y && apt-get install -y faketime && faketime '370 days ago' openssl x509 -req -days \"${days}\" -in \"${1}cert.csr\" -sha256 -CA \"${2}cert.pem\" -CAkey \"${2}key.pem\" -CAcreateserial -out \"${1}cert.pem\" -extensions v3_ca -extfile \"${1}cert.cfg\"" echo -e "// NOLINT(namespace-envoy)\nconstexpr char TEST_$(echo "$1" | tr "[:lower:]" "[:upper:]")_CERT_HASH[] = \"$(openssl x509 -in "${1}cert.pem" -noout -fingerprint -sha256 | cut -d"=" -f2)\";" > "${1}cert_hash.h" } From 13d20f26b7ce7e2f303317c72f7d646778bcdf07 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Wed, 6 May 2026 19:17:12 -0400 Subject: [PATCH 248/750] ci: restrict Slack failure notifications to post-submit runs (#44911) Only send Slack notifications for CI failures on push events (post-submit), not on pull requests, to reduce noise. Signed-off-by: Jonh Wendell --- .github/workflows/_run.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 45f8d0d0aba11..e0479fa3a3271 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -451,7 +451,7 @@ jobs: ENVOY_PUBLISH_DRY_RUN: ${{ (fromJSON(inputs.request).request.version.dev || ! inputs.trusted) && 1 || '' }} - name: Notify Slack on failure - if: ${{ steps.ci-run.outputs.exit-code != 0 && inputs.slack-channel != '' }} + if: ${{ steps.ci-run.outputs.exit-code != 0 && inputs.slack-channel != '' && github.event.workflow_run.event == 'push' }} uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 env: JOB_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} From 510ee25ff996da2a7b2f416cef4ba612abe4df02 Mon Sep 17 00:00:00 2001 From: code Date: Thu, 7 May 2026 09:47:04 +0800 Subject: [PATCH 249/750] test only: improve coverage of proto extraction util (#44913) Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: wbpcode/wangbaiping --- .../extraction_util/BUILD | 1 + .../extraction_util/extraction_util_test.cc | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/test/extensions/filters/http/proto_message_extraction/extraction_util/BUILD b/test/extensions/filters/http/proto_message_extraction/extraction_util/BUILD index 525c22c841824..411faf617f675 100644 --- a/test/extensions/filters/http/proto_message_extraction/extraction_util/BUILD +++ b/test/extensions/filters/http/proto_message_extraction/extraction_util/BUILD @@ -18,6 +18,7 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/filters/http/proto_message_extraction/extraction_util", + "//source/extensions/filters/http/proto_message_extraction/extraction_util:proto_extractor", "//test/proto:extraction_proto_cc_proto", "//test/test_common:environment_lib", "//test/test_common:status_utility_lib", diff --git a/test/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util_test.cc b/test/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util_test.cc index aa2970e239bcc..a375b4c467993 100644 --- a/test/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util_test.cc +++ b/test/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util_test.cc @@ -6,6 +6,7 @@ #include #include "source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.h" +#include "source/extensions/filters/http/proto_message_extraction/extraction_util/proto_extractor.h" #include "test/proto/extraction.pb.h" #include "test/test_common/environment.h" @@ -1081,6 +1082,61 @@ INSTANTIATE_TEST_SUITE_P( return info.param.test_name; }); +// When the extracted field value is non-empty, both target_resource (callback=false) and +// target_resource_callback (callback=true) should be populated. +TEST_F(ExtractionUtilTest, GetTargetResourceOrTargetResourceCallback_NonEmptyValue) { + FieldPathToExtractType field_policies = {{"bucket.name", {ExtractedMessageDirective::EXTRACT}}}; + auto extractor = ProtoExtractor::Create(ScrubberContext::kTestScrubbing, type_helper_.get(), + request_type_, field_policies); + + ExtractedMessageMetadata metadata = extractor->ExtractMessage(test_request_raw_proto_); + + // callback=false path: target_resource is set. + ASSERT_TRUE(metadata.target_resource.has_value()); + EXPECT_EQ(metadata.target_resource.value(), "test-bucket"); + + // callback=true path with non-empty value: target_resource_callback is set. + ASSERT_TRUE(metadata.target_resource_callback.has_value()); + EXPECT_EQ(metadata.target_resource_callback.value(), "test-bucket"); +} + +// When the extracted field value is empty, callback=false sets target_resource to "" and +// callback=true falls through to the else branch (also setting target_resource to ""), so +// target_resource_callback remains unset. +TEST_F(ExtractionUtilTest, GetTargetResourceOrTargetResourceCallback_EmptyValue) { + test_request_proto_.mutable_bucket()->clear_name(); + test_request_raw_proto_ = CordMessageData(test_request_proto_.SerializeAsCord()); + + FieldPathToExtractType field_policies = {{"bucket.name", {ExtractedMessageDirective::EXTRACT}}}; + auto extractor = ProtoExtractor::Create(ScrubberContext::kTestScrubbing, type_helper_.get(), + request_type_, field_policies); + + ExtractedMessageMetadata metadata = extractor->ExtractMessage(test_request_raw_proto_); + + // callback=false path: target_resource is set with an empty string. + ASSERT_TRUE(metadata.target_resource.has_value()); + EXPECT_EQ(metadata.target_resource.value(), ""); + + // callback=true path with empty value hits the else branch, so target_resource_callback is NOT + // set. + EXPECT_FALSE(metadata.target_resource_callback.has_value()); +} + +// When ExtractStringFieldValue fails (field is not a string type), the function returns early +// without setting either target_resource or target_resource_callback. +TEST_F(ExtractionUtilTest, GetTargetResourceOrTargetResourceCallback_ExtractionFailure) { + // "bucket.ratio" is a float field (not a string), so ExtractStringFieldValue will fail, + // but it is a valid field path for the scrubber so no crash occurs. + FieldPathToExtractType field_policies = {{"bucket.ratio", {ExtractedMessageDirective::EXTRACT}}}; + auto extractor = ProtoExtractor::Create(ScrubberContext::kTestScrubbing, type_helper_.get(), + request_type_, field_policies); + + ExtractedMessageMetadata metadata = extractor->ExtractMessage(test_request_raw_proto_); + + EXPECT_FALSE(metadata.target_resource.has_value()); + EXPECT_FALSE(metadata.target_resource_callback.has_value()); +} + } // namespace } // namespace ProtoMessageExtraction } // namespace HttpFilters From 2013db5601ea2e1d2ef6bc9291d3431342daf633 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Thu, 7 May 2026 03:47:23 +0200 Subject: [PATCH 250/750] dynamic_modules: harden cache-hit module load + atomic stat_creation_frozen_ (#44851) ## Description This PR hardens the cache-hit module load and atomic stat_creation_frozen_. --- **Commit Message:** dynamic_modules: harden cache-hit module load + atomic stat_creation_frozen_ **Additional Description:** Harden cache-hit module load + atomic stat_creation_frozen_ **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** Added **Release Notes:** N/A --------- Signed-off-by: Rohit Agrawal --- source/extensions/dynamic_modules/BUILD | 2 + .../dynamic_modules/dynamic_modules.cc | 48 +++++++++ .../dynamic_modules/dynamic_modules.h | 18 ++++ .../filters/http/dynamic_modules/abi_impl.cc | 12 ++- .../filters/http/dynamic_modules/factory.cc | 45 ++++++--- .../http/dynamic_modules/filter_config.cc | 7 +- .../http/dynamic_modules/filter_config.h | 12 ++- test/extensions/dynamic_modules/BUILD | 3 + .../dynamic_modules/dynamic_modules_test.cc | 98 +++++++++++++++++++ .../dynamic_modules/http/abi_impl_test.cc | 2 +- .../dynamic_modules/http/config_test.cc | 54 ++++++++++ 11 files changed, 284 insertions(+), 17 deletions(-) diff --git a/source/extensions/dynamic_modules/BUILD b/source/extensions/dynamic_modules/BUILD index 7c5922745b797..508e3c8d10174 100644 --- a/source/extensions/dynamic_modules/BUILD +++ b/source/extensions/dynamic_modules/BUILD @@ -15,8 +15,10 @@ envoy_cc_library( hdrs = [ "dynamic_modules.h", ], + external_deps = ["ssl"], deps = [ "//envoy/common:exception_lib", + "//source/common/common:hex_lib", "//source/common/common:utility_lib", "//source/extensions/dynamic_modules/abi", ], diff --git a/source/extensions/dynamic_modules/dynamic_modules.cc b/source/extensions/dynamic_modules/dynamic_modules.cc index 0a18ef812fc49..43fa80b518d11 100644 --- a/source/extensions/dynamic_modules/dynamic_modules.cc +++ b/source/extensions/dynamic_modules/dynamic_modules.cc @@ -4,14 +4,19 @@ #include #include +#include #include #include "envoy/common/exception.h" +#include "source/common/common/hex.h" #include "source/common/common/utility.h" #include "source/extensions/dynamic_modules/abi/abi.h" +#include "absl/strings/ascii.h" #include "absl/strings/str_cat.h" +#include "openssl/evp.h" +#include "openssl/sha.h" namespace Envoy { namespace Extensions { @@ -155,6 +160,49 @@ std::filesystem::path moduleTempPath(const absl::string_view sha256) { return std::filesystem::temp_directory_path() / fmt::format("envoy_dynamic_module_{}.so", sha256); } +absl::Status verifyFileSha256(const std::filesystem::path& path, + absl::string_view expected_sha256_hex) { + std::ifstream file(path, std::ios::binary); + if (!file.is_open()) { + return absl::InternalError(absl::StrCat( + "Failed to open file for SHA256 verification: ", path.string(), ": ", errorDetails(errno))); + } + bssl::ScopedEVP_MD_CTX ctx; + if (EVP_DigestInit(ctx.get(), EVP_sha256()) != 1) { + return absl::InternalError("Failed to initialize SHA256 digest context"); + } + // 64 KiB chunks: large enough to keep syscall overhead low, small enough to keep stack clean. + std::array buf; + while (file) { + file.read(buf.data(), buf.size()); + const std::streamsize got = file.gcount(); + if (got > 0 && EVP_DigestUpdate(ctx.get(), buf.data(), static_cast(got)) != 1) { + return absl::InternalError("Failed to update SHA256 digest"); + } + if (file.bad()) { + return absl::InternalError( + absl::StrCat("I/O error reading file for SHA256 verification: ", path.string())); + } + } + std::array digest; + if (EVP_DigestFinal(ctx.get(), digest.data(), nullptr) != 1) { + return absl::InternalError("Failed to finalize SHA256 digest"); + } + std::string actual_hex = Hex::encode(digest.data(), digest.size()); + // The expected hash is operator-supplied (proto config, not user input) and the actual digest + // is computed from a file the attacker may control; the only information leaked by an + // early-exit comparison is "wrong remote module", which carries no secret. A constant-time + // compare is therefore not warranted here. + std::string expected_normalised{expected_sha256_hex}; + absl::AsciiStrToLower(&expected_normalised); + if (actual_hex != expected_normalised) { + return absl::FailedPreconditionError( + absl::StrCat("SHA256 mismatch for cached dynamic module at ", path.string(), ": expected ", + expected_normalised, " got ", actual_hex)); + } + return absl::OkStatus(); +} + absl::Status writeDynamicModuleBytesToDisk(const absl::string_view module_bytes, const absl::string_view sha256) { std::filesystem::path temp_file_path = moduleTempPath(sha256); diff --git a/source/extensions/dynamic_modules/dynamic_modules.h b/source/extensions/dynamic_modules/dynamic_modules.h index 2bb7c685270a1..e1f3909921b0b 100644 --- a/source/extensions/dynamic_modules/dynamic_modules.h +++ b/source/extensions/dynamic_modules/dynamic_modules.h @@ -145,6 +145,24 @@ absl::Status writeDynamicModuleBytesToDisk(absl::string_view module_bytes, */ std::filesystem::path moduleTempPath(absl::string_view sha256); +/** + * Verifies that the file at ``path`` has SHA256 digest equal to ``expected_sha256_hex``. + * + * This is used to defend the cache-hit fast path in remote-module loading: ``moduleTempPath`` + * is a deterministic location under ``/tmp`` derived from the expected hash. Without this + * check, an attacker who can write to ``/tmp`` (a co-tenant container or shared host) can + * pre-populate the path with a malicious shared object that Envoy will then dlopen, yielding + * attacker-controlled code execution. + * + * @param path absolute path to the file to verify. + * @param expected_sha256_hex the hex-encoded expected SHA256 digest (64 chars). The + * comparison is case-insensitive. + * @return OkStatus when the on-disk SHA256 matches; FailedPreconditionError on mismatch; + * InternalError on any I/O failure. + */ +absl::Status verifyFileSha256(const std::filesystem::path& path, + absl::string_view expected_sha256_hex); + } // namespace DynamicModules } // namespace Extensions } // namespace Envoy diff --git a/source/extensions/filters/http/dynamic_modules/abi_impl.cc b/source/extensions/filters/http/dynamic_modules/abi_impl.cc index a326689c3932c..0f5220fc95f8f 100644 --- a/source/extensions/filters/http/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/http/dynamic_modules/abi_impl.cc @@ -457,7 +457,9 @@ envoy_dynamic_module_callback_http_filter_config_define_counter( envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* counter_id_ptr) { auto filter_config = static_cast(filter_config_envoy_ptr); - if (filter_config->stat_creation_frozen_) { + // Acquire-load pairs with the release-store in the factory's + // ``newDynamicModuleHttpFilterConfig``. See filter_config.h for the memory-order contract. + if (filter_config->stat_creation_frozen_.load(std::memory_order_acquire)) { return envoy_dynamic_module_type_metrics_result_Frozen; } absl::string_view name_view(name.ptr, name.length); @@ -517,7 +519,9 @@ envoy_dynamic_module_callback_http_filter_config_define_gauge( envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* gauge_id_ptr) { auto filter_config = static_cast(filter_config_envoy_ptr); - if (filter_config->stat_creation_frozen_) { + // Acquire-load pairs with the release-store in the factory's + // ``newDynamicModuleHttpFilterConfig``. See filter_config.h for the memory-order contract. + if (filter_config->stat_creation_frozen_.load(std::memory_order_acquire)) { return envoy_dynamic_module_type_metrics_result_Frozen; } absl::string_view name_view(name.ptr, name.length); @@ -630,7 +634,9 @@ envoy_dynamic_module_callback_http_filter_config_define_histogram( envoy_dynamic_module_type_module_buffer* label_names, size_t label_names_length, size_t* histogram_id_ptr) { auto filter_config = static_cast(filter_config_envoy_ptr); - if (filter_config->stat_creation_frozen_) { + // Acquire-load pairs with the release-store in the factory's + // ``newDynamicModuleHttpFilterConfig``. See filter_config.h for the memory-order contract. + if (filter_config->stat_creation_frozen_.load(std::memory_order_acquire)) { return envoy_dynamic_module_type_metrics_result_Frozen; } absl::string_view name_view(name.ptr, name.length); diff --git a/source/extensions/filters/http/dynamic_modules/factory.cc b/source/extensions/filters/http/dynamic_modules/factory.cc index 3d9594c651581..fe26af71d021f 100644 --- a/source/extensions/filters/http/dynamic_modules/factory.cc +++ b/source/extensions/filters/http/dynamic_modules/factory.cc @@ -1,10 +1,13 @@ #include "source/extensions/filters/http/dynamic_modules/factory.h" #include +#include +#include "source/common/common/logger.h" #include "source/common/runtime/runtime_features.h" #include "source/extensions/common/wasm/remote_async_datasource.h" #include "source/extensions/dynamic_modules/background_fetch_manager.h" +#include "source/extensions/dynamic_modules/dynamic_modules.h" #include "source/extensions/filters/http/dynamic_modules/filter.h" #include "source/extensions/filters/http/dynamic_modules/filter_config.h" @@ -95,18 +98,38 @@ absl::StatusOr DynamicModuleConfigFactory::createFilterFa // filesystem itself acts as the cache. auto cached_path = Extensions::DynamicModules::moduleTempPath(sha256); if (std::filesystem::exists(cached_path)) { - dynamic_module = Extensions::DynamicModules::newDynamicModule( - cached_path, module_config.do_not_close(), module_config.load_globally()); - if (dynamic_module.ok()) { - Extensions::DynamicModules::BackgroundFetchManager::singleton(context.singletonManager()) - ->erase(sha256); - return buildFilterFactoryCallback(std::move(dynamic_module.value()), proto_config, - module_config, context, scope); + // Re-verify SHA256 of the cached file before dlopen. The cache path is in /tmp, which + // may be writable by other processes (co-tenant containers, shared hosts); without this + // check, an attacker who pre-populates ``/tmp/envoy_dynamic_module_.so`` + // with a malicious shared object turns the cache-hit fast path into arbitrary code + // execution. The fetch path that wrote the cached file already verified the hash, but + // we cannot trust that the file was not replaced between writes. + const auto verify_status = + Extensions::DynamicModules::verifyFileSha256(cached_path, sha256); + if (!verify_status.ok()) { + // Tampered or corrupted cache entry — remove it and fall through to the fetch path + // below so the legitimate remote source can re-supply correct bytes. + std::error_code ec; + std::filesystem::remove(cached_path, ec); + ENVOY_LOG_MISC(warn, + "dynamic_modules: removed cached file failing SHA256 verification: {}", + verify_status.message()); + // Fall through to the fetch logic below. + } else { + dynamic_module = Extensions::DynamicModules::newDynamicModule( + cached_path, module_config.do_not_close(), module_config.load_globally()); + if (dynamic_module.ok()) { + Extensions::DynamicModules::BackgroundFetchManager::singleton( + context.singletonManager()) + ->erase(sha256); + return buildFilterFactoryCallback(std::move(dynamic_module.value()), proto_config, + module_config, context, scope); + } + // File exists, hash matches, but failed to load — re-fetching the same SHA256 would + // produce identical bytes, so there is no point in falling through. + return absl::InvalidArgumentError("Cached remote module failed to load: " + + std::string(dynamic_module.status().message())); } - // File exists but failed to load — re-fetching the same SHA256 would produce - // identical bytes, so there is no point in falling through to the remote path. - return absl::InvalidArgumentError("Cached remote module failed to load: " + - std::string(dynamic_module.status().message())); } // In NACK mode, reject the config and kick off a background fetch. The control diff --git a/source/extensions/filters/http/dynamic_modules/filter_config.cc b/source/extensions/filters/http/dynamic_modules/filter_config.cc index e07805dd5c711..6934e3404eda7 100644 --- a/source/extensions/filters/http/dynamic_modules/filter_config.cc +++ b/source/extensions/filters/http/dynamic_modules/filter_config.cc @@ -214,7 +214,12 @@ absl::StatusOr newDynamicModuleHttpFilte } config->terminal_filter_ = terminal_filter; - config->stat_creation_frozen_ = true; + // Release-store: pairs with the acquire-loads in the per-request ``define_*`` callbacks + // (in abi_impl.cc) so a worker thread that observes ``stat_creation_frozen_=true`` is + // guaranteed to also observe all preceding writes to ``config``. Defends against an + // adversarial module spawning a worker inside ``on_http_filter_config_new`` that reads the + // flag before the C++-side shared_ptr publication establishes happens-before. + config->stat_creation_frozen_.store(true, std::memory_order_release); config->in_module_config_ = filter_config_envoy_ptr; config->on_http_filter_config_destroy_ = on_config_destroy.value(); diff --git a/source/extensions/filters/http/dynamic_modules/filter_config.h b/source/extensions/filters/http/dynamic_modules/filter_config.h index ee56193f20cce..a0cb294d78cbf 100644 --- a/source/extensions/filters/http/dynamic_modules/filter_config.h +++ b/source/extensions/filters/http/dynamic_modules/filter_config.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "envoy/server/factory_context.h" #include "envoy/upstream/cluster_manager.h" @@ -134,7 +136,15 @@ class DynamicModuleHttpFilterConfig // We only allow the module to create stats during envoy_dynamic_module_on_http_filter_config_new, // and not later during request handling, so that we don't have to wrap the stat storage in a // lock. - bool stat_creation_frozen_ = false; + // + // ``stat_creation_frozen_`` is set by the factory (main thread, before any worker observes + // this config) and read by the per-request ``define_*`` ABI callbacks (worker thread). The + // store/load pair establishes the necessary happens-before via release/acquire ordering; + // pre-fix this was a plain ``bool`` whose load was racy with the publishing store under the + // C++ memory model, even though steady-state shared_ptr publication usually masked it. An + // adversarial module spawning a worker thread inside ``on_http_filter_config_new`` could + // observe the stale ``false`` and reproduce the seed StatNamePool race. + std::atomic stat_creation_frozen_{false}; bool terminal_filter_ = false; diff --git a/test/extensions/dynamic_modules/BUILD b/test/extensions/dynamic_modules/BUILD index 331656e4498aa..cadfe0b779bf0 100644 --- a/test/extensions/dynamic_modules/BUILD +++ b/test/extensions/dynamic_modules/BUILD @@ -36,6 +36,9 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ ":util", + "//source/common/buffer:buffer_lib", + "//source/common/common:hex_lib", + "//source/common/crypto:utility_lib", "//source/extensions/dynamic_modules:abi_impl", "//source/extensions/dynamic_modules:dynamic_modules_lib", "//test/extensions/dynamic_modules/test_data/c:matcher_no_op_static", diff --git a/test/extensions/dynamic_modules/dynamic_modules_test.cc b/test/extensions/dynamic_modules/dynamic_modules_test.cc index f99254b035aae..4a54b0a3c8630 100644 --- a/test/extensions/dynamic_modules/dynamic_modules_test.cc +++ b/test/extensions/dynamic_modules/dynamic_modules_test.cc @@ -1,10 +1,14 @@ #include +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/hex.h" +#include "source/common/crypto/utility.h" #include "source/extensions/dynamic_modules/dynamic_modules.h" #include "test/extensions/dynamic_modules/util.h" #include "test/test_common/utility.h" +#include "absl/strings/ascii.h" #include "gtest/gtest.h" namespace Envoy { @@ -232,6 +236,100 @@ TEST(NewDynamicModuleFromBytes, InvalidBytes) { EXPECT_FALSE(std::filesystem::exists(temp_path)); } +TEST(VerifyFileSha256, RoundTripWithComputedDigest) { + const std::filesystem::path tmp = + std::filesystem::temp_directory_path() / "envoy_verify_sha256_round_trip.bin"; + const std::string contents = "the quick brown fox jumps over the lazy dog"; + { + std::ofstream out(tmp, std::ios::binary); + out << contents; + } + Buffer::OwnedImpl hash_buffer(contents); + const std::string expected_hex = + Hex::encode(Common::Crypto::UtilitySingleton::get().getSha256Digest(hash_buffer)); + EXPECT_TRUE(verifyFileSha256(tmp, expected_hex).ok()); + std::filesystem::remove(tmp); +} + +TEST(VerifyFileSha256, MismatchReturnsFailedPrecondition) { + const std::filesystem::path tmp = + std::filesystem::temp_directory_path() / "envoy_verify_sha256_mismatch.bin"; + { + std::ofstream out(tmp, std::ios::binary); + out << "envoy"; + } + const std::string wrong_sha = "0000000000000000000000000000000000000000000000000000000000000000"; + auto status = verifyFileSha256(tmp, wrong_sha); + EXPECT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition); + EXPECT_THAT(std::string(status.message()), testing::HasSubstr("SHA256 mismatch")); + EXPECT_THAT(std::string(status.message()), testing::HasSubstr(wrong_sha)); + std::filesystem::remove(tmp); +} + +TEST(VerifyFileSha256, MissingFileReturnsInternal) { + const std::filesystem::path missing = + std::filesystem::temp_directory_path() / "envoy_verify_sha256_missing.bin"; + std::filesystem::remove(missing); + ASSERT_FALSE(std::filesystem::exists(missing)); + auto status = + verifyFileSha256(missing, "0000000000000000000000000000000000000000000000000000000000000000"); + EXPECT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kInternal); + EXPECT_THAT(std::string(status.message()), + testing::HasSubstr("Failed to open file for SHA256 verification")); +} + +TEST(VerifyFileSha256, EmptyFileMatchesEmptyDigest) { + const std::filesystem::path tmp = + std::filesystem::temp_directory_path() / "envoy_verify_sha256_empty.bin"; + { std::ofstream out(tmp, std::ios::binary); } + // SHA256 of the empty input. + const std::string empty_sha = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + EXPECT_TRUE(verifyFileSha256(tmp, empty_sha).ok()); + std::filesystem::remove(tmp); +} + +TEST(VerifyFileSha256, LargeFileExceedingChunkSize) { + // Force the helper through multiple iterations of its 64 KiB read loop. 200 KiB picks up + // three full chunks plus a partial tail, exercising the streaming `SHA256_Update` path. + const std::filesystem::path tmp = + std::filesystem::temp_directory_path() / "envoy_verify_sha256_large.bin"; + std::string contents(200 * 1024, '\0'); + for (size_t i = 0; i < contents.size(); ++i) { + contents[i] = static_cast(i & 0xFF); + } + { + std::ofstream out(tmp, std::ios::binary); + out.write(contents.data(), contents.size()); + } + Buffer::OwnedImpl hash_buffer(contents); + const std::string expected_hex = + Hex::encode(Common::Crypto::UtilitySingleton::get().getSha256Digest(hash_buffer)); + EXPECT_TRUE(verifyFileSha256(tmp, expected_hex).ok()); + std::filesystem::remove(tmp); +} + +TEST(VerifyFileSha256, MixedCaseExpectedHexNormalised) { + const std::filesystem::path tmp = + std::filesystem::temp_directory_path() / "envoy_verify_sha256_mixed_case.bin"; + const std::string contents = "case-insensitive-hash"; + { + std::ofstream out(tmp, std::ios::binary); + out << contents; + } + Buffer::OwnedImpl hash_buffer(contents); + std::string expected_hex = + Hex::encode(Common::Crypto::UtilitySingleton::get().getSha256Digest(hash_buffer)); + // Upper-case the expected hex and verify it still matches (the helper lower-cases before + // comparing). + for (char& c : expected_hex) { + c = absl::ascii_toupper(c); + } + EXPECT_TRUE(verifyFileSha256(tmp, expected_hex).ok()); + std::filesystem::remove(tmp); +} + TEST(NewDynamicModuleFromBytes, RepeatedLoadReusesDlopenHandle) { std::filesystem::path test_lib = testSharedObjectPath("no_op", "c"); std::ifstream input(test_lib, std::ios::binary); diff --git a/test/extensions/dynamic_modules/http/abi_impl_test.cc b/test/extensions/dynamic_modules/http/abi_impl_test.cc index 2cd7200241ba0..47237208961c8 100644 --- a/test/extensions/dynamic_modules/http/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/http/abi_impl_test.cc @@ -2422,7 +2422,7 @@ TEST(ABIImpl, Stats) { EXPECT_EQ(result, envoy_dynamic_module_type_metrics_result_InvalidLabels); // test stat creation after freezing - filter_config->stat_creation_frozen_ = true; + filter_config->stat_creation_frozen_.store(true, std::memory_order_release); result = envoy_dynamic_module_callback_http_filter_config_define_counter( filter_config.get(), {counter_vec_name.data(), counter_vec_name.size()}, counter_vec_labels.data(), counter_vec_labels.size(), &counter_vec_id); diff --git a/test/extensions/dynamic_modules/http/config_test.cc b/test/extensions/dynamic_modules/http/config_test.cc index e72e972df4f8a..a21cb45bb23e4 100644 --- a/test/extensions/dynamic_modules/http/config_test.cc +++ b/test/extensions/dynamic_modules/http/config_test.cc @@ -505,6 +505,60 @@ TEST_F(DynamicModuleFilterConfigTest, NackModeCacheHit) { std::filesystem::remove(cached_path); } +// When the cache path holds a file whose contents do not match the expected SHA256, the +// factory must remove the tampered file and refuse to dlopen it. The fall-through fetch +// path then reports the cache miss in NACK mode. +TEST_F(DynamicModuleFilterConfigTest, NackModeCacheHitTamperedFileIsRejected) { + const std::string module_path = Extensions::DynamicModules::testSharedObjectPath("no_op", "c"); + std::ifstream input(module_path, std::ios::binary); + ASSERT_TRUE(input.good()); + const std::string module_bytes((std::istreambuf_iterator(input)), + std::istreambuf_iterator()); + + Buffer::OwnedImpl hash_buffer(module_bytes); + const std::string expected_sha256 = + Hex::encode(Common::Crypto::UtilitySingleton::get().getSha256Digest(hash_buffer)); + + // Pre-populate the cache path with attacker-controlled bytes that hash to something other + // than ``expected_sha256``. Without the SHA256 verification this would be dlopen'd as is. + auto cached_path = Extensions::DynamicModules::moduleTempPath(expected_sha256); + std::filesystem::create_directories(cached_path.parent_path()); + { + std::ofstream tampered(cached_path, std::ios::binary); + tampered << "not the expected module bytes"; + } + ASSERT_TRUE(std::filesystem::exists(cached_path)); + + const std::string yaml = TestEnvironment::substitute(absl::StrCat(R"EOF( + dynamic_module_config: + module: + remote: + http_uri: + uri: https://example.com/module.so + cluster: cluster_1 + timeout: 5s + sha256: ")EOF", + expected_sha256, R"EOF(" + do_not_close: true + nack_on_cache_miss: true + filter_name: "test_filter" + )EOF")); + + envoy::extensions::filters::http::dynamic_modules::v3::DynamicModuleFilter proto_config; + TestUtility::loadFromYamlAndValidate(yaml, proto_config); + + DynamicModuleConfigFactory factory; + auto cb_or_error = factory.createFilterFactoryFromProto(proto_config, "stats", context_); + // The verification step removes the tampered file and the code falls through to the fetch + // path; in NACK mode the fetch path reports "not cached". + EXPECT_FALSE(cb_or_error.ok()); + EXPECT_THAT(cb_or_error.status().message(), testing::HasSubstr("not cached")); + + // The tampered cache entry must have been removed by the factory so a subsequent legitimate + // fetch can repopulate it. + EXPECT_FALSE(std::filesystem::exists(cached_path)); +} + // A cache miss with nack_on_cache_miss rejects the config. TEST_F(DynamicModuleFilterConfigTest, NackModeCacheMissReturnsError) { const std::string yaml = R"EOF( From e49070a6b8720c3b6ee66110263b48f9a9b603ee Mon Sep 17 00:00:00 2001 From: Anton Kanugalawattage Date: Wed, 6 May 2026 23:07:22 -0400 Subject: [PATCH 251/750] reverse tunnels: allow connection upgrades (#44904) When reverse tunnels are used with L7 middle proxies (envoy HCM in our case) there are codec issues after the 200 OK, where the responder's sent data gets parsed as malformed requests. Allowing connection upgrades so that L7 proxying can be supported. Adds use_http_upgrade flag (default off) that negotiates the reverse-tunnel handshake as `HTTP/1.1 Upgrade: reverse-tunnel -> 101 Switching Protocols`. This lets HCM (or any L7 proxy) splice the connection after the 101 (same as websockets) and act as a pipe. Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Anton Kanugalawattage --- ..._reverse_connection_socket_interface.proto | 6 ++ .../reverse_tunnel/v3/reverse_tunnel.proto | 9 +- .../common/reverse_connection_utility.h | 4 + .../rc_connection_wrapper.cc | 16 +++- .../reverse_connection_io_handle.h | 8 +- .../reverse_tunnel_initiator.cc | 1 + .../reverse_tunnel_initiator_extension.h | 7 ++ .../reverse_tunnel/reverse_tunnel_filter.cc | 55 ++++++++++-- .../reverse_tunnel/reverse_tunnel_filter.h | 6 ++ .../rc_connection_wrapper_test.cc | 67 ++++++++++++++ .../reverse_tunnel/filter_unit_test.cc | 88 +++++++++++++++++++ 11 files changed, 256 insertions(+), 11 deletions(-) diff --git a/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/downstream_reverse_connection_socket_interface.proto b/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/downstream_reverse_connection_socket_interface.proto index cb3ccb4c1d9c1..59eb0bfaf5fd4 100644 --- a/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/downstream_reverse_connection_socket_interface.proto +++ b/api/envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/downstream_reverse_connection_socket_interface.proto @@ -27,6 +27,12 @@ message DownstreamReverseConnectionSocketInterface { // Additional headers to include in the HTTP handshake request. repeated config.core.v3.HeaderValueOption additional_headers = 2; + + // Perform the handshake as an HTTP/1.1 ``Upgrade`` exchange (``Upgrade: reverse-tunnel``, + // success on ``101``) so HTTP proxies can route the handshake and splice the tunnel + // afterward. The responder must set this flag to the same value. + // Defaults to ``false``. + bool use_http_upgrade = 3; } // Stat prefix to be used for downstream reverse connection socket interface stats. diff --git a/api/envoy/extensions/filters/network/reverse_tunnel/v3/reverse_tunnel.proto b/api/envoy/extensions/filters/network/reverse_tunnel/v3/reverse_tunnel.proto index ac9bf47174507..6dce5bda0608f 100644 --- a/api/envoy/extensions/filters/network/reverse_tunnel/v3/reverse_tunnel.proto +++ b/api/envoy/extensions/filters/network/reverse_tunnel/v3/reverse_tunnel.proto @@ -96,7 +96,7 @@ message Validation { // Configuration for the reverse tunnel network filter. // This filter handles reverse tunnel connection acceptance and rejection by processing // HTTP requests where required identification values are provided via HTTP headers. -// [#next-free-field: 7] +// [#next-free-field: 8] message ReverseTunnel { // Ping interval for health checks on established reverse tunnel connections. // If not specified, defaults to ``2 seconds``. @@ -133,4 +133,11 @@ message ReverseTunnel { // via ``x-envoy-reverse-tunnel-upstream-cluster-name`` header. Connections with mismatched or missing // cluster names are rejected with HTTP ``400 Bad Request``. When empty, no cluster name validation is performed. string required_cluster_name = 6 [(validate.rules).string = {max_len: 255 ignore_empty: true}]; + + // Accept the handshake as an HTTP/1.1 ``Upgrade`` exchange (``Upgrade: reverse-tunnel``, + // reply ``101``) so HTTP proxies can route the handshake and splice the tunnel + // afterward. Non-upgrade requests are rejected with ``426``. The initiator must set this + // flag to the same value. + // Defaults to ``false``. + bool use_http_upgrade = 7; } diff --git a/source/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility.h b/source/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility.h index 6522cfbe74027..f12c5aabc1d01 100644 --- a/source/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility.h +++ b/source/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility.h @@ -26,6 +26,10 @@ class ReverseConnectionUtility : public Logger::Loggable "/reverse_connections/request"; static constexpr absl::string_view TENANT_SCOPE_DELIMITER = ":"; + // Upgrade token advertised when the handshake is negotiated as an HTTP/1.1 Upgrade. + // Used by both the initiator (request `Upgrade:` header) and responder (`101` response). + static constexpr absl::string_view REVERSE_TUNNEL_UPGRADE_PROTOCOL = "reverse-tunnel"; + struct TenantScopedIdentifierView { absl::string_view tenant; absl::string_view identifier; diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc index c78c1bf86e8de..bfeab1188772e 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper.cc @@ -135,6 +135,15 @@ std::string RCConnectionWrapper::connect(const std::string& src_tenant_id, {{Http::Headers::get().Method, Http::Headers::get().MethodValues.Get}, {Http::Headers::get().Path, parent_.requestPath()}, {Http::Headers::get().Host, host_value}}); + if (parent_.useHttpUpgrade()) { + // Negotiate the handshake as an HTTP/1.1 Upgrade so HCM `upgrade_configs` can splice + // the connection raw after `101 Switching Protocols`. Both ends must agree. + headers->setReferenceKey(Http::Headers::get().Connection, + Http::Headers::get().ConnectionValues.Upgrade); + headers->setReferenceKey(Http::Headers::get().Upgrade, + ::Envoy::Extensions::Bootstrap::ReverseConnection:: + ReverseConnectionUtility::REVERSE_TUNNEL_UPGRADE_PROTOCOL); + } headers->addCopy(node_hdr, std::string(node_id)); headers->addCopy(cluster_hdr, std::string(cluster_id)); headers->addCopy(tenant_hdr, std::string(tenant_id)); @@ -178,11 +187,12 @@ std::string RCConnectionWrapper::connect(const std::string& src_tenant_id, void RCConnectionWrapper::decodeHeaders(Http::ResponseHeaderMapPtr&& headers, bool) { const uint64_t status = Http::Utility::getResponseStatus(*headers); - if (status == 200) { - ENVOY_LOG(debug, "Received HTTP 200 OK response"); + const uint64_t expected = parent_.useHttpUpgrade() ? 101 : 200; + if (status == expected) { + ENVOY_LOG(debug, "Received HTTP {} response", status); onHandshakeSuccess(); } else { - ENVOY_LOG(error, "Received non-200 HTTP response: {}", status); + ENVOY_LOG(error, "Received unexpected HTTP response: {} (expected {})", status, expected); onHandshakeFailure(HandshakeFailureReason::httpStatusError(absl::StrCat(status))); } } diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h index 49b628620e1be..ec097a8041b91 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h @@ -85,7 +85,8 @@ struct ReverseConnectionSocketConfig { std::string request_path{ std::string(ReverseConnectionUtility::DEFAULT_REVERSE_TUNNEL_REQUEST_PATH)}; std::vector - additional_headers; // Additional headers for the handshake request. + additional_headers; // Additional headers for the handshake request. + bool use_http_upgrade{false}; // Negotiate handshake as HTTP/1.1 Upgrade -> 101. // TODO(basundhara-c): Add support for multiple remote clusters using the same // ReverseConnectionIOHandle. Currently, each ReverseConnectionIOHandle handles // reverse connections for a single upstream cluster since a different ReverseConnectionAddress @@ -315,6 +316,11 @@ class ReverseConnectionIOHandle : public Network::IoSocketHandleImpl, return config_.additional_headers; } + /** + * @return whether the handshake is negotiated as an HTTP/1.1 Upgrade exchange. + */ + bool useHttpUpgrade() const { return config_.use_http_upgrade; } + private: /** * Get time source for consistent time operations. diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator.cc b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator.cc index 1f73dd4844fa1..ffa27813c3ab8 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator.cc +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator.cc @@ -128,6 +128,7 @@ ReverseTunnelInitiator::socket(Envoy::Network::Socket::Type socket_type, if (extension_ != nullptr) { socket_config.request_path = extension_->handshakeRequestPath(); socket_config.additional_headers = extension_->handshakeAdditionalHeaders(); + socket_config.use_http_upgrade = extension_->handshakeUsesHttpUpgrade(); } // Pass config directly to helper method. diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension.h b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension.h index ebf1734995de5..fd894c3d9dc01 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension.h +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension.h @@ -47,6 +47,7 @@ class ReverseTunnelInitiatorExtension : public Server::BootstrapExtension, if (config.has_http_handshake()) { additional_headers_ = {config.http_handshake().additional_headers().begin(), config.http_handshake().additional_headers().end()}; + use_http_upgrade_ = config.http_handshake().use_http_upgrade(); } ENVOY_LOG(debug, "ReverseTunnelInitiatorExtension: creating downstream reverse connection " @@ -117,6 +118,11 @@ class ReverseTunnelInitiatorExtension : public Server::BootstrapExtension, return additional_headers_; } + /** + * @return whether the handshake is negotiated as an HTTP/1.1 Upgrade exchange. + */ + bool handshakeUsesHttpUpgrade() const { return use_http_upgrade_; } + /** * Increment handshake stats for reverse tunnel connections (per-worker only). * Only tracks stats if enable_detailed_stats flag is true. @@ -147,6 +153,7 @@ class ReverseTunnelInitiatorExtension : public Server::BootstrapExtension, bool enable_detailed_stats_{false}; std::string handshake_request_path_; std::vector additional_headers_; + bool use_http_upgrade_{false}; /** * Update per-worker connection stats for debugging purposes. diff --git a/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc b/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc index ea24e8f9cb3ca..959501f3e3d90 100644 --- a/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc +++ b/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc @@ -13,6 +13,7 @@ #include "source/common/http/header_map_impl.h" #include "source/common/http/headers.h" #include "source/common/http/http1/codec_impl.h" +#include "source/common/http/utility.h" #include "source/common/network/connection_socket_impl.h" #include "source/common/router/string_accessor_impl.h" #include "source/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility.h" @@ -156,7 +157,8 @@ ReverseTunnelFilterConfig::ReverseTunnelFilterConfig( !proto_config.validation().dynamic_metadata_namespace().empty() ? proto_config.validation().dynamic_metadata_namespace() : "envoy.filters.network.reverse_tunnel"), - required_cluster_name_(proto_config.required_cluster_name()) {} + required_cluster_name_(proto_config.required_cluster_name()), + use_http_upgrade_(proto_config.use_http_upgrade()) {} bool ReverseTunnelFilterConfig::validateIdentifiers( absl::string_view node_id, absl::string_view cluster_id, absl::string_view tenant_id, @@ -281,7 +283,11 @@ Http::RequestDecoder& ReverseTunnelFilter::newStream(Http::ResponseEncoder& resp void ReverseTunnelFilter::RequestDecoderImpl::decodeHeaders( Http::RequestHeaderMapSharedPtr&& headers, bool end_stream) { headers_ = std::move(headers); - if (end_stream) { + // For an Upgrade request, the HTTP/1 server codec calls decodeHeaders with + // end_stream=false because it now considers the connection a tunnel awaiting + // more bytes. The handshake has no body, so process the headers immediately + // rather than waiting for an end-of-stream that never comes. + if (end_stream || Http::Utility::isUpgrade(*headers_)) { processIfComplete(true); } } @@ -350,6 +356,31 @@ void ReverseTunnelFilter::RequestDecoderImpl::processIfComplete(bool end_stream) return; } + // When upgrade negotiation is enabled, require the request to advertise the + // `reverse-tunnel` upgrade. The HTTP/1 server codec already validates the + // `Connection: Upgrade` paired token, so we only re-check the `Upgrade` value here. + if (parent_.config_->useHttpUpgrade()) { + const auto upgrade = headers_->getUpgradeValue(); + if (!absl::EqualsIgnoreCase(upgrade, Bootstrap::ReverseConnection::ReverseConnectionUtility:: + REVERSE_TUNNEL_UPGRADE_PROTOCOL)) { + parent_.stats_.parse_error_.inc(); + ENVOY_CONN_LOG(debug, + "reverse_tunnel: upgrade negotiation enabled but Upgrade header missing or " + "unexpected (got '{}')", + parent_.read_callbacks_->connection(), upgrade); + sendLocalReply( + Http::Code::UpgradeRequired, "Upgrade: reverse-tunnel required", + [](Http::ResponseHeaderMap& h) { + h.setReferenceKey(Http::Headers::get().Upgrade, + Bootstrap::ReverseConnection::ReverseConnectionUtility:: + REVERSE_TUNNEL_UPGRADE_PROTOCOL); + }, + absl::nullopt, "reverse_tunnel_upgrade_required"); + parent_.read_callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite); + return; + } + } + // Extract node/cluster/tenant identifiers from HTTP headers. const auto node_vals = headers_->get(Extensions::Bootstrap::ReverseConnection::reverseTunnelNodeIdHeader()); @@ -453,16 +484,28 @@ void ReverseTunnelFilter::RequestDecoderImpl::processIfComplete(bool end_stream) return; } - // Respond with 200 OK. + // Respond. In upgrade mode we send `101 Switching Protocols` with the upgrade headers + // echoed; the HTTP/1 server codec (created with allow_upgrade) flushes them and stops + // parsing further bytes as HTTP, leaving the spliced connection for the tunnel. auto resp_headers = Http::ResponseHeaderMapImpl::create(); - resp_headers->setStatus(200); + if (parent_.config_->useHttpUpgrade()) { + resp_headers->setStatus(101); + resp_headers->setReferenceKey(Http::Headers::get().Connection, + Http::Headers::get().ConnectionValues.Upgrade); + resp_headers->setReferenceKey( + Http::Headers::get().Upgrade, + Bootstrap::ReverseConnection::ReverseConnectionUtility::REVERSE_TUNNEL_UPGRADE_PROTOCOL); + } else { + resp_headers->setStatus(200); + } encoder_.encodeHeaders(*resp_headers, true); parent_.processAcceptedConnection(node_id, cluster_id, tenant_id); parent_.stats_.accepted_.inc(); - // Close the connection if configured to do so after handling the request. - if (parent_.config_->autoCloseConnections()) { + // Close the listener-side connection so tunnel bytes go to the duped fd, not back into + // this filter's codec. Required in upgrade mode; opt-in otherwise. + if (parent_.config_->useHttpUpgrade() || parent_.config_->autoCloseConnections()) { auto& connection = parent_.read_callbacks_->connection(); Bootstrap::ReverseConnection::ReverseConnectionUtility::applySslQuietClose(connection); connection.close(Network::ConnectionCloseType::FlushWrite); diff --git a/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.h b/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.h index 08411407f8781..a665c806998d7 100644 --- a/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.h +++ b/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.h @@ -62,6 +62,9 @@ class ReverseTunnelFilterConfig : public Logger::Loggable { // Returns the required cluster name for validation. const std::string& requiredClusterName() const { return required_cluster_name_; } + // Returns whether the handshake is negotiated as an HTTP/1.1 Upgrade exchange. + bool useHttpUpgrade() const { return use_http_upgrade_; } + private: ReverseTunnelFilterConfig( const envoy::extensions::filters::network::reverse_tunnel::v3::ReverseTunnel& proto_config, @@ -83,6 +86,9 @@ class ReverseTunnelFilterConfig : public Logger::Loggable { // Required cluster name for validation (empty means no validation). const std::string required_cluster_name_; + + // When true, expect `Connection: Upgrade` + `Upgrade: reverse-tunnel` and respond `101`. + const bool use_http_upgrade_{false}; }; using ReverseTunnelFilterConfigSharedPtr = std::shared_ptr; diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc index 06f07c7cd7fd8..cd63ad988869a 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc @@ -1073,6 +1073,73 @@ TEST_F(RCConnectionWrapperTest, DecodeHeadersNonOk) { wrapper.decodeHeaders(std::move(headers), true); } +// In upgrade mode, a 101 Switching Protocols response is treated as handshake success +// and a 200 response is treated as a protocol mismatch. +TEST_F(RCConnectionWrapperTest, DecodeHeadersUpgradeMode) { + ReverseConnectionSocketConfig upgrade_config = createDefaultTestConfig(); + upgrade_config.use_http_upgrade = true; + auto upgrade_io_handle = createTestIOHandle(upgrade_config); + + auto mock_host = std::make_shared>(); + + // 101 -> success path. + { + auto mock_connection = setupMockConnection(); + RCConnectionWrapper wrapper(*upgrade_io_handle, std::move(mock_connection), mock_host, + "test-cluster"); + Http::ResponseHeaderMapPtr headers = Http::ResponseHeaderMapImpl::create(); + headers->setStatus(101); + wrapper.decodeHeaders(std::move(headers), true); + } + // 200 in upgrade mode -> failure path (server didn't switch protocols). + { + auto mock_connection = setupMockConnection(); + RCConnectionWrapper wrapper(*upgrade_io_handle, std::move(mock_connection), mock_host, + "test-cluster"); + Http::ResponseHeaderMapPtr headers = Http::ResponseHeaderMapImpl::create(); + headers->setStatus(200); + wrapper.decodeHeaders(std::move(headers), true); + } +} + +// In upgrade mode, connect() emits `Connection: Upgrade` + `Upgrade: reverse-tunnel` +// in the handshake request. Verifies by capturing the bytes written by the encoder. +TEST_F(RCConnectionWrapperTest, ConnectEmitsUpgradeHeaders) { + ReverseConnectionSocketConfig upgrade_config = createDefaultTestConfig(); + upgrade_config.use_http_upgrade = true; + auto upgrade_io_handle = createTestIOHandle(upgrade_config); + + auto mock_connection = std::make_unique>(); + std::string written; + EXPECT_CALL(*mock_connection, addConnectionCallbacks(_)); + EXPECT_CALL(*mock_connection, addReadFilter(_)); + EXPECT_CALL(*mock_connection, connect()); + EXPECT_CALL(*mock_connection, id()).WillRepeatedly(Return(43)); + EXPECT_CALL(*mock_connection, state()).WillRepeatedly(Return(Network::Connection::State::Open)); + EXPECT_CALL(*mock_connection, write(_, _)) + .WillRepeatedly(Invoke([&](Buffer::Instance& buffer, bool) { + written.append(buffer.toString()); + buffer.drain(buffer.length()); + })); + + auto mock_remote = std::make_shared("10.0.0.1", 80); + auto mock_local = std::make_shared("127.0.0.1", 10001); + EXPECT_CALL(*mock_connection, connectionInfoProvider()) + .WillRepeatedly(Invoke([mock_remote, mock_local]() -> const Network::ConnectionInfoProvider& { + static auto provider = + std::make_unique(mock_local, mock_remote); + return *provider; + })); + + auto mock_host = std::make_shared>(); + RCConnectionWrapper wrapper(*upgrade_io_handle, std::move(mock_connection), mock_host, + "test-cluster"); + (void)wrapper.connect("tenant", "cluster", "node"); + + EXPECT_THAT(written, testing::HasSubstr("upgrade: reverse-tunnel")); + EXPECT_THAT(written, testing::HasSubstr("connection: upgrade")); +} + // Test dispatchHttp1 error path by initializing codec via connect() and // then feeding invalid bytes to the parser. TEST_F(RCConnectionWrapperTest, DispatchHttp1ErrorPath) { diff --git a/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc b/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc index 492bb0a19d312..23e6025130165 100644 --- a/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc +++ b/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc @@ -2094,6 +2094,94 @@ TEST_F(ReverseTunnelFilterWithTenantIsolationTest, FilterReadsTenantIsolationFro EXPECT_EQ(1, parse_error->value()); } +// Upgrade-mode handshake: `Upgrade: reverse-tunnel` request -> `101` with echoed headers. +TEST_F(ReverseTunnelFilterWithUpstreamTest, UpgradeMode_RespondsWith101) { + // Reconfigure filter with upgrade enabled. + proto_config_.set_use_http_upgrade(true); + auto config_or_error = ReverseTunnelFilterConfig::create(proto_config_, factory_context_); + ASSERT_TRUE(config_or_error.ok()); + config_ = config_or_error.value(); + filter_ = + std::make_unique(config_, *stats_store_.rootScope(), overload_manager_); + filter_->initializeReadFilterCallbacks(callbacks_); + + // Build a socket whose io_handle.duplicate() returns a valid duped handle, since the + // success path in `processAcceptedConnection` registers the duped fd with the upstream + // socket manager. Pattern mirrors `SuccessfulSocketDuplication` above. + auto mock_socket = std::make_unique(); + auto mock_io_handle = std::make_unique(); + auto dup_handle = std::make_unique(); + EXPECT_CALL(*dup_handle, isOpen()).WillRepeatedly(testing::Return(true)); + EXPECT_CALL(*dup_handle, resetFileEvents()); + EXPECT_CALL(*dup_handle, fdDoNotUse()).WillRepeatedly(testing::Return(123)); + EXPECT_CALL(*mock_io_handle, duplicate()) + .WillOnce(testing::Return(testing::ByMove(std::move(dup_handle)))); + EXPECT_CALL(*mock_socket, ioHandle()).WillRepeatedly(testing::ReturnRef(*mock_io_handle)); + EXPECT_CALL(*mock_socket, isOpen()).WillRepeatedly(testing::Return(true)); + EXPECT_CALL(*mock_io_handle, fdDoNotUse()).WillRepeatedly(testing::Return(122)); + static Network::ConnectionSocketPtr stored_socket; + static std::unique_ptr stored_handle; + stored_handle = std::move(mock_io_handle); + stored_socket = std::move(mock_socket); + EXPECT_CALL(callbacks_.connection_, getSocket()) + .WillRepeatedly(testing::ReturnRef(stored_socket)); + + std::string written; + EXPECT_CALL(callbacks_.connection_, write(testing::_, testing::_)) + .WillRepeatedly(testing::Invoke([&](Buffer::Instance& data, bool) { + written.append(data.toString()); + data.drain(data.length()); + })); + // Upgrade mode forces the original connection to close after handshake. + EXPECT_CALL(callbacks_.connection_, close(Network::ConnectionCloseType::FlushWrite)); + + std::string req = "GET /reverse_connections/request HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: Upgrade\r\n" + "Upgrade: reverse-tunnel\r\n" + "x-envoy-reverse-tunnel-node-id: n\r\n" + "x-envoy-reverse-tunnel-cluster-id: c\r\n" + "x-envoy-reverse-tunnel-tenant-id: t\r\n" + "Content-Length: 0\r\n\r\n"; + Buffer::OwnedImpl request(req); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(request, false)); + EXPECT_THAT(written, testing::HasSubstr("101 Switching Protocols")); + EXPECT_THAT(written, testing::HasSubstr("upgrade: reverse-tunnel")); + + auto accepted = TestUtility::findCounter(stats_store_, "reverse_tunnel.handshake.accepted"); + ASSERT_NE(nullptr, accepted); + EXPECT_EQ(1, accepted->value()); +} + +// When `use_http_upgrade=true` but the request does not advertise the upgrade, +// the filter rejects with `426 Upgrade Required` and closes the connection. +TEST_F(ReverseTunnelFilterWithUpstreamTest, UpgradeMode_MissingUpgradeRejected) { + proto_config_.set_use_http_upgrade(true); + auto config_or_error = ReverseTunnelFilterConfig::create(proto_config_, factory_context_); + ASSERT_TRUE(config_or_error.ok()); + config_ = config_or_error.value(); + filter_ = + std::make_unique(config_, *stats_store_.rootScope(), overload_manager_); + filter_->initializeReadFilterCallbacks(callbacks_); + + std::string written; + EXPECT_CALL(callbacks_.connection_, write(testing::_, testing::_)) + .WillRepeatedly(testing::Invoke([&](Buffer::Instance& data, bool) { + written.append(data.toString()); + data.drain(data.length()); + })); + EXPECT_CALL(callbacks_.connection_, close(Network::ConnectionCloseType::FlushWrite)); + + Buffer::OwnedImpl request( + makeHttpRequestWithRtHeaders("GET", "/reverse_connections/request", "n", "c", "t")); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(request, false)); + EXPECT_THAT(written, testing::HasSubstr("426 Upgrade Required")); + + auto parse_error = TestUtility::findCounter(stats_store_, "reverse_tunnel.handshake.parse_error"); + ASSERT_NE(nullptr, parse_error); + EXPECT_EQ(1, parse_error->value()); +} + } // namespace } // namespace ReverseTunnel } // namespace NetworkFilters From 280958d41e80443ef315ea597bb1ad9710921804 Mon Sep 17 00:00:00 2001 From: Xin Date: Thu, 7 May 2026 07:47:18 -0400 Subject: [PATCH 252/750] ext_proc: avoid heap allocation for QueuedChunk in ChunkQueue, it doesn't deserve a unique_ptr when it's ~ 8B.(#44824) Commit Message: avoid heap allocation for QueuedChunk in ChunkQueue, it doesn't deserve a unique_ptr when it's < 8B. Additional Description: part of issue #44795 Risk Level: LOW Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Xin Zhuang --- .../filters/http/ext_proc/ext_proc.cc | 2 +- .../filters/http/ext_proc/processor_state.cc | 37 +++++++++--------- .../filters/http/ext_proc/processor_state.h | 10 ++--- .../filters/http/ext_proc/filter_test.cc | 39 +++++++++++++++++++ 4 files changed, 63 insertions(+), 25 deletions(-) diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index f5c8d30340337..87f9b35eb4e23 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -1710,7 +1710,7 @@ bool eosSeenInBody(ProcessorState& state, return false; case ProcessingMode::STREAMED: if (!state.chunkQueue().empty()) { - return state.chunkQueue().queue().front()->end_stream; + return state.chunkQueue().queue().front().end_stream; } return false; case ProcessingMode::FULL_DUPLEX_STREAMED: { diff --git a/source/extensions/filters/http/ext_proc/processor_state.cc b/source/extensions/filters/http/ext_proc/processor_state.cc index ac1e9eae0ebfd..81bb91df8f071 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.cc +++ b/source/extensions/filters/http/ext_proc/processor_state.cc @@ -443,7 +443,7 @@ ProcessorState::handleStreamedBodyCallback(const CommonResponse& common_response absl::StatusOr ProcessorState::handleBufferedPartialBodyCallback(const CommonResponse& common_response) { Buffer::OwnedImpl chunk_data; - QueuedChunkPtr chunk = dequeueStreamingChunk(chunk_data); + absl::optional chunk = dequeueStreamingChunk(chunk_data); if (!chunk) { ENVOY_BUG(false, "Bad partial body callback state"); return absl::InternalError("Invalid chunk in partial body callback"); @@ -574,7 +574,7 @@ void ProcessorState::enqueueStreamingChunk(Buffer::Instance& data, bool end_stre } } -QueuedChunkPtr ProcessorState::dequeueStreamingChunk(Buffer::OwnedImpl& out_data) { +absl::optional ProcessorState::dequeueStreamingChunk(Buffer::OwnedImpl& out_data) { return chunk_queue_.pop(out_data); } @@ -602,8 +602,11 @@ void ProcessorState::continueIfNecessary() { bool ProcessorState::handleStreamedBodyResponse(const CommonResponse& common_response) { Buffer::OwnedImpl chunk_data; - QueuedChunkPtr chunk = dequeueStreamingChunk(chunk_data); - ENVOY_BUG(chunk != nullptr, "Bad streamed body callback state"); + absl::optional chunk = dequeueStreamingChunk(chunk_data); + if (!chunk.has_value()) { + IS_ENVOY_BUG("Bad streamed body callback state"); + return false; + } if (common_response.has_body_mutation()) { Effect processing_effect; ENVOY_STREAM_LOG(debug, "Applying body response to chunk of data. Size = {}", @@ -851,46 +854,42 @@ void EncodingProcessorState::setLocalResponseStreaming() { void ChunkQueue::push(Buffer::Instance& data, bool end_stream) { // Adding the chunk into the queue. - auto next_chunk = std::make_unique(); - next_chunk->length = data.length(); - next_chunk->end_stream = end_stream; - queue_.push_back(std::move(next_chunk)); + queue_.push_back({end_stream, static_cast(data.length())}); bytes_enqueued_ += data.length(); // Adding the data to the buffer. received_data_.move(data); } -QueuedChunkPtr ChunkQueue::pop(Buffer::OwnedImpl& out_data) { +absl::optional ChunkQueue::pop(Buffer::OwnedImpl& out_data) { if (queue_.empty()) { - return nullptr; + return absl::nullopt; } - QueuedChunkPtr chunk = std::move(queue_.front()); + QueuedChunk chunk = queue_.front(); queue_.pop_front(); - bytes_enqueued_ -= chunk->length; + bytes_enqueued_ -= chunk.length; // Move the corresponding data out. - out_data.move(received_data_, chunk->length); + out_data.move(received_data_, chunk.length); return chunk; } const QueuedChunk& ChunkQueue::consolidate() { if (queue_.size() > 1) { - auto new_chunk = std::make_unique(); - new_chunk->end_stream = queue_.back()->end_stream; - new_chunk->length = bytes_enqueued_; + bool end_stream = queue_.back().end_stream; queue_.clear(); - queue_.push_front(std::move(new_chunk)); + queue_.push_front({end_stream, bytes_enqueued_}); } - auto& chunk = *(queue_.front()); + auto& chunk = queue_.front(); return chunk; } void ChunkQueue::clear() { - if (queue_.size() > 1) { + if (!queue_.empty()) { received_data_.drain(received_data_.length()); queue_.clear(); + bytes_enqueued_ = 0; } } diff --git a/source/extensions/filters/http/ext_proc/processor_state.h b/source/extensions/filters/http/ext_proc/processor_state.h index c0aac5f8f2b7c..e008a171a924c 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.h +++ b/source/extensions/filters/http/ext_proc/processor_state.h @@ -17,6 +17,7 @@ #include "source/extensions/filters/common/processing_effect/processing_effect.h" #include "absl/status/status.h" +#include "absl/types/optional.h" #include "matching_utils.h" namespace Envoy { @@ -32,7 +33,6 @@ class QueuedChunk { bool end_stream = false; uint32_t length = 0; }; -using QueuedChunkPtr = std::unique_ptr; class ChunkQueue { public: @@ -43,13 +43,13 @@ class ChunkQueue { bool empty() const { return queue_.empty(); } void push(Buffer::Instance& data, bool end_stream); void clear(); - QueuedChunkPtr pop(Buffer::OwnedImpl& out_data); + absl::optional pop(Buffer::OwnedImpl& out_data); const QueuedChunk& consolidate(); Buffer::OwnedImpl& receivedData() { return received_data_; } - const std::deque& queue() const { return queue_; } + const std::deque& queue() const { return queue_; } private: - std::deque queue_; + std::deque queue_; // The total size of chunks in the queue. uint32_t bytes_enqueued_{}; // The received data that had not been sent to downstream/upstream. @@ -222,7 +222,7 @@ class ProcessorState : public Logger::Loggable { // Move the contents of "data" into a QueuedChunk object on the streaming queue. void enqueueStreamingChunk(Buffer::Instance& data, bool end_stream); // If the queue has chunks, return the head of the queue. - QueuedChunkPtr dequeueStreamingChunk(Buffer::OwnedImpl& out_data); + absl::optional dequeueStreamingChunk(Buffer::OwnedImpl& out_data); // Consolidate all the chunks on the queue into a single one and return a reference. const QueuedChunk& consolidateStreamedChunks() { return chunk_queue_.consolidate(); } bool queueOverHighLimit() const { return chunk_queue_.bytesEnqueued() > bufferLimit(); } diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index 85918a826f142..ce66a158e5fe1 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -3534,6 +3534,45 @@ TEST_F(HttpFilterTest, OutOfOrderFailClose) { EXPECT_EQ(1, config_->stats().streams_closed_.value()); } +// Test the "!chunk.has_value()" behavior when chunk_queue_ is empty during a streamed body +// callback. +TEST_F(HttpFilterTest, StreamedBodyCallbackWithEmptyQueue) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + processing_mode: + request_header_mode: "SEND" + response_header_mode: "SKIP" + request_body_mode: "STREAMED" + )EOF"); + + HttpTestUtility::addDefaultHeaders(request_headers_); + request_headers_.setMethod("POST"); + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + + // Handle headers response to establish the gRPC stream and initialize stream_callbacks_. + processRequestHeaders(false, absl::nullopt); + + // Manually transition decoding_state_ to StreamedBodyCallback while the chunk_queue_ is empty. + auto& decoding_state = const_cast(filter_->decodingState()); + decoding_state.onFinishProcessorCall(Grpc::Status::Ok, + ProcessorState::CallbackState::StreamedBodyCallback); + + // Receive a body response from the server. + std::unique_ptr resp = std::make_unique(); + resp->mutable_request_body(); + + // This triggers handleBodyResponse, which delegates to handleStreamedBodyResponse. + // Since the chunk_queue_ is empty, it will hit the "!chunk.has_value()" branch, trigger + // IS_ENVOY_BUG, and return false. + EXPECT_ENVOY_BUG( + { stream_callbacks_->onReceiveMessage(std::move(resp)); }, + "Bad streamed body callback state"); + + filter_->onDestroy(); +} + class OverrideTest : public testing::Test { protected: void SetUp() override { From 60c3829600b529d4f2bbb534097b2823cf951008 Mon Sep 17 00:00:00 2001 From: "Ethan M." <59028647+ebm@users.noreply.github.com> Date: Thu, 7 May 2026 12:40:08 -0400 Subject: [PATCH 253/750] Added coverage for the uninitialized gauge (#44914) Commit Message: test: adds coverage for uninitialized gauge import mode behavior Additional Description: Adds code in a unit test that ensures Gauge::ImportMode::Uninitialized correctly reports its import mode and is not hidden. Risk Level: Low Testing: All existing tests pass locally. Docs Changes: None Release Notes: Test only change. Platform Specific Features: None Signed-off-by: Ethan Marantz --- test/common/stats/allocator_test.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/common/stats/allocator_test.cc b/test/common/stats/allocator_test.cc index 42ba717ab43ad..70b0ea2d2f702 100644 --- a/test/common/stats/allocator_test.cc +++ b/test/common/stats/allocator_test.cc @@ -138,6 +138,11 @@ TEST_F(AllocatorTest, RefCountDecAllocRaceSynchronized) { } TEST_F(AllocatorTest, HiddenGauge) { + GaugeSharedPtr uninitialized_gauge = + alloc_.makeGauge(makeStat("uninitialized"), StatName(), {}, Gauge::ImportMode::Uninitialized); + EXPECT_EQ(uninitialized_gauge->importMode(), Gauge::ImportMode::Uninitialized); + EXPECT_FALSE(uninitialized_gauge->hidden()); + GaugeSharedPtr hidden_gauge = alloc_.makeGauge(makeStat("hidden"), StatName(), {}, Gauge::ImportMode::HiddenAccumulate); EXPECT_EQ(hidden_gauge->importMode(), Gauge::ImportMode::HiddenAccumulate); From 2ba9b5e5396549e9a634c49711e0d8a9ccaef6c8 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 7 May 2026 18:01:07 +0100 Subject: [PATCH 254/750] ci: Cleanup dockerhub-username (#44927) Signed-off-by: Ryan Northey --- .github/workflows/_publish_release.yml | 6 ++++-- .github/workflows/_publish_release_container.yml | 6 ++++-- .github/workflows/_run.yml | 3 --- .github/workflows/envoy-publish.yml | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/_publish_release.yml b/.github/workflows/_publish_release.yml index ade83584a840f..b47c726bf426a 100644 --- a/.github/workflows/_publish_release.yml +++ b/.github/workflows/_publish_release.yml @@ -7,7 +7,6 @@ on: workflow_call: secrets: dockerhub-password: - dockerhub-username: ENVOY_CI_SYNC_APP_ID: ENVOY_CI_SYNC_APP_KEY: ENVOY_CI_PUBLISH_APP_ID: @@ -17,6 +16,9 @@ on: gpg-key-password: required: true inputs: + dockerhub-username: + type: string + required: true request: type: string required: true @@ -75,7 +77,6 @@ jobs: container: secrets: - dockerhub-username: ${{ secrets.dockerhub-username }} dockerhub-password: ${{ secrets.dockerhub-password }} permissions: actions: read @@ -85,6 +86,7 @@ jobs: uses: ./.github/workflows/_publish_release_container.yml with: dockerhub-repo: ${{ vars.DOCKERHUB_REPO || 'envoy' }} + dockerhub-username: ${{ inputs.dockerhub-username }} dev: ${{ fromJSON(inputs.request).request.version.dev }} sha: ${{ fromJSON(inputs.request).request.sha }} target-branch: ${{ fromJSON(inputs.request).request.target-branch }} diff --git a/.github/workflows/_publish_release_container.yml b/.github/workflows/_publish_release_container.yml index 6ac51cb5be5ba..1e050b276dab0 100644 --- a/.github/workflows/_publish_release_container.yml +++ b/.github/workflows/_publish_release_container.yml @@ -7,7 +7,6 @@ on: workflow_call: secrets: dockerhub-password: - dockerhub-username: inputs: dev: required: true @@ -17,6 +16,9 @@ on: required: true default: envoy type: string + dockerhub-username: + required: true + type: string sha: required: true type: string @@ -230,5 +232,5 @@ jobs: artifacts-pattern: oci.* manifest-config: ${{ steps.dev-config.outputs.value || steps.release-config.outputs.value }} dry-run: ${{ ! inputs.trusted || (inputs.target-branch != 'main' && inputs.dev) }} - dockerhub-username: ${{ inputs.trusted && secrets.dockerhub-username || '' }} + dockerhub-username: ${{ inputs.trusted && inputs.dockerhub-username || '' }} dockerhub-password: ${{ inputs.trusted && secrets.dockerhub-password || '' }} diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index e0479fa3a3271..0604009aaffd9 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -82,9 +82,6 @@ on: docker-ipv6: default: true type: boolean - dockerhub-username: - default: envoyproxy - type: string downloads: type: string entrypoint: diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index 06cde48e512da..d488589c95e85 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -83,7 +83,6 @@ jobs: release: secrets: dockerhub-password: ${{ secrets.DOCKERHUB_PASSWORD }} - dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} ENVOY_CI_SYNC_APP_ID: >- ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) @@ -125,6 +124,7 @@ jobs: uses: ./.github/workflows/_publish_release.yml name: Release with: + dockerhub-username: ${{ vars.DOCKERHUB_USERNAME }} request: ${{ needs.load.outputs.request }} trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} From 2a525e81033aad46ce8260e068b1a2ab64d7d297 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 7 May 2026 14:15:01 -0400 Subject: [PATCH 255/750] fix: rebuilding devcontainer causes duplicate lines in user.bazelrc (#44892) Currently each time you open the project in a devcontainer the setup script will re-run and add a copy of the `build --config=clang` to user.bazelrc Risk Level: Low Testing: manual Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Ryan Daniels Co-authored-by: Copilot --- .devcontainer/setup.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index fe6e0aa79b2c1..3c743e9ca6948 100755 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash -echo "build --config=clang" >> user.bazelrc +if [[ ! -f user.bazelrc ]] || ! grep -Fxq 'build --config=clang' user.bazelrc; then + echo -e "build --config=clang" >> user.bazelrc +fi # Ideally we want this line so bazel doesn't pollute things outside of the devcontainer, but some of # API tooling (proto_sync) depends on symlink like bazel-bin. From 4c1daf37d261bef857cb40413a3e9503e9656801 Mon Sep 17 00:00:00 2001 From: code Date: Fri, 8 May 2026 02:19:41 +0800 Subject: [PATCH 256/750] minor improvement: prefer reference to construct lock (#44921) Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: wbpcode/wangbaiping --- contrib/golang/common/dso/dso.h | 10 +++++----- .../golang/filters/http/source/golang_filter.cc | 6 +++--- source/common/http/headers.h | 4 ++-- source/common/jwt/simple_lru_cache_inl.h | 2 +- source/common/upstream/upstream_impl.h | 8 ++++---- .../open_telemetry/http_access_log_impl.cc | 4 ++-- .../extensions/clusters/dynamic_modules/cluster.cc | 12 ++++++------ .../common/thread_aware_lb_impl.cc | 2 +- .../clusters/dynamic_modules/cluster_test.cc | 2 +- .../http/wasm/wasm_filter_integration_test.cc | 14 +++++++------- tools/spelling/spelling_dictionary.txt | 2 ++ 11 files changed, 34 insertions(+), 32 deletions(-) diff --git a/contrib/golang/common/dso/dso.h b/contrib/golang/common/dso/dso.h index 2ccb0cc8e525f..22fad9c47cf76 100644 --- a/contrib/golang/common/dso/dso.h +++ b/contrib/golang/common/dso/dso.h @@ -291,7 +291,7 @@ template class DsoManager { auto dso = load(dso_id, dso_name); if (dso != nullptr) { DsoStoreType& dsoStore = getDsoStore(); - absl::WriterMutexLock lock(&dsoStore.mutex_); + absl::WriterMutexLock lock(dsoStore.mutex_); dsoStore.plugin_name_to_dso_[plugin_name] = dso; } return dso; @@ -307,7 +307,7 @@ template class DsoManager { ENVOY_LOG_MISC(debug, "load {} {} dso instance.", dso_id, dso_name); DsoStoreType& dsoStore = getDsoStore(); - absl::WriterMutexLock lock(&dsoStore.mutex_); + absl::WriterMutexLock lock(dsoStore.mutex_); auto it = dsoStore.id_to_dso_.find(dso_id); if (it != dsoStore.id_to_dso_.end()) { return it->second; @@ -328,7 +328,7 @@ template class DsoManager { */ static std::shared_ptr getDsoByID(std::string dso_id) { DsoStoreType& dsoStore = getDsoStore(); - absl::ReaderMutexLock lock(&dsoStore.mutex_); + absl::ReaderMutexLock lock(dsoStore.mutex_); auto it = dsoStore.id_to_dso_.find(dso_id); if (it != dsoStore.id_to_dso_.end()) { return it->second; @@ -343,7 +343,7 @@ template class DsoManager { */ static std::shared_ptr getDsoByPluginName(std::string plugin_name) { DsoStoreType& dsoStore = getDsoStore(); - absl::ReaderMutexLock lock(&dsoStore.mutex_); + absl::ReaderMutexLock lock(dsoStore.mutex_); auto it = dsoStore.plugin_name_to_dso_.find(plugin_name); if (it != dsoStore.plugin_name_to_dso_.end()) { return it->second; @@ -356,7 +356,7 @@ template class DsoManager { */ static void cleanUpForTest() { DsoStoreType& dsoStore = getDsoStore(); - absl::WriterMutexLock lock(&dsoStore.mutex_); + absl::WriterMutexLock lock(dsoStore.mutex_); for (auto it = dsoStore.id_to_dso_.begin(); it != dsoStore.id_to_dso_.end(); it++) { auto dso = it->second; if (dso != nullptr) { diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index 0cee621746905..4cb88818e4438 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -1994,7 +1994,7 @@ RoutePluginConfig::RoutePluginConfig( }; RoutePluginConfig::~RoutePluginConfig() { - absl::WriterMutexLock lock(&mutex_); + absl::WriterMutexLock lock(mutex_); if (config_id_ > 0) { dso_lib_->envoyGoFilterDestroyHttpPluginConfig(config_id_, 0); } @@ -2026,12 +2026,12 @@ uint64_t RoutePluginConfig::getConfigId() { uint64_t RoutePluginConfig::getMergedConfigId(uint64_t parent_id) { { // this is the fast path for most cases. - absl::ReaderMutexLock lock(&mutex_); + absl::ReaderMutexLock lock(mutex_); if (merged_config_id_ > 0 && cached_parent_id_ == parent_id) { return merged_config_id_; } } - absl::WriterMutexLock lock(&mutex_); + absl::WriterMutexLock lock(mutex_); if (merged_config_id_ > 0) { if (cached_parent_id_ == parent_id) { return merged_config_id_; diff --git a/source/common/http/headers.h b/source/common/http/headers.h index ab31e2ed3a539..70f4dfdefa9de 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -18,7 +18,7 @@ namespace Http { class PrefixValue { public: const char* prefix() { - absl::WriterMutexLock lock(&m_); + absl::WriterMutexLock lock(m_); read_ = true; return prefix_.c_str(); } @@ -26,7 +26,7 @@ class PrefixValue { // The char* prefix is used directly, so must be available for the interval where prefix() may be // called. void setPrefix(const char* prefix) { - absl::WriterMutexLock lock(&m_); + absl::WriterMutexLock lock(m_); // The check for unchanged string is purely for integration tests - this // should not happen in production. RELEASE_ASSERT(!read_ || prefix_ == std::string(prefix), diff --git a/source/common/jwt/simple_lru_cache_inl.h b/source/common/jwt/simple_lru_cache_inl.h index c74e6f73c3ed0..2c47601537d55 100644 --- a/source/common/jwt/simple_lru_cache_inl.h +++ b/source/common/jwt/simple_lru_cache_inl.h @@ -228,7 +228,7 @@ template class SimpleLRUCacheB // NOTE: Be extremely careful when using ScopedLookup with Mutexes. This // code is safe since the lock will be released after the ScopedLookup is // destroyed. - // MutexLock l(&mu_); + // MutexLock l(mu_); // ScopedLookup lookup(....); // // This is NOT safe since the lock is released before the ScopedLookup is diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 1387c129ca66c..8a4b1fffe8394 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -161,7 +161,7 @@ class HostDescriptionImplBase : virtual public HostDescription, protected Logger::Loggable { public: Network::UpstreamTransportSocketFactory& transportSocketFactory() const override { - absl::ReaderMutexLock lock(&metadata_mutex_); + absl::ReaderMutexLock lock(metadata_mutex_); return socket_factory_; } @@ -175,17 +175,17 @@ class HostDescriptionImplBase : virtual public HostDescription, // would be to use TLS and post metadata updates from the main thread. This model would // possibly benefit other related and expensive computations too (e.g.: updating subsets). MetadataConstSharedPtr metadata() const override { - absl::ReaderMutexLock lock(&metadata_mutex_); + absl::ReaderMutexLock lock(metadata_mutex_); return endpoint_metadata_; } std::size_t metadataHash() const override { - absl::ReaderMutexLock lock(&metadata_mutex_); + absl::ReaderMutexLock lock(metadata_mutex_); return endpoint_metadata_hash_; } void metadata(MetadataConstSharedPtr new_metadata) override { auto& new_socket_factory = resolveTransportSocketFactory(address(), new_metadata.get()); { - absl::WriterMutexLock lock(&metadata_mutex_); + absl::WriterMutexLock lock(metadata_mutex_); endpoint_metadata_ = new_metadata; endpoint_metadata_hash_ = new_metadata ? MessageUtil::hash(*new_metadata) : 0; // Update data members dependent on metadata. diff --git a/source/extensions/access_loggers/open_telemetry/http_access_log_impl.cc b/source/extensions/access_loggers/open_telemetry/http_access_log_impl.cc index 58a973cadb7eb..043457e52132b 100644 --- a/source/extensions/access_loggers/open_telemetry/http_access_log_impl.cc +++ b/source/extensions/access_loggers/open_telemetry/http_access_log_impl.cc @@ -175,7 +175,7 @@ HttpAccessLoggerCacheImpl::getOrCreateApplicator( ASSERT_IS_MAIN_OR_TEST_THREAD(); const std::size_t config_hash = MessageUtil::hash(http_service); - absl::MutexLock lock(&applicator_mutex_); + absl::MutexLock lock(applicator_mutex_); const auto it = applicators_.find(config_hash); if (it != applicators_.end()) { @@ -196,7 +196,7 @@ HttpAccessLoggerCacheImpl::getOrCreateApplicator( headers_applicator.release(), [self, config_hash](const Http::HttpServiceHeadersApplicator* ptr) { { - absl::MutexLock lock(&self->applicator_mutex_); + absl::MutexLock lock(self->applicator_mutex_); const auto it = self->applicators_.find(config_hash); // Check for expired in case a new entry was added at nearly the same time because the // check for an existing entry failed to `lock()`. diff --git a/source/extensions/clusters/dynamic_modules/cluster.cc b/source/extensions/clusters/dynamic_modules/cluster.cc index 7dc23d99df010..2ff67bdc2cbc6 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.cc +++ b/source/extensions/clusters/dynamic_modules/cluster.cc @@ -360,7 +360,7 @@ bool DynamicModuleCluster::addHosts( } { - absl::WriterMutexLock lock(&host_map_lock_); + absl::WriterMutexLock lock(host_map_lock_); for (const auto& host : result_hosts) { host_map_[host.get()] = host; } @@ -442,7 +442,7 @@ Upstream::HostSharedPtr DynamicModuleCluster::findHostByAddress(const std::strin } Upstream::HostSharedPtr DynamicModuleCluster::findHost(void* raw_host_ptr) { - absl::ReaderMutexLock lock(&host_map_lock_); + absl::ReaderMutexLock lock(host_map_lock_); auto it = host_map_.find(raw_host_ptr); if (it == host_map_.end()) { return nullptr; @@ -456,7 +456,7 @@ size_t DynamicModuleCluster::removeHosts(const std::vector& activeDynamicModuleLoadBa } void registerActiveDynamicModuleLoadBalancer(const DynamicModuleLoadBalancer* lb) { - absl::MutexLock lock(&activeDynamicModuleLoadBalancersMutex()); + absl::MutexLock lock(activeDynamicModuleLoadBalancersMutex()); activeDynamicModuleLoadBalancers().insert(lb); } void unregisterActiveDynamicModuleLoadBalancer(const DynamicModuleLoadBalancer* lb) { - absl::MutexLock lock(&activeDynamicModuleLoadBalancersMutex()); + absl::MutexLock lock(activeDynamicModuleLoadBalancersMutex()); activeDynamicModuleLoadBalancers().erase(lb); } } // namespace @@ -632,7 +632,7 @@ void unregisterActiveDynamicModuleLoadBalancer(const DynamicModuleLoadBalancer* bool DynamicModuleLoadBalancer::withActiveInstance( const DynamicModuleLoadBalancer* lb, absl::FunctionRef f) { - absl::MutexLock lock(&activeDynamicModuleLoadBalancersMutex()); + absl::MutexLock lock(activeDynamicModuleLoadBalancersMutex()); if (!activeDynamicModuleLoadBalancers().contains(lb)) { return false; } diff --git a/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.cc b/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.cc index 574c7c6d66fbf..dcfcc8d1f62de 100644 --- a/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.cc +++ b/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.cc @@ -177,7 +177,7 @@ void ThreadAwareLoadBalancerBase::refresh() { } { - absl::WriterMutexLock lock(&factory_->mutex_); + absl::WriterMutexLock lock(factory_->mutex_); factory_->healthy_per_priority_load_ = healthy_per_priority_load; factory_->degraded_per_priority_load_ = degraded_per_priority_load; factory_->per_priority_state_ = per_priority_state_vector; diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index a8e66e37d3cfb..6729ff7a268f5 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -39,7 +39,7 @@ class DynamicModuleClusterTestPeer { } static size_t getHostMapSize(DynamicModuleCluster& cluster) { - absl::ReaderMutexLock lock(&cluster.host_map_lock_); + absl::ReaderMutexLock lock(cluster.host_map_lock_); return cluster.host_map_.size(); } diff --git a/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc b/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc index fed2c26af9449..1a60deee663c3 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc @@ -321,7 +321,7 @@ class WasmDeferredDataSetupFilter : public Http::PassThroughFilter { Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool) override { bool first_request = false; { - absl::MutexLock l(&state_->mu); + absl::MutexLock l(state_->mu); first_request = !state_->headers_processed; } // Only the first request triggers the read-disable that the bug reproduction @@ -332,7 +332,7 @@ class WasmDeferredDataSetupFilter : public Http::PassThroughFilter { } decoder_callbacks_->onDecoderFilterAboveWriteBufferHighWatermark(); { - absl::MutexLock l(&state_->mu); + absl::MutexLock l(state_->mu); state_->callbacks = decoder_callbacks_; state_->dispatcher = &decoder_callbacks_->dispatcher(); state_->headers_processed = true; @@ -341,7 +341,7 @@ class WasmDeferredDataSetupFilter : public Http::PassThroughFilter { } Http::FilterDataStatus decodeData(Buffer::Instance&, bool) override { - absl::MutexLock l(&state_->mu); + absl::MutexLock l(state_->mu); if (state_->destroyed) { state_->decode_data_after_destroy = true; } @@ -349,7 +349,7 @@ class WasmDeferredDataSetupFilter : public Http::PassThroughFilter { } void onDestroy() override { - absl::MutexLock l(&state_->mu); + absl::MutexLock l(state_->mu); state_->destroyed = true; state_->callbacks = nullptr; } @@ -451,7 +451,7 @@ name: envoy.filters.http.wasm // Wait until the server has processed decodeHeaders and readDisable is on. { - absl::MutexLock l(&state->mu); + absl::MutexLock l(state->mu); state->mu.Await(absl::Condition(&state->headers_processed)); } @@ -474,7 +474,7 @@ name: envoy.filters.http.wasm Event::Dispatcher* conn_dispatcher; Http::StreamDecoderFilterCallbacks* cbs; { - absl::MutexLock l(&state->mu); + absl::MutexLock l(state->mu); conn_dispatcher = state->dispatcher; cbs = state->callbacks; } @@ -511,7 +511,7 @@ name: envoy.filters.http.wasm // Check 1: the setup filter must not have seen decodeData after destroy. { - absl::MutexLock l(&state->mu); + absl::MutexLock l(state->mu); EXPECT_FALSE(state->decode_data_after_destroy) << "decodeData was invoked after onDestroy — deferred processing " "callback fired on the destroyed filter chain (use-after-free bug)"; diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index c1125d30480e5..9b676da8be13a 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1709,3 +1709,5 @@ IWYU CRTP bitrate kbps +dataset +datasets From 9b5b194117c419c777a498a18941f64c04bde0a7 Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 7 May 2026 16:00:42 -0400 Subject: [PATCH 257/750] mobile: add new CRTP EngineBuilderBase and MobileEngineBuilder (#44865) Commit Message: extract all common bootstrap generation and engine creation pipelines from the C++ `EngineBuilder` into a unified CRTP template base class (`EngineBuilderBase`). And add a new `MobileEngineBuilder` which is branched from the existing `EngineBuilder` but inherits from`EngineBuilderBase`. `MobileEngineBuilder` shares identical C++ API, Envoy configuration and engine setup as the`EngineBuilder`. And the latter will be deprecated in preference of `MobileEngineBuilder`. Setup all the existing C++ tests to run against both EngineBuilder and MobileEngineBuilder. The same test targets are double-compiled into 2 test targets: one for legacy `EngineBuilder` and is renamed to `foo_test_legacy_builder`; and one for `MobileEngineBuilder` with test target name as is. Swift and Python APIs directly switch to use the new `MobileEngineBuilder` in this PR. Java APIs directly interacts with InternalEngine, so it's irrelevant to this change. `Platform::XdsBuilder` & `Platform::NodeLocality` are extracted into a standalone file for sharing between 2 engine builders. For better feature isolation, this PR also refactors `InternalEngine` by removing disable_dns_refresh_on_network_change from the constructor and add a setter for it. So that the EngineBuilderBase doesn't need to be aware of this mobile-specific feature. Additional Description: also fixes an JNI bug in Envoy::InternalEngine() construction where `disable_dns_refresh_on_network_change` is passed in as the wrong parameter. Notes to reviewers: due to GitHub's unified diff limitation, the main "Files changed" tab shows the newly added `mobile_engine_builder.h/cc` as 100% new green lines. But these files are actually modified based on engine_builder.h/cc. A better diff can viewed at commit https://github.com/envoyproxy/envoy/pull/44865/changes/7357c340acff893bfbf85b16c3b25e1b52ca9afc. Risk Level: low, new class not in use Testing: existing tests with new test combination Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A --------- Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- mobile/.bazelrc | 2 +- mobile/bazel/envoy_mobile_cc_test.bzl | 81 ++ mobile/library/cc/BUILD | 97 ++ mobile/library/cc/engine.h | 1 + mobile/library/cc/engine_builder.cc | 89 +- mobile/library/cc/engine_builder.h | 96 +- mobile/library/cc/engine_builder_base.h | 382 +++++++ mobile/library/cc/engine_builder_types.cc | 99 ++ mobile/library/cc/engine_builder_types.h | 111 ++ mobile/library/cc/mobile_engine_builder.cc | 1012 +++++++++++++++++ mobile/library/cc/mobile_engine_builder.h | 298 +++++ mobile/library/common/internal_engine.cc | 20 +- mobile/library/common/internal_engine.h | 13 +- mobile/library/jni/BUILD | 2 +- mobile/library/jni/jni_impl.cc | 27 +- mobile/library/objective-c/BUILD | 2 +- .../library/objective-c/EnvoyConfiguration.mm | 12 +- mobile/library/objective-c/EnvoyEngineImpl.mm | 2 +- mobile/test/cc/BUILD | 16 +- mobile/test/cc/engine_builder_test_shim.h | 27 + mobile/test/cc/engine_test.cc | 2 +- mobile/test/cc/integration/BUILD | 67 +- .../cc/integration/base/integration_test.cc | 177 +++ .../base/test_engine_and_server.cc | 121 ++ .../integration/base/test_engine_and_server.h | 37 + .../cc/integration/base/test_engine_builder.h | 86 ++ mobile/test/cc/integration/lifetimes_test.cc | 2 +- .../test/cc/integration/receive_data_test.cc | 2 +- .../cc/integration/receive_headers_test.cc | 2 +- .../cc/integration/receive_trailers_test.cc | 2 +- mobile/test/cc/integration/send_data_test.cc | 2 +- .../test/cc/integration/send_headers_test.cc | 2 +- .../test/cc/integration/send_trailers_test.cc | 2 +- mobile/test/cc/unit/BUILD | 12 +- mobile/test/cc/unit/engine_handle_test.cc | 2 +- mobile/test/cc/unit/envoy_config_test.cc | 2 +- mobile/test/cc/unit/fetch_client_test.cc | 2 +- mobile/test/common/BUILD | 9 +- mobile/test/common/engine_common_test.cc | 2 +- mobile/test/common/integration/BUILD | 23 +- .../base_client_integration_test.h | 2 +- .../integration/engine_with_test_server.h | 2 +- mobile/test/common/internal_engine_test.cc | 8 +- 43 files changed, 2681 insertions(+), 276 deletions(-) create mode 100644 mobile/bazel/envoy_mobile_cc_test.bzl create mode 100644 mobile/library/cc/engine_builder_base.h create mode 100644 mobile/library/cc/engine_builder_types.cc create mode 100644 mobile/library/cc/engine_builder_types.h create mode 100644 mobile/library/cc/mobile_engine_builder.cc create mode 100644 mobile/library/cc/mobile_engine_builder.h create mode 100644 mobile/test/cc/engine_builder_test_shim.h create mode 100644 mobile/test/cc/integration/base/integration_test.cc create mode 100644 mobile/test/cc/integration/base/test_engine_and_server.cc create mode 100644 mobile/test/cc/integration/base/test_engine_and_server.h create mode 100644 mobile/test/cc/integration/base/test_engine_builder.h diff --git a/mobile/.bazelrc b/mobile/.bazelrc index 43af1474b5cc5..a3078d99a573c 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -168,7 +168,7 @@ build:mobile-cc --test_env=ENVOY_IP_TEST_VERSIONS=v4only # CC with xDS build:mobile-cc-xds-enabled --config=mobile-cc -test:mobile-cc-xds-enabled --define=envoy_mobile_xds=enabled +build:mobile-cc-xds-enabled --define=envoy_mobile_xds=enabled # Coverage build:mobile-coverage --build_tests_only diff --git a/mobile/bazel/envoy_mobile_cc_test.bzl b/mobile/bazel/envoy_mobile_cc_test.bzl new file mode 100644 index 0000000000000..75f5c395d36af --- /dev/null +++ b/mobile/bazel/envoy_mobile_cc_test.bzl @@ -0,0 +1,81 @@ +load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_cc_test_library") + +# List of intermediate test libraries that concretely depend on Platform::EngineBuilder. +# Targets in this list will be double-compiled into 2 targets to support both Engine Builders +# (the legacy pristine EngineBuilder and the new MobileEngineBuilder) cleanly. +# Note: Any library depending on these targets is transitively forced to be double-compiled +# as well due to ODR layout constraints. This cascading effect is a minor side-effect given +# that these test libraries reside very close to the leaves/end of the test dependency tree. +LEGACY_BUILDER_LIBRARIES = [ + "base_client_integration_test_lib", + "engine_with_test_server", + "xds_integration_test_lib", +] + +def envoy_cc_test_library_with_engine_builder(name, srcs, deps = [], copts = [], **kwargs): + # 1. Legacy library using pristine EngineBuilder (suffixed) + overridden_deps = [] + for dep in deps: + is_legacy = False + for suffix in LEGACY_BUILDER_LIBRARIES: + if dep.endswith(":" + suffix): + is_legacy = True + break + if is_legacy: + overridden_deps.append(dep + "_legacy_builder") + else: + overridden_deps.append(dep) + + envoy_cc_test_library( + name = name + "_legacy_builder", + srcs = srcs, + copts = copts, + deps = overridden_deps + ["//test/cc:engine_builder_test_shim_lib"], + **kwargs + ) + + # 2. New library using MobileEngineBuilder under macro switch (default name) + envoy_cc_test_library( + name = name, + srcs = srcs, + copts = copts + ["-DUSE_MOBILE_ENGINE_BUILDER"], + deps = deps + [ + "//test/cc:engine_builder_test_shim_lib", + "//library/cc:mobile_engine_builder_lib", + ], + **kwargs + ) + +def envoy_cc_test_with_engine_builder(name, srcs, deps = [], copts = [], **kwargs): + # 1. Legacy test target (suffixed): rewrite deps to map to _legacy_builder libraries + overridden_deps = [] + for dep in deps: + is_legacy = False + for suffix in LEGACY_BUILDER_LIBRARIES: + if dep.endswith(":" + suffix): + is_legacy = True + break + if is_legacy: + overridden_deps.append(dep + "_legacy_builder") + else: + overridden_deps.append(dep) + + envoy_cc_test( + name = name + "_legacy_builder", + srcs = srcs, + copts = copts, + deps = overridden_deps + ["//test/cc:engine_builder_test_shim_lib"], + **kwargs + ) + + # 2. Mobile test target (default name): uses default deps mapping directly + envoy_cc_test( + name = name, + srcs = srcs, + copts = copts + ["-DUSE_MOBILE_ENGINE_BUILDER"], + deps = deps + [ + "//test/cc:engine_builder_test_shim_lib", + "//library/cc:mobile_engine_builder_lib", + ], + **kwargs + ) diff --git a/mobile/library/cc/BUILD b/mobile/library/cc/BUILD index 37e7802387e5f..73883691953c1 100644 --- a/mobile/library/cc/BUILD +++ b/mobile/library/cc/BUILD @@ -8,6 +8,23 @@ licenses(["notice"]) # Apache 2 envoy_mobile_package() +envoy_cc_library( + name = "engine_builder_types_lib", + srcs = [ + "engine_builder_types.cc", + ], + hdrs = [ + "engine_builder_types.h", + ], + repository = "@envoy", + visibility = ["//visibility:public"], + deps = [ + "@abseil-cpp//absl/strings", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "network_change_monitor_interface", srcs = [ @@ -18,6 +35,30 @@ envoy_cc_library( deps = [], ) +envoy_cc_library( + name = "engine_builder_base_lib", + hdrs = [ + "engine_builder_base.h", + ], + repository = "@envoy", + visibility = ["//visibility:public"], + deps = [ + ":envoy_engine_cc_lib_no_stamp", + "//library/common:engine_types_lib", + "//library/common:internal_engine_lib_no_stamp", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/types:optional", + "@envoy//source/common/common:base_logger_lib", + "@envoy//source/common/runtime:runtime_features_lib", + "@envoy//source/server:options_base", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/http/router/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "engine_builder_lib", srcs = [ @@ -33,6 +74,61 @@ envoy_cc_library( repository = "@envoy", visibility = ["//visibility:public"], deps = [ + ":engine_builder_base_lib", + ":engine_builder_types_lib", + ":envoy_engine_cc_lib_no_stamp", + "@abseil-cpp//absl/types:optional", + "@envoy//source/common/common:assert_lib", + "@envoy//source/common/protobuf", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/config/metrics/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/compression/brotli/decompressor/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/compression/gzip/decompressor/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/early_data/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/http/alternate_protocols_cache/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/http/decompressor/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/http/dynamic_forward_proxy/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/http/router/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/http/header_formatters/preserve_case/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/network/dns_resolver/apple/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/network/dns_resolver/getaddrinfo/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/http_11_proxy/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/raw_buffer/v3:pkg_cc_proto", + "@envoy_mobile//library/common:engine_types_lib", + "@envoy_mobile//library/common/config:certificates_lib", + "@envoy_mobile//library/common/extensions/cert_validator/platform_bridge:platform_bridge_cc_proto", + "@envoy_mobile//library/common/extensions/filters/http/local_error:filter_cc_proto", + "@envoy_mobile//library/common/extensions/filters/http/network_configuration:filter_cc_proto", + "@envoy_mobile//library/common/extensions/filters/http/socket_tag:filter_cc_proto", + "@envoy_mobile//library/common/extensions/quic_packet_writer/platform:platform_packet_writer_proto_cc_proto", + "@envoy_mobile//library/common/types:matcher_data_lib", + ] + select({ + "@envoy//bazel:apple": [ + "@envoy_mobile//library/common/network:apple_proxy_resolution_lib", + ], + "//conditions:default": [], + }), +) + +envoy_cc_library( + name = "mobile_engine_builder_lib", + srcs = [ + "mobile_engine_builder.cc", + ], + hdrs = [ + "mobile_engine_builder.h", + ], + copts = select({ + "//bazel:exclude_certificates": ["-DEXCLUDE_CERTIFICATES"], + "//conditions:default": [], + }), + repository = "@envoy", + visibility = ["//visibility:public"], + deps = [ + ":engine_builder_base_lib", + ":engine_builder_types_lib", ":envoy_engine_cc_lib_no_stamp", "@abseil-cpp//absl/types:optional", "@envoy//source/common/common:assert_lib", @@ -111,6 +207,7 @@ envoy_cc_library( deps = [ ":engine_builder_lib", ":envoy_engine_cc_lib_no_stamp", + ":mobile_engine_builder_lib", "//library/common:engine_common_lib_stamped", ], ) diff --git a/mobile/library/cc/engine.h b/mobile/library/cc/engine.h index 7fe0f23fbb35c..4a85296eb6bb4 100644 --- a/mobile/library/cc/engine.h +++ b/mobile/library/cc/engine.h @@ -47,6 +47,7 @@ class Engine : public std::enable_shared_from_this, public NetworkChange // required to access private constructor friend class EngineBuilder; + template friend class EngineBuilderBase; // required to use envoy_engine_t without exposing it publicly friend class StreamPrototype; // for testing only diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index 6df864a6eab88..4a215f67a6806 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -481,91 +481,6 @@ EngineBuilder& EngineBuilder::setIosNetworkServiceType(int ios_network_service_t #endif #ifdef ENVOY_MOBILE_XDS -XdsBuilder::XdsBuilder(std::string xds_server_address, const uint32_t xds_server_port) - : xds_server_address_(std::move(xds_server_address)), xds_server_port_(xds_server_port) {} - -XdsBuilder& XdsBuilder::addInitialStreamHeader(std::string header, std::string value) { - envoy::config::core::v3::HeaderValue header_value; - header_value.set_key(std::move(header)); - header_value.set_value(std::move(value)); - xds_initial_grpc_metadata_.emplace_back(std::move(header_value)); - return *this; -} - -XdsBuilder& XdsBuilder::setSslRootCerts(std::string root_certs) { - ssl_root_certs_ = std::move(root_certs); - return *this; -} - -XdsBuilder& XdsBuilder::addRuntimeDiscoveryService(std::string resource_name, - const int timeout_in_seconds) { - rtds_resource_name_ = std::move(resource_name); - rtds_timeout_in_seconds_ = timeout_in_seconds > 0 ? timeout_in_seconds : DefaultXdsTimeout; - return *this; -} - -XdsBuilder& XdsBuilder::addClusterDiscoveryService(std::string cds_resources_locator, - const int timeout_in_seconds) { - enable_cds_ = true; - cds_resources_locator_ = std::move(cds_resources_locator); - cds_timeout_in_seconds_ = timeout_in_seconds > 0 ? timeout_in_seconds : DefaultXdsTimeout; - return *this; -} - -void XdsBuilder::build(envoy::config::bootstrap::v3::Bootstrap& bootstrap) const { - auto* ads_config = bootstrap.mutable_dynamic_resources()->mutable_ads_config(); - ads_config->set_transport_api_version(envoy::config::core::v3::ApiVersion::V3); - ads_config->set_set_node_on_first_message_only(true); - ads_config->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); - - auto& grpc_service = *ads_config->add_grpc_services(); - grpc_service.mutable_envoy_grpc()->set_cluster_name("base"); - grpc_service.mutable_envoy_grpc()->set_authority( - absl::StrCat(xds_server_address_, ":", xds_server_port_)); - - if (!xds_initial_grpc_metadata_.empty()) { - grpc_service.mutable_initial_metadata()->Assign(xds_initial_grpc_metadata_.begin(), - xds_initial_grpc_metadata_.end()); - } - - if (!rtds_resource_name_.empty()) { - auto* layered_runtime = bootstrap.mutable_layered_runtime(); - auto* layer = layered_runtime->add_layers(); - layer->set_name("rtds_layer"); - auto* rtds_layer = layer->mutable_rtds_layer(); - rtds_layer->set_name(rtds_resource_name_); - auto* rtds_config = rtds_layer->mutable_rtds_config(); - rtds_config->mutable_ads(); - rtds_config->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); - rtds_config->mutable_initial_fetch_timeout()->set_seconds(rtds_timeout_in_seconds_); - } - - if (enable_cds_) { - auto* cds_config = bootstrap.mutable_dynamic_resources()->mutable_cds_config(); - if (cds_resources_locator_.empty()) { - cds_config->mutable_ads(); - } else { - bootstrap.mutable_dynamic_resources()->set_cds_resources_locator(cds_resources_locator_); - cds_config->mutable_api_config_source()->set_api_type( - envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC); - cds_config->mutable_api_config_source()->set_transport_api_version( - envoy::config::core::v3::ApiVersion::V3); - } - cds_config->mutable_initial_fetch_timeout()->set_seconds(cds_timeout_in_seconds_); - cds_config->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); - bootstrap.add_node_context_params("cluster"); - // Stat prefixes that we use in tests. - auto* list = - bootstrap.mutable_stats_config()->mutable_stats_matcher()->mutable_inclusion_list(); - list->add_patterns()->set_exact("cluster_manager.active_clusters"); - list->add_patterns()->set_exact("cluster_manager.cluster_added"); - list->add_patterns()->set_exact("cluster_manager.cluster_updated"); - list->add_patterns()->set_exact("cluster_manager.cluster_removed"); - // Allow SDS related stats. - list->add_patterns()->mutable_safe_regex()->set_regex("sds\\..*"); - list->add_patterns()->mutable_safe_regex()->set_regex(".*\\.ssl_context_update_by_sds"); - } -} EngineBuilder& EngineBuilder::setXds(XdsBuilder xds_builder) { xds_builder_ = std::move(xds_builder); @@ -1180,8 +1095,8 @@ std::unique_ptr EngineBuilder::generate EngineSharedPtr EngineBuilder::build() { InternalEngine* envoy_engine = absl::IgnoreLeak(new InternalEngine( std::move(callbacks_), std::move(logger_), std::move(event_tracker_), - network_thread_priority_, high_watermark_, disable_dns_refresh_on_network_change_, - enable_logger_, use_worker_thread_)); + network_thread_priority_, high_watermark_, enable_logger_, use_worker_thread_)); + envoy_engine->disableDnsRefreshOnNetworkChange(disable_dns_refresh_on_network_change_); for (const auto& [name, store] : key_value_stores_) { // TODO(goaway): This leaks, but it's tied to the life of the engine. diff --git a/mobile/library/cc/engine_builder.h b/mobile/library/cc/engine_builder.h index cb989918ef4bb..81ee90517f8f8 100644 --- a/mobile/library/cc/engine_builder.h +++ b/mobile/library/cc/engine_builder.h @@ -18,103 +18,11 @@ #include "library/cc/string_accessor.h" #include "library/common/engine_types.h" +#include "library/cc/engine_builder_types.h" + namespace Envoy { namespace Platform { -// Represents the locality information in the Bootstrap's node, as defined in: -// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#envoy-v3-api-msg-config-core-v3-locality -struct NodeLocality { - std::string region; - std::string zone; - std::string sub_zone; -}; - -#ifdef ENVOY_MOBILE_XDS -constexpr int DefaultXdsTimeout = 5; - -// Forward declaration so it can be referenced by XdsBuilder. -class EngineBuilder; - -// A class for building the xDS configuration for the Envoy Mobile engine. -// xDS is a protocol for dynamic configuration of Envoy instances, more information can be found in: -// https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol. -// -// This class is typically used as input to the EngineBuilder's setXds() method. -class XdsBuilder final { -public: - // `xds_server_address`: the host name or IP address of the xDS management server. The xDS server - // must support the ADS protocol - // (https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#aggregated-xds-ads). - // `xds_server_port`: the port on which the xDS management server listens for ADS discovery - // requests. - XdsBuilder(std::string xds_server_address, const uint32_t xds_server_port); - - // Adds a header to the initial HTTP metadata headers sent on the gRPC stream. - // - // A common use for the initial metadata headers is for authentication to the xDS management - // server. - // - // For example, if using API keys to authenticate to Traffic Director on GCP (see - // https://cloud.google.com/docs/authentication/api-keys for details), invoke: - // builder.addInitialStreamHeader("x-goog-api-key", api_key_token) - // .addInitialStreamHeader("X-Android-Package", app_package_name) - // .addInitialStreamHeader("X-Android-Cert", sha1_key_fingerprint); - XdsBuilder& addInitialStreamHeader(std::string header, std::string value); - - // Sets the PEM-encoded server root certificates used to negotiate the TLS handshake for the gRPC - // connection. If no root certs are specified, the operating system defaults are used. - XdsBuilder& setSslRootCerts(std::string root_certs); - - // Adds Runtime Discovery Service (RTDS) to the Runtime layers of the Bootstrap configuration, - // to retrieve dynamic runtime configuration via the xDS management server. - // - // `resource_name`: The runtime config resource to subscribe to. - // `timeout_in_seconds`: specifies the `initial_fetch_timeout` field on the - // api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch - // timeout value of 5s, to prevent mobile app initialization from stalling. The default - // parameter value may change through the course of experimentation and no assumptions should - // be made of its exact value. - XdsBuilder& addRuntimeDiscoveryService(std::string resource_name, - int timeout_in_seconds = DefaultXdsTimeout); - - // Adds the Cluster Discovery Service (CDS) configuration for retrieving dynamic cluster resources - // via the xDS management server. - // - // `cds_resources_locator`: the xdstp:// URI for subscribing to the cluster resources. - // If not using xdstp, then `cds_resources_locator` should be set to the empty string. - // `timeout_in_seconds`: specifies the `initial_fetch_timeout` field on the - // api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch - // timeout value of 5s, to prevent mobile app initialization from stalling. The default - // parameter value may change through the course of experimentation and no assumptions should - // be made of its exact value. - XdsBuilder& addClusterDiscoveryService(std::string cds_resources_locator = "", - int timeout_in_seconds = DefaultXdsTimeout); - -protected: - // Sets the xDS configuration specified on this XdsBuilder instance on the Bootstrap proto - // provided as an input parameter. - // - // This method takes in a modifiable Bootstrap proto pointer because returning a new Bootstrap - // proto would rely on proto's MergeFrom behavior, which can lead to unexpected results in the - // Bootstrap config. - void build(envoy::config::bootstrap::v3::Bootstrap& bootstrap) const; - -private: - // Required so that EngineBuilder can call the XdsBuilder's protected build() method. - friend class EngineBuilder; - - std::string xds_server_address_; - uint32_t xds_server_port_; - std::vector xds_initial_grpc_metadata_; - std::string ssl_root_certs_; - std::string rtds_resource_name_; - int rtds_timeout_in_seconds_ = DefaultXdsTimeout; - bool enable_cds_ = false; - std::string cds_resources_locator_; - int cds_timeout_in_seconds_ = DefaultXdsTimeout; -}; -#endif // ENVOY_MOBILE_XDS - // The C++ Engine builder creates a structured bootstrap proto and modifies it through parameters // set through the EngineBuilder API calls to produce the Bootstrap config that the Engine is // created from. diff --git a/mobile/library/cc/engine_builder_base.h b/mobile/library/cc/engine_builder_base.h new file mode 100644 index 0000000000000..8978320ef138e --- /dev/null +++ b/mobile/library/cc/engine_builder_base.h @@ -0,0 +1,382 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "absl/debugging/leak_check.h" +#include "absl/status/statusor.h" +#include "absl/types/optional.h" +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/extensions/filters/http/router/v3/router.pb.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" +#include "library/cc/engine.h" +#include "library/common/engine_types.h" +#include "library/common/internal_engine.h" +#include "source/common/common/base_logger.h" +#include "source/server/options_impl_base.h" +#include "source/common/runtime/runtime_features.h" +#include "envoy/type/matcher/v3/string.pb.h" + +namespace Envoy { +namespace Platform { + +/** + * Base class template implementing the Curiously Recurring Template Pattern (CRTP) + * to share common bootstrap generation and lifecycle logic between Engine Builders. + * + * How to Subclass: + * Subclasses should derive from this base class passing themselves as the template parameter: + * class MobileEngineBuilder : public Platform::EngineBuilderBase { ... }; + * + * Key CRTP Hooks the Derived Class SHOULD/MUST implement: + * 1. Pre-run and Post-run Lifecycle Hooks: + * - `void preRunSetup(InternalEngine* engine)`: Called inside build() on the engine thread + * prior to starting the event loop. Derived builders should register dynamic key-value stores + * or string accessors here. + * - `void postRunSetup(Engine* engine)`: Called inside build() immediately after starting + * the engine. Used to initialize platform monitors (e.g., NetworkChangeMonitor). + * + * 2. Bootstrap Configuration Hooks: + * - `absl::Status configureNode(envoy::config::core::v3::Node* node)`: + * Configures the Bootstrap's node metadata (e.g., setting app_id, app_version, device_os). + * - `absl::Status configureRouteConfig(envoy::config::route::v3::RouteConfiguration* + * route_config)`: Configures virtual hosts, domains, routes, and early data policies. + * - `absl::Status configureStaticClusters( + * Protobuf::RepeatedPtrField* clusters)`: + * Defines and registers static upstream clusters (e.g., base DFP cluster or mock test + * clusters). + * - `absl::Status configXds(envoy::config::bootstrap::v3::Bootstrap* bootstrap)`: + * Configures dynamic xDS RTDS/CDS layers on the bootstrap. + * + * 3. Optional HTTP Filter Config Overrides: + * - `absl::Status configureHttpFilters( + * std::function + * add_filter)`: Customizes the HttpFilter chain (e.g., AlternateProtocolsCache, Decompressors, + * SocketTagging). + * - `void configureCustomRouterFilter( + * envoy::extensions::filters::http::router::v3::Router& router_config)`: + * Applies custom tweaks to the terminal HTTP router filter. + * - `absl::Status configureTracing(...)`: + * Configures custom tracing providers. + */ +template class EngineBuilderBase { +public: + EngineBuilderBase() : callbacks_(std::make_unique<::Envoy::EngineCallbacks>()) {} + virtual ~EngineBuilderBase() = default; + EngineBuilderBase(EngineBuilderBase&&) = default; + + T& setLogLevel(Logger::Logger::Levels log_level) { + log_level_ = log_level; + return static_cast(*this); + } + + T& setLogger(std::unique_ptr logger) { + logger_ = std::move(logger); + return static_cast(*this); + } + + T& enableLogger(bool logger_on) { + enable_logger_ = logger_on; + return static_cast(*this); + } + + T& setEngineCallbacks(std::unique_ptr callbacks) { + callbacks_ = std::move(callbacks); + return static_cast(*this); + } + + T& setOnEngineRunning(absl::AnyInvocable closure) { + if (!callbacks_) { + callbacks_ = std::make_unique(); + } + callbacks_->on_engine_running_ = std::move(closure); + return static_cast(*this); + } + + T& setOnEngineExit(absl::AnyInvocable closure) { + if (!callbacks_) { + callbacks_ = std::make_unique(); + } + callbacks_->on_exit_ = std::move(closure); + return static_cast(*this); + } + + T& setEventTracker(std::unique_ptr event_tracker) { + event_tracker_ = std::move(event_tracker); + return static_cast(*this); + } + + T& setBufferHighWatermark(size_t high_watermark) { + high_watermark_ = high_watermark; + return static_cast(*this); + } + + T& enableStatsCollection(bool stats_collection_on) { + enable_stats_collection_ = stats_collection_on; + return static_cast(*this); + } + + T& addReloadableRuntimeGuard(std::string guard, bool value) { + reloadable_runtime_guards_.emplace_back(std::move(guard), value); + return static_cast(*this); + } + + T& addRestartRuntimeGuard(std::string guard, bool value) { + restart_runtime_guards_.emplace_back(std::move(guard), value); + return static_cast(*this); + } + + T& setStreamIdleTimeoutSeconds(int stream_idle_timeout_seconds) { + stream_idle_timeout_seconds_ = stream_idle_timeout_seconds; + return static_cast(*this); + } + + T& setPerConnectionBufferLimitBytes(uint32_t per_connection_buffer_limit_bytes) { + per_connection_buffer_limit_bytes_ = per_connection_buffer_limit_bytes; + return static_cast(*this); + } + + // If true, all HTTP requests are handled on a dedicated worker thread instead of on the Envoy + // main thread which also handles all xDS requests. + // Note: Engine in worker thread model doesn't support platform certificate validation and system + // proxy settings. And these settings will be ignored if worker thread model is enabled. + T& enableWorkerThread(bool use_worker_thread) { + use_worker_thread_ = use_worker_thread; + return static_cast(*this); + } + + absl::StatusOr> build() { + auto bootstrap = static_cast(this)->generateBootstrap(); + if (!bootstrap.ok()) { + return bootstrap.status(); + } + + auto envoy_engine = std::make_unique<::Envoy::InternalEngine>( + std::move(callbacks_), std::move(logger_), std::move(event_tracker_), + /*thread_priority=*/absl::nullopt, high_watermark_, enable_logger_, useWorkerThread()); + + // Pre-run setup (to be implemented by derived classes if needed) + static_cast(this)->preRunSetup(envoy_engine.get()); + + auto options = std::make_shared(); + options->setConfigProto(std::move(bootstrap.value())); + options->setLogLevel(static_cast(log_level_)); + options->setConcurrency(1); + + envoy_engine->run(options); + + auto engine_wrapper = + std::shared_ptr(new Engine(absl::IgnoreLeak(envoy_engine.release()))); + + static_cast(this)->postRunSetup(engine_wrapper.get()); + + return engine_wrapper; + } + + absl::StatusOr> generateBootstrap() { + auto bootstrap = std::make_unique(); + + // Set up the HCM, make sure a route config is provided. + ::envoy::extensions::filters::network::http_connection_manager::v3:: + EnvoyMobileHttpConnectionManager api_listener_config; + auto* hcm = api_listener_config.mutable_config(); + hcm->set_stat_prefix("hcm"); + hcm->set_server_header_transformation( + ::envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: + PASS_THROUGH); + ::envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager_Tracing tracing; + if (auto status = static_cast(this)->configureTracing(&tracing); !status.ok()) { + return status; + } + if (tracing.ByteSizeLong() > 0) { + *hcm->mutable_tracing() = std::move(tracing); + } + hcm->mutable_stream_idle_timeout()->set_seconds(stream_idle_timeout_seconds_); + if (auto status = static_cast(this)->configureRouteConfig(hcm->mutable_route_config()); + !status.ok()) { + return status; + } + + if (auto status = static_cast(this)->configureHttpFilters( + [hcm]() { return hcm->add_http_filters(); }); + !status.ok()) { + return status; + } + + // Add the router filter to the HCM last. + auto* router_filter = hcm->add_http_filters(); + router_filter->set_name("envoy.router"); + ::envoy::extensions::filters::http::router::v3::Router router_config; + static_cast(this)->configureCustomRouterFilter(router_config); + router_filter->mutable_typed_config()->PackFrom(router_config); + + auto* static_resources = bootstrap->mutable_static_resources(); + + // Finally create the base API listener, and point it at the HCM. + auto* base_listener = static_resources->add_listeners(); + base_listener->set_name("base_api_listener"); + auto* base_address = base_listener->mutable_address(); + base_address->mutable_socket_address()->set_protocol( + ::envoy::config::core::v3::SocketAddress::TCP); + base_address->mutable_socket_address()->set_address("0.0.0.0"); + base_address->mutable_socket_address()->set_port_value(10000); + base_listener->mutable_per_connection_buffer_limit_bytes()->set_value( + per_connection_buffer_limit_bytes_); + base_listener->mutable_api_listener()->mutable_api_listener()->PackFrom(api_listener_config); + + // Add all clusters to the bootstrap, there should be at least one. + if (auto status = + static_cast(this)->configureStaticClusters(static_resources->mutable_clusters()); + !status.ok()) { + return status; + } + + if (watchdog_.has_value()) { + *bootstrap->mutable_watchdogs() = std::move(watchdog_).value(); + } + + if (auto status = static_cast(this)->configureNode(bootstrap->mutable_node()); + !status.ok()) { + return status; + } + + configureStats(*bootstrap); + configureStaticLayeredRuntime(*bootstrap); + configureListenerManager(*bootstrap); + + if (auto status = static_cast(this)->configXds(bootstrap.get()); !status.ok()) { + return status; + } + + return bootstrap; + } + +protected: + // The default implementation if the derived class does not override it. + absl::Status configureTracing(::envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager_Tracing* tracing_config) { + (void)tracing_config; + return absl::OkStatus(); + } + + absl::Status configureHttpFilters( + std::function + add_filter) { + (void)add_filter; + return absl::OkStatus(); + } + + absl::Status configXds(envoy::config::bootstrap::v3::Bootstrap* bootstrap) { + (void)bootstrap; + return absl::OkStatus(); + } + +protected: + bool useWorkerThread() const { return use_worker_thread_; } + + void setWatchdog(envoy::config::bootstrap::v3::Watchdogs watchdog) { + watchdog_ = std::move(watchdog); + } + + void addStatsInclusionPattern(envoy::type::matcher::v3::StringMatcher pattern) { + if (!stats_inclusion_list_.has_value()) { + stats_inclusion_list_.emplace(); + } + *stats_inclusion_list_->add_patterns() = std::move(pattern); + } + +private: + void configureStats(envoy::config::bootstrap::v3::Bootstrap& bootstrap) const { + if (enable_stats_collection_) { + if (stats_inclusion_list_.has_value()) { + auto* list = + bootstrap.mutable_stats_config()->mutable_stats_matcher()->mutable_inclusion_list(); + list->MergeFrom(stats_inclusion_list_.value()); + } + } else { + bootstrap.mutable_stats_config()->mutable_stats_matcher()->set_reject_all(true); + } + bootstrap.mutable_stats_config()->mutable_use_all_default_tags()->set_value(false); + } + + void configureStaticLayeredRuntime(envoy::config::bootstrap::v3::Bootstrap& bootstrap) const { + auto* runtime = bootstrap.mutable_layered_runtime()->add_layers(); + runtime->set_name("static_layer_0"); + Protobuf::Struct envoy_layer; + Protobuf::Struct& runtime_values = + *(*envoy_layer.mutable_fields())["envoy"].mutable_struct_value(); + + if (!reloadable_runtime_guards_.empty()) { + Protobuf::Struct& reloadable_features = + *(*runtime_values.mutable_fields())["reloadable_features"].mutable_struct_value(); + for (const auto& guard_and_value : reloadable_runtime_guards_) { + if (Runtime::RuntimeFeaturesDefaults::get().getFlag(absl::StrJoin( + {"envoy", "reloadable_features", guard_and_value.first}, ".")) == nullptr) { + continue; + } + (*reloadable_features.mutable_fields())[guard_and_value.first].set_bool_value( + guard_and_value.second); + } + } + + if (!restart_runtime_guards_.empty()) { + Protobuf::Struct& restart_features = + *(*runtime_values.mutable_fields())["restart_features"].mutable_struct_value(); + for (const auto& guard_and_value : restart_runtime_guards_) { + if (Runtime::RuntimeFeaturesDefaults::get().getFlag(absl::StrJoin( + {"envoy", "restart_features", guard_and_value.first}, ".")) == nullptr) { + continue; + } + (*restart_features.mutable_fields())[guard_and_value.first].set_bool_value( + guard_and_value.second); + } + } + + (*runtime_values.mutable_fields())["disallow_global_stats"].set_bool_value(true); + + Protobuf::Struct& overload_values = + *(*envoy_layer.mutable_fields())["overload"].mutable_struct_value(); + (*overload_values.mutable_fields())["global_downstream_max_connections"].set_string_value( + "4294967295"); + + runtime->mutable_static_layer()->MergeFrom(envoy_layer); + } + + void configureListenerManager(envoy::config::bootstrap::v3::Bootstrap& bootstrap) const { + envoy::config::bootstrap::v3::ApiListenerManager api; + if (!useWorkerThread()) { + api.set_threading_model(envoy::config::bootstrap::v3::ApiListenerManager::MAIN_THREAD_ONLY); + } else { + api.set_threading_model( + envoy::config::bootstrap::v3::ApiListenerManager::STANDALONE_WORKER_THREAD); + } + auto* listener_manager = bootstrap.mutable_listener_manager(); + listener_manager->mutable_typed_config()->PackFrom(api); + listener_manager->set_name("envoy.listener_manager_impl.api"); + } + + absl::optional watchdog_; + absl::optional stats_inclusion_list_; + uint32_t per_connection_buffer_limit_bytes_ = 10485760; + int stream_idle_timeout_seconds_ = 15; + std::vector> reloadable_runtime_guards_; + std::vector> restart_runtime_guards_; + + // Common fields for InternalEngine + Logger::Logger::Levels log_level_ = Logger::Logger::Levels::info; + std::unique_ptr logger_{nullptr}; + bool enable_logger_{true}; + std::unique_ptr callbacks_; + std::unique_ptr event_tracker_{nullptr}; + absl::optional high_watermark_ = absl::nullopt; + bool enable_stats_collection_ = true; + bool use_worker_thread_ = false; +}; + +} // namespace Platform +} // namespace Envoy diff --git a/mobile/library/cc/engine_builder_types.cc b/mobile/library/cc/engine_builder_types.cc new file mode 100644 index 0000000000000..7695f7e46fd5a --- /dev/null +++ b/mobile/library/cc/engine_builder_types.cc @@ -0,0 +1,99 @@ +#include "library/cc/engine_builder_types.h" + +#include "absl/strings/str_cat.h" +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/core/v3/base.pb.h" + +namespace Envoy { +namespace Platform { + +#ifdef ENVOY_MOBILE_XDS +XdsBuilder::XdsBuilder(std::string xds_server_address, const uint32_t xds_server_port) + : xds_server_address_(std::move(xds_server_address)), xds_server_port_(xds_server_port) {} + +XdsBuilder& XdsBuilder::addInitialStreamHeader(std::string header, std::string value) { + envoy::config::core::v3::HeaderValue header_value; + header_value.set_key(std::move(header)); + header_value.set_value(std::move(value)); + xds_initial_grpc_metadata_.emplace_back(std::move(header_value)); + return *this; +} + +XdsBuilder& XdsBuilder::setSslRootCerts(std::string root_certs) { + ssl_root_certs_ = std::move(root_certs); + return *this; +} + +XdsBuilder& XdsBuilder::addRuntimeDiscoveryService(std::string resource_name, + const int timeout_in_seconds) { + rtds_resource_name_ = std::move(resource_name); + rtds_timeout_in_seconds_ = timeout_in_seconds > 0 ? timeout_in_seconds : DefaultXdsTimeout; + return *this; +} + +XdsBuilder& XdsBuilder::addClusterDiscoveryService(std::string cds_resources_locator, + const int timeout_in_seconds) { + enable_cds_ = true; + cds_resources_locator_ = std::move(cds_resources_locator); + cds_timeout_in_seconds_ = timeout_in_seconds > 0 ? timeout_in_seconds : DefaultXdsTimeout; + return *this; +} + +void XdsBuilder::build(envoy::config::bootstrap::v3::Bootstrap& bootstrap) const { + auto* ads_config = bootstrap.mutable_dynamic_resources()->mutable_ads_config(); + ads_config->set_transport_api_version(envoy::config::core::v3::ApiVersion::V3); + ads_config->set_set_node_on_first_message_only(true); + ads_config->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); + + auto& grpc_service = *ads_config->add_grpc_services(); + grpc_service.mutable_envoy_grpc()->set_cluster_name("base"); + grpc_service.mutable_envoy_grpc()->set_authority( + absl::StrCat(xds_server_address_, ":", xds_server_port_)); + + if (!xds_initial_grpc_metadata_.empty()) { + grpc_service.mutable_initial_metadata()->Assign(xds_initial_grpc_metadata_.begin(), + xds_initial_grpc_metadata_.end()); + } + + if (!rtds_resource_name_.empty()) { + auto* layered_runtime = bootstrap.mutable_layered_runtime(); + auto* layer = layered_runtime->add_layers(); + layer->set_name("rtds_layer"); + auto* rtds_layer = layer->mutable_rtds_layer(); + rtds_layer->set_name(rtds_resource_name_); + auto* rtds_config = rtds_layer->mutable_rtds_config(); + rtds_config->mutable_ads(); + rtds_config->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); + rtds_config->mutable_initial_fetch_timeout()->set_seconds(rtds_timeout_in_seconds_); + } + + if (enable_cds_) { + auto* cds_config = bootstrap.mutable_dynamic_resources()->mutable_cds_config(); + if (cds_resources_locator_.empty()) { + cds_config->mutable_ads(); + } else { + bootstrap.mutable_dynamic_resources()->set_cds_resources_locator(cds_resources_locator_); + cds_config->mutable_api_config_source()->set_api_type( + envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC); + cds_config->mutable_api_config_source()->set_transport_api_version( + envoy::config::core::v3::ApiVersion::V3); + } + cds_config->mutable_initial_fetch_timeout()->set_seconds(cds_timeout_in_seconds_); + cds_config->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); + bootstrap.add_node_context_params("cluster"); + // Stat prefixes that we use in tests. + auto* list = + bootstrap.mutable_stats_config()->mutable_stats_matcher()->mutable_inclusion_list(); + list->add_patterns()->set_exact("cluster_manager.active_clusters"); + list->add_patterns()->set_exact("cluster_manager.cluster_added"); + list->add_patterns()->set_exact("cluster_manager.cluster_updated"); + list->add_patterns()->set_exact("cluster_manager.cluster_removed"); + // Allow SDS related stats. + list->add_patterns()->mutable_safe_regex()->set_regex("sds\\..*"); + list->add_patterns()->mutable_safe_regex()->set_regex(".*\\.ssl_context_update_by_sds"); + } +} +#endif // ENVOY_MOBILE_XDS + +} // namespace Platform +} // namespace Envoy diff --git a/mobile/library/cc/engine_builder_types.h b/mobile/library/cc/engine_builder_types.h new file mode 100644 index 0000000000000..f0c1bd217a0e0 --- /dev/null +++ b/mobile/library/cc/engine_builder_types.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/core/v3/base.pb.h" + +namespace Envoy { +namespace Platform { + +// Represents the locality information in the Bootstrap's node, as defined in: +// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#envoy-v3-api-msg-config-core-v3-locality +struct NodeLocality { + std::string region; + std::string zone; + std::string sub_zone; +}; + +#ifdef ENVOY_MOBILE_XDS +constexpr int DefaultXdsTimeout = 5; + +// Forward declarations so they can be friended by XdsBuilder. +class EngineBuilder; +class MobileEngineBuilder; + +// A class for building the xDS configuration for the Envoy Mobile engine. +// xDS is a protocol for dynamic configuration of Envoy instances, more information can be found in: +// https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol. +// +// This class is typically used as input to the EngineBuilder's/MobileEngineBuilder's setXds() +// method. +class XdsBuilder final { +public: + // `xds_server_address`: the host name or IP address of the xDS management server. The xDS server + // must support the ADS protocol + // (https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#aggregated-xds-ads). + // `xds_server_port`: the port on which the xDS management server listens for ADS discovery + // requests. + XdsBuilder(std::string xds_server_address, const uint32_t xds_server_port); + + // Adds a header to the initial HTTP metadata headers sent on the gRPC stream. + // + // A common use for the initial metadata headers is for authentication to the xDS management + // server. + // + // For example, if using API keys to authenticate to Traffic Director on GCP (see + // https://cloud.google.com/docs/authentication/api-keys for details), invoke: + // builder.addInitialStreamHeader("x-goog-api-key", api_key_token) + // .addInitialStreamHeader("X-Android-Package", app_package_name) + // .addInitialStreamHeader("X-Android-Cert", sha1_key_fingerprint); + XdsBuilder& addInitialStreamHeader(std::string header, std::string value); + + // Sets the PEM-encoded server root certificates used to negotiate the TLS handshake for the gRPC + // connection. If no root certs are specified, the operating system defaults are used. + XdsBuilder& setSslRootCerts(std::string root_certs); + + // Adds Runtime Discovery Service (RTDS) to the Runtime layers of the Bootstrap configuration, + // to retrieve dynamic runtime configuration via the xDS management server. + // + // `resource_name`: The runtime config resource to subscribe to. + // `timeout_in_seconds`: specifies the `initial_fetch_timeout` field on the + // api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch + // timeout value of 5s, to prevent mobile app initialization from stalling. The default + // parameter value may change through the course of experimentation and no assumptions should + // be made of its exact value. + XdsBuilder& addRuntimeDiscoveryService(std::string resource_name, + int timeout_in_seconds = DefaultXdsTimeout); + + // Adds the Cluster Discovery Service (CDS) configuration for retrieving dynamic cluster resources + // via the xDS management server. + // + // `cds_resources_locator`: the xdstp:// URI for subscribing to the cluster resources. + // If not using xdstp, then `cds_resources_locator` should be set to the empty string. + // `timeout_in_seconds`: specifies the `initial_fetch_timeout` field on the + // api.v3.core.ConfigSource. Unlike the ConfigSource default of 15s, we set a default fetch + // timeout value of 5s, to prevent mobile app initialization from stalling. The default + // parameter value may change through the course of experimentation and no assumptions should + // be made of its exact value. + XdsBuilder& addClusterDiscoveryService(std::string cds_resources_locator = "", + int timeout_in_seconds = DefaultXdsTimeout); + +protected: + // Sets the xDS configuration specified on this XdsBuilder instance on the Bootstrap proto + // provided as an input parameter. + // + // This method takes in a modifiable Bootstrap proto pointer because returning a new Bootstrap + // proto would rely on proto's MergeFrom behavior, which can lead to unexpected results in the + // Bootstrap config. + void build(envoy::config::bootstrap::v3::Bootstrap& bootstrap) const; + +private: + // Required so that both Builders can call the XdsBuilder's protected build() method. + friend class EngineBuilder; + friend class MobileEngineBuilder; + + std::string xds_server_address_; + uint32_t xds_server_port_; + std::vector xds_initial_grpc_metadata_; + std::string ssl_root_certs_; + std::string rtds_resource_name_; + int rtds_timeout_in_seconds_ = DefaultXdsTimeout; + bool enable_cds_ = false; + std::string cds_resources_locator_; + int cds_timeout_in_seconds_ = DefaultXdsTimeout; +}; +#endif // ENVOY_MOBILE_XDS + +} // namespace Platform +} // namespace Envoy diff --git a/mobile/library/cc/mobile_engine_builder.cc b/mobile/library/cc/mobile_engine_builder.cc new file mode 100644 index 0000000000000..38ff587db72f3 --- /dev/null +++ b/mobile/library/cc/mobile_engine_builder.cc @@ -0,0 +1,1012 @@ +#include "library/cc/mobile_engine_builder.h" + +#include +#include + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/core/v3/socket_option.pb.h" +#include "envoy/config/metrics/v3/metrics_service.pb.h" +#include "envoy/extensions/compression/brotli/decompressor/v3/brotli.pb.h" +#include "envoy/extensions/compression/gzip/decompressor/v3/gzip.pb.h" +#include "envoy/extensions/filters/http/alternate_protocols_cache/v3/alternate_protocols_cache.pb.h" +#include "envoy/extensions/filters/http/decompressor/v3/decompressor.pb.h" +#include "envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.h" +#include "envoy/extensions/filters/http/router/v3/router.pb.h" +#include "envoy/extensions/http/header_formatters/preserve_case/v3/preserve_case.pb.h" +#include "envoy/extensions/early_data/v3/default_early_data_policy.pb.h" + +#if defined(__APPLE__) +#include "envoy/extensions/network/dns_resolver/apple/v3/apple_dns_resolver.pb.h" +#endif +#include "envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.pb.h" +#include "envoy/extensions/transport_sockets/http_11_proxy/v3/upstream_http_11_connect.pb.h" +#include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.h" +#include "envoy/extensions/transport_sockets/raw_buffer/v3/raw_buffer.pb.h" + +#include "source/common/http/matching/inputs.h" +#include "envoy/config/core/v3/base.pb.h" +#include "source/extensions/clusters/dynamic_forward_proxy/cluster.h" +#include "source/common/runtime/runtime_features.h" +#include "envoy/type/matcher/v3/string.pb.h" + +#include "absl/strings/str_join.h" +#include "absl/strings/str_replace.h" +#include "absl/debugging/leak_check.h" +#include "fmt/core.h" +#include "library/common/internal_engine.h" +#include "library/common/extensions/cert_validator/platform_bridge/platform_bridge.pb.h" +#include "library/common/extensions/filters/http/platform_bridge/filter.pb.h" +#include "library/common/extensions/filters/http/local_error/filter.pb.h" +#include "library/common/extensions/filters/http/network_configuration/filter.pb.h" +#include "library/common/extensions/filters/http/socket_tag/filter.pb.h" +#include "library/common/extensions/key_value/platform/platform.pb.h" +#include "library/common/extensions/quic_packet_writer/platform/platform_packet_writer.pb.h" + +#if defined(__APPLE__) +#include "library/common/network/apple_proxy_resolution.h" +#endif + +namespace Envoy { +namespace Platform { + +MobileEngineBuilder::MobileEngineBuilder() {} + +MobileEngineBuilder& MobileEngineBuilder::setNetworkThreadPriority(int thread_priority) { + network_thread_priority_ = thread_priority; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addConnectTimeoutSeconds(int connect_timeout_seconds) { + connect_timeout_seconds_ = connect_timeout_seconds; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addDnsRefreshSeconds(int dns_refresh_seconds) { + dns_refresh_seconds_ = dns_refresh_seconds; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addDnsMinRefreshSeconds(int dns_min_refresh_seconds) { + dns_min_refresh_seconds_ = dns_min_refresh_seconds; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addDnsFailureRefreshSeconds(int base, int max) { + dns_failure_refresh_seconds_base_ = base; + dns_failure_refresh_seconds_max_ = max; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addDnsQueryTimeoutSeconds(int dns_query_timeout_seconds) { + dns_query_timeout_seconds_ = dns_query_timeout_seconds; + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::setDisableDnsRefreshOnFailure(bool disable_dns_refresh_on_failure) { + disable_dns_refresh_on_failure_ = disable_dns_refresh_on_failure; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setDisableDnsRefreshOnNetworkChange( + bool disable_dns_refresh_on_network_change) { + disable_dns_refresh_on_network_change_ = disable_dns_refresh_on_network_change; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setDnsNumRetries(uint32_t dns_num_retries) { + dns_num_retries_ = dns_num_retries; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setGetaddrinfoNumThreads(uint32_t num_threads) { + getaddrinfo_num_threads_ = num_threads; + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::addDnsPreresolveHostnames(const std::vector& hostnames) { + dns_preresolve_hostnames_.clear(); + for (const std::string& hostname : hostnames) { + dns_preresolve_hostnames_.push_back({hostname /* host */, 443 /* port */}); + } + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setDnsResolver( + const envoy::config::core::v3::TypedExtensionConfig& dns_resolver_config) { + dns_resolver_config_ = dns_resolver_config; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setAdditionalSocketOptions( + const std::vector& socket_options) { + socket_options_ = socket_options; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addMaxConnectionsPerHost(int max_connections_per_host) { + max_connections_per_host_ = max_connections_per_host; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addH2ConnectionKeepaliveIdleIntervalMilliseconds( + int h2_connection_keepalive_idle_interval_milliseconds) { + h2_connection_keepalive_idle_interval_milliseconds_ = + h2_connection_keepalive_idle_interval_milliseconds; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addH2ConnectionKeepaliveTimeoutSeconds( + int h2_connection_keepalive_timeout_seconds) { + h2_connection_keepalive_timeout_seconds_ = h2_connection_keepalive_timeout_seconds; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addKeyValueStore(std::string name, + KeyValueStoreSharedPtr key_value_store) { + key_value_stores_[std::move(name)] = std::move(key_value_store); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setAppVersion(std::string app_version) { + app_version_ = std::move(app_version); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setAppId(std::string app_id) { + app_id_ = std::move(app_id); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setDeviceOs(std::string device_os) { + device_os_ = std::move(device_os); + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::setPerTryIdleTimeoutSeconds(int per_try_idle_timeout_seconds) { + per_try_idle_timeout_seconds_ = per_try_idle_timeout_seconds; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::enableGzipDecompression(bool gzip_decompression_on) { + gzip_decompression_filter_ = gzip_decompression_on; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::enableBrotliDecompression(bool brotli_decompression_on) { + brotli_decompression_filter_ = brotli_decompression_on; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::enableSocketTagging(bool socket_tagging_on) { + socket_tagging_filter_ = socket_tagging_on; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::enableHttp3(bool http3_on) { + enable_http3_ = http3_on; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::enableEarlyData(bool early_data_on) { + enable_early_data_ = early_data_on; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::enableScone(bool enable) { + scone_enabled_ = enable; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addQuicConnectionOption(std::string option) { + quic_connection_options_.push_back(std::move(option)); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addQuicClientConnectionOption(std::string option) { + quic_client_connection_options_.push_back(std::move(option)); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setHttp3ConnectionOptions(std::string options) { + http3_connection_options_ = std::move(options); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setHttp3ClientConnectionOptions(std::string options) { + http3_client_connection_options_ = std::move(options); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addQuicHint(std::string host, int port) { + quic_hints_.emplace_back(std::move(host), port); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addQuicCanonicalSuffix(std::string suffix) { + quic_suffixes_.emplace_back(std::move(suffix)); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setNumTimeoutsToTriggerPortMigration(int num_timeouts) { + num_timeouts_to_trigger_port_migration_ = num_timeouts; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::enableInterfaceBinding(bool interface_binding_on) { + enable_interface_binding_ = interface_binding_on; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setUdpSocketReceiveBufferSize(int32_t size) { + udp_socket_receive_buffer_size_ = size; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setUdpSocketSendBufferSize(int32_t size) { + udp_socket_send_buffer_size_ = size; + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::enableDrainPostDnsRefresh(bool drain_post_dns_refresh_on) { + enable_drain_post_dns_refresh_ = drain_post_dns_refresh_on; + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::enforceTrustChainVerification(bool trust_chain_verification_on) { + enforce_trust_chain_verification_ = trust_chain_verification_on; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setUpstreamTlsSni(std::string sni) { + upstream_tls_sni_ = std::move(sni); + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::setQuicConnectionIdleTimeoutSeconds(int quic_connection_idle_timeout_seconds) { + quic_connection_idle_timeout_seconds_ = quic_connection_idle_timeout_seconds; + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::setKeepAliveInitialIntervalMilliseconds(int keepalive_initial_interval_ms) { + keepalive_initial_interval_ms_ = keepalive_initial_interval_ms; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setMaxConcurrentStreams(int max_concurrent_streams) { + max_concurrent_streams_ = max_concurrent_streams; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::enablePlatformCertificatesValidation( + bool platform_certificates_validation_on) { + if (useWorkerThread()) { + return *this; + } + platform_certificates_validation_on_ = platform_certificates_validation_on; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::enableDnsCache(bool dns_cache_on, + int save_interval_seconds) { + dns_cache_on_ = dns_cache_on; + dns_cache_save_interval_seconds_ = save_interval_seconds; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addStringAccessor(std::string name, + StringAccessorSharedPtr accessor) { + string_accessors_[std::move(name)] = std::move(accessor); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addNativeFilter(std::string name, + std::string typed_config) { + native_filter_chain_.emplace_back(NativeFilterConfig(std::move(name), std::move(typed_config))); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addNativeFilter(const std::string& name, + const Protobuf::Any& typed_config) { + native_filter_chain_.push_back(NativeFilterConfig(name, typed_config)); + return *this; +} + +#if defined(__APPLE__) +MobileEngineBuilder& +MobileEngineBuilder::enableNetworkChangeMonitor(bool network_change_monitor_on) { + enable_network_change_monitor_ = network_change_monitor_on; + return *this; +} +#endif + +std::string MobileEngineBuilder::nativeNameToConfig(absl::string_view name) { +#ifdef ENVOY_ENABLE_FULL_PROTOS + return absl::StrCat("[type.googleapis.com/" + "envoymobile.extensions.filters.http.platform_bridge.PlatformBridge] {" + "platform_filter_name: \"", + name, "\" }"); +#else + envoymobile::extensions::filters::http::platform_bridge::PlatformBridge proto_config; + proto_config.set_platform_filter_name(name); + std::string ret; + proto_config.SerializeToString(&ret); + Protobuf::Any any_config; + any_config.set_type_url( + "type.googleapis.com/envoymobile.extensions.filters.http.platform_bridge.PlatformBridge"); + any_config.set_value(ret); + any_config.SerializeToString(&ret); + return ret; +#endif +} + +MobileEngineBuilder& MobileEngineBuilder::addPlatformFilter(const std::string& name) { + addNativeFilter("envoy.filters.http.platform_bridge", nativeNameToConfig(name)); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::enableWorkerThread(bool use_worker_thread) { + Platform::EngineBuilderBase::enableWorkerThread(use_worker_thread); + if (useWorkerThread()) { + platform_certificates_validation_on_ = false; +#ifdef __APPLE__ + respect_system_proxy_settings_ = false; +#endif + } + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::addRuntimeGuard(std::string guard, bool value) { + addReloadableRuntimeGuard(std::move(guard), value); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setNodeId(std::string node_id) { + node_id_ = std::move(node_id); + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setNodeLocality(std::string region, std::string zone, + std::string sub_zone) { + node_locality_ = {std::move(region), std::move(zone), std::move(sub_zone)}; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setNodeMetadata(Protobuf::Struct node_metadata) { + node_metadata_ = std::move(node_metadata); + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::setUseQuicPlatformPacketWriter(bool use_quic_platform_packet_writer) { + use_quic_platform_packet_writer_ = use_quic_platform_packet_writer; + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::enableQuicConnectionMigration(bool quic_connection_migration_on) { + enable_quic_connection_migration_ = quic_connection_migration_on; + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::setMigrateIdleQuicConnection(bool migrate_idle_quic_connection) { + migrate_idle_quic_connection_ = migrate_idle_quic_connection; + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setMaxIdleTimeBeforeQuicMigrationSeconds( + int max_idle_time_before_quic_migration) { + max_idle_time_before_quic_migration_seconds_ = max_idle_time_before_quic_migration; + return *this; +} + +MobileEngineBuilder& +MobileEngineBuilder::setMaxTimeOnNonDefaultNetworkSeconds(int max_time_on_non_default_network) { + max_time_on_non_default_network_seconds_ = max_time_on_non_default_network; + return *this; +} + +#if defined(__APPLE__) +MobileEngineBuilder& MobileEngineBuilder::respectSystemProxySettings(bool value, + int refresh_interval_secs) { + if (useWorkerThread()) { + return *this; + } + respect_system_proxy_settings_ = value; + if (refresh_interval_secs > 0) { + proxy_settings_refresh_interval_secs_ = refresh_interval_secs; + } + return *this; +} + +MobileEngineBuilder& MobileEngineBuilder::setIosNetworkServiceType(int ios_network_service_type) { + ios_network_service_type_ = ios_network_service_type; + return *this; +} +#endif + +#ifdef ENVOY_MOBILE_XDS + +MobileEngineBuilder& MobileEngineBuilder::setXds(XdsBuilder xds_builder) { + xds_builder_ = std::move(xds_builder); + dns_preresolve_hostnames_.push_back( + {xds_builder_->xds_server_address_ /* host */, xds_builder_->xds_server_port_ /* port */}); + return *this; +} +#endif // ENVOY_MOBILE_XDS + +absl::Status MobileEngineBuilder::configureRouteConfig( + envoy::config::route::v3::RouteConfiguration* route_config) { + route_config->set_name("api_router"); + + auto* api_service = route_config->add_virtual_hosts(); + api_service->set_name("api"); + api_service->set_include_attempt_count_in_response(true); + api_service->add_domains("*"); + + auto* route = api_service->add_routes(); + route->mutable_match()->set_prefix("/"); + route->add_request_headers_to_remove("x-forwarded-proto"); + route->add_request_headers_to_remove("x-envoy-mobile-cluster"); + route->mutable_per_request_buffer_limit_bytes()->set_value(4096); + auto* route_to = route->mutable_route(); + route_to->set_cluster_header("x-envoy-mobile-cluster"); + route_to->mutable_timeout()->set_seconds(0); + route_to->mutable_retry_policy()->mutable_per_try_idle_timeout()->set_seconds( + per_try_idle_timeout_seconds_); + auto* backoff = route_to->mutable_retry_policy()->mutable_retry_back_off(); + backoff->mutable_base_interval()->set_nanos(250000000); + backoff->mutable_max_interval()->set_seconds(60); + + if (!enable_early_data_) { + auto* early_data = route_to->mutable_early_data_policy(); + early_data->set_name("envoy.route.early_data_policy.default"); + ::envoy::extensions::early_data::v3::DefaultEarlyDataPolicy config; + early_data->mutable_typed_config()->PackFrom(config); + } + return absl::OkStatus(); +} + +absl::Status MobileEngineBuilder::configureHttpFilters( + std::function + add_filter) { + for (auto filter = native_filter_chain_.rbegin(); filter != native_filter_chain_.rend(); + ++filter) { + auto* native_filter = add_filter(); + native_filter->set_name(filter->name_); + if (!filter->textproto_typed_config_.empty()) { +#if ENVOY_ENABLE_FULL_PROTOS + Protobuf::TextFormat::ParseFromString((*filter).textproto_typed_config_, + native_filter->mutable_typed_config()); + RELEASE_ASSERT(!native_filter->typed_config().DebugString().empty(), + "Failed to parse: " + (*filter).textproto_typed_config_); +#else + RELEASE_ASSERT( + native_filter->mutable_typed_config()->ParseFromString((*filter).textproto_typed_config_), + "Failed to parse binary proto: " + (*filter).textproto_typed_config_); +#endif // !ENVOY_ENABLE_FULL_PROTOS + } else { + *native_filter->mutable_typed_config() = filter->typed_config_; + } + } + + if (enable_http3_) { + envoy::extensions::filters::http::alternate_protocols_cache::v3::FilterConfig cache_config; + auto* cache_filter = add_filter(); + cache_filter->set_name("alternate_protocols_cache"); + cache_filter->mutable_typed_config()->PackFrom(cache_config); + } + + if (gzip_decompression_filter_) { + envoy::extensions::compression::gzip::decompressor::v3::Gzip gzip_config; + gzip_config.mutable_window_bits()->set_value(15); + envoy::extensions::filters::http::decompressor::v3::Decompressor decompressor_config; + decompressor_config.mutable_decompressor_library()->set_name("gzip"); + decompressor_config.mutable_decompressor_library()->mutable_typed_config()->PackFrom( + gzip_config); + auto* common_request = + decompressor_config.mutable_request_direction_config()->mutable_common_config(); + common_request->mutable_enabled()->mutable_default_value(); + common_request->mutable_enabled()->set_runtime_key("request_decompressor_enabled"); + decompressor_config.mutable_response_direction_config() + ->mutable_common_config() + ->set_ignore_no_transform_header(true); + auto* gzip_filter = add_filter(); + gzip_filter->set_name("envoy.filters.http.decompressor"); + gzip_filter->mutable_typed_config()->PackFrom(decompressor_config); + } + if (brotli_decompression_filter_) { + envoy::extensions::compression::brotli::decompressor::v3::Brotli brotli_config; + envoy::extensions::filters::http::decompressor::v3::Decompressor decompressor_config; + decompressor_config.mutable_decompressor_library()->set_name("text_optimized"); + decompressor_config.mutable_decompressor_library()->mutable_typed_config()->PackFrom( + brotli_config); + auto* common_request = + decompressor_config.mutable_request_direction_config()->mutable_common_config(); + common_request->mutable_enabled()->mutable_default_value(); + common_request->mutable_enabled()->set_runtime_key("request_decompressor_enabled"); + decompressor_config.mutable_response_direction_config() + ->mutable_common_config() + ->set_ignore_no_transform_header(true); + auto* brotli_filter = add_filter(); + brotli_filter->set_name("envoy.filters.http.decompressor"); + brotli_filter->mutable_typed_config()->PackFrom(decompressor_config); + } + if (socket_tagging_filter_) { + envoymobile::extensions::filters::http::socket_tag::SocketTag tag_config; + auto* tag_filter = add_filter(); + tag_filter->set_name("envoy.filters.http.socket_tag"); + tag_filter->mutable_typed_config()->PackFrom(tag_config); + } + + envoymobile::extensions::filters::http::network_configuration::NetworkConfiguration + network_config; + network_config.set_enable_drain_post_dns_refresh(enable_drain_post_dns_refresh_); + network_config.set_enable_interface_binding(enable_interface_binding_); + auto* network_filter = add_filter(); + network_filter->set_name("envoy.filters.http.network_configuration"); + network_filter->mutable_typed_config()->PackFrom(network_config); + + envoymobile::extensions::filters::http::local_error::LocalError local_config; + auto* local_filter = add_filter(); + local_filter->set_name("envoy.filters.http.local_error"); + local_filter->mutable_typed_config()->PackFrom(local_config); + + envoy::extensions::filters::http::dynamic_forward_proxy::v3::FilterConfig dfp_config; + configureDnsCache(dfp_config.mutable_dns_cache_config()); + auto* dfp_filter = add_filter(); + dfp_filter->set_name("envoy.filters.http.dynamic_forward_proxy"); + dfp_filter->mutable_typed_config()->PackFrom(dfp_config); + return absl::OkStatus(); +} + +void MobileEngineBuilder::configureDnsCache( + envoy::extensions::common::dynamic_forward_proxy::v3::DnsCacheConfig* dns_cache_config) const { + dns_cache_config->set_name("base_dns_cache"); + dns_cache_config->set_dns_lookup_family(envoy::config::cluster::v3::Cluster::ALL); + dns_cache_config->mutable_host_ttl()->set_seconds(86400); + dns_cache_config->mutable_dns_min_refresh_rate()->set_seconds(dns_min_refresh_seconds_); + dns_cache_config->mutable_dns_refresh_rate()->set_seconds(dns_refresh_seconds_); + dns_cache_config->mutable_dns_failure_refresh_rate()->mutable_base_interval()->set_seconds( + dns_failure_refresh_seconds_base_); + dns_cache_config->mutable_dns_failure_refresh_rate()->mutable_max_interval()->set_seconds( + dns_failure_refresh_seconds_max_); + dns_cache_config->mutable_dns_query_timeout()->set_seconds(dns_query_timeout_seconds_); + dns_cache_config->set_disable_dns_refresh_on_failure(disable_dns_refresh_on_failure_); + if (dns_cache_on_) { + envoymobile::extensions::key_value::platform::PlatformKeyValueStoreConfig kv_config; + kv_config.set_key("dns_persistent_cache"); + kv_config.mutable_save_interval()->set_seconds(dns_cache_save_interval_seconds_); + kv_config.set_max_entries(100); + dns_cache_config->mutable_key_value_config()->mutable_config()->set_name( + "envoy.key_value.platform"); + dns_cache_config->mutable_key_value_config() + ->mutable_config() + ->mutable_typed_config() + ->PackFrom(kv_config); + } + + if (dns_resolver_config_.has_value()) { + *dns_cache_config->mutable_typed_dns_resolver_config() = *dns_resolver_config_; + } else { +#if defined(__APPLE__) + envoy::extensions::network::dns_resolver::apple::v3::AppleDnsResolverConfig resolver_config; + dns_cache_config->mutable_typed_dns_resolver_config()->set_name( + "envoy.network.dns_resolver.apple"); + dns_cache_config->mutable_typed_dns_resolver_config()->mutable_typed_config()->PackFrom( + resolver_config); +#else + envoy::extensions::network::dns_resolver::getaddrinfo::v3::GetAddrInfoDnsResolverConfig + resolver_config; + if (dns_num_retries_.has_value()) { + resolver_config.mutable_num_retries()->set_value(*dns_num_retries_); + } + resolver_config.mutable_num_resolver_threads()->set_value(getaddrinfo_num_threads_); + dns_cache_config->mutable_typed_dns_resolver_config()->set_name( + "envoy.network.dns_resolver.getaddrinfo"); + dns_cache_config->mutable_typed_dns_resolver_config()->mutable_typed_config()->PackFrom( + resolver_config); +#endif + } + + for (const auto& [host, port] : dns_preresolve_hostnames_) { + envoy::config::core::v3::SocketAddress* address = dns_cache_config->add_preresolve_hostnames(); + address->set_address(host); + address->set_port_value(port); + } +} + +absl::Status MobileEngineBuilder::configureStaticClusters( + Protobuf::RepeatedPtrField* clusters) { + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_socket; + if (!upstream_tls_sni_.empty()) { + tls_socket.set_sni(upstream_tls_sni_); + } + tls_socket.mutable_common_tls_context()->mutable_tls_params()->set_tls_maximum_protocol_version( + envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_3); + auto* validation = tls_socket.mutable_common_tls_context()->mutable_validation_context(); + if (enforce_trust_chain_verification_) { + validation->set_trust_chain_verification(envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::VERIFY_TRUST_CHAIN); + } else { + validation->set_trust_chain_verification(envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::ACCEPT_UNTRUSTED); + } + + if (platform_certificates_validation_on_) { + envoy_mobile::extensions::cert_validator::platform_bridge::PlatformBridgeCertValidator + validator; + if (network_thread_priority_.has_value()) { + validator.mutable_thread_priority()->set_value(*network_thread_priority_); + } + validation->mutable_custom_validator_config()->set_name( + "envoy_mobile.cert_validator.platform_bridge_cert_validator"); + validation->mutable_custom_validator_config()->mutable_typed_config()->PackFrom(validator); + } else { + std::string certs; +#ifdef ENVOY_MOBILE_XDS + if (xds_builder_ && !xds_builder_->ssl_root_certs_.empty()) { + certs = xds_builder_->ssl_root_certs_; + } +#endif // ENVOY_MOBILE_XDS + if (certs.empty()) { + const char* inline_certs = "" +#ifndef EXCLUDE_CERTIFICATES +#include "library/common/config/certificates.inc" +#endif + ""; + certs = inline_certs; + absl::StrReplaceAll({{"\n ", "\n"}}, &certs); + } + if (!certs.empty()) { + validation->mutable_trusted_ca()->set_inline_string(certs); + } + } + envoy::extensions::transport_sockets::http_11_proxy::v3::Http11ProxyUpstreamTransport + ssl_proxy_socket; + ssl_proxy_socket.mutable_transport_socket()->set_name("envoy.transport_sockets.tls"); + ssl_proxy_socket.mutable_transport_socket()->mutable_typed_config()->PackFrom(tls_socket); + + envoy::extensions::upstreams::http::v3::HttpProtocolOptions h2_protocol_options; + h2_protocol_options.mutable_explicit_http_config()->mutable_http2_protocol_options(); + + envoy::config::cluster::v3::Cluster* base_cluster = clusters->Add(); + envoy::extensions::clusters::dynamic_forward_proxy::v3::ClusterConfig base_cluster_config; + envoy::config::cluster::v3::Cluster::CustomClusterType base_cluster_type; + configureDnsCache(base_cluster_config.mutable_dns_cache_config()); + base_cluster_type.set_name("envoy.clusters.dynamic_forward_proxy"); + base_cluster_type.mutable_typed_config()->PackFrom(base_cluster_config); + + auto* upstream_opts = base_cluster->mutable_upstream_connection_options(); + upstream_opts->set_set_local_interface_name_on_upstream_connections(true); + upstream_opts->mutable_tcp_keepalive()->mutable_keepalive_interval()->set_value(5); + upstream_opts->mutable_tcp_keepalive()->mutable_keepalive_probes()->set_value(1); + upstream_opts->mutable_tcp_keepalive()->mutable_keepalive_time()->set_value(10); + + auto* circuit_breaker_settings = base_cluster->mutable_circuit_breakers(); + auto* breaker1 = circuit_breaker_settings->add_thresholds(); + breaker1->set_priority(envoy::config::core::v3::RoutingPriority::DEFAULT); + breaker1->mutable_retry_budget()->mutable_budget_percent()->set_value(100); + breaker1->mutable_retry_budget()->mutable_min_retry_concurrency()->set_value(0xffffffff); + auto* breaker2 = circuit_breaker_settings->add_per_host_thresholds(); + breaker2->set_priority(envoy::config::core::v3::RoutingPriority::DEFAULT); + breaker2->mutable_max_connections()->set_value(max_connections_per_host_); + + envoy::extensions::upstreams::http::v3::HttpProtocolOptions alpn_options; + alpn_options.mutable_upstream_http_protocol_options()->set_auto_sni(true); + alpn_options.mutable_upstream_http_protocol_options()->set_auto_san_validation(true); + auto* h2_options = alpn_options.mutable_auto_config()->mutable_http2_protocol_options(); + if (h2_connection_keepalive_idle_interval_milliseconds_ > 1000) { + h2_options->mutable_connection_keepalive()->mutable_connection_idle_interval()->set_seconds( + h2_connection_keepalive_idle_interval_milliseconds_ / 1000); + } else { + h2_options->mutable_connection_keepalive()->mutable_connection_idle_interval()->set_nanos( + h2_connection_keepalive_idle_interval_milliseconds_ * 1000 * 1000); + } + h2_options->mutable_connection_keepalive()->mutable_timeout()->set_seconds( + h2_connection_keepalive_timeout_seconds_); + h2_options->mutable_max_concurrent_streams()->set_value(100); + h2_options->mutable_initial_stream_window_size()->set_value(initial_stream_window_size_); + h2_options->mutable_initial_connection_window_size()->set_value(initial_connection_window_size_); + if (max_concurrent_streams_ > 0) { + h2_options->mutable_max_concurrent_streams()->set_value(max_concurrent_streams_); + } + + envoy::extensions::http::header_formatters::preserve_case::v3::PreserveCaseFormatterConfig + preserve_case_config; + preserve_case_config.set_forward_reason_phrase(false); + preserve_case_config.set_formatter_type_on_envoy_headers( + envoy::extensions::http::header_formatters::preserve_case::v3::PreserveCaseFormatterConfig:: + DEFAULT); + + auto* h1_options = alpn_options.mutable_auto_config()->mutable_http_protocol_options(); + auto* formatter = h1_options->mutable_header_key_format()->mutable_stateful_formatter(); + formatter->set_name("preserve_case"); + formatter->mutable_typed_config()->PackFrom(preserve_case_config); + + // Base cluster + base_cluster->set_name("base"); + base_cluster->mutable_connect_timeout()->set_seconds(connect_timeout_seconds_); + base_cluster->set_lb_policy(envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED); + (*base_cluster->mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] + .PackFrom(alpn_options); + base_cluster->mutable_cluster_type()->CopyFrom(base_cluster_type); + base_cluster->mutable_transport_socket()->set_name("envoy.transport_sockets.http_11_proxy"); + base_cluster->mutable_transport_socket()->mutable_typed_config()->PackFrom(ssl_proxy_socket); + + // Base clear-text cluster set up + envoy::extensions::transport_sockets::raw_buffer::v3::RawBuffer raw_buffer; + envoy::extensions::transport_sockets::http_11_proxy::v3::Http11ProxyUpstreamTransport + cleartext_proxy_socket; + cleartext_proxy_socket.mutable_transport_socket()->mutable_typed_config()->PackFrom(raw_buffer); + cleartext_proxy_socket.mutable_transport_socket()->set_name("envoy.transport_sockets.raw_buffer"); + envoy::extensions::upstreams::http::v3::HttpProtocolOptions h1_protocol_options; + h1_protocol_options.mutable_upstream_http_protocol_options()->set_auto_sni(true); + h1_protocol_options.mutable_upstream_http_protocol_options()->set_auto_san_validation(true); + h1_protocol_options.mutable_explicit_http_config()->mutable_http_protocol_options()->CopyFrom( + *alpn_options.mutable_auto_config()->mutable_http_protocol_options()); + + // Base clear-text cluster. + envoy::config::cluster::v3::Cluster* base_clear = clusters->Add(); + base_clear->set_name("base_clear"); + base_clear->mutable_connect_timeout()->set_seconds(connect_timeout_seconds_); + base_clear->set_lb_policy(envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED); + base_clear->mutable_cluster_type()->CopyFrom(base_cluster_type); + base_clear->mutable_transport_socket()->set_name("envoy.transport_sockets.http_11_proxy"); + base_clear->mutable_transport_socket()->mutable_typed_config()->PackFrom(cleartext_proxy_socket); + base_clear->mutable_upstream_connection_options()->CopyFrom( + *base_cluster->mutable_upstream_connection_options()); + base_clear->mutable_circuit_breakers()->CopyFrom(*base_cluster->mutable_circuit_breakers()); + (*base_clear->mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] + .PackFrom(h1_protocol_options); + + // Edit and re-pack + tls_socket.mutable_common_tls_context()->add_alpn_protocols("h2"); + ssl_proxy_socket.mutable_transport_socket()->mutable_typed_config()->PackFrom(tls_socket); + + // Edit base cluster to be an HTTP/3 cluster. + if (enable_http3_) { + envoy::extensions::transport_sockets::quic::v3::QuicUpstreamTransport h3_inner_socket; + tls_socket.mutable_common_tls_context()->mutable_alpn_protocols()->Clear(); + h3_inner_socket.mutable_upstream_tls_context()->CopyFrom(tls_socket); + envoy::extensions::transport_sockets::http_11_proxy::v3::Http11ProxyUpstreamTransport + h3_proxy_socket; + h3_proxy_socket.mutable_transport_socket()->mutable_typed_config()->PackFrom(h3_inner_socket); + h3_proxy_socket.mutable_transport_socket()->set_name("envoy.transport_sockets.quic"); + + auto* quic_protocol_options = alpn_options.mutable_auto_config() + ->mutable_http3_protocol_options() + ->mutable_quic_protocol_options(); + if (!quic_connection_options_.empty()) { + quic_protocol_options->set_connection_options(absl::StrJoin(quic_connection_options_, ",")); + } else { + quic_protocol_options->set_connection_options(http3_connection_options_); + } + if (!quic_client_connection_options_.empty()) { + quic_protocol_options->set_client_connection_options( + absl::StrJoin(quic_client_connection_options_, ",")); + } else { + quic_protocol_options->set_client_connection_options(http3_client_connection_options_); + } + quic_protocol_options->mutable_initial_stream_window_size()->set_value( + initial_stream_window_size_); + quic_protocol_options->mutable_initial_connection_window_size()->set_value( + initial_connection_window_size_); + quic_protocol_options->mutable_idle_network_timeout()->set_seconds( + quic_connection_idle_timeout_seconds_); + if (num_timeouts_to_trigger_port_migration_ > 0) { + quic_protocol_options->mutable_num_timeouts_to_trigger_port_migration()->set_value( + num_timeouts_to_trigger_port_migration_); + } + if (keepalive_initial_interval_ms_ > 0) { + quic_protocol_options->mutable_connection_keepalive()->mutable_initial_interval()->set_nanos( + keepalive_initial_interval_ms_ * 1000 * 1000); + } + if (max_concurrent_streams_ > 0) { + quic_protocol_options->mutable_max_concurrent_streams()->set_value(max_concurrent_streams_); + } + if (enable_quic_connection_migration_) { + auto* migration_setting = quic_protocol_options->mutable_connection_migration(); + if (migrate_idle_quic_connection_) { + auto* migrate_idle_connections = migration_setting->mutable_migrate_idle_connections(); + if (max_idle_time_before_quic_migration_seconds_ > 0) { + migrate_idle_connections->mutable_max_idle_time_before_migration()->set_seconds( + max_idle_time_before_quic_migration_seconds_); + } + } + if (max_time_on_non_default_network_seconds_ > 0) { + migration_setting->mutable_max_time_on_non_default_network()->set_seconds( + max_time_on_non_default_network_seconds_); + } + } + + if (scone_enabled_) { + quic_protocol_options->mutable_enable_scone()->set_value(true); + } + + if (use_quic_platform_packet_writer_ || enable_quic_connection_migration_) { + envoy_mobile::extensions::quic_packet_writer::platform::QuicPlatformPacketWriterConfig + writer_config; + quic_protocol_options->mutable_client_packet_writer()->mutable_typed_config()->PackFrom( + writer_config); + quic_protocol_options->mutable_client_packet_writer()->set_name( + "envoy.quic.packet_writer.platform"); + } + + alpn_options.mutable_auto_config()->mutable_alternate_protocols_cache_options()->set_name( + "default_alternate_protocols_cache"); + for (const auto& [host, port] : quic_hints_) { + auto* entry = alpn_options.mutable_auto_config() + ->mutable_alternate_protocols_cache_options() + ->add_prepopulated_entries(); + entry->set_hostname(host); + entry->set_port(port); + } + for (const auto& suffix : quic_suffixes_) { + alpn_options.mutable_auto_config() + ->mutable_alternate_protocols_cache_options() + ->add_canonical_suffixes(suffix); + } + + base_cluster->mutable_transport_socket()->mutable_typed_config()->PackFrom(h3_proxy_socket); + (*base_cluster->mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] + .PackFrom(alpn_options); + + // Set the upstream connections UDP socket receive buffer size. The operating system defaults + // are usually too small for QUIC. + envoy::config::core::v3::SocketOption* udp_rcv_buf_sock_opt = + base_cluster->mutable_upstream_bind_config()->add_socket_options(); + udp_rcv_buf_sock_opt->set_name(SO_RCVBUF); + udp_rcv_buf_sock_opt->set_level(SOL_SOCKET); + udp_rcv_buf_sock_opt->set_int_value(udp_socket_receive_buffer_size_); + // Only apply the socket option to the datagram socket. + udp_rcv_buf_sock_opt->mutable_type()->mutable_datagram(); + udp_rcv_buf_sock_opt->set_description( + absl::StrCat("UDP SO_RCVBUF = ", udp_socket_receive_buffer_size_, " bytes")); + + envoy::config::core::v3::SocketOption* udp_snd_buf_sock_opt = + base_cluster->mutable_upstream_bind_config()->add_socket_options(); + udp_snd_buf_sock_opt->set_name(SO_SNDBUF); + udp_snd_buf_sock_opt->set_level(SOL_SOCKET); + udp_snd_buf_sock_opt->set_int_value(udp_socket_send_buffer_size_); + // Only apply the socket option to the datagram socket. + udp_snd_buf_sock_opt->mutable_type()->mutable_datagram(); + udp_snd_buf_sock_opt->set_description( + absl::StrCat("UDP SO_SNDBUF = ", udp_socket_send_buffer_size_, " bytes")); + // Set the network service type on iOS, if supplied. +#if defined(__APPLE__) + if (ios_network_service_type_ > 0) { + envoy::config::core::v3::SocketOption* net_svc_sock_opt = + base_cluster->mutable_upstream_bind_config()->add_socket_options(); + net_svc_sock_opt->set_name(SO_NET_SERVICE_TYPE); + net_svc_sock_opt->set_level(SOL_SOCKET); + net_svc_sock_opt->set_int_value(ios_network_service_type_); + net_svc_sock_opt->set_description( + absl::StrCat("SO_NET_SERVICE_TYPE = ", ios_network_service_type_)); + } +#endif + for (const auto& socket_option : socket_options_) { + envoy::config::core::v3::SocketOption* sock_opt = + base_cluster->mutable_upstream_bind_config()->add_socket_options(); + sock_opt->CopyFrom(socket_option); + } + } + return absl::OkStatus(); +} + +void MobileEngineBuilder::addStatsInclusionStringMatchers() { + auto add_prefix = [this](const std::string& prefix) { + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_prefix(prefix); + addStatsInclusionPattern(std::move(matcher)); + }; + auto add_regex = [this](const std::string& regex) { + envoy::type::matcher::v3::StringMatcher matcher; + matcher.mutable_safe_regex()->set_regex(regex); + addStatsInclusionPattern(std::move(matcher)); + }; + add_prefix("cluster.base.upstream_rq_"); + add_prefix("cluster.stats.upstream_rq_"); + add_prefix("cluster.base.upstream_cx_"); + add_prefix("cluster.stats.upstream_cx_"); + add_regex("^cluster\\.base\\.http2\\.keepalive_timeout$"); + add_regex("^cluster\\.base\\.upstream_http3_broken$"); + add_regex("^cluster\\.stats\\.http2\\.keepalive_timeout$"); + add_prefix("http.hcm.downstream_rq_"); + add_prefix("http.hcm.decompressor."); + add_prefix("pulse."); + add_prefix("runtime.load_success"); + add_prefix("dns_cache"); + add_regex("^vhost\\.[\\w]+\\.vcluster\\.[\\w]+?\\.upstream_rq_(?:[12345]xx|[3-5][0-9][0-9]|retry|" + "total)"); + add_regex(".*quic_connection_close_error_code.*"); + add_regex(".*quic_reset_stream_error_code.*"); +} + +void MobileEngineBuilder::addWatchdog() { + envoy::config::bootstrap::v3::Watchdogs watchdog; + watchdog.mutable_main_thread_watchdog()->mutable_megamiss_timeout()->set_seconds(60); + watchdog.mutable_main_thread_watchdog()->mutable_miss_timeout()->set_seconds(60); + watchdog.mutable_worker_watchdog()->mutable_megamiss_timeout()->set_seconds(60); + watchdog.mutable_worker_watchdog()->mutable_miss_timeout()->set_seconds(60); + setWatchdog(std::move(watchdog)); +} + +absl::Status MobileEngineBuilder::configureNode(envoy::config::core::v3::Node* node) { + node->set_id(node_id_.empty() ? "envoy-mobile" : node_id_); + node->set_cluster("envoy-mobile"); + if (node_locality_ && !node_locality_->region.empty()) { + node->mutable_locality()->set_region(node_locality_->region); + node->mutable_locality()->set_zone(node_locality_->zone); + node->mutable_locality()->set_sub_zone(node_locality_->sub_zone); + } + if (node_metadata_.has_value()) { + *node->mutable_metadata() = *node_metadata_; + } + Protobuf::Struct& metadata = *node->mutable_metadata(); + (*metadata.mutable_fields())["app_id"].set_string_value(app_id_); + (*metadata.mutable_fields())["app_version"].set_string_value(app_version_); + (*metadata.mutable_fields())["device_os"].set_string_value(device_os_); + + return absl::OkStatus(); +} + +absl::Status MobileEngineBuilder::configXds(envoy::config::bootstrap::v3::Bootstrap* bootstrap) { + (void)bootstrap; +#ifdef ENVOY_MOBILE_XDS + if (xds_builder_) { + xds_builder_->build(*bootstrap); + } +#endif // ENVOY_MOBILE_XDS + return absl::OkStatus(); +} + +EngineSharedPtr MobileEngineBuilder::build() { + return Platform::EngineBuilderBase::build().value(); +} + +void MobileEngineBuilder::preRunSetup(InternalEngine* engine) { + engine->disableDnsRefreshOnNetworkChange(disable_dns_refresh_on_network_change_); + for (const auto& [name, store] : key_value_stores_) { + // TODO(goaway): This leaks, but it's tied to the life of the engine. + if (!Api::External::retrieveApi(name, true)) { + auto* api = new envoy_kv_store(); + *api = store->asEnvoyKeyValueStore(); + Envoy::Api::External::registerApi(name.c_str(), api); + } + } + + for (const auto& [name, accessor] : string_accessors_) { + // TODO(RyanTheOptimist): This leaks, but it's tied to the life of the engine. + if (!Api::External::retrieveApi(name, true)) { + auto* api = new envoy_string_accessor(); + *api = StringAccessor::asEnvoyStringAccessor(accessor); + Envoy::Api::External::registerApi(name.c_str(), api); + } + } + +#if defined(__APPLE__) + if (respect_system_proxy_settings_) { + registerAppleProxyResolver(proxy_settings_refresh_interval_secs_); + } +#endif +} + +void MobileEngineBuilder::postRunSetup(Engine* engine) { + if (enable_network_change_monitor_) { + engine->initializeNetworkChangeMonitor(); + } +} + +} // namespace Platform +} // namespace Envoy diff --git a/mobile/library/cc/mobile_engine_builder.h b/mobile/library/cc/mobile_engine_builder.h new file mode 100644 index 0000000000000..566020dd6585f --- /dev/null +++ b/mobile/library/cc/mobile_engine_builder.h @@ -0,0 +1,298 @@ +#pragma once + +#include +#include +#include +#include + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/core/v3/socket_option.pb.h" + +#include "source/common/protobuf/protobuf.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/types/optional.h" +#include "library/cc/engine.h" +#include "library/cc/engine_builder_base.h" +#include "library/cc/key_value_store.h" +#include "library/cc/string_accessor.h" +#include "library/common/engine_types.h" + +#include "library/cc/engine_builder_types.h" + +namespace Envoy { +namespace Platform { + +// The C++ Engine builder creates a structured bootstrap proto and modifies it through parameters +// set through the MobileEngineBuilder API calls to produce the Bootstrap config that the Engine is +// created from. +class MobileEngineBuilder : public Platform::EngineBuilderBase { +public: + MobileEngineBuilder(); + MobileEngineBuilder(MobileEngineBuilder&&) = default; + virtual ~MobileEngineBuilder() = default; + static std::string nativeNameToConfig(absl::string_view name); + + MobileEngineBuilder& addConnectTimeoutSeconds(int connect_timeout_seconds); + MobileEngineBuilder& addDnsRefreshSeconds(int dns_refresh_seconds); + MobileEngineBuilder& addDnsFailureRefreshSeconds(int base, int max); + MobileEngineBuilder& addDnsQueryTimeoutSeconds(int dns_query_timeout_seconds); + MobileEngineBuilder& setDisableDnsRefreshOnFailure(bool disable_dns_refresh_on_failure); + MobileEngineBuilder& + setDisableDnsRefreshOnNetworkChange(bool disable_dns_refresh_on_network_change); + + MobileEngineBuilder& addDnsMinRefreshSeconds(int dns_min_refresh_seconds); + MobileEngineBuilder& setDnsNumRetries(uint32_t dns_num_retries); + MobileEngineBuilder& setGetaddrinfoNumThreads(uint32_t num_threads); + MobileEngineBuilder& addMaxConnectionsPerHost(int max_connections_per_host); + MobileEngineBuilder& addH2ConnectionKeepaliveIdleIntervalMilliseconds( + int h2_connection_keepalive_idle_interval_milliseconds); + MobileEngineBuilder& + addH2ConnectionKeepaliveTimeoutSeconds(int h2_connection_keepalive_timeout_seconds); + MobileEngineBuilder& setAppVersion(std::string app_version); + MobileEngineBuilder& setAppId(std::string app_id); + MobileEngineBuilder& setDeviceOs(std::string app_id); + + MobileEngineBuilder& setPerTryIdleTimeoutSeconds(int per_try_idle_timeout_seconds); + MobileEngineBuilder& enableGzipDecompression(bool gzip_decompression_on); + MobileEngineBuilder& enableBrotliDecompression(bool brotli_decompression_on); + MobileEngineBuilder& enableSocketTagging(bool socket_tagging_on); + MobileEngineBuilder& enableHttp3(bool http3_on); + MobileEngineBuilder& enableEarlyData(bool early_data_on); + MobileEngineBuilder& enableScone(bool enable); + MobileEngineBuilder& addQuicConnectionOption(std::string option); + MobileEngineBuilder& addQuicClientConnectionOption(std::string option); + // Deprecated, use addQuicConnectionOption() instead. + MobileEngineBuilder& setHttp3ConnectionOptions(std::string options); + // Deprecated, use addQuicClientConnectionOption() instead. + MobileEngineBuilder& setHttp3ClientConnectionOptions(std::string options); + MobileEngineBuilder& addQuicHint(std::string host, int port); + MobileEngineBuilder& addQuicCanonicalSuffix(std::string suffix); + // 0 means port migration is disabled. + MobileEngineBuilder& setNumTimeoutsToTriggerPortMigration(int num_timeouts); + MobileEngineBuilder& enableInterfaceBinding(bool interface_binding_on); + MobileEngineBuilder& enableDrainPostDnsRefresh(bool drain_post_dns_refresh_on); + MobileEngineBuilder& setUdpSocketReceiveBufferSize(int32_t size); + MobileEngineBuilder& setUdpSocketSendBufferSize(int32_t size); + MobileEngineBuilder& enforceTrustChainVerification(bool trust_chain_verification_on); + MobileEngineBuilder& setUpstreamTlsSni(std::string sni); + MobileEngineBuilder& + enablePlatformCertificatesValidation(bool platform_certificates_validation_on); + // Overridden to turn off system proxying and platform certs validation while enabling worker + // thread. + MobileEngineBuilder& enableWorkerThread(bool use_worker_thread); + MobileEngineBuilder& setUseQuicPlatformPacketWriter(bool use_quic_platform_packet_writer); + // If called to enable QUIC connection migration, no need to call setUseQuicPlatformPacketWriter() + // separately. + MobileEngineBuilder& enableQuicConnectionMigration(bool quic_connection_migration_on); + MobileEngineBuilder& setMigrateIdleQuicConnection(bool migrate_idle_quic_connection); + // 0 means using the Envoy default 30s. + MobileEngineBuilder& + setMaxIdleTimeBeforeQuicMigrationSeconds(int max_idle_time_before_quic_migration); + // 0 means using the Envoy default 128s. + MobileEngineBuilder& setMaxTimeOnNonDefaultNetworkSeconds(int max_time_on_non_default_network); + + MobileEngineBuilder& enableDnsCache(bool dns_cache_on, int save_interval_seconds = 1); + // Set additional socket options on the upstream cluster outbound sockets. + MobileEngineBuilder& setAdditionalSocketOptions( + const std::vector& socket_options); + // Adds the hostnames that should be pre-resolved by DNS prior to the first request issued for + // that host. When invoked, any previous preresolve hostname entries get cleared and only the ones + // provided in the hostnames argument get set. + // TODO(abeyad): change this method and the other language APIs to take a {host,port} pair. + // E.g. addDnsPreresolveHost(std::string host, uint32_t port); + MobileEngineBuilder& addDnsPreresolveHostnames(const std::vector& hostnames); + MobileEngineBuilder& + setDnsResolver(const envoy::config::core::v3::TypedExtensionConfig& dns_resolver_config); + MobileEngineBuilder& addNativeFilter(std::string name, std::string typed_config); + MobileEngineBuilder& addNativeFilter(const std::string& name, const Protobuf::Any& typed_config); + + MobileEngineBuilder& addPlatformFilter(const std::string& name); + // Adds a runtime guard for the `envoy.reloadable_features.`. + // For example if the runtime guard is `envoy.reloadable_features.use_foo`, the guard name is + // `use_foo`. + MobileEngineBuilder& addRuntimeGuard(std::string guard, bool value); + + // These functions don't affect the Bootstrap configuration but instead perform registrations. + MobileEngineBuilder& addKeyValueStore(std::string name, KeyValueStoreSharedPtr key_value_store); + MobileEngineBuilder& addStringAccessor(std::string name, StringAccessorSharedPtr accessor); + + // Sets the thread priority of the Envoy main (network) thread. + // The value must be an integer between -20 (highest priority) and 19 (lowest priority). Values + // outside of this range will be ignored. + MobileEngineBuilder& setNetworkThreadPriority(int thread_priority); + + // Sets the QUIC connection idle timeout in seconds. + MobileEngineBuilder& + setQuicConnectionIdleTimeoutSeconds(int quic_connection_idle_timeout_seconds); + + // Sets the QUIC connection keepalive initial interval in nanoseconds + MobileEngineBuilder& setKeepAliveInitialIntervalMilliseconds(int keepalive_initial_interval_ms); + + // Sets the maximum number of concurrent streams on a multiplexed connection (HTTP/2 or HTTP/3). + MobileEngineBuilder& setMaxConcurrentStreams(int max_concurrent_streams); + + // Sets the node.id field in the Bootstrap configuration. + MobileEngineBuilder& setNodeId(std::string node_id); + // Sets the node.locality field in the Bootstrap configuration. + MobileEngineBuilder& setNodeLocality(std::string region, std::string zone, std::string sub_zone); + // Sets the node.metadata field in the Bootstrap configuration. + MobileEngineBuilder& setNodeMetadata(Protobuf::Struct node_metadata); + +#if defined(__APPLE__) + // If true, initialize the platform network change monitor to listen for network change events. + // Only takes effect on iOS, where it is required in order to enable the network change monitor. + // Defaults to false. + MobileEngineBuilder& enableNetworkChangeMonitor(bool network_change_monitor_on); +#endif + +#ifdef ENVOY_MOBILE_XDS + // Sets the xDS configuration for the Envoy Mobile engine. + // + // `xds_builder`: the XdsBuilder instance used to specify the xDS configuration options. + MobileEngineBuilder& setXds(XdsBuilder xds_builder); +#endif // ENVOY_MOBILE_XDS + +#if defined(__APPLE__) + // Right now, this API is only used by Apple (iOS) to register the Apple proxy resolver API for + // use in reading and using the system proxy settings. + // If/when we move Android system proxy registration to the C++ Engine Builder, we will make this + // API available on all platforms. + // The optional `refresh_interval_secs` parameter determines how often the system proxy settings + // are polled by the operating system; defaults to 10 seconds. If the value is <= 0, the default + // value will be used. + MobileEngineBuilder& respectSystemProxySettings(bool value, int refresh_interval_secs = 10); + MobileEngineBuilder& setIosNetworkServiceType(int ios_network_service_type); +#endif + // Overload to preserve the same return type as the EngineBuilder class. + EngineSharedPtr build(); + +private: + friend class Platform::EngineBuilderBase; + + // base class hooks + void preRunSetup(InternalEngine* engine); + void postRunSetup(Engine* engine); + absl::Status configXds(envoy::config::bootstrap::v3::Bootstrap* bootstrap); + void configureDnsCache( + envoy::extensions::common::dynamic_forward_proxy::v3::DnsCacheConfig* dns_cache_config) const; + absl::Status configureNode(envoy::config::core::v3::Node* node); + absl::Status configureRouteConfig(envoy::config::route::v3::RouteConfiguration* route_config); + absl::Status configureStaticClusters( + Protobuf::RepeatedPtrField* clusters); + absl::Status configureHttpFilters( + std::function + add_filter); + void configureCustomRouterFilter( + ::envoy::extensions::filters::http::router::v3::Router& router_config) { + (void)router_config; + } + + void addWatchdog(); + void addStatsInclusionStringMatchers(); + + struct NativeFilterConfig { + NativeFilterConfig(std::string name, std::string typed_config) + : name_(std::move(name)), textproto_typed_config_(std::move(typed_config)) {} + + NativeFilterConfig(const std::string& name, const Protobuf::Any& typed_config) + : name_(name), typed_config_(typed_config) {} + + std::string name_; + std::string textproto_typed_config_{}; + Protobuf::Any typed_config_{}; + }; + + int connect_timeout_seconds_ = 10; + int dns_refresh_seconds_ = 60; + int dns_failure_refresh_seconds_base_ = 2; + int dns_failure_refresh_seconds_max_ = 10; + int dns_query_timeout_seconds_ = 120; + bool disable_dns_refresh_on_failure_{false}; + bool disable_dns_refresh_on_network_change_{false}; + absl::optional dns_num_retries_ = 3; + uint32_t getaddrinfo_num_threads_ = 1; + int h2_connection_keepalive_idle_interval_milliseconds_ = 100000000; + int h2_connection_keepalive_timeout_seconds_ = 15; + std::string app_version_ = "unspecified"; + std::string app_id_ = "unspecified"; + std::string device_os_ = "unspecified"; + int per_try_idle_timeout_seconds_ = 15; + bool gzip_decompression_filter_ = true; + bool brotli_decompression_filter_ = false; + bool socket_tagging_filter_ = false; + bool platform_certificates_validation_on_ = false; + bool dns_cache_on_ = false; + int dns_cache_save_interval_seconds_ = 1; + absl::optional network_thread_priority_ = absl::nullopt; + + absl::flat_hash_map key_value_stores_{}; + + bool enable_interface_binding_ = false; + bool enable_drain_post_dns_refresh_ = false; + bool enforce_trust_chain_verification_ = true; + std::string upstream_tls_sni_; + bool enable_http3_ = true; + bool enable_early_data_{true}; + bool scone_enabled_ = false; + std::string http3_connection_options_ = ""; + std::string http3_client_connection_options_ = ""; + // EVMB is to distinguish Envoy Mobile client connections. + std::vector quic_connection_options_{"AKDU", "BWRS", "5RTO", "EVMB"}; + std::vector quic_client_connection_options_; + std::vector> quic_hints_; + std::vector quic_suffixes_; + int num_timeouts_to_trigger_port_migration_ = 0; +#if defined(__APPLE__) + bool respect_system_proxy_settings_ = true; + int proxy_settings_refresh_interval_secs_ = 10; + int ios_network_service_type_ = 0; +#endif + int dns_min_refresh_seconds_ = 60; + int max_connections_per_host_ = 7; + + std::vector native_filter_chain_; + std::vector> dns_preresolve_hostnames_; + absl::optional dns_resolver_config_; + std::vector socket_options_; + + absl::flat_hash_map string_accessors_; + + // This is the same value Cronet uses for QUIC: + // https://source.chromium.org/chromium/chromium/src/+/main:net/quic/quic_context.h;drc=ccfe61524368c94b138ddf96ae8121d7eb7096cf;l=87 + int32_t udp_socket_receive_buffer_size_ = 1024 * 1024; // 1MB + // This is the same value Cronet uses for QUIC: + // https://source.chromium.org/chromium/chromium/src/+/main:net/quic/quic_session_pool.cc;l=790-793;drc=7f04a8e033c23dede6beae129cd212e6d4473d72 + // https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/quic/core/quic_constants.h;l=43-47;drc=34ad7f3844f882baf3d31a6bc6e300acaa0e3fc8 + int32_t udp_socket_send_buffer_size_ = 1452 * 20; + // These are the same values Cronet uses for QUIC: + // https://source.chromium.org/chromium/chromium/src/+/main:net/quic/quic_context.cc;l=21-22;drc=6849bf6b37e96bd1c38a5f77f7deaa28b53779c4;bpv=1;bpt=1 + const uint32_t initial_stream_window_size_ = 6 * 1024 * 1024; // 6MB + const uint32_t initial_connection_window_size_ = 15 * 1024 * 1024; // 15MB + int quic_connection_idle_timeout_seconds_ = 60; + + int keepalive_initial_interval_ms_ = 0; + int max_concurrent_streams_ = 0; + bool use_quic_platform_packet_writer_ = false; + + // QUIC connection migration. + bool enable_quic_connection_migration_ = false; + bool migrate_idle_quic_connection_ = false; + int max_idle_time_before_quic_migration_seconds_ = 0; + int max_time_on_non_default_network_seconds_ = 0; + + std::string node_id_; + absl::optional node_locality_ = absl::nullopt; + absl::optional node_metadata_ = absl::nullopt; + + bool enable_network_change_monitor_ = false; +#ifdef ENVOY_MOBILE_XDS + absl::optional xds_builder_ = absl::nullopt; +#endif // ENVOY_MOBILE_XDS +}; + +using MobileEngineBuilderSharedPtr = std::shared_ptr; + +} // namespace Platform +} // namespace Envoy diff --git a/mobile/library/common/internal_engine.cc b/mobile/library/common/internal_engine.cc index 43be67845adba..3a3dd0468ef1f 100644 --- a/mobile/library/common/internal_engine.cc +++ b/mobile/library/common/internal_engine.cc @@ -79,16 +79,17 @@ bool areIpAddressesDifferent(const Network::Address::InstanceConstSharedPtr& add static std::atomic current_stream_handle_{0}; -InternalEngine::InternalEngine( - std::unique_ptr callbacks, std::unique_ptr logger, - std::unique_ptr event_tracker, absl::optional thread_priority, - absl::optional high_watermark, bool disable_dns_refresh_on_network_change, - Thread::PosixThreadFactoryPtr thread_factory, bool enable_logger, bool use_worker_thread) +InternalEngine::InternalEngine(std::unique_ptr callbacks, + std::unique_ptr logger, + std::unique_ptr event_tracker, + absl::optional thread_priority, + absl::optional high_watermark, + Thread::PosixThreadFactoryPtr thread_factory, bool enable_logger, + bool use_worker_thread) : thread_factory_(std::move(thread_factory)), callbacks_(std::move(callbacks)), logger_(std::move(logger)), event_tracker_(std::move(event_tracker)), thread_priority_(thread_priority), high_watermark_(high_watermark), main_dispatcher_(std::make_unique()), - disable_dns_refresh_on_network_change_(disable_dns_refresh_on_network_change), enable_logger_(enable_logger), use_worker_thread_(use_worker_thread), request_dispatcher_(std::make_unique()) { ExtensionRegistry::registerFactories(); @@ -100,12 +101,11 @@ InternalEngine::InternalEngine(std::unique_ptr callbacks, std::unique_ptr logger, std::unique_ptr event_tracker, absl::optional thread_priority, - absl::optional high_watermark, - bool disable_dns_refresh_on_network_change, bool enable_logger, + absl::optional high_watermark, bool enable_logger, bool use_worker_thread) : InternalEngine(std::move(callbacks), std::move(logger), std::move(event_tracker), - thread_priority, high_watermark, disable_dns_refresh_on_network_change, - Thread::PosixThreadFactory::create(), enable_logger, use_worker_thread) {} + thread_priority, high_watermark, Thread::PosixThreadFactory::create(), + enable_logger, use_worker_thread) {} envoy_stream_t InternalEngine::initStream() { return current_stream_handle_++; } diff --git a/mobile/library/common/internal_engine.h b/mobile/library/common/internal_engine.h index 826c639e9d1c2..9817a3ed33803 100644 --- a/mobile/library/common/internal_engine.h +++ b/mobile/library/common/internal_engine.h @@ -35,8 +35,7 @@ class InternalEngine : public Logger::Loggable { InternalEngine(std::unique_ptr callbacks, std::unique_ptr logger, std::unique_ptr event_tracker, absl::optional thread_priority = absl::nullopt, - absl::optional high_watermark = absl::nullopt, - bool disable_dns_refresh_on_network_change = false, bool enable_logger = true, + absl::optional high_watermark = absl::nullopt, bool enable_logger = true, bool use_worker_thread = false); /** @@ -205,6 +204,13 @@ class InternalEngine : public Logger::Loggable { */ Stats::Store& getStatsStore(); + /** + * Set whether to disable DNS refresh on network change events. + */ + void disableDnsRefreshOnNetworkChange(bool disable) { + disable_dns_refresh_on_network_change_ = disable; + } + private: // Needs access to the private constructor. GTEST_FRIEND_CLASS(InternalEngineTest, ThreadCreationFailed); @@ -212,7 +218,6 @@ class InternalEngine : public Logger::Loggable { InternalEngine(std::unique_ptr callbacks, std::unique_ptr logger, std::unique_ptr event_tracker, absl::optional thread_priority, absl::optional high_watermark, - bool disable_dns_refresh_on_network_change, Thread::PosixThreadFactoryPtr thread_factory, bool enable_logger = true, bool use_worker_thread = false); @@ -266,7 +271,7 @@ class InternalEngine : public Logger::Loggable { Thread::PosixThreadPtr main_thread_{nullptr}; // Empty placeholder to be populated later. bool terminated_{false}; absl::Notification engine_running_; - bool disable_dns_refresh_on_network_change_; + bool disable_dns_refresh_on_network_change_{false}; int prev_network_type_{0}; Network::Address::InstanceConstSharedPtr prev_local_addr_{nullptr}; bool enable_logger_{true}; diff --git a/mobile/library/jni/BUILD b/mobile/library/jni/BUILD index 78b0d8f9ee1d6..e53c05a2dda6e 100644 --- a/mobile/library/jni/BUILD +++ b/mobile/library/jni/BUILD @@ -77,7 +77,7 @@ envoy_cc_library( ":android_network_utility_lib", ":jni_init_lib", ":jni_utility_lib", - "//library/cc:engine_builder_lib", + "//library/cc:mobile_engine_builder_lib", "//library/common:internal_engine_lib", "//library/common/api:c_types", "//library/common/bridge:utility_lib", diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index f542ad286e78c..2ecbc999bfba9 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -3,7 +3,7 @@ #include "source/common/protobuf/protobuf.h" -#include "library/cc/engine_builder.h" +#include "library/cc/mobile_engine_builder.h" #include "library/common/api/c_types.h" #include "library/common/bridge/utility.h" #include "library/common/extensions/filters/http/platform_bridge/c_types.h" @@ -16,7 +16,7 @@ #include "library/jni/jni_init.h" #include "library/jni/jni_utility.h" -using Envoy::Platform::EngineBuilder; +using Envoy::Platform::MobileEngineBuilder; // NOLINT(namespace-envoy) @@ -107,10 +107,11 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr }; } - return reinterpret_cast( + auto* engine = new Envoy::InternalEngine(std::move(callbacks), std::move(logger), std::move(event_tracker), - /*network_thread_priority*/ absl::nullopt, - (disable_dns_refresh_on_network_change == JNI_TRUE))); + /*network_thread_priority*/ absl::nullopt); + engine->disableDnsRefreshOnNetworkChange(disable_dns_refresh_on_network_change == JNI_TRUE); + return reinterpret_cast(engine); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_runEngine( @@ -1066,8 +1067,8 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_registerStringAccessor(JNIEnv* // Takes a jstring from Java, converts it to a C++ string, calls the supplied // setter on it. -void setString(Envoy::JNI::JniHelper& jni_helper, jstring java_string, EngineBuilder* builder, - EngineBuilder& (EngineBuilder::*setter)(std::string)) { +void setString(Envoy::JNI::JniHelper& jni_helper, jstring java_string, MobileEngineBuilder* builder, + MobileEngineBuilder& (MobileEngineBuilder::*setter)(std::string)) { if (!java_string) { return; } @@ -1155,7 +1156,7 @@ void configureBuilder( jobjectArray runtime_guards, jlong h3_connection_keepalive_initial_interval_milliseconds, jboolean use_quic_platform_packet_writer, jboolean enable_connection_migration, jboolean migrate_idle_connection, jlong max_idle_time_before_migration_seconds, - jlong max_time_on_non_default_network_seconds, Envoy::Platform::EngineBuilder& builder) { + jlong max_time_on_non_default_network_seconds, MobileEngineBuilder& builder) { builder.addConnectTimeoutSeconds((connect_timeout_seconds)); builder.setDisableDnsRefreshOnFailure(disable_dns_refresh_on_failure); builder.setDisableDnsRefreshOnNetworkChange(disable_dns_refresh_on_network_change); @@ -1173,8 +1174,8 @@ void configureBuilder( (h2_connection_keepalive_idle_interval_milliseconds)); builder.addH2ConnectionKeepaliveTimeoutSeconds((h2_connection_keepalive_timeout_seconds)); - setString(jni_helper, app_version, &builder, &EngineBuilder::setAppVersion); - setString(jni_helper, app_id, &builder, &EngineBuilder::setAppId); + setString(jni_helper, app_version, &builder, &MobileEngineBuilder::setAppVersion); + setString(jni_helper, app_id, &builder, &MobileEngineBuilder::setAppId); builder.setDeviceOs("Android"); builder.setStreamIdleTimeoutSeconds((stream_idle_timeout_seconds)); @@ -1238,7 +1239,7 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_getNativeFilterConfig(JNIEnv* e jstring filter_name_jstr) { Envoy::JNI::JniHelper jni_helper(env); std::string filter_name = Envoy::JNI::javaStringToCppString(jni_helper, filter_name_jstr); - std::string filter_config = EngineBuilder::nativeNameToConfig(filter_name); + std::string filter_config = MobileEngineBuilder::nativeNameToConfig(filter_name); return jni_helper.newStringUtf(filter_config.c_str()).release(); } @@ -1265,7 +1266,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr jboolean migrate_idle_connection, jlong max_idle_time_before_migration_seconds, jlong max_time_on_non_default_network_seconds) { Envoy::JNI::JniHelper jni_helper(env); - Envoy::Platform::EngineBuilder builder; + MobileEngineBuilder builder; configureBuilder( jni_helper, connect_timeout_seconds, disable_dns_refresh_on_failure, @@ -1283,7 +1284,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr h3_connection_keepalive_initial_interval_milliseconds, use_quic_platform_packet_writer, enable_connection_migration, migrate_idle_connection, max_idle_time_before_migration_seconds, max_time_on_non_default_network_seconds, builder); - return reinterpret_cast(builder.generateBootstrap().release()); + return reinterpret_cast(builder.generateBootstrap().value().release()); } #if defined(__GNUC__) diff --git a/mobile/library/objective-c/BUILD b/mobile/library/objective-c/BUILD index 4e732b72972ef..e8c0e3fa4aa77 100644 --- a/mobile/library/objective-c/BUILD +++ b/mobile/library/objective-c/BUILD @@ -54,7 +54,7 @@ envoy_objc_library( ":envoy_key_value_store_bridge_impl_lib", ":envoy_key_value_store_lib", ":envoy_objc_bridge_lib", - "//library/cc:engine_builder_lib", + "//library/cc:mobile_engine_builder_lib", "//library/cc:network_change_monitor_interface", "//library/common:internal_engine_lib", "//library/common/api:c_types", diff --git a/mobile/library/objective-c/EnvoyConfiguration.mm b/mobile/library/objective-c/EnvoyConfiguration.mm index 2f6e830299df8..1e0aceeca2d99 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.mm +++ b/mobile/library/objective-c/EnvoyConfiguration.mm @@ -1,9 +1,11 @@ #import "library/objective-c/EnvoyEngine.h" -#import "library/cc/engine_builder.h" +#import "library/cc/mobile_engine_builder.h" #import "library/cc/direct_response_testing.h" #include "source/common/protobuf/utility.h" +using Envoy::Platform::MobileEngineBuilder; + @implementation NSString (CXX) - (std::string)toCXXString { return std::string([self UTF8String], @@ -154,8 +156,8 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds return self; } -- (Envoy::Platform::EngineBuilder)applyToCXXBuilder { - Envoy::Platform::EngineBuilder builder; +- (MobileEngineBuilder)applyToCXXBuilder { + MobileEngineBuilder builder; for (EnvoyNativeFilterConfig *nativeFilterConfig in [self.nativeFilterChain reverseObjectEnumerator]) { @@ -228,8 +230,8 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds - (std::unique_ptr)generateBootstrap { try { - Envoy::Platform::EngineBuilder builder = [self applyToCXXBuilder]; - return builder.generateBootstrap(); + MobileEngineBuilder builder = [self applyToCXXBuilder]; + return std::move(builder.generateBootstrap().value()); } catch (const std::exception &e) { NSLog(@"[Envoy] error generating bootstrap: %@", @(e.what())); return nullptr; diff --git a/mobile/library/objective-c/EnvoyEngineImpl.mm b/mobile/library/objective-c/EnvoyEngineImpl.mm index 330ac574e276e..2332142489509 100644 --- a/mobile/library/objective-c/EnvoyEngineImpl.mm +++ b/mobile/library/objective-c/EnvoyEngineImpl.mm @@ -8,7 +8,7 @@ #import "library/common/types/c_types.h" #import "library/common/extensions/key_value/platform/c_types.h" -#import "library/cc/engine_builder.h" +#import "library/cc/mobile_engine_builder.h" #import "library/cc/network_change_monitor.h" #import "library/common/internal_engine.h" #include "library/common/system/system_helper.h" diff --git a/mobile/test/cc/BUILD b/mobile/test/cc/BUILD index a962d0cb570e4..9b55ad92205e7 100644 --- a/mobile/test/cc/BUILD +++ b/mobile/test/cc/BUILD @@ -1,16 +1,26 @@ -load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_mobile_package") +load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_mobile_package") +load("//bazel:envoy_mobile_cc_test.bzl", "envoy_cc_test_with_engine_builder") licenses(["notice"]) # Apache 2 envoy_mobile_package() -envoy_cc_test( +envoy_cc_library( + name = "engine_builder_test_shim_lib", + hdrs = ["engine_builder_test_shim.h"], + repository = "@envoy", + visibility = ["//visibility:public"], + deps = [ + "//library/cc:engine_builder_lib", + ], +) + +envoy_cc_test_with_engine_builder( name = "engine_test", srcs = ["engine_test.cc"], env = {"ENVOY_NO_LOG_SINK": ""}, repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:engine_types_lib", "//test/common/integration:engine_with_test_server", "//test/common/integration:test_server_lib", diff --git a/mobile/test/cc/engine_builder_test_shim.h b/mobile/test/cc/engine_builder_test_shim.h new file mode 100644 index 0000000000000..82f6de0eb461a --- /dev/null +++ b/mobile/test/cc/engine_builder_test_shim.h @@ -0,0 +1,27 @@ +#pragma once + +#if defined(USE_MOBILE_ENGINE_BUILDER) + +#include "library/cc/mobile_engine_builder.h" + +namespace Envoy { +namespace Platform { +class EngineBuilder : public MobileEngineBuilder { +public: + using MobileEngineBuilder::MobileEngineBuilder; + + std::unique_ptr generateBootstrap() const { + auto* mutable_this = const_cast(this); + return mutable_this->Platform::EngineBuilderBase::generateBootstrap() + .value(); + } +}; +using EngineBuilderSharedPtr = std::shared_ptr; +} // namespace Platform +} // namespace Envoy + +#else + +#include "library/cc/engine_builder.h" + +#endif diff --git a/mobile/test/cc/engine_test.cc b/mobile/test/cc/engine_test.cc index 5beba1d965c04..071394d616745 100644 --- a/mobile/test/cc/engine_test.cc +++ b/mobile/test/cc/engine_test.cc @@ -5,7 +5,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/engine_types.h" #include "library/common/http/header_utility.h" diff --git a/mobile/test/cc/integration/BUILD b/mobile/test/cc/integration/BUILD index 26924f1466bc2..c796518726d5e 100644 --- a/mobile/test/cc/integration/BUILD +++ b/mobile/test/cc/integration/BUILD @@ -1,15 +1,15 @@ -load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_mobile_package") +load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_cc_test_library", "envoy_mobile_package") +load("//bazel:envoy_mobile_cc_test.bzl", "envoy_cc_test_with_engine_builder") licenses(["notice"]) # Apache 2 envoy_mobile_package() -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "send_headers_test", srcs = ["send_headers_test.cc"], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:engine_types_lib", "//library/common/http:header_utility_lib", "//test/common/http/filters/assertion:filter_cc_proto", @@ -19,12 +19,11 @@ envoy_cc_test( ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "send_trailers_test", srcs = ["send_trailers_test.cc"], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:engine_types_lib", "//library/common/http:header_utility_lib", "//test/common/http/filters/assertion:filter_cc_proto", @@ -34,12 +33,11 @@ envoy_cc_test( ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "send_data_test", srcs = ["send_data_test.cc"], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:engine_types_lib", "//library/common/http:header_utility_lib", "//test/common/http/filters/assertion:filter_cc_proto", @@ -49,12 +47,11 @@ envoy_cc_test( ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "receive_headers_test", srcs = ["receive_headers_test.cc"], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:engine_types_lib", "//library/common/http:header_utility_lib", "//test/common/integration:engine_with_test_server", @@ -63,12 +60,11 @@ envoy_cc_test( ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "receive_data_test", srcs = ["receive_data_test.cc"], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:engine_types_lib", "//library/common/http:header_utility_lib", "//test/common/integration:engine_with_test_server", @@ -77,12 +73,11 @@ envoy_cc_test( ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "receive_trailers_test", srcs = ["receive_trailers_test.cc"], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:engine_types_lib", "//library/common/http:header_utility_lib", "//test/common/integration:engine_with_test_server", @@ -91,12 +86,11 @@ envoy_cc_test( ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "lifetimes_test", srcs = ["lifetimes_test.cc"], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:engine_types_lib", "//library/common/http:header_utility_lib", "//test/common/integration:engine_with_test_server", @@ -104,3 +98,46 @@ envoy_cc_test( "@envoy_build_config//:test_extensions", ], ) + +envoy_cc_test_library( + name = "test_base_lib", + srcs = [ + "base/test_engine_and_server.cc", + ], + hdrs = [ + "base/test_engine_and_server.h", + "base/test_engine_builder.h", + ], + repository = "@envoy", + deps = [ + "//library/cc:engine_builder_base_lib", + "//library/cc:envoy_engine_cc_lib_no_stamp", + "//test/common/integration:test_server_lib", + "@abseil-cpp//absl/container:flat_hash_map", + "@abseil-cpp//absl/strings", + "@envoy//source/extensions/clusters/static:static_cluster_lib", + "@envoy//source/extensions/load_balancing_policies/round_robin:config", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", + "@envoy_api//envoy/config/route/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/http_11_proxy/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/raw_buffer/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/upstreams/http/v3:pkg_cc_proto", + ], +) + +envoy_cc_test( + name = "integration_test", + srcs = ["base/integration_test.cc"], + repository = "@envoy", + deps = [ + ":test_base_lib", + "//library/cc:envoy_engine_cc_lib_no_stamp", + "//test/common/http/filters/assertion:filter_cc_proto", + "@envoy//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + ], +) diff --git a/mobile/test/cc/integration/base/integration_test.cc b/mobile/test/cc/integration/base/integration_test.cc new file mode 100644 index 0000000000000..a07865c4bd759 --- /dev/null +++ b/mobile/test/cc/integration/base/integration_test.cc @@ -0,0 +1,177 @@ +#include +#include +#include +#include + +#include "test/cc/integration/base/test_engine_builder.h" +#include "test/cc/integration/base/test_engine_and_server.h" +#include "test/test_common/utility.h" +#include "library/cc/stream_prototype.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/notification.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" +#include "envoy/buffer/buffer.h" +#include "envoy/http/header_map.h" +#include "library/cc/stream.h" +#include "library/common/engine_types.h" +#include "library/common/http/header_utility.h" +#include "library/common/types/c_types.h" +#include "test/common/http/filters/assertion/filter.pb.h" +#include "test/common/integration/test_server.h" +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/base_logger.h" + +namespace Envoy { +namespace { + +class BaseEngineBuilderTest : public testing::TestWithParam { +protected: + BaseEngineBuilderTest() : server_type_(GetParam()) {} + + void + SetUpEngineAndServer(absl::optional + assertion_config = std::nullopt, + const absl::flat_hash_map& headers = {}, + absl::string_view body = "", + const absl::flat_hash_map& trailers = {}) { + absl::Notification engine_running; + Platform::TestEngineBuilder engine_builder; + + if (assertion_config.has_value()) { + ::envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter + assertion_filter; + assertion_filter.set_name("envoy.filters.http.assertion"); + assertion_filter.mutable_typed_config()->PackFrom(assertion_config.value()); + engine_builder.addHcmHttpFilter(std::move(assertion_filter)); + } + + engine_builder.enableLogger(false).setLogLevel(Logger::Logger::debug).setOnEngineRunning([&]() { + engine_running.Notify(); + }); + client_engine_with_test_server_ = std::make_unique( + engine_builder, server_type_, headers, body, trailers); + engine_running.WaitForNotification(); + } + + std::shared_ptr StartStream(Platform::StreamPrototypeSharedPtr stream_prototype, + bool end_stream = true) { + on_data_was_called_ = false; + on_headers_was_called_ = false; + on_trailers_was_called_ = false; + actual_status_code_ = ""; + actual_response_body_ = ""; + actual_trailer_value_ = ""; + actual_end_stream_ = false; + + EnvoyStreamCallbacks callbacks; + callbacks.on_headers_ = [this](const Http::ResponseHeaderMap& headers, bool end_stream, + envoy_stream_intel) { + on_headers_was_called_ = true; + actual_end_stream_ = end_stream; + auto status = headers.get(Http::LowerCaseString(":status")); + if (!status.empty()) { + actual_status_code_ = std::string(status[0]->value().getStringView()); + } + }; + callbacks.on_data_ = [this](const Buffer::Instance& data, uint64_t, bool end_stream, + envoy_stream_intel) { + on_data_was_called_ = true; + actual_end_stream_ = end_stream; + actual_response_body_ += data.toString(); + }; + callbacks.on_trailers_ = [this](const Http::ResponseTrailerMap& trailers, envoy_stream_intel) { + on_trailers_was_called_ = true; + actual_end_stream_ = true; + auto trailer = trailers.get(Http::LowerCaseString("test-trailer")); + if (!trailer.empty()) { + actual_trailer_value_ = std::string(trailer[0]->value().getStringView()); + } + }; + callbacks.on_complete_ = [this](envoy_stream_intel, envoy_final_stream_intel) { + stream_completed_.Notify(); + }; + + auto stream = stream_prototype->start(std::move(callbacks)); + + auto headers = std::make_unique(); + headers->addCopy(Http::LowerCaseString(":method"), "GET"); + headers->addCopy(Http::LowerCaseString(":scheme"), "http"); + headers->addCopy(Http::LowerCaseString(":authority"), + client_engine_with_test_server_->test_server().getAddress()); + headers->addCopy(Http::LowerCaseString(":path"), "/"); + stream->sendHeaders(std::move(headers), end_stream); + return stream; + } + + TestServerType server_type_; + std::unique_ptr client_engine_with_test_server_; + absl::Notification stream_completed_; + bool on_data_was_called_ = false; + bool on_headers_was_called_ = false; + bool on_trailers_was_called_ = false; + bool actual_end_stream_ = false; + std::string actual_status_code_; + std::string actual_trailer_value_; + std::string actual_response_body_; +}; + +INSTANTIATE_TEST_SUITE_P(BaseEngineBuilderTest, BaseEngineBuilderTest, + testing::Values(TestServerType::HTTP1_WITHOUT_TLS, + TestServerType::HTTP1_WITH_TLS, + TestServerType::HTTP2_WITH_TLS, TestServerType::HTTP3)); + +TEST_P(BaseEngineBuilderTest, GetRequest) { + SetUpEngineAndServer(/*assertion_config=*/std::nullopt, + /*headers=*/{{"test-header", "test-value"}}, + /*body=*/"test body"); + auto engine = client_engine_with_test_server_->engine(); + auto stream_prototype = engine->streamClient()->newStreamPrototype(); + StartStream(stream_prototype); + stream_completed_.WaitForNotification(); + + EXPECT_TRUE(on_headers_was_called_); + EXPECT_TRUE(on_data_was_called_); + EXPECT_TRUE(actual_end_stream_); + EXPECT_EQ(actual_status_code_, "200"); + EXPECT_EQ(actual_response_body_, "test body"); +} + +TEST_P(BaseEngineBuilderTest, GetRequestWithTrailers) { + SetUpEngineAndServer(/*assertion_config=*/std::nullopt, + /*headers=*/{{"test-header", "test-value"}}, + /*body=*/"test body", + /*trailers=*/{{"test-trailer", "test-trailer-value"}}); + auto engine = client_engine_with_test_server_->engine(); + auto stream_prototype = engine->streamClient()->newStreamPrototype(); + StartStream(stream_prototype); + stream_completed_.WaitForNotification(); + + EXPECT_TRUE(on_headers_was_called_); + EXPECT_TRUE(on_data_was_called_); + EXPECT_TRUE(on_trailers_was_called_); + EXPECT_TRUE(actual_end_stream_); + EXPECT_EQ(actual_status_code_, "200"); + EXPECT_EQ(actual_response_body_, "test body"); + EXPECT_EQ(actual_trailer_value_, "test-trailer-value"); +} + +TEST_P(BaseEngineBuilderTest, PostRequestWithBody) { + SetUpEngineAndServer(); + auto engine = client_engine_with_test_server_->engine(); + auto stream_prototype = engine->streamClient()->newStreamPrototype(); + auto stream = StartStream(stream_prototype, /*end_stream=*/false); + + auto buffer = std::make_unique("post body"); + stream->close(std::move(buffer)); + stream_completed_.WaitForNotification(); + + EXPECT_TRUE(on_headers_was_called_); + EXPECT_TRUE(on_data_was_called_); + EXPECT_TRUE(actual_end_stream_); + EXPECT_EQ(actual_status_code_, "200"); +} + +} // namespace +} // namespace Envoy diff --git a/mobile/test/cc/integration/base/test_engine_and_server.cc b/mobile/test/cc/integration/base/test_engine_and_server.cc new file mode 100644 index 0000000000000..b26e96554af5d --- /dev/null +++ b/mobile/test/cc/integration/base/test_engine_and_server.cc @@ -0,0 +1,121 @@ +#include "test/cc/integration/base/test_engine_and_server.h" + +#include +#include +#include + +#include "test/cc/integration/base/test_engine_builder.h" +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "absl/strings/str_cat.h" +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/config/endpoint/v3/endpoint_components.pb.h" +#include "envoy/config/route/v3/route.pb.h" +#include "envoy/config/route/v3/route_components.pb.h" +#include "envoy/extensions/transport_sockets/http_11_proxy/v3/upstream_http_11_connect.pb.h" +#include "envoy/extensions/transport_sockets/quic/v3/quic_transport.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/tls.pb.h" +#include "envoy/extensions/transport_sockets/raw_buffer/v3/raw_buffer.pb.h" +#include "envoy/extensions/upstreams/http/v3/http_protocol_options.pb.h" +#include "test/common/integration/test_server.h" +#include "source/common/common/assert.h" + +namespace Envoy { + +TestEngineAndServer::TestEngineAndServer( + Platform::TestEngineBuilder& engine_builder, TestServerType type, + const absl::flat_hash_map& headers, absl::string_view body, + const absl::flat_hash_map& trailers) { + test_server_.start(type); + test_server_.setResponse(headers, body, trailers); + + // Set up route configuration and cluster for ClientEngineBuilder to route + // requests to test_server_. + ::envoy::config::route::v3::RouteConfiguration route_configuration; + route_configuration.set_name("route_config"); + auto* virtual_host = route_configuration.add_virtual_hosts(); + virtual_host->set_name("test_virtual_host"); + virtual_host->add_domains("*"); + auto* route = virtual_host->add_routes(); + route->mutable_match()->set_prefix("/"); + route->mutable_route()->set_cluster("test_cluster"); + engine_builder.setHcmRouteConfiguration(std::move(route_configuration)); + + ::envoy::config::cluster::v3::Cluster cluster; + cluster.set_name("test_cluster"); + cluster.mutable_connect_timeout()->set_seconds(5); + auto* endpoint = + cluster.mutable_load_assignment()->add_endpoints()->add_lb_endpoints()->mutable_endpoint(); + endpoint->mutable_address()->mutable_socket_address()->set_address(test_server_.getIpAddress()); + endpoint->mutable_address()->mutable_socket_address()->set_port_value(test_server_.getPort()); + cluster.mutable_load_assignment()->set_cluster_name("test_cluster"); + cluster.set_type(::envoy::config::cluster::v3::Cluster::STATIC); + + bool use_tls = type == TestServerType::HTTP1_WITH_TLS || type == TestServerType::HTTP2_WITH_TLS || + type == TestServerType::HTTP3; + + ::envoy::extensions::upstreams::http::v3::HttpProtocolOptions protocol_options; + + if (use_tls) { + ::envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; + tls_context.mutable_common_tls_context() + ->mutable_validation_context() + ->set_trust_chain_verification(::envoy::extensions::transport_sockets::tls::v3:: + CertificateValidationContext::ACCEPT_UNTRUSTED); + tls_context.set_sni("www.lyft.com"); + + if (type == TestServerType::HTTP2_WITH_TLS) { + tls_context.mutable_common_tls_context()->add_alpn_protocols("h2"); + protocol_options.mutable_explicit_http_config()->mutable_http2_protocol_options(); + } else if (type == TestServerType::HTTP1_WITH_TLS) { + tls_context.mutable_common_tls_context()->add_alpn_protocols("http/1.1"); + protocol_options.mutable_explicit_http_config() + ->mutable_http_protocol_options() + ->set_enable_trailers(true); + } + cluster.mutable_transport_socket()->set_name("envoy.transport_sockets.tls"); + cluster.mutable_transport_socket()->mutable_typed_config()->PackFrom(tls_context); + + if (type == TestServerType::HTTP3) { + protocol_options.mutable_explicit_http_config()->mutable_http3_protocol_options(); + ::envoy::extensions::transport_sockets::quic::v3::QuicUpstreamTransport h3_inner_socket; + h3_inner_socket.mutable_upstream_tls_context()->CopyFrom(tls_context); + ::envoy::extensions::transport_sockets::http_11_proxy::v3::Http11ProxyUpstreamTransport + h3_proxy_socket; + h3_proxy_socket.mutable_transport_socket()->mutable_typed_config()->PackFrom(h3_inner_socket); + h3_proxy_socket.mutable_transport_socket()->set_name("envoy.transport_sockets.quic"); + cluster.mutable_transport_socket()->set_name("envoy.transport_sockets.http_11_proxy"); + cluster.mutable_transport_socket()->mutable_typed_config()->PackFrom(h3_proxy_socket); + } + } else { + cluster.mutable_transport_socket()->set_name("envoy.transport_sockets.raw_buffer"); + ::envoy::extensions::transport_sockets::raw_buffer::v3::RawBuffer raw_buffer; + cluster.mutable_transport_socket()->mutable_typed_config()->PackFrom(raw_buffer); + protocol_options.mutable_explicit_http_config() + ->mutable_http_protocol_options() + ->set_enable_trailers(true); + } + + (*cluster.mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] + .PackFrom(protocol_options); + + engine_builder.addCluster(std::move(cluster)); + + auto engine = engine_builder.build(); + RELEASE_ASSERT(engine.ok(), absl::StrCat("Failed to build engine: ", engine.status().message())); + engine_ = *std::move(engine); +} + +TestEngineAndServer::~TestEngineAndServer() { + test_server_.shutdown(); + if (engine_ != nullptr) { + engine_->terminate(); + } +} + +std::shared_ptr TestEngineAndServer::engine() { return engine_; } + +TestServer& TestEngineAndServer::test_server() { return test_server_; } + +} // namespace Envoy diff --git a/mobile/test/cc/integration/base/test_engine_and_server.h b/mobile/test/cc/integration/base/test_engine_and_server.h new file mode 100644 index 0000000000000..8042d5af84299 --- /dev/null +++ b/mobile/test/cc/integration/base/test_engine_and_server.h @@ -0,0 +1,37 @@ +#pragma once + +#ifndef TEST_CC_INTEGRATION_BASE_TEST_ENGINE_AND_SERVER_H_ +#define TEST_CC_INTEGRATION_BASE_TEST_ENGINE_AND_SERVER_H_ + +#include +#include + +#include "test/cc/integration/base/test_engine_builder.h" +#include "library/cc/engine.h" +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "test/common/integration/test_server.h" + +namespace Envoy { + +// Test only class. +class TestEngineAndServer { +public: + TestEngineAndServer(Platform::TestEngineBuilder& engine_builder, TestServerType type, + const absl::flat_hash_map& headers = {}, + absl::string_view body = "", + const absl::flat_hash_map& trailers = {}); + ~TestEngineAndServer(); + + std::shared_ptr engine(); + + TestServer& test_server(); + +private: + std::shared_ptr engine_; + TestServer test_server_; +}; + +} // namespace Envoy + +#endif // TEST_CC_INTEGRATION_BASE_TEST_ENGINE_AND_SERVER_H_ diff --git a/mobile/test/cc/integration/base/test_engine_builder.h b/mobile/test/cc/integration/base/test_engine_builder.h new file mode 100644 index 0000000000000..a6c5a06c4840c --- /dev/null +++ b/mobile/test/cc/integration/base/test_engine_builder.h @@ -0,0 +1,86 @@ +#pragma once + +#include "library/cc/engine_builder_base.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" + +#include "source/extensions/clusters/static/static_cluster.h" +#include "source/extensions/load_balancing_policies/round_robin/config.h" + +namespace Envoy { +namespace Platform { + +class TestEngineBuilder : public EngineBuilderBase { + friend class EngineBuilderBase; + +public: + TestEngineBuilder() = default; + + TestEngineBuilder& addHcmHttpFilter( + ::envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter filter) { + hcm_http_filters_.push_back(std::move(filter)); + return *this; + } + + TestEngineBuilder& + setHcmRouteConfiguration(::envoy::config::route::v3::RouteConfiguration route_configuration) { + route_configuration_ = std::move(route_configuration); + return *this; + } + + TestEngineBuilder& addCluster(::envoy::config::cluster::v3::Cluster cluster) { + clusters_.push_back(std::move(cluster)); + return *this; + } + +private: + // Hooks required by EngineBuilderBase + void preRunSetup(InternalEngine* engine) { + (void)engine; + Envoy::Upstream::forceRegisterStaticClusterFactory(); + Envoy::Extensions::LoadBalancingPolicies::RoundRobin::forceRegisterFactory(); + } + void postRunSetup(Engine*) {} + + absl::Status configXds(envoy::config::bootstrap::v3::Bootstrap*) { return absl::OkStatus(); } + absl::Status configureNode(envoy::config::core::v3::Node* node) { + node->set_id("envoy-test-client"); + node->set_cluster("envoy-test-client"); + return absl::OkStatus(); + } + + absl::Status configureRouteConfig(envoy::config::route::v3::RouteConfiguration* route_config) { + if (route_configuration_.has_value()) { + route_config->Swap(&route_configuration_.value()); + } + return absl::OkStatus(); + } + + absl::Status configureStaticClusters( + Protobuf::RepeatedPtrField* clusters) { + for (auto& cluster : clusters_) { + *clusters->Add() = std::move(cluster); + } + clusters_.clear(); + return absl::OkStatus(); + } + + absl::Status configureHttpFilters( + std::function + add_filter) { + for (auto& filter : hcm_http_filters_) { + *add_filter() = std::move(filter); + } + hcm_http_filters_.clear(); + return absl::OkStatus(); + } + + void configureCustomRouterFilter(::envoy::extensions::filters::http::router::v3::Router&) {} + + std::vector<::envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter> + hcm_http_filters_; + absl::optional<::envoy::config::route::v3::RouteConfiguration> route_configuration_; + std::vector<::envoy::config::cluster::v3::Cluster> clusters_; +}; + +} // namespace Platform +} // namespace Envoy diff --git a/mobile/test/cc/integration/lifetimes_test.cc b/mobile/test/cc/integration/lifetimes_test.cc index 1c15661060779..3fd5f27e5726e 100644 --- a/mobile/test/cc/integration/lifetimes_test.cc +++ b/mobile/test/cc/integration/lifetimes_test.cc @@ -3,7 +3,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/engine_types.h" #include "library/common/http/header_utility.h" diff --git a/mobile/test/cc/integration/receive_data_test.cc b/mobile/test/cc/integration/receive_data_test.cc index 8301b8b6baad4..aac34c744a81e 100644 --- a/mobile/test/cc/integration/receive_data_test.cc +++ b/mobile/test/cc/integration/receive_data_test.cc @@ -3,7 +3,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/engine_types.h" #include "library/common/http/header_utility.h" diff --git a/mobile/test/cc/integration/receive_headers_test.cc b/mobile/test/cc/integration/receive_headers_test.cc index c124f31ae1ba1..7e8f341c60b87 100644 --- a/mobile/test/cc/integration/receive_headers_test.cc +++ b/mobile/test/cc/integration/receive_headers_test.cc @@ -3,7 +3,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/engine_types.h" #include "library/common/http/header_utility.h" diff --git a/mobile/test/cc/integration/receive_trailers_test.cc b/mobile/test/cc/integration/receive_trailers_test.cc index c7383c47744f4..cb67bb10be232 100644 --- a/mobile/test/cc/integration/receive_trailers_test.cc +++ b/mobile/test/cc/integration/receive_trailers_test.cc @@ -3,7 +3,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/engine_types.h" #include "library/common/http/header_utility.h" diff --git a/mobile/test/cc/integration/send_data_test.cc b/mobile/test/cc/integration/send_data_test.cc index b20723794bd46..f243064ee3183 100644 --- a/mobile/test/cc/integration/send_data_test.cc +++ b/mobile/test/cc/integration/send_data_test.cc @@ -4,7 +4,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/engine_types.h" #include "library/common/http/header_utility.h" diff --git a/mobile/test/cc/integration/send_headers_test.cc b/mobile/test/cc/integration/send_headers_test.cc index 25bdf2c623456..b1d4a02bb2037 100644 --- a/mobile/test/cc/integration/send_headers_test.cc +++ b/mobile/test/cc/integration/send_headers_test.cc @@ -4,7 +4,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/engine_types.h" #include "library/common/http/header_utility.h" diff --git a/mobile/test/cc/integration/send_trailers_test.cc b/mobile/test/cc/integration/send_trailers_test.cc index bcf75c9134ad6..a50ed70da1761 100644 --- a/mobile/test/cc/integration/send_trailers_test.cc +++ b/mobile/test/cc/integration/send_trailers_test.cc @@ -4,7 +4,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/engine_types.h" #include "library/common/http/header_utility.h" diff --git a/mobile/test/cc/unit/BUILD b/mobile/test/cc/unit/BUILD index 988e3849e47b8..bf3ed7022cb27 100644 --- a/mobile/test/cc/unit/BUILD +++ b/mobile/test/cc/unit/BUILD @@ -1,10 +1,11 @@ -load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_mobile_package") +load("@envoy//bazel:envoy_build_system.bzl", "envoy_mobile_package") +load("//bazel:envoy_mobile_cc_test.bzl", "envoy_cc_test_with_engine_builder") licenses(["notice"]) # Apache 2 envoy_mobile_package() -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "envoy_config_test", srcs = ["envoy_config_test.cc"], linkopts = select({ @@ -17,7 +18,6 @@ envoy_cc_test( }), repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/cc:envoy_engine_cc_lib_no_stamp", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -28,7 +28,7 @@ envoy_cc_test( ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "fetch_client_test", srcs = ["fetch_client_test.cc"], exec_properties = { @@ -36,18 +36,16 @@ envoy_cc_test( }, repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "@envoy_build_config//:extension_registry", "@envoy_build_config//:test_extensions", ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "engine_handle_test", srcs = ["engine_handle_test.cc"], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:internal_engine_lib_no_stamp", "@envoy_build_config//:extension_registry", "@envoy_build_config//:test_extensions", diff --git a/mobile/test/cc/unit/engine_handle_test.cc b/mobile/test/cc/unit/engine_handle_test.cc index e18040674bbae..ea972767f6dc9 100644 --- a/mobile/test/cc/unit/engine_handle_test.cc +++ b/mobile/test/cc/unit/engine_handle_test.cc @@ -1,6 +1,6 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/internal_engine.h" namespace Envoy { diff --git a/mobile/test/cc/unit/envoy_config_test.cc b/mobile/test/cc/unit/envoy_config_test.cc index a7fb65be1e7b2..dbe237310ff6b 100644 --- a/mobile/test/cc/unit/envoy_config_test.cc +++ b/mobile/test/cc/unit/envoy_config_test.cc @@ -14,7 +14,7 @@ #include "absl/strings/str_replace.h" #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/api/external.h" #include "library/common/bridge//utility.h" diff --git a/mobile/test/cc/unit/fetch_client_test.cc b/mobile/test/cc/unit/fetch_client_test.cc index 6adc63ca5415d..719514bfa5772 100644 --- a/mobile/test/cc/unit/fetch_client_test.cc +++ b/mobile/test/cc/unit/fetch_client_test.cc @@ -8,7 +8,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" #include "library/cc/engine.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/cc/stream.h" #include "library/cc/stream_client.h" #include "library/cc/stream_prototype.h" diff --git a/mobile/test/common/BUILD b/mobile/test/common/BUILD index 5e42141543206..e977977e12ff9 100644 --- a/mobile/test/common/BUILD +++ b/mobile/test/common/BUILD @@ -1,26 +1,25 @@ -load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_mobile_package") +load("@envoy//bazel:envoy_build_system.bzl", "envoy_mobile_package") +load("//bazel:envoy_mobile_cc_test.bzl", "envoy_cc_test_with_engine_builder") licenses(["notice"]) # Apache 2 envoy_mobile_package() -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "engine_common_test", srcs = ["engine_common_test.cc"], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:engine_common_lib", "@envoy_build_config//:extension_registry", ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "internal_engine_test", srcs = ["internal_engine_test.cc"], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common:internal_engine_lib_no_stamp", "//library/common/bridge:utility_lib", "//library/common/http:header_utility_lib", diff --git a/mobile/test/common/engine_common_test.cc b/mobile/test/common/engine_common_test.cc index 6e2f1737c66b9..b4dff6b6897f1 100644 --- a/mobile/test/common/engine_common_test.cc +++ b/mobile/test/common/engine_common_test.cc @@ -1,6 +1,6 @@ #include "extension_registry.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/engine_common.h" namespace Envoy { diff --git a/mobile/test/common/integration/BUILD b/mobile/test/common/integration/BUILD index 55fb8c9890b78..647da5a551aac 100644 --- a/mobile/test/common/integration/BUILD +++ b/mobile/test/common/integration/BUILD @@ -1,17 +1,21 @@ load( "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_test", "envoy_cc_test_library", "envoy_mobile_package", "envoy_select_envoy_mobile_xds", "envoy_select_signal_trace", ) +load( + "//bazel:envoy_mobile_cc_test.bzl", + "envoy_cc_test_library_with_engine_builder", + "envoy_cc_test_with_engine_builder", +) licenses(["notice"]) # Apache 2 envoy_mobile_package() -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "client_integration_test", srcs = ["client_integration_test.cc"], linkopts = select({ @@ -45,7 +49,7 @@ envoy_cc_test( ], ) -envoy_cc_test_library( +envoy_cc_test_library_with_engine_builder( name = "base_client_integration_test_lib", srcs = [ "base_client_integration_test.cc", @@ -55,7 +59,6 @@ envoy_cc_test_library( ], repository = "@envoy", deps = [ - "//library/cc:engine_builder_lib", "//library/common/http:client_lib", "//library/common/http:header_utility_lib", "//library/common/network:network_types_lib", @@ -109,7 +112,7 @@ envoy_cc_test_library( ], ) -envoy_cc_test_library( +envoy_cc_test_library_with_engine_builder( name = "engine_with_test_server", srcs = [ "engine_with_test_server.cc", @@ -120,11 +123,10 @@ envoy_cc_test_library( repository = "@envoy", deps = [ ":test_server_lib", - "//library/cc:engine_builder_lib", ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "rtds_integration_test", srcs = envoy_select_envoy_mobile_xds( ["rtds_integration_test.cc"], @@ -147,7 +149,7 @@ envoy_cc_test( ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "cds_integration_test", srcs = envoy_select_envoy_mobile_xds( ["cds_integration_test.cc"], @@ -169,7 +171,7 @@ envoy_cc_test( ], ) -envoy_cc_test( +envoy_cc_test_with_engine_builder( name = "sds_integration_test", srcs = envoy_select_envoy_mobile_xds( ["sds_integration_test.cc"], @@ -194,7 +196,7 @@ envoy_cc_test( ], ) -envoy_cc_test_library( +envoy_cc_test_library_with_engine_builder( name = "xds_integration_test_lib", srcs = [ "xds_integration_test.cc", @@ -225,7 +227,6 @@ envoy_cc_test_library( ], repository = "@envoy", deps = [ - ":base_client_integration_test_lib", "@envoy//source/common/event:libevent_lib", "@envoy//source/common/tls:context_config_lib", "@envoy//source/common/tls:context_lib", diff --git a/mobile/test/common/integration/base_client_integration_test.h b/mobile/test/common/integration/base_client_integration_test.h index 7f83fb135ecb9..eb3ee9ad6601c 100644 --- a/mobile/test/common/integration/base_client_integration_test.h +++ b/mobile/test/common/integration/base_client_integration_test.h @@ -2,7 +2,7 @@ #include "test/integration/integration.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/cc/stream.h" #include "library/cc/stream_prototype.h" #include "library/common/http/client.h" diff --git a/mobile/test/common/integration/engine_with_test_server.h b/mobile/test/common/integration/engine_with_test_server.h index e3e066080fdd3..0b5965378c105 100644 --- a/mobile/test/common/integration/engine_with_test_server.h +++ b/mobile/test/common/integration/engine_with_test_server.h @@ -2,7 +2,7 @@ #include "test/common/integration/test_server.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" namespace Envoy { diff --git a/mobile/test/common/internal_engine_test.cc b/mobile/test/common/internal_engine_test.cc index ac65ddb30c085..798c48bdcbe1b 100644 --- a/mobile/test/common/internal_engine_test.cc +++ b/mobile/test/common/internal_engine_test.cc @@ -14,7 +14,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine_builder.h" +#include "test/cc/engine_builder_test_shim.h" #include "library/common/api/external.h" #include "library/common/bridge//utility.h" #include "library/common/http/header_utility.h" @@ -445,9 +445,9 @@ TEST_F(InternalEngineTest, ThreadCreationFailed) { EngineTestContext test_context{}; auto thread_factory = std::make_unique(); EXPECT_CALL(*thread_factory, createThread(_, _, false)).WillOnce(Return(ByMove(nullptr))); - std::unique_ptr engine( - new InternalEngine(createDefaultEngineCallbacks(test_context), {}, {}, {}, {}, false, - std::move(thread_factory))); + std::unique_ptr engine(new InternalEngine( + createDefaultEngineCallbacks(test_context), {}, {}, {}, {}, std::move(thread_factory))); + engine->disableDnsRefreshOnNetworkChange(false); Platform::EngineBuilder builder; envoy_status_t status = runEngine(engine, builder, LOG_LEVEL); EXPECT_EQ(status, ENVOY_FAILURE); From e7c165e6df8f64d5b837cbafb19c8c4c4e77cc5b Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Thu, 7 May 2026 22:25:53 +0200 Subject: [PATCH 258/750] dynamic_modules: replace `from_utf8_unchecked` and bare `from_raw_parts` UB at FFI seam (#44850) ## Description Replaces all unsafe `std::slice::from_raw_parts` and `std::str::from_utf8_unchecked` call sites at the C-Rust FFI boundary with safe wrappers in a new `ffi_helpers` module. The wrappers null-guard FFI inputs and lossy-decode invalid UTF-8 instead of UB, and emit a structured error log on contract violation. This closes the `(nullptr, 0)` UB class for both `*const u8` byte buffers and `*const T` struct arrays such as `*const EnvoyBuffer` and `*const (EnvoyBuffer, EnvoyBuffer)` header pairs. --- **Commit Message:** dynamic_modules: replace `from_utf8_unchecked` and bare `from_raw_parts` UB at FFI seam **Additional Description:** Replaced `from_utf8_unchecked` and bare `from_raw_parts` UB at FFI seam **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** Added **Release Notes:** N/A Signed-off-by: Rohit Agrawal --- .../sdk/rust/src/access_log.rs | 17 +- .../dynamic_modules/sdk/rust/src/bootstrap.rs | 89 ++-- .../dynamic_modules/sdk/rust/src/buffer.rs | 11 + .../sdk/rust/src/cert_validator.rs | 28 +- .../dynamic_modules/sdk/rust/src/cluster.rs | 113 ++--- .../sdk/rust/src/dns_resolver.rs | 30 +- .../sdk/rust/src/ffi_helpers.rs | 211 +++++++++ .../dynamic_modules/sdk/rust/src/http.rs | 94 ++-- .../dynamic_modules/sdk/rust/src/lib.rs | 9 +- .../dynamic_modules/sdk/rust/src/lib_test.rs | 414 ++++++++++++++++++ .../dynamic_modules/sdk/rust/src/listener.rs | 112 ++--- .../sdk/rust/src/load_balancer.rs | 99 ++--- .../dynamic_modules/sdk/rust/src/matcher.rs | 23 +- .../dynamic_modules/sdk/rust/src/network.rs | 120 ++--- .../dynamic_modules/sdk/rust/src/tracer.rs | 43 +- .../sdk/rust/src/transport_socket.rs | 13 +- .../sdk/rust/src/udp_listener.rs | 32 +- .../sdk/rust/src/upstream_http_tcp_bridge.rs | 25 +- 18 files changed, 1001 insertions(+), 482 deletions(-) create mode 100644 source/extensions/dynamic_modules/sdk/rust/src/ffi_helpers.rs diff --git a/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs b/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs index 66f7e7df71d44..cc3e4bbd0c3d0 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/access_log.rs @@ -1278,18 +1278,19 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_access_logger_config_new( config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> *const c_void { catch_unwind(AssertUnwindSafe(|| { - // The name and config originate from protobuf string/bytes fields, so they are valid - // UTF-8 and within bounds when received here. Mirrors the http filter FFI entry point. - let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )); - let config_bytes = std::slice::from_raw_parts(config.ptr as *const _, config.length); + // SAFETY: `name` is a protobuf string (UTF-8 by contract) and `config` is opaque bytes. + // The helpers additionally tolerate `(nullptr, 0)` empty inputs, and `str_lossy_from_raw` + // substitutes `U+FFFD` for any malformed UTF-8 rather than triggering UB. + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_bytes = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) + }; let ctx = ConfigContext::new(config_envoy_ptr); envoy_dynamic_module_on_access_logger_config_new_impl( &ctx, - name_str, + name_str.as_ref(), config_bytes, crate::NEW_ACCESS_LOGGER_CONFIG_FUNCTION .get() diff --git a/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs b/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs index c639d7197c8ad..8c639bcffbdac 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs @@ -1241,7 +1241,8 @@ impl EnvoyBootstrapExtension for EnvoyBootstrapExtensionImpl { if wrapper.stopped { return abi::envoy_dynamic_module_type_stats_iteration_action::Stop; } - let name_slice = unsafe { std::slice::from_raw_parts(name.ptr as *const u8, name.length) }; + let name_slice = + unsafe { crate::ffi_helpers::slice_from_raw_or_empty(name.ptr as *const u8, name.length) }; let name_str = std::str::from_utf8(name_slice).unwrap_or(""); if (wrapper.callback)(name_str, value) { abi::envoy_dynamic_module_type_stats_iteration_action::Continue @@ -1280,7 +1281,8 @@ impl EnvoyBootstrapExtension for EnvoyBootstrapExtensionImpl { if wrapper.stopped { return abi::envoy_dynamic_module_type_stats_iteration_action::Stop; } - let name_slice = unsafe { std::slice::from_raw_parts(name.ptr as *const u8, name.length) }; + let name_slice = + unsafe { crate::ffi_helpers::slice_from_raw_or_empty(name.ptr as *const u8, name.length) }; let name_str = std::str::from_utf8(name_slice).unwrap_or(""); if (wrapper.callback)(name_str, value) { abi::envoy_dynamic_module_type_stats_iteration_action::Continue @@ -1315,16 +1317,14 @@ pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_config_new( catch_unwind(AssertUnwindSafe(|| { let mut envoy_extension_config = EnvoyBootstrapExtensionConfigImpl::new(envoy_extension_config_ptr); - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; init_bootstrap_extension_config( &mut envoy_extension_config, - name_str, + name_str.as_ref(), config_slice, NEW_BOOTSTRAP_EXTENSION_CONFIG_FUNCTION .get() @@ -1539,14 +1539,20 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_http_callou let headers = if headers_size > 0 { Some(unsafe { - std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) + crate::ffi_helpers::slice_from_raw_or_empty( + headers as *const (EnvoyBuffer, EnvoyBuffer), + headers_size, + ) }) } else { None }; let body = if body_chunks_size > 0 { Some(unsafe { - std::slice::from_raw_parts(body_chunks as *const EnvoyBuffer, body_chunks_size) + crate::ffi_helpers::slice_from_raw_or_empty( + body_chunks as *const EnvoyBuffer, + body_chunks_size, + ) }) } else { None @@ -1607,16 +1613,12 @@ pub extern "C" fn envoy_dynamic_module_on_bootstrap_extension_file_changed( let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; let extension_config = unsafe { &**extension_config }; - let path_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - path.ptr as *const u8, - path.length, - )) - }; + let path_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(path.ptr as *const u8, path.length) }; extension_config.on_file_changed( &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - path_str, + path_str.as_ref(), events, ); })) @@ -1651,24 +1653,17 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_admin_reque let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig; let extension_config = unsafe { &**extension_config }; - let method_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - method.ptr as *const u8, - method.length, - )) - }; - let path_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - path.ptr as *const u8, - path.length, - )) - }; - let body_slice = unsafe { std::slice::from_raw_parts(body.ptr as *const u8, body.length) }; + let method_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(method.ptr as *const u8, method.length) }; + let path_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(path.ptr as *const u8, path.length) }; + let body_slice = + unsafe { crate::ffi_helpers::slice_from_raw_or_empty(body.ptr as *const u8, body.length) }; let (status_code, response_str) = extension_config.on_admin_request( &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - method_str, - path_str, + method_str.as_ref(), + path_str.as_ref(), body_slice, ); @@ -1714,15 +1709,12 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_cluster_add let extension_config = unsafe { &**extension_config }; let cluster_name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - cluster_name.ptr as *const u8, - cluster_name.length, - )) + crate::ffi_helpers::str_lossy_from_raw(cluster_name.ptr as *const u8, cluster_name.length) }; extension_config.on_cluster_add_or_update( &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - cluster_name_str, + cluster_name_str.as_ref(), ); })) .map_err(|panic| { @@ -1750,15 +1742,12 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_cluster_rem let extension_config = unsafe { &**extension_config }; let cluster_name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - cluster_name.ptr as *const u8, - cluster_name.length, - )) + crate::ffi_helpers::str_lossy_from_raw(cluster_name.ptr as *const u8, cluster_name.length) }; extension_config.on_cluster_removal( &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - cluster_name_str, + cluster_name_str.as_ref(), ); })) .map_err(|panic| { @@ -1786,15 +1775,12 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_listener_ad let extension_config = unsafe { &**extension_config }; let listener_name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - listener_name.ptr as *const u8, - listener_name.length, - )) + crate::ffi_helpers::str_lossy_from_raw(listener_name.ptr as *const u8, listener_name.length) }; extension_config.on_listener_add_or_update( &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - listener_name_str, + listener_name_str.as_ref(), ); })) .map_err(|panic| { @@ -1822,15 +1808,12 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_listener_re let extension_config = unsafe { &**extension_config }; let listener_name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - listener_name.ptr as *const u8, - listener_name.length, - )) + crate::ffi_helpers::str_lossy_from_raw(listener_name.ptr as *const u8, listener_name.length) }; extension_config.on_listener_removal( &mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr), - listener_name_str, + listener_name_str.as_ref(), ); })) .map_err(|panic| { diff --git a/source/extensions/dynamic_modules/sdk/rust/src/buffer.rs b/source/extensions/dynamic_modules/sdk/rust/src/buffer.rs index dafb61fddab0a..ae71d02590e61 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/buffer.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/buffer.rs @@ -52,9 +52,15 @@ impl EnvoyBuffer<'_> { } pub fn as_slice(&self) -> &[u8] { + // The null guard is inlined here rather than going through `ffi_helpers` so that this + // method does not transitively reference `envoy_log_*`. `as_slice` is reached by SDK + // doc tests, which compile without `#[cfg(test)]` and so cannot link the host-provided + // `envoy_dynamic_module_callback_log` symbols that the logging macros expand to. if self.raw_ptr.is_null() { return &[]; } + // Safety: caller invariant from `new` / `new_from_raw` that `(raw_ptr, length)` describes + // a live region of `length` bytes for the buffer's lifetime. unsafe { std::slice::from_raw_parts(self.raw_ptr, self.length) } } } @@ -107,17 +113,22 @@ impl EnvoyMutBuffer<'_> { /// This returns an immutable slice to the underlying memory region managed by Envoy. pub fn as_slice(&self) -> &[u8] { + // See `EnvoyBuffer::as_slice` for why the null guard is inlined here. if self.raw_ptr.is_null() { return &[]; } + // Safety: caller invariant from `new` / `new_from_raw`. unsafe { std::slice::from_raw_parts(self.raw_ptr, self.length) } } /// This returns a mutable slice to the underlying memory region managed by Envoy. pub fn as_mut_slice(&mut self) -> &mut [u8] { + // See `EnvoyBuffer::as_slice` for why the null guard is inlined here. if self.raw_ptr.is_null() { return &mut []; } + // Safety: caller invariant from `new` / `new_from_raw`, plus exclusive borrow on + // the underlying memory for the duration of the returned slice. unsafe { std::slice::from_raw_parts_mut(self.raw_ptr, self.length) } } } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs b/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs index 1eb0512f92db2..030a529b305b2 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/cert_validator.rs @@ -255,13 +255,13 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_config_new( config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_cert_validator_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { - let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )); - let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length); + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) + }; init_cert_validator_config( - name_str, + name_str.as_ref(), config_slice, NEW_CERT_VALIDATOR_CONFIG_FUNCTION .get() @@ -325,21 +325,23 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_do_verify_cert_c let envoy_cert_validator = EnvoyCertValidator::new(config_envoy_ptr); - let cert_buffers = std::slice::from_raw_parts(certs, certs_count); + // `certs` may be null when `certs_count` is 0 (for example, a TLS handshake with no + // client certificate). `slice_from_raw_or_empty` returns an empty slice for null without + // dereferencing. + let cert_buffers: &[crate::abi::envoy_dynamic_module_type_envoy_buffer] = + crate::ffi_helpers::slice_from_raw_or_empty(certs, certs_count); let cert_slices: Vec<&[u8]> = cert_buffers .iter() - .map(|buf| std::slice::from_raw_parts(buf.ptr as *const u8, buf.length)) + .map(|buf| crate::ffi_helpers::slice_from_raw_or_empty(buf.ptr as *const u8, buf.length)) .collect(); - let host_name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - host_name.ptr as *const _, - host_name.length, - )); + let host_name_str = + crate::ffi_helpers::str_lossy_from_raw(host_name.ptr as *const u8, host_name.length); let result = config.do_verify_cert_chain( &envoy_cert_validator, &cert_slices, - host_name_str, + host_name_str.as_ref(), is_server, ); diff --git a/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs b/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs index ed8d7ba7a173e..f74a2c8df0584 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs @@ -986,11 +986,7 @@ impl EnvoyClusterLoadBalancer for EnvoyClusterLoadBalancerImpl { String::new() } else { unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const u8, - result.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length).into_owned() } } } @@ -1024,11 +1020,7 @@ impl EnvoyClusterLoadBalancer for EnvoyClusterLoadBalancerImpl { }; if found && !result.ptr.is_null() && result.length > 0 { Some(unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const u8, - result.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length).into_owned() }) } else { None @@ -1088,11 +1080,7 @@ impl EnvoyClusterLoadBalancer for EnvoyClusterLoadBalancerImpl { }; if found && !result.ptr.is_null() && result.length > 0 { Some(unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const u8, - result.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length).into_owned() }) } else { None @@ -1144,29 +1132,19 @@ impl EnvoyClusterLoadBalancer for EnvoyClusterLoadBalancerImpl { let region_str = if region.ptr.is_null() || region.length == 0 { String::new() } else { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - region.ptr as *const u8, - region.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(region.ptr as *const u8, region.length) + .into_owned() }; let zone_str = if zone.ptr.is_null() || zone.length == 0 { String::new() } else { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - zone.ptr as *const u8, - zone.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(zone.ptr as *const u8, zone.length).into_owned() }; let sub_zone_str = if sub_zone.ptr.is_null() || sub_zone.length == 0 { String::new() } else { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - sub_zone.ptr as *const u8, - sub_zone.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(sub_zone.ptr as *const u8, sub_zone.length) + .into_owned() }; Some((region_str, zone_str, sub_zone_str)) } @@ -1220,11 +1198,7 @@ impl EnvoyClusterLoadBalancer for EnvoyClusterLoadBalancerImpl { }; if found && !result.ptr.is_null() && result.length > 0 { Some(unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const u8, - result.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length).into_owned() }) } else { None @@ -1320,11 +1294,7 @@ impl EnvoyClusterLoadBalancer for EnvoyClusterLoadBalancerImpl { }; if found && !result.ptr.is_null() && result.length > 0 { Some(unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const u8, - result.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length).into_owned() }) } else { None @@ -1356,11 +1326,7 @@ impl EnvoyClusterLoadBalancer for EnvoyClusterLoadBalancerImpl { }; if found && !result.ptr.is_null() && result.length > 0 { Some(unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const u8, - result.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length).into_owned() }) } else { None @@ -1771,15 +1737,10 @@ impl ClusterLbContext for ClusterLbContextImpl { raw_headers .iter() .map(|h| unsafe { - let key = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - h.key_ptr as *const u8, - h.key_length, - )); - let value = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - h.value_ptr as *const u8, - h.value_length, - )); - (key.to_string(), value.to_string()) + let key = crate::ffi_helpers::str_lossy_from_raw(h.key_ptr as *const u8, h.key_length); + let value = + crate::ffi_helpers::str_lossy_from_raw(h.value_ptr as *const u8, h.value_length); + (key.into_owned(), value.into_owned()) }) .collect(), ) @@ -1805,12 +1766,9 @@ impl ClusterLbContext for ClusterLbContextImpl { return None; } let value = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result_buffer.ptr as *const u8, - result_buffer.length, - )) + crate::ffi_helpers::str_lossy_from_raw(result_buffer.ptr as *const u8, result_buffer.length) }; - Some((value.to_string(), total_size)) + Some((value.into_owned(), total_size)) } fn get_host_selection_retry_count(&self) -> u32 { @@ -1848,13 +1806,9 @@ impl ClusterLbContext for ClusterLbContextImpl { if !ok { return None; } - let addr_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const u8, - address.length, - )) - }; - Some((addr_str.to_string(), strict)) + let addr_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + Some((addr_str.into_owned(), strict)) } fn get_downstream_connection_sni(&self) -> Option { @@ -1872,12 +1826,9 @@ impl ClusterLbContext for ClusterLbContextImpl { return None; } let sni = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result_buffer.ptr as *const u8, - result_buffer.length, - )) + crate::ffi_helpers::str_lossy_from_raw(result_buffer.ptr as *const u8, result_buffer.length) }; - Some(sni.to_string()) + Some(sni.into_owned()) } } @@ -1894,19 +1845,21 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_config_new( config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_cluster_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { - // SAFETY: Envoy guarantees name and config are valid UTF-8 per the ABI contract. - let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )); - let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length); + // SAFETY: `name` is a protobuf string (UTF-8 by contract) and `config` is opaque bytes. + // The helpers additionally tolerate `(nullptr, 0)` empty inputs, and `str_lossy_from_raw` + // substitutes `U+FFFD` for any malformed UTF-8 rather than triggering UB. + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) + }; let new_config_fn = NEW_CLUSTER_CONFIG_FUNCTION .get() .expect("NEW_CLUSTER_CONFIG_FUNCTION must be set"); let envoy_cluster_metrics: Arc = Arc::new(EnvoyClusterMetricsImpl { raw: config_envoy_ptr, }); - match new_config_fn(name_str, config_slice, envoy_cluster_metrics) { + match new_config_fn(name_str.as_ref(), config_slice, envoy_cluster_metrics) { Some(config) => wrap_into_c_void_ptr!(config), None => std::ptr::null(), } @@ -2248,7 +2201,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_http_callout_done( let cluster = &mut *cluster; let headers = if headers_size > 0 { - Some(std::slice::from_raw_parts( + Some(crate::ffi_helpers::slice_from_raw_or_empty( headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size, )) @@ -2256,7 +2209,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_http_callout_done( None }; let body = if body_chunks_size > 0 { - Some(std::slice::from_raw_parts( + Some(crate::ffi_helpers::slice_from_raw_or_empty( body_chunks as *const EnvoyBuffer, body_chunks_size, )) diff --git a/source/extensions/dynamic_modules/sdk/rust/src/dns_resolver.rs b/source/extensions/dynamic_modules/sdk/rust/src/dns_resolver.rs index f7d0f25298c17..2397dd69ee623 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/dns_resolver.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/dns_resolver.rs @@ -614,15 +614,14 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolver_config_new( config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_dns_resolver_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { - // SAFETY: Envoy guarantees name and config are valid UTF-8 per the ABI contract. - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const u8, - name.length, - )) + // SAFETY: `name` is a protobuf string (UTF-8 by contract) and `config` is opaque bytes. + // The helpers additionally tolerate `(nullptr, 0)` empty inputs, and `str_lossy_from_raw` + // substitutes `U+FFFD` for any malformed UTF-8 rather than triggering UB. + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) }; - let config_slice = - unsafe { std::slice::from_raw_parts(config.ptr as *const u8, config.length) }; let new_config_fn = crate::NEW_DNS_RESOLVER_CONFIG_FUNCTION .get() .expect("NEW_DNS_RESOLVER_CONFIG_FUNCTION must be set"); @@ -630,7 +629,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolver_config_new( Arc::new(EnvoyDnsResolverConfigImpl { raw: config_envoy_ptr, }); - match new_config_fn(name_str, config_slice, envoy_dns_resolver_config) { + match new_config_fn(name_str.as_ref(), config_slice, envoy_dns_resolver_config) { Some(config) => wrap_into_c_void_ptr!(config), None => std::ptr::null(), } @@ -711,12 +710,8 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolve( ) -> abi::envoy_dynamic_module_type_dns_query_module_ptr { catch_unwind(AssertUnwindSafe(|| { let wrapper = unsafe { &*(resolver_module_ptr as *const DnsResolverWrapper) }; - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - dns_name.ptr as *const u8, - dns_name.length, - )) - }; + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(dns_name.ptr as *const u8, dns_name.length) }; let family = match lookup_family { abi::envoy_dynamic_module_type_dns_lookup_family::V4Only => DnsLookupFamily::V4Only, @@ -726,7 +721,10 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_dns_resolve( abi::envoy_dynamic_module_type_dns_lookup_family::All => DnsLookupFamily::All, }; - match wrapper.resolver.resolve(name_str, family, query_id) { + match wrapper + .resolver + .resolve(name_str.as_ref(), family, query_id) + { Some(query) => { let boxed = Box::new(query); Box::into_raw(boxed) as abi::envoy_dynamic_module_type_dns_query_module_ptr diff --git a/source/extensions/dynamic_modules/sdk/rust/src/ffi_helpers.rs b/source/extensions/dynamic_modules/sdk/rust/src/ffi_helpers.rs new file mode 100644 index 0000000000000..73a7ce447264c --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/rust/src/ffi_helpers.rs @@ -0,0 +1,211 @@ +//! Safe wrappers over `slice::from_raw_parts` and `str::from_utf8_unchecked` for the FFI seam. +//! +//! Two well-known footguns motivate this module: +//! +//! 1. `slice::from_raw_parts(ptr, len)` is undefined behaviour if `ptr` is null, even when `len == +//! 0`. Per the [Rust reference][1], the pointer must be non-null and aligned regardless of +//! length. C ABIs routinely pass `(nullptr, 0)` to mean "empty". +//! +//! 2. `str::from_utf8_unchecked` is undefined behaviour on invalid UTF-8. Many ABI buffers carry +//! attacker-influenced bytes (HTTP header values, TLS SNI, EDS metadata) that are not +//! contractually UTF-8. +//! +//! [1]: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety +//! +//! These helpers are `#[doc(hidden)] pub` so SDK-provided macros that expand in user crates +//! can reach them; users should call them via the macros rather than directly. + +use std::borrow::Cow; + +/// Constructs a `&[T]` slice from an FFI-supplied `(ptr, len)` pair. +/// +/// Common element types include `u8` (byte buffers), `EnvoyBuffer` (chunk arrays), and +/// `(EnvoyBuffer, EnvoyBuffer)` (header-pair arrays). +/// +/// - When `ptr` is null, returns an empty slice regardless of `len`. The C convention of passing +/// `(nullptr, 0)` for "empty" is the well-defined path. A non-zero `len` paired with a null `ptr` +/// is a caller-side contract violation; this function logs an error via `envoy_log_error!` and +/// still returns an empty slice rather than dereferencing. +/// - Otherwise dereferences `(ptr, len)` exactly as `std::slice::from_raw_parts` would. +/// +/// # Safety +/// +/// When `ptr` is non-null, the caller must guarantee: +/// - `ptr` is properly aligned for `T`. +/// - `ptr` points to `len` consecutive initialised values of `T`. +/// - The memory outlives the lifetime `'a`. `'a` is freely chosen by the caller; choosing a +/// lifetime longer than the underlying memory is a contract violation. +/// +/// When `ptr` is null this function never reads any memory; passing `(nullptr, 0)` is safe. +#[inline] +#[doc(hidden)] +pub unsafe fn slice_from_raw_or_empty<'a, T>(ptr: *const T, len: usize) -> &'a [T] { + if ptr.is_null() { + if len != 0 { + crate::envoy_log_error!( + "ffi_helpers::slice_from_raw_or_empty: null ptr with len={len} (treating as empty)" + ); + } + return &[]; + } + // Safety: caller upholds the non-null branch's invariants. + unsafe { std::slice::from_raw_parts(ptr, len) } +} + +/// Mutable variant of [`slice_from_raw_or_empty`]. Same semantics; same null-ptr handling. +/// +/// # Safety +/// +/// Same constraints as [`slice_from_raw_or_empty`], plus exclusive-borrow on the underlying +/// values for the lifetime `'a`. +#[inline] +#[doc(hidden)] +pub unsafe fn slice_from_raw_or_empty_mut<'a, T>(ptr: *mut T, len: usize) -> &'a mut [T] { + if ptr.is_null() { + if len != 0 { + crate::envoy_log_error!( + "ffi_helpers::slice_from_raw_or_empty_mut: null ptr with len={len} (treating as empty)" + ); + } + // `&mut []` is a valid empty mutable slice of any lifetime; no memory is ever written. + return &mut []; + } + // Safety: caller upholds the non-null branch's invariants. + unsafe { std::slice::from_raw_parts_mut(ptr, len) } +} + +/// Lossy UTF-8 conversion of an FFI-supplied byte buffer. +/// +/// Always succeeds: invalid UTF-8 is replaced with `U+FFFD REPLACEMENT CHARACTER` per +/// [`String::from_utf8_lossy`]. Returns `Cow<'a, str>` so the common case of valid UTF-8 +/// avoids allocation. +/// +/// Use this helper for any ABI buffer where UTF-8 is the desired representation but not +/// contractually guaranteed. Examples: filter / cluster / module names from operator config, +/// HTTP header values, SNI strings, EDS metadata keys/values. +/// +/// Callers that need "invalid UTF-8 maps to empty" semantics (for example, the +/// `declare_matcher!` macro, which uses the name as a registry key where `U+FFFD` substitution +/// would route to a different entry) should call [`slice_from_raw_or_empty`] followed by +/// `str::from_utf8(...).unwrap_or("")` instead. +/// +/// # Safety +/// +/// Same constraints as [`slice_from_raw_or_empty`]. Passing `(nullptr, 0)` is safe and +/// produces an empty `Cow::Borrowed("")`. +#[inline] +#[doc(hidden)] +pub unsafe fn str_lossy_from_raw<'a>(ptr: *const u8, len: usize) -> Cow<'a, str> { + let bytes = unsafe { slice_from_raw_or_empty::<'a>(ptr, len) }; + String::from_utf8_lossy(bytes) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn slice_from_null_ptr_with_zero_len_is_empty() { + // Safety: null ptr with zero len is the documented safe path. + let slice: &[u8] = unsafe { slice_from_raw_or_empty(std::ptr::null(), 0) }; + assert!(slice.is_empty()); + } + + #[test] + fn slice_from_valid_ptr_round_trips() { + let data = b"hello"; + // Safety: the borrow lives through the call. + let slice: &[u8] = unsafe { slice_from_raw_or_empty(data.as_ptr(), data.len()) }; + assert_eq!(slice, b"hello"); + } + + #[test] + fn str_lossy_handles_valid_utf8_without_alloc() { + let data = b"hello"; + // Safety: the borrow lives through the call. + let s = unsafe { str_lossy_from_raw(data.as_ptr(), data.len()) }; + assert_eq!(s, "hello"); + assert!(matches!(s, Cow::Borrowed(_))); + } + + #[test] + fn str_lossy_replaces_invalid_utf8() { + // 0xff is never a valid UTF-8 byte. + let data = [0xff_u8, b'a']; + // Safety: the borrow lives through the call. + let s = unsafe { str_lossy_from_raw(data.as_ptr(), data.len()) }; + // U+FFFD followed by 'a'. + assert_eq!(s.as_ref(), "\u{fffd}a"); + assert!(matches!(s, Cow::Owned(_))); + } + + #[test] + fn str_lossy_from_null_ptr_is_empty() { + // Safety: null ptr with zero len is the documented safe path. + let s = unsafe { str_lossy_from_raw(std::ptr::null(), 0) }; + assert_eq!(s, ""); + } + + #[test] + fn slice_mut_from_null_ptr_with_zero_len_is_empty() { + // Safety: null ptr with zero len is the documented safe path. + let slice: &mut [u8] = unsafe { slice_from_raw_or_empty_mut(std::ptr::null_mut(), 0) }; + assert!(slice.is_empty()); + } + + #[test] + fn slice_mut_from_valid_ptr_round_trips() { + let mut data: [u8; 5] = *b"hello"; + // Safety: the borrow lives through the call. + let slice: &mut [u8] = unsafe { slice_from_raw_or_empty_mut(data.as_mut_ptr(), data.len()) }; + slice[0] = b'H'; + assert_eq!(&data, b"Hello"); + } + + #[test] + fn slice_from_null_ptr_with_nonzero_len_returns_empty_and_does_not_panic() { + // Contract violation path: caller passed `(null, len > 0)`. The helper logs an error + // and returns an empty slice rather than dereferencing. + // Safety: null ptr is the documented safe-but-misuse path. + let slice: &[u8] = unsafe { slice_from_raw_or_empty(std::ptr::null(), 5) }; + assert!(slice.is_empty()); + } + + #[test] + fn slice_mut_from_null_ptr_with_nonzero_len_returns_empty_and_does_not_panic() { + // Safety: null ptr is the documented safe-but-misuse path. + let slice: &mut [u8] = unsafe { slice_from_raw_or_empty_mut(std::ptr::null_mut(), 5) }; + assert!(slice.is_empty()); + } + + #[test] + fn slice_from_struct_ptr_round_trips() { + // Generic over arbitrary `T`; verify with an `(EnvoyBuffer, EnvoyBuffer)`-shaped tuple. + #[repr(C)] + #[derive(Copy, Clone, Debug, PartialEq)] + struct Pair { + a: u32, + b: u32, + } + let data = [ + Pair { a: 1, b: 2 }, + Pair { a: 3, b: 4 }, + Pair { a: 5, b: 6 }, + ]; + // Safety: the borrow lives through the call. + let slice: &[Pair] = unsafe { slice_from_raw_or_empty(data.as_ptr(), data.len()) }; + assert_eq!(slice, &data); + } + + #[test] + fn slice_from_null_struct_ptr_with_nonzero_len_returns_empty() { + #[repr(C)] + struct Pair { + _a: u32, + _b: u32, + } + // Safety: null ptr is the documented safe-but-misuse path. + let slice: &[Pair] = unsafe { slice_from_raw_or_empty(std::ptr::null::(), 5) }; + assert!(slice.is_empty()); + } +} diff --git a/source/extensions/dynamic_modules/sdk/rust/src/http.rs b/source/extensions/dynamic_modules/sdk/rust/src/http.rs index da003ef6956f0..2ec2079c508a9 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/http.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/http.rs @@ -1812,7 +1812,9 @@ impl EnvoySpan for EnvoySpanImpl { ) }; if success && !result.ptr.is_null() && result.length > 0 { - let slice = unsafe { std::slice::from_raw_parts(result.ptr as *const u8, result.length) }; + let slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(result.ptr as *const u8, result.length) + }; Some(String::from_utf8_lossy(slice).to_string()) } else { None @@ -1841,7 +1843,9 @@ impl EnvoySpan for EnvoySpanImpl { ) }; if success && !result.ptr.is_null() && result.length > 0 { - let slice = unsafe { std::slice::from_raw_parts(result.ptr as *const u8, result.length) }; + let slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(result.ptr as *const u8, result.length) + }; Some(String::from_utf8_lossy(slice).to_string()) } else { None @@ -1860,7 +1864,9 @@ impl EnvoySpan for EnvoySpanImpl { ) }; if success && !result.ptr.is_null() && result.length > 0 { - let slice = unsafe { std::slice::from_raw_parts(result.ptr as *const u8, result.length) }; + let slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(result.ptr as *const u8, result.length) + }; Some(String::from_utf8_lossy(slice).to_string()) } else { None @@ -3425,7 +3431,9 @@ impl EnvoyHttpFilter for EnvoyHttpFilterImpl { ) }; if success && !result.ptr.is_null() && result.length > 0 { - let slice = unsafe { std::slice::from_raw_parts(result.ptr as *const u8, result.length) }; + let slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(result.ptr as *const u8, result.length) + }; Some(slice.to_vec()) } else { None @@ -3803,15 +3811,14 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_new( config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_http_filter_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { - // This assumes that the name is a valid UTF-8 string. Should we relax? At the moment, - // it is a String at protobuf level. - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) + // The name is sourced from a protobuf string field (and thus UTF-8 by contract); we still + // route through `str_lossy_from_raw` so a malformed input on the FFI seam produces a lossy + // decode rather than undefined behaviour. + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; let mut envoy_filter_config = EnvoyHttpFilterConfigImpl { raw_ptr: envoy_filter_config_ptr, @@ -3819,7 +3826,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_new( envoy_dynamic_module_on_http_filter_config_new_impl( &mut envoy_filter_config, - name_str, + name_str.as_ref(), config_slice, NEW_HTTP_FILTER_CONFIG_FUNCTION .get() @@ -3894,18 +3901,17 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_per_route_config_ne config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_http_filter_per_route_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { - // This assumes that the name is a valid UTF-8 string. Should we relax? At the moment, - // it is a String at protobuf level. - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) + // See `envoy_dynamic_module_on_http_filter_config_new`: route through `str_lossy_from_raw` + // so a malformed input on the FFI seam produces a lossy decode rather than undefined + // behaviour. + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; envoy_dynamic_module_on_http_filter_per_route_config_new_impl( - name_str, + name_str.as_ref(), config_slice, NEW_HTTP_FILTER_PER_ROUTE_CONFIG_FUNCTION .get() @@ -4178,14 +4184,20 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_callout_done( let filter = &mut **filter; let headers = if headers_size > 0 { Some(unsafe { - std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) + crate::ffi_helpers::slice_from_raw_or_empty( + headers as *const (EnvoyBuffer, EnvoyBuffer), + headers_size, + ) }) } else { None }; let body = if body_chunks_size > 0 { Some(unsafe { - std::slice::from_raw_parts(body_chunks as *const EnvoyBuffer, body_chunks_size) + crate::ffi_helpers::slice_from_raw_or_empty( + body_chunks as *const EnvoyBuffer, + body_chunks_size, + ) }) } else { None @@ -4319,7 +4331,10 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_stream_headers let filter = &mut **filter; let headers = if headers_size > 0 { unsafe { - std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) + crate::ffi_helpers::slice_from_raw_or_empty( + headers as *const (EnvoyBuffer, EnvoyBuffer), + headers_size, + ) } } else { &[] @@ -4356,7 +4371,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_stream_data( let filter = filter_ptr as *mut *mut dyn HttpFilter; let filter = &mut **filter; let data = if data_count > 0 { - unsafe { std::slice::from_raw_parts(data as *const EnvoyBuffer, data_count) } + unsafe { crate::ffi_helpers::slice_from_raw_or_empty(data as *const EnvoyBuffer, data_count) } } else { &[] }; @@ -4392,7 +4407,10 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_stream_trailer let filter = &mut **filter; let trailers = if trailers_size > 0 { unsafe { - std::slice::from_raw_parts(trailers as *const (EnvoyBuffer, EnvoyBuffer), trailers_size) + crate::ffi_helpers::slice_from_raw_or_empty( + trailers as *const (EnvoyBuffer, EnvoyBuffer), + trailers_size, + ) } } else { &[] @@ -4482,14 +4500,20 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_http_callout let config = &**config; let headers = if headers_size > 0 { Some(unsafe { - std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) + crate::ffi_helpers::slice_from_raw_or_empty( + headers as *const (EnvoyBuffer, EnvoyBuffer), + headers_size, + ) }) } else { None }; let body = if body_chunks_size > 0 { Some(unsafe { - std::slice::from_raw_parts(body_chunks as *const EnvoyBuffer, body_chunks_size) + crate::ffi_helpers::slice_from_raw_or_empty( + body_chunks as *const EnvoyBuffer, + body_chunks_size, + ) }) } else { None @@ -4530,7 +4554,10 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_http_stream_ let config = &**config; let headers = if headers_size > 0 { unsafe { - std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size) + crate::ffi_helpers::slice_from_raw_or_empty( + headers as *const (EnvoyBuffer, EnvoyBuffer), + headers_size, + ) } } else { &[] @@ -4569,7 +4596,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_http_stream_ let config = config_ptr as *mut *mut dyn HttpFilterConfig; let config = &**config; let data = if data_count > 0 { - unsafe { std::slice::from_raw_parts(data as *const EnvoyBuffer, data_count) } + unsafe { crate::ffi_helpers::slice_from_raw_or_empty(data as *const EnvoyBuffer, data_count) } } else { &[] }; @@ -4607,7 +4634,10 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_http_filter_config_http_stream_ let config = &**config; let trailers = if trailers_size > 0 { unsafe { - std::slice::from_raw_parts(trailers as *const (EnvoyBuffer, EnvoyBuffer), trailers_size) + crate::ffi_helpers::slice_from_raw_or_empty( + trailers as *const (EnvoyBuffer, EnvoyBuffer), + trailers_size, + ) } } else { &[] diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs index eea14c0110adc..0813c28fb126a 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs @@ -12,6 +12,11 @@ pub mod catch_unwind; pub mod cert_validator; pub mod cluster; pub mod dns_resolver; +// Implementation detail. Public so SDK-provided macros (for example, `declare_matcher!`) that +// expand in user crates can reach the safe helpers; users should not depend on this module +// directly. +#[doc(hidden)] +pub mod ffi_helpers; pub mod http; pub mod listener; pub mod load_balancer; @@ -416,8 +421,8 @@ macro_rules! set_factory_once { if !::std::ptr::fn_addr_eq(*$static.get().unwrap(), new_val) { $crate::envoy_log_critical!( "Duplicate factory registration for {}. A different module already registered this \ - factory. Check dynamic_module_config for conflicting standalone and consolidated \ - .so loads.", + factory. Check dynamic_module_config for conflicting standalone and consolidated .so \ + loads.", $name, ); // Return the "init failed" sentinel rather than panicking; unwinding across the diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs index dd850459b8827..4b8a808dd446b 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs @@ -3221,6 +3221,116 @@ fn test_cert_validator_do_verify_cert_chain_failed() { } } +#[test] +fn test_cert_validator_do_verify_cert_chain_with_null_certs_and_zero_count() { + // TLS handshake with no client certificate: Envoy may pass `(certs=null, certs_count=0)`. + // The FFI entry must accept this without dereferencing the null pointer. + struct TestCertValidatorConfig; + impl cert_validator::CertValidatorConfig for TestCertValidatorConfig { + fn do_verify_cert_chain( + &self, + _envoy_cert_validator: &cert_validator::EnvoyCertValidator, + certs: &[&[u8]], + _host_name: &str, + _is_server: bool, + ) -> cert_validator::ValidationResult { + assert!(certs.is_empty()); + cert_validator::ValidationResult::successful() + } + fn get_ssl_verify_mode(&self, _handshaker_provides_certificates: bool) -> i32 { + 0x03 + } + fn update_digest(&self) -> &[u8] { + b"test" + } + } + + let config: Box = Box::new(TestCertValidatorConfig); + let config_ptr = Box::into_raw(Box::new(config)) as *const ::std::os::raw::c_void; + + let host_name = "example.com"; + let host_name_buf = abi::envoy_dynamic_module_type_envoy_buffer { + ptr: host_name.as_ptr() as *const _, + length: host_name.len(), + }; + + let result = unsafe { + envoy_dynamic_module_on_cert_validator_do_verify_cert_chain( + std::ptr::null_mut(), + config_ptr, + std::ptr::null_mut(), + 0, + host_name_buf, + false, + ) + }; + assert_eq!( + result.status, + abi::envoy_dynamic_module_type_cert_validator_validation_status::Successful + ); + + unsafe { + envoy_dynamic_module_on_cert_validator_config_destroy(config_ptr); + } +} + +#[test] +fn test_cert_validator_do_verify_cert_chain_with_null_certs_and_nonzero_count_is_safe() { + // Caller-side contract violation: `(certs=null, certs_count > 0)`. The FFI entry must log + // and treat the cert list as empty rather than dereferencing the null pointer. + struct TestCertValidatorConfig; + impl cert_validator::CertValidatorConfig for TestCertValidatorConfig { + fn do_verify_cert_chain( + &self, + _envoy_cert_validator: &cert_validator::EnvoyCertValidator, + certs: &[&[u8]], + _host_name: &str, + _is_server: bool, + ) -> cert_validator::ValidationResult { + assert!(certs.is_empty()); + cert_validator::ValidationResult::failed( + cert_validator::ClientValidationStatus::Failed, + None, + None, + ) + } + fn get_ssl_verify_mode(&self, _handshaker_provides_certificates: bool) -> i32 { + 0x03 + } + fn update_digest(&self) -> &[u8] { + b"test" + } + } + + let config: Box = Box::new(TestCertValidatorConfig); + let config_ptr = Box::into_raw(Box::new(config)) as *const ::std::os::raw::c_void; + + let host_name = "example.com"; + let host_name_buf = abi::envoy_dynamic_module_type_envoy_buffer { + ptr: host_name.as_ptr() as *const _, + length: host_name.len(), + }; + + let result = unsafe { + envoy_dynamic_module_on_cert_validator_do_verify_cert_chain( + std::ptr::null_mut(), + config_ptr, + std::ptr::null_mut(), + 5, + host_name_buf, + false, + ) + }; + assert_eq!( + result.status, + abi::envoy_dynamic_module_type_cert_validator_validation_status::Failed + ); + + unsafe { + envoy_dynamic_module_on_cert_validator_config_destroy(config_ptr); + } +} + #[test] fn test_cert_validator_filter_state_methods() { // Test that EnvoyCertValidator filter state methods call the ABI functions correctly. @@ -5735,3 +5845,307 @@ fn test_outer_ffi_pattern_returns_fail_closed_bool_on_panic() { }); assert!(!result); } + +// Regression tests for the `*const EnvoyBuffer` / `*const (EnvoyBuffer, EnvoyBuffer)` +// null-guard migration. Each test invokes a migrated FFI entry with `(null, 0)` for the +// header- and body-array pairs and verifies that the trait callback receives the empty +// representation (`None` or `Vec::new()`) without dereferencing the null pointer. + +#[test] +fn test_http_filter_callout_done_with_null_buffers_yields_none() { + static GOT_NONE_HEADERS: AtomicBool = AtomicBool::new(false); + static GOT_NONE_BODY: AtomicBool = AtomicBool::new(false); + + struct TestHttpFilterConfig; + impl HttpFilterConfig for TestHttpFilterConfig { + fn new_http_filter(&self, _envoy: &mut EHF) -> Box> { + Box::new(TestHttpFilter) + } + } + + struct TestHttpFilter; + impl HttpFilter for TestHttpFilter { + fn on_http_callout_done( + &mut self, + _envoy_filter: &mut EHF, + _callout_id: u64, + _result: abi::envoy_dynamic_module_type_http_callout_result, + response_headers: Option<&[(EnvoyBuffer, EnvoyBuffer)]>, + response_body: Option<&[EnvoyBuffer]>, + ) { + if response_headers.is_none() { + GOT_NONE_HEADERS.store(true, std::sync::atomic::Ordering::SeqCst); + } + if response_body.is_none() { + GOT_NONE_BODY.store(true, std::sync::atomic::Ordering::SeqCst); + } + } + } + + let filter_config = TestHttpFilterConfig; + let filter = envoy_dynamic_module_on_http_filter_new_impl( + &mut EnvoyHttpFilterImpl { + raw_ptr: std::ptr::null_mut(), + }, + &filter_config, + ); + + unsafe { + http::envoy_dynamic_module_on_http_filter_http_callout_done( + std::ptr::null_mut(), + filter, + 1, + abi::envoy_dynamic_module_type_http_callout_result::Success, + std::ptr::null(), + 0, + std::ptr::null(), + 0, + ); + envoy_dynamic_module_on_http_filter_destroy(filter); + } + + assert!(GOT_NONE_HEADERS.load(std::sync::atomic::Ordering::SeqCst)); + assert!(GOT_NONE_BODY.load(std::sync::atomic::Ordering::SeqCst)); +} + +#[test] +fn test_network_filter_callout_done_with_null_buffers_yields_empty_vecs() { + static GOT_EMPTY_HEADERS: AtomicBool = AtomicBool::new(false); + static GOT_EMPTY_BODY: AtomicBool = AtomicBool::new(false); + + struct TestNetworkFilterConfig; + impl NetworkFilterConfig for TestNetworkFilterConfig { + fn new_network_filter(&self, _envoy: &mut ENF) -> Box> { + Box::new(TestNetworkFilter) + } + } + + struct TestNetworkFilter; + impl NetworkFilter for TestNetworkFilter { + fn on_http_callout_done( + &mut self, + _envoy_filter: &mut ENF, + _callout_id: u64, + _result: abi::envoy_dynamic_module_type_http_callout_result, + headers: Vec<(EnvoyBuffer, EnvoyBuffer)>, + body_chunks: Vec, + ) { + if headers.is_empty() { + GOT_EMPTY_HEADERS.store(true, std::sync::atomic::Ordering::SeqCst); + } + if body_chunks.is_empty() { + GOT_EMPTY_BODY.store(true, std::sync::atomic::Ordering::SeqCst); + } + } + } + + let filter_config = TestNetworkFilterConfig; + let filter = network::envoy_dynamic_module_on_network_filter_new_impl( + &mut EnvoyNetworkFilterImpl { + raw: std::ptr::null_mut(), + }, + &filter_config, + ); + + unsafe { + network::envoy_dynamic_module_on_network_filter_http_callout_done( + std::ptr::null_mut(), + filter, + 1, + abi::envoy_dynamic_module_type_http_callout_result::Success, + std::ptr::null(), + 0, + std::ptr::null(), + 0, + ); + envoy_dynamic_module_on_network_filter_destroy(filter); + } + + assert!(GOT_EMPTY_HEADERS.load(std::sync::atomic::Ordering::SeqCst)); + assert!(GOT_EMPTY_BODY.load(std::sync::atomic::Ordering::SeqCst)); +} + +#[test] +fn test_listener_filter_callout_done_with_null_buffers_yields_empty_vecs() { + static GOT_EMPTY_HEADERS: AtomicBool = AtomicBool::new(false); + static GOT_EMPTY_BODY: AtomicBool = AtomicBool::new(false); + + struct TestListenerFilterConfig; + impl ListenerFilterConfig for TestListenerFilterConfig { + fn new_listener_filter(&self, _envoy: &mut ELF) -> Box> { + Box::new(TestListenerFilter) + } + } + + struct TestListenerFilter; + impl ListenerFilter for TestListenerFilter { + fn on_http_callout_done( + &mut self, + _envoy_filter: &mut ELF, + _callout_id: u64, + _result: abi::envoy_dynamic_module_type_http_callout_result, + response_headers: Vec<(EnvoyBuffer, EnvoyBuffer)>, + response_body: Vec, + ) { + if response_headers.is_empty() { + GOT_EMPTY_HEADERS.store(true, std::sync::atomic::Ordering::SeqCst); + } + if response_body.is_empty() { + GOT_EMPTY_BODY.store(true, std::sync::atomic::Ordering::SeqCst); + } + } + } + + let filter_config = TestListenerFilterConfig; + let filter = listener::envoy_dynamic_module_on_listener_filter_new_impl( + &mut EnvoyListenerFilterImpl { + raw: std::ptr::null_mut(), + }, + &filter_config, + ); + + unsafe { + listener::envoy_dynamic_module_on_listener_filter_http_callout_done( + std::ptr::null_mut(), + filter, + 1, + abi::envoy_dynamic_module_type_http_callout_result::Success, + std::ptr::null(), + 0, + std::ptr::null(), + 0, + ); + envoy_dynamic_module_on_listener_filter_destroy(filter); + } + + assert!(GOT_EMPTY_HEADERS.load(std::sync::atomic::Ordering::SeqCst)); + assert!(GOT_EMPTY_BODY.load(std::sync::atomic::Ordering::SeqCst)); +} + +#[test] +fn test_bootstrap_extension_callout_done_with_null_buffers_yields_none() { + static GOT_NONE_HEADERS: AtomicBool = AtomicBool::new(false); + static GOT_NONE_BODY: AtomicBool = AtomicBool::new(false); + + struct TestBootstrapExtensionConfig; + impl BootstrapExtensionConfig for TestBootstrapExtensionConfig { + fn new_bootstrap_extension( + &self, + _envoy_extension: &mut dyn EnvoyBootstrapExtension, + ) -> Box { + Box::new(TestBootstrapExtension) + } + + fn on_http_callout_done( + &self, + _envoy_extension_config: &mut dyn EnvoyBootstrapExtensionConfig, + _callout_id: u64, + _result: abi::envoy_dynamic_module_type_http_callout_result, + response_headers: Option<&[(EnvoyBuffer, EnvoyBuffer)]>, + response_body: Option<&[EnvoyBuffer]>, + ) { + if response_headers.is_none() { + GOT_NONE_HEADERS.store(true, std::sync::atomic::Ordering::SeqCst); + } + if response_body.is_none() { + GOT_NONE_BODY.store(true, std::sync::atomic::Ordering::SeqCst); + } + } + } + + struct TestBootstrapExtension; + impl BootstrapExtension for TestBootstrapExtension {} + + fn new_config( + _envoy_extension_config: &mut dyn EnvoyBootstrapExtensionConfig, + _name: &str, + _config: &[u8], + ) -> Option> { + Some(Box::new(TestBootstrapExtensionConfig)) + } + + let mut envoy_config = bootstrap::EnvoyBootstrapExtensionConfigImpl::new(std::ptr::null_mut()); + let config_ptr = bootstrap::init_bootstrap_extension_config( + &mut envoy_config, + "test", + b"config", + &(new_config as NewBootstrapExtensionConfigFunction), + ); + assert!(!config_ptr.is_null()); + + unsafe { + bootstrap::envoy_dynamic_module_on_bootstrap_extension_http_callout_done( + std::ptr::null_mut(), + config_ptr, + 1, + abi::envoy_dynamic_module_type_http_callout_result::Success, + std::ptr::null(), + 0, + std::ptr::null(), + 0, + ); + envoy_dynamic_module_on_bootstrap_extension_config_destroy(config_ptr); + } + + assert!(GOT_NONE_HEADERS.load(std::sync::atomic::Ordering::SeqCst)); + assert!(GOT_NONE_BODY.load(std::sync::atomic::Ordering::SeqCst)); +} + +#[test] +fn test_cluster_callout_done_with_null_buffers_yields_none() { + use cluster::{Cluster, ClusterLb, EnvoyCluster, EnvoyClusterLoadBalancer}; + + static GOT_NONE_HEADERS: AtomicBool = AtomicBool::new(false); + static GOT_NONE_BODY: AtomicBool = AtomicBool::new(false); + + struct TestCluster; + impl Cluster for TestCluster { + fn on_init(&mut self, _envoy_cluster: &dyn EnvoyCluster) {} + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { + unimplemented!("not exercised by this test") + } + + fn on_http_callout_done( + &mut self, + _envoy_cluster: &dyn EnvoyCluster, + _callout_id: u64, + _result: abi::envoy_dynamic_module_type_http_callout_result, + response_headers: Option<&[(EnvoyBuffer, EnvoyBuffer)]>, + response_body: Option<&[EnvoyBuffer]>, + ) { + if response_headers.is_none() { + GOT_NONE_HEADERS.store(true, std::sync::atomic::Ordering::SeqCst); + } + if response_body.is_none() { + GOT_NONE_BODY.store(true, std::sync::atomic::Ordering::SeqCst); + } + } + } + + // Construct the cluster_module_ptr directly without going through the factory chain; + // the FFI entry only requires a `*mut Box`, which is what the SDK's + // `wrap_into_c_void_ptr!` produces internally. + let test_cluster: Box = Box::new(TestCluster); + let cluster_ptr = + Box::into_raw(Box::new(test_cluster)) as abi::envoy_dynamic_module_type_cluster_module_ptr; + let cluster_envoy_ptr = std::ptr::null_mut::() + as abi::envoy_dynamic_module_type_cluster_envoy_ptr; + + unsafe { + cluster::envoy_dynamic_module_on_cluster_http_callout_done( + cluster_envoy_ptr, + cluster_ptr, + 1, + abi::envoy_dynamic_module_type_http_callout_result::Success, + std::ptr::null(), + 0, + std::ptr::null(), + 0, + ); + + drop(Box::from_raw(cluster_ptr as *mut Box)); + } + + assert!(GOT_NONE_HEADERS.load(std::sync::atomic::Ordering::SeqCst)); + assert!(GOT_NONE_BODY.load(std::sync::atomic::Ordering::SeqCst)); +} diff --git a/source/extensions/dynamic_modules/sdk/rust/src/listener.rs b/source/extensions/dynamic_modules/sdk/rust/src/listener.rs index 95b50b01ac279..eb564d75d5043 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/listener.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/listener.rs @@ -568,13 +568,9 @@ impl EnvoyListenerFilter for EnvoyListenerFilterImpl { if !result || address.length == 0 || address.ptr.is_null() { return None; } - let address_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - }; - Some((address_str.to_string(), port)) + let address_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + Some((address_str.into_owned(), port)) } fn get_local_address(&self) -> Option<(String, u32)> { @@ -593,13 +589,9 @@ impl EnvoyListenerFilter for EnvoyListenerFilterImpl { if !result || address.length == 0 || address.ptr.is_null() { return None; } - let address_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - }; - Some((address_str.to_string(), port)) + let address_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + Some((address_str.into_owned(), port)) } fn get_direct_remote_address(&self) -> Option<(String, u32)> { @@ -618,13 +610,9 @@ impl EnvoyListenerFilter for EnvoyListenerFilterImpl { if !result || address.length == 0 || address.ptr.is_null() { return None; } - let address_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - }; - Some((address_str.to_string(), port)) + let address_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + Some((address_str.into_owned(), port)) } fn get_direct_local_address(&self) -> Option<(String, u32)> { @@ -643,13 +631,9 @@ impl EnvoyListenerFilter for EnvoyListenerFilterImpl { if !result || address.length == 0 || address.ptr.is_null() { return None; } - let address_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - }; - Some((address_str.to_string(), port)) + let address_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + Some((address_str.into_owned(), port)) } fn get_original_dst(&self) -> Option<(String, u32)> { @@ -668,13 +652,9 @@ impl EnvoyListenerFilter for EnvoyListenerFilterImpl { if !result || address.length == 0 || address.ptr.is_null() { return None; } - let address_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - }; - Some((address_str.to_string(), port)) + let address_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + Some((address_str.into_owned(), port)) } fn get_address_type(&self) -> abi::envoy_dynamic_module_type_address_type { @@ -722,13 +702,9 @@ impl EnvoyListenerFilter for EnvoyListenerFilterImpl { ) }; if success && !result.ptr.is_null() && result.length > 0 { - let value_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const _, - result.length, - )) - }; - Some(value_str.to_string()) + let value_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length) }; + Some(value_str.into_owned()) } else { None } @@ -1220,16 +1196,14 @@ pub extern "C" fn envoy_dynamic_module_on_listener_filter_config_new( ) -> abi::envoy_dynamic_module_type_listener_filter_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { let mut envoy_filter_config = EnvoyListenerFilterConfigImpl::new(envoy_filter_config_ptr); - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; init_listener_filter_config( &mut envoy_filter_config, - name_str, + name_str.as_ref(), config_slice, NEW_LISTENER_FILTER_CONFIG_FUNCTION .get() @@ -1428,31 +1402,25 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_listener_filter_http_callout_do let filter = unsafe { &mut *filter }; // Convert headers to Vec<(EnvoyBuffer, EnvoyBuffer)>. - let header_vec = if headers.is_null() || headers_size == 0 { - Vec::new() - } else { - let headers_slice = unsafe { std::slice::from_raw_parts(headers, headers_size) }; - headers_slice - .iter() - .map(|h| { - ( - unsafe { EnvoyBuffer::new_from_raw(h.key_ptr as *const _, h.key_length) }, - unsafe { EnvoyBuffer::new_from_raw(h.value_ptr as *const _, h.value_length) }, - ) - }) - .collect() - }; + let headers_slice = + unsafe { crate::ffi_helpers::slice_from_raw_or_empty(headers, headers_size) }; + let header_vec: Vec<(EnvoyBuffer, EnvoyBuffer)> = headers_slice + .iter() + .map(|h| { + ( + unsafe { EnvoyBuffer::new_from_raw(h.key_ptr as *const _, h.key_length) }, + unsafe { EnvoyBuffer::new_from_raw(h.value_ptr as *const _, h.value_length) }, + ) + }) + .collect(); // Convert body chunks to Vec. - let body_vec = if body_chunks.is_null() || body_chunks_size == 0 { - Vec::new() - } else { - let chunks_slice = unsafe { std::slice::from_raw_parts(body_chunks, body_chunks_size) }; - chunks_slice - .iter() - .map(|c| unsafe { EnvoyBuffer::new_from_raw(c.ptr as *const _, c.length) }) - .collect() - }; + let chunks_slice = + unsafe { crate::ffi_helpers::slice_from_raw_or_empty(body_chunks, body_chunks_size) }; + let body_vec: Vec = chunks_slice + .iter() + .map(|c| unsafe { EnvoyBuffer::new_from_raw(c.ptr as *const _, c.length) }) + .collect(); filter.on_http_callout_done( &mut EnvoyListenerFilterImpl::new(envoy_ptr), diff --git a/source/extensions/dynamic_modules/sdk/rust/src/load_balancer.rs b/source/extensions/dynamic_modules/sdk/rust/src/load_balancer.rs index d47697490217c..b6879ced468d7 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/load_balancer.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/load_balancer.rs @@ -214,11 +214,7 @@ impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl { unsafe { abi::envoy_dynamic_module_callback_lb_get_cluster_name(self.lb_ptr, &mut result); if !result.ptr.is_null() && result.length > 0 { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const _, - result.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length).into_owned() } else { String::new() } @@ -257,11 +253,8 @@ impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl { if found && !result.ptr.is_null() && result.length > 0 { unsafe { Some( - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const _, - result.length, - )) - .to_string(), + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length) + .into_owned(), ) } } else { @@ -323,11 +316,8 @@ impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl { if found && !result.ptr.is_null() && result.length > 0 { unsafe { Some( - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const _, - result.length, - )) - .to_string(), + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length) + .into_owned(), ) } } else { @@ -378,29 +368,18 @@ impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl { } unsafe { let region_str = if !region.ptr.is_null() && region.length > 0 { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - region.ptr as *const _, - region.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(region.ptr as *const u8, region.length).into_owned() } else { String::new() }; let zone_str = if !zone.ptr.is_null() && zone.length > 0 { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - zone.ptr as *const _, - zone.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(zone.ptr as *const u8, zone.length).into_owned() } else { String::new() }; let sub_zone_str = if !sub_zone.ptr.is_null() && sub_zone.length > 0 { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - sub_zone.ptr as *const _, - sub_zone.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(sub_zone.ptr as *const u8, sub_zone.length) + .into_owned() } else { String::new() }; @@ -458,11 +437,8 @@ impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl { if found && !result.ptr.is_null() && result.length > 0 { unsafe { Some( - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const _, - result.length, - )) - .to_string(), + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length) + .into_owned(), ) } } else { @@ -572,11 +548,8 @@ impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl { if found && !result.ptr.is_null() && result.length > 0 { unsafe { Some( - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const _, - result.length, - )) - .to_string(), + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length) + .into_owned(), ) } } else { @@ -609,11 +582,7 @@ impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl { }; if found && !result.ptr.is_null() && result.length > 0 { Some(unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const _, - result.length, - )) - .to_string() + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length).into_owned() }) } else { None @@ -679,16 +648,10 @@ impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl { .iter() .map(|h| unsafe { ( - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - h.key_ptr as *const _, - h.key_length, - )) - .to_string(), - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - h.value_ptr as *const _, - h.value_length, - )) - .to_string(), + crate::ffi_helpers::str_lossy_from_raw(h.key_ptr as *const u8, h.key_length) + .into_owned(), + crate::ffi_helpers::str_lossy_from_raw(h.value_ptr as *const u8, h.value_length) + .into_owned(), ) }) .collect(), @@ -720,11 +683,8 @@ impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl { if found { unsafe { Some(( - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const _, - result.length, - )) - .to_string(), + crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length) + .into_owned(), count, )) } @@ -775,11 +735,8 @@ impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl { if found { unsafe { Some(( - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - .to_string(), + crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) + .into_owned(), strict, )) } @@ -1310,18 +1267,18 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_lb_config_new( config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_lb_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { - let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )); - let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length); + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) + }; let new_config_fn = NEW_LOAD_BALANCER_CONFIG_FUNCTION .get() .expect("NEW_LOAD_BALANCER_CONFIG_FUNCTION must be set"); let envoy_lb_config: Arc = Arc::new(EnvoyLbConfigImpl { raw: lb_config_envoy_ptr, }); - match new_config_fn(name_str, config_slice, envoy_lb_config) { + match new_config_fn(name_str.as_ref(), config_slice, envoy_lb_config) { Some(config) => wrap_into_c_void_ptr!(config), None => std::ptr::null(), } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/matcher.rs b/source/extensions/dynamic_modules/sdk/rust/src/matcher.rs index 15d69a68b1c68..cd474ad731952 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/matcher.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/matcher.rs @@ -5,7 +5,7 @@ use crate::abi; use std::ffi::c_void; -use std::{ptr, slice}; +use std::ptr; /// Read-only context for accessing HTTP matching data during match evaluation. /// @@ -92,8 +92,8 @@ impl MatchContext { .iter() .map(|h| unsafe { ( - slice::from_raw_parts(h.key_ptr as *const u8, h.key_length), - slice::from_raw_parts(h.value_ptr as *const u8, h.value_length), + crate::ffi_helpers::slice_from_raw_or_empty(h.key_ptr as *const u8, h.key_length), + crate::ffi_helpers::slice_from_raw_or_empty(h.value_ptr as *const u8, h.value_length), ) }) .collect(), @@ -125,7 +125,7 @@ impl MatchContext { ) } { unsafe { - Some(slice::from_raw_parts( + Some(crate::ffi_helpers::slice_from_raw_or_empty( result.ptr as *const u8, result.length, )) @@ -206,12 +206,17 @@ macro_rules! declare_matcher { config: $crate::abi::envoy_dynamic_module_type_envoy_buffer, ) -> *const ::std::ffi::c_void { ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { - let name_str = unsafe { - let slice = ::std::slice::from_raw_parts(name.ptr as *const u8, name.length); - ::std::str::from_utf8(slice).unwrap_or("") + // Safe under `(nullptr, 0)` via `ffi_helpers`. The matcher name is used as a registry + // lookup key by user `MatcherConfig::new` implementations, so invalid UTF-8 must map to + // the empty string rather than being rewritten with `U+FFFD` substitutions; the latter + // would silently route invalid bytes to a different registry entry. + let name_slice = unsafe { + $crate::ffi_helpers::slice_from_raw_or_empty(name.ptr as *const u8, name.length) + }; + let name_str: &str = ::std::str::from_utf8(name_slice).unwrap_or(""); + let config_bytes = unsafe { + $crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) }; - let config_bytes = - unsafe { ::std::slice::from_raw_parts(config.ptr as *const u8, config.length) }; match <$config_type as $crate::matcher::MatcherConfig>::new(name_str, config_bytes) { Ok(c) => Box::into_raw(Box::new(c)) as *const ::std::ffi::c_void, diff --git a/source/extensions/dynamic_modules/sdk/rust/src/network.rs b/source/extensions/dynamic_modules/sdk/rust/src/network.rs index 109094e2b54fa..92b9838de0144 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/network.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/network.rs @@ -886,13 +886,9 @@ impl EnvoyNetworkFilter for EnvoyNetworkFilterImpl { if !result || address.length == 0 || address.ptr.is_null() { return (String::new(), 0); } - let address = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - }; - (address.to_string(), port) + let address = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + (address.into_owned(), port) } fn get_local_address(&self) -> (String, u32) { @@ -911,13 +907,9 @@ impl EnvoyNetworkFilter for EnvoyNetworkFilterImpl { if !result || address.length == 0 || address.ptr.is_null() { return (String::new(), 0); } - let address = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - }; - (address.to_string(), port) + let address = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + (address.into_owned(), port) } fn is_ssl(&self) -> bool { @@ -1162,13 +1154,9 @@ impl EnvoyNetworkFilter for EnvoyNetworkFilterImpl { ) }; if success && !result.ptr.is_null() && result.length > 0 { - let value_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - result.ptr as *const _, - result.length, - )) - }; - Some(value_str.to_string()) + let value_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(result.ptr as *const u8, result.length) }; + Some(value_str.into_owned()) } else { None } @@ -1304,7 +1292,9 @@ impl EnvoyNetworkFilter for EnvoyNetworkFilterImpl { ) }; if success && !result.ptr.is_null() && result.length > 0 { - let slice = unsafe { std::slice::from_raw_parts(result.ptr as *const u8, result.length) }; + let slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(result.ptr as *const u8, result.length) + }; Some(slice.to_vec()) } else { None @@ -1345,8 +1335,11 @@ impl EnvoyNetworkFilter for EnvoyNetworkFilterImpl { abi::envoy_dynamic_module_type_socket_option_value_type::Bytes => { if !opt.byte_value.ptr.is_null() && opt.byte_value.length > 0 { let bytes = unsafe { - std::slice::from_raw_parts(opt.byte_value.ptr as *const u8, opt.byte_value.length) - .to_vec() + crate::ffi_helpers::slice_from_raw_or_empty( + opt.byte_value.ptr as *const u8, + opt.byte_value.length, + ) + .to_vec() }; SocketOptionValue::Bytes(bytes) } else { @@ -1530,13 +1523,9 @@ impl EnvoyNetworkFilter for EnvoyNetworkFilterImpl { if !result || address.length == 0 || address.ptr.is_null() { return None; } - let address_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - }; - Some((address_str.to_string(), port)) + let address_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + Some((address_str.into_owned(), port)) } fn get_upstream_host_hostname(&self) -> Option { @@ -1553,13 +1542,9 @@ impl EnvoyNetworkFilter for EnvoyNetworkFilterImpl { if !result || hostname.length == 0 || hostname.ptr.is_null() { return None; } - let hostname_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - hostname.ptr as *const _, - hostname.length, - )) - }; - Some(hostname_str.to_string()) + let hostname_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(hostname.ptr as *const u8, hostname.length) }; + Some(hostname_str.into_owned()) } fn get_upstream_host_cluster(&self) -> Option { @@ -1577,12 +1562,9 @@ impl EnvoyNetworkFilter for EnvoyNetworkFilterImpl { return None; } let cluster_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - cluster_name.ptr as *const _, - cluster_name.length, - )) + crate::ffi_helpers::str_lossy_from_raw(cluster_name.ptr as *const u8, cluster_name.length) }; - Some(cluster_str.to_string()) + Some(cluster_str.into_owned()) } fn has_upstream_host(&self) -> bool { @@ -1656,16 +1638,14 @@ pub extern "C" fn envoy_dynamic_module_on_network_filter_config_new( ) -> abi::envoy_dynamic_module_type_network_filter_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { let mut envoy_filter_config = EnvoyNetworkFilterConfigImpl::new(envoy_filter_config_ptr); - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; init_network_filter_config( &mut envoy_filter_config, - name_str, + name_str.as_ref(), config_slice, NEW_NETWORK_FILTER_CONFIG_FUNCTION .get() @@ -1855,31 +1835,25 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_network_filter_http_callout_don let filter = unsafe { &mut *filter }; // Convert headers to Vec<(EnvoyBuffer, EnvoyBuffer)>. - let header_vec = if headers.is_null() || headers_size == 0 { - Vec::new() - } else { - let headers_slice = unsafe { std::slice::from_raw_parts(headers, headers_size) }; - headers_slice - .iter() - .map(|h| { - ( - unsafe { EnvoyBuffer::new_from_raw(h.key_ptr as *const _, h.key_length) }, - unsafe { EnvoyBuffer::new_from_raw(h.value_ptr as *const _, h.value_length) }, - ) - }) - .collect() - }; + let headers_slice = + unsafe { crate::ffi_helpers::slice_from_raw_or_empty(headers, headers_size) }; + let header_vec: Vec<(EnvoyBuffer, EnvoyBuffer)> = headers_slice + .iter() + .map(|h| { + ( + unsafe { EnvoyBuffer::new_from_raw(h.key_ptr as *const _, h.key_length) }, + unsafe { EnvoyBuffer::new_from_raw(h.value_ptr as *const _, h.value_length) }, + ) + }) + .collect(); // Convert body chunks to Vec. - let body_vec = if body_chunks.is_null() || body_chunks_size == 0 { - Vec::new() - } else { - let chunks_slice = unsafe { std::slice::from_raw_parts(body_chunks, body_chunks_size) }; - chunks_slice - .iter() - .map(|c| unsafe { EnvoyBuffer::new_from_raw(c.ptr as *const _, c.length) }) - .collect() - }; + let chunks_slice = + unsafe { crate::ffi_helpers::slice_from_raw_or_empty(body_chunks, body_chunks_size) }; + let body_vec: Vec = chunks_slice + .iter() + .map(|c| unsafe { EnvoyBuffer::new_from_raw(c.ptr as *const _, c.length) }) + .collect(); filter.on_http_callout_done( &mut EnvoyNetworkFilterImpl::new(envoy_ptr), diff --git a/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs b/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs index 0490621f64005..e35b525d54fe3 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/tracer.rs @@ -448,7 +448,9 @@ impl EnvoyTracerSpanImpl { }; let found = unsafe { getter(self.raw, &mut result) }; if found && !result.ptr.is_null() { - let slice = unsafe { std::slice::from_raw_parts(result.ptr as *const u8, result.length) }; + let slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(result.ptr as *const u8, result.length) + }; Some(slice.to_vec()) } else { None @@ -471,7 +473,9 @@ impl EnvoyTracerSpan for EnvoyTracerSpanImpl { ) }; if found && !result.ptr.is_null() { - let slice = unsafe { std::slice::from_raw_parts(result.ptr as *const u8, result.length) }; + let slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(result.ptr as *const u8, result.length) + }; Some(slice.to_vec()) } else { None @@ -551,15 +555,18 @@ impl SpanWrapper { // Panic-safe FFI helper // ----------------------------------------------------------------------------- -/// Decode an envoy_buffer into a `&str` without UTF-8 validation. +/// Decode an envoy_buffer into a `Cow<'static, str>` with lossy UTF-8 fallback. +/// +/// Routes through [`crate::ffi_helpers::str_lossy_from_raw`] so a malformed input on the FFI +/// seam produces a lossy decode rather than undefined behaviour. /// /// # Safety /// -/// The buffer must contain valid UTF-8 data and the pointer must be valid for the given length. -unsafe fn envoy_buffer_to_str(buf: abi::envoy_dynamic_module_type_envoy_buffer) -> &'static str { - unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts(buf.ptr as *const _, buf.length)) - } +/// Same constraints as [`crate::ffi_helpers::str_lossy_from_raw`]. +unsafe fn envoy_buffer_to_str( + buf: abi::envoy_dynamic_module_type_envoy_buffer, +) -> std::borrow::Cow<'static, str> { + unsafe { crate::ffi_helpers::str_lossy_from_raw(buf.ptr as *const u8, buf.length) } } // ----------------------------------------------------------------------------- @@ -578,14 +585,16 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_config_new( ) -> abi::envoy_dynamic_module_type_tracer_config_module_ptr { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let name_str = unsafe { envoy_buffer_to_str(name) }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) + }; let ctx = TracerConfigContext { envoy_ptr: config_envoy_ptr, }; let new_config_fn = NEW_TRACER_CONFIG_FUNCTION .get() .expect("NEW_TRACER_CONFIG_FUNCTION must be set"); - match new_config_fn(ctx, name_str, config_slice) { + match new_config_fn(ctx, name_str.as_ref(), config_slice) { Some(config) => wrap_into_c_void_ptr!(config), None => std::ptr::null(), } @@ -636,7 +645,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_start_span( let config = unsafe { &**config }; let envoy_span = EnvoyTracerSpanImpl::new(span_envoy_ptr); let op_name = unsafe { envoy_buffer_to_str(operation_name) }; - match config.start_span(&envoy_span, op_name, traced, reason.into()) { + match config.start_span(&envoy_span, op_name.as_ref(), traced, reason.into()) { Some(span) => { let wrapper = SpanWrapper::new(span); wrap_into_c_void_ptr!(wrapper) @@ -665,7 +674,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_span_set_operation( let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let wrapper = unsafe { &mut *(span_module_ptr as *mut SpanWrapper) }; let op = unsafe { envoy_buffer_to_str(operation) }; - wrapper.inner.set_operation(op); + wrapper.inner.set_operation(op.as_ref()); })); } @@ -683,7 +692,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_span_set_tag( let wrapper = unsafe { &mut *(span_module_ptr as *mut SpanWrapper) }; let k = unsafe { envoy_buffer_to_str(key) }; let v = unsafe { envoy_buffer_to_str(value) }; - wrapper.inner.set_tag(k, v); + wrapper.inner.set_tag(k.as_ref(), v.as_ref()); })); } @@ -700,7 +709,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_span_log( let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let wrapper = unsafe { &mut *(span_module_ptr as *mut SpanWrapper) }; let e = unsafe { envoy_buffer_to_str(event) }; - wrapper.inner.log(timestamp_ns, e); + wrapper.inner.log(timestamp_ns, e.as_ref()); })); } @@ -747,7 +756,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_span_spawn_child( let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let wrapper = unsafe { &mut *(span_module_ptr as *mut SpanWrapper) }; let n = unsafe { envoy_buffer_to_str(name) }; - match wrapper.inner.spawn_child(n, start_time_ns) { + match wrapper.inner.spawn_child(n.as_ref(), start_time_ns) { Some(child) => { let child_wrapper = SpanWrapper::new(child); wrap_into_c_void_ptr!(child_wrapper) @@ -807,7 +816,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_span_get_baggage( let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let wrapper = unsafe { &mut *(span_module_ptr as *mut SpanWrapper) }; let k = unsafe { envoy_buffer_to_str(key) }; - match wrapper.inner.get_baggage(k) { + match wrapper.inner.get_baggage(k.as_ref()) { Some(val) => { wrapper.write_scratch_to_out(val, value_out); true @@ -847,7 +856,7 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_tracer_span_set_baggage( let wrapper = unsafe { &mut *(span_module_ptr as *mut SpanWrapper) }; let k = unsafe { envoy_buffer_to_str(key) }; let v = unsafe { envoy_buffer_to_str(value) }; - wrapper.inner.set_baggage(k, v); + wrapper.inner.set_baggage(k.as_ref(), v.as_ref()); })); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/transport_socket.rs b/source/extensions/dynamic_modules/sdk/rust/src/transport_socket.rs index 89fb18827bd16..dd1d4568e981e 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/transport_socket.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/transport_socket.rs @@ -406,8 +406,9 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_factory_config is_upstream: bool, ) -> abi::envoy_dynamic_module_type_transport_socket_factory_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { - let name_bytes = - unsafe { std::slice::from_raw_parts(socket_name.ptr as *const u8, socket_name.length) }; + let name_bytes = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(socket_name.ptr as *const u8, socket_name.length) + }; let name_str = match std::str::from_utf8(name_bytes) { Ok(s) => s, Err(_) => { @@ -415,8 +416,12 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_transport_socket_factory_config return std::ptr::null(); }, }; - let config_slice = - unsafe { std::slice::from_raw_parts(socket_config.ptr as *const u8, socket_config.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty( + socket_config.ptr as *const u8, + socket_config.length, + ) + }; let Some(new_config_fn) = crate::NEW_TRANSPORT_SOCKET_FACTORY_CONFIG_FUNCTION.get() else { crate::envoy_log_error!( "transport socket factory config function not registered; ensure \ diff --git a/source/extensions/dynamic_modules/sdk/rust/src/udp_listener.rs b/source/extensions/dynamic_modules/sdk/rust/src/udp_listener.rs index dfeabd505d82c..ebf4a459381ba 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/udp_listener.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/udp_listener.rs @@ -259,13 +259,9 @@ impl EnvoyUdpListenerFilter for EnvoyUdpListenerFilterImpl { if !result || address.length == 0 || address.ptr.is_null() { return None; } - let address_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - }; - Some((address_str.to_string(), port)) + let address_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + Some((address_str.into_owned(), port)) } fn get_local_address(&self) -> Option<(String, u32)> { @@ -284,13 +280,9 @@ impl EnvoyUdpListenerFilter for EnvoyUdpListenerFilterImpl { if !result || address.length == 0 || address.ptr.is_null() { return None; } - let address_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - address.ptr as *const _, - address.length, - )) - }; - Some((address_str.to_string(), port)) + let address_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(address.ptr as *const u8, address.length) }; + Some((address_str.into_owned(), port)) } fn send_datagram(&mut self, data: &[u8], peer_address: &str, peer_port: u32) -> bool { @@ -401,16 +393,14 @@ pub extern "C" fn envoy_dynamic_module_on_udp_listener_filter_config_new( ) -> abi::envoy_dynamic_module_type_udp_listener_filter_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { let mut envoy_filter_config = EnvoyUdpListenerFilterConfigImpl::new(envoy_filter_config_ptr); - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; init_udp_listener_filter_config( &mut envoy_filter_config, - name_str, + name_str.as_ref(), config_slice, NEW_UDP_LISTENER_FILTER_CONFIG_FUNCTION .get() diff --git a/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs b/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs index c2ce56916825a..a4b572b6cbaa0 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/upstream_http_tcp_bridge.rs @@ -140,7 +140,8 @@ impl EnvoyUpstreamHttpTcpBridgeImpl { let mut result = Vec::new(); for buf in buffers.iter().take(num_slices) { if !buf.ptr.is_null() && buf.length > 0 { - let slice = unsafe { std::slice::from_raw_parts(buf.ptr as *const u8, buf.length) }; + let slice = + unsafe { crate::ffi_helpers::slice_from_raw_or_empty(buf.ptr as *const u8, buf.length) }; result.extend_from_slice(slice); } } @@ -180,7 +181,9 @@ impl EnvoyUpstreamHttpTcpBridge for EnvoyUpstreamHttpTcpBridgeImpl { ) }; if found && !result.ptr.is_null() { - let slice = unsafe { std::slice::from_raw_parts(result.ptr as *const u8, result.length) }; + let slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(result.ptr as *const u8, result.length) + }; (Some(slice.to_vec()), total_count) } else { (None, total_count) @@ -220,8 +223,10 @@ impl EnvoyUpstreamHttpTcpBridge for EnvoyUpstreamHttpTcpBridgeImpl { .iter() .map(|h| unsafe { ( - std::slice::from_raw_parts(h.key_ptr as *const u8, h.key_length).to_vec(), - std::slice::from_raw_parts(h.value_ptr as *const u8, h.value_length).to_vec(), + crate::ffi_helpers::slice_from_raw_or_empty(h.key_ptr as *const u8, h.key_length) + .to_vec(), + crate::ffi_helpers::slice_from_raw_or_empty(h.value_ptr as *const u8, h.value_length) + .to_vec(), ) }) .collect() @@ -320,17 +325,15 @@ pub extern "C" fn envoy_dynamic_module_on_upstream_http_tcp_bridge_config_new( config: abi::envoy_dynamic_module_type_envoy_buffer, ) -> abi::envoy_dynamic_module_type_upstream_http_tcp_bridge_config_module_ptr { catch_unwind(AssertUnwindSafe(|| { - let name_str = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - name.ptr as *const _, - name.length, - )) + let name_str = + unsafe { crate::ffi_helpers::str_lossy_from_raw(name.ptr as *const u8, name.length) }; + let config_slice = unsafe { + crate::ffi_helpers::slice_from_raw_or_empty(config.ptr as *const u8, config.length) }; - let config_slice = unsafe { std::slice::from_raw_parts(config.ptr as *const _, config.length) }; let new_config_fn = NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION .get() .expect("NEW_UPSTREAM_HTTP_TCP_BRIDGE_CONFIG_FUNCTION must be set"); - match new_config_fn(name_str, config_slice) { + match new_config_fn(name_str.as_ref(), config_slice) { Some(config) => wrap_into_c_void_ptr!(config), None => std::ptr::null(), } From 352042274a0a88dfe89af9f10ff43a6c7f2b785a Mon Sep 17 00:00:00 2001 From: Raven Black Date: Thu, 7 May 2026 17:52:53 -0400 Subject: [PATCH 259/750] Make integration test metric matchers more flexible (#44864) Commit Message: Make integration test metric matchers more flexible Additional Description: The meat of the change of this PR is in five files: `mobile/test/common/integration/base_client_integration_test.h` (and `.cc`), `test/test_common/utility.h` (and `.cc`), and `test/integration/server.h` - everything else is pretty much mechanically just replacing `waitForCounterGe(..., n[, ...])` with `waitForCounter(..., Ge(n)[, ...])` or the equivalent for `Eq` and `Gauge`, and adding `using testing::Ge;` etc. when necessary. To make the change self-documenting to minimize disruption to external extensions calling these functions, I've included static assertions on the old function names directing to the new methods. While it was an option to add the new flexible matcher and just implement the existing ones in terms of the new one, that would leave the test code in a state where your classic "copy code from another example" will keep perpetuating use of the inflexible model, and creation of new, similar inflexible models. Fully migrating to the better test helpers gives better future code cleanliness and maintainability (at the cost of a horrible one-time review). To be clear, the benefits of this change are: 1. there's only two functions [in each place] instead of four. 2. Those functions also enable `waitForCounter(someCounter, Le(1000))` which otherwise would require a custom implementation in a test, or adding a new shared helper function. Risk Level: Test-only. Testing: Yes it is. Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a --------- Signed-off-by: Raven Black --- .../kv_store_xds_delegate_integration_test.cc | 20 +- .../network/test/mysql_integration_test.cc | 9 +- .../test/mysql_ssl_integration_test.cc | 35 +- .../postgres_inspector_integration_test.cc | 23 +- ...postgres_inspector_sni_integration_test.cc | 25 +- .../network/test/postgres_integration_test.cc | 31 +- .../udp_sink/test/udp_sink_interation_test.cc | 2 +- .../mobile_grpc_streamer_integration_test.cc | 6 +- .../base_client_integration_test.cc | 32 +- .../base_client_integration_test.h | 10 +- .../integration/cds_integration_test.cc | 11 +- .../integration/client_integration_test.cc | 44 +-- .../integration/rtds_integration_test.cc | 5 +- .../integration/sds_integration_test.cc | 11 +- .../access_log_manager_impl_test.cc | 26 +- .../default_validator_integration_test.cc | 13 +- .../tls/integration/ssl_integration_test.cc | 24 +- .../ssl_upstream_integration_test.cc | 2 +- test/config/utility.cc | 5 +- .../process_ratelimit/integration_test.cc | 2 +- .../fluentd_access_log_integration_test.cc | 73 ++-- .../http_grpc_access_log_integration_test.cc | 18 +- .../access_log_integration_test.cc | 5 +- .../access_loggers/stats/integration_test.cc | 55 +-- .../internal_listener_integration_test.cc | 13 +- .../aggregate/cluster_integration_test.cc | 340 +++++++++--------- .../composite/cluster_integration_test.cc | 2 +- .../cluster_integration_test.cc | 2 +- .../redis/redis_cluster_integration_test.cc | 5 +- ...rse_connection_cluster_integration_test.cc | 162 ++++----- ...mum_clusters_validator_integration_test.cc | 3 +- .../grpc/xds_failover_integration_test.cc | 88 ++--- ...ive_concurrency_filter_integration_test.cc | 9 +- .../admission_control_filter_test.cc | 29 +- .../filter_integration_test.cc | 70 ++-- .../aws_request_signing_integration_test.cc | 155 ++++---- .../composite_filter_integration_test.cc | 31 +- .../compressor_integration_tests.cc | 5 +- .../decompressor_filter_integration_test.cc | 86 ++--- .../proxy_filter_integration_test.cc | 86 ++--- .../ext_authz/ext_authz_integration_test.cc | 10 +- .../ext_proc/ext_proc_integration_common.cc | 5 +- .../ext_proc/ext_proc_integration_test.cc | 24 +- .../ext_proc_misc_integration_test.cc | 2 +- .../fault/fault_filter_integration_test.cc | 9 +- .../geoip/geoip_filter_integration_test.cc | 41 +-- .../http/jwt_authn/filter_integration_test.cc | 2 +- .../local_ratelimit_integration_test.cc | 12 +- .../filters/http/lua/lua_integration_test.cc | 28 +- .../mcp_router/mcp_router_integration_test.cc | 27 +- .../http/oauth2/oauth_integration_test.cc | 20 +- .../http/on_demand/odcds_integration_test.cc | 5 +- .../on_demand/on_demand_integration_test.cc | 9 +- .../proto_api_scrubber/integration_test.cc | 9 +- .../filter_persistence_test.cc | 17 +- .../ratelimit/ratelimit_integration_test.cc | 5 +- .../http/rbac/rbac_filter_integration_test.cc | 5 +- .../http/tap/tap_filter_integration_test.cc | 27 +- .../http/wasm/wasm_filter_integration_test.cc | 8 +- .../http_inspector_integration_test.cc | 7 +- .../local_ratelimit_integration_test.cc | 3 +- .../original_dst_integration_test.cc | 2 +- .../connection_limit_integration_test.cc | 13 +- .../ext_authz/ext_authz_integration_test.cc | 15 +- .../ext_proc/ext_proc_integration_test.cc | 7 +- .../geoip/geoip_filter_integration_test.cc | 23 +- .../filters/network/rbac/integration_test.cc | 9 +- .../reverse_tunnel/integration_test.cc | 62 ++-- .../proxy_filter_integration_test.cc | 16 +- .../tcp_bandwidth_limit_integration_test.cc | 12 +- .../network/thrift_proxy/integration_test.cc | 7 +- .../proxy_filter_integration_test.cc | 41 +-- .../http_capsule_integration_test.cc | 11 +- .../udp_proxy/udp_proxy_integration_test.cc | 71 ++-- ...on_extension_discovery_integration_test.cc | 172 ++++----- .../generic_secret_integration_test.cc | 2 +- ...dential_injector_oauth_integration_test.cc | 73 ++-- .../integration_test.cc | 24 +- .../wrr_locality/integration_test.cc | 5 +- .../quic_stats/integration_test.cc | 5 +- .../cx_limit_overload_integration_test.cc | 13 +- ...ected_resource_monitor_integration_test.cc | 20 +- .../metrics_service_integration_test.cc | 6 +- .../open_telemetry_integration_test.cc | 8 +- .../internal_upstream_integration_test.cc | 3 +- .../tap/ssl_tap_integration_test.cc | 21 +- .../tcp_stats/tcp_stats_integration_test.cc | 5 +- .../on_demand/integration_test.cc | 34 +- test/integration/ads_integration.cc | 10 +- test/integration/ads_integration_test.cc | 246 ++++++------- ...s_xdstp_config_sources_integration_test.cc | 15 +- test/integration/alpn_integration_test.cc | 24 +- .../buffer_accounting_integration_test.cc | 205 ++++++----- test/integration/cds_integration_test.cc | 84 ++--- .../circuit_breakers_integration_test.cc | 20 +- ..._http_protocol_options_integration_test.cc | 17 +- .../custom_cluster_integration_test.cc | 2 +- test/integration/cx_limit_integration_test.cc | 15 +- .../drain_close_integration_test.cc | 7 +- test/integration/eds_integration_test.cc | 48 +-- .../extension_discovery_integration_test.cc | 122 +++---- test/integration/filter_integration_test.cc | 2 +- .../filter_manager_integration_test.cc | 2 +- test/integration/hds_integration_test.cc | 53 +-- .../health_check_integration_test.cc | 86 ++--- .../http2_flood_integration_test.cc | 22 +- .../http_conn_pool_integration_test.cc | 25 +- test/integration/http_integration.cc | 15 +- .../http_timeout_integration_test.cc | 2 +- .../idle_timeout_integration_test.cc | 29 +- test/integration/integration_admin_test.cc | 12 +- test/integration/integration_test.cc | 70 ++-- test/integration/leds_integration_test.cc | 70 ++-- ...er_extension_discovery_integration_test.cc | 149 ++++---- .../listener_lds_integration_test.cc | 175 ++++----- .../load_stats_integration_test.cc | 12 +- .../multiplexed_integration_test.cc | 125 +++---- .../multiplexed_upstream_integration_test.cc | 24 +- ...rk_extension_discovery_integration_test.cc | 151 ++++---- test/integration/overload_integration_test.cc | 256 +++++++------ test/integration/protocol_integration_test.cc | 98 ++--- .../integration/quic_http_integration_test.cc | 81 +++-- test/integration/quic_http_integration_test.h | 44 +-- test/integration/redirect_integration_test.cc | 3 +- test/integration/rtds_integration_test.cc | 14 +- .../scoped_rds_integration_test.cc | 83 +++-- .../sds_dynamic_integration_test.cc | 100 +++--- test/integration/server.h | 42 ++- test/integration/server_stats.h | 41 +-- .../shadow_policy_integration_test.cc | 38 +- .../tcp_async_client_integration_test.cc | 86 ++--- .../tcp_conn_pool_integration_test.cc | 9 +- .../integration/tcp_proxy_integration_test.cc | 47 +-- .../tcp_proxy_many_connections_test.cc | 4 +- .../tcp_proxy_odcds_integration_test.cc | 20 +- .../tcp_tunneling_integration_test.cc | 28 +- .../udp_tunneling_integration_test.cc | 138 +++---- .../upstream_http_filter_integration_test.cc | 72 ++-- ...pstream_network_filter_integration_test.cc | 112 +++--- .../integration/websocket_integration_test.cc | 48 +-- .../weighted_cluster_integration_test.cc | 2 +- .../xds_config_tracker_integration_test.cc | 22 +- ...xds_delegate_extension_integration_test.cc | 5 +- test/integration/xds_integration_test.cc | 54 +-- .../xdstp_config_sources_integration_test.cc | 20 +- test/server/config_validation/xds_fuzz.cc | 29 +- test/server/server_test.cc | 15 +- test/test_common/utility.cc | 78 ++-- test/test_common/utility.h | 86 ++--- 149 files changed, 3140 insertions(+), 2902 deletions(-) diff --git a/contrib/config/test/kv_store_xds_delegate_integration_test.cc b/contrib/config/test/kv_store_xds_delegate_integration_test.cc index 924e148225e88..958394ea67bfd 100644 --- a/contrib/config/test/kv_store_xds_delegate_integration_test.cc +++ b/contrib/config/test/kv_store_xds_delegate_integration_test.cc @@ -17,6 +17,8 @@ #include "contrib/config/test/invalid_proto_kv_store_config.pb.h" #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -339,11 +341,11 @@ TEST_P(KeyValueStoreXdsDelegateIntegrationTest, BasicSuccess) { initialize(); // Wait until the discovery responses have been processed. - test_server_->waitForCounterGe( - "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + test_server_->waitForCounter( + "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", Ge(1)); // Initial snapshot load, post-listener-manager TLS refresh (onWorkerThreadsRegistered), and // first RTDS response each increment runtime.load_success; wait until all have completed. - test_server_->waitForCounterGe("runtime.load_success", 3); + test_server_->waitForCounter("runtime.load_success", Ge(3)); // Verify that the xDS resources are used by Envoy. checkSecretExists(std::string(CLIENT_CERT_NAME), /*version_info=*/"1"); @@ -366,7 +368,7 @@ TEST_P(KeyValueStoreXdsDelegateIntegrationTest, BasicSuccess) { test_server_->counter("runtime.load_success")->value(); sendSotwDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {rtds_resource}, "2", rtds_stream_.get()); - test_server_->waitForCounterGe("runtime.load_success", runtime_loads_before_rtds_v2 + 1); + test_server_->waitForCounter("runtime.load_success", Ge(runtime_loads_before_rtds_v2 + 1)); EXPECT_EQ("whatevs", getRuntimeKey("foo")); EXPECT_EQ("yar", getRuntimeKey("bar")); @@ -376,10 +378,10 @@ TEST_P(KeyValueStoreXdsDelegateIntegrationTest, BasicSuccess) { shutdownAndRestartTestServer(); // Wait until SDS and RTDS have been loaded from the KV store and updated the Envoy instance. - test_server_->waitForCounterGe( - "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + test_server_->waitForCounter( + "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", Ge(1)); // Expect initial snapshot load, post-listener-manager TLS refresh, and RTDS restored from KV. - test_server_->waitForCounterGe("runtime.load_success", 3); + test_server_->waitForCounter("runtime.load_success", Ge(3)); // Verify that the latest resource values in the KV store are used by Envoy. EXPECT_EQ(2, test_server_->counter("xds.kv_store.load_success")->value()); @@ -407,7 +409,7 @@ TEST_P(KeyValueStoreXdsDelegateIntegrationTest, BasicSuccess) { sendSotwDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {rtds_resource_v2}, /*version=*/"3", rtds_stream_.get()); - test_server_->waitForCounterGe("runtime.load_success", runtime_loads_before_rtds_v3 + 1); + test_server_->waitForCounter("runtime.load_success", Ge(runtime_loads_before_rtds_v3 + 1)); // Verify that the values from the xDS response are used instead of from the persisted xDS once // connectivity is re-established. @@ -544,7 +546,7 @@ TEST_P(InvalidProtoKeyValueStoreXdsDelegateIntegrationTest, InvalidProto) { initialize(); // Make sure that the proto parsing of a serialized resource with an invalid enum value fails. - test_server_->waitForCounterEq("xds.kv_store.xds_load_failed", 1); + test_server_->waitForCounter("xds.kv_store.xds_load_failed", Eq(1)); } } // namespace diff --git a/contrib/mysql_proxy/filters/network/test/mysql_integration_test.cc b/contrib/mysql_proxy/filters/network/test/mysql_integration_test.cc index 282dc83bdb0ed..61745354e3980 100644 --- a/contrib/mysql_proxy/filters/network/test/mysql_integration_test.cc +++ b/contrib/mysql_proxy/filters/network/test/mysql_integration_test.cc @@ -13,6 +13,7 @@ #include "gtest/gtest.h" #include "mysql_test_utils.h" +using testing::Ge; namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -57,7 +58,7 @@ TEST_P(MySQLIntegrationTest, MySQLStatsNewSessionTest) { ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); } - test_server_->waitForCounterGe("mysql.mysql_stats.sessions", SESSIONS); + test_server_->waitForCounter("mysql.mysql_stats.sessions", Ge(SESSIONS)); } /** @@ -99,7 +100,7 @@ TEST_P(MySQLIntegrationTest, MySQLLoginTest) { tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + test_server_->waitForCounter("mysql.mysql_stats.login_attempts", Ge(1)); EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 0); } @@ -149,7 +150,7 @@ TEST_P(MySQLIntegrationTest, DISABLED_MySQLUnitTestMultiClientsLoop) { } // Verify counters: CLIENT_NUM login attempts, no failures - test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", CLIENT_NUM); + test_server_->waitForCounter("mysql.mysql_stats.login_attempts", Ge(CLIENT_NUM)); EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_attempts")->value(), CLIENT_NUM); EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 0); } @@ -193,7 +194,7 @@ TEST_P(MySQLIntegrationTest, MySQLLoginFailTest) { tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + test_server_->waitForCounter("mysql.mysql_stats.login_attempts", Ge(1)); EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 1); } diff --git a/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc b/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc index 69bbda2378b9c..0f3e571293f70 100644 --- a/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc +++ b/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc @@ -22,6 +22,7 @@ #include "openssl/evp.h" #include "openssl/pem.h" +using testing::Ge; namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -115,7 +116,7 @@ class MySQLSSLIntegrationTest : public testing::TestWithParamwaitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); + test_server_->waitForCounter("mysql.mysql_stats.upgraded_to_ssl", Ge(1)); conn_->upgradeToTls(tls_context_->createTransportSocket( std::make_shared( absl::string_view(""), std::vector(), std::vector()), @@ -245,8 +246,8 @@ TEST_P(MySQLSSLIntegrationTest, CachingSha2FastAuth) { conn_->close(Network::ConnectionCloseType::FlushWrite); ASSERT_TRUE(fake_upstream->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); - test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + test_server_->waitForCounter("mysql.mysql_stats.upgraded_to_ssl", Ge(1)); + test_server_->waitForCounter("mysql.mysql_stats.login_attempts", Ge(1)); EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 0); } @@ -331,8 +332,8 @@ TEST_P(MySQLSSLIntegrationTest, CachingSha2FullAuthRsaMediation) { conn_->close(Network::ConnectionCloseType::FlushWrite); ASSERT_TRUE(fake_upstream->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); - test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + test_server_->waitForCounter("mysql.mysql_stats.upgraded_to_ssl", Ge(1)); + test_server_->waitForCounter("mysql.mysql_stats.login_attempts", Ge(1)); EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 0); } @@ -396,7 +397,7 @@ TEST_P(MySQLSSLIntegrationTest, CachingSha2FullAuthRsaErr) { conn_->close(Network::ConnectionCloseType::FlushWrite); ASSERT_TRUE(fake_upstream->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.login_failures", 1); + test_server_->waitForCounter("mysql.mysql_stats.login_failures", Ge(1)); } /** @@ -449,9 +450,9 @@ TEST_P(MySQLSSLIntegrationTest, SslTerminateLoginThenQuery) { conn_->close(Network::ConnectionCloseType::FlushWrite); ASSERT_TRUE(fake_upstream->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); - test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); - test_server_->waitForCounterGe("mysql.mysql_stats.queries_parsed", 1); + test_server_->waitForCounter("mysql.mysql_stats.upgraded_to_ssl", Ge(1)); + test_server_->waitForCounter("mysql.mysql_stats.login_attempts", Ge(1)); + test_server_->waitForCounter("mysql.mysql_stats.queries_parsed", Ge(1)); } /** @@ -525,7 +526,7 @@ TEST_P(MySQLSSLIntegrationTest, CachingSha2FullAuthRsaThenQuery) { conn_->close(Network::ConnectionCloseType::FlushWrite); ASSERT_TRUE(fake_upstream->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.queries_parsed", 1); + test_server_->waitForCounter("mysql.mysql_stats.queries_parsed", Ge(1)); } // ============================================================================= @@ -587,7 +588,7 @@ TEST_P(MySQLDisableIntegrationTest, DisableBasicLogin) { tcp_client->close(); ASSERT_TRUE(fake_upstream->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + test_server_->waitForCounter("mysql.mysql_stats.login_attempts", Ge(1)); EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 0); } @@ -686,7 +687,7 @@ class MySQLAllowIntegrationTest : public testing::TestWithParamwaitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); + test_server_->waitForCounter("mysql.mysql_stats.upgraded_to_ssl", Ge(1)); conn_->upgradeToTls(tls_context_->createTransportSocket( std::make_shared( absl::string_view(""), std::vector(), std::vector()), @@ -795,8 +796,8 @@ TEST_P(MySQLAllowIntegrationTest, AllowSslClientLogin) { conn_->close(Network::ConnectionCloseType::FlushWrite); ASSERT_TRUE(fake_upstream->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); - test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + test_server_->waitForCounter("mysql.mysql_stats.upgraded_to_ssl", Ge(1)); + test_server_->waitForCounter("mysql.mysql_stats.login_attempts", Ge(1)); } /** @@ -828,7 +829,7 @@ TEST_P(MySQLAllowIntegrationTest, AllowNonSslClientLogin) { conn_->close(Network::ConnectionCloseType::FlushWrite); ASSERT_TRUE(fake_upstream->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + test_server_->waitForCounter("mysql.mysql_stats.login_attempts", Ge(1)); EXPECT_EQ(test_server_->counter("mysql.mysql_stats.upgraded_to_ssl")->value(), 0); } @@ -897,8 +898,8 @@ TEST_P(MySQLAllowIntegrationTest, AllowSslFullAuthRsaMediation) { conn_->close(Network::ConnectionCloseType::FlushWrite); ASSERT_TRUE(fake_upstream->waitForDisconnect()); - test_server_->waitForCounterGe("mysql.mysql_stats.upgraded_to_ssl", 1); - test_server_->waitForCounterGe("mysql.mysql_stats.login_attempts", 1); + test_server_->waitForCounter("mysql.mysql_stats.upgraded_to_ssl", Ge(1)); + test_server_->waitForCounter("mysql.mysql_stats.login_attempts", Ge(1)); EXPECT_EQ(test_server_->counter("mysql.mysql_stats.login_failures")->value(), 0); } diff --git a/contrib/postgres_inspector/filters/listener/test/postgres_inspector_integration_test.cc b/contrib/postgres_inspector/filters/listener/test/postgres_inspector_integration_test.cc index be5e355b818f4..189fc59ab51e6 100644 --- a/contrib/postgres_inspector/filters/listener/test/postgres_inspector_integration_test.cc +++ b/contrib/postgres_inspector/filters/listener/test/postgres_inspector_integration_test.cc @@ -14,6 +14,7 @@ #include "contrib/postgres_inspector/filters/listener/test/postgres_test_utils.h" #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace Extensions { namespace ListenerFilters { @@ -169,8 +170,8 @@ TEST_P(PostgresInspectorIntegrationTest, DetectsPostgresPre17SSLRequest) { ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(upstream)); // Verify postgres_inspector detected the protocol. - test_server_->waitForCounterGe("postgres_inspector.postgres_found", 1); - test_server_->waitForCounterGe("postgres_inspector.ssl_requested", 1); + test_server_->waitForCounter("postgres_inspector.postgres_found", Ge(1)); + test_server_->waitForCounter("postgres_inspector.ssl_requested", Ge(1)); driver->close(); } @@ -186,8 +187,8 @@ TEST_P(PostgresInspectorIntegrationTest, DetectsPostgres17DirectSSL) { ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(upstream)); // Verify postgres_inspector detected the protocol. - test_server_->waitForCounterGe("postgres_inspector.postgres_found", 1); - test_server_->waitForCounterGe("postgres_inspector.ssl_requested", 1); + test_server_->waitForCounter("postgres_inspector.postgres_found", Ge(1)); + test_server_->waitForCounter("postgres_inspector.ssl_requested", Ge(1)); driver->close(); } @@ -203,8 +204,8 @@ TEST_P(PostgresInspectorIntegrationTest, DetectsPlaintextPostgresStartup) { ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(upstream)); // Verify postgres_inspector detected the protocol. - test_server_->waitForCounterGe("postgres_inspector.postgres_found", 1); - test_server_->waitForCounterGe("postgres_inspector.ssl_not_requested", 1); + test_server_->waitForCounter("postgres_inspector.postgres_found", Ge(1)); + test_server_->waitForCounter("postgres_inspector.ssl_not_requested", Ge(1)); driver->close(); } @@ -220,7 +221,7 @@ TEST_P(PostgresInspectorIntegrationTest, DoesNotDetectNonPostgres) { ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(upstream)); // Verify postgres_inspector did NOT detect PostgreSQL. - test_server_->waitForCounterGe("postgres_inspector.postgres_not_found", 1); + test_server_->waitForCounter("postgres_inspector.postgres_not_found", Ge(1)); driver->close(); } @@ -251,10 +252,10 @@ TEST_P(PostgresInspectorIntegrationTest, MultipleMixedConnectionsDetectedCorrect ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(upstream4)); // Verify stats. - test_server_->waitForCounterGe("postgres_inspector.postgres_found", 3); - test_server_->waitForCounterGe("postgres_inspector.postgres_not_found", 1); - test_server_->waitForCounterGe("postgres_inspector.ssl_requested", 2); - test_server_->waitForCounterGe("postgres_inspector.ssl_not_requested", 1); + test_server_->waitForCounter("postgres_inspector.postgres_found", Ge(3)); + test_server_->waitForCounter("postgres_inspector.postgres_not_found", Ge(1)); + test_server_->waitForCounter("postgres_inspector.ssl_requested", Ge(2)); + test_server_->waitForCounter("postgres_inspector.ssl_not_requested", Ge(1)); driver1->close(); driver2->close(); diff --git a/contrib/postgres_inspector/filters/listener/test/postgres_inspector_sni_integration_test.cc b/contrib/postgres_inspector/filters/listener/test/postgres_inspector_sni_integration_test.cc index 002e7954a2a4a..50a984d75dc1e 100644 --- a/contrib/postgres_inspector/filters/listener/test/postgres_inspector_sni_integration_test.cc +++ b/contrib/postgres_inspector/filters/listener/test/postgres_inspector_sni_integration_test.cc @@ -15,6 +15,7 @@ #include "contrib/postgres_inspector/filters/listener/test/postgres_test_utils.h" #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace Extensions { namespace ListenerFilters { @@ -172,11 +173,11 @@ TEST_P(PostgresInspectorWithProxyIntegrationTest, PostgresPre17DetectedAndRouted ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(upstream)); // Verify postgres_inspector detected the protocol. - test_server_->waitForCounterGe("postgres_inspector.postgres_found", 1); - test_server_->waitForCounterGe("postgres_inspector.ssl_requested", 1); + test_server_->waitForCounter("postgres_inspector.postgres_found", Ge(1)); + test_server_->waitForCounter("postgres_inspector.ssl_requested", Ge(1)); // Verify postgres_proxy is in the filter chain. - test_server_->waitForCounterGe("postgres.postgres_with_ssl.sessions", 1); + test_server_->waitForCounter("postgres.postgres_with_ssl.sessions", Ge(1)); driver->close(); } @@ -192,11 +193,11 @@ TEST_P(PostgresInspectorWithProxyIntegrationTest, Postgres17DirectSSLDetectedAnd ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(upstream)); // Verify postgres_inspector detected the protocol. - test_server_->waitForCounterGe("postgres_inspector.postgres_found", 1); - test_server_->waitForCounterGe("postgres_inspector.ssl_requested", 1); + test_server_->waitForCounter("postgres_inspector.postgres_found", Ge(1)); + test_server_->waitForCounter("postgres_inspector.ssl_requested", Ge(1)); // Verify postgres_proxy is in the filter chain. - test_server_->waitForCounterGe("postgres.postgres_with_ssl.sessions", 1); + test_server_->waitForCounter("postgres.postgres_with_ssl.sessions", Ge(1)); driver->close(); } @@ -212,8 +213,8 @@ TEST_P(PostgresInspectorWithProxyIntegrationTest, PlaintextPostgresDetectedAndRo ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(upstream)); // Verify postgres_inspector detected the protocol. - test_server_->waitForCounterGe("postgres_inspector.postgres_found", 1); - test_server_->waitForCounterGe("postgres_inspector.ssl_not_requested", 1); + test_server_->waitForCounter("postgres_inspector.postgres_found", Ge(1)); + test_server_->waitForCounter("postgres_inspector.ssl_not_requested", Ge(1)); driver->close(); } @@ -235,12 +236,12 @@ TEST_P(PostgresInspectorWithProxyIntegrationTest, MixedVersionConnectionsHandled ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(upstream3)); // Verify stats demonstrate detection of all three types. - test_server_->waitForCounterGe("postgres_inspector.postgres_found", 3); - test_server_->waitForCounterGe("postgres_inspector.ssl_requested", 2); - test_server_->waitForCounterGe("postgres_inspector.ssl_not_requested", 1); + test_server_->waitForCounter("postgres_inspector.postgres_found", Ge(3)); + test_server_->waitForCounter("postgres_inspector.ssl_requested", Ge(2)); + test_server_->waitForCounter("postgres_inspector.ssl_not_requested", Ge(1)); // Verify postgres_proxy handled at least the SSL sessions. - test_server_->waitForCounterGe("postgres.postgres_with_ssl.sessions", 2); + test_server_->waitForCounter("postgres.postgres_with_ssl.sessions", Ge(2)); driver1->close(); driver2->close(); diff --git a/contrib/postgres_proxy/filters/network/test/postgres_integration_test.cc b/contrib/postgres_proxy/filters/network/test/postgres_integration_test.cc index 3783ae7d62a73..a7449dd37e39b 100644 --- a/contrib/postgres_proxy/filters/network/test/postgres_integration_test.cc +++ b/contrib/postgres_proxy/filters/network/test/postgres_integration_test.cc @@ -19,6 +19,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Eq; namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -121,7 +122,7 @@ TEST_P(BasicPostgresIntegrationTest, Login) { ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); // Make sure that the successful login bumped up the number of sessions. - test_server_->waitForCounterEq("postgres.postgres_stats.sessions", 1); + test_server_->waitForCounter("postgres.postgres_stats.sessions", Eq(1)); } INSTANTIATE_TEST_SUITE_P(IpVersions, BasicPostgresIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); @@ -180,7 +181,7 @@ TEST_P(DownstreamSSLPostgresIntegrationTest, TerminateSSL) { ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); // Make sure that the successful login bumped up the number of sessions. - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_terminated_ssl", 1); + test_server_->waitForCounter("postgres.postgres_stats.sessions_terminated_ssl", Eq(1)); } // Test verifies that Postgres filter replies with error code upon @@ -244,7 +245,7 @@ TEST_P(DownstreamSSLWrongConfigPostgresIntegrationTest, TerminateSSLNoStartTlsTr tcp_client->waitForDisconnect(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_terminated_ssl", 0); + test_server_->waitForCounter("postgres.postgres_stats.sessions_terminated_ssl", Eq(0)); } INSTANTIATE_TEST_SUITE_P(IpVersions, DownstreamSSLWrongConfigPostgresIntegrationTest, @@ -414,8 +415,8 @@ TEST_P(UpstreamSSLDisabledPostgresIntegrationTest, BasicConnectivityTest) { tcp_client->close(); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_upstream_ssl_success", 0); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_upstream_ssl_failed", 0); + test_server_->waitForCounter("postgres.postgres_stats.sessions_upstream_ssl_success", Eq(0)); + test_server_->waitForCounter("postgres.postgres_stats.sessions_upstream_ssl_failed", Eq(0)); } INSTANTIATE_TEST_SUITE_P(IpVersions, UpstreamSSLDisabledPostgresIntegrationTest, @@ -477,8 +478,8 @@ TEST_P(UpstreamSSLRequirePostgresIntegrationTest, ServerAgreesForSSLTest) { tcp_client->close(); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_upstream_ssl_success", 1); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_upstream_ssl_failed", 0); + test_server_->waitForCounter("postgres.postgres_stats.sessions_upstream_ssl_success", Eq(1)); + test_server_->waitForCounter("postgres.postgres_stats.sessions_upstream_ssl_failed", Eq(0)); } // Test verifies that postgres filter will not continue when upstream SSL @@ -513,8 +514,8 @@ TEST_P(UpstreamSSLRequirePostgresIntegrationTest, ServerDeniesSSLTest) { tcp_client->waitForDisconnect(); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_upstream_ssl_success", 0); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_upstream_ssl_failed", 1); + test_server_->waitForCounter("postgres.postgres_stats.sessions_upstream_ssl_success", Eq(0)); + test_server_->waitForCounter("postgres.postgres_stats.sessions_upstream_ssl_failed", Eq(1)); } INSTANTIATE_TEST_SUITE_P(IpVersions, UpstreamSSLRequirePostgresIntegrationTest, @@ -663,9 +664,9 @@ TEST_P(UpstreamAndDownstreamSSLIntegrationTest, ServerAgreesForSSL) { tcp_client->close(); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_terminated_ssl", 1); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_upstream_ssl_success", 1); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_upstream_ssl_failed", 0); + test_server_->waitForCounter("postgres.postgres_stats.sessions_terminated_ssl", Eq(1)); + test_server_->waitForCounter("postgres.postgres_stats.sessions_upstream_ssl_success", Eq(1)); + test_server_->waitForCounter("postgres.postgres_stats.sessions_upstream_ssl_failed", Eq(0)); } // Integration test when both downstream and upstream SSL is enabled. @@ -733,9 +734,9 @@ TEST_P(UpstreamAndDownstreamSSLIntegrationTest, ServerRejectsSSL) { tcp_client->waitForDisconnect(); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_terminated_ssl", 1); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_upstream_ssl_success", 0); - test_server_->waitForCounterEq("postgres.postgres_stats.sessions_upstream_ssl_failed", 1); + test_server_->waitForCounter("postgres.postgres_stats.sessions_terminated_ssl", Eq(1)); + test_server_->waitForCounter("postgres.postgres_stats.sessions_upstream_ssl_success", Eq(0)); + test_server_->waitForCounter("postgres.postgres_stats.sessions_upstream_ssl_failed", Eq(1)); } INSTANTIATE_TEST_SUITE_P(IpVersions, UpstreamAndDownstreamSSLIntegrationTest, diff --git a/contrib/tap_sinks/udp_sink/test/udp_sink_interation_test.cc b/contrib/tap_sinks/udp_sink/test/udp_sink_interation_test.cc index 96f1fa850eb98..dc583ef3aa872 100644 --- a/contrib/tap_sinks/udp_sink/test/udp_sink_interation_test.cc +++ b/contrib/tap_sinks/udp_sink/test/udp_sink_interation_test.cc @@ -224,7 +224,7 @@ name: tap codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); makeRequest(request_headers_udp_tap_, {}, nullptr, response_headers_udp_tap_, {}, nullptr); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", testing::Ge(1)); // Verify whether get the expect message tap_server.checkRcvedUDPMsg(); diff --git a/mobile/test/common/extensions/stat_sinks/metrics_service/mobile_grpc_streamer_integration_test.cc b/mobile/test/common/extensions/stat_sinks/metrics_service/mobile_grpc_streamer_integration_test.cc index 36afe1f174f92..37b56c0cc5198 100644 --- a/mobile/test/common/extensions/stat_sinks/metrics_service/mobile_grpc_streamer_integration_test.cc +++ b/mobile/test/common/extensions/stat_sinks/metrics_service/mobile_grpc_streamer_integration_test.cc @@ -11,6 +11,8 @@ #include "library/common/extensions/stat_sinks/metrics_service/service.pb.h" using testing::AssertionResult; +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -180,10 +182,10 @@ TEST_P(EnvoyMobileMetricsServiceIntegrationTest, BasicFlow) { switch (clientType()) { case Grpc::ClientType::EnvoyGrpc: - test_server_->waitForGaugeEq("cluster.metrics_service.upstream_rq_active", 0); + test_server_->waitForGauge("cluster.metrics_service.upstream_rq_active", Eq(0)); break; case Grpc::ClientType::GoogleGrpc: - test_server_->waitForCounterGe("grpc.metrics_service.streams_closed_0", 1); + test_server_->waitForCounter("grpc.metrics_service.streams_closed_0", Ge(1)); break; default: PANIC("not implemented"); diff --git a/mobile/test/common/integration/base_client_integration_test.cc b/mobile/test/common/integration/base_client_integration_test.cc index 7efee9274154e..8d4f5dbf8c27b 100644 --- a/mobile/test/common/integration/base_client_integration_test.cc +++ b/mobile/test/common/integration/base_client_integration_test.cc @@ -187,18 +187,22 @@ uint64_t BaseClientIntegrationTest::getCounterValue(const std::string& name) { return counter_value; } -testing::AssertionResult BaseClientIntegrationTest::waitForCounterGe(const std::string& name, - uint64_t value) { +testing::AssertionResult +BaseClientIntegrationTest::waitForCounter(const std::string& name, + testing::Matcher value_matcher) { constexpr std::chrono::milliseconds timeout = TestUtility::DefaultTimeout; Event::TestTimeSystem::RealTimeBound bound(timeout); - while (getCounterValue(name) < value) { + while (true) { + const uint64_t current_value = getCounterValue(name); + if (value_matcher.Matches(current_value)) { + return testing::AssertionSuccess(); + } timeSystem().advanceTimeWait(std::chrono::milliseconds(10)); if (timeout != std::chrono::milliseconds::zero() && !bound.withinBound()) { - return testing::AssertionFailure() - << fmt::format("timed out waiting for {} to be {}", name, value); + return testing::AssertionFailure() << "timed out waiting for " << name << " to " + << value_matcher << ": current value " << current_value; } } - return testing::AssertionSuccess(); } uint64_t BaseClientIntegrationTest::getGaugeValue(const std::string& name) { @@ -216,18 +220,22 @@ uint64_t BaseClientIntegrationTest::getGaugeValue(const std::string& name) { return gauge_value; } -testing::AssertionResult BaseClientIntegrationTest::waitForGaugeGe(const std::string& name, - uint64_t value) { +testing::AssertionResult +BaseClientIntegrationTest::waitForGauge(const std::string& name, + testing::Matcher value_matcher) { constexpr std::chrono::milliseconds timeout = TestUtility::DefaultTimeout; Event::TestTimeSystem::RealTimeBound bound(timeout); - while (getGaugeValue(name) < value) { + while (true) { + const uint64_t current_value = getGaugeValue(name); + if (value_matcher.Matches(current_value)) { + return testing::AssertionSuccess(); + } timeSystem().advanceTimeWait(std::chrono::milliseconds(10)); if (timeout != std::chrono::milliseconds::zero() && !bound.withinBound()) { - return testing::AssertionFailure() - << fmt::format("timed out waiting for {} to be {}", name, value); + return testing::AssertionFailure() << "timed out waiting for " << name << " to " + << value_matcher << ": current value " << current_value; } } - return testing::AssertionSuccess(); } } // namespace Envoy diff --git a/mobile/test/common/integration/base_client_integration_test.h b/mobile/test/common/integration/base_client_integration_test.h index eb3ee9ad6601c..9131fe8ea9bdc 100644 --- a/mobile/test/common/integration/base_client_integration_test.h +++ b/mobile/test/common/integration/base_client_integration_test.h @@ -58,12 +58,12 @@ class BaseClientIntegrationTest : public BaseIntegrationTest { // Get the value of a Counter in the Envoy instance. uint64_t getCounterValue(const std::string& name); - // Wait until the Counter specified by `name` is >= `value`. - ABSL_MUST_USE_RESULT testing::AssertionResult waitForCounterGe(const std::string& name, - uint64_t value); + // Wait until the Counter specified by `name` matches `value_matcher`. + ABSL_MUST_USE_RESULT testing::AssertionResult + waitForCounter(const std::string& name, testing::Matcher value_matcher); uint64_t getGaugeValue(const std::string& name); - ABSL_MUST_USE_RESULT testing::AssertionResult waitForGaugeGe(const std::string& name, - uint64_t value); + ABSL_MUST_USE_RESULT testing::AssertionResult + waitForGauge(const std::string& name, testing::Matcher value_matcher); EnvoyStreamCallbacks createDefaultStreamCallbacks(); diff --git a/mobile/test/common/integration/cds_integration_test.cc b/mobile/test/common/integration/cds_integration_test.cc index 44da4227d1bf4..4e5cad38cd1c3 100644 --- a/mobile/test/common/integration/cds_integration_test.cc +++ b/mobile/test/common/integration/cds_integration_test.cc @@ -11,6 +11,7 @@ namespace Envoy { namespace { using envoy::config::cluster::v3::Cluster; +using testing::Ge; class CdsIntegrationTest : public XdsIntegrationTest { public: @@ -72,15 +73,15 @@ class CdsIntegrationTest : public XdsIntegrationTest { {cluster}, {cluster}, {}, version); // Wait for cluster to be added. - EXPECT_TRUE(waitForCounterGe("cluster_manager.cluster_added", 1)); - EXPECT_TRUE(waitForGaugeGe("cluster_manager.active_clusters", cluster_count + 1)); + EXPECT_TRUE(waitForCounter("cluster_manager.cluster_added", Ge(1))); + EXPECT_TRUE(waitForGauge("cluster_manager.active_clusters", Ge(cluster_count + 1))); // ACK of the initial version. EXPECT_TRUE(compareDiscoveryRequest(Config::getTypeUrl(), version, expected_resources, {}, {}, /*expect_node=*/false)); - EXPECT_TRUE(waitForGaugeGe("cluster_manager.cluster_removed", 0)); + EXPECT_TRUE(waitForGauge("cluster_manager.cluster_removed", Ge(0))); } void sendUpdatedCdsResponseAndVerify(const std::string& version) { @@ -98,8 +99,8 @@ class CdsIntegrationTest : public XdsIntegrationTest { /*expect_node=*/false)); // Cluster count should stay the same. - EXPECT_TRUE(waitForGaugeGe("cluster_manager.active_clusters", cluster_count)); - EXPECT_TRUE(waitForGaugeGe("cluster_manager.cluster_removed", 0)); + EXPECT_TRUE(waitForGauge("cluster_manager.active_clusters", Ge(cluster_count))); + EXPECT_TRUE(waitForGauge("cluster_manager.cluster_removed", Ge(0))); } bool use_xdstp_{false}; diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index 03c2b51a118d2..7bcfbbd19cf72 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -33,9 +33,9 @@ using testing::_; using testing::AnyNumber; +using testing::Ge; using testing::Return; using testing::ReturnRef; - namespace Envoy { namespace { @@ -467,17 +467,17 @@ TEST_P(ClientIntegrationTest, Http3IdleConnectionClosedUponNetworkChangeEventsAn ASSERT_EQ(0, last_stream_final_intel_.socket_reused); // An h3 upstream connection should have been established. - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_cx_http3_total", 1)); - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_rq_total", 1)); + ASSERT_TRUE(waitForCounter("cluster.base.upstream_cx_http3_total", Ge(1))); + ASSERT_TRUE(waitForCounter("cluster.base.upstream_rq_total", Ge(1))); EXPECT_CALL(helper_handle_->mock_helper(), bindSocketToNetwork(_, 123)).Times(0u); // A new cellular network appears and becomes the default network. The idle connection should be // closed. internalEngine()->onNetworkConnectAndroid(ConnectionType::CONNECTION_4G, 123); internalEngine()->onDefaultNetworkChangedAndroid(ConnectionType::CONNECTION_4G, 123); - ASSERT_TRUE(waitForCounterGe("http3.upstream.tx.quic_connection_close_error_code_QUIC_CONNECTION_" - "MIGRATION_NO_MIGRATABLE_STREAMS", - 1)); + ASSERT_TRUE(waitForCounter("http3.upstream.tx.quic_connection_close_error_code_QUIC_CONNECTION_" + "MIGRATION_NO_MIGRATABLE_STREAMS", + Ge(1))); // A new connection will be created on the new default network to serve new requests. EXPECT_CALL(helper_handle_->mock_helper(), bindSocketToNetwork(_, 123)) @@ -505,7 +505,7 @@ TEST_P(ClientIntegrationTest, Http3IdleConnectionClosedUponNetworkChangeEventsAn ASSERT_EQ(3, last_stream_final_intel_.upstream_protocol); ASSERT_EQ(0, last_stream_final_intel_.socket_reused); - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_rq_total", 2)); + ASSERT_TRUE(waitForCounter("cluster.base.upstream_rq_total", Ge(2))); // The total h3 connection count should have increased. EXPECT_EQ(2, getCounterValue("cluster.base.upstream_cx_http3_total")); } @@ -534,8 +534,8 @@ TEST_P(ClientIntegrationTest, Http3ConnectionMigrationUponNetworkChangeEventsAnd stream_->sendHeaders(std::make_unique(default_request_headers_), false); // Wait for the upstream connection to be established. - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_cx_http3_total", 1)); - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_rq_total", 1)); + ASSERT_TRUE(waitForCounter("cluster.base.upstream_cx_http3_total", Ge(1))); + ASSERT_TRUE(waitForCounter("cluster.base.upstream_rq_total", Ge(1))); absl::Notification probing_socket_created; EXPECT_CALL(helper_handle_->mock_helper(), bindSocketToNetwork(_, 123)) @@ -586,7 +586,7 @@ TEST_P(ClientIntegrationTest, Http3ConnectionMigrationUponNetworkChangeEventsAnd ASSERT_EQ(3, last_stream_final_intel_.upstream_protocol); ASSERT_EQ(1, last_stream_final_intel_.socket_reused); - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_rq_total", 2)); + ASSERT_TRUE(waitForCounter("cluster.base.upstream_rq_total", Ge(2))); // The total h3 connection count shouldn't have increased. EXPECT_EQ(1, getCounterValue("cluster.base.upstream_cx_http3_total")); } @@ -622,8 +622,8 @@ TEST_P(ClientIntegrationTest, Http3ConnectionMigrationUponNetworkDisconnectedAnd ASSERT_EQ(0, last_stream_final_intel_.socket_reused); // Wait for the upstream connection to be established. - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_cx_http3_total", 1)); - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_rq_total", 1)); + ASSERT_TRUE(waitForCounter("cluster.base.upstream_cx_http3_total", Ge(1))); + ASSERT_TRUE(waitForCounter("cluster.base.upstream_rq_total", Ge(1))); // Send a new request with body during which network gets disconnected. Buffer::OwnedImpl request_data = Buffer::OwnedImpl("request body"); @@ -670,9 +670,9 @@ TEST_P(ClientIntegrationTest, Http3ConnectionMigrationUponNetworkDisconnectedAnd internalEngine()->onNetworkConnectAndroid(ConnectionType::CONNECTION_WIFI, 1); internalEngine()->onDefaultNetworkChangedAndroid(ConnectionType::CONNECTION_WIFI, 1); - ASSERT_TRUE(waitForCounterGe("http3.upstream.tx.quic_connection_close_error_code_QUIC_CONNECTION_" - "MIGRATION_NO_MIGRATABLE_STREAMS", - 1)); + ASSERT_TRUE(waitForCounter("http3.upstream.tx.quic_connection_close_error_code_QUIC_CONNECTION_" + "MIGRATION_NO_MIGRATABLE_STREAMS", + Ge(1))); } TEST_P(ClientIntegrationTest, LargeResponse) { @@ -1173,7 +1173,7 @@ TEST_P(ClientIntegrationTest, ReresolveAndDrain) { } // Make sure the attempt happened. - ASSERT_TRUE(waitForCounterGe("dns_cache.base_dns_cache.dns_query_attempt", 1)); + ASSERT_TRUE(waitForCounter("dns_cache.base_dns_cache.dns_query_attempt", Ge(1))); EXPECT_EQ(0, getCounterValue("dns_cache.base_dns_cache.dns_query_success")); // The next request should go to the original upstream as there's been no drain. stream_ = createNewStream(createDefaultStreamCallbacks()); @@ -1187,7 +1187,7 @@ TEST_P(ClientIntegrationTest, ReresolveAndDrain) { // Force the lookup to resolve to localhost. // Unblock the resolution and wait for it to succeed. Network::TestResolver::unblockResolve("127.0.0.3"); - ASSERT_TRUE(waitForCounterGe("dns_cache.base_dns_cache.dns_query_success", 1)); + ASSERT_TRUE(waitForCounter("dns_cache.base_dns_cache.dns_query_success", Ge(1))); // Do one final request. It should go to the second upstream and return 202 stream_ = createNewStream(createDefaultStreamCallbacks()); @@ -1436,9 +1436,9 @@ TEST_P(ClientIntegrationTest, CancelDuringResponse) { ASSERT_TRUE(upstream_connection_->waitForDisconnect()); upstream_connection_.reset(); ASSERT_TRUE( - waitForCounterGe("http3.upstream.tx.quic_connection_close_error_code_QUIC_NO_ERROR", 1)); - ASSERT_TRUE(waitForCounterGe( - "http3.upstream.tx.quic_reset_stream_error_code_QUIC_STREAM_REQUEST_REJECTED", 1)); + waitForCounter("http3.upstream.tx.quic_connection_close_error_code_QUIC_NO_ERROR", Ge(1))); + ASSERT_TRUE(waitForCounter( + "http3.upstream.tx.quic_reset_stream_error_code_QUIC_STREAM_REQUEST_REJECTED", Ge(1))); } } @@ -1844,7 +1844,7 @@ TEST_P(ClientIntegrationTest, NoSpaceAvailableWriteErrorSwallowed) { // Wait for the upstream connection to be created and introduce a transient SOCKET_ERROR_NOBUFS // write error. - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_cx_http3_total", 1)); + ASSERT_TRUE(waitForCounter("cluster.base.upstream_cx_http3_total", Ge(1))); sys_calls.target_fd_.store(fd); sys_calls.fail_send_.store(true); @@ -1904,7 +1904,7 @@ TEST_P(ClientIntegrationTest, HttpsWithEarlyData) { ASSERT_TRUE(upstream_connection_->close()); ASSERT_TRUE(upstream_connection_->waitForDisconnect()); upstream_connection_.reset(); - ASSERT_TRUE(waitForCounterGe("cluster.base.upstream_cx_destroy", old_upstream_cx_destroy + 1)); + ASSERT_TRUE(waitForCounter("cluster.base.upstream_cx_destroy", Ge(old_upstream_cx_destroy + 1))); // Reset terminal callback for the second request. ConditionalInitializer terminal_callback; diff --git a/mobile/test/common/integration/rtds_integration_test.cc b/mobile/test/common/integration/rtds_integration_test.cc index 20f5e64489ca0..873ef7041447c 100644 --- a/mobile/test/common/integration/rtds_integration_test.cc +++ b/mobile/test/common/integration/rtds_integration_test.cc @@ -12,6 +12,7 @@ #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace { @@ -79,7 +80,7 @@ class RtdsIntegrationTest : public XdsIntegrationTest { Config::getTypeUrl(), {some_rtds_resource}, {some_rtds_resource}, {}, "1"); // Wait until the RTDS updates from the DiscoveryResponse have been applied. - ASSERT_TRUE(waitForCounterGe(load_success_counter, load_success_value + 1)); + ASSERT_TRUE(waitForCounter(load_success_counter, Ge(load_success_value + 1))); // Verify that the Runtime config values are from the RTDS response. EXPECT_TRUE(Runtime::runtimeFeatureEnabled("envoy.reloadable_features.test_feature_false")); @@ -95,7 +96,7 @@ class RtdsIntegrationTest : public XdsIntegrationTest { Config::getTypeUrl(), {some_rtds_resource}, {some_rtds_resource}, {}, "2", {{"test", Protobuf::Any()}}); // Wait until the RTDS updates from the DiscoveryResponse have been applied. - ASSERT_TRUE(waitForCounterGe(load_success_counter, load_success_value + 1)); + ASSERT_TRUE(waitForCounter(load_success_counter, Ge(load_success_value + 1))); // Verify that the Runtime config values are from the RTDS response. EXPECT_FALSE(Runtime::runtimeFeatureEnabled("envoy.reloadable_features.test_feature_false")); diff --git a/mobile/test/common/integration/sds_integration_test.cc b/mobile/test/common/integration/sds_integration_test.cc index 1fa6151ca63a4..41d105d2e7304 100644 --- a/mobile/test/common/integration/sds_integration_test.cc +++ b/mobile/test/common/integration/sds_integration_test.cc @@ -9,6 +9,7 @@ #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace { @@ -94,17 +95,17 @@ INSTANTIATE_TEST_SUITE_P( TEST_P(SdsIntegrationTest, SdsForUpstreamCluster) { // Wait until the new cluster from CDS is added before sending the SDS response. sendCdsResponse(); - ASSERT_TRUE(waitForCounterGe("cluster_manager.cluster_added", 1)); + ASSERT_TRUE(waitForCounter("cluster_manager.cluster_added", Ge(1))); // Wait until the Envoy instance has obtained an updated secret from the SDS cluster. This // verifies that the SDS API is working from the Envoy client and allows us to know we can start // sending HTTP requests to the upstream cluster using the secret. sendSdsResponse(getClientSecret()); - ASSERT_TRUE(waitForCounterGe(fmt::format("sds.{}.update_success", SECRET_NAME), 1)); + ASSERT_TRUE(waitForCounter(fmt::format("sds.{}.update_success", SECRET_NAME), Ge(1))); ASSERT_TRUE( - waitForCounterGe(fmt::format("cluster.{}.client_ssl_socket_factory.ssl_context_update_by_sds", - XDS_CLUSTER_NAME), - 1)); + waitForCounter(fmt::format("cluster.{}.client_ssl_socket_factory.ssl_context_update_by_sds", + XDS_CLUSTER_NAME), + Ge(1))); } } // namespace diff --git a/test/common/access_log/access_log_manager_impl_test.cc b/test/common/access_log/access_log_manager_impl_test.cc index f18ab330dc7cb..9fb2d13039e68 100644 --- a/test/common/access_log/access_log_manager_impl_test.cc +++ b/test/common/access_log/access_log_manager_impl_test.cc @@ -16,6 +16,7 @@ using testing::_; using testing::ByMove; +using testing::Eq; using testing::Invoke; using testing::NiceMock; using testing::Return; @@ -47,12 +48,13 @@ class AccessLogManagerImplTest : public testing::Test { EXPECT_CALL(api_, threadFactory()).WillRepeatedly(ReturnRef(thread_factory_)); } - AssertionResult waitForCounterEq(const std::string& name, uint64_t value) { - return TestUtility::waitForCounterEq(store_, name, value, time_system_); + AssertionResult waitForCounter(const std::string& name, + testing::Matcher value_matcher) { + return TestUtility::waitForCounter(store_, name, value_matcher, time_system_); } - AssertionResult waitForGaugeEq(const std::string& name, uint64_t value) { - return TestUtility::waitForGaugeEq(store_, name, value, time_system_); + AssertionResult waitForGauge(const std::string& name, testing::Matcher value_matcher) { + return TestUtility::waitForGauge(store_, name, value_matcher, time_system_); } NiceMock api_; @@ -124,10 +126,10 @@ TEST_F(AccessLogManagerImplTest, FlushToLogFilePeriodically) { EXPECT_TRUE(file_->waitForEventCount(file_->num_writes_, 1)); - EXPECT_TRUE(waitForCounterEq("filesystem.write_completed", 1)); + EXPECT_TRUE(waitForCounter("filesystem.write_completed", Eq(1))); EXPECT_EQ(1UL, store_.counter("filesystem.write_buffered").value()); EXPECT_EQ(0UL, store_.counter("filesystem.flushed_by_timer").value()); - EXPECT_TRUE(waitForGaugeEq("filesystem.write_total_buffered", 0)); + EXPECT_TRUE(waitForGauge("filesystem.write_total_buffered", Eq(0))); EXPECT_CALL(*file_, write_(_)) .WillOnce(Invoke([&](absl::string_view data) -> Api::IoCallSizeResult { @@ -148,11 +150,11 @@ TEST_F(AccessLogManagerImplTest, FlushToLogFilePeriodically) { EXPECT_TRUE(file_->waitForEventCount(file_->num_writes_, 2)); - EXPECT_TRUE(waitForCounterEq("filesystem.write_completed", 2)); + EXPECT_TRUE(waitForCounter("filesystem.write_completed", Eq(2))); EXPECT_EQ(0UL, store_.counter("filesystem.write_failed").value()); EXPECT_EQ(1UL, store_.counter("filesystem.flushed_by_timer").value()); EXPECT_EQ(2UL, store_.counter("filesystem.write_buffered").value()); - EXPECT_TRUE(waitForGaugeEq("filesystem.write_total_buffered", 0)); + EXPECT_TRUE(waitForGauge("filesystem.write_total_buffered", Eq(0))); EXPECT_CALL(*file_, close_()).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); } @@ -197,7 +199,7 @@ TEST_F(AccessLogManagerImplTest, FlushToLogFileOnDemand) { expected_writes++; EXPECT_TRUE(file_->waitForEventCount(file_->num_writes_, expected_writes)); - EXPECT_TRUE(waitForCounterEq("filesystem.write_completed", 2)); + EXPECT_TRUE(waitForCounter("filesystem.write_completed", Eq(2))); EXPECT_EQ(0UL, store_.counter("filesystem.flushed_by_timer").value()); EXPECT_CALL(*file_, write_(_)) @@ -237,7 +239,7 @@ TEST_F(AccessLogManagerImplTest, FlushCountsIOErrors) { log_file->write("test"); EXPECT_TRUE(file_->waitForEventCount(file_->num_writes_, 1)); - EXPECT_TRUE(waitForCounterEq("filesystem.write_failed", 1)); + EXPECT_TRUE(waitForCounter("filesystem.write_failed", Eq(1))); EXPECT_EQ(0UL, store_.counter("filesystem.write_completed").value()); EXPECT_CALL(*file_, close_()).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); @@ -408,8 +410,8 @@ TEST_F(AccessLogManagerImplTest, ReopenRetry) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) EXPECT_TRUE(file_->waitForEventCount(file_->num_writes_, 3)); - EXPECT_TRUE(waitForCounterEq("filesystem.reopen_failed", 2)); - EXPECT_TRUE(waitForGaugeEq("filesystem.write_total_buffered", 0)); + EXPECT_TRUE(waitForCounter("filesystem.reopen_failed", Eq(2))); + EXPECT_TRUE(waitForGauge("filesystem.write_total_buffered", Eq(0))); } TEST_F(AccessLogManagerImplTest, BigDataChunkShouldBeFlushedWithoutTimer) { diff --git a/test/common/tls/cert_validator/default_validator_integration_test.cc b/test/common/tls/cert_validator/default_validator_integration_test.cc index b3535d2b4ba38..3a3547bb06868 100644 --- a/test/common/tls/cert_validator/default_validator_integration_test.cc +++ b/test/common/tls/cert_validator/default_validator_integration_test.cc @@ -9,6 +9,7 @@ #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace Ssl { @@ -63,7 +64,7 @@ TEST_P(SslCertValidatorIntegrationTest, CertValidated) { auto conn = makeSslClientConnection({}); IntegrationCodecClientPtr codec = makeHttpConnection(std::move(conn)); ASSERT_TRUE(codec->connected()); - test_server_->waitForCounterGe(listenerStatPrefix("ssl.handshake"), 1); + test_server_->waitForCounter(listenerStatPrefix("ssl.handshake"), Ge(1)); EXPECT_EQ(test_server_->counter(listenerStatPrefix("ssl.fail_verify_error"))->value(), 0); codec->close(); } @@ -82,7 +83,7 @@ TEST_P(SslCertValidatorIntegrationTest, CertValidatedWithVerifyDepth) { auto conn = makeSslClientConnection({}); IntegrationCodecClientPtr codec = makeRawHttpConnection(std::move(conn), absl::nullopt); ASSERT_TRUE(codec->connected()); - test_server_->waitForCounterGe(listenerStatPrefix("ssl.handshake"), 1); + test_server_->waitForCounter(listenerStatPrefix("ssl.handshake"), Ge(1)); EXPECT_EQ(test_server_->counter(listenerStatPrefix("ssl.fail_verify_error"))->value(), 0); codec->close(); } @@ -102,7 +103,7 @@ TEST_P(SslCertValidatorIntegrationTest, CertValidationSucceedNoDepthWithTrustRoo auto conn = makeSslClientConnection({}); IntegrationCodecClientPtr codec = makeRawHttpConnection(std::move(conn), absl::nullopt); ASSERT_TRUE(codec->connected()); - test_server_->waitForCounterGe(listenerStatPrefix("ssl.handshake"), 1); + test_server_->waitForCounter(listenerStatPrefix("ssl.handshake"), Ge(1)); EXPECT_EQ(test_server_->counter(listenerStatPrefix("ssl.fail_verify_error"))->value(), 0); codec->close(); } @@ -123,7 +124,7 @@ TEST_P(SslCertValidatorIntegrationTest, CertValidationSucceedDepthWithTrustRootO auto conn = makeSslClientConnection({}); IntegrationCodecClientPtr codec = makeRawHttpConnection(std::move(conn), absl::nullopt); ASSERT_TRUE(codec->connected()); - test_server_->waitForCounterGe(listenerStatPrefix("ssl.handshake"), 1); + test_server_->waitForCounter(listenerStatPrefix("ssl.handshake"), Ge(1)); EXPECT_EQ(test_server_->counter(listenerStatPrefix("ssl.fail_verify_error"))->value(), 0); codec->close(); } @@ -143,7 +144,7 @@ TEST_P(SslCertValidatorIntegrationTest, CertValidationFailedDepthWithTrustRootOn initialize(); auto conn = makeSslClientConnection({}); IntegrationCodecClientPtr codec = makeRawHttpConnection(std::move(conn), absl::nullopt); - test_server_->waitForCounterGe(listenerStatPrefix("ssl.fail_verify_error"), 1); + test_server_->waitForCounter(listenerStatPrefix("ssl.fail_verify_error"), Ge(1)); ASSERT_TRUE(codec->waitForDisconnect()); } @@ -234,7 +235,7 @@ TEST_P(SslCertValidatorIntegrationTest, CertValidatedWithCustomMatcher) { auto conn = makeSslClientConnection({}); IntegrationCodecClientPtr codec = makeHttpConnection(std::move(conn)); ASSERT_TRUE(codec->connected()); - test_server_->waitForCounterGe(listenerStatPrefix("ssl.handshake"), 1); + test_server_->waitForCounter(listenerStatPrefix("ssl.handshake"), Ge(1)); EXPECT_EQ(test_server_->counter(listenerStatPrefix("ssl.fail_verify_error"))->value(), 0); codec->close(); } diff --git a/test/common/tls/integration/ssl_integration_test.cc b/test/common/tls/integration/ssl_integration_test.cc index 8489ccc2a05a8..bd56b5412a8b3 100644 --- a/test/common/tls/integration/ssl_integration_test.cc +++ b/test/common/tls/integration/ssl_integration_test.cc @@ -33,6 +33,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; using testing::StartsWith; namespace Envoy { @@ -78,7 +80,7 @@ BORINGSSL_TEST_P(SslIntegrationTest, UnknownSslAlert) { const std::string counter_name = listenerStatPrefix("ssl.connection_error"); Stats::CounterSharedPtr counter = test_server_->counter(counter_name); - test_server_->waitForCounterGe(counter_name, 1); + test_server_->waitForCounter(counter_name, Ge(1)); connection->close(Network::ConnectionCloseType::NoFlush); } @@ -835,7 +837,7 @@ TEST_P(SslCertficateIntegrationTest, ServerEcdsaClientRsaOnly) { EXPECT_FALSE(codec_client->connected()); const std::string counter_name = listenerStatPrefix("ssl.connection_error"); Stats::CounterSharedPtr counter = test_server_->counter(counter_name); - test_server_->waitForCounterGe(counter_name, 1); + test_server_->waitForCounter(counter_name, Ge(1)); EXPECT_EQ(1U, counter->value()); counter->reset(); } @@ -904,7 +906,7 @@ TEST_P(SslCertficateIntegrationTest, ServerRsaClientEcdsaOnly) { ->connected()); const std::string counter_name = listenerStatPrefix("ssl.connection_error"); Stats::CounterSharedPtr counter = test_server_->counter(counter_name); - test_server_->waitForCounterGe(counter_name, 1); + test_server_->waitForCounter(counter_name, Ge(1)); EXPECT_EQ(1U, counter->value()); counter->reset(); } @@ -1375,8 +1377,8 @@ name: test-tls-context-provider ASSERT(socket); // wait for the server tls handshake into sleep state. - test_server_->waitForCounterEq("aysnc_cert_selection.cert_selection_sleep", 1, - TestUtility::DefaultTimeout, dispatcher_.get()); + test_server_->waitForCounter("aysnc_cert_selection.cert_selection_sleep", Eq(1), + TestUtility::DefaultTimeout, dispatcher_.get()); ASSERT_EQ(connection->state(), Network::Connection::State::Open); ENVOY_LOG_MISC(debug, "debug: closing connection"); @@ -1384,8 +1386,8 @@ name: test-tls-context-provider connection.reset(); // wait the sleep timer in cert selector is triggered. - test_server_->waitForCounterEq("aysnc_cert_selection.cert_selection_sleep_finished", 1, - TestUtility::DefaultTimeout, dispatcher_.get()); + test_server_->waitForCounter("aysnc_cert_selection.cert_selection_sleep_finished", Eq(1), + TestUtility::DefaultTimeout, dispatcher_.get()); } BORINGSSL_TEST_P(SslIntegrationTest, AsyncCertSelectionAfterSslShutdown) { @@ -1406,15 +1408,15 @@ name: test-tls-context-provider ASSERT(socket); // wait for the server tls handshake into sleep state. - test_server_->waitForCounterEq("aysnc_cert_selection.cert_selection_sleep", 1, - TestUtility::DefaultTimeout, dispatcher_.get()); + test_server_->waitForCounter("aysnc_cert_selection.cert_selection_sleep", Eq(1), + TestUtility::DefaultTimeout, dispatcher_.get()); ASSERT_EQ(connection->state(), Network::Connection::State::Open); connection->close(Network::ConnectionCloseType::NoFlush); // wait the sleep timer in cert selector is triggered. - test_server_->waitForCounterEq("aysnc_cert_selection.cert_selection_sleep_finished", 1, - TestUtility::DefaultTimeout, dispatcher_.get()); + test_server_->waitForCounter("aysnc_cert_selection.cert_selection_sleep_finished", Eq(1), + TestUtility::DefaultTimeout, dispatcher_.get()); connection.reset(); } diff --git a/test/common/tls/integration/ssl_upstream_integration_test.cc b/test/common/tls/integration/ssl_upstream_integration_test.cc index 800b6a074c58a..ca9fcd4a071f0 100644 --- a/test/common/tls/integration/ssl_upstream_integration_test.cc +++ b/test/common/tls/integration/ssl_upstream_integration_test.cc @@ -73,7 +73,7 @@ class SslUpstreamIntegrationTest : public testing::TestWithParamheaders().getStatusValue()); codec_client_->close(); - test_server_->waitForCounterEq("cluster.cluster_0.ssl.fail_verify_san", 1); + test_server_->waitForCounter("cluster.cluster_0.ssl.fail_verify_san", testing::Eq(1)); } void expectCertValidationSuccess() { testSni(); } diff --git a/test/config/utility.cc b/test/config/utility.cc index 7ab11c2cfdc4c..16d04e8f917a7 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -32,6 +32,7 @@ #include "absl/strings/str_replace.h" #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace { envoy::config::bootstrap::v3::Bootstrap& @@ -1960,11 +1961,11 @@ void EdsHelper::setEdsAndWait( const std::vector& cluster_load_assignments, IntegrationTestServerStats& server_stats) { // Make sure the last version has been accepted before setting a new one. - server_stats.waitForCounterGe("cluster.cluster_0.update_success", update_successes_); + server_stats.waitForCounter("cluster.cluster_0.update_success", Ge(update_successes_)); setEds(cluster_load_assignments); // Make sure Envoy has consumed the update now that it is running. ++update_successes_; - server_stats.waitForCounterGe("cluster.cluster_0.update_success", update_successes_); + server_stats.waitForCounter("cluster.cluster_0.update_success", Ge(update_successes_)); RELEASE_ASSERT( update_successes_ == server_stats.counter("cluster.cluster_0.update_success")->value(), ""); } diff --git a/test/extensions/access_loggers/filters/process_ratelimit/integration_test.cc b/test/extensions/access_loggers/filters/process_ratelimit/integration_test.cc index d0a693c30e418..659ee457c9a88 100644 --- a/test/extensions/access_loggers/filters/process_ratelimit/integration_test.cc +++ b/test/extensions/access_loggers/filters/process_ratelimit/integration_test.cc @@ -147,7 +147,7 @@ class AccessLogAdsIntegrationTest : public AdsIntegrationTest { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", testing::Ge(1)); // Make a request to verify listener_0 is working. makeSingleRequest(); diff --git a/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc b/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc index eb96dafac5e9c..5c8df238ab68c 100644 --- a/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc +++ b/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc @@ -13,6 +13,7 @@ #include "msgpack.hpp" using testing::AssertionResult; +using testing::Eq; namespace Envoy { namespace { @@ -192,19 +193,19 @@ TEST_F(FluentdAccessLogIntegrationTest, LogLostOnBufferFull) { init(default_cluster_name, false, /* max_buffer_size = */ 0); sendBidirectionalData(); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_lost", 1); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.entries_lost", Eq(1)); } TEST_F(FluentdAccessLogIntegrationTest, SingleEntrySingleRecord) { init(); sendBidirectionalData(); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 1); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.entries_buffered", Eq(1)); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.events_sent", Eq(1)); ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); - test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); - test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 1); + test_server_->waitForCounter("cluster.fluentd_cluster.upstream_cx_total", Eq(1)); + test_server_->waitForGauge("cluster.fluentd_cluster.upstream_cx_active", Eq(1)); EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { bool validated = false; @@ -218,12 +219,12 @@ TEST_F(FluentdAccessLogIntegrationTest, SingleEntrySingleRecordWithFormatter) { init(default_cluster_name, false, 1, 1, 20, 30, "envoy.extensions.formatter.cel.v3.Cel"); sendBidirectionalData(); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 1); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.entries_buffered", Eq(1)); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.events_sent", Eq(1)); ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); - test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); - test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 1); + test_server_->waitForCounter("cluster.fluentd_cluster.upstream_cx_total", Eq(1)); + test_server_->waitForGauge("cluster.fluentd_cluster.upstream_cx_active", Eq(1)); // Using CEL for formatter validation with sample use case. EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { @@ -239,12 +240,12 @@ TEST_F(FluentdAccessLogIntegrationTest, SingleEntryTwoRecords) { init(default_cluster_name, /*flush_access_log_on_connected = */ true); sendBidirectionalData(); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 2); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.entries_buffered", Eq(2)); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.events_sent", Eq(1)); ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); - test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); - test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 1); + test_server_->waitForCounter("cluster.fluentd_cluster.upstream_cx_total", Eq(1)); + test_server_->waitForGauge("cluster.fluentd_cluster.upstream_cx_active", Eq(1)); EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { bool validated = false; @@ -259,12 +260,12 @@ TEST_F(FluentdAccessLogIntegrationTest, TwoEntries) { init(default_cluster_name, /*flush_access_log_on_connected = */ true, /*buffer_size_bytes = */ 1); sendBidirectionalData(); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 2); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 2); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.entries_buffered", Eq(2)); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.events_sent", Eq(2)); ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); - test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); - test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 1); + test_server_->waitForCounter("cluster.fluentd_cluster.upstream_cx_total", Eq(1)); + test_server_->waitForGauge("cluster.fluentd_cluster.upstream_cx_active", Eq(1)); EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { bool validated = false; @@ -279,12 +280,12 @@ TEST_F(FluentdAccessLogIntegrationTest, UpstreamConnectionClosed) { init(); sendBidirectionalData(); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 1); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.entries_buffered", Eq(1)); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.events_sent", Eq(1)); ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); - test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); - test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 1); + test_server_->waitForCounter("cluster.fluentd_cluster.upstream_cx_total", Eq(1)); + test_server_->waitForGauge("cluster.fluentd_cluster.upstream_cx_active", Eq(1)); EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { bool validated = false; @@ -294,42 +295,42 @@ TEST_F(FluentdAccessLogIntegrationTest, UpstreamConnectionClosed) { })); ASSERT_TRUE(fake_access_log_connection_->close()); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.connections_closed", 1); - test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 0); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.connections_closed", Eq(1)); + test_server_->waitForGauge("cluster.fluentd_cluster.upstream_cx_active", Eq(0)); // New access log would be discarded because the connection is closed. sendBidirectionalData(); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_lost", 1); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.entries_lost", Eq(1)); } TEST_F(FluentdAccessLogIntegrationTest, UpstreamConnectionClosedWithMultipleReconnects) { init(default_cluster_name, false, {}, /* max_reconnect_attempts = */ 3); sendBidirectionalData(); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 1); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.entries_buffered", Eq(1)); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.events_sent", Eq(1)); ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); - test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); + test_server_->waitForCounter("cluster.fluentd_cluster.upstream_cx_total", Eq(1)); ASSERT_TRUE(fake_access_log_connection_->close()); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.connections_closed", 1); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.reconnect_attempts", 1); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.connections_closed", Eq(1)); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.reconnect_attempts", Eq(1)); FakeRawConnectionPtr fake_access_log_connection_2; ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_2)); - test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 2); + test_server_->waitForCounter("cluster.fluentd_cluster.upstream_cx_total", Eq(2)); ASSERT_TRUE(fake_access_log_connection_2->close()); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.connections_closed", 2); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.reconnect_attempts", 2); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.connections_closed", Eq(2)); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.reconnect_attempts", Eq(2)); FakeRawConnectionPtr fake_access_log_connection_3; ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_3)); - test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 3); + test_server_->waitForCounter("cluster.fluentd_cluster.upstream_cx_total", Eq(3)); ASSERT_TRUE(fake_access_log_connection_3->close()); - test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.connections_closed", 3); - test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_connect_attempts_exceeded", - 1); + test_server_->waitForCounter("access_logs.fluentd.fluentd_1.connections_closed", Eq(3)); + test_server_->waitForCounter("cluster.fluentd_cluster.upstream_cx_connect_attempts_exceeded", + Eq(1)); } } // namespace diff --git a/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc b/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc index 306f9da1a2267..30b93f63f64e9 100644 --- a/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc +++ b/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc @@ -15,6 +15,8 @@ #include "gtest/gtest.h" using testing::AssertionResult; +using testing::Eq; +using testing::Ge; using testing::HasSubstr; namespace Envoy { @@ -197,10 +199,10 @@ TEST_P(AccessLogIntegrationTest, BasicAccessLogFlow) { access_log_request_->finishGrpcStream(Grpc::Status::Ok); switch (clientType()) { case Grpc::ClientType::EnvoyGrpc: - test_server_->waitForGaugeEq("cluster.accesslog.upstream_rq_active", 0); + test_server_->waitForGauge("cluster.accesslog.upstream_rq_active", Eq(0)); break; case Grpc::ClientType::GoogleGrpc: - test_server_->waitForCounterGe("grpc.accesslog.streams_closed_0", 1); + test_server_->waitForCounter("grpc.accesslog.streams_closed_0", Ge(1)); break; default: PANIC("reached unexpected code"); @@ -326,7 +328,7 @@ TEST_P(AccessLogIntegrationTest, GrpcLoggerSurvivesAfterReloadConfig) { &response, true); EXPECT_TRUE(response.find("HTTP/1.1 200") == 0); - test_server_->waitForCounterEq("access_logs.grpc_access_log.logs_written", 2); + test_server_->waitForCounter("access_logs.grpc_access_log.logs_written", Eq(2)); // Create a new config with HTTP/1.0 proxying. The goal is to trigger a listener update. ConfigHelper new_config_helper(version_, config_helper_.bootstrap()); @@ -339,15 +341,15 @@ TEST_P(AccessLogIntegrationTest, GrpcLoggerSurvivesAfterReloadConfig) { // Create an LDS response with the new config, and reload config. new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); - test_server_->waitForCounterEq("listener_manager.lds.update_success", 2); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); + test_server_->waitForCounter("listener_manager.lds.update_success", Eq(2)); // Wait until the http 1.1 connection is destroyed due to the listener update. It indicates the // listener starts draining. - test_server_->waitForGaugeEq("listener.listener_0.downstream_cx_active", 0); + test_server_->waitForGauge("listener.listener_0.downstream_cx_active", Eq(0)); // Wait until all the draining filter chain is gone. It indicates the old listener and filter // chains are destroyed. - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 0); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(0)); // Verify that the new listener config is applied. std::string response2; @@ -355,7 +357,7 @@ TEST_P(AccessLogIntegrationTest, GrpcLoggerSurvivesAfterReloadConfig) { EXPECT_THAT(response2, HasSubstr("HTTP/1.0 200 OK\r\n")); // Verify that the grpc access logger is available after the listener update. - test_server_->waitForCounterEq("access_logs.grpc_access_log.logs_written", 4); + test_server_->waitForCounter("access_logs.grpc_access_log.logs_written", Eq(4)); } } // namespace diff --git a/test/extensions/access_loggers/open_telemetry/access_log_integration_test.cc b/test/extensions/access_loggers/open_telemetry/access_log_integration_test.cc index 4b397eb2bad8d..f27233cc35c7c 100644 --- a/test/extensions/access_loggers/open_telemetry/access_log_integration_test.cc +++ b/test/extensions/access_loggers/open_telemetry/access_log_integration_test.cc @@ -17,6 +17,7 @@ #include "opentelemetry/proto/collector/logs/v1/logs_service.pb.h" using testing::AssertionResult; +using testing::Eq; constexpr char EXPECTED_REQUEST_MESSAGE[] = R"EOF( resource_logs: @@ -277,7 +278,7 @@ TEST_P(AccessLogIntegrationTest, AccessLoggerStatsAreIndependentOfListener) { }); new_config_helper.setLds("1"); ASSERT_TRUE(codec_client_->waitForDisconnect()); - test_server_->waitForGaugeEq("listener_manager.total_listeners_active", 1); + test_server_->waitForGauge("listener_manager.total_listeners_active", Eq(1)); } // Make another request, the existing access logger should be used. @@ -290,7 +291,7 @@ TEST_P(AccessLogIntegrationTest, AccessLoggerStatsAreIndependentOfListener) { codec_client_->close(); cleanup(); - test_server_->waitForCounterEq("access_logs.open_telemetry_access_log.logs_written", 2); + test_server_->waitForCounter("access_logs.open_telemetry_access_log.logs_written", Eq(2)); } class AccessLogFormatterHeaderTest : public testing::TestWithParam, diff --git a/test/extensions/access_loggers/stats/integration_test.cc b/test/extensions/access_loggers/stats/integration_test.cc index 45e29f2d9a1cd..3b2df4247f4fe 100644 --- a/test/extensions/access_loggers/stats/integration_test.cc +++ b/test/extensions/access_loggers/stats/integration_test.cc @@ -7,6 +7,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -87,9 +89,10 @@ TEST_P(StatsAccessLogIntegrationTest, Basic) { ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ(response->headers().getStatusValue(), "200"); - test_server_->waitForCounterEq( - "test_stat_prefix.fixedcounter.fixed_tag.fixed_value.dynamic_tag.mytagvalue_HTTP/1.1", 42); - test_server_->waitForCounterEq("test_stat_prefix.formatcounter", 200); + test_server_->waitForCounter( + "test_stat_prefix.fixedcounter.fixed_tag.fixed_value.dynamic_tag.mytagvalue_HTTP/1.1", + Eq(42)); + test_server_->waitForCounter("test_stat_prefix.formatcounter", Eq(200)); test_server_->waitUntilHistogramHasSamples("test_stat_prefix.testhistogram.tag.mytagvalue"); Stats::ParentHistogramSharedPtr histogram = @@ -218,7 +221,7 @@ TEST_P(StatsAccessLogIntegrationTest, ActiveRequestsGauge) { waitForNextUpstreamRequest(); // After DownstreamStart is logged, gauge should be 1. - test_server_->waitForGaugeEq("test_stat_prefix.active_requests.request_header_tag.my-tag", 1); + test_server_->waitForGauge("test_stat_prefix.active_requests.request_header_tag.my-tag", Eq(1)); // Send response from upstream. Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; @@ -229,7 +232,7 @@ TEST_P(StatsAccessLogIntegrationTest, ActiveRequestsGauge) { EXPECT_EQ(response->headers().getStatusValue(), "200"); // After DownstreamEnd is logged, gauge should be 0. - test_server_->waitForGaugeEq("test_stat_prefix.active_requests.request_header_tag.my-tag", 0); + test_server_->waitForGauge("test_stat_prefix.active_requests.request_header_tag.my-tag", Eq(0)); codec_client_->close(); } @@ -275,9 +278,9 @@ TEST_P(StatsAccessLogIntegrationTest, SubtractWithoutAdd) { ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ(response->headers().getStatusValue(), "200"); - test_server_->waitForGaugeEq("test_stat_prefix.active_requests.request_header_tag.my-tag", 0); + test_server_->waitForGauge("test_stat_prefix.active_requests.request_header_tag.my-tag", Eq(0)); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); } TEST_P(StatsAccessLogIntegrationTest, GaugeInterleavedOpsWithEviction) { @@ -311,8 +314,8 @@ TEST_P(StatsAccessLogIntegrationTest, GaugeInterleavedOpsWithEviction) { IntegrationCodecClientPtr codec_client1 = makeHttpConnection(lookupPort("http")); IntegrationStreamDecoderPtr response1 = codec_client1->makeHeaderOnlyRequest(request_headers); waitForNextUpstreamRequest(); - test_server_->waitForGaugeEq( - "test_stat_prefix.active_requests.request_header_tag.my-eviction-test-tag", 1); + test_server_->waitForGauge( + "test_stat_prefix.active_requests.request_header_tag.my-eviction-test-tag", Eq(1)); // Simulate eviction from the store. absl::Notification evict_done; @@ -338,15 +341,15 @@ TEST_P(StatsAccessLogIntegrationTest, GaugeInterleavedOpsWithEviction) { ASSERT_TRUE(upstream_request2->waitForEndStream(*dispatcher_)); // The gauge should be kept even with eviction happened and the active request is 2. - test_server_->waitForGaugeEq( - "test_stat_prefix.active_requests.request_header_tag.my-eviction-test-tag", 2); + test_server_->waitForGauge( + "test_stat_prefix.active_requests.request_header_tag.my-eviction-test-tag", Eq(2)); // Clean up. Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; upstream_request_->encodeHeaders(response_headers, true); ASSERT_TRUE(response1->waitForEndStream()); - test_server_->waitForGaugeEq( - "test_stat_prefix.active_requests.request_header_tag.my-eviction-test-tag", 1); + test_server_->waitForGauge( + "test_stat_prefix.active_requests.request_header_tag.my-eviction-test-tag", Eq(1)); upstream_request2->encodeHeaders(response_headers, true); ASSERT_TRUE(response2->waitForEndStream()); @@ -359,8 +362,8 @@ TEST_P(StatsAccessLogIntegrationTest, GaugeInterleavedOpsWithEviction) { }); evict_done3.WaitForNotification(); - test_server_->waitForGaugeEq( - "test_stat_prefix.active_requests.request_header_tag.my-eviction-test-tag", 0); + test_server_->waitForGauge( + "test_stat_prefix.active_requests.request_header_tag.my-eviction-test-tag", Eq(0)); codec_client1->close(); codec_client2->close(); @@ -412,8 +415,8 @@ TEST_P(StatsAccessLogIntegrationTest, ActiveRequestsGaugeEvictedWhileInflight) { IntegrationStreamDecoderPtr response1 = codec_client1->makeHeaderOnlyRequest(request_headers); waitForNextUpstreamRequest(); - test_server_->waitForGaugeEq( - "test_stat_prefix.active_requests.request_header_tag.my-evict-crash-tag", 1); + test_server_->waitForGauge( + "test_stat_prefix.active_requests.request_header_tag.my-evict-crash-tag", Eq(1)); // Force gauge value to 0 so it can be evicted while FilterState is holding it. test_server_ @@ -476,8 +479,8 @@ TEST_P(StatsAccessLogIntegrationTest, GaugeCleanupOnDestructor) { waitForNextUpstreamRequest(); // DownstreamStart logged, gauge should be 1. - test_server_->waitForGaugeEq( - "test_stat_prefix.active_requests.request_header_tag.my-evict-cleanup-tag", 1); + test_server_->waitForGauge( + "test_stat_prefix.active_requests.request_header_tag.my-evict-cleanup-tag", Eq(1)); upstream_request_->encodeHeaders(response_headers, true); ASSERT_TRUE(response->waitForEndStream()); @@ -487,8 +490,8 @@ TEST_P(StatsAccessLogIntegrationTest, GaugeCleanupOnDestructor) { // Since sub_log_type is configured for UdpPeriodic (which never happens in HTTP), the explicit // SUB op is skipped. When the request dies, AccessLogState destructor should run and subtract the // gauge. The gauge should go back to 0. - test_server_->waitForGaugeEq( - "test_stat_prefix.active_requests.request_header_tag.my-evict-cleanup-tag", 0); + test_server_->waitForGauge( + "test_stat_prefix.active_requests.request_header_tag.my-evict-cleanup-tag", Eq(0)); } TEST_P(StatsAccessLogIntegrationTest, SharedScope) { @@ -535,7 +538,7 @@ TEST_P(StatsAccessLogIntegrationTest, SharedScope) { // Since both access loggers share the same configuration, they should share the same scope. // We expect the first counter to be incremented once (by the first access logger). // The second counter (from the second logger) should be dropped because the scope limit is 1. - test_server_->waitForCounterEq("shared_scope_limits.formatcounter1", 200); + test_server_->waitForCounter("shared_scope_limits.formatcounter1", Eq(200)); auto store_counter = test_server_->counter("shared_scope_limits.formatcounter2"); EXPECT_EQ(store_counter, nullptr); @@ -589,20 +592,20 @@ name: envoy.filters.network.tcp_proxy ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(raw_conn1)); ASSERT_TRUE(client1->connected()); - test_server_->waitForGaugeEq("test_stat_prefix.active_connections", 1); + test_server_->waitForGauge("test_stat_prefix.active_connections", Eq(1)); IntegrationTcpClientPtr client2 = makeTcpConnection(lookupPort("listener_0")); FakeRawConnectionPtr raw_conn2; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(raw_conn2)); ASSERT_TRUE(client2->connected()); - test_server_->waitForGaugeEq("test_stat_prefix.active_connections", 2); + test_server_->waitForGauge("test_stat_prefix.active_connections", Eq(2)); client1->close(); - test_server_->waitForGaugeEq("test_stat_prefix.active_connections", 1); + test_server_->waitForGauge("test_stat_prefix.active_connections", Eq(1)); client2->close(); - test_server_->waitForGaugeEq("test_stat_prefix.active_connections", 0); + test_server_->waitForGauge("test_stat_prefix.active_connections", Eq(0)); } INSTANTIATE_TEST_SUITE_P(IpVersions, StatsAccessLogTcpIntegrationTest, diff --git a/test/extensions/bootstrap/internal_listener/internal_listener_integration_test.cc b/test/extensions/bootstrap/internal_listener/internal_listener_integration_test.cc index b1a36c24b443a..3c73479af501c 100644 --- a/test/extensions/bootstrap/internal_listener/internal_listener_integration_test.cc +++ b/test/extensions/bootstrap/internal_listener/internal_listener_integration_test.cc @@ -16,6 +16,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Eq; namespace Envoy { namespace { @@ -57,8 +58,8 @@ TEST_P(InternalListenerIntegrationTest, BasicConfigUpdate) { new_config_helper.setLds("1"); - test_server_->waitForCounterEq("listener_manager.listener_modified", 1); - test_server_->waitForGaugeEq("listener_manager.total_listeners_draining", 0); + test_server_->waitForCounter("listener_manager.listener_modified", Eq(1)); + test_server_->waitForGauge("listener_manager.total_listeners_draining", Eq(0)); } TEST_P(InternalListenerIntegrationTest, InplaceUpdate) { @@ -80,8 +81,8 @@ TEST_P(InternalListenerIntegrationTest, InplaceUpdate) { new_config_helper.setLds("1"); - test_server_->waitForCounterEq("listener_manager.listener_modified", 1); - test_server_->waitForGaugeEq("listener_manager.total_listeners_draining", 0); + test_server_->waitForCounter("listener_manager.listener_modified", Eq(1)); + test_server_->waitForGauge("listener_manager.total_listeners_draining", Eq(0)); } TEST_P(InternalListenerIntegrationTest, DeleteListener) { @@ -96,8 +97,8 @@ TEST_P(InternalListenerIntegrationTest, DeleteListener) { new_config_helper.setLds("1"); - test_server_->waitForCounterEq("listener_manager.listener_removed", 1); - test_server_->waitForGaugeEq("listener_manager.total_listeners_draining", 0); + test_server_->waitForCounter("listener_manager.listener_removed", Eq(1)); + test_server_->waitForGauge("listener_manager.total_listeners_draining", Eq(0)); } INSTANTIATE_TEST_SUITE_P(IpVersions, InternalListenerIntegrationTest, diff --git a/test/extensions/clusters/aggregate/cluster_integration_test.cc b/test/extensions/clusters/aggregate/cluster_integration_test.cc index fe0ab80276998..3d41017e06c0c 100644 --- a/test/extensions/clusters/aggregate/cluster_integration_test.cc +++ b/test/extensions/clusters/aggregate/cluster_integration_test.cc @@ -20,6 +20,8 @@ #include "gtest/gtest.h" using testing::AssertionResult; +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -233,7 +235,7 @@ class AggregateIntegrationTest sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {cluster1_}, {cluster1_}, {}, "55"); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); // Wait for our statically specified listener to become ready, and register its port in the // test framework's downstream listener port map. @@ -269,7 +271,7 @@ TEST_P(AggregateIntegrationTest, ClusterUpDownUp) { {}, {FirstClusterName}, "42"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); // Now that cluster_1 is gone, the listener (with its routing to cluster_1) should 503. BufferingStreamDecoderPtr response = @@ -286,7 +288,7 @@ TEST_P(AggregateIntegrationTest, ClusterUpDownUp) { sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {cluster1_}, {cluster1_}, {}, "413"); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); testRouterHeaderOnlyRequestAndResponse(nullptr, FirstUpstreamIndex, "/aggregatecluster"); cleanupUpstreamAndDownstream(); @@ -305,7 +307,7 @@ TEST_P(AggregateIntegrationTest, TwoClusters) { sendDiscoveryResponse( Config::TestTypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster2_}, {}, "42"); // The '4' includes the fake CDS server and aggregate cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(4)); // A request for aggregate cluster should be fine. testRouterHeaderOnlyRequestAndResponse(nullptr, FirstUpstreamIndex, "/aggregatecluster"); @@ -318,7 +320,7 @@ TEST_P(AggregateIntegrationTest, TwoClusters) { Config::TestTypeUrl::get().Cluster, {cluster2_}, {}, {FirstClusterName}, "43"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); testRouterHeaderOnlyRequestAndResponse(nullptr, SecondUpstreamIndex, "/aggregatecluster"); cleanupUpstreamAndDownstream(); @@ -329,7 +331,7 @@ TEST_P(AggregateIntegrationTest, TwoClusters) { sendDiscoveryResponse( Config::TestTypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster1_}, {}, "413"); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(4)); testRouterHeaderOnlyRequestAndResponse(nullptr, FirstUpstreamIndex, "/aggregatecluster"); cleanupUpstreamAndDownstream(); @@ -345,7 +347,7 @@ TEST_P(AggregateIntegrationTest, PreviousPrioritiesRetryPredicate) { sendDiscoveryResponse( Config::TestTypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster2_}, {}, "42"); // The '4' includes the fake CDS server and aggregate cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(4)); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeRequestWithBody( @@ -400,18 +402,18 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxConnections) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "55", {}, {}, {})); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {cluster1_}, {cluster1_}, {}, "56"); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); // initial circuit breaker states: // the aggregate cluster circuit breaker is closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.cx_open", 0); - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.remaining_cx", - 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_cx_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.cx_open", Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_cx", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_cx_overflow", Eq(0)); // the cluster1 circuit breaker is closed - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.cx_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_cx", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_cx_overflow", 0); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.cx_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_cx", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_cx_overflow", Eq(0)); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -427,14 +429,14 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxConnections) { // after the first request arrives at cluster1 (there is now a single active upstream connection) // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.cx_open", 0); - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.remaining_cx", - 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_cx_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.cx_open", Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_cx", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_cx_overflow", Eq(0)); // the cluster1 circuit breaker opens - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.cx_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_cx", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_cx_overflow", 0); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.cx_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_cx", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_cx_overflow", Eq(0)); // send a second request to the aggregate cluster auto aggregate_cluster_response2 = codec_client_->makeHeaderOnlyRequest( @@ -445,14 +447,14 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxConnections) { // the second request is rejected because the cluster1 circuit breaker is open // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.cx_open", 0); - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.remaining_cx", - 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_cx_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.cx_open", Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_cx", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_cx_overflow", Eq(0)); // the cluster1 circuit breaker remains open and overflows - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.cx_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_cx", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_cx_overflow", 1); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.cx_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_cx", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_cx_overflow", Eq(1)); // the requests to the aggregate cluster route affect the cluster1 circuit breaker state // send a third request directly to cluster1 to confirm this @@ -461,14 +463,14 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxConnections) { // the third request is rejected because the cluster1 circuit breaker is already open // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.cx_open", 0); - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.remaining_cx", - 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_cx_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.cx_open", Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_cx", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_cx_overflow", Eq(0)); // the cluster1 circuit breaker remains open and overflows again - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.cx_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_cx", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_cx_overflow", 2); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.cx_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_cx", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_cx_overflow", Eq(2)); // respond to the first request to the aggregate cluster upstream_request_->encodeHeaders(default_response_headers_, true); @@ -483,16 +485,16 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxConnections) { // after completing the first request and closing the connection // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.cx_open", 0); - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.remaining_cx", - 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_cx_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.cx_open", Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_cx", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_cx_overflow", Eq(0)); // the cluster1 circuit breaker closes - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.cx_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_cx", 1); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.cx_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_cx", Eq(1)); // the overflow may be greater than 2 because after completing the first request // the queued pending requests will attempt to reuse the connection - test_server_->waitForCounterGe("cluster.cluster_1.upstream_cx_overflow", 2); + test_server_->waitForCounter("cluster.cluster_1.upstream_cx_overflow", Ge(2)); cleanupUpstreamAndDownstream(); } @@ -520,18 +522,18 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxRequests) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "55", {}, {}, {})); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {cluster1_}, {cluster1_}, {}, "56"); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); // initial circuit breaker states: // the aggregate cluster circuit breaker is closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_open", 0); - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.remaining_rq", - 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_open", Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_rq", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_pending_overflow", Eq(0)); // the cluster1 circuit breaker is closed - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_rq", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_active_overflow", 0); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_rq", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_active_overflow", Eq(0)); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -547,14 +549,14 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxRequests) { // after the first request arrives at cluster1 (there is now a single active upstream request) // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_open", 0); - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.remaining_rq", - 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_open", Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_rq", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_pending_overflow", Eq(0)); // the cluster1 circuit breaker opens - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_rq", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_active_overflow", 0); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_rq", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_active_overflow", Eq(0)); // send a second request to the aggregate cluster auto aggregate_cluster_response2 = codec_client_->makeHeaderOnlyRequest( @@ -568,14 +570,14 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxRequests) { EXPECT_EQ("503", aggregate_cluster_response2->headers().getStatusValue()); // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_open", 0); - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.remaining_rq", - 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_open", Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_rq", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_pending_overflow", Eq(0)); // the cluster1 circuit breaker remains open and overflows - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_rq", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_active_overflow", 1); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_rq", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_active_overflow", Eq(1)); // the requests to the aggregate cluster route affect the cluster1 circuit breaker state // send a third request directly to cluster1 to confirm this @@ -587,14 +589,14 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxRequests) { EXPECT_EQ("503", cluster1_response1->headers().getStatusValue()); // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_open", 0); - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.remaining_rq", - 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_open", Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_rq", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_pending_overflow", Eq(0)); // the cluster1 circuit breaker remains open and overflows again - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_rq", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_active_overflow", 2); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_rq", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_active_overflow", Eq(2)); // respond to the first request to the aggregate cluster upstream_request_->encodeHeaders(default_response_headers_, true); @@ -604,14 +606,14 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxRequests) { EXPECT_EQ("200", aggregate_cluster_response1->headers().getStatusValue()); // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_open", 0); - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.remaining_rq", - 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_open", Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_rq", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_pending_overflow", Eq(0)); // the cluster1 circuit breaker closes - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_rq", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_active_overflow", 2); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_rq", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_active_overflow", Eq(2)); cleanupUpstreamAndDownstream(); } @@ -644,19 +646,19 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxPendingRequests) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "55", {}, {}, {})); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {cluster1_}, {cluster1_}, {}, "56"); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); // initial circuit breaker states: // the aggregate cluster circuit breaker is closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_pending_open", - 0); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_pending", 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_pending_open", + Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_pending", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_pending_overflow", Eq(0)); // the cluster1 circuit breaker is closed - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_pending_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_pending", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_pending_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_pending", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_pending_overflow", Eq(0)); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -678,15 +680,15 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxPendingRequests) { {":authority", "host"}}); // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_pending_open", - 0); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_pending", 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_pending_open", + Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_pending", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_pending_overflow", Eq(0)); // the cluster1 circuit breaker opens - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_pending_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_pending", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_pending_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_pending", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_pending_overflow", Eq(0)); // send a third request to the aggregate cluster (this is the second pending request) auto aggregate_cluster_response3 = codec_client_->makeHeaderOnlyRequest( @@ -700,15 +702,15 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxPendingRequests) { EXPECT_EQ("503", aggregate_cluster_response3->headers().getStatusValue()); // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_pending_open", - 0); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_pending", 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_pending_open", + Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_pending", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_pending_overflow", Eq(0)); // the cluster1 circuit breaker remains open and overflows - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_pending_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_pending", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_pending_overflow", 1); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_pending_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_pending", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_pending_overflow", Eq(1)); // the pending requests to the aggregate cluster route affect the cluster1 circuit breaker state // send a fourth request directly to cluster1 to confirm this @@ -720,15 +722,15 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxPendingRequests) { EXPECT_EQ("503", cluster1_response1->headers().getStatusValue()); // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_pending_open", - 0); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_pending", 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_pending_open", + Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_pending", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_pending_overflow", Eq(0)); // the cluster1 circuit breaker remains open and overflows again - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_pending_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_pending", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_pending_overflow", 2); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_pending_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_pending", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_pending_overflow", Eq(2)); // respond to the first request to the aggregate cluster upstream_request_->encodeHeaders(default_response_headers_, true); @@ -744,15 +746,15 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerTestMaxPendingRequests) { EXPECT_EQ("200", aggregate_cluster_response2->headers().getStatusValue()); // the aggregate cluster circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_pending_open", - 0); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_pending", 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_pending_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_pending_open", + Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_pending", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_pending_overflow", Eq(0)); // the cluster1 circuit breaker closes - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_pending_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_pending", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_pending_overflow", 2); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_pending_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_pending", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_pending_overflow", Eq(2)); cleanupUpstreamAndDownstream(); } @@ -798,19 +800,19 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerMaxRetriesTest) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "55", {}, {}, {})); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {cluster1_}, {cluster1_}, {}, "56"); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); // initial circuit breaker states: // the aggregate cluster circuit breaker is closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", - 0); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_retry_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", + Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_retry_overflow", Eq(0)); // the cluster1 circuit breaker is closed - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_retry_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_retries", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_retry_overflow", 0); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_retry_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_retries", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_retry_overflow", Eq(0)); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -831,15 +833,15 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerMaxRetriesTest) { auto first_request_retry = std::move(upstream_request_); // the aggregate_cluster the circuit breaker opens - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", - 1); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", 0); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_retry_overflow", 0); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", + Eq(1)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", + Eq(0)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_retry_overflow", Eq(0)); // the cluster1 circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_retry_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_retries", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_retry_overflow", 0); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_retry_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_retries", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_retry_overflow", Eq(0)); // send a second request to the aggregate cluster auto aggregate_cluster_response2 = codec_client_->makeHeaderOnlyRequest( @@ -854,15 +856,15 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerMaxRetriesTest) { upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "503"}}, true); // the aggregate_cluster the circuit breaker remains open and overflows - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", - 1); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", 0); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_retry_overflow", 1); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", + Eq(1)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", + Eq(0)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_retry_overflow", Eq(1)); // the cluster1 circuit breaker remains closed - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_retry_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_retries", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_retry_overflow", 0); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_retry_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_retries", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_retry_overflow", Eq(0)); // send a third request directly to cluster1 auto cluster1_response1 = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ @@ -878,15 +880,15 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerMaxRetriesTest) { auto third_request_retry = std::move(upstream_request_); // the aggregate_cluster the circuit breaker remains open - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", - 1); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", 0); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_retry_overflow", 1); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", + Eq(1)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", + Eq(0)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_retry_overflow", Eq(1)); // the cluster1 circuit breaker opens - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_retry_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_retries", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_retry_overflow", 0); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_retry_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_retries", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_retry_overflow", Eq(0)); // send a fourth request directly to cluster1 auto cluster1_response2 = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ @@ -898,15 +900,15 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerMaxRetriesTest) { upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "503"}}, true); // the aggregate_cluster the circuit breaker remains open - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", - 1); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", 0); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_retry_overflow", 1); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", + Eq(1)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", + Eq(0)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_retry_overflow", Eq(1)); // the cluster1 circuit breaker remains open and overflows - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_retry_open", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_retries", 0); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_retry_overflow", 1); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_retry_open", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_retries", Eq(0)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_retry_overflow", Eq(1)); // respond to the third request to cluster1 third_request_retry->encodeHeaders(default_response_headers_, true); @@ -924,15 +926,15 @@ TEST_P(AggregateIntegrationTest, CircuitBreakerMaxRetriesTest) { EXPECT_EQ("503", aggregate_cluster_response2->headers().getStatusValue()); // the aggregate cluster circuit breaker is closed - test_server_->waitForGaugeEq("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", - 0); - test_server_->waitForGaugeEq( - "cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", 1); - test_server_->waitForCounterEq("cluster.aggregate_cluster.upstream_rq_retry_overflow", 1); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.rq_retry_open", + Eq(0)); + test_server_->waitForGauge("cluster.aggregate_cluster.circuit_breakers.default.remaining_retries", + Eq(1)); + test_server_->waitForCounter("cluster.aggregate_cluster.upstream_rq_retry_overflow", Eq(1)); // the cluster1 circuit breaker is closed - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.rq_retry_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.remaining_retries", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_retry_overflow", 1); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.rq_retry_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.remaining_retries", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_retry_overflow", Eq(1)); cleanupUpstreamAndDownstream(); } diff --git a/test/extensions/clusters/composite/cluster_integration_test.cc b/test/extensions/clusters/composite/cluster_integration_test.cc index a2059669c4ebe..bdf6698009579 100644 --- a/test/extensions/clusters/composite/cluster_integration_test.cc +++ b/test/extensions/clusters/composite/cluster_integration_test.cc @@ -86,7 +86,7 @@ class CompositeClusterIntegrationTest : public testing::TestWithParamwaitForGaugeGe("cluster_manager.active_clusters", 4); + test_server_->waitForGauge("cluster_manager.active_clusters", testing::Ge(4)); } void setNumRetries(uint32_t retries) { num_retries_ = retries; } diff --git a/test/extensions/clusters/mcp_multicluster/cluster_integration_test.cc b/test/extensions/clusters/mcp_multicluster/cluster_integration_test.cc index 98221baed38be..01baa5fb56cdf 100644 --- a/test/extensions/clusters/mcp_multicluster/cluster_integration_test.cc +++ b/test/extensions/clusters/mcp_multicluster/cluster_integration_test.cc @@ -83,7 +83,7 @@ class CompositeClusterIntegrationTest : public testing::TestWithParamwaitForGaugeGe("cluster_manager.active_clusters", 4); + test_server_->waitForGauge("cluster_manager.active_clusters", testing::Ge(4)); } void setNumRetries(uint32_t retries) { num_retries_ = retries; } diff --git a/test/extensions/clusters/redis/redis_cluster_integration_test.cc b/test/extensions/clusters/redis/redis_cluster_integration_test.cc index 092d0f1e02e92..e41659a68ff80 100644 --- a/test/extensions/clusters/redis/redis_cluster_integration_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_integration_test.cc @@ -11,6 +11,7 @@ #include "test/integration/ads_integration.h" #include "test/integration/integration.h" +using testing::Ge; using testing::Return; namespace Envoy { @@ -709,7 +710,7 @@ TEST_P(RedisAdsIntegrationTest, RedisClusterRemoval) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "1", {}, {}, {})); // Validate that redis listener is successfully created. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Now send a CDS update, removing redis cluster added above. sendDiscoveryResponse( @@ -717,7 +718,7 @@ TEST_P(RedisAdsIntegrationTest, RedisClusterRemoval) { {"redis_cluster"}, "2"); // Validate that the cluster is removed successfully. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); } INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDeltaWildcard, RedisAdsIntegrationTest, diff --git a/test/extensions/clusters/reverse_connection/reverse_connection_cluster_integration_test.cc b/test/extensions/clusters/reverse_connection/reverse_connection_cluster_integration_test.cc index c398bbef4503a..295205f7d2070 100644 --- a/test/extensions/clusters/reverse_connection/reverse_connection_cluster_integration_test.cc +++ b/test/extensions/clusters/reverse_connection/reverse_connection_cluster_integration_test.cc @@ -18,6 +18,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace Extensions { namespace Clusters { @@ -343,15 +345,15 @@ TEST_P(ReverseConnectionClusterIntegrationTest, EndToEndReverseTunnelTest) { ENVOY_LOG_MISC(info, "Waiting for reverse tunnel connections to be established."); // Wait for reverse tunnel to establish. - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1, - std::chrono::milliseconds(5000)); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1), + std::chrono::milliseconds(5000)); // Wait for the listener to accept a downstream connection. - test_server_->waitForCounterGe("listener.reverse_conn_listener.downstream_cx_total", 1, - std::chrono::milliseconds(5000)); + test_server_->waitForCounter("listener.reverse_conn_listener.downstream_cx_total", Ge(1), + std::chrono::milliseconds(5000)); // Verify reverse tunnel stats. - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.nodes.test-node-id", 1); - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.clusters.test-cluster-id", 1); + test_server_->waitForGauge("reverse_tunnel_acceptor.nodes.test-node-id", Ge(1)); + test_server_->waitForGauge("reverse_tunnel_acceptor.clusters.test-cluster-id", Ge(1)); // Verify no handshake errors occurred. EXPECT_EQ(test_server_->counter("reverse_tunnel.handshake.parse_error")->value(), 0); @@ -364,10 +366,10 @@ TEST_P(ReverseConnectionClusterIntegrationTest, EndToEndReverseTunnelTest) { const std::string formatted_tunnel_address = formattedTunnelAddress(tunnel_listener_port); const std::string initiator_host_stat = fmt::format("reverse_tunnel_initiator.host.{}.connected", formatted_tunnel_address); - test_server_->waitForGaugeGe(initiator_host_stat, 1, std::chrono::milliseconds(2000)); + test_server_->waitForGauge(initiator_host_stat, Ge(1), std::chrono::milliseconds(2000)); // Verify cluster-level initiator stats. - test_server_->waitForGaugeGe("reverse_tunnel_initiator.cluster.tunnel_cluster.connected", 1); + test_server_->waitForGauge("reverse_tunnel_initiator.cluster.tunnel_cluster.connected", Ge(1)); ENVOY_LOG_MISC(info, "Reverse tunnel established. Sending HTTP request through tunnel."); @@ -398,9 +400,9 @@ TEST_P(ReverseConnectionClusterIntegrationTest, EndToEndReverseTunnelTest) { // Verify cluster stats for the reverse connection cluster. ENVOY_LOG_MISC(info, "Verifying reverse connection cluster stats."); - test_server_->waitForCounterGe("cluster.reverse_connection_cluster.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.reverse_connection_cluster.upstream_rq_total", 1); - test_server_->waitForCounterGe("cluster.reverse_connection_cluster.upstream_rq_completed", 1); + test_server_->waitForCounter("cluster.reverse_connection_cluster.upstream_cx_total", Ge(1)); + test_server_->waitForCounter("cluster.reverse_connection_cluster.upstream_rq_total", Ge(1)); + test_server_->waitForCounter("cluster.reverse_connection_cluster.upstream_rq_completed", Ge(1)); EXPECT_EQ( test_server_->counter("cluster.reverse_connection_cluster.upstream_cx_connect_fail")->value(), 0); @@ -493,14 +495,14 @@ TEST_P(ReverseConnectionClusterIntegrationTest, EndToEndReverseTunnelTest) { // Verify updated cluster stats after concurrent requests. ENVOY_LOG_MISC(info, "Verifying updated stats after concurrent requests."); - test_server_->waitForCounterGe("cluster.reverse_connection_cluster.upstream_rq_total", - 6); // 1 initial + 5 concurrent - test_server_->waitForCounterGe("cluster.reverse_connection_cluster.upstream_rq_completed", 6); + test_server_->waitForCounter("cluster.reverse_connection_cluster.upstream_rq_total", + Ge(6)); // 1 initial + 5 concurrent + test_server_->waitForCounter("cluster.reverse_connection_cluster.upstream_rq_completed", Ge(6)); // Verify that all requests routed through the existing reverse tunnel. // Since all requests use test-node-id or test-cluster-id (which both map to the same tunnel), // they all successfully use the established connection. - test_server_->waitForCounterEq("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Eq(1)); ENVOY_LOG_MISC(info, "All concurrent requests successfully routed through single established tunnel."); @@ -517,8 +519,8 @@ TEST_P(ReverseConnectionClusterIntegrationTest, EndToEndReverseTunnelTest) { EXPECT_EQ("200", drain_response->headers().getStatusValue()); // Wait for listeners to be fully stopped before test cleanup. - test_server_->waitForCounterEq("listener_manager.listener_stopped", 3, - std::chrono::milliseconds(5000)); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(3), + std::chrono::milliseconds(5000)); } // End-to-end reverse connection cluster test with mTLS. @@ -601,14 +603,14 @@ TEST_P(ReverseConnectionClusterIntegrationTest, EndToEndReverseTunnelTestWithMut ENVOY_LOG_MISC(info, "Waiting for mTLS reverse tunnel connections to be established."); // Wait for reverse tunnel to establish with mTLS. - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1, - std::chrono::milliseconds(5000)); - test_server_->waitForCounterGe("listener.reverse_conn_listener.downstream_cx_total", 1, - std::chrono::milliseconds(5000)); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1), + std::chrono::milliseconds(5000)); + test_server_->waitForCounter("listener.reverse_conn_listener.downstream_cx_total", Ge(1), + std::chrono::milliseconds(5000)); // Verify reverse tunnel stats. - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.nodes.test-node-id", 1); - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.clusters.test-cluster-id", 1); + test_server_->waitForGauge("reverse_tunnel_acceptor.nodes.test-node-id", Ge(1)); + test_server_->waitForGauge("reverse_tunnel_acceptor.clusters.test-cluster-id", Ge(1)); // Verify no handshake errors occurred. EXPECT_EQ(test_server_->counter("reverse_tunnel.handshake.parse_error")->value(), 0); @@ -620,12 +622,12 @@ TEST_P(ReverseConnectionClusterIntegrationTest, EndToEndReverseTunnelTestWithMut const std::string formatted_tunnel_address = formattedTunnelAddress(tunnel_listener_port); const std::string initiator_host_stat = fmt::format("reverse_tunnel_initiator.host.{}.connected", formatted_tunnel_address); - test_server_->waitForGaugeGe(initiator_host_stat, 1, std::chrono::milliseconds(1000)); - test_server_->waitForGaugeGe("reverse_tunnel_initiator.cluster.tunnel_cluster.connected", 1); + test_server_->waitForGauge(initiator_host_stat, Ge(1), std::chrono::milliseconds(1000)); + test_server_->waitForGauge("reverse_tunnel_initiator.cluster.tunnel_cluster.connected", Ge(1)); // Give a small delay for pings to occur. - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1, - std::chrono::milliseconds(1000)); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1), + std::chrono::milliseconds(1000)); ENVOY_LOG_MISC(info, "Sending HTTP request through mTLS tunnel."); @@ -655,9 +657,9 @@ TEST_P(ReverseConnectionClusterIntegrationTest, EndToEndReverseTunnelTestWithMut // Verify cluster stats for the reverse connection cluster. ENVOY_LOG_MISC(info, "Verifying reverse connection cluster stats."); - test_server_->waitForCounterGe("cluster.reverse_connection_cluster.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.reverse_connection_cluster.upstream_rq_total", 1); - test_server_->waitForCounterGe("cluster.reverse_connection_cluster.upstream_rq_completed", 1); + test_server_->waitForCounter("cluster.reverse_connection_cluster.upstream_cx_total", Ge(1)); + test_server_->waitForCounter("cluster.reverse_connection_cluster.upstream_rq_total", Ge(1)); + test_server_->waitForCounter("cluster.reverse_connection_cluster.upstream_rq_completed", Ge(1)); EXPECT_EQ( test_server_->counter("cluster.reverse_connection_cluster.upstream_cx_connect_fail")->value(), 0); @@ -675,8 +677,8 @@ TEST_P(ReverseConnectionClusterIntegrationTest, EndToEndReverseTunnelTestWithMut EXPECT_EQ("200", drain_response->headers().getStatusValue()); // Wait for listeners to be fully stopped before test cleanup. - test_server_->waitForCounterEq("listener_manager.listener_stopped", 3, - std::chrono::milliseconds(5000)); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(3), + std::chrono::milliseconds(5000)); } // Test resilience when an initiator node goes down and comes back up. @@ -917,12 +919,12 @@ name: envoy.bootstrap.reverse_tunnel.downstream_socket_interface HttpIntegrationTest::initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); - test_server_->waitForCounterGe("listener_manager.listener_create_success", - 4); // 4 initiator listeners + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); + test_server_->waitForCounter("listener_manager.listener_create_success", + Ge(4)); // 4 initiator listeners test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.total_listeners_active", - 7); // egress + 2 clouds + 4 initiators + test_server_->waitForGauge("listener_manager.total_listeners_active", + Ge(7)); // egress + 2 clouds + 4 initiators // Register static listener ports (cloud listeners and egress). registerTestServerPorts({"cloud_1_listener", "cloud_2_listener", "egress_listener"}); @@ -930,14 +932,14 @@ name: envoy.bootstrap.reverse_tunnel.downstream_socket_interface ENVOY_LOG_MISC(info, "Waiting for all 4 tunnel connections to establish."); // Wait for all 4 tunnels (2 nodes x 2 clouds). - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 4, - std::chrono::milliseconds(10000)); - test_server_->waitForCounterGe("listener.reverse_conn_listener.downstream_cx_total", 4, - std::chrono::milliseconds(5000)); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(4), + std::chrono::milliseconds(10000)); + test_server_->waitForCounter("listener.reverse_conn_listener.downstream_cx_total", Ge(4), + std::chrono::milliseconds(5000)); - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.nodes.node-1", 2); - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.nodes.node-2", 2); - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.clusters.test-cluster", 4); + test_server_->waitForGauge("reverse_tunnel_acceptor.nodes.node-1", Ge(2)); + test_server_->waitForGauge("reverse_tunnel_acceptor.nodes.node-2", Ge(2)); + test_server_->waitForGauge("reverse_tunnel_acceptor.clusters.test-cluster", Ge(4)); ENVOY_LOG_MISC(info, "All 4 tunnels established. Testing initial connectivity."); @@ -980,14 +982,14 @@ name: envoy.bootstrap.reverse_tunnel.downstream_socket_interface ENVOY_LOG_MISC(info, "Simulating node-1 failure by removing its initiator listeners via LDS."); sendLdsResponse({node2_cloud1_config, node2_cloud2_config}, "2"); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 2); - test_server_->waitForCounterGe("listener_manager.listener_removed", - 2); // 2 node-1 listeners removed + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(2)); + test_server_->waitForCounter("listener_manager.listener_removed", + Ge(2)); // 2 node-1 listeners removed // Verify stats show reduced connections (should drop from 4 to 2). ENVOY_LOG_MISC(info, "Verifying that node-1 connections are gone."); - test_server_->waitForGaugeEq("reverse_tunnel_acceptor.nodes.node-1", 0); - test_server_->waitForGaugeEq("reverse_tunnel_acceptor.nodes.node-2", 2); + test_server_->waitForGauge("reverse_tunnel_acceptor.nodes.node-1", Eq(0)); + test_server_->waitForGauge("reverse_tunnel_acceptor.nodes.node-2", Eq(2)); // Verify node-2 still works. ENVOY_LOG_MISC(info, "Verifying node-2 still works after node-1 failure."); @@ -1023,19 +1025,19 @@ name: envoy.bootstrap.reverse_tunnel.downstream_socket_interface sendLdsResponse( {node1_cloud1_config, node1_cloud2_config, node2_cloud1_config, node2_cloud2_config}, "3"); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 3); - test_server_->waitForCounterGe("listener_manager.listener_create_success", - 6); // 4 initial + 2 re-added + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(3)); + test_server_->waitForCounter("listener_manager.listener_create_success", + Ge(6)); // 4 initial + 2 re-added // Wait for node-1 tunnels to re-establish. ENVOY_LOG_MISC(info, "Waiting for node-1 tunnels to re-establish."); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 6, - std::chrono::milliseconds(10000)); // 4 initial + 2 reconnect - test_server_->waitForCounterGe("listener.reverse_conn_listener.downstream_cx_total", 6, - std::chrono::milliseconds(5000)); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(6), + std::chrono::milliseconds(10000)); // 4 initial + 2 reconnect + test_server_->waitForCounter("listener.reverse_conn_listener.downstream_cx_total", Ge(6), + std::chrono::milliseconds(5000)); - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.nodes.node-1", 2); - test_server_->waitForGaugeEq("reverse_tunnel_acceptor.nodes.node-2", 2); + test_server_->waitForGauge("reverse_tunnel_acceptor.nodes.node-1", Ge(2)); + test_server_->waitForGauge("reverse_tunnel_acceptor.nodes.node-2", Eq(2)); // Verify both nodes work after recovery. ENVOY_LOG_MISC(info, "Verifying full connectivity restored."); @@ -1098,8 +1100,8 @@ name: envoy.bootstrap.reverse_tunnel.downstream_socket_interface EXPECT_EQ("200", drain_response->headers().getStatusValue()); // Wait for listeners to be fully stopped before test cleanup. - test_server_->waitForCounterGe("listener_manager.listener_stopped", 7, - std::chrono::milliseconds(5000)); + test_server_->waitForCounter("listener_manager.listener_stopped", Ge(7), + std::chrono::milliseconds(5000)); } // Multi-worker reverse tunnel test where: @@ -1136,15 +1138,15 @@ TEST_P(ReverseConnectionClusterIntegrationTest, MultiWorkerEndToEndReverseTunnel ENVOY_LOG_MISC(info, "Waiting for all 4 workers to establish reverse tunnel connections."); // Each of the 4 workers should establish 1 connection, so we expect 4 total handshakes. - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 4, - std::chrono::milliseconds(10000)); - test_server_->waitForCounterGe("listener.reverse_conn_listener.downstream_cx_total", 4, - std::chrono::milliseconds(5000)); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(4), + std::chrono::milliseconds(10000)); + test_server_->waitForCounter("listener.reverse_conn_listener.downstream_cx_total", Ge(4), + std::chrono::milliseconds(5000)); // Verify total node connections. Since all workers use the same node-id (test-node-id), // the acceptor should show 4 connections from the same logical node. - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.nodes.test-node-id", 4); - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.clusters.test-cluster-id", 4); + test_server_->waitForGauge("reverse_tunnel_acceptor.nodes.test-node-id", Ge(4)); + test_server_->waitForGauge("reverse_tunnel_acceptor.clusters.test-cluster-id", Ge(4)); // Verify no handshake errors occurred. EXPECT_EQ(test_server_->counter("reverse_tunnel.handshake.parse_error")->value(), 0); @@ -1163,12 +1165,12 @@ TEST_P(ReverseConnectionClusterIntegrationTest, MultiWorkerEndToEndReverseTunnel const std::string worker_host_stat = fmt::format("reverse_tunnel_initiator.worker_{}.host.{}.connected", worker_id, formatted_tunnel_address); - test_server_->waitForGaugeEq(worker_host_stat, 1, std::chrono::milliseconds(2000)); + test_server_->waitForGauge(worker_host_stat, Eq(1), std::chrono::milliseconds(2000)); // Check per-worker cluster stat const std::string worker_cluster_stat = fmt::format( "reverse_tunnel_initiator.worker_{}.cluster.tunnel_cluster.connected", worker_id); - test_server_->waitForGaugeEq(worker_cluster_stat, 1, std::chrono::milliseconds(2000)); + test_server_->waitForGauge(worker_cluster_stat, Eq(1), std::chrono::milliseconds(2000)); ENVOY_LOG_MISC(info, "Worker {} has initiated 1 reverse connection.", worker_id); } @@ -1176,12 +1178,12 @@ TEST_P(ReverseConnectionClusterIntegrationTest, MultiWorkerEndToEndReverseTunnel // Verify cross-worker initiator stats (aggregated across all workers). const std::string cross_worker_initiator_host_stat = fmt::format("reverse_tunnel_initiator.host.{}.connected", formatted_tunnel_address); - test_server_->waitForGaugeEq(cross_worker_initiator_host_stat, 4, - std::chrono::milliseconds(2000)); + test_server_->waitForGauge(cross_worker_initiator_host_stat, Eq(4), + std::chrono::milliseconds(2000)); const std::string cross_worker_initiator_cluster_stat = "reverse_tunnel_initiator.cluster.tunnel_cluster.connected"; - test_server_->waitForGaugeEq(cross_worker_initiator_cluster_stat, 4, - std::chrono::milliseconds(2000)); + test_server_->waitForGauge(cross_worker_initiator_cluster_stat, Eq(4), + std::chrono::milliseconds(2000)); // Verify that each worker accepted exactly 1 connection (acceptor side). ENVOY_LOG_MISC(info, "Verifying per-worker acceptor connections."); @@ -1190,21 +1192,21 @@ TEST_P(ReverseConnectionClusterIntegrationTest, MultiWorkerEndToEndReverseTunnel // Check per-worker node stat const std::string worker_node_stat = fmt::format("reverse_tunnel_acceptor.worker_{}.node.test-node-id", worker_id); - test_server_->waitForGaugeEq(worker_node_stat, 1, std::chrono::milliseconds(2000)); + test_server_->waitForGauge(worker_node_stat, Eq(1), std::chrono::milliseconds(2000)); // Check per-worker cluster stat const std::string worker_cluster_stat = fmt::format("reverse_tunnel_acceptor.worker_{}.cluster.test-cluster-id", worker_id); - test_server_->waitForGaugeEq(worker_cluster_stat, 1, std::chrono::milliseconds(2000)); + test_server_->waitForGauge(worker_cluster_stat, Eq(1), std::chrono::milliseconds(2000)); // Check per-worker aggregate metrics (total_nodes and total_clusters for each worker) const std::string worker_total_nodes_stat = fmt::format("reverse_tunnel_acceptor.worker_{}.total_nodes", worker_id); - test_server_->waitForGaugeEq(worker_total_nodes_stat, 1, std::chrono::milliseconds(2000)); + test_server_->waitForGauge(worker_total_nodes_stat, Eq(1), std::chrono::milliseconds(2000)); const std::string worker_total_clusters_stat = fmt::format("reverse_tunnel_acceptor.worker_{}.total_clusters", worker_id); - test_server_->waitForGaugeEq(worker_total_clusters_stat, 1, std::chrono::milliseconds(2000)); + test_server_->waitForGauge(worker_total_clusters_stat, Eq(1), std::chrono::milliseconds(2000)); ENVOY_LOG_MISC(info, "Worker {} has accepted 1 reverse connection.", worker_id); } @@ -1246,8 +1248,8 @@ TEST_P(ReverseConnectionClusterIntegrationTest, MultiWorkerEndToEndReverseTunnel ENVOY_LOG_MISC(info, "{} of {} requests returned 200.", success_count, num_requests); // Verify cluster stats — all requests were attempted through the tunnel. - test_server_->waitForCounterGe("cluster.reverse_connection_cluster.upstream_rq_total", - num_requests); + test_server_->waitForCounter("cluster.reverse_connection_cluster.upstream_rq_total", + Ge(num_requests)); // Verify that the 4 worker tunnels were established. EXPECT_EQ(test_server_->counter("reverse_tunnel.handshake.accepted")->value(), 4); @@ -1265,8 +1267,8 @@ TEST_P(ReverseConnectionClusterIntegrationTest, MultiWorkerEndToEndReverseTunnel EXPECT_EQ("200", drain_response->headers().getStatusValue()); // Wait for listeners to be fully stopped before test cleanup. - test_server_->waitForCounterEq("listener_manager.listener_stopped", 3, - std::chrono::milliseconds(5000)); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(3), + std::chrono::milliseconds(5000)); } } // namespace diff --git a/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc b/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc index dd2ebb697dec7..b7c7db216c76b 100644 --- a/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc +++ b/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc @@ -97,7 +97,8 @@ class MinimumClustersValidatorIntegrationTest : public Grpc::DeltaSotwIntegratio // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse describing cluster_1 that we sent. // 2 because the statically specified CDS server itself counts as a cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", initial_clusters_num + 1); + test_server_->waitForGauge("cluster_manager.active_clusters", + testing::Ge(initial_clusters_num + 1)); // Wait for our statically specified listener to become ready, and register its port in the // test framework's downstream listener port map. diff --git a/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc b/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc index e6dbc02089a1c..eec899a1e476a 100644 --- a/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc +++ b/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc @@ -13,6 +13,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -209,7 +211,7 @@ class XdsFailoverAdsIntegrationTest : public AdsDeltaSotwIntegrationSubStatePara // current invocation of gRPC stream closure piece of code that will also // enable the retry timer. // 3. The test thread will increase the simulated time. - test_server_->waitForCounterGe("cluster_manager.cds.update_failure", expected_failures); + test_server_->waitForCounter("cluster_manager.cds.update_failure", Ge(expected_failures)); absl::Notification notification; test_server_->server().dispatcher().post([&]() { notification.Notify(); }); notification.WaitForNotification(); @@ -256,17 +258,17 @@ class XdsFailoverAdsIntegrationTest : public AdsDeltaSotwIntegrationSubStatePara sendDiscoveryResponse( CdsTypeUrl, {ConfigHelper::buildCluster("cluster_0")}, {ConfigHelper::buildCluster("cluster_0")}, {}, "1", {}, xds_stream); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest(EdsTypeUrl, "", {"cluster_0"}, {"cluster_0"}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", xds_stream)); sendDiscoveryResponse( EdsTypeUrl, {buildClusterLoadAssignment("cluster_0")}, {buildClusterLoadAssignment("cluster_0")}, {}, "1", {}, xds_stream); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(0)); EXPECT_TRUE(compareDiscoveryRequest(CdsTypeUrl, "1", {}, {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", xds_stream)); @@ -283,7 +285,7 @@ class XdsFailoverAdsIntegrationTest : public AdsDeltaSotwIntegrationSubStatePara EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "1", {}, {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", xds_stream)); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); } @@ -354,7 +356,7 @@ TEST_P(XdsFailoverAdsIntegrationTest, StartupPrimaryNotResponding) { ASSERT_TRUE(xds_connection_->waitForDisconnect()); // The CDS request fails when the primary disconnects. - test_server_->waitForCounterGe("cluster_manager.cds.update_failure", 2); + test_server_->waitForCounter("cluster_manager.cds.update_failure", Ge(2)); AssertionResult result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); @@ -415,7 +417,7 @@ TEST_P(XdsFailoverAdsIntegrationTest, StartupPrimaryGrpcFailure) { } // The CDS request fails when the primary disconnects. - test_server_->waitForCounterGe("cluster_manager.cds.update_failure", 2); + test_server_->waitForCounter("cluster_manager.cds.update_failure", Ge(2)); ASSERT(failover_xds_connection_ == nullptr); result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); @@ -462,7 +464,7 @@ TEST_P(XdsFailoverAdsIntegrationTest, StartupPrimaryGrpcFailureAfterHeaders) { xds_stream_->finishGrpcStream(Grpc::Status::Internal); // The CDS request fails when the primary disconnects. - test_server_->waitForCounterGe("cluster_manager.cds.update_failure", 2); + test_server_->waitForCounter("cluster_manager.cds.update_failure", Ge(2)); ASSERT(failover_xds_connection_ == nullptr); result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); @@ -504,8 +506,8 @@ TEST_P(XdsFailoverAdsIntegrationTest, NoFailoverUseAfterPrimaryResponse) { sendDiscoveryResponse( CdsTypeUrl, {ConfigHelper::buildCluster("cluster_0")}, {ConfigHelper::buildCluster("cluster_0")}, {}, "1", {}, xds_stream_.get()); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(1)); // Envoy has received a CDS response, it means the primary is available. // Now disconnect the primary. @@ -513,7 +515,7 @@ TEST_P(XdsFailoverAdsIntegrationTest, NoFailoverUseAfterPrimaryResponse) { // CDS was successful, but EDS will fail. After that add a notification to the // main thread to ensure that the retry timer kicks in. - test_server_->waitForCounterGe("cluster.cluster_0.update_failure", 1); + test_server_->waitForCounter("cluster.cluster_0.update_failure", Ge(1)); absl::Notification notification; test_server_->server().dispatcher().post([&]() { notification.Notify(); }); notification.WaitForNotification(); @@ -601,7 +603,7 @@ TEST_P(XdsFailoverAdsIntegrationTest, PrimaryUseAfterFailoverResponseAndDisconne ASSERT_TRUE(xds_connection_->waitForDisconnect()); // The CDS request fails when the primary disconnects. - test_server_->waitForCounterGe("cluster_manager.cds.update_failure", 2); + test_server_->waitForCounter("cluster_manager.cds.update_failure", Ge(2)); AssertionResult result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); @@ -620,8 +622,8 @@ TEST_P(XdsFailoverAdsIntegrationTest, PrimaryUseAfterFailoverResponseAndDisconne {ConfigHelper::buildCluster("failover_cluster_0")}, {}, "failover1", {}, failover_xds_stream_.get()); // Wait for an EDS request, and send its response. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.failover_cluster_0.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.failover_cluster_0.warming_state", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest( EdsTypeUrl, "", {"failover_cluster_0"}, {"failover_cluster_0"}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", failover_xds_stream_.get())); @@ -629,9 +631,9 @@ TEST_P(XdsFailoverAdsIntegrationTest, PrimaryUseAfterFailoverResponseAndDisconne EdsTypeUrl, {buildClusterLoadAssignment("failover_cluster_0")}, {buildClusterLoadAssignment("failover_cluster_0")}, {}, "failover1", {}, failover_xds_stream_.get()); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); - test_server_->waitForGaugeEq("cluster.failover_cluster_0.warming_state", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); + test_server_->waitForGauge("cluster.failover_cluster_0.warming_state", Eq(0)); EXPECT_EQ(2, test_server_->gauge("control_plane.connected_state")->value()); EXPECT_TRUE(compareDiscoveryRequest(CdsTypeUrl, "failover1", {}, {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", @@ -646,7 +648,7 @@ TEST_P(XdsFailoverAdsIntegrationTest, PrimaryUseAfterFailoverResponseAndDisconne // CDS and EDS were successful, but LDS will fail. After that add a notification to the // main thread to ensure that the retry timer kicks in. - test_server_->waitForCounterGe("listener_manager.lds.update_failure", 1); + test_server_->waitForCounter("listener_manager.lds.update_failure", Ge(1)); absl::Notification notification; test_server_->server().dispatcher().post([&]() { notification.Notify(); }); notification.WaitForNotification(); @@ -679,8 +681,8 @@ TEST_P(XdsFailoverAdsIntegrationTest, PrimaryUseAfterFailoverResponseAndDisconne sendDiscoveryResponse( CdsTypeUrl, {ConfigHelper::buildCluster("primary_cluster_0")}, {ConfigHelper::buildCluster("primary_cluster_0")}, {}, "primary1", {}, xds_stream_.get()); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.primary_cluster_0.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.primary_cluster_0.warming_state", Eq(1)); // Expect an updated failover EDS request. EXPECT_TRUE(compareDiscoveryRequest(EdsTypeUrl, "", {"primary_cluster_0"}, {"primary_cluster_0"}, @@ -720,7 +722,7 @@ TEST_P(XdsFailoverAdsIntegrationTest, FailoverUseAfterFailoverResponseAndDisconn ASSERT_TRUE(xds_connection_->waitForDisconnect()); // The CDS request fails when the primary disconnects. - test_server_->waitForCounterGe("cluster_manager.cds.update_failure", 2); + test_server_->waitForCounter("cluster_manager.cds.update_failure", Ge(2)); AssertionResult result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); @@ -739,8 +741,8 @@ TEST_P(XdsFailoverAdsIntegrationTest, FailoverUseAfterFailoverResponseAndDisconn {ConfigHelper::buildCluster("failover_cluster_0")}, {}, "failover1", {}, failover_xds_stream_.get()); // Wait for an EDS request, and send its response. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.failover_cluster_0.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.failover_cluster_0.warming_state", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest( EdsTypeUrl, "", {"failover_cluster_0"}, {"failover_cluster_0"}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", failover_xds_stream_.get())); @@ -748,9 +750,9 @@ TEST_P(XdsFailoverAdsIntegrationTest, FailoverUseAfterFailoverResponseAndDisconn EdsTypeUrl, {buildClusterLoadAssignment("failover_cluster_0")}, {buildClusterLoadAssignment("failover_cluster_0")}, {}, "failover1", {}, failover_xds_stream_.get()); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); - test_server_->waitForGaugeEq("cluster.failover_cluster_0.warming_state", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); + test_server_->waitForGauge("cluster.failover_cluster_0.warming_state", Eq(0)); EXPECT_EQ(2, test_server_->gauge("control_plane.connected_state")->value()); EXPECT_TRUE(compareDiscoveryRequest(CdsTypeUrl, "failover1", {}, {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", @@ -802,8 +804,8 @@ TEST_P(XdsFailoverAdsIntegrationTest, FailoverUseAfterFailoverResponseAndDisconn CdsTypeUrl, {ConfigHelper::buildCluster("failover_cluster_1")}, {ConfigHelper::buildCluster("failover_cluster_1")}, {}, "failover2", {}, failover_xds_stream_.get()); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.failover_cluster_1.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.failover_cluster_1.warming_state", Eq(1)); // Expect an updated failover EDS request. EXPECT_TRUE(compareDiscoveryRequest( @@ -844,7 +846,7 @@ TEST_P(XdsFailoverAdsIntegrationTest, ASSERT_TRUE(xds_connection_->waitForDisconnect()); // The CDS request fails when the primary disconnects. - test_server_->waitForCounterGe("cluster_manager.cds.update_failure", 2); + test_server_->waitForCounter("cluster_manager.cds.update_failure", Ge(2)); AssertionResult result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); @@ -863,8 +865,8 @@ TEST_P(XdsFailoverAdsIntegrationTest, {ConfigHelper::buildCluster("failover_cluster_0")}, {}, "failover1", {}, failover_xds_stream_.get()); // Wait for an EDS request, and send its response. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.failover_cluster_0.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.failover_cluster_0.warming_state", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest( EdsTypeUrl, "", {"failover_cluster_0"}, {"failover_cluster_0"}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", failover_xds_stream_.get())); @@ -872,9 +874,9 @@ TEST_P(XdsFailoverAdsIntegrationTest, EdsTypeUrl, {buildClusterLoadAssignment("failover_cluster_0")}, {buildClusterLoadAssignment("failover_cluster_0")}, {}, "failover1", {}, failover_xds_stream_.get()); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); - test_server_->waitForGaugeEq("cluster.failover_cluster_0.warming_state", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); + test_server_->waitForGauge("cluster.failover_cluster_0.warming_state", Eq(0)); EXPECT_EQ(2, test_server_->gauge("control_plane.connected_state")->value()); EXPECT_TRUE(compareDiscoveryRequest(CdsTypeUrl, "failover1", {}, {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", @@ -951,7 +953,7 @@ TEST_P(XdsFailoverAdsIntegrationTest, NoPrimaryUseAfterFailoverResponse) { ASSERT_TRUE(xds_connection_->waitForDisconnect()); // The CDS request fails when the primary disconnects. - test_server_->waitForCounterGe("cluster_manager.cds.update_failure", 2); + test_server_->waitForCounter("cluster_manager.cds.update_failure", Ge(2)); AssertionResult result = failover_xds_upstream_->waitForHttpConnection(*dispatcher_, failover_xds_connection_); @@ -970,8 +972,8 @@ TEST_P(XdsFailoverAdsIntegrationTest, NoPrimaryUseAfterFailoverResponse) { {ConfigHelper::buildCluster("failover_cluster_0")}, {}, "failover1", {}, failover_xds_stream_.get()); // Wait for an EDS request, and send its response. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.failover_cluster_0.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.failover_cluster_0.warming_state", Eq(1)); // Ensure basic flow with failover works. EXPECT_TRUE(compareDiscoveryRequest( EdsTypeUrl, "", {"failover_cluster_0"}, {"failover_cluster_0"}, {}, false, @@ -980,9 +982,9 @@ TEST_P(XdsFailoverAdsIntegrationTest, NoPrimaryUseAfterFailoverResponse) { EdsTypeUrl, {buildClusterLoadAssignment("failover_cluster_0")}, {buildClusterLoadAssignment("failover_cluster_0")}, {}, "failover1", {}, failover_xds_stream_.get()); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); - test_server_->waitForGaugeEq("cluster.failover_cluster_0.warming_state", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); + test_server_->waitForGauge("cluster.failover_cluster_0.warming_state", Eq(0)); EXPECT_EQ(2, test_server_->gauge("control_plane.connected_state")->value()); EXPECT_TRUE(compareDiscoveryRequest(CdsTypeUrl, "failover1", {}, {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", @@ -995,7 +997,7 @@ TEST_P(XdsFailoverAdsIntegrationTest, NoPrimaryUseAfterFailoverResponse) { // Now disconnect the failover source, this should result in an LDS failure. // After that add a notification to the main thread to ensure that the retry timer kicks in. failover_xds_stream_->finishGrpcStream(Grpc::Status::Internal); - test_server_->waitForCounterGe("listener_manager.lds.update_failure", 1); + test_server_->waitForCounter("listener_manager.lds.update_failure", Ge(1)); absl::Notification notification; test_server_->server().dispatcher().post([&]() { notification.Notify(); }); notification.WaitForNotification(); diff --git a/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.cc b/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.cc index 57affa3a51801..02d1053729ccc 100644 --- a/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.cc +++ b/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.cc @@ -7,6 +7,7 @@ #include "gtest/gtest.h" +using testing::Eq; namespace Envoy { void AdaptiveConcurrencyIntegrationTest::sendRequests(uint32_t request_count, @@ -44,7 +45,7 @@ void AdaptiveConcurrencyIntegrationTest::sendRequests(uint32_t request_count, auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); responses_.push_back(std::move(response)); - test_server_->waitForCounterEq(REQUEST_BLOCK_COUNTER_NAME, ++blocked_counter); + test_server_->waitForCounter(REQUEST_BLOCK_COUNTER_NAME, Eq(++blocked_counter)); // These will remain nullptr. upstream_connections_.emplace_back(); @@ -113,7 +114,7 @@ TEST_P(AdaptiveConcurrencyIntegrationTest, TestConcurrency1) { EXPECT_EQ(0, test_server_->counter(REQUEST_BLOCK_COUNTER_NAME)->value()); sendRequests(2, 1); respondToAllRequests(1, std::chrono::milliseconds(5)); - test_server_->waitForCounterEq(REQUEST_BLOCK_COUNTER_NAME, 1); + test_server_->waitForCounter(REQUEST_BLOCK_COUNTER_NAME, Eq(1)); } // Test many requests, where only a single request returns 200 during the minRTT window. @@ -123,7 +124,7 @@ TEST_P(AdaptiveConcurrencyIntegrationTest, TestManyConcurrency1) { EXPECT_EQ(0, test_server_->counter(REQUEST_BLOCK_COUNTER_NAME)->value()); sendRequests(10, 1); respondToAllRequests(1, std::chrono::milliseconds(5)); - test_server_->waitForCounterEq(REQUEST_BLOCK_COUNTER_NAME, 9); + test_server_->waitForCounter(REQUEST_BLOCK_COUNTER_NAME, Eq(9)); } // Test many grpc requests, where only a single request returns 200 during the minRTT window. @@ -134,7 +135,7 @@ TEST_P(AdaptiveConcurrencyIntegrationTest, TestManyConcurrencyGrpc) { EXPECT_EQ(0, test_server_->counter(REQUEST_BLOCK_COUNTER_NAME)->value()); sendRequests(10, 1); respondToAllRequests(1, std::chrono::milliseconds(5)); - test_server_->waitForCounterEq(REQUEST_BLOCK_COUNTER_NAME, 9); + test_server_->waitForCounter(REQUEST_BLOCK_COUNTER_NAME, Eq(9)); } /** diff --git a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc index c145bb2f7dd87..1dc896b974b3c 100644 --- a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc +++ b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc @@ -19,6 +19,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Eq; using testing::NiceMock; using testing::Return; @@ -210,7 +211,7 @@ TEST_F(AdmissionControlTest, HttpFailureBehavior) { setupFilter(config); // We expect rejection counter to increment upon failure. - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 0, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(0), time_system_)); EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isHttpSuccess(500)).WillRepeatedly(Return(false)); @@ -221,7 +222,7 @@ TEST_F(AdmissionControlTest, HttpFailureBehavior) { filter_->decodeHeaders(request_headers, true)); sampleHttpRequest("500"); - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 1, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(1), time_system_)); } // Validate simple HTTP success case. @@ -230,7 +231,7 @@ TEST_F(AdmissionControlTest, HttpSuccessBehavior) { setupFilter(config); // We expect rejection counter to NOT increment upon success. - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 0, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(0), time_system_)); EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isHttpSuccess(200)).WillRepeatedly(Return(true)); @@ -240,7 +241,7 @@ TEST_F(AdmissionControlTest, HttpSuccessBehavior) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); sampleHttpRequest("200"); - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 0, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(0), time_system_)); } // Validate simple gRPC failure case. @@ -248,7 +249,7 @@ TEST_F(AdmissionControlTest, GrpcFailureBehavior) { auto config = makeConfig(default_yaml_); setupFilter(config); - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 0, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(0), time_system_)); EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isGrpcSuccess(7)).WillRepeatedly(Return(false)); @@ -260,7 +261,7 @@ TEST_F(AdmissionControlTest, GrpcFailureBehavior) { sampleGrpcRequest(Grpc::Status::WellKnownGrpcStatus::PermissionDenied); // We expect rejection counter to increment upon failure. - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 1, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(1), time_system_)); } // Validate simple gRPC success case with status in the trailer. @@ -268,7 +269,7 @@ TEST_F(AdmissionControlTest, GrpcSuccessBehaviorTrailer) { auto config = makeConfig(default_yaml_); setupFilter(config); - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 0, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(0), time_system_)); EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isGrpcSuccess(0)).WillRepeatedly(Return(true)); @@ -279,7 +280,7 @@ TEST_F(AdmissionControlTest, GrpcSuccessBehaviorTrailer) { sampleGrpcRequestTrailer(Grpc::Status::WellKnownGrpcStatus::Ok); // We expect rejection counter to NOT increment upon success. - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 0, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(0), time_system_)); } // Validate simple gRPC failure case with status in the trailer. @@ -287,7 +288,7 @@ TEST_F(AdmissionControlTest, GrpcFailureBehaviorTrailer) { auto config = makeConfig(default_yaml_); setupFilter(config); - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 0, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(0), time_system_)); EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isGrpcSuccess(7)).WillRepeatedly(Return(false)); @@ -299,7 +300,7 @@ TEST_F(AdmissionControlTest, GrpcFailureBehaviorTrailer) { sampleGrpcRequestTrailer(Grpc::Status::WellKnownGrpcStatus::PermissionDenied); // We expect rejection counter to increment upon failure. - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 1, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(1), time_system_)); } // Validate simple gRPC success case. @@ -307,7 +308,7 @@ TEST_F(AdmissionControlTest, GrpcSuccessBehavior) { auto config = makeConfig(default_yaml_); setupFilter(config); - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 0, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(0), time_system_)); EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isGrpcSuccess(0)).WillRepeatedly(Return(true)); @@ -318,7 +319,7 @@ TEST_F(AdmissionControlTest, GrpcSuccessBehavior) { sampleGrpcRequest(Grpc::Status::WellKnownGrpcStatus::Ok); // We expect rejection counter to NOT increment upon success. - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 0, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(0), time_system_)); } // Validate rejection probabilities. @@ -422,7 +423,7 @@ sampling_window: 10s EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(100)); // We expect rejection counter to increment upon failure. - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 0, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(0), time_system_)); EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isHttpSuccess(500)).WillRepeatedly(Return(false)); @@ -432,7 +433,7 @@ sampling_window: 10s filter_->decodeHeaders(request_headers, true)); sampleHttpRequest("500"); - EXPECT_TRUE(TestUtility::waitForCounterEq(store_, "test_prefix.rq_rejected", 1, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(store_, "test_prefix.rq_rejected", Eq(1), time_system_)); } // Validate max rejection probability. diff --git a/test/extensions/filters/http/alternate_protocols_cache/filter_integration_test.cc b/test/extensions/filters/http/alternate_protocols_cache/filter_integration_test.cc index 05a9ee608b1d6..32ba9a8b50145 100644 --- a/test/extensions/filters/http/alternate_protocols_cache/filter_integration_test.cc +++ b/test/extensions/filters/http/alternate_protocols_cache/filter_integration_test.cc @@ -13,6 +13,7 @@ #include "test/integration/http_protocol_integration.h" #include "test/integration/ssl_utility.h" +using testing::Eq; namespace Envoy { namespace { @@ -144,9 +145,9 @@ TEST_P(FilterIntegrationTest, AltSvc) { checkSimpleRequestSuccess(request_size, response_size, response.get()); // Close the connection so the HTTP/2 connection will not be used. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http2_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http2_total", Eq(1)); ASSERT_TRUE(fake_upstream_connection_->close()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); fake_upstream_connection_.reset(); // Second request should go out over HTTP/3 (upstream index 1) because of the Alt-Svc information. @@ -155,7 +156,7 @@ TEST_P(FilterIntegrationTest, AltSvc) { auto response2 = sendRequestAndWaitForResponse(request_headers, request_size, response_headers, response_size, 1, timeout); checkSimpleRequestSuccess(request_size, response_size, response2.get()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http3_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http3_total", Eq(1)); } TEST_P(FilterIntegrationTest, AltSvcCached) { @@ -181,7 +182,7 @@ TEST_P(FilterIntegrationTest, AltSvcCached) { auto response2 = sendRequestAndWaitForResponse(request_headers, request_size, response_headers, response_size, 1, timeout); checkSimpleRequestSuccess(request_size, response_size, response2.get()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http3_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http3_total", Eq(1)); } TEST_P(FilterIntegrationTest, AltSvcCachedH3Slow) { @@ -214,7 +215,8 @@ TEST_P(FilterIntegrationTest, AltSvcCachedH3Slow) { waitForNextUpstreamConnection(std::vector{1}, TestUtility::DefaultTimeout, h3_connection); // Of the 100 connection pools configured, the grid registers as taking up one. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", 99); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", + Eq(99)); // The created stream will reset. FakeStreamPtr upstream_request2; @@ -231,7 +233,7 @@ TEST_P(FilterIntegrationTest, AltSvcCachedH3Slow) { // Now close the connection to make sure it doesn't cause problems for the // downstream stream. ASSERT_TRUE(h3_connection->close()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); // Finish the response. upstream_request_->encodeHeaders(default_response_headers_, true); @@ -239,12 +241,12 @@ TEST_P(FilterIntegrationTest, AltSvcCachedH3Slow) { ASSERT_TRUE(response->waitForEndStream(timeout)); checkSimpleRequestSuccess(request_size, response_size, response.get()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http3_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http3_total", Eq(1)); cleanupUpstreamAndDownstream(); // Wait for the grid to be torn down to make sure it is not problematic. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", - 100); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", + Eq(100)); } TEST_P(FilterIntegrationTest, AltSvcCachedH3SlowTillH2Finishes) { @@ -282,8 +284,9 @@ TEST_P(FilterIntegrationTest, AltSvcCachedH3SlowTillH2Finishes) { waitForNextUpstreamConnection(std::vector{1}, TestUtility::DefaultTimeout, h3_connection); // Of the 100 connection pools configured, the grid registers as taking up one. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", 99); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http3_total", 1); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", + Eq(99)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http3_total", Eq(1)); // An upstream HTTP/3 stream should be created without crash, and the created stream will be // reset. @@ -294,12 +297,12 @@ TEST_P(FilterIntegrationTest, AltSvcCachedH3SlowTillH2Finishes) { // Now close the HTTP/3 connection to make sure it doesn't cause problems for the // downstream stream. ASSERT_TRUE(h3_connection->close()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); cleanupUpstreamAndDownstream(); // Wait for the grid to be torn down to make sure it is not problematic. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", - 100); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", + Eq(100)); } // TODO(32151): Figure out why it's flaky and re-enable. @@ -345,9 +348,10 @@ TEST_P(FilterIntegrationTest, DISABLED_AltSvcCachedH2Slow) { auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); // Wait for both connections to be attempted. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(2)); // Of the 100 connection pools configured, the grid registers as taking up one. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", 99); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", + Eq(99)); // Unblock Http3. block_http3.Notify(); @@ -364,8 +368,8 @@ TEST_P(FilterIntegrationTest, DISABLED_AltSvcCachedH2Slow) { cleanupUpstreamAndDownstream(); // Wait for the grid to be torn down to make sure it is not problematic. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", - 100); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.remaining_cx_pools", + Eq(100)); } TEST_P(FilterIntegrationTest, AltSvcIgnoredWithProxyConfig) { @@ -392,9 +396,9 @@ TEST_P(FilterIntegrationTest, AltSvcIgnoredWithProxyConfig) { checkSimpleRequestSuccess(request_size, response_size, response.get()); // Close the connection so the HTTP/2 connection will not be used. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http2_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http2_total", Eq(1)); ASSERT_TRUE(fake_upstream_connection_->close()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); fake_upstream_connection_.reset(); absl::string_view upstream_address(fake_upstreams_[0]->localAddress()->asStringView()); @@ -405,8 +409,8 @@ TEST_P(FilterIntegrationTest, AltSvcIgnoredWithProxyConfig) { auto response2 = sendRequestAndWaitForResponse(request_headers, request_size, response_headers, response_size, 0, timeout); checkSimpleRequestSuccess(request_size, response_size, response2.get()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http2_total", 2); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http3_total", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http2_total", Eq(2)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http3_total", Eq(0)); } TEST_P(FilterIntegrationTest, RetryAfterHttp3ZeroRttHandshakeFailed) { @@ -426,10 +430,10 @@ TEST_P(FilterIntegrationTest, RetryAfterHttp3ZeroRttHandshakeFailed) { auto response = sendRequestAndWaitForResponse(default_request_headers_, 0, response_headers, 0, /*upstream_index=*/0, timeout); checkSimpleRequestSuccess(0, response_size, response.get()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http2_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http2_total", Eq(1)); // Close the connection so the HTTP/2 connection will not be used. ASSERT_TRUE(fake_upstream_connection_->close()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); fake_upstream_connection_.reset(); // The 2nd request should go out over HTTP/3 because of the Alt-Svc information. @@ -440,7 +444,7 @@ TEST_P(FilterIntegrationTest, RetryAfterHttp3ZeroRttHandshakeFailed) { EXPECT_EQ(1u, test_server_->counter("cluster.cluster_0.upstream_cx_http3_total")->value()); // Close the h3 upstream connection so that the next request will create another connection. ASSERT_TRUE(fake_upstream_connection_->close()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(2)); fake_upstream_connection_.reset(); // Stop the HTTP/3 fake upstream. @@ -450,9 +454,9 @@ TEST_P(FilterIntegrationTest, RetryAfterHttp3ZeroRttHandshakeFailed) { // credentials. auto response3 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); // Wait for the upstream to connect timeout and the failed early data request to be retried. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_retry", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_retry", Eq(1)); EXPECT_EQ(1u, test_server_->counter("cluster.cluster_0.upstream_rq_0rtt")->value()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 3); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(3)); // The retry should attempt both HTTP/3 and HTTP/2. And the TCP connection will win the race. waitForNextUpstreamRequest(0); @@ -461,7 +465,7 @@ TEST_P(FilterIntegrationTest, RetryAfterHttp3ZeroRttHandshakeFailed) { checkSimpleRequestSuccess(0, response_size, response3.get()); EXPECT_EQ(2u, test_server_->counter("cluster.cluster_0.upstream_cx_http2_total")->value()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_connect_fail", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_connect_fail", Eq(2)); EXPECT_EQ(3u, test_server_->counter("cluster.cluster_0.upstream_cx_http3_total")->value()); EXPECT_EQ(1u, test_server_->counter("cluster.cluster_0.upstream_http3_broken")->value()); @@ -505,19 +509,19 @@ TEST_P(FilterIntegrationTest, H3PostHandshakeFailoverToTcp) { auto response = sendRequestAndWaitForResponse(request_headers, 2048, response_headers, 0, /*upstream_index=*/0, timeout); checkSimpleRequestSuccess(2048, response_size, response.get()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http2_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http2_total", Eq(1)); // Close the connection so the HTTP/2 connection will not be used. ASSERT_TRUE(fake_upstream_connection_->close()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); fake_upstream_connection_.reset(); // Second request should go out over HTTP/3 because of the Alt-Svc information. auto response2 = codec_client_->makeRequestWithBody(request_headers, 2048); waitForNextUpstreamRequest(1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http3_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http3_total", Eq(1)); // Close the HTTP/3 connection before sending back response. This would cause an upstream reset. ASSERT_TRUE(fake_upstream_connection_->close()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(2)); fake_upstream_connection_.reset(); upstream_request_.reset(); @@ -528,7 +532,7 @@ TEST_P(FilterIntegrationTest, H3PostHandshakeFailoverToTcp) { EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_rq_retry")->value()); checkSimpleRequestSuccess(2048, response_size, response2.get()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_http2_total", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http2_total", Eq(2)); } INSTANTIATE_TEST_SUITE_P(Protocols, FilterIntegrationTest, diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_integration_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_integration_test.cc index fa2de5db10138..4b2ac615667dc 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_integration_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_integration_test.cc @@ -15,6 +15,7 @@ namespace Common { namespace Aws { namespace { +using testing::Ge; using testing::Return; const std::string AWS_REQUEST_SIGNING_CONFIG_SIGV4 = R"EOF( @@ -534,9 +535,9 @@ TEST_F(InitializeFilterTest, TestWithOneClusterStandard) { initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithOneClusterCustomWebIdentity) { @@ -548,9 +549,9 @@ TEST_F(InitializeFilterTest, TestWithOneClusterCustomWebIdentity) { addCustomCredentialChainFilter(); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithOneClusterStandardUpstream) { @@ -566,9 +567,9 @@ TEST_F(InitializeFilterTest, TestWithOneClusterStandardUpstream) { addUpstreamProtocolOptions(); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithTwoClustersUpstreamCheckForSingletonIMDS) { @@ -592,9 +593,9 @@ TEST_F(InitializeFilterTest, TestWithTwoClustersUpstreamCheckForSingletonIMDS) { initialize(); // We should see a successful credential refresh - test_server_->waitForCounterGe("aws.metadata_credentials_provider.ec2_instance_metadata_server_" - "internal.credential_refreshes_performed", - 1); + test_server_->waitForCounter("aws.metadata_credentials_provider.ec2_instance_metadata_server_" + "internal.credential_refreshes_performed", + Ge(1)); // If credential refresh has succeeded, then check we added only a single cluster via the // extension EXPECT_EQ(test_server_->counter("cluster_manager.cluster_added"), 1); @@ -610,9 +611,9 @@ TEST_F(InitializeFilterTest, TestWithOneClusterRouteLevel) { addPerRouteFilter(AWS_REQUEST_SIGNING_CONFIG_SIGV4_ROUTE_LEVEL); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithOneClusterRouteLevelAndStandard) { @@ -626,9 +627,9 @@ TEST_F(InitializeFilterTest, TestWithOneClusterRouteLevelAndStandard) { addPerRouteFilter(AWS_REQUEST_SIGNING_CONFIG_SIGV4_ROUTE_LEVEL); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithTwoClustersStandard) { @@ -643,13 +644,13 @@ TEST_F(InitializeFilterTest, TestWithTwoClustersStandard) { addStandardFilter(); initialize(); std::vector gauges = test_server_->gauges(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.ecs_task_" - "metadata_server_internal.credential_refreshes_performed", - 1); + test_server_->waitForCounter("aws.metadata_credentials_provider.ecs_task_" + "metadata_server_internal.credential_refreshes_performed", + Ge(1)); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithTwoClustersRouteLevel) { @@ -663,13 +664,13 @@ TEST_F(InitializeFilterTest, TestWithTwoClustersRouteLevel) { TestEnvironment::setEnvVar("AWS_CONTAINER_AUTHORIZATION_TOKEN", "auth_token", 1); addPerRouteFilter(AWS_REQUEST_SIGNING_CONFIG_SIGV4_ROUTE_LEVEL); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.ecs_task_" - "metadata_server_internal.credential_refreshes_performed", - 1); + test_server_->waitForCounter("aws.metadata_credentials_provider.ecs_task_" + "metadata_server_internal.credential_refreshes_performed", + Ge(1)); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithIAMRolesAnywhereCluster) { @@ -684,9 +685,9 @@ TEST_F(InitializeFilterTest, TestWithIAMRolesAnywhereCluster) { addPerRouteFilter(AWS_REQUEST_SIGNING_CONFIG_SIGV4_ROLES_ANYWHERE); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.rolesanywhere_ap-southeast-2_" - "amazonaws_com.credential_refreshes_performed", - 1); + test_server_->waitForCounter("aws.metadata_credentials_provider.rolesanywhere_ap-southeast-2_" + "amazonaws_com.credential_refreshes_performed", + Ge(1)); } TEST_F(InitializeFilterTest, TestWithIAMRolesAnywhereCustom) { @@ -702,9 +703,9 @@ TEST_F(InitializeFilterTest, TestWithIAMRolesAnywhereCustom) { addPerRouteFilter(AWS_REQUEST_SIGNING_CONFIG_SIGV4_ROLES_ANYWHERE_CUSTOM); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.rolesanywhere_ap-southeast-2_" - "amazonaws_com.credential_refreshes_performed", - 1); + test_server_->waitForCounter("aws.metadata_credentials_provider.rolesanywhere_ap-southeast-2_" + "amazonaws_com.credential_refreshes_performed", + Ge(1)); } TEST_F(InitializeFilterTest, TestWithMultipleWebidentityRouteLevel) { @@ -804,21 +805,21 @@ TEST_F(InitializeFilterTest, TestWithMultipleWebidentityRouteLevel) { initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-1.credential_refreshes_performed", - 1, std::chrono::seconds(10)); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-eu-" - "west-1.credential_refreshes_performed", - 1, std::chrono::seconds(10)); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-eu-" - "west-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-eu-" - "west-3.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-1.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-eu-" + "west-1.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-eu-" + "west-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-eu-" + "west-3.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithTwoClustersRouteLevelAndStandard) { @@ -833,13 +834,13 @@ TEST_F(InitializeFilterTest, TestWithTwoClustersRouteLevelAndStandard) { addStandardFilter(); addPerRouteFilter(AWS_REQUEST_SIGNING_CONFIG_SIGV4_ROUTE_LEVEL); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.ecs_task_" - "metadata_server_internal.credential_refreshes_performed", - 1); + test_server_->waitForCounter("aws.metadata_credentials_provider.ecs_task_" + "metadata_server_internal.credential_refreshes_performed", + Ge(1)); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithTwoClustersStandardInstanceProfile) { @@ -850,13 +851,13 @@ TEST_F(InitializeFilterTest, TestWithTwoClustersStandardInstanceProfile) { TestEnvironment::setEnvVar("AWS_ROLE_SESSION_NAME", "role-session-name", 1); addStandardFilter(); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.ec2_instance_" - "metadata_server_internal.credential_refreshes_performed", - 1); + test_server_->waitForCounter("aws.metadata_credentials_provider.ec2_instance_" + "metadata_server_internal.credential_refreshes_performed", + Ge(1)); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithTwoClustersRouteLevelInstanceProfile) { @@ -867,13 +868,13 @@ TEST_F(InitializeFilterTest, TestWithTwoClustersRouteLevelInstanceProfile) { TestEnvironment::setEnvVar("AWS_ROLE_SESSION_NAME", "role-session-name", 1); addPerRouteFilter(AWS_REQUEST_SIGNING_CONFIG_SIGV4_ROUTE_LEVEL); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.ec2_instance_" - "metadata_server_internal.credential_refreshes_performed", - 1); + test_server_->waitForCounter("aws.metadata_credentials_provider.ec2_instance_" + "metadata_server_internal.credential_refreshes_performed", + Ge(1)); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } TEST_F(InitializeFilterTest, TestWithTwoClustersRouteLevelAndStandardInstanceProfile) { @@ -885,13 +886,13 @@ TEST_F(InitializeFilterTest, TestWithTwoClustersRouteLevelAndStandardInstancePro addStandardFilter(); addPerRouteFilter(AWS_REQUEST_SIGNING_CONFIG_SIGV4_ROUTE_LEVEL); initialize(); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.ec2_instance_" - "metadata_server_internal.credential_refreshes_performed", - 1); + test_server_->waitForCounter("aws.metadata_credentials_provider.ec2_instance_" + "metadata_server_internal.credential_refreshes_performed", + Ge(1)); - test_server_->waitForCounterGe("aws.metadata_credentials_provider.sts_token_service_internal-ap-" - "southeast-2.credential_refreshes_performed", - 1, std::chrono::seconds(10)); + test_server_->waitForCounter("aws.metadata_credentials_provider.sts_token_service_internal-ap-" + "southeast-2.credential_refreshes_performed", + Ge(1), std::chrono::seconds(10)); } class CdsInteractionTest : public testing::Test, public HttpIntegrationTest { @@ -986,12 +987,12 @@ TEST_F(CdsInteractionTest, CDSUpdateDoesNotRemoveOurClusters) { cds_helper_.setCds({cluster_}); initialize(); - test_server_->waitForCounterGe("cluster_manager.cluster_added", 2); + test_server_->waitForCounter("cluster_manager.cluster_added", Ge(2)); cluster_.set_name("testing"); cds_helper_.setCds({cluster_}); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 2); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(2)); EXPECT_EQ(1, test_server_->counter("cluster_manager.cluster_removed")->value()); EXPECT_EQ(3, test_server_->counter("cluster_manager.cluster_added")->value()); } diff --git a/test/extensions/filters/http/composite/composite_filter_integration_test.cc b/test/extensions/filters/http/composite/composite_filter_integration_test.cc index 767c7e174375a..6c08387272c8e 100644 --- a/test/extensions/filters/http/composite/composite_filter_integration_test.cc +++ b/test/extensions/filters/http/composite/composite_filter_integration_test.cc @@ -38,6 +38,7 @@ using test::integration::filters::SetResponseCodeFilterConfig; using test::integration::filters::SetResponseCodeFilterConfigDual; using test::integration::filters::SetResponseCodePerRouteFilterConfig; using test::integration::filters::SetResponseCodePerRouteFilterConfigDual; +using testing::Ge; using xds::type::matcher::v3::Matcher_OnMatch; // Returns the complete YAML for a composite filter config, wrapping the given matcher_yaml. @@ -494,14 +495,14 @@ TEST_P(CompositeFilterIntegrationTest, TestBasicDynamicFilter) { prependCompositeDynamicFilter("composite-dynamic"); initialize(); if (downstream_filter_) { - test_server_->waitForCounterGe( - "extension_config_discovery.http_filter.set-response-code.config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.http_filter.set-response-code.config_reload", Ge(1)); } else { - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_http_filter.set-response-code.config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter.set-response-code.config_reload", Ge(1)); } test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -530,14 +531,14 @@ TEST_P(CompositeFilterIntegrationTest, TestBasicDynamicFilterNoSampling) { prependCompositeDynamicFilter("composite-dynamic", "set_response_code.yaml", false); initialize(); if (downstream_filter_) { - test_server_->waitForCounterGe( - "extension_config_discovery.http_filter.set-response-code.config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.http_filter.set-response-code.config_reload", Ge(1)); } else { - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_http_filter.set-response-code.config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter.set-response-code.config_reload", Ge(1)); } test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); codec_client_ = makeHttpConnection(lookupPort("http")); { @@ -570,14 +571,14 @@ TEST_P(CompositeFilterIntegrationTest, TestMissingDynamicFilter) { prependMissingCompositeDynamicFilter("composite-dynamic-missing"); initialize(); if (downstream_filter_) { - test_server_->waitForCounterGe( - "extension_config_discovery.http_filter.missing-config.config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.http_filter.missing-config.config_fail", Ge(1)); } else { - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_http_filter.missing-config.config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter.missing-config.config_fail", Ge(1)); } test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeRequestWithBody(match_request_headers_, 1024); diff --git a/test/extensions/filters/http/compressor/compressor_integration_tests.cc b/test/extensions/filters/http/compressor/compressor_integration_tests.cc index 0434b028351de..34a3ea118643f 100644 --- a/test/extensions/filters/http/compressor/compressor_integration_tests.cc +++ b/test/extensions/filters/http/compressor/compressor_integration_tests.cc @@ -16,6 +16,7 @@ #include "absl/strings/str_cat.h" #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace { @@ -163,8 +164,8 @@ void WebsocketWithCompressorIntegrationTest::performUpgrade( auto encoder_decoder = codec_client_->startRequest(upgrade_request_headers); request_encoder_ = &encoder_decoder.first; response_ = std::move(encoder_decoder.second); - test_server_->waitForCounterGe("http.config_test.downstream_cx_upgrades_total", 1); - test_server_->waitForGaugeGe("http.config_test.downstream_cx_upgrades_active", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_upgrades_total", Ge(1)); + test_server_->waitForGauge("http.config_test.downstream_cx_upgrades_active", Ge(1)); // Verify the upgrade was received upstream. ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); diff --git a/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc b/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc index 8f20be58b0a94..b256cca867879 100644 --- a/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc +++ b/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc @@ -8,6 +8,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { class DecompressorIntegrationTest : public testing::TestWithParam, @@ -111,16 +113,16 @@ TEST_P(DecompressorIntegrationTest, BidirectionalDecompression) { .getStringView()); // Verify stats - test_server_->waitForCounterEq("http.config_test.decompressor.testlib.gzip.request.decompressed", - 1); - test_server_->waitForCounterEq( - "http.config_test.decompressor.testlib.gzip.request.not_decompressed", 0); - test_server_->waitForCounterEq( + test_server_->waitForCounter("http.config_test.decompressor.testlib.gzip.request.decompressed", + Eq(1)); + test_server_->waitForCounter( + "http.config_test.decompressor.testlib.gzip.request.not_decompressed", Eq(0)); + test_server_->waitForCounter( "http.config_test.decompressor.testlib.gzip.request.total_compressed_bytes", - compressed_request_length); - test_server_->waitForCounterEq( + Eq(compressed_request_length)); + test_server_->waitForCounter( "http.config_test.decompressor.testlib.gzip.request.total_uncompressed_bytes", - uncompressed_request_length); + Eq(uncompressed_request_length)); // Enable response decompression by setting the Content-Encoding header to gzip. upstream_request_->encodeHeaders( @@ -162,16 +164,16 @@ TEST_P(DecompressorIntegrationTest, BidirectionalDecompression) { .getStringView()); // Verify stats - test_server_->waitForCounterEq("http.config_test.decompressor.testlib.gzip.response.decompressed", - 1); - test_server_->waitForCounterEq( - "http.config_test.decompressor.testlib.gzip.response.not_decompressed", 0); - test_server_->waitForCounterEq( + test_server_->waitForCounter("http.config_test.decompressor.testlib.gzip.response.decompressed", + Eq(1)); + test_server_->waitForCounter( + "http.config_test.decompressor.testlib.gzip.response.not_decompressed", Eq(0)); + test_server_->waitForCounter( "http.config_test.decompressor.testlib.gzip.response.total_compressed_bytes", - compressed_response_length); - test_server_->waitForCounterEq( + Eq(compressed_response_length)); + test_server_->waitForCounter( "http.config_test.decompressor.testlib.gzip.response.total_uncompressed_bytes", - uncompressed_response_length); + Eq(uncompressed_response_length)); } /** @@ -234,15 +236,15 @@ TEST_P(DecompressorIntegrationTest, BidirectionalDecompressionError) { .getStringView()); // Verify stats. While the stream was decompressed, there should be a decompression failure. - test_server_->waitForCounterEq("http.config_test.decompressor.testlib.gzip.request.decompressed", - 1); - test_server_->waitForCounterEq( - "http.config_test.decompressor.testlib.gzip.request.not_decompressed", 0); - test_server_->waitForCounterEq( + test_server_->waitForCounter("http.config_test.decompressor.testlib.gzip.request.decompressed", + Eq(1)); + test_server_->waitForCounter( + "http.config_test.decompressor.testlib.gzip.request.not_decompressed", Eq(0)); + test_server_->waitForCounter( "http.config_test.decompressor.testlib.gzip.request.total_compressed_bytes", - compressed_request_length); - test_server_->waitForCounterEq( - "http.config_test.decompressor.testlib.gzip.decompressor_library.zlib_data_error", 2); + Eq(compressed_request_length)); + test_server_->waitForCounter( + "http.config_test.decompressor.testlib.gzip.decompressor_library.zlib_data_error", Eq(2)); // Enable response decompression by setting the Content-Encoding header to gzip. upstream_request_->encodeHeaders( @@ -274,15 +276,15 @@ TEST_P(DecompressorIntegrationTest, BidirectionalDecompressionError) { .getStringView()); // Verify stats. While the stream was decompressed, there should be a decompression failure. - test_server_->waitForCounterEq("http.config_test.decompressor.testlib.gzip.response.decompressed", - 1); - test_server_->waitForCounterEq( - "http.config_test.decompressor.testlib.gzip.response.not_decompressed", 0); - test_server_->waitForCounterEq( + test_server_->waitForCounter("http.config_test.decompressor.testlib.gzip.response.decompressed", + Eq(1)); + test_server_->waitForCounter( + "http.config_test.decompressor.testlib.gzip.response.not_decompressed", Eq(0)); + test_server_->waitForCounter( "http.config_test.decompressor.testlib.gzip.response.total_compressed_bytes", - compressed_response_length); - test_server_->waitForCounterGe( - "http.config_test.decompressor.testlib.gzip.decompressor_library.zlib_data_error", 3); + Eq(compressed_response_length)); + test_server_->waitForCounter( + "http.config_test.decompressor.testlib.gzip.decompressor_library.zlib_data_error", Ge(3)); } // Buffer the request after it's been decompressed. @@ -411,17 +413,17 @@ TEST_P(DecompressorIntegrationTest, LimitMaxDecompressOutputSize) { .getStringView()); // Verify stats - test_server_->waitForCounterEq("http.config_test.decompressor.testlib.gzip.request.decompressed", - 1); - test_server_->waitForCounterEq( - "http.config_test.decompressor.testlib.gzip.request.not_decompressed", 0); - test_server_->waitForCounterEq( + test_server_->waitForCounter("http.config_test.decompressor.testlib.gzip.request.decompressed", + Eq(1)); + test_server_->waitForCounter( + "http.config_test.decompressor.testlib.gzip.request.not_decompressed", Eq(0)); + test_server_->waitForCounter( "http.config_test.decompressor.testlib.gzip.request.total_compressed_bytes", - compressed_data_length); - test_server_->waitForCounterEq( - "http.config_test.decompressor.testlib.gzip.request.total_uncompressed_bytes", 4096); - test_server_->waitForCounterGe( - "http.config_test.decompressor.testlib.gzip.decompressor_library.zlib_data_error", 1); + Eq(compressed_data_length)); + test_server_->waitForCounter( + "http.config_test.decompressor.testlib.gzip.request.total_uncompressed_bytes", Eq(4096)); + test_server_->waitForCounter( + "http.config_test.decompressor.testlib.gzip.decompressor_library.zlib_data_error", Ge(1)); } } // namespace Envoy diff --git a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc index acb030adafffb..74af906316250 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -16,6 +16,8 @@ #include "test/test_common/test_runtime.h" #include "test/test_common/threadsafe_singleton_injector.h" +using testing::Eq; +using testing::Ge; using testing::HasSubstr; namespace Envoy { @@ -251,8 +253,8 @@ name: envoy.clusters.dynamic_forward_proxy // CDS cluster is loaded in createUpstreams() to handle late H3 information. HttpIntegrationTest::initialize(); - test_server_->waitForCounterEq("cluster_manager.cluster_added", 1); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(1)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); default_request_headers_.setHost( fmt::format("localhost:{}", fake_upstreams_[0]->localAddress()->ip()->port())); @@ -391,7 +393,7 @@ name: envoy.clusters.dynamic_forward_proxy // Close the upstream connection and wait for it to be detected. ASSERT_TRUE(fake_upstream_connection_->close()); fake_upstream_connection_.reset(); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); IntegrationStreamDecoderPtr response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -707,11 +709,11 @@ TEST_P(ProxyFilterIntegrationTest, ParallelRequestsWithFakeResolver) { // Kick off the first request. auto response1 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); // Wait fo the query to kick off - test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); + test_server_->waitForCounter("dns_cache.foo.dns_query_attempt", Eq(1)); // Start the next request before unblocking the resolve. auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); // Wait for both requests to be received downstream before unblocking DNS. - test_server_->waitForCounterEq("http.config_test.downstream_rq_total", 2); + test_server_->waitForCounter("http.config_test.downstream_rq_total", Eq(2)); Network::TestResolver::unblockResolve(); ASSERT_TRUE(response1->waitForEndStream()); @@ -801,8 +803,8 @@ TEST_P(ProxyFilterIntegrationTest, ReloadClusterAndAttachToCache) { // Cause a cluster reload via CDS. cluster_.mutable_circuit_breakers()->add_thresholds()->mutable_max_connections()->set_value(100); cds_helper_.setCds({cluster_}); - test_server_->waitForCounterEq("cluster_manager.cluster_modified", 1); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForCounter("cluster_manager.cluster_modified", Eq(1)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); // We need to wait until the workers have gotten the new cluster update. The only way we can // know this currently is when the connection pools drain and terminate. @@ -838,7 +840,7 @@ TEST_P(ProxyFilterWithSimtimeIntegrationTest, RemoveHostViaTTL) { // > 5m simTime().advanceTimeWait(std::chrono::milliseconds(300001)); - test_server_->waitForGaugeEq("dns_cache.foo.num_hosts", 0); + test_server_->waitForGauge("dns_cache.foo.num_hosts", Eq(0)); EXPECT_EQ(1, test_server_->counter("dns_cache.foo.host_removed")->value()); } @@ -1076,7 +1078,7 @@ TEST_P(ProxyFilterIntegrationTest, UseCacheFileShortTtl) { EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_cx_http1_total")->value()); // Wait for the host to be removed due to short TTL - test_server_->waitForCounterGe("dns_cache.foo.host_removed", 1); + test_server_->waitForCounter("dns_cache.foo.host_removed", Ge(1)); // Send a request and expect an error due to 1) removed host and 2) DNS resolution fail. response = codec_client_->makeHeaderOnlyRequest(request_headers); @@ -1107,7 +1109,7 @@ TEST_P(ProxyFilterIntegrationTest, StreamPersistAcrossShortTtlResFail) { // When the TTL is hit, the host will be removed from the DNS cache. This // won't break the outstanding connection. - test_server_->waitForCounterGe("dns_cache.foo.host_removed", 1); + test_server_->waitForCounter("dns_cache.foo.host_removed", Ge(1)); // Kick off a new request before the first is served. auto response2 = codec_client_->makeHeaderOnlyRequest(request_headers); @@ -1153,8 +1155,8 @@ TEST_P(ProxyFilterIntegrationTest, StreamPersistAcrossShortTtlResSuccess) { // When the TTL is hit, the host will be removed from the DNS cache. This // won't break the outstanding connection. - // test_server_->waitForCounterGe("dns.cares.resolve_total", 1); - test_server_->waitForCounterGe("dns_cache.foo.dns_query_success", 1); + // test_server_->waitForCounter("dns.cares.resolve_total", Ge(1)); + test_server_->waitForCounter("dns_cache.foo.dns_query_success", Ge(1)); // Kick off a new request before the first is served. auto response2 = codec_client_->makeHeaderOnlyRequest(request_headers); @@ -1189,7 +1191,7 @@ TEST_P(ProxyFilterIntegrationTest, UseCacheFileShortTtlHostActive) { waitForNextUpstreamRequest(); // Wait for the host to be removed due to short TTL - test_server_->waitForCounterGe("dns_cache.foo.host_removed", 1); + test_server_->waitForCounter("dns_cache.foo.host_removed", Ge(1)); // Finish the response. upstream_request_->encodeHeaders(default_response_headers_, true); @@ -1220,7 +1222,7 @@ TEST_P(ProxyFilterIntegrationTest, UseCacheFileAndTestHappyEyeballs) { auto response = codec_client_->makeHeaderOnlyRequest(request_headers); // Wait for the request to be received. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_total", Eq(1)); EXPECT_EQ(2, test_server_->counter("cluster.cluster_0.upstream_cx_total")->value()); EXPECT_TRUE(response->waitForEndStream()); EXPECT_EQ(1, test_server_->counter("dns_cache.foo.cache_load")->value()); @@ -1255,7 +1257,7 @@ TEST_P(ProxyFilterIntegrationTest, UseCacheFileAndHttp3) { auto response = codec_client_->makeHeaderOnlyRequest(request_headers); // Wait for the request to be received. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_total", Eq(1)); EXPECT_TRUE(response->waitForEndStream()); EXPECT_EQ(1, test_server_->counter("dns_cache.foo.cache_load")->value()); EXPECT_EQ(1, test_server_->counter("dns_cache.foo.host_added")->value()); @@ -1290,7 +1292,7 @@ TEST_P(ProxyFilterIntegrationTest, MultipleRequestsLowStreamLimit) { // Start another request. IntegrationStreamDecoderPtr response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); - test_server_->waitForCounterEq("http.config_test.downstream_rq_total", 2); + test_server_->waitForCounter("http.config_test.downstream_rq_total", Eq(2)); // Make sure the stream is not received. ASSERT_FALSE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_, std::chrono::milliseconds(100))); @@ -1309,7 +1311,7 @@ TEST_P(ProxyFilterIntegrationTest, MultipleRequestsLowStreamLimit) { ASSERT_TRUE(response2->waitForEndStream()); EXPECT_TRUE(response2->complete()); EXPECT_EQ("200", response2->headers().getStatusValue()); - test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); + test_server_->waitForCounter("dns_cache.foo.dns_query_attempt", Eq(1)); } TEST_P(ProxyFilterIntegrationTest, MultipleRequestsForceRefreshOff) { @@ -1385,7 +1387,7 @@ TEST_P(ProxyFilterIntegrationTest, TestQueueingBasedOnCircuitBreakers) { IntegrationStreamDecoderPtr response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); // Make sure the stream is received, but no new connection is established. - test_server_->waitForCounterEq("http.config_test.downstream_rq_total", 2); + test_server_->waitForCounter("http.config_test.downstream_rq_total", Eq(2)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.upstream_cx_active")->value()); // Finish the first stream. @@ -1442,19 +1444,19 @@ TEST_P(ProxyFilterIntegrationTest, SubClusterReloadCluster) { sendRequestAndWaitForResponse(request_headers, 1024, default_response_headers_, 1024); checkSimpleRequestSuccess(1024, 1024, response.get()); // one more sub cluster - test_server_->waitForCounterEq("cluster_manager.cluster_added", 2); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForCounterEq("cluster_manager.cluster_modified", 0); - test_server_->waitForCounterEq("cluster_manager.cluster_removed", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(2)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForCounter("cluster_manager.cluster_modified", Eq(0)); + test_server_->waitForCounter("cluster_manager.cluster_removed", Eq(0)); // Cause a cluster reload via CDS. cluster_.mutable_circuit_breakers()->add_thresholds()->mutable_max_connections()->set_value(100); cds_helper_.setCds({cluster_}); // sub cluster is removed - test_server_->waitForCounterEq("cluster_manager.cluster_added", 2); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForCounterEq("cluster_manager.cluster_modified", 1); - test_server_->waitForCounterEq("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(2)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForCounter("cluster_manager.cluster_modified", Eq(1)); + test_server_->waitForCounter("cluster_manager.cluster_removed", Eq(1)); // We need to wait until the workers have gotten the new cluster update. The only way we can // know this currently is when the connection pools drain and terminate. @@ -1465,8 +1467,8 @@ TEST_P(ProxyFilterIntegrationTest, SubClusterReloadCluster) { // Now send another request. This should create a new sub cluster. response = sendRequestAndWaitForResponse(request_headers, 512, default_response_headers_, 512); checkSimpleRequestSuccess(512, 512, response.get()); - test_server_->waitForCounterEq("cluster_manager.cluster_added", 3); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(3)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); } // Verify that we expire sub clusters. @@ -1484,14 +1486,14 @@ TEST_P(ProxyFilterWithSimtimeIntegrationTest, RemoveSubClusterViaTTL) { sendRequestAndWaitForResponse(request_headers, 1024, default_response_headers_, 1024); checkSimpleRequestSuccess(1024, 1024, response.get()); // one more cluster - test_server_->waitForCounterEq("cluster_manager.cluster_added", 2); - test_server_->waitForCounterEq("cluster_manager.cluster_removed", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(2)); + test_server_->waitForCounter("cluster_manager.cluster_removed", Eq(0)); cleanupUpstreamAndDownstream(); // > 5m simTime().advanceTimeWait(std::chrono::milliseconds(300001)); - test_server_->waitForCounterEq("cluster_manager.cluster_added", 2); - test_server_->waitForCounterEq("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(2)); + test_server_->waitForCounter("cluster_manager.cluster_removed", Eq(1)); } // Test sub clusters overflow. @@ -1577,19 +1579,19 @@ TEST_P(ProxyFilterIntegrationTest, CDSReloadNotRemoveDFPCluster) { sendRequestAndWaitForResponse(request_headers, 1024, default_response_headers_, 1024); checkSimpleRequestSuccess(1024, 1024, response.get()); // one more sub cluster - test_server_->waitForCounterEq("cluster_manager.cluster_added", 2); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForCounterEq("cluster_manager.cluster_modified", 0); - test_server_->waitForCounterEq("cluster_manager.cluster_removed", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(2)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForCounter("cluster_manager.cluster_modified", Eq(0)); + test_server_->waitForCounter("cluster_manager.cluster_removed", Eq(0)); // Cause a cluster reload via CDS. cds_helper_.setCds({cluster_, cluster}); // a new cluster is added and no dfp cluster is removed - test_server_->waitForCounterEq("cluster_manager.cluster_added", 3); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForCounterEq("cluster_manager.cluster_modified", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(3)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForCounter("cluster_manager.cluster_modified", Eq(0)); // No DFP cluster should be removed. - test_server_->waitForCounterEq("cluster_manager.cluster_removed", 0); + test_server_->waitForCounter("cluster_manager.cluster_removed", Eq(0)); // The fake upstream connection should stay connected ASSERT_TRUE(fake_upstream_connection_->connected()); @@ -1599,8 +1601,8 @@ TEST_P(ProxyFilterIntegrationTest, CDSReloadNotRemoveDFPCluster) { checkSimpleRequestSuccess(512, 512, response.get()); // No new cluster should be added as DFP cluster already exists - test_server_->waitForCounterEq("cluster_manager.cluster_added", 3); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(3)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); } TEST_P(ProxyFilterIntegrationTest, ResetStreamDuringDnsLookup) { diff --git a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc index 3982766672437..9ec5d4189d244 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc @@ -27,6 +27,8 @@ using test::integration::filters::LoggingTestFilterConfig; using testing::AssertionResult; +using testing::Eq; +using testing::Ge; using testing::Not; using testing::TestWithParam; using testing::ValuesIn; @@ -1368,8 +1370,8 @@ TEST_P(ExtAuthzGrpcIntegrationTest, Retry) { waitForSuccessfulUpstreamResponse("200"); // Verify retry stats are incremented correctly. - test_server_->waitForCounterGe("cluster.ext_authz_cluster.upstream_rq_retry", 1); - test_server_->waitForCounterGe("cluster.ext_authz_cluster.upstream_rq_total", 2); + test_server_->waitForCounter("cluster.ext_authz_cluster.upstream_rq_retry", Ge(1)); + test_server_->waitForCounter("cluster.ext_authz_cluster.upstream_rq_total", Ge(2)); cleanup(); } @@ -1394,7 +1396,7 @@ TEST_P(ExtAuthzGrpcIntegrationTest, ValidateMutations) { ASSERT_TRUE(response_->waitForEndStream()); EXPECT_TRUE(response_->complete()); EXPECT_EQ("500", response_->headers().getStatusValue()); - test_server_->waitForCounterEq("cluster.cluster_0.ext_authz.invalid", 1); + test_server_->waitForCounter("cluster.cluster_0.ext_authz.invalid", Eq(1)); cleanup(); } @@ -1417,7 +1419,7 @@ TEST_P(ExtAuthzGrpcIntegrationTest, ValidateMutationsSentinelAppendAction) { ASSERT_TRUE(response_->waitForEndStream()); EXPECT_TRUE(response_->complete()); EXPECT_EQ("500", response_->headers().getStatusValue()); - test_server_->waitForCounterEq("cluster.cluster_0.ext_authz.invalid", 1); + test_server_->waitForCounter("cluster.cluster_0.ext_authz.invalid", Eq(1)); cleanup(); } diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc index 636e8e163a5f0..4b39128a5a458 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc @@ -24,7 +24,6 @@ namespace ExternalProcessing { using envoy::extensions::filters::http::ext_proc::v3::ProcessingMode; using Envoy::Protobuf::Any; using Envoy::Protobuf::MapPair; - using namespace std::chrono_literals; // ExtProcIntegrationTest:: @@ -489,8 +488,8 @@ void ExtProcIntegrationTest::processRequestBodyMessage( if (check_downstream_flow_control) { // Check the flow control counter in downstream, which is triggered on the request // path to ext_proc server (i.e., from side stream). - test_server_->waitForCounterGe("http.config_test.downstream_flow_control_paused_reading_total", - 1); + test_server_->waitForCounter("http.config_test.downstream_flow_control_paused_reading_total", + testing::Ge(1)); } // Send back the response from ext_proc server. diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index bf8d5969e381c..355c5195cfaa9 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -67,8 +67,8 @@ using Extensions::HttpFilters::ExternalProcessing::TestOnProcessingResponseFacto using Http::LowerCaseString; using test::integration::filters::LoggingTestFilterConfig; using testing::_; +using testing::Ge; using testing::Not; - using namespace std::chrono_literals; INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDeferredProcessing, ExtProcIntegrationTest, @@ -379,7 +379,7 @@ TEST_P(ExtProcIntegrationTest, OnlyRequestHeadersResetOnServerMessage) { EXPECT_TRUE(processor_stream_->waitForReset()); // In case of Envoy gRPC client the cluster reset stat will be incremented if (IsEnvoyGrpc()) { - test_server_->waitForCounterGe("cluster.ext_proc_server_0.upstream_rq_tx_reset", 1); + test_server_->waitForCounter("cluster.ext_proc_server_0.upstream_rq_tx_reset", Ge(1)); } } @@ -4392,8 +4392,8 @@ TEST_P(ExtProcIntegrationTest, RetryStatsVerification) { "success"); // Verify retry stats are incremented correctly. - test_server_->waitForCounterGe("cluster.ext_proc_server_0.upstream_rq_retry", 2); - test_server_->waitForCounterGe("cluster.ext_proc_server_0.upstream_rq_total", 3); + test_server_->waitForCounter("cluster.ext_proc_server_0.upstream_rq_retry", Ge(2)); + test_server_->waitForCounter("cluster.ext_proc_server_0.upstream_rq_total", Ge(3)); verifyDownstreamResponse(*response, 200); } @@ -4455,8 +4455,8 @@ TEST_P(ExtProcIntegrationTest, RetryOnDeadlineExceeded) { "passed"); // Verify retry stats are incremented. - test_server_->waitForCounterGe("cluster.ext_proc_server_0.upstream_rq_retry", 1); - test_server_->waitForCounterGe("cluster.ext_proc_server_0.upstream_rq_total", 2); + test_server_->waitForCounter("cluster.ext_proc_server_0.upstream_rq_retry", Ge(1)); + test_server_->waitForCounter("cluster.ext_proc_server_0.upstream_rq_total", Ge(2)); verifyDownstreamResponse(*response, 200); } @@ -4530,9 +4530,9 @@ TEST_P(ExtProcIntegrationTest, SidestreamPushbackUpstream) { // Large body is sent from sidestream server to downstream client. Thus, flow control is expected // to be triggered in sidestream cluster. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_flow_control_" - "paused_reading_total", - 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_" + "paused_reading_total", + Ge(1)); verifyDownstreamResponse(*response, 200); } @@ -4579,9 +4579,9 @@ TEST_P(ExtProcIntegrationTest, SidestreamPushbackUpstreamObservabilityMode) { // Large body is sent from sidestream server to downstream client. Thus, flow control is expected // to be triggered in sidestream cluster. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_flow_control_" - "paused_reading_total", - 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_" + "paused_reading_total", + Ge(2)); verifyDownstreamResponse(*response, 200); } diff --git a/test/extensions/filters/http/ext_proc/ext_proc_misc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_misc_integration_test.cc index 2a765c6f0b27f..a76c12fbe20c4 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_misc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_misc_integration_test.cc @@ -176,7 +176,7 @@ TEST_P(ExtProcStatusOnErrorIntegrationTest, MessageTimeoutReturnsGatewayTimeout) // Don't send a response to trigger timeout - just wait for the timeout to occur. // Let timeout occur. - test_server_->waitForCounterGe("http.config_test.ext_proc.message_timeouts", 1); + test_server_->waitForCounter("http.config_test.ext_proc.message_timeouts", testing::Ge(1)); // Should return 504 Gateway Timeout instead of configured status_on_error. ASSERT_TRUE(response->waitForEndStream()); 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 3ea5f7efa64fb..0441ffa49d82c 100644 --- a/test/extensions/filters/http/fault/fault_filter_integration_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_integration_test.cc @@ -3,6 +3,7 @@ #include "gtest/gtest.h" +using testing::Eq; namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -150,8 +151,8 @@ TEST_P(FaultIntegrationTestAllProtocols, HeaderFaultConfig) { {"x-envoy-fault-delay-request", "200"}, {"x-envoy-fault-throughput-response", "1"}}; IntegrationStreamDecoderPtr response = codec_client_->makeHeaderOnlyRequest(request_headers); - test_server_->waitForCounterEq("http.config_test.fault.delays_injected", 1, - TestUtility::DefaultTimeout, dispatcher_.get()); + test_server_->waitForCounter("http.config_test.fault.delays_injected", Eq(1), + TestUtility::DefaultTimeout, dispatcher_.get()); simTime().advanceTimeWait(std::chrono::milliseconds(200)); waitForNextUpstreamRequest(); @@ -286,8 +287,8 @@ TEST_P(FaultIntegrationTestAllProtocols, HeaderFaultsConfig100PercentageHeaders) {"x-envoy-fault-delay-request-percentage", "100"}, {"x-envoy-fault-throughput-response", "100"}, {"x-envoy-fault-throughput-response-percentage", "100"}}); - test_server_->waitForCounterEq("http.config_test.fault.delays_injected", 1, - TestUtility::DefaultTimeout, dispatcher_.get()); + test_server_->waitForCounter("http.config_test.fault.delays_injected", Eq(1), + TestUtility::DefaultTimeout, dispatcher_.get()); simTime().advanceTimeWait(std::chrono::milliseconds(100)); waitForNextUpstreamRequest(); upstream_request_->encodeHeaders(default_response_headers_, true); diff --git a/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc b/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc index 8a6b8d9ddf931..fb3d4e39643cc 100644 --- a/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc +++ b/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc @@ -6,6 +6,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Eq; namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -237,7 +238,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoDataDontPopulatedWhenCalledFromLocalhosNoX ASSERT_TRUE(response->headers().get(Http::LowerCaseString("x-geo-anon")).empty()); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.asn_db.total")->value()); EXPECT_EQ(nullptr, test_server_->counter("http.config_test.maxmind.city_db.hit")); @@ -258,7 +259,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoAnonDataPopulatedUseXff) { EXPECT_EQ("true", headerValue("x-geo-anon-vpn")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.anon_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.anon_db.hit")->value()); } @@ -279,7 +280,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoDataPopulatedUseXff) { EXPECT_EQ("209", headerValue("x-geo-asn")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.hit")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.asn_db.total")->value()); @@ -304,7 +305,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoDataPopulatedUseXffWithIspAndAsn) { EXPECT_EQ("false", headerValue("x-geo-apple-private-relay")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.hit")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.total")->value()); @@ -331,7 +332,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoDataPopulatedUseXffWithIsp) { EXPECT_EQ("false", headerValue("x-geo-apple-private-relay")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.hit")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.total")->value()); @@ -359,7 +360,7 @@ TEST_P(GeoipFilterIntegrationTest, AsnDbTakesPrecedenceOverIspDbForAsnOrg) { EXPECT_EQ("Bredband2 AB", headerValue("x-geo-asn-org")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.asn_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.asn_db.hit")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.total")->value()); @@ -379,7 +380,7 @@ TEST_P(GeoipFilterIntegrationTest, AsnOrgFallsBackToIspDbWhenAsnDbNotConfigured) EXPECT_EQ("Telstra Internet", headerValue("x-geo-asn-org")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(nullptr, test_server_->counter("http.config_test.maxmind.asn_db.total")); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.hit")->value()); @@ -400,7 +401,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoHeadersOverridenInRequest) { EXPECT_EQ("Milton", headerValue("x-geo-city")); EXPECT_EQ("US", headerValue("x-geo-country")); ASSERT_TRUE(response->complete()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.hit")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.asn_db.total")->value()); @@ -420,7 +421,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoDataNotPopulatedOnEmptyLookupResult) { // 10.10.10.10 is a private IP and is absent in test_data/GeoIP2-Anonymous-IP-Test.mmdb database. ASSERT_TRUE(response->headers().get(Http::LowerCaseString("x-geo-anon")).empty()); ASSERT_TRUE(response->headers().get(Http::LowerCaseString("x-geo-anon-vpn")).empty()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.anon_db.total")->value()); EXPECT_EQ(nullptr, test_server_->counter("http.config_test.maxmind.anon_db.hit")); } @@ -438,9 +439,9 @@ TEST_P(GeoipFilterIntegrationTest, GeoipFilterNoCrashOnLdsUpdate) { listener->mutable_listener_filters_timeout()->set_seconds(10); }); new_config_helper.setLds("1"); - test_server_->waitForGaugeEq("listener_manager.total_listeners_active", 1); - test_server_->waitForCounterEq("listener_manager.lds.update_success", 2); - test_server_->waitForGaugeEq("listener_manager.total_listeners_draining", 0); + test_server_->waitForGauge("listener_manager.total_listeners_active", Eq(1)); + test_server_->waitForCounter("listener_manager.lds.update_success", Eq(2)); + test_server_->waitForGauge("listener_manager.total_listeners_draining", Eq(0)); } codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, @@ -457,7 +458,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoipFilterNoCrashOnLdsUpdate) { auto response2 = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 2); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(2)); EXPECT_EQ(2, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); EXPECT_EQ(2, test_server_->counter("http.config_test.maxmind.city_db.hit")->value()); } @@ -478,7 +479,7 @@ TEST_P(GeoipFilterIntegrationTest, OnlyApplePrivateRelayHeaderIsPopulated) { EXPECT_EQ("false", headerValue("x-geo-apple-private-relay")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.isp_db.hit")->value()); } @@ -503,7 +504,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoDataPopulatedUseCountryDb) { EXPECT_EQ("US", headerValue("x-geo-country")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.country_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.country_db.hit")->value()); } @@ -522,7 +523,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoDataPopulatedUseCountryDbAndCityDb) { EXPECT_EQ("Milton", headerValue("x-geo-city")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); // Country should be looked up from Country DB. EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.country_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.country_db.hit")->value()); @@ -547,7 +548,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoDataPopulatedUseIpAddressHeader) { EXPECT_EQ("209", headerValue("x-geo-asn")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.hit")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.asn_db.total")->value()); @@ -569,7 +570,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoDataNotPopulatedWhenIpAddressHeaderMissing ASSERT_TRUE(response->headers().get(Http::LowerCaseString("x-geo-asn")).empty()); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); } TEST_P(GeoipFilterIntegrationTest, GeoDataNotPopulatedWhenIpAddressHeaderInvalid) { @@ -594,7 +595,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoDataNotPopulatedWhenIpAddressHeaderInvalid ASSERT_TRUE(response->headers().get(Http::LowerCaseString("x-geo-asn")).empty()); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); }); } @@ -636,7 +637,7 @@ TEST_P(GeoipFilterIntegrationTest, EXPECT_EQ("209", headerValue("x-geo-asn")); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.geoip.total", 1); + test_server_->waitForCounter("http.config_test.geoip.total", Eq(1)); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.total")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.city_db.hit")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.maxmind.asn_db.total")->value()); diff --git a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc index 2c0be8ee2ba06..44a21cfcd4563 100644 --- a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc @@ -666,7 +666,7 @@ TEST_P(RemoteJwksIntegrationTest, WithGoodTokenAsyncFetchFast) { // the first request will trigger a second jwks fetch, this is not expected, test will fail. // To avoid such race condition, before making the first request, wait for the first // fetch stats to be updated. - test_server_->waitForCounterGe("http.config_test.jwt_authn.jwks_fetch_success", 1); + test_server_->waitForCounter("http.config_test.jwt_authn.jwks_fetch_success", testing::Ge(1)); codec_client_ = makeHttpConnection(lookupPort("http")); diff --git a/test/extensions/filters/http/local_ratelimit/local_ratelimit_integration_test.cc b/test/extensions/filters/http/local_ratelimit/local_ratelimit_integration_test.cc index 2fccaa2dd628b..ab676a1c2dfec 100644 --- a/test/extensions/filters/http/local_ratelimit/local_ratelimit_integration_test.cc +++ b/test/extensions/filters/http/local_ratelimit/local_ratelimit_integration_test.cc @@ -8,6 +8,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -536,7 +538,7 @@ TEST_P(LocalRateLimitFilterIntegrationTest, ShadowModeTest) { sendAndVerifyRequest("foo", "200", 0); cleanupUpstreamAndDownstream(); - test_server_->waitForCounterEq("http_local_rate_limiter.http_local_rate_limit.shadow_mode", 1); + test_server_->waitForCounter("http_local_rate_limiter.http_local_rate_limit.shadow_mode", Eq(1)); EXPECT_EQ( 1, test_server_->counter("http_local_rate_limiter.http_local_rate_limit.shadow_mode")->value()); @@ -551,7 +553,7 @@ TEST_P(LocalRateLimitFilterIntegrationTest, ShadowModeTest) { sendAndVerifyRequest("bar", "200", 0); cleanupUpstreamAndDownstream(); - test_server_->waitForCounterEq("http_local_rate_limiter.http_local_rate_limit.shadow_mode", 2); + test_server_->waitForCounter("http_local_rate_limiter.http_local_rate_limit.shadow_mode", Eq(2)); EXPECT_EQ( 2, test_server_->counter("http_local_rate_limiter.http_local_rate_limit.shadow_mode")->value()); @@ -714,7 +716,7 @@ TEST_P(LocalRateLimitFilterIntegrationTest, BasicTestPerRouteAndRds) { Config::TestTypeUrl::get().RouteConfiguration, {TestUtility::parseYaml(update_route_config_)}, "2"); - test_server_->waitForCounterGe("http.config_test.rds.basic_routes.update_success", 2); + test_server_->waitForCounter("http.config_test.rds.basic_routes.update_success", Ge(2)); ASSERT_TRUE(response->waitForEndStream()); EXPECT_TRUE(upstream_request_->complete()); @@ -755,7 +757,7 @@ TEST_P(LocalRateLimitFilterIntegrationTest, TestLocalClusterRateLimit) { ASSERT(share_provider_manager != nullptr); auto share_provider = share_provider_manager->getShareProvider({}); - test_server_->waitForGaugeEq("cluster.local_cluster.membership_total", 1); + test_server_->waitForGauge("cluster.local_cluster.membership_total", Eq(1)); simTime().advanceTimeWait(std::chrono::milliseconds(1)); EXPECT_EQ(1.0, share_provider->getTokensShareFactor()); @@ -766,7 +768,7 @@ TEST_P(LocalRateLimitFilterIntegrationTest, TestLocalClusterRateLimit) { update_local_cluster_endpoints_)}, "2"); - test_server_->waitForGaugeEq("cluster.local_cluster.membership_total", 2); + test_server_->waitForGauge("cluster.local_cluster.membership_total", Eq(2)); simTime().advanceTimeWait(std::chrono::milliseconds(1)); EXPECT_EQ(0.5, share_provider->getTokensShareFactor()); diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index a66c0aefe476d..8951fdf5b4f98 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -12,6 +12,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -1412,7 +1414,7 @@ TEST_P(LuaIntegrationTest, RdsTestOfLuaPerRoute) { Config::TestTypeUrl::get().RouteConfiguration, {TestUtility::parseYaml(UPDATE_ROUTE_CONFIG)}, "2"); - test_server_->waitForCounterGe("http.config_test.rds.basic_lua_routes.update_success", 2); + test_server_->waitForCounter("http.config_test.rds.basic_lua_routes.update_success", Ge(2)); check_request(hello_headers, "inline_code_from_hello"); check_request(inline_headers, "new_inline_code_from_inline"); @@ -2713,14 +2715,14 @@ name: lua EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("http.config_test.lua.config1.executions", 2); - test_server_->waitForCounterEq("http.config_test.lua.config1.errors", 0); - test_server_->waitForCounterEq("http.config_test.lua.config2.executions", 2); - test_server_->waitForCounterEq("http.config_test.lua.config2.errors", 0); - test_server_->waitForCounterEq("http.config_test.lua.config3.executions", 1); - test_server_->waitForCounterEq("http.config_test.lua.config3.errors", 1); - test_server_->waitForCounterEq("http.config_test.lua.config4.executions", 0); - test_server_->waitForCounterEq("http.config_test.lua.config4.errors", 0); + test_server_->waitForCounter("http.config_test.lua.config1.executions", Eq(2)); + test_server_->waitForCounter("http.config_test.lua.config1.errors", Eq(0)); + test_server_->waitForCounter("http.config_test.lua.config2.executions", Eq(2)); + test_server_->waitForCounter("http.config_test.lua.config2.errors", Eq(0)); + test_server_->waitForCounter("http.config_test.lua.config3.executions", Eq(1)); + test_server_->waitForCounter("http.config_test.lua.config3.errors", Eq(1)); + test_server_->waitForCounter("http.config_test.lua.config4.executions", Eq(0)); + test_server_->waitForCounter("http.config_test.lua.config4.errors", Eq(0)); cleanup(); } @@ -2782,15 +2784,15 @@ name: lua EXPECT_EQ("200", response->headers().getStatusValue()); // Verify the counter was incremented correctly (inc + add(2) + inc = 4). - test_server_->waitForCounterEq("http.config_test.lua.stats_test.requests", 4); + test_server_->waitForCounter("http.config_test.lua.stats_test.requests", Eq(4)); // Verify the gauge value (set(10) + inc - dec - sub(5) = 5). - test_server_->waitForGaugeEq("http.config_test.lua.stats_test.active_requests", 5); + test_server_->waitForGauge("http.config_test.lua.stats_test.active_requests", Eq(5)); // Verify histogram exists (we can't easily check recorded values in integration tests, // but we can verify the histogram was created by checking it appears in stats). - test_server_->waitForCounterEq("http.config_test.lua.stats_test.executions", 2); - test_server_->waitForCounterEq("http.config_test.lua.stats_test.errors", 0); + test_server_->waitForCounter("http.config_test.lua.stats_test.executions", Eq(2)); + test_server_->waitForCounter("http.config_test.lua.stats_test.errors", Eq(0)); cleanup(); } diff --git a/test/extensions/filters/http/mcp_router/mcp_router_integration_test.cc b/test/extensions/filters/http/mcp_router/mcp_router_integration_test.cc index b4572f15af8f2..3d59864f021e4 100644 --- a/test/extensions/filters/http/mcp_router/mcp_router_integration_test.cc +++ b/test/extensions/filters/http/mcp_router/mcp_router_integration_test.cc @@ -18,6 +18,7 @@ namespace { // TODO(yanavlasov): remove this parameterization once server config is removed from the filter. enum class ConfigSource { Filter, Cluster }; using TestParams = std::tuple; +using testing::Eq; class McpRouterIntegrationTest : public testing::TestWithParam, public HttpIntegrationTest { @@ -199,8 +200,8 @@ TEST_P(McpRouterIntegrationTest, PingReturnsEmptyResult) { EXPECT_THAT(response->body(), testing::HasSubstr("\"result\":{}")); // Verify stats: ping is a direct response - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_total", 1); - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_direct_response", 1); + test_server_->waitForCounter("http.config_test.mcp_router.rq_total", Eq(1)); + test_server_->waitForCounter("http.config_test.mcp_router.rq_direct_response", Eq(1)); } // Test notifications/initialized request returns 202 Accepted @@ -229,9 +230,9 @@ TEST_P(McpRouterIntegrationTest, NotificationInitializedReturns202) { EXPECT_EQ("202", response->headers().getStatusValue()); // Verify stats: notification is a direct response with fanout - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_total", 1); - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_direct_response", 1); - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_fanout", 1); + test_server_->waitForCounter("http.config_test.mcp_router.rq_total", Eq(1)); + test_server_->waitForCounter("http.config_test.mcp_router.rq_direct_response", Eq(1)); + test_server_->waitForCounter("http.config_test.mcp_router.rq_fanout", Eq(1)); } // Test invalid JSON returns 400 @@ -359,8 +360,8 @@ TEST_P(McpRouterIntegrationTest, InitializeFanoutToBothBackends) { EXPECT_THAT(response->body(), testing::HasSubstr("envoy-mcp-gateway")); // Verify stats: initialize is a fanout operation - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_total", 1); - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_fanout", 1); + test_server_->waitForCounter("http.config_test.mcp_router.rq_total", Eq(1)); + test_server_->waitForCounter("http.config_test.mcp_router.rq_fanout", Eq(1)); } // Test tools/list request fans out to both backends and aggregates tools with prefixes @@ -489,8 +490,8 @@ TEST_P(McpRouterIntegrationTest, ToolCallRoutesToCorrectBackend) { EXPECT_THAT(response->body(), testing::HasSubstr("2023-10-27T10:00:00Z")); // Verify stats: tool call with body rewrite - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_total", 1); - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_body_rewrite", 1); + test_server_->waitForCounter("http.config_test.mcp_router.rq_total", Eq(1)); + test_server_->waitForCounter("http.config_test.mcp_router.rq_body_rewrite", Eq(1)); } // Test tools/call routes to the second backend (tools) based on prefix @@ -920,8 +921,8 @@ TEST_P(McpRouterIntegrationTest, ToolCallWithUnknownBackendReturns400) { EXPECT_EQ("400", response->headers().getStatusValue()); // Verify stats: unknown backend - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_total", 1); - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_unknown_backend", 1); + test_server_->waitForCounter("http.config_test.mcp_router.rq_total", Eq(1)); + test_server_->waitForCounter("http.config_test.mcp_router.rq_unknown_backend", Eq(1)); } // Test tools/call with SSE response from backend returns SSE to client @@ -2322,8 +2323,8 @@ TEST_P(McpRouterSubjectValidationIntegrationTest, SubjectMismatchReturns403) { EXPECT_EQ("403", response->headers().getStatusValue()); // Verify stats: auth failure (subject mismatch) - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_total", 1); - test_server_->waitForCounterEq("http.config_test.mcp_router.rq_auth_failure", 1); + test_server_->waitForCounter("http.config_test.mcp_router.rq_total", Eq(1)); + test_server_->waitForCounter("http.config_test.mcp_router.rq_auth_failure", Eq(1)); } // Missing auth header returns 403 diff --git a/test/extensions/filters/http/oauth2/oauth_integration_test.cc b/test/extensions/filters/http/oauth2/oauth_integration_test.cc index 21dadfa687a29..5edcb7feca5fd 100644 --- a/test/extensions/filters/http/oauth2/oauth_integration_test.cc +++ b/test/extensions/filters/http/oauth2/oauth_integration_test.cc @@ -10,6 +10,8 @@ #include "absl/strings/escaping.h" #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -576,10 +578,10 @@ TEST_P(OauthIntegrationTest, AuthenticationFlow) { EXPECT_EQ(test_server_->counter("sds.hmac.update_success")->value(), 1); TestEnvironment::renameFile(TestEnvironment::temporaryPath("token_secret_1.yaml"), TestEnvironment::temporaryPath("token_secret.yaml")); - test_server_->waitForCounterEq("sds.token.update_success", 2, std::chrono::milliseconds(5000)); + test_server_->waitForCounter("sds.token.update_success", Eq(2), std::chrono::milliseconds(5000)); TestEnvironment::renameFile(TestEnvironment::temporaryPath("hmac_secret_1.yaml"), TestEnvironment::temporaryPath("hmac_secret.yaml")); - test_server_->waitForCounterEq("sds.hmac.update_success", 2, std::chrono::milliseconds(5000)); + test_server_->waitForCounter("sds.hmac.update_success", Eq(2), std::chrono::milliseconds(5000)); // 3. Do another one authentication flow. doAuthenticationFlow("token_secret_1", "hmac_secret_1", TEST_STATE_CSRF_TOKEN_1, TEST_ENCODED_STATE_1, TEST_ENCRYPTED_CODE_VERIFIER_1); @@ -601,10 +603,10 @@ TEST_P(OauthIntegrationTest, RefreshTokenFlow) { EXPECT_EQ(test_server_->counter("sds.hmac.update_success")->value(), 1); TestEnvironment::renameFile(TestEnvironment::temporaryPath("token_secret_1.yaml"), TestEnvironment::temporaryPath("token_secret.yaml")); - test_server_->waitForCounterEq("sds.token.update_success", 2, std::chrono::milliseconds(5000)); + test_server_->waitForCounter("sds.token.update_success", Eq(2), std::chrono::milliseconds(5000)); TestEnvironment::renameFile(TestEnvironment::temporaryPath("hmac_secret_1.yaml"), TestEnvironment::temporaryPath("hmac_secret.yaml")); - test_server_->waitForCounterEq("sds.hmac.update_success", 2, std::chrono::milliseconds(5000)); + test_server_->waitForCounter("sds.hmac.update_success", Eq(2), std::chrono::milliseconds(5000)); // 3. Do another one refresh token flow. doRefreshTokenFlow("token_secret_1", "hmac_secret_1"); } @@ -677,8 +679,8 @@ TEST_P(OauthIntegrationTest, LoadListenerAfterServerIsInitialized) { // add listener with oauth2 filter and sds configs sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "delayed"); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 2); - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", 0); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(2)); + test_server_->waitForGauge("listener_manager.total_listeners_warming", Eq(0)); doAuthenticationFlow("token_secret", "hmac_secret", TEST_STATE_CSRF_TOKEN, TEST_ENCODED_STATE, TEST_ENCRYPTED_CODE_VERIFIER); @@ -830,10 +832,10 @@ TEST_P(OauthUseRefreshTokenDisabled, FailRefreshTokenFlow) { EXPECT_EQ(test_server_->counter("sds.hmac.update_success")->value(), 1); TestEnvironment::renameFile(TestEnvironment::temporaryPath("token_secret_1.yaml"), TestEnvironment::temporaryPath("token_secret.yaml")); - test_server_->waitForCounterEq("sds.token.update_success", 2, std::chrono::milliseconds(5000)); + test_server_->waitForCounter("sds.token.update_success", Eq(2), std::chrono::milliseconds(5000)); TestEnvironment::renameFile(TestEnvironment::temporaryPath("hmac_secret_1.yaml"), TestEnvironment::temporaryPath("hmac_secret.yaml")); - test_server_->waitForCounterEq("sds.hmac.update_success", 2, std::chrono::milliseconds(5000)); + test_server_->waitForCounter("sds.hmac.update_success", Eq(2), std::chrono::milliseconds(5000)); // 3. Do one refresh token flow. This should fail. doRefreshTokenFlow("token_secret_1", "hmac_secret_1", /* expect_failure */ true); } @@ -896,7 +898,7 @@ TEST_P(OauthIntegrationTest, HmacChangeCausesReauth) { EXPECT_EQ(test_server_->counter("sds.hmac.update_success")->value(), 1); TestEnvironment::renameFile(TestEnvironment::temporaryPath("hmac_secret_1.yaml"), TestEnvironment::temporaryPath("hmac_secret.yaml")); - test_server_->waitForCounterEq("sds.hmac.update_success", 2, std::chrono::milliseconds(5000)); + test_server_->waitForCounter("sds.hmac.update_success", Eq(2), std::chrono::milliseconds(5000)); // 3. Make another request with the saved cookies after HMAC secret was changed codec_client_ = makeHttpConnection(lookupPort("http")); diff --git a/test/extensions/filters/http/on_demand/odcds_integration_test.cc b/test/extensions/filters/http/on_demand/odcds_integration_test.cc index 3849c15339e1b..66960f3a99dd1 100644 --- a/test/extensions/filters/http/on_demand/odcds_integration_test.cc +++ b/test/extensions/filters/http/on_demand/odcds_integration_test.cc @@ -297,6 +297,7 @@ class OdCdsIntegrationTestBase : public HttpIntegrationTest, }; using OdCdsIntegrationTest = OdCdsIntegrationTestBase; +using testing::Ge; INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, OdCdsIntegrationTest, GRPC_CLIENT_INTEGRATION_PARAMS); @@ -2042,7 +2043,7 @@ TEST_P(OdCdsXdstpAdsIntegrationTest, OnDemandClusterDiscoveryWithSotwAds) { EXPECT_EQ(5, test_server_->gauge("cluster_manager.active_clusters")->value()); // Envoy should now complete initialization. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); registerTestServerPorts({"http"}); // Send the first request. @@ -2157,7 +2158,7 @@ on_demand: true {"Addr", "x-foo-key=foo"}}); createRdsStream("foo_route1"); sendRdsResponse(route_config_formatter("foo_route1"), "1"); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_success", Ge(1)); return response; } diff --git a/test/extensions/filters/http/on_demand/on_demand_integration_test.cc b/test/extensions/filters/http/on_demand/on_demand_integration_test.cc index 0d64a54873d55..382324785b814 100644 --- a/test/extensions/filters/http/on_demand/on_demand_integration_test.cc +++ b/test/extensions/filters/http/on_demand/on_demand_integration_test.cc @@ -24,6 +24,7 @@ namespace Envoy { namespace { using OnDemandScopedRdsIntegrationTest = ScopedRdsIntegrationTest; +using testing::Ge; INSTANTIATE_TEST_SUITE_P(IpVersionsAndGrpcTypes, OnDemandScopedRdsIntegrationTest, DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS); @@ -67,7 +68,7 @@ on_demand: true {"Addr", "x-foo-key=foo"}}); createRdsStream("foo_route1"); sendRdsResponse(fmt::format(route_config_tmpl, "foo_route1", "cluster_0"), "1"); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_success", Ge(1)); waitForNextUpstreamRequest(); // Send response headers, and end_stream if there is no response body. @@ -276,7 +277,7 @@ on_demand: true initialize(); registerTestServerPorts({"http"}); codec_client_ = makeHttpConnection(lookupPort("http")); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_success", Ge(1)); cleanupUpstreamAndDownstream(); // "foo" request should succeed because the foo scope is loaded eagerly by default. // "bar" request will initialize rds provider on demand and also succeed. @@ -327,7 +328,7 @@ on_demand: true {":authority", "sni.lyft.com"}, {":scheme", "http"}, {"Addr", "x-foo-key=foo"}}); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_attempt", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_attempt", Ge(1)); // Close the connection and destroy the active stream. cleanupUpstreamAndDownstream(); // Push rds update, on demand updated callback is post to worker thread. @@ -335,7 +336,7 @@ on_demand: true // locked. createRdsStream("foo_route1"); sendRdsResponse(fmt::format(route_config_tmpl, "foo_route1", "cluster_0"), "1"); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_success", Ge(1)); } class OnDemandVhdsIntegrationTest : public VhdsIntegrationTest { diff --git a/test/extensions/filters/http/proto_api_scrubber/integration_test.cc b/test/extensions/filters/http/proto_api_scrubber/integration_test.cc index 0ce6516c01f95..016ab577a2511 100644 --- a/test/extensions/filters/http/proto_api_scrubber/integration_test.cc +++ b/test/extensions/filters/http/proto_api_scrubber/integration_test.cc @@ -31,6 +31,7 @@ namespace scrubber_test = test::extensions::filters::http::proto_api_scrubber; using envoy::extensions::filters::http::proto_api_scrubber::v3::ProtoApiScrubberConfig; using envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter; using ::Envoy::Extensions::HttpFilters::GrpcFieldExtraction::checkSerializedData; +using testing::Ge; std::string apikeysDescriptorPath() { return TestEnvironment::runfilesPath("test/proto/apikeys.descriptor"); @@ -772,7 +773,7 @@ TEST_P(ProtoApiScrubberIntegrationTest, UnaryRequestPassesThrough) { ASSERT_TRUE(response->waitForEndStream()); // Verify Stats - test_server_->waitForCounterGe("proto_api_scrubber.total_requests_checked", 1); + test_server_->waitForCounter("proto_api_scrubber.total_requests_checked", Ge(1)); } // Tests that the streaming request passes through without modification if there are no restrictions @@ -1042,7 +1043,7 @@ TEST_P(ProtoApiScrubberIntegrationTest, RejectsMethodNotInDescriptor) { EXPECT_EQ("3", grpc_status->value().getStringView()); // 3 = Invalid Argument // Verify Stats: Scrubbing failed because method not found implies no type info found. - test_server_->waitForCounterGe("proto_api_scrubber.request_scrubbing_failed", 1); + test_server_->waitForCounter("proto_api_scrubber.request_scrubbing_failed", Ge(1)); } // Tests that the request is rejected if the gRPC `:path` header is in invalid format. @@ -1066,7 +1067,7 @@ TEST_P(ProtoApiScrubberIntegrationTest, RejectsInvalidPathFormat) { EXPECT_EQ("3", grpc_status->value().getStringView()); // 3 = Invalid Argument // Verify Stats for invalid method name. - test_server_->waitForCounterGe("proto_api_scrubber.invalid_method_name", 1); + test_server_->waitForCounter("proto_api_scrubber.invalid_method_name", Ge(1)); } // Tests that the request is rejected if a method-level block rule matches. @@ -1116,7 +1117,7 @@ TEST_P(ProtoApiScrubberIntegrationTest, RejectsBlockedMethod) { EXPECT_EQ("5", grpc_status->value().getStringView()); // 5 = Not Found. // Verify Stats for blocked method. - test_server_->waitForCounterGe("proto_api_scrubber.method_blocked", 1); + test_server_->waitForCounter("proto_api_scrubber.method_blocked", Ge(1)); } } // namespace diff --git a/test/extensions/filters/http/rate_limit_quota/filter_persistence_test.cc b/test/extensions/filters/http/rate_limit_quota/filter_persistence_test.cc index e71f0e199f66e..b0282701509c0 100644 --- a/test/extensions/filters/http/rate_limit_quota/filter_persistence_test.cc +++ b/test/extensions/filters/http/rate_limit_quota/filter_persistence_test.cc @@ -38,6 +38,7 @@ using envoy::service::rate_limit_quota::v3::BucketId; using envoy::service::rate_limit_quota::v3::RateLimitQuotaResponse; using envoy::service::rate_limit_quota::v3::RateLimitQuotaUsageReports; using Protobuf::util::MessageDifferencer; +using testing::Eq; MATCHER_P2(ProtoEqIgnoringFieldAndOrdering, expected, /* const FieldDescriptor* */ ignored_field, "") { @@ -162,19 +163,19 @@ class FilterPersistenceTest : public Event::TestUsingSimulatedTime, void updateConfigInPlace(std::function modifier) { // update_success starts at 1 after the initial server configuration. - test_server_->waitForCounterEq("listener_manager.lds.update_success", config_updates_ + 1); - test_server_->waitForCounterEq("listener_manager.listener_modified", config_updates_); - test_server_->waitForCounterEq("listener_manager.listener_in_place_updated", config_updates_); + test_server_->waitForCounter("listener_manager.lds.update_success", Eq(config_updates_ + 1)); + test_server_->waitForCounter("listener_manager.listener_modified", Eq(config_updates_)); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Eq(config_updates_)); ConfigHelper new_config_helper(version_, config_helper_.bootstrap()); new_config_helper.addConfigModifier(modifier); new_config_helper.setLds(absl::StrCat(config_updates_ + 1)); - test_server_->waitForCounterEq("listener_manager.lds.update_success", config_updates_ + 2); - test_server_->waitForCounterEq("listener_manager.listener_modified", config_updates_ + 1); - test_server_->waitForCounterEq("listener_manager.listener_in_place_updated", - config_updates_ + 1); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 0); + test_server_->waitForCounter("listener_manager.lds.update_success", Eq(config_updates_ + 2)); + test_server_->waitForCounter("listener_manager.listener_modified", Eq(config_updates_ + 1)); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", + Eq(config_updates_ + 1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(0)); config_updates_++; } diff --git a/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc index ac3b72ff86b49..f8058dddcbf3d 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc @@ -22,6 +22,7 @@ #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace { @@ -363,8 +364,8 @@ TEST_P(RatelimitIntegrationTest, Timeout) { waitForRatelimitRequest(); switch (clientType()) { case Grpc::ClientType::EnvoyGrpc: - test_server_->waitForCounterGe("cluster.ratelimit_cluster.upstream_rq_timeout", 1); - test_server_->waitForCounterGe("cluster.ratelimit_cluster.upstream_rq_504", 1); + test_server_->waitForCounter("cluster.ratelimit_cluster.upstream_rq_timeout", Ge(1)); + test_server_->waitForCounter("cluster.ratelimit_cluster.upstream_rq_504", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.ratelimit_cluster.upstream_rq_timeout")->value()); EXPECT_EQ(1, test_server_->counter("cluster.ratelimit_cluster.upstream_rq_504")->value()); break; diff --git a/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc b/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc index cfd238e0a822b..5f8e788bea782 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc @@ -594,6 +594,7 @@ name: rbac )EOF"; using RBACIntegrationTest = HttpProtocolIntegrationTest; +using testing::Eq; // TODO(#26236): Fix test suite for HTTP/3. INSTANTIATE_TEST_SUITE_P( @@ -1728,8 +1729,8 @@ name: envoy.clusters.dynamic_forward_proxy // Load the CDS cluster and wait for it to initialize. cds_helper_.setCds({cluster_}); HttpIntegrationTest::initialize(); - test_server_->waitForCounterEq("cluster_manager.cluster_added", 1); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(1)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); } CdsHelper cds_helper_; diff --git a/test/extensions/filters/http/tap/tap_filter_integration_test.cc b/test/extensions/filters/http/tap/tap_filter_integration_test.cc index d809ba2a05d62..bc75871acfadc 100644 --- a/test/extensions/filters/http/tap/tap_filter_integration_test.cc +++ b/test/extensions/filters/http/tap/tap_filter_integration_test.cc @@ -11,6 +11,11 @@ namespace Envoy { namespace { +using ::testing::AnyOf; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::StrEq; + class TapIntegrationTest : public testing::TestWithParam, public HttpIntegrationTest { public: @@ -140,7 +145,7 @@ class TapIntegrationTest : public testing::TestWithParamclose(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); // Find the written .pb file and verify it. auto files = TestUtility::listFiles(getTempPathPrefix(), false); @@ -312,7 +317,7 @@ config_id: test_config_id // Setup a tap and disconnect it without any request/response. startAdminRequest(admin_request_yaml); admin_client_->close(); - test_server_->waitForGaugeEq("http.admin.downstream_rq_active", 0); + test_server_->waitForGauge("http.admin.downstream_rq_active", Eq(0)); // Second request/response with no tap. makeRequest(request_headers_tap_, {}, nullptr, response_headers_no_tap_, {}, nullptr); @@ -352,7 +357,7 @@ config_id: test_config_id EXPECT_EQ("baz", findHeader("bar", trace.http_buffered_trace().response().headers())->value()); admin_client_->close(); - test_server_->waitForGaugeEq("http.admin.downstream_rq_active", 0); + test_server_->waitForGauge("http.admin.downstream_rq_active", Eq(0)); // Now setup a tap that matches on logical AND. const std::string admin_request_yaml2 = @@ -394,7 +399,7 @@ config_id: test_config_id admin_client_->close(); EXPECT_EQ(current_tapped + 3UL, test_server_->counter("http.config_test.tap.rq_tapped")->value()); - test_server_->waitForGaugeEq("http.admin.downstream_rq_active", 0); + test_server_->waitForGauge("http.admin.downstream_rq_active", Eq(0)); } // Make sure that an admin tap works correctly across an LDS reload. @@ -429,7 +434,7 @@ config_id: test_config_id new_config_helper.prependFilter(admin_filter_config_); new_config_helper.renameListener("foo"); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); registerTestServerPorts({"http"}); codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); @@ -1060,7 +1065,7 @@ name: tap makeRequest(request_headers_tap_, {"hello"}, &request_trailers_, response_headers_no_tap_, {"world"}, &response_trailers_); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); std::vector traces = Extensions::Common::Tap::readTracesFromPath(path_prefix); @@ -1107,7 +1112,7 @@ name: tap makeRequest(request_headers_no_tap_, {"hello"}, &request_trailers_, response_headers_tap_, {"world"}, &response_trailers_); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); std::vector traces = Extensions::Common::Tap::readTracesFromPath(path_prefix); @@ -1154,7 +1159,7 @@ name: tap makeRequest(request_headers_tap_, {"hello"}, &request_trailers_, response_headers_no_tap_, {"world"}, &response_trailers_); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); std::vector traces = Extensions::Common::Tap::readTracesFromPath(path_prefix); @@ -1196,7 +1201,7 @@ name: tap makeRequest(request_headers_tap_, {"hello"}, &request_trailers_, response_headers_no_tap_, {"world"}, &response_trailers_); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); std::vector traces = Extensions::Common::Tap::readTracesFromPath(path_prefix); @@ -1238,15 +1243,13 @@ name: tap makeRequest(request_headers_tap_, {"hello"}, &request_trailers_, response_headers_no_tap_, {"world"}, &response_trailers_); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); std::vector traces = Extensions::Common::Tap::readTracesFromPath(path_prefix); ASSERT_EQ(1, traces.size()); EXPECT_TRUE(traces[0].has_http_buffered_trace()); EXPECT_TRUE(traces[0].http_buffered_trace().has_upstream_connection()); - using ::testing::AnyOf; - using ::testing::StrEq; std::string upstream_local_address = traces[0] .http_buffered_trace() .upstream_connection() diff --git a/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc b/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc index 1a60deee663c3..6701fbdcbef54 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_integration_test.cc @@ -24,6 +24,8 @@ namespace Extensions { namespace Wasm { namespace { +using testing::Ge; + class WasmFilterIntegrationTest : public HttpIntegrationTest, public testing::TestWithParam> { @@ -467,8 +469,8 @@ name: envoy.filters.http.wasm // Wait until the DATA frame (1024 payload + 9-byte frame header) has been // received by the server's HTTP/2 codec. - test_server_->waitForCounterGe("http.config_test.downstream_cx_rx_bytes_total", - bytes_before_data + 1033); + test_server_->waitForCounter("http.config_test.downstream_cx_rx_bytes_total", + Ge(bytes_before_data + 1033)); // Grab the worker-thread dispatcher and callbacks. Event::Dispatcher* conn_dispatcher; @@ -526,7 +528,7 @@ name: envoy.filters.http.wasm test_server_->counter("http.config_test.downstream_rq_total")->value(); codec_client_ = makeHttpConnection(lookupPort("http")); auto follow_up_response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); - test_server_->waitForCounterGe("http.config_test.downstream_rq_total", initial_rq_total + 1); + test_server_->waitForCounter("http.config_test.downstream_rq_total", Ge(initial_rq_total + 1)); // The reload check (when the bug is present) runs on the worker thread as // part of the Wasm filter's per-request setup; downstream_rq_total can tick // before that work has finished. Post a fence to the same worker dispatcher diff --git a/test/extensions/filters/listener/http_inspector/http_inspector_integration_test.cc b/test/extensions/filters/listener/http_inspector/http_inspector_integration_test.cc index a9dd0c9c134a8..a9429c43487fc 100644 --- a/test/extensions/filters/listener/http_inspector/http_inspector_integration_test.cc +++ b/test/extensions/filters/listener/http_inspector/http_inspector_integration_test.cc @@ -13,6 +13,7 @@ #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace { @@ -50,7 +51,7 @@ TEST_P(HttpInspectorTcpIntegrationTest, DetectNoHttp) { ASSERT_TRUE(fake_upstream_connection->close()); tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - test_server_->waitForCounterGe("http_inspector.http_not_found", 1); + test_server_->waitForCounter("http_inspector.http_not_found", Ge(1)); } TEST_P(HttpInspectorTcpIntegrationTest, DetectHttp) { @@ -65,7 +66,7 @@ TEST_P(HttpInspectorTcpIntegrationTest, DetectHttp) { ASSERT_TRUE(fake_upstream_connection->close()); tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - test_server_->waitForCounterGe("http_inspector.http11_found", 1); + test_server_->waitForCounter("http_inspector.http11_found", Ge(1)); } // Tests that the inspector makes a decision when CRLF is read. @@ -81,7 +82,7 @@ TEST_P(HttpInspectorTcpIntegrationTest, DetectCRLF) { ASSERT_TRUE(fake_upstream_connection->close()); tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - test_server_->waitForCounterGe("http_inspector.http_not_found", 1); + test_server_->waitForCounter("http_inspector.http_not_found", Ge(1)); } } // namespace Envoy diff --git a/test/extensions/filters/listener/local_ratelimit/local_ratelimit_integration_test.cc b/test/extensions/filters/listener/local_ratelimit/local_ratelimit_integration_test.cc index 64a6e026c1b8c..4f7191906dcbb 100644 --- a/test/extensions/filters/listener/local_ratelimit/local_ratelimit_integration_test.cc +++ b/test/extensions/filters/listener/local_ratelimit/local_ratelimit_integration_test.cc @@ -81,7 +81,8 @@ stat_prefix: local_rate_limit_stats tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); tcp_client->waitForDisconnect(); - test_server_->waitForCounterGe("listener_local_ratelimit.local_rate_limit_stats.rate_limited", 1); + test_server_->waitForCounter("listener_local_ratelimit.local_rate_limit_stats.rate_limited", + testing::Ge(1)); } } // namespace Envoy diff --git a/test/extensions/filters/listener/original_dst/original_dst_integration_test.cc b/test/extensions/filters/listener/original_dst/original_dst_integration_test.cc index 9169dad2fbfc2..3b800de86da55 100644 --- a/test/extensions/filters/listener/original_dst/original_dst_integration_test.cc +++ b/test/extensions/filters/listener/original_dst/original_dst_integration_test.cc @@ -141,7 +141,7 @@ TEST_P(OriginalDstTcpProxyIntegrationTest, TestManyConnections) { ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); } - test_server_->waitForCounterGe("cluster_manager.cluster_updated", kMaxConnections); + test_server_->waitForCounter("cluster_manager.cluster_updated", testing::Ge(kMaxConnections)); } INSTANTIATE_TEST_SUITE_P(IpVersions, OriginalDstTcpProxyIntegrationTest, diff --git a/test/extensions/filters/network/connection_limit/connection_limit_integration_test.cc b/test/extensions/filters/network/connection_limit/connection_limit_integration_test.cc index ea589aa50fb97..7748d3c31c9ed 100644 --- a/test/extensions/filters/network/connection_limit/connection_limit_integration_test.cc +++ b/test/extensions/filters/network/connection_limit/connection_limit_integration_test.cc @@ -1,5 +1,6 @@ #include "test/integration/integration.h" +using testing::Eq; namespace Envoy { namespace { @@ -46,8 +47,8 @@ name: connectionlimit tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - test_server_->waitForGaugeEq("connection_limit.connection_limit_stats.active_connections", 0, - std::chrono::milliseconds(100)); + test_server_->waitForGauge("connection_limit.connection_limit_stats.active_connections", Eq(0), + std::chrono::milliseconds(100)); EXPECT_EQ(0, test_server_->counter("connection_limit.connection_limit_stats.limited_connections") ->value()); @@ -72,14 +73,14 @@ name: connectionlimit ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection1) || fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection2)); - test_server_->waitForGaugeEq("connection_limit.connection_limit_stats.active_connections", 1, - std::chrono::milliseconds(200)); + test_server_->waitForGauge("connection_limit.connection_limit_stats.active_connections", Eq(1), + std::chrono::milliseconds(200)); tcp_client1->close(); tcp_client2->close(); - test_server_->waitForGaugeEq("connection_limit.connection_limit_stats.active_connections", 0, - std::chrono::milliseconds(100)); + test_server_->waitForGauge("connection_limit.connection_limit_stats.active_connections", Eq(0), + std::chrono::milliseconds(100)); EXPECT_EQ(1, test_server_->counter("connection_limit.connection_limit_stats.limited_connections") ->value()); diff --git a/test/extensions/filters/network/ext_authz/ext_authz_integration_test.cc b/test/extensions/filters/network/ext_authz/ext_authz_integration_test.cc index c9979b69f36cb..4472d22fb6a53 100644 --- a/test/extensions/filters/network/ext_authz/ext_authz_integration_test.cc +++ b/test/extensions/filters/network/ext_authz/ext_authz_integration_test.cc @@ -20,6 +20,7 @@ namespace Envoy { namespace { +using testing::Ge; using testing::HasSubstr; class ExtAuthzNetworkIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, @@ -177,8 +178,8 @@ BORINGSSL_TEST_P(ExtAuthzNetworkIntegrationTest, DenialWithTlsAlertEnabled) { sendExtAuthzResponse(Grpc::Status::WellKnownGrpcStatus::PermissionDenied); - test_server_->waitForCounterGe("ext_authz.ext_authz.denied", 1); - test_server_->waitForCounterGe("ext_authz.ext_authz.cx_closed", 1); + test_server_->waitForCounter("ext_authz.ext_authz.denied", Ge(1)); + test_server_->waitForCounter("ext_authz.ext_authz.cx_closed", Ge(1)); // Wait for the connection to close and ensure all events are processed. while (!connect_callbacks_.closed()) { @@ -244,8 +245,8 @@ TEST_P(ExtAuthzNetworkIntegrationTest, DenialWithTlsAlertDisabled) { sendExtAuthzResponse(Grpc::Status::WellKnownGrpcStatus::PermissionDenied); - test_server_->waitForCounterGe("ext_authz.ext_authz.denied", 1); - test_server_->waitForCounterGe("ext_authz.ext_authz.cx_closed", 1); + test_server_->waitForCounter("ext_authz.ext_authz.denied", Ge(1)); + test_server_->waitForCounter("ext_authz.ext_authz.cx_closed", Ge(1)); while (!connect_callbacks_.closed()) { dispatcher_->run(Event::Dispatcher::RunType::NonBlock); @@ -303,7 +304,7 @@ TEST_P(ExtAuthzNetworkIntegrationTest, AllowedConnection) { sendExtAuthzResponse(Grpc::Status::WellKnownGrpcStatus::Ok); - test_server_->waitForCounterGe("ext_authz.ext_authz.ok", 1); + test_server_->waitForCounter("ext_authz.ext_authz.ok", Ge(1)); FakeRawConnectionPtr fake_upstream_connection; result = fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection); @@ -353,8 +354,8 @@ TEST_P(ExtAuthzNetworkIntegrationTest, DenialWithoutTls) { sendExtAuthzResponse(Grpc::Status::WellKnownGrpcStatus::PermissionDenied); // Wait for denial to be processed. - test_server_->waitForCounterGe("ext_authz.ext_authz.denied", 1); - test_server_->waitForCounterGe("ext_authz.ext_authz.cx_closed", 1); + test_server_->waitForCounter("ext_authz.ext_authz.denied", Ge(1)); + test_server_->waitForCounter("ext_authz.ext_authz.cx_closed", Ge(1)); // For non-TLS connections, ext_authz closes immediately without sending an alert. // Close the client connection to clean up the test. diff --git a/test/extensions/filters/network/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/network/ext_proc/ext_proc_integration_test.cc index 5e49d11b86a5f..b0ebe4924f06e 100644 --- a/test/extensions/filters/network/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/network/ext_proc/ext_proc_integration_test.cc @@ -19,6 +19,7 @@ namespace ExtProc { using envoy::service::network_ext_proc::v3::ProcessingRequest; using envoy::service::network_ext_proc::v3::ProcessingResponse; +using testing::Ge; // Test-only filter that sets both typed and untyped connection metadata based on filter config class MetadataSetterFilter : public Network::ReadFilter { @@ -338,7 +339,7 @@ class NetworkExtProcFilterIntegrationTest void verifyCounters(const std::map& expected_counters) { for (const auto& [name, value] : expected_counters) { - test_server_->waitForCounterGe("network_ext_proc.ext_proc_prefix." + name, value); + test_server_->waitForCounter("network_ext_proc.ext_proc_prefix." + name, Ge(value)); } } @@ -670,7 +671,7 @@ TEST_P(NetworkExtProcFilterIntegrationTest, GrpcStreamFailure) { // Close gRPC stream with an error status instead of Ok // This should trigger onGrpcError instead of onGrpcClose closeGrpcStream(); - test_server_->waitForCounterGe("network_ext_proc.ext_proc_prefix.streams_grpc_error", 1); + test_server_->waitForCounter("network_ext_proc.ext_proc_prefix.streams_grpc_error", Ge(1)); ASSERT_FALSE(tcp_client->write("", true)); tcp_client->waitForDisconnect(); @@ -699,7 +700,7 @@ TEST_P(NetworkExtProcFilterIntegrationTest, GrpcStreamFailureWithFailureModeAllo closeGrpcStream(); // Wait for the failure_mode_allowed counter to increment - test_server_->waitForCounterGe("network_ext_proc.ext_proc_prefix.failure_mode_allowed", 1); + test_server_->waitForCounter("network_ext_proc.ext_proc_prefix.failure_mode_allowed", Ge(1)); // Should be able to continue using the connection after stream failure ASSERT_TRUE(tcp_client->write("more_data", true)); diff --git a/test/extensions/filters/network/geoip/geoip_filter_integration_test.cc b/test/extensions/filters/network/geoip/geoip_filter_integration_test.cc index 874a2460c7654..709d4aa560fe4 100644 --- a/test/extensions/filters/network/geoip/geoip_filter_integration_test.cc +++ b/test/extensions/filters/network/geoip/geoip_filter_integration_test.cc @@ -7,6 +7,7 @@ #include "gtest/gtest.h" +using testing::Eq; namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -63,7 +64,7 @@ TEST_P(GeoipFilterIntegrationTest, GeoipFilterProcessesConnection) { ASSERT_TRUE(tcp_client->connected()); // Verify stats were incremented indicating the filter processed the connection. - test_server_->waitForCounterEq("geoip.total", 1); + test_server_->waitForCounter("geoip.total", Eq(1)); tcp_client->close(); } @@ -83,20 +84,20 @@ TEST_P(GeoipFilterIntegrationTest, GeoipFilterNoCrashOnLdsUpdate) { listener->mutable_listener_filters_timeout()->set_seconds(10); }); new_config_helper.setLds("1"); - test_server_->waitForGaugeEq("listener_manager.total_listeners_active", 1); - test_server_->waitForCounterEq("listener_manager.lds.update_success", 2); - test_server_->waitForGaugeEq("listener_manager.total_listeners_draining", 0); + test_server_->waitForGauge("listener_manager.total_listeners_active", Eq(1)); + test_server_->waitForCounter("listener_manager.lds.update_success", Eq(2)); + test_server_->waitForGauge("listener_manager.total_listeners_draining", Eq(0)); } // Connection after LDS update to verify filter still works and no crash occurs. IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp")); ASSERT_TRUE(tcp_client->connected()); - test_server_->waitForCounterEq("geoip.total", 1); + test_server_->waitForCounter("geoip.total", Eq(1)); // Second connection to verify continued operation. IntegrationTcpClientPtr tcp_client2 = makeTcpConnection(lookupPort("tcp")); ASSERT_TRUE(tcp_client2->connected()); - test_server_->waitForCounterEq("geoip.total", 2); + test_server_->waitForCounter("geoip.total", Eq(2)); tcp_client->close(); tcp_client2->close(); @@ -149,7 +150,7 @@ name: envoy.filters.network.geoip ASSERT_TRUE(tcp_client->connected()); // Wait for geoip lookup to complete before closing connection. - test_server_->waitForCounterEq("geoip.total", 1); + test_server_->waitForCounter("geoip.total", Eq(1)); tcp_client->close(); test_server_.reset(); @@ -193,7 +194,7 @@ name: envoy.filters.network.geoip ASSERT_TRUE(tcp_client->connected()); // Wait for geoip lookup to complete before closing connection. - test_server_->waitForCounterEq("geoip.total", 1); + test_server_->waitForCounter("geoip.total", Eq(1)); tcp_client->close(); test_server_.reset(); @@ -237,7 +238,7 @@ name: envoy.filters.network.geoip // Verify stats were incremented indicating the filter processed the connection. // The filter should fall back to connection remote address when formatter returns empty. - test_server_->waitForCounterEq("geoip.total", 1); + test_server_->waitForCounter("geoip.total", Eq(1)); tcp_client->close(); } @@ -283,7 +284,7 @@ name: envoy.filters.network.geoip IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp")); ASSERT_TRUE(tcp_client->connected()); - test_server_->waitForCounterEq("geoip.total", 1); + test_server_->waitForCounter("geoip.total", Eq(1)); tcp_client->close(); test_server_.reset(); @@ -335,7 +336,7 @@ name: envoy.filters.network.geoip IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp")); ASSERT_TRUE(tcp_client->connected()); - test_server_->waitForCounterEq("geoip.total", 1); + test_server_->waitForCounter("geoip.total", Eq(1)); tcp_client->close(); test_server_.reset(); diff --git a/test/extensions/filters/network/rbac/integration_test.cc b/test/extensions/filters/network/rbac/integration_test.cc index 5af83ebaa0c4b..e54e9cae546b5 100644 --- a/test/extensions/filters/network/rbac/integration_test.cc +++ b/test/extensions/filters/network/rbac/integration_test.cc @@ -10,6 +10,7 @@ #include "fmt/printf.h" +using testing::Ge; namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -110,10 +111,10 @@ name: rbac ASSERT_TRUE(tcp_client->connected()); tcp_client->close(); - test_server_->waitForCounterGe("tcp.rbac.allowed", 1); + test_server_->waitForCounter("tcp.rbac.allowed", Ge(1)); EXPECT_EQ(0U, test_server_->counter("tcp.rbac.denied")->value()); EXPECT_EQ(0U, test_server_->counter("tcp.rbac.shadow_allowed")->value()); - test_server_->waitForCounterGe("tcp.rbac.shadow_denied", 1); + test_server_->waitForCounter("tcp.rbac.shadow_denied", Ge(1)); } TEST_P(RoleBasedAccessControlNetworkFilterIntegrationTest, Denied) { @@ -233,10 +234,10 @@ name: rbac ASSERT_TRUE(tcp_client->connected()); tcp_client->close(); - test_server_->waitForCounterGe("tcp.rbac.allowed", 1); + test_server_->waitForCounter("tcp.rbac.allowed", Ge(1)); EXPECT_EQ(0U, test_server_->counter("tcp.rbac.denied")->value()); EXPECT_EQ(0U, test_server_->counter("tcp.rbac.shadow_allowed")->value()); - test_server_->waitForCounterGe("tcp.rbac.shadow_denied", 1); + test_server_->waitForCounter("tcp.rbac.shadow_denied", Ge(1)); } TEST_P(RoleBasedAccessControlNetworkFilterIntegrationTest, MatcherDenied) { diff --git a/test/extensions/filters/network/reverse_tunnel/integration_test.cc b/test/extensions/filters/network/reverse_tunnel/integration_test.cc index 5f9074eef9d96..8c05fbb8b1515 100644 --- a/test/extensions/filters/network/reverse_tunnel/integration_test.cc +++ b/test/extensions/filters/network/reverse_tunnel/integration_test.cc @@ -17,6 +17,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -414,17 +416,17 @@ void ReverseTunnelFilterIntegrationTest::runEndToEndReverseConnectionHandshakeSc ENVOY_LOG_MISC(info, "Waiting for reverse connections to be established."); timeSystem().advanceTimeWait(std::chrono::milliseconds(1000)); - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.nodes.e2e-node", 1); - test_server_->waitForGaugeGe("reverse_tunnel_acceptor.clusters.e2e-cluster", 1); + test_server_->waitForGauge("reverse_tunnel_acceptor.nodes.e2e-node", Ge(1)); + test_server_->waitForGauge("reverse_tunnel_acceptor.clusters.e2e-cluster", Ge(1)); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); BufferingStreamDecoderPtr admin_response = IntegrationUtil::makeSingleRequest( lookupPort("admin"), "POST", "/drain_listeners", "", Http::CodecType::HTTP1, GetParam()); EXPECT_TRUE(admin_response->complete()); EXPECT_EQ("200", admin_response->headers().getStatusValue()); - test_server_->waitForCounterEq("listener_manager.listener_stopped", 2); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(2)); } INSTANTIATE_TEST_SUITE_P(IpVersions, ReverseTunnelFilterIntegrationTest, @@ -611,7 +613,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, BasicReverseTunnelHandshake) { tcp_client->waitForData("HTTP/1.1 200 OK"); // Verify stats show successful reverse tunnel handshake. - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); // Send a second request to test socket caching for different node IDs. IntegrationTcpClientPtr tcp_client2 = makeTcpConnection(lookupPort("listener_0")); @@ -622,7 +624,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, BasicReverseTunnelHandshake) { tcp_client2->waitForData("HTTP/1.1 200 OK"); // Verify additional handshake was processed. - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 2); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(2)); tcp_client->close(); tcp_client2->close(); @@ -699,7 +701,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, DrainingAwareHcmSendsGoAwayOnReverseC // Advance simulated time past the 1s drain_time so the graceful drain completion timer fires. timeSystem().advanceTimeWait(std::chrono::seconds(2)); // Confirm the full chain completed: workers stopped the listener and called. - test_server_->waitForCounterGe("listener_manager.listener_stopped", 1); + test_server_->waitForCounter("listener_manager.listener_stopped", Ge(1)); } // Test validation with static expected values. @@ -722,7 +724,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationWithStaticValuesSuccess) { tcp_client->waitForData("HTTP/1.1 200 OK"); tcp_client->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); } // Test validation with static expected values. @@ -744,7 +746,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationWithStaticValuesFailure) { tcp_client->waitForData("HTTP/1.1 403 Forbidden"); tcp_client->waitForDisconnect(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.validation_failed", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.validation_failed", Ge(1)); } // Test validation with only node_id validation. @@ -765,7 +767,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationOnlyNodeId) { tcp_client1->waitForData("HTTP/1.1 200 OK"); tcp_client1->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); // Failure: node_id doesn't match. std::string http_request_fail = createHttpRequestWithRtHeaders( @@ -776,7 +778,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationOnlyNodeId) { tcp_client2->waitForData("HTTP/1.1 403 Forbidden"); tcp_client2->waitForDisconnect(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.validation_failed", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.validation_failed", Ge(1)); } // Test validation with only cluster_id validation. @@ -797,7 +799,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationOnlyClusterId) { tcp_client1->waitForData("HTTP/1.1 200 OK"); tcp_client1->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); // Failure: cluster_id doesn't match. std::string http_request_fail = createHttpRequestWithRtHeaders( @@ -808,7 +810,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationOnlyClusterId) { tcp_client2->waitForData("HTTP/1.1 403 Forbidden"); tcp_client2->waitForDisconnect(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.validation_failed", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.validation_failed", Ge(1)); } // Test validation with only tenant_id validation. @@ -829,7 +831,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationOnlyTenantId) { tcp_client1->waitForData("HTTP/1.1 200 OK"); tcp_client1->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); // Failure: tenant_id doesn't match. std::string http_request_fail = createHttpRequestWithRtHeaders( @@ -840,7 +842,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationOnlyTenantId) { tcp_client2->waitForData("HTTP/1.1 403 Forbidden"); tcp_client2->waitForDisconnect(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.validation_failed", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.validation_failed", Ge(1)); } // Test validation with empty format strings. In this case validation is skipped. @@ -863,7 +865,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationWithEmptyFormatters) { tcp_client->waitForData("HTTP/1.1 200 OK"); tcp_client->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); } // Test validation with dynamic metadata emission. @@ -886,7 +888,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationWithDynamicMetadataEmission tcp_client->waitForData("HTTP/1.1 200 OK"); tcp_client->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); } // Test validation with multiple formatters in format string. @@ -910,7 +912,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationWithComplexFormatString) { // Ensure the validation_failed counter is updated. test_server_->waitForCounterExists("reverse_tunnel.handshake.validation_failed"); - test_server_->waitForCounterGe("reverse_tunnel.handshake.validation_failed", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.validation_failed", Ge(1)); } // Test validation passes when formatter returns empty and actual value is empty. @@ -931,7 +933,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationWithBothValuesMatching) { tcp_client->waitForData("HTTP/1.1 200 OK"); tcp_client->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); } // Test validation with FILTER_STATE formatter. @@ -990,7 +992,7 @@ name: envoy.filters.network.set_filter_state tcp_client->waitForData("HTTP/1.1 200 OK"); tcp_client->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); } // Test validation with FILTER_STATE formatter. @@ -1048,7 +1050,7 @@ name: envoy.filters.network.set_filter_state tcp_client->waitForData("HTTP/1.1 403 Forbidden"); tcp_client->waitForDisconnect(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.validation_failed", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.validation_failed", Ge(1)); } // Helper network filter to set dynamic metadata for testing. @@ -1179,7 +1181,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationWithDynamicMetadataSuccess) tcp_client->waitForData("HTTP/1.1 200 OK"); tcp_client->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); } // Test validation with DYNAMIC_METADATA formatter. @@ -1230,7 +1232,7 @@ TEST_P(ReverseTunnelFilterIntegrationTest, ValidationWithDynamicMetadataFailure) tcp_client->waitForData("HTTP/1.1 403 Forbidden"); tcp_client->waitForDisconnect(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.validation_failed", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.validation_failed", Ge(1)); } // Test validation with mixed FILTER_STATE and DYNAMIC_METADATA formatters. @@ -1301,7 +1303,7 @@ name: envoy.filters.network.set_filter_state tcp_client->waitForData("HTTP/1.1 200 OK"); tcp_client->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); } // Test validation with mixed formatters. @@ -1372,7 +1374,7 @@ name: envoy.filters.network.set_filter_state tcp_client->waitForData("HTTP/1.1 403 Forbidden"); tcp_client->waitForDisconnect(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.validation_failed", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.validation_failed", Ge(1)); } // Test validation with mixed formatters. @@ -1443,7 +1445,7 @@ name: envoy.filters.network.set_filter_state tcp_client->waitForData("HTTP/1.1 403 Forbidden"); tcp_client->waitForDisconnect(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.validation_failed", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.validation_failed", Ge(1)); } // Test end-to-end tenant isolation flow. @@ -1483,7 +1485,7 @@ cleanup_interval: 1s initialize(); test_server_->waitUntilListenersReady(); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); std::string http_request = createHttpRequestWithRtHeaders("GET", "/reverse_connections/request", "node1", "cluster1", "tenant1"); @@ -1493,7 +1495,7 @@ cleanup_interval: 1s tcp_client->waitForData("HTTP/1.1 200 OK"); tcp_client->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 1); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(1)); } // Test multiple tenants are isolated correctly. @@ -1533,7 +1535,7 @@ cleanup_interval: 1s initialize(); test_server_->waitUntilListenersReady(); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); std::string http_request_tenant_a = createHttpRequestWithRtHeaders( "GET", "/reverse_connections/request", "node-a", "cluster-a", "tenant-a"); @@ -1551,7 +1553,7 @@ cleanup_interval: 1s tcp_client_b->waitForData("HTTP/1.1 200 OK"); tcp_client_b->close(); - test_server_->waitForCounterGe("reverse_tunnel.handshake.accepted", 2); + test_server_->waitForCounter("reverse_tunnel.handshake.accepted", Ge(2)); } // Test startup validation fails when tenant isolation enabled but tenant_id_format missing. diff --git a/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_integration_test.cc index 45ca8b15e3bc0..165b0742e947a 100644 --- a/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -10,6 +10,8 @@ #include "test/integration/http_integration.h" #include "test/integration/ssl_utility.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -90,8 +92,8 @@ name: envoy.clusters.dynamic_forward_proxy // Load the CDS cluster and wait for it to initialize. cds_helper_.setCds({cluster_}); HttpIntegrationTest::initialize(); - test_server_->waitForCounterEq("cluster_manager.cluster_added", 1); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(1)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); } void createUpstreams() override { @@ -222,8 +224,8 @@ TEST_P(SniDynamicProxyFilterIntegrationTest, DnsCacheQueryFailureStatistics) { ASSERT_FALSE(codec_client_->connected()); // Verify DNS failure statistics. - test_server_->waitForCounterGe("dns_cache.foo.dns_query_attempt", 1); - test_server_->waitForCounterGe("dns_cache.foo.dns_query_failure", 1); + test_server_->waitForCounter("dns_cache.foo.dns_query_attempt", Ge(1)); + test_server_->waitForCounter("dns_cache.foo.dns_query_failure", Ge(1)); EXPECT_EQ(1, test_server_->counter("dns_cache.foo.dns_query_attempt")->value()); EXPECT_EQ(1, test_server_->counter("dns_cache.foo.dns_query_failure")->value()); } @@ -294,8 +296,8 @@ name: envoy.clusters.dynamic_forward_proxy config_helper_.addListenerFilter(ConfigHelper::tlsInspectorFilter()); cds_helper_.setCds({cluster_}); HttpIntegrationTest::initialize(); - test_server_->waitForCounterEq("cluster_manager.cluster_added", 1); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(1)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); // Initial state. It should have no timeouts yet. EXPECT_EQ(0, test_server_->counter("dns_cache.foo.dns_query_timeout")->value()); @@ -307,7 +309,7 @@ name: envoy.clusters.dynamic_forward_proxy ASSERT_FALSE(codec_client_->connected()); // Verify DNS timeout statistics. - test_server_->waitForCounterGe("dns_cache.foo.dns_query_attempt", 1); + test_server_->waitForCounter("dns_cache.foo.dns_query_attempt", Ge(1)); // Note: timeout detection can be flaky in test environment, so we check attempts were made. EXPECT_GE(test_server_->counter("dns_cache.foo.dns_query_attempt")->value(), 1); } diff --git a/test/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit_integration_test.cc b/test/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit_integration_test.cc index 9a24b641f559a..17744d1bcfa79 100644 --- a/test/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit_integration_test.cc +++ b/test/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit_integration_test.cc @@ -1,5 +1,7 @@ #include "test/integration/integration.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -85,11 +87,11 @@ name: envoy.filters.network.tcp_bandwidth_limit const std::string large_data(2048, 'a'); ASSERT_TRUE(tcp_client->write(large_data)); - test_server_->waitForCounterGe("tcp_bw.tcp_bandwidth_limit.read_throttled", 1); - test_server_->waitForGaugeEq("tcp_bw.tcp_bandwidth_limit.read_bytes_buffered", 1024); + test_server_->waitForCounter("tcp_bw.tcp_bandwidth_limit.read_throttled", Ge(1)); + test_server_->waitForGauge("tcp_bw.tcp_bandwidth_limit.read_bytes_buffered", Eq(1024)); timeSystem().advanceTimeWait(std::chrono::milliseconds(100)); - test_server_->waitForGaugeEq("tcp_bw.tcp_bandwidth_limit.read_bytes_buffered", 0); + test_server_->waitForGauge("tcp_bw.tcp_bandwidth_limit.read_bytes_buffered", Eq(0)); tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); @@ -111,8 +113,8 @@ name: envoy.filters.network.tcp_bandwidth_limit const std::string large_data(2048, 'b'); ASSERT_TRUE(fake_upstream_connection->write(large_data)); - test_server_->waitForCounterGe("tcp_bw.tcp_bandwidth_limit.write_throttled", 1); - test_server_->waitForGaugeEq("tcp_bw.tcp_bandwidth_limit.write_bytes_buffered", 1024); + test_server_->waitForCounter("tcp_bw.tcp_bandwidth_limit.write_throttled", Ge(1)); + test_server_->waitForGauge("tcp_bw.tcp_bandwidth_limit.write_bytes_buffered", Eq(1024)); tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); diff --git a/test/extensions/filters/network/thrift_proxy/integration_test.cc b/test/extensions/filters/network/thrift_proxy/integration_test.cc index f5cb90cd80b16..08e17de797f64 100644 --- a/test/extensions/filters/network/thrift_proxy/integration_test.cc +++ b/test/extensions/filters/network/thrift_proxy/integration_test.cc @@ -10,6 +10,7 @@ #include "gtest/gtest.h" using testing::Combine; +using testing::Ge; using testing::HasSubstr; using ::testing::TestParamInfo; using testing::Values; @@ -353,7 +354,7 @@ TEST_P(ThriftConnManagerIntegrationTest, EarlyClose) { FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(expected_upstream->waitForRawConnection(fake_upstream_connection)); - test_server_->waitForCounterGe("thrift.thrift_stats.cx_destroy_remote_with_active_rq", 1); + test_server_->waitForCounter("thrift.thrift_stats.cx_destroy_remote_with_active_rq", Ge(1)); Stats::CounterSharedPtr counter = test_server_->counter("thrift.thrift_stats.cx_destroy_remote_with_active_rq"); @@ -377,7 +378,7 @@ TEST_P(ThriftConnManagerIntegrationTest, EarlyCloseWithUpstream) { tcp_client->close(); - test_server_->waitForCounterGe("thrift.thrift_stats.cx_destroy_remote_with_active_rq", 1); + test_server_->waitForCounter("thrift.thrift_stats.cx_destroy_remote_with_active_rq", Ge(1)); Stats::CounterSharedPtr counter = test_server_->counter("thrift.thrift_stats.cx_destroy_remote_with_active_rq"); @@ -474,7 +475,7 @@ TEST_P(ThriftConnManagerIntegrationTest, OnewayEarlyClosePartialRequest) { FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(expected_upstream->waitForRawConnection(fake_upstream_connection)); - test_server_->waitForCounterGe("thrift.thrift_stats.cx_destroy_remote_with_active_rq", 1); + test_server_->waitForCounter("thrift.thrift_stats.cx_destroy_remote_with_active_rq", Ge(1)); Stats::CounterSharedPtr counter = test_server_->counter("thrift.thrift_stats.cx_destroy_remote_with_active_rq"); diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc index 7c486ce20a639..045082426921e 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -16,6 +16,7 @@ #include "test/integration/integration.h" #include "test/test_common/network_utility.h" +using testing::Eq; namespace Envoy { namespace Extensions { namespace UdpFilters { @@ -162,8 +163,8 @@ name: envoy.clusters.dynamic_forward_proxy // Load the CDS cluster and wait for it to initialize. cds_helper_.setCds({dynamic_cluster_}); BaseIntegrationTest::initialize(); - test_server_->waitForCounterEq("cluster_manager.cluster_added", 2); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForCounter("cluster_manager.cluster_added", Eq(2)); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); } CdsHelper cds_helper_; @@ -183,9 +184,9 @@ TEST_P(DynamicForwardProxyIntegrationTest, BasicFlow) { Network::Test::UdpSyncPeer client(version_); client.write("hello1", *listener_address); - test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); - test_server_->waitForCounterEq("dns_cache.foo.dns_query_success", 1); - test_server_->waitForCounterEq("dns_cache.foo.host_added", 1); + test_server_->waitForCounter("dns_cache.foo.dns_query_attempt", Eq(1)); + test_server_->waitForCounter("dns_cache.foo.dns_query_success", Eq(1)); + test_server_->waitForCounter("dns_cache.foo.host_added", Eq(1)); // There is no buffering in this test, so the first message was dropped. Send another message // to verify that it's able to go through after the DNS resolution completed. @@ -217,9 +218,9 @@ TEST_P(DynamicForwardProxyIntegrationTest, BasicFlowWithBuffering) { Network::Test::UdpSyncPeer client(version_); client.write("hello1", *listener_address); - test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); - test_server_->waitForCounterEq("dns_cache.foo.dns_query_success", 1); - test_server_->waitForCounterEq("dns_cache.foo.host_added", 1); + test_server_->waitForCounter("dns_cache.foo.dns_query_attempt", Eq(1)); + test_server_->waitForCounter("dns_cache.foo.dns_query_success", Eq(1)); + test_server_->waitForCounter("dns_cache.foo.host_added", Eq(1)); // Buffering is enabled so check that the first datagram is sent after the resolution completed. Network::UdpRecvData request_datagram; @@ -235,10 +236,10 @@ TEST_P(DynamicForwardProxyIntegrationTest, BufferOverflowDueToDatagramSize) { Network::Test::UdpSyncPeer client(version_); client.write("hello1", *listener_address); - test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); - test_server_->waitForCounterEq("dns_cache.foo.dns_query_success", 1); - test_server_->waitForCounterEq("dns_cache.foo.host_added", 1); - test_server_->waitForCounterEq("udp.session.dynamic_forward_proxy.foo.buffer_overflow", 1); + test_server_->waitForCounter("dns_cache.foo.dns_query_attempt", Eq(1)); + test_server_->waitForCounter("dns_cache.foo.dns_query_success", Eq(1)); + test_server_->waitForCounter("dns_cache.foo.host_added", Eq(1)); + test_server_->waitForCounter("udp.session.dynamic_forward_proxy.foo.buffer_overflow", Eq(1)); // The first datagram should be dropped because it exceeds the buffer size. Send another message // to verify that it's able to go through after the DNS resolution completed. @@ -257,16 +258,16 @@ TEST_P(DynamicForwardProxyIntegrationTest, EmptyDnsResponseDueToDummyHost) { Network::Test::UdpSyncPeer client(version_); client.write("hello1", *listener_address); - test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 1); + test_server_->waitForCounter("dns_cache.foo.dns_query_attempt", Eq(1)); // The DNS response is empty, so will not be found any valid host and session will be removed. - test_server_->waitForCounterEq("cluster.dynamic_cluster.upstream_cx_none_healthy", 1); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForCounter("cluster.dynamic_cluster.upstream_cx_none_healthy", Eq(1)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); // DNS cache hit but still no host found. client.write("hello2", *listener_address); - test_server_->waitForCounterEq("cluster.dynamic_cluster.upstream_cx_none_healthy", 2); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForCounter("cluster.dynamic_cluster.upstream_cx_none_healthy", Eq(2)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(DynamicForwardProxyIntegrationTest, DynamicClusterWithEmptyHostRemoveTheSession) { @@ -277,10 +278,10 @@ TEST_P(DynamicForwardProxyIntegrationTest, DynamicClusterWithEmptyHostRemoveTheS Network::Test::UdpSyncPeer client(version_); client.write("hello1", *listener_address); - test_server_->waitForCounterEq("dns_cache.foo.dns_query_attempt", 0); + test_server_->waitForCounter("dns_cache.foo.dns_query_attempt", Eq(0)); // The host is empty, the session will be removed. - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(DynamicForwardProxyIntegrationTest, StaticClusterWithEmptyHostValidFlow) { @@ -291,7 +292,7 @@ TEST_P(DynamicForwardProxyIntegrationTest, StaticClusterWithEmptyHostValidFlow) Network::Test::UdpSyncPeer client(version_); client.write("hello1", *listener_address); - test_server_->waitForCounterEq("cluster.static_cluster.upstream_cx_tx_bytes_total", 6); + test_server_->waitForCounter("cluster.static_cluster.upstream_cx_tx_bytes_total", Eq(6)); // Buffering is enabled so check that the first datagram is sent after the resolution completed. Network::UdpRecvData request_datagram; diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule_integration_test.cc b/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule_integration_test.cc index ac85002260bb0..5927bc4a6448d 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule_integration_test.cc +++ b/test/extensions/filters/udp/udp_proxy/session_filters/http_capsule/http_capsule_integration_test.cc @@ -8,6 +8,7 @@ #include "test/integration/integration.h" #include "test/test_common/network_utility.h" +using testing::Eq; namespace Envoy { namespace Extensions { namespace UdpFilters { @@ -132,7 +133,7 @@ TEST_P(HttpCapsuleIntegrationTest, SendSplitCapsule) { request_datagram.addresses_.peer_); // Make sure that only one payload received, but none sent downstream because it's not a complete // capsule. - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 1); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_rx_datagrams", Eq(1)); EXPECT_EQ(0, test_server_->counter("udp.foo.downstream_sess_tx_datagrams")->value()); // Sending the rest of the capsule, so we expect a datagram flushed downstream. @@ -141,8 +142,8 @@ TEST_P(HttpCapsuleIntegrationTest, SendSplitCapsule) { Network::UdpRecvData response_datagram; client.recv(response_datagram); EXPECT_EQ(expected_response, response_datagram.buffer_->toString()); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 2); - test_server_->waitForCounterEq("udp.foo.downstream_sess_tx_datagrams", 1); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_rx_datagrams", Eq(2)); + test_server_->waitForCounter("udp.foo.downstream_sess_tx_datagrams", Eq(1)); } TEST_P(HttpCapsuleIntegrationTest, SendMultipleCapsules) { @@ -185,8 +186,8 @@ TEST_P(HttpCapsuleIntegrationTest, SendMultipleCapsules) { Network::UdpRecvData response_datagram; client.recv(response_datagram); EXPECT_EQ("response1", response_datagram.buffer_->toString()); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 1); - test_server_->waitForCounterEq("udp.foo.downstream_sess_tx_datagrams", 2); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_rx_datagrams", Eq(1)); + test_server_->waitForCounter("udp.foo.downstream_sess_tx_datagrams", Eq(2)); } } // namespace diff --git a/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc b/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc index 8f45bb7825204..c02e86349e74b 100644 --- a/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc +++ b/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc @@ -14,6 +14,8 @@ #include "test/test_common/network_utility.h" #include "test/test_common/registry.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -266,21 +268,22 @@ name: test.udp_listener.reverse EXPECT_EQ(expected_response, response_datagram.buffer_->toString()); EXPECT_EQ(listener_address.asString(), response_datagram.addresses_.peer_->asString()); - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_bytes", request.size()); - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_tx_bytes_total", - expected_request.size()); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tx_datagrams", 1); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_bytes", Eq(request.size())); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_datagrams", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_tx_bytes_total", + Eq(expected_request.size())); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tx_datagrams", Eq(1)); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_rx_bytes_total", response.size()); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", + Eq(response.size())); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_rx_datagrams", Eq(1)); // The stat is incremented after the send so there is a race condition and we must wait for // the counter to be incremented. - test_server_->waitForCounterEq("udp.foo.downstream_sess_tx_bytes", expected_response.size()); - test_server_->waitForCounterEq("udp.foo.downstream_sess_tx_datagrams", 1); + test_server_->waitForCounter("udp.foo.downstream_sess_tx_bytes", Eq(expected_response.size())); + test_server_->waitForCounter("udp.foo.downstream_sess_tx_datagrams", Eq(1)); - test_server_->waitForCounterEq("udp.foo.downstream_sess_total", 1); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + test_server_->waitForCounter("udp.foo.downstream_sess_total", Eq(1)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); } UdpReverseFilterConfigFactory factory_; @@ -309,7 +312,7 @@ TEST_P(UdpProxyIntegrationTest, NoReusePort) { // Do not wait for listeners to start as the listener will fail. defer_listener_finalization_ = true; setup(1); - test_server_->waitForCounterGe("listener_manager.lds.update_rejected", 1); + test_server_->waitForCounter("listener_manager.lds.update_rejected", Ge(1)); } // Basic loopback test. @@ -332,9 +335,9 @@ TEST_P(UdpProxyIntegrationTest, DownstreamDrop) { (Network::DEFAULT_UDP_MAX_DATAGRAM_SIZE * Network::NUM_DATAGRAMS_PER_RECEIVE) + 1024; client.write(std::string(large_datagram_size, 'a'), *listener_address); if (GetParam() == Network::Address::IpVersion::v4) { - test_server_->waitForCounterEq("listener.0.0.0.0_0.udp.downstream_rx_datagram_dropped", 1); + test_server_->waitForCounter("listener.0.0.0.0_0.udp.downstream_rx_datagram_dropped", Eq(1)); } else { - test_server_->waitForCounterEq("listener.[__]_0.udp.downstream_rx_datagram_dropped", 1); + test_server_->waitForCounter("listener.[__]_0.udp.downstream_rx_datagram_dropped", Eq(1)); } } @@ -588,7 +591,7 @@ TEST_P(UdpProxyIntegrationTest, ReadSessionFilterStopOnNewSessionButNotOnData) { client.write("hello1", *listener_address); // One datagram should be received, but none sent upstream because socket was not created. - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", 1); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_datagrams", Eq(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.udp.sess_tx_datagrams")->value()); } @@ -667,7 +670,7 @@ TEST_P(UdpProxyIntegrationTest, BufferingFilterBasicFlow) { client.write("hello2", *listener_address); // Two downstream datagrams should be received, but none sent upstream due to filter buffering. - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", 2); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_datagrams", Eq(2)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.udp.sess_tx_datagrams")->value()); // Third downstream datagram should flush the previously buffered datagrams, due to @@ -682,13 +685,13 @@ TEST_P(UdpProxyIntegrationTest, BufferingFilterBasicFlow) { EXPECT_EQ("hello2", request_datagram.buffer_->toString()); ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); EXPECT_EQ("hello3", request_datagram.buffer_->toString()); - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", 3); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tx_datagrams", 3); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_datagrams", Eq(3)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tx_datagrams", Eq(3)); // Two upstream datagrams should be received, but none sent downstream due to filter buffering. fake_upstreams_[0]->sendUdpDatagram("response1", request_datagram.addresses_.peer_); fake_upstreams_[0]->sendUdpDatagram("response2", request_datagram.addresses_.peer_); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 2); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_rx_datagrams", Eq(2)); EXPECT_EQ(0, test_server_->counter("udp.foo.downstream_sess_tx_datagrams")->value()); // Third upstream datagram should flush the previously buffered datagrams, due to @@ -702,8 +705,8 @@ TEST_P(UdpProxyIntegrationTest, BufferingFilterBasicFlow) { EXPECT_EQ("response2", response_datagram.buffer_->toString()); client.recv(response_datagram); EXPECT_EQ("response3", response_datagram.buffer_->toString()); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 3); - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", 3); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_rx_datagrams", Eq(3)); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_datagrams", Eq(3)); } TEST_P(UdpProxyIntegrationTest, TwoBufferingFilters) { @@ -718,7 +721,7 @@ TEST_P(UdpProxyIntegrationTest, TwoBufferingFilters) { client.write("hello2", *listener_address); // Two downstream datagrams should be received, but none sent upstream due to filter buffering. - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", 2); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_datagrams", Eq(2)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.udp.sess_tx_datagrams")->value()); // 'hello1' will flush upstream, 'hello2' will proceed to second filter. 'hello3' will @@ -729,8 +732,8 @@ TEST_P(UdpProxyIntegrationTest, TwoBufferingFilters) { Network::UdpRecvData request_datagram; ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); EXPECT_EQ("hello1", request_datagram.buffer_->toString()); - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", 3); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tx_datagrams", 1); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_datagrams", Eq(3)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tx_datagrams", Eq(1)); // 'hello2' will flush upstream, 'hello3' will proceed to second filter. 'hello4' will // buffer in the first filter. @@ -739,15 +742,15 @@ TEST_P(UdpProxyIntegrationTest, TwoBufferingFilters) { // Wait for the upstream datagram. ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); EXPECT_EQ("hello2", request_datagram.buffer_->toString()); - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", 4); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tx_datagrams", 2); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_datagrams", Eq(4)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tx_datagrams", Eq(2)); // Testing the upstream to downstream direction. // Two upstream datagrams should be received, but none sent downstream due to filter buffering. fake_upstreams_[0]->sendUdpDatagram("response1", request_datagram.addresses_.peer_); // 'response1' will proceed to second filter. 'response2' will buffer in first filter. fake_upstreams_[0]->sendUdpDatagram("response2", request_datagram.addresses_.peer_); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 2); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_rx_datagrams", Eq(2)); EXPECT_EQ(0, test_server_->counter("udp.foo.downstream_sess_tx_datagrams")->value()); // 'response1' will flush downstream, 'response2' will proceed to second filter. 'response3' will @@ -758,16 +761,16 @@ TEST_P(UdpProxyIntegrationTest, TwoBufferingFilters) { Network::UdpRecvData response_datagram; client.recv(response_datagram); EXPECT_EQ("response1", response_datagram.buffer_->toString()); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 3); - test_server_->waitForCounterEq("udp.foo.downstream_sess_tx_datagrams", 1); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_rx_datagrams", Eq(3)); + test_server_->waitForCounter("udp.foo.downstream_sess_tx_datagrams", Eq(1)); // 'response2' will flush downstream, 'response3' will proceed to second filter. 'response4' will // buffer in the first filter. fake_upstreams_[0]->sendUdpDatagram("response4", request_datagram.addresses_.peer_); client.recv(response_datagram); EXPECT_EQ("response2", response_datagram.buffer_->toString()); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 4); - test_server_->waitForCounterEq("udp.foo.downstream_sess_tx_datagrams", 2); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_rx_datagrams", Eq(4)); + test_server_->waitForCounter("udp.foo.downstream_sess_tx_datagrams", Eq(2)); } // Per session cluster setter filter sets non-existent cluster. @@ -789,9 +792,9 @@ TEST_P(UdpProxyIntegrationTest, PerSessionClusterSetterFilterNoClusterFound) { client.write("hello", *listener_address); // cluster_1 does not exist, so the session will be closed. - test_server_->waitForCounterEq("udp.foo.downstream_sess_no_route", 1); - test_server_->waitForCounterEq("udp.foo.downstream_sess_total", 1); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForCounter("udp.foo.downstream_sess_no_route", Eq(1)); + test_server_->waitForCounter("udp.foo.downstream_sess_total", Eq(1)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } // Basic loopback test with per session cluster setter filter. diff --git a/test/extensions/filters/udp/udp_proxy/udp_session_extension_discovery_integration_test.cc b/test/extensions/filters/udp/udp_proxy/udp_session_extension_discovery_integration_test.cc index 4c137d944f4b3..f6e779bf8e107 100644 --- a/test/extensions/filters/udp/udp_proxy/udp_session_extension_discovery_integration_test.cc +++ b/test/extensions/filters/udp/udp_proxy/udp_session_extension_discovery_integration_test.cc @@ -11,6 +11,8 @@ #include "test/integration/integration.h" #include "test/test_common/network_utility.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -272,19 +274,21 @@ name: udp_proxy } void verifyStats() { - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_bytes", ds_rx_bytes_); - test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", expected_datagrams_); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_tx_bytes_total", us_tx_bytes_); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tx_datagrams", expected_datagrams_); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_bytes", Eq(ds_rx_bytes_)); + test_server_->waitForCounter("udp.foo.downstream_sess_rx_datagrams", Eq(expected_datagrams_)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_tx_bytes_total", Eq(us_tx_bytes_)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tx_datagrams", + Eq(expected_datagrams_)); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_rx_bytes_total", us_rx_bytes_); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", expected_datagrams_); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", Eq(us_rx_bytes_)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_rx_datagrams", + Eq(expected_datagrams_)); - test_server_->waitForCounterEq("udp.foo.downstream_sess_tx_bytes", ds_tx_bytes_); - test_server_->waitForCounterEq("udp.foo.downstream_sess_tx_datagrams", expected_datagrams_); + test_server_->waitForCounter("udp.foo.downstream_sess_tx_bytes", Eq(ds_tx_bytes_)); + test_server_->waitForCounter("udp.foo.downstream_sess_tx_datagrams", Eq(expected_datagrams_)); - test_server_->waitForCounterEq("udp.foo.downstream_sess_total", total_sessions_); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", active_sessions_); + test_server_->waitForCounter("udp.foo.downstream_sess_total", Eq(total_sessions_)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(active_sessions_)); } void requestResponseWithListenerAddress(const Network::Address::Instance& listener_address, @@ -312,7 +316,7 @@ name: udp_proxy void sendDataVerifyResults(uint32_t bytes_drained) { test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); const uint32_t port = lookupPort(port_name_); @@ -335,7 +339,7 @@ name: udp_proxy // The new datagram is expected to create a session that will be destroyed, since the session // filter configuration is missing. Expect that the follow stat will increase. - test_server_->waitForCounterGe("udp.foo.session_filter_config_missing", 1); + test_server_->waitForCounter("udp.foo.session_filter_config_missing", Ge(1)); total_sessions_ += 1; verifyStats(); } @@ -414,19 +418,19 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, BasicSuccess) { addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send 1st config update to have filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(5); // Send 2nd config update to have filter drain 3 bytes of data. sendXdsResponse(filter_name_, "2", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(2)); sendDataVerifyResults(3); } @@ -436,25 +440,25 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { addDynamicFilter(filter_name_, false, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send 1st config update with TTL 1s, and have the filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(5); // Wait for configuration expired. - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(2)); sendDataExpectSessionFailure(); // Reinstate the configuration. sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 3); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(3)); sendDataVerifyResults(3); } @@ -464,18 +468,18 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, BasicSuccessWithTtlWithDefau addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send 1st config update with TTL 1s, and have the filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(5); // Wait for configuration expired. The default filter will be installed. - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(2)); // Start a new session. The default filter drains 2 bytes. sendDataVerifyResults(default_bytes_to_drain_); @@ -487,13 +491,13 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, BasicFailWithDefault) { addDynamicFilter(filter_name_, false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send config update with invalid config (bytes_to_drain needs to be <= 20). sendXdsResponse(filter_name_, "1", 21); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_fail", Ge(1)); // The default filter will be used. Start a UDP session. The default filter drain 2 bytes. sendDataVerifyResults(default_bytes_to_drain_); @@ -505,13 +509,13 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, BasicFailWithoutDefault) { addDynamicFilter(filter_name_, false, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send config update with invalid config (bytes_to_drain needs to be <= 20). sendXdsResponse(filter_name_, "1", 21); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_fail", Ge(1)); sendDataExpectSessionFailure(); } @@ -522,15 +526,15 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, BasicWithoutWarming) { addDynamicFilter(filter_name_, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // Send data without send config update. sendDataVerifyResults(default_bytes_to_drain_); // Send update should cause a different response. sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(3); } @@ -540,15 +544,15 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, BasicWithoutWarmingConfigFai addDynamicFilter(filter_name_, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // Send data without send config update. sendDataVerifyResults(default_bytes_to_drain_); // Send config update with invalid config (drain_bytes has to be <= 21). sendXdsResponse(filter_name_, "1", 21); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_fail", Ge(1)); sendDataVerifyResults(default_bytes_to_drain_); } @@ -558,7 +562,7 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, BasicWithoutWarmingNoDefault addDynamicFilter(filter_name_, true, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // No default configuration and no warming, expect new session to fail due to missing config. sendDataExpectSessionFailure(); @@ -571,12 +575,12 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, TwoSubscriptionsSameName) { addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(1)); // Each filter drain 3 bytes. sendDataVerifyResults(6); @@ -590,26 +594,26 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, TwoSubscriptionsDifferentNam addDynamicFilter("bar", false, true, false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send 1st config update. sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe("extension_config_discovery.udp_session_filter.foo.config_reload", - 1); - test_server_->waitForCounterGe("extension_config_discovery.udp_session_filter.bar.config_reload", - 1); + test_server_->waitForCounter("extension_config_discovery.udp_session_filter.foo.config_reload", + Ge(1)); + test_server_->waitForCounter("extension_config_discovery.udp_session_filter.bar.config_reload", + Ge(1)); // The two filters drain 3 + 4 bytes. sendDataVerifyResults(7); // Send 2nd config update. sendXdsResponse("foo", "2", 4); sendXdsResponse("bar", "2", 5, false, true); - test_server_->waitForCounterGe("extension_config_discovery.udp_session_filter.foo.config_reload", - 2); - test_server_->waitForCounterGe("extension_config_discovery.udp_session_filter.bar.config_reload", - 2); + test_server_->waitForCounter("extension_config_discovery.udp_session_filter.foo.config_reload", + Ge(2)); + test_server_->waitForCounter("extension_config_discovery.udp_session_filter.bar.config_reload", + Ge(2)); // The two filters drain 4 + 5 bytes. sendDataVerifyResults(9); } @@ -623,12 +627,12 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, TwoDynamicTwoStaticFilterMix addStaticFilter("foobar", 2); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(1)); // filter drain 3 + 2 + 3 + 2 bytes. sendDataVerifyResults(10); } @@ -642,12 +646,12 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, DynamicStaticFilterMixedDiff addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 2); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(1)); // filter drain 2 + 2 + 2 + 2 bytes. sendDataVerifyResults(8); } @@ -659,13 +663,13 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, BasicSuccessWithConfigDump) addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send 1st config update to have network filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(1)); // Verify ECDS config dump are working correctly. BufferingStreamDecoderPtr response; @@ -706,16 +710,16 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, ConfigDumpWithFilterConfigRe addDynamicFilter(filter_name_, false, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send config update with TTL 1s. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(1)); // Wait for configuration expired. - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(2)); BufferingStreamDecoderPtr response; EXPECT_EQ("200", request("admin", "GET", "/config_dump?resource=ecds_filters", response)); @@ -736,15 +740,15 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, TwoSubscriptionsSameFilterTy addDynamicFilter("bar", false, true, false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe("extension_config_discovery.udp_session_filter.foo.config_reload", - 1); - test_server_->waitForCounterGe("extension_config_discovery.udp_session_filter.bar.config_reload", - 1); + test_server_->waitForCounter("extension_config_discovery.udp_session_filter.foo.config_reload", + Ge(1)); + test_server_->waitForCounter("extension_config_discovery.udp_session_filter.bar.config_reload", + Ge(1)); // Verify ECDS config dump are working correctly. BufferingStreamDecoderPtr response; @@ -789,15 +793,15 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, addDynamicFilter("bar", false, true, false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe("extension_config_discovery.udp_session_filter.foo.config_reload", - 1); - test_server_->waitForCounterGe("extension_config_discovery.udp_session_filter.bar.config_reload", - 1); + test_server_->waitForCounter("extension_config_discovery.udp_session_filter.foo.config_reload", + Ge(1)); + test_server_->waitForCounter("extension_config_discovery.udp_session_filter.bar.config_reload", + Ge(1)); BufferingStreamDecoderPtr response; EXPECT_EQ("200", request("admin", "GET", "/config_dump?resource=ecds_filters&name_regex=.a.", response)); @@ -823,17 +827,17 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, ConfigUpdateDoesNotApplyToEx addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send config update to have filter drain 5 bytes of data. uint32_t bytes_to_drain = 5; sendXdsResponse(filter_name_, "1", bytes_to_drain); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); const uint32_t port = lookupPort(port_name_); @@ -866,8 +870,8 @@ TEST_P(UdpSessionExtensionDiscoveryIntegrationTest, ConfigUpdateDoesNotApplyToEx // Send 2nd config update to have filter drain 3 bytes of data. sendXdsResponse(filter_name_, "2", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.udp_session_filter." + filter_name_ + ".config_reload", Ge(2)); // Using the same client to send another datagram. It should not create a new session, and the // number of bytes drained should not change, as the new configuration does not apply to the diff --git a/test/extensions/formatter/generic_secret/generic_secret_integration_test.cc b/test/extensions/formatter/generic_secret/generic_secret_integration_test.cc index e31cd58489c97..64484a8599138 100644 --- a/test/extensions/formatter/generic_secret/generic_secret_integration_test.cc +++ b/test/extensions/formatter/generic_secret/generic_secret_integration_test.cc @@ -85,7 +85,7 @@ TEST_P(GenericSecretRotationIntegrationTest, SecretRotationReflectedInAccessLog) // Rotate the secret and wait for the SDS update to propagate. rotateSecret("rotated-token"); - test_server_->waitForCounterGe("sds.api-token.update_success", 2); + test_server_->waitForCounter("sds.api-token.update_success", testing::Ge(2)); // Trigger a second access log entry; the flush will use the rotated secret value. auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); diff --git a/test/extensions/http/credential_injector/oauth2/credential_injector_oauth_integration_test.cc b/test/extensions/http/credential_injector/oauth2/credential_injector_oauth_integration_test.cc index b833d559935d6..2e9de72f4a197 100644 --- a/test/extensions/http/credential_injector/oauth2/credential_injector_oauth_integration_test.cc +++ b/test/extensions/http/credential_injector/oauth2/credential_injector_oauth_integration_test.cc @@ -6,6 +6,7 @@ #include "gtest/gtest.h" +using testing::Eq; namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -161,8 +162,8 @@ name: envoy.filters.http.credential_injector }); initializeFilter(filter_config); waitForOAuth2Response("test_client_secret"); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_fetched", 1, - std::chrono::milliseconds(2500)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_fetched", Eq(1), + std::chrono::milliseconds(2500)); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -206,8 +207,8 @@ name: envoy.filters.http.credential_injector )EOF"; initializeFilter(filter_config); waitForOAuth2Response("test_client_secret"); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_fetched", 1, - std::chrono::milliseconds(2500)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_fetched", Eq(1), + std::chrono::milliseconds(2500)); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -251,8 +252,8 @@ name: envoy.filters.http.credential_injector )EOF"; initializeFilter(filter_config); waitForOAuth2Response("test_client_secret"); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_fetched", 1, - std::chrono::milliseconds(2500)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_fetched", Eq(1), + std::chrono::milliseconds(2500)); codec_client_ = makeHttpConnection(lookupPort("http")); default_request_headers_.setCopy(Envoy::Http::LowerCaseString("Authorization"), @@ -307,8 +308,8 @@ name: envoy.filters.http.credential_injector )EOF"; initializeFilter(filter_config); waitForOAuth2Response("test_client_secret"); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_fetched", 1, - std::chrono::milliseconds(2500)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_fetched", Eq(1), + std::chrono::milliseconds(2500)); codec_client_ = makeHttpConnection(lookupPort("http")); default_request_headers_.setCopy(Envoy::Http::LowerCaseString("Authorization"), @@ -361,8 +362,8 @@ name: envoy.filters.http.credential_injector path: "{{ test_tmpdir }}/initial_secret.yaml" )EOF"; initializeFilter(filter_config); - test_server_->waitForCounterEq( - "http.config_test.credential_injector.oauth2.token_fetch_failed_on_client_secret", 2, + test_server_->waitForCounter( + "http.config_test.credential_injector.oauth2.token_fetch_failed_on_client_secret", Eq(2), std::chrono::milliseconds(5000)); EXPECT_EQ(0UL, test_server_->counter("http.config_test.credential_injector.oauth2.token_requested") @@ -374,13 +375,13 @@ name: envoy.filters.http.credential_injector // Update the client secret and now token request should succeed after retry TestEnvironment::renameFile(TestEnvironment::temporaryPath("client_secret.yaml"), TestEnvironment::temporaryPath("initial_secret.yaml")); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_requested", 1, - std::chrono::milliseconds(2500)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_requested", Eq(1), + std::chrono::milliseconds(2500)); waitForOAuth2Response("test_client_secret"); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_fetched", 1, - std::chrono::milliseconds(2500)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_fetched", Eq(1), + std::chrono::milliseconds(2500)); EXPECT_EQ(1UL, test_server_->counter("http.config_test.credential_injector.oauth2.token_requested") @@ -452,8 +453,8 @@ name: envoy.filters.http.credential_injector ->counter( "http.config_test.credential_injector.oauth2.token_fetch_failed_on_bad_response_code") ->value()); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_fetched", 1, - std::chrono::milliseconds(2500)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_fetched", Eq(1), + std::chrono::milliseconds(2500)); EXPECT_EQ(2UL, test_server_->counter("http.config_test.credential_injector.oauth2.token_requested") @@ -508,8 +509,8 @@ name: envoy.filters.http.credential_injector oauth2_request_->encodeHeaders(jsonResponseHeaders(), false); encodeGoodJsonResponseBody(2); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_fetched", 1, - std::chrono::milliseconds(500)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_fetched", Eq(1), + std::chrono::milliseconds(500)); EXPECT_EQ(1UL, test_server_->counter("http.config_test.credential_injector.oauth2.token_requested") @@ -537,8 +538,8 @@ name: envoy.filters.http.credential_injector oauth2_request_->encodeHeaders(jsonResponseHeaders(), false); encodeGoodJsonResponseBody(); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_fetched", 2, - std::chrono::milliseconds(1200)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_fetched", Eq(2), + std::chrono::milliseconds(1200)); } TEST_P(CredentialInjectorIntegrationTest, BadTokenNoExpiry) { @@ -571,8 +572,8 @@ name: envoy.filters.http.credential_injector EXPECT_THAT(request_body_, HasClientSecret("test_client_secret")); oauth2_request_->encodeHeaders(jsonResponseHeaders(), false); encodeBadTokenResponseBody(); - test_server_->waitForCounterEq( - "http.config_test.credential_injector.oauth2.token_fetch_failed_on_bad_token", 1, + test_server_->waitForCounter( + "http.config_test.credential_injector.oauth2.token_fetch_failed_on_bad_token", Eq(1), std::chrono::milliseconds(1000)); } @@ -607,8 +608,8 @@ name: envoy.filters.http.credential_injector oauth2_request_->encodeHeaders(jsonResponseHeaders(), false); encodeBadJsonResponseBody(); - test_server_->waitForCounterEq( - "http.config_test.credential_injector.oauth2.token_fetch_failed_on_bad_token", 1, + test_server_->waitForCounter( + "http.config_test.credential_injector.oauth2.token_fetch_failed_on_bad_token", Eq(1), std::chrono::milliseconds(1000)); } @@ -637,11 +638,11 @@ name: envoy.filters.http.credential_injector path: "{{ test_tmpdir }}/client_secret.yaml" )EOF"; initializeFilter(filter_config); - test_server_->waitForCounterEq( - "http.config_test.credential_injector.oauth2.token_fetch_failed_on_cluster_not_found", 1, + test_server_->waitForCounter( + "http.config_test.credential_injector.oauth2.token_fetch_failed_on_cluster_not_found", Eq(1), std::chrono::milliseconds(1490)); - test_server_->waitForCounterEq( - "http.config_test.credential_injector.oauth2.token_fetch_failed_on_cluster_not_found", 2, + test_server_->waitForCounter( + "http.config_test.credential_injector.oauth2.token_fetch_failed_on_cluster_not_found", Eq(2), std::chrono::milliseconds(1490)); } @@ -676,21 +677,21 @@ name: envoy.filters.http.credential_injector EXPECT_THAT(request_body_, HasClientSecret("test_client_secret")); oauth2_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "503"}}, false); - test_server_->waitForCounterEq( - "http.config_test.credential_injector.oauth2.token_fetch_failed_on_stream_reset", 1, + test_server_->waitForCounter( + "http.config_test.credential_injector.oauth2.token_fetch_failed_on_stream_reset", Eq(1), std::chrono::milliseconds(1000)); EXPECT_EQ(1UL, test_server_->counter("http.config_test.credential_injector.oauth2.token_requested") ->value()); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_requested", 2, - std::chrono::milliseconds(1200)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_requested", Eq(2), + std::chrono::milliseconds(1200)); // wait for retried token request and respond with good response acceptNewStream(); EXPECT_THAT(request_body_, HasClientSecret("test_client_secret")); oauth2_request_->encodeHeaders(jsonResponseHeaders(), false); encodeGoodJsonResponseBody(20); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_fetched", 1, - std::chrono::milliseconds(1200)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_fetched", Eq(1), + std::chrono::milliseconds(1200)); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -772,8 +773,8 @@ name: envoy.filters.http.credential_injector Buffer::OwnedImpl buffer(MessageUtil::getJsonStringFromMessageOrError(oauth_response)); oauth2_request_->encodeData(buffer, true); - test_server_->waitForCounterEq("http.config_test.credential_injector.oauth2.token_fetched", 1, - std::chrono::milliseconds(2500)); + test_server_->waitForCounter("http.config_test.credential_injector.oauth2.token_fetched", Eq(1), + std::chrono::milliseconds(2500)); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); diff --git a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/integration_test.cc b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/integration_test.cc index dcb0f572e027c..5dabd2c476baf 100644 --- a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/integration_test.cc +++ b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/integration_test.cc @@ -13,6 +13,8 @@ #include "absl/strings/numbers.h" #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace Extensions { namespace LoadBalancingPolicies { @@ -267,7 +269,7 @@ class ClientSideWeightedRoundRobinXdsIntegrationTest // Wait for the server initialization to be done. server_initialized.WaitForNotification(); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); // Wait for our statically specified listener to become ready, and register // its port in the test framework's downstream listener port map. @@ -398,7 +400,7 @@ TEST_P(ClientSideWeightedRoundRobinXdsIntegrationTest, ClusterUpDownUp) { {}, {FirstClusterName}, "42"); // We can continue the test once we're sure that Envoy's ClusterManager has // made use of the DiscoveryResponse that says cluster_1 is gone. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); // Now that cluster_1 is gone, the listener (with its routing to cluster_1) // should 503. @@ -414,7 +416,7 @@ TEST_P(ClientSideWeightedRoundRobinXdsIntegrationTest, ClusterUpDownUp) { sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {cluster1_}, {cluster1_}, {}, "413"); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); waitFor200("/cluster1"); cleanupUpstreamAndDownstream(); @@ -440,7 +442,7 @@ TEST_P(ClientSideWeightedRoundRobinXdsIntegrationTest, TwoClusters) { Config::TestTypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster2_}, {}, "42"); // Wait for the cluster to be active (two upstream clusters plus the CDS // cluster). - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); // A request for the second cluster should be fine. waitFor200("/cluster2"); @@ -453,7 +455,7 @@ TEST_P(ClientSideWeightedRoundRobinXdsIntegrationTest, TwoClusters) { Config::TestTypeUrl::get().Cluster, {cluster2_}, {}, {FirstClusterName}, "43"); // We can continue the test once we're sure that Envoy's ClusterManager has // made use of the DiscoveryResponse that says cluster_1 is gone. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); response = IntegrationUtil::makeSingleRequest(lookupPort("http"), "GET", "/cluster2", "", downstream_protocol_, version_, "foo.com"); @@ -466,7 +468,7 @@ TEST_P(ClientSideWeightedRoundRobinXdsIntegrationTest, TwoClusters) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "43", {}, {}, {})); sendDiscoveryResponse( Config::TestTypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster1_}, {}, "413"); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); waitFor200("/cluster1"); cleanupUpstreamAndDownstream(); @@ -542,8 +544,8 @@ class ClientSideWeightedRoundRobinEdsIntegrationTest {cluster1_}, {cluster1_}, {}, "55"); // Wait for EDS request. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.warming_state", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest( Config::getTypeUrl(), "", {FirstClusterName}, {FirstClusterName}, {})); @@ -592,7 +594,7 @@ class ClientSideWeightedRoundRobinEdsIntegrationTest {FirstClusterName}, {}, {})); // Cluster should become active. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); // Wait for our statically specified listener to become ready, and register // its port in the test framework's downstream listener port map. @@ -749,7 +751,7 @@ TEST_P(ClientSideWeightedRoundRobinEdsIntegrationTest, UpdateLocalityPriority) { sendRequestsAndTrackUpstreamUsage(upstream_qps, 10, initial_usage); ENVOY_LOG(trace, "initial_usage {}", initial_usage); - test_server_->waitForCounterEq("cluster.cluster_1.membership_change", i * 2 + 1); + test_server_->waitForCounter("cluster.cluster_1.membership_change", Eq(i * 2 + 1)); // Send another 100 requests to cluster1, expecting weights to be used. std::vector upstream_usage; @@ -798,7 +800,7 @@ TEST_P(ClientSideWeightedRoundRobinEdsIntegrationTest, AddRemoveLocality) { sendRequestsAndTrackUpstreamUsage(upstream_qps, 10, initial_usage); ENVOY_LOG(trace, "initial_usage {}", initial_usage); - test_server_->waitForCounterEq("cluster.cluster_1.membership_change", i + 1); + test_server_->waitForCounter("cluster.cluster_1.membership_change", Eq(i + 1)); // Send another 100 requests to cluster1, expecting weights to be used. std::vector upstream_usage; diff --git a/test/extensions/load_balancing_policies/wrr_locality/integration_test.cc b/test/extensions/load_balancing_policies/wrr_locality/integration_test.cc index 2ee5b1064d354..657bb20d294ff 100644 --- a/test/extensions/load_balancing_policies/wrr_locality/integration_test.cc +++ b/test/extensions/load_balancing_policies/wrr_locality/integration_test.cc @@ -13,6 +13,7 @@ #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace Extensions { namespace LoadBalancingPolicies { @@ -225,7 +226,7 @@ class WrrLocalityEdsIntegrationTest : public testing::TestWithParamwaitForGaugeGe("cluster_manager.active_clusters", 1); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(1)); // Wait for our statically specified listener to become ready, and register // its port in the test framework's downstream listener port map. @@ -347,7 +348,7 @@ TEST_P(WrrLocalityEdsIntegrationTest, AddRemoveLocality) { current_endpoints.mutable_endpoints()->DeleteSubrange(1, 1); } eds_helper_.setEds({current_endpoints}); - test_server_->waitForCounterGe("cluster.cluster_1.membership_change", i + 1); + test_server_->waitForCounter("cluster.cluster_1.membership_change", Ge(i + 1)); const std::vector upstream_qps = {100, 100, 100, 100}; // Send another 100 requests to cluster1, expecting weights to be used. diff --git a/test/extensions/quic/connection_debug_visitor/quic_stats/integration_test.cc b/test/extensions/quic/connection_debug_visitor/quic_stats/integration_test.cc index 9db6d3729aa46..9f2161fb550f0 100644 --- a/test/extensions/quic/connection_debug_visitor/quic_stats/integration_test.cc +++ b/test/extensions/quic/connection_debug_visitor/quic_stats/integration_test.cc @@ -3,6 +3,7 @@ #include "test/integration/http_integration.h" +using testing::Ge; namespace Envoy { namespace Extensions { namespace Quic { @@ -50,7 +51,7 @@ TEST_P(QuicStatsIntegrationTest, Basic) { // before validating values and ranges. Gauges/counters and histograms go through slightly // different paths, so check each to avoid test flakes. test_server_->waitUntilHistogramHasSamples("listener.test.quic_stats.cx_rtt_us"); - test_server_->waitForCounterGe("listener.test.quic_stats.cx_tx_packets_total", 1); + test_server_->waitForCounter("listener.test.quic_stats.cx_tx_packets_total", Ge(1)); auto validateCounterRange = [this](const std::string& name, uint64_t lower, uint64_t upper) { auto counter = test_server_->counter(absl::StrCat("listener.test.quic_stats.", name)); @@ -120,7 +121,7 @@ TEST_P(QuicStatsIntegrationTest, CertChainTooLong) { testRouterHeaderOnlyRequestAndResponse(); codec_client_->goAway(); codec_client_->close(Network::ConnectionCloseType::FlushWrite); - test_server_->waitForCounterGe("listener.test.quic_stats.cx_tx_packets_total", 1); + test_server_->waitForCounter("listener.test.quic_stats.cx_tx_packets_total", Ge(1)); EXPECT_GE(test_server_->counter("listener.test.quic_stats.cx_tx_amplification_throttling_total") ->value(), diff --git a/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc b/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc index b2fa4afe96595..076c61aafb46b 100644 --- a/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc +++ b/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc @@ -4,6 +4,7 @@ #include "test/integration/integration.h" #include "test/test_common/test_runtime.h" +using testing::Eq; namespace Envoy { namespace { @@ -71,14 +72,14 @@ TEST_F(GlobalDownstreamCxLimitIntegrationTest, GlobalLimitInOverloadManager) { raw_conns.push_back(std::move(conn)); ASSERT_TRUE(tcp_clients.back()->connected()); } - test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_global_cx_overflow", 0); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_global_cx_overflow", Eq(0)); // 7th connection should fail because we have hit the configured limit for // `max_active_downstream_connections`. tcp_clients.emplace_back(makeTcpConnection(lookupPort("listener_0"))); FakeRawConnectionPtr conn; ASSERT_FALSE(fake_upstreams_[0]->waitForRawConnection(conn, std::chrono::milliseconds(500))); tcp_clients.back()->waitForDisconnect(); - test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_global_cx_overflow", 1); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_global_cx_overflow", Eq(1)); // Get rid of the client that failed to connect. tcp_clients.back()->close(); tcp_clients.pop_back(); @@ -125,7 +126,7 @@ TEST_F(GlobalDownstreamCxLimitIntegrationTest, GlobalLimitSetViaRuntimeKeyAndOve FakeRawConnectionPtr conn1; ASSERT_FALSE(fake_upstreams_[0]->waitForRawConnection(conn1, std::chrono::milliseconds(500))); tcp_clients.back()->waitForDisconnect(); - test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_global_cx_overflow", 1); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_global_cx_overflow", Eq(1)); for (auto& tcp_client : tcp_clients) { tcp_client->close(); } @@ -147,7 +148,7 @@ TEST_F(GlobalDownstreamCxLimitIntegrationTest, GlobalLimitOptOutRespected) { raw_conns.push_back(std::move(conn)); ASSERT_TRUE(tcp_clients.back()->connected()); } - test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_global_cx_overflow", 0); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_global_cx_overflow", Eq(0)); for (auto& tcp_client : tcp_clients) { tcp_client->close(); } @@ -171,8 +172,8 @@ TEST_F(GlobalDownstreamCxLimitIntegrationTest, PerListenerLimitAndGlobalLimitInO FakeRawConnectionPtr conn1; ASSERT_FALSE(fake_upstreams_[0]->waitForRawConnection(conn1, std::chrono::milliseconds(500))); tcp_clients.back()->waitForDisconnect(); - test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_cx_overflow", 1); - test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_global_cx_overflow", 0); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_cx_overflow", Eq(1)); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_global_cx_overflow", Eq(0)); for (auto& tcp_client : tcp_clients) { tcp_client->close(); } diff --git a/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_integration_test.cc b/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_integration_test.cc index 815da8ab32303..736c0d50a46bb 100644 --- a/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_integration_test.cc +++ b/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_integration_test.cc @@ -13,6 +13,7 @@ #include "test/integration/http_integration.h" #include "test/test_common/utility.h" +using testing::Eq; #if defined(ENVOY_CONFIG_COVERAGE) #define DISABLE_UNDER_COVERAGE return #else @@ -103,8 +104,8 @@ TEST_P(OverloadIntegrationTest, StopAcceptingConnectionsWhenOverloaded) { initialize(); // Put envoy in overloaded state and check that it doesn't accept the new client connection. updateResource(file_updater_1_, 0.95); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.stop_accepting_connections.active", - 1); + test_server_->waitForGauge("overload.envoy.overload_actions.stop_accepting_connections.active", + Eq(1)); IntegrationStreamDecoderPtr response; // For HTTP/2 and below, excess connection won't be accepted, but will hang out // in a pending state and resume below. @@ -115,8 +116,8 @@ TEST_P(OverloadIntegrationTest, StopAcceptingConnectionsWhenOverloaded) { // Reduce load a little to allow the connection to be accepted. updateResource(file_updater_1_, 0.8); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.stop_accepting_connections.active", - 0); + test_server_->waitForGauge("overload.envoy.overload_actions.stop_accepting_connections.active", + Eq(0)); EXPECT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); EXPECT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); @@ -147,7 +148,8 @@ TEST_P(OverloadIntegrationTest, NoNewStreamsWhenOverloaded) { // Enable the disable-keepalive overload action. This should send a shutdown notice before // encoding the headers. updateResource(file_updater_2_, 0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.disable_http_keepalive.active", 1); + test_server_->waitForGauge("overload.envoy.overload_actions.disable_http_keepalive.active", + Eq(1)); // The call to disable keep alive could take some time to be executed on the worker // even if the stat on the main thread is shows the action is enabled. @@ -188,8 +190,8 @@ TEST_P(ListenerMaxConnectionPerSocketEventTest, AcceptsConnectionsUpToTheMaximum initialize(); // Put envoy in overloaded state and check that it doesn't accept the new client connection. updateResource(file_updater_1_, 0.95); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.stop_accepting_connections.active", - 1); + test_server_->waitForGauge("overload.envoy.overload_actions.stop_accepting_connections.active", + Eq(1)); // The TCP stack will accept the connections, but the Envoy listener will not // not yet acknowledge the connection. @@ -202,14 +204,14 @@ TEST_P(ListenerMaxConnectionPerSocketEventTest, AcceptsConnectionsUpToTheMaximum const std::string downstream_cx_active = (version_ == Network::Address::IpVersion::v4) ? "listener.127.0.0.1_0.downstream_cx_active" : "listener.[__1]_0.downstream_cx_active"; - test_server_->waitForGaugeEq(downstream_cx_active, 0); + test_server_->waitForGauge(downstream_cx_active, Eq(0)); EXPECT_LOG_CONTAINS_N_TIMES("trace", "accepted 2 new connections", 5, { // Reduce load a little to allow the connection to be accepted. updateResource(file_updater_1_, 0.8); // As we are using level trigger for listeners, all new connections get recognized. - test_server_->waitForGaugeEq(downstream_cx_active, 10); + test_server_->waitForGauge(downstream_cx_active, Eq(10)); // Wait for the histogram to be updated as that occurs after the logs we are // expecting. diff --git a/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc b/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc index 6fe18877624de..0e8c2adb9f180 100644 --- a/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc +++ b/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc @@ -14,6 +14,8 @@ #include "gtest/gtest.h" using testing::AssertionResult; +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -178,10 +180,10 @@ TEST_P(MetricsServiceIntegrationTest, BasicFlow) { switch (clientType()) { case Grpc::ClientType::EnvoyGrpc: - test_server_->waitForGaugeEq("cluster.metrics_service.upstream_rq_active", 0); + test_server_->waitForGauge("cluster.metrics_service.upstream_rq_active", Eq(0)); break; case Grpc::ClientType::GoogleGrpc: - test_server_->waitForCounterGe("grpc.metrics_service.streams_closed_0", 1); + test_server_->waitForCounter("grpc.metrics_service.streams_closed_0", Ge(1)); break; default: PANIC("reached unexpected code"); diff --git a/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc b/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc index 0c3d57ac174f3..551136982da3e 100644 --- a/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc +++ b/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc @@ -17,6 +17,8 @@ #include "opentelemetry/proto/resource/v1/resource.pb.h" using testing::AssertionResult; +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -228,9 +230,9 @@ class OpenTelemetryIntegrationTest // GoogleGrpc uses its own stream tracking; EnvoyGrpc uses Envoy's cluster stats. [client_type](IntegrationTestServer& server) { if (client_type == Grpc::ClientType::GoogleGrpc) { - server.waitForCounterGe("grpc.otlp_collector.streams_closed_0", 1); + server.waitForCounter("grpc.otlp_collector.streams_closed_0", Ge(1)); } else { - server.waitForGaugeEq("cluster.otlp_collector.upstream_rq_active", 0); + server.waitForGauge("cluster.otlp_collector.upstream_rq_active", Eq(0)); } }}; } @@ -259,7 +261,7 @@ class OpenTelemetryIntegrationTest }, // HTTP uses standard cluster request tracking. [](IntegrationTestServer& server) { - server.waitForGaugeEq("cluster.otlp_collector.upstream_rq_active", 0); + server.waitForGauge("cluster.otlp_collector.upstream_rq_active", Eq(0)); }}; } diff --git a/test/extensions/transport_sockets/internal_upstream/internal_upstream_integration_test.cc b/test/extensions/transport_sockets/internal_upstream/internal_upstream_integration_test.cc index 97b7f8c89ba7e..1c138d736ac19 100644 --- a/test/extensions/transport_sockets/internal_upstream/internal_upstream_integration_test.cc +++ b/test/extensions/transport_sockets/internal_upstream/internal_upstream_integration_test.cc @@ -347,7 +347,8 @@ TEST_F(InternalUpstreamIntegrationTest, TcpProxyHalfCloseLeak) { // Verify that the connection does not leak by waiting for the active connection gauge to drop to // zero. If the server side of the internal connection fails to clean up the downstream connection // then this wait will time out because the connection is stuck in CloseAfterFlush indefinitely. - test_server_->waitForGaugeEq("listener.envoy_internal_internal_listener.downstream_cx_active", 0); + test_server_->waitForGauge("listener.envoy_internal_internal_listener.downstream_cx_active", + testing::Eq(0)); } } // namespace diff --git a/test/extensions/transport_sockets/tap/ssl_tap_integration_test.cc b/test/extensions/transport_sockets/tap/ssl_tap_integration_test.cc index eabac2303ecf9..673c59a11d498 100644 --- a/test/extensions/transport_sockets/tap/ssl_tap_integration_test.cc +++ b/test/extensions/transport_sockets/tap/ssl_tap_integration_test.cc @@ -8,6 +8,7 @@ #include "test/common/tls/integration/ssl_integration_test_base.h" #include "test/extensions/common/tap/common.h" +using testing::Ge; namespace Envoy { namespace Ssl { @@ -145,7 +146,7 @@ TEST_P(SslTapIntegrationTest, TwoRequestsWithBinaryProto) { *codec_client_->connection()->connectionInfoProvider().localAddress(), expected_remote_address); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); envoy::data::tap::v3::TraceWrapper trace; TestUtility::loadFromFile(fmt::format("{}_{}.pb", path_prefix_, first_id), trace, *api_); // Validate general expected properties in the trace. @@ -178,7 +179,7 @@ TEST_P(SslTapIntegrationTest, TwoRequestsWithBinaryProto) { EXPECT_EQ(256, response->body().size()); checkStats(); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 2); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(2)); TestUtility::loadFromFile(fmt::format("{}_{}.pb", path_prefix_, second_id), trace, *api_); // Validate second connection ID. EXPECT_EQ(second_id, trace.socket_buffered_trace().trace_id()); @@ -225,7 +226,7 @@ TEST_P(SslTapIntegrationTest, TruncationWithMultipleDataFrames) { checkStats(); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); envoy::data::tap::v3::TraceWrapper trace; TestUtility::loadFromFile(fmt::format("{}_{}.pb", path_prefix_, id), trace, *api_); @@ -252,7 +253,7 @@ TEST_P(SslTapIntegrationTest, RequestWithTextProto) { testRouterRequestAndResponseWithBody(1024, 512, false, false, &creator); checkStats(); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); envoy::data::tap::v3::TraceWrapper trace; TestUtility::loadFromFile(fmt::format("{}_{}.pb_text", path_prefix_, id), trace, *api_); // Test some obvious properties. @@ -283,7 +284,7 @@ TEST_P(SslTapIntegrationTest, RequestWithJsonBodyAsStringUpstreamTap) { testRouterRequestAndResponseWithBody(512, 1024, false, false, &creator); checkStats(); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); test_server_.reset(); // This must be done after server shutdown so that connection pool connections are closed and @@ -319,7 +320,7 @@ TEST_P(SslTapIntegrationTest, RequestWithStreamingUpstreamTap) { testRouterRequestAndResponseWithBody(512, 1024, false, false, &creator); checkStats(); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); test_server_.reset(); // This must be done after server shutdown so that connection pool connections are closed and @@ -363,8 +364,8 @@ TEST_P(SslTapIntegrationTest, RequestWithStreamingDownstreamTapPegCounter) { testRouterRequestAndResponseWithBody(512, 1024, false, false, &creator); checkStats(); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); - test_server_->waitForCounterGe("transport.tap.tranTapPrefix.streamed_submit", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); + test_server_->waitForCounter("transport.tap.tranTapPrefix.streamed_submit", Ge(1)); test_server_.reset(); // Restore the value. @@ -394,8 +395,8 @@ TEST_P(SslTapIntegrationTest, RequestWithBuffedDownstreamTapPegCounter) { testRouterRequestAndResponseWithBody(512, 1024, false, false, &creator); checkStats(); codec_client_->close(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); - test_server_->waitForCounterGe("transport.tap.tranTapPrefix.buffered_submit", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); + test_server_->waitForCounter("transport.tap.tranTapPrefix.buffered_submit", Ge(1)); test_server_.reset(); // Restore the value. diff --git a/test/extensions/transport_sockets/tcp_stats/tcp_stats_integration_test.cc b/test/extensions/transport_sockets/tcp_stats/tcp_stats_integration_test.cc index 47f8b59c16084..7046b959cdd8e 100644 --- a/test/extensions/transport_sockets/tcp_stats/tcp_stats_integration_test.cc +++ b/test/extensions/transport_sockets/tcp_stats/tcp_stats_integration_test.cc @@ -5,6 +5,7 @@ #include "test/integration/integration.h" +using testing::Ge; namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -78,9 +79,9 @@ TEST_P(TcpStatsSocketIntegrationTest, Basic) { // before validating values and ranges. Gauges/counters and histograms go through slightly // different paths, so check each to avoid test flakes. test_server_->waitUntilHistogramHasSamples("cluster.cluster_0.tcp_stats.cx_rtt_us"); - test_server_->waitForCounterGe("cluster.cluster_0.tcp_stats.cx_tx_segments", 1); + test_server_->waitForCounter("cluster.cluster_0.tcp_stats.cx_tx_segments", Ge(1)); test_server_->waitUntilHistogramHasSamples("listener.test.tcp_stats.cx_rtt_us"); - test_server_->waitForCounterGe("listener.test.tcp_stats.cx_tx_segments", 1); + test_server_->waitForCounter("listener.test.tcp_stats.cx_tx_segments", Ge(1)); auto validateCounterRange = [this](const std::string& name, uint64_t lower, uint64_t upper) { auto counter = test_server_->counter(absl::StrCat("cluster.cluster_0.tcp_stats.", name)); diff --git a/test/extensions/transport_sockets/tls/cert_selectors/on_demand/integration_test.cc b/test/extensions/transport_sockets/tls/cert_selectors/on_demand/integration_test.cc index d5f90835ff615..4b6453d72379a 100644 --- a/test/extensions/transport_sockets/tls/cert_selectors/on_demand/integration_test.cc +++ b/test/extensions/transport_sockets/tls/cert_selectors/on_demand/integration_test.cc @@ -22,6 +22,7 @@ #include "gtest/gtest.h" +using testing::Eq; namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -260,8 +261,8 @@ class OnDemandIntegrationTest : public BaseTcpProxySslIntegrationTest, } void waitCertsRequested(uint32_t count) { - test_server_->waitForCounterEq(onDemandStat("cert_requested"), count, - TestUtility::DefaultTimeout, dispatcher_.get()); + test_server_->waitForCounter(onDemandStat("cert_requested"), Eq(count), + TestUtility::DefaultTimeout, dispatcher_.get()); } std::string onDemandStat(absl::string_view stat) { @@ -295,8 +296,8 @@ TEST_P(OnDemandIntegrationTest, BasicSuccessWithPrefetch) { } EXPECT_EQ(1, test_server_->counter(onDemandStat("cert_requested"))->value()); EXPECT_EQ(1, test_server_->gauge(onDemandStat("cert_active"))->value()); - test_server_->waitForCounterEq("sds.server.update_success", 1); - test_server_->waitForCounterEq(onDemandStat("cert_updated"), 1); + test_server_->waitForCounter("sds.server.update_success", Eq(1)); + test_server_->waitForCounter(onDemandStat("cert_updated"), Eq(1)); EXPECT_EQ(0, test_server_->counter("sds.server.update_rejected")->value()); } @@ -332,8 +333,8 @@ TEST_P(OnDemandIntegrationTest, BasicSuccessWithoutPrefetch) { } EXPECT_EQ(1, test_server_->counter(onDemandStat("cert_requested"))->value()); EXPECT_EQ(1, test_server_->gauge(onDemandStat("cert_active"))->value()); - test_server_->waitForCounterEq("sds.server.update_success", 1); - test_server_->waitForCounterEq(onDemandStat("cert_updated"), 1); + test_server_->waitForCounter("sds.server.update_success", Eq(1)); + test_server_->waitForCounter(onDemandStat("cert_updated"), Eq(1)); EXPECT_EQ(0, test_server_->counter("sds.server.update_rejected")->value()); } @@ -357,7 +358,7 @@ TEST_P(OnDemandIntegrationTest, BasicSuccessSNI) { conn->sendAndReceiveTlsData("hello", "world"); conn.reset(); EXPECT_EQ(1, test_server_->gauge(onDemandStat("cert_active"))->value()); - test_server_->waitForCounterEq("sds.server.update_success", 1); + test_server_->waitForCounter("sds.server.update_success", Eq(1)); EXPECT_EQ(0, test_server_->counter("sds.server.update_rejected")->value()); } @@ -385,8 +386,8 @@ TEST_P(OnDemandIntegrationTest, BasicSuccessMixed) { } conn->sendAndReceiveTlsData("hello", "world"); conn.reset(); - test_server_->waitForCounterEq("sds.server.update_success", 1); - test_server_->waitForCounterEq("sds.server2.update_success", 1); + test_server_->waitForCounter("sds.server.update_success", Eq(1)); + test_server_->waitForCounter("sds.server2.update_success", Eq(1)); EXPECT_EQ(2, test_server_->gauge(onDemandStat("cert_active"))->value()); } @@ -400,7 +401,7 @@ TEST_P(OnDemandIntegrationTest, BasicFail) { createXdsConnection(); waitSendSdsResponse("server", "", true); conn->waitForDisconnect(); - test_server_->waitForGaugeEq(onDemandStat("cert_active"), 0); + test_server_->waitForGauge(onDemandStat("cert_active"), Eq(0)); } TEST_P(OnDemandIntegrationTest, TwoPendingConnections) { @@ -458,9 +459,8 @@ TEST_P(OnDemandIntegrationTest, ListenerConnectTimeout) { }); setup(); auto conn = createClientConnection(); - test_server_->waitForCounterEq( - listenerStatPrefix("downstream_cx_transport_socket_connect_timeout"), 1, - TestUtility::DefaultTimeout, dispatcher_.get()); + test_server_->waitForCounter(listenerStatPrefix("downstream_cx_transport_socket_connect_timeout"), + Eq(1), TestUtility::DefaultTimeout, dispatcher_.get()); conn->close(); conn.reset(); // SDS request is still outstanding, so we can respond to it, and it will be used later. @@ -487,7 +487,7 @@ TEST_P(OnDemandIntegrationTest, SecretAddRemove) { // Remove. removeSecret(stream, "server"); - test_server_->waitForGaugeEq(onDemandStat("cert_active"), 0); + test_server_->waitForGauge(onDemandStat("cert_active"), Eq(0)); // Request again. auto conn2 = createClientConnection(); @@ -519,7 +519,7 @@ TEST_P(OnDemandIntegrationTest, SecretUpdate) { // Update with another valid secret. sendSecret(stream, "server", "server2"); - test_server_->waitForCounterEq(onDemandStat("cert_updated"), 2); + test_server_->waitForCounter(onDemandStat("cert_updated"), Eq(2)); auto conn2 = createClientConnection(); conn2->waitForUpstreamConnection(); @@ -589,12 +589,12 @@ TEST_P(OnDemandIntegrationTest, ValidationContextUpdate) { conn->sendAndReceiveTlsData("hello", "world"); conn.reset(); } - test_server_->waitForCounterEq(onDemandStat("cert_updated"), 1); + test_server_->waitForCounter(onDemandStat("cert_updated"), Eq(1)); // Send a wrong CA via validation SDS and open a new connection that fails. { sendSecret(*ca_stream, cacert(), upstream_selector_ ? "cacert" : "upstreamcacert"); - test_server_->waitForCounterEq(onDemandStat("cert_updated"), 2); + test_server_->waitForCounter(onDemandStat("cert_updated"), Eq(2)); auto conn = createClientConnection(); if (upstream_selector_) { conn->waitForUpstreamConnection(); diff --git a/test/integration/ads_integration.cc b/test/integration/ads_integration.cc index 00d93a2f9660e..036b454474184 100644 --- a/test/integration/ads_integration.cc +++ b/test/integration/ads_integration.cc @@ -18,6 +18,8 @@ #include "test/test_common/utility.h" using testing::AssertionResult; +using testing::Eq; +using testing::Ge; namespace Envoy { @@ -233,7 +235,7 @@ void AdsIntegrationTestBase::testBasicFlow() { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); const Protobuf::Timestamp first_active_listener_ts_1 = getListenersConfigDump().dynamic_listeners(0).active_state().last_updated(); @@ -246,13 +248,13 @@ void AdsIntegrationTestBase::testBasicFlow() { sendDiscoveryResponse( Config::TestTypeUrl::get().Cluster, {buildCluster("cluster_1"), buildCluster("cluster_2")}, {buildCluster("cluster_1"), buildCluster("cluster_2")}, {"cluster_0"}, "2"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(2)); sendDiscoveryResponse( Config::TestTypeUrl::get().ClusterLoadAssignment, {buildClusterLoadAssignment("cluster_1"), buildClusterLoadAssignment("cluster_2")}, {buildClusterLoadAssignment("cluster_1"), buildClusterLoadAssignment("cluster_2")}, {"cluster_0"}, "2"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().ClusterLoadAssignment, "1", {"cluster_2", "cluster_1"}, {"cluster_2", "cluster_1"}, {"cluster_0"})); @@ -299,7 +301,7 @@ void AdsIntegrationTestBase::testBasicFlow() { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "3", {"route_config_2", "route_config_1"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); makeSingleRequest(); const Protobuf::Timestamp first_active_listener_ts_3 = getListenersConfigDump().dynamic_listeners(0).active_state().last_updated(); diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index f07ad500ce902..bf63d32cf3090 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -26,6 +26,8 @@ #include "gtest/gtest.h" using testing::AssertionResult; +using testing::Eq; +using testing::Ge; namespace Envoy { @@ -48,16 +50,16 @@ TEST_P(AdsIntegrationTest, BasicClusterInitialWarming) { EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); sendDiscoveryResponse( cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); sendDiscoveryResponse( eds_type_url, {buildClusterLoadAssignment("cluster_0")}, {buildClusterLoadAssignment("cluster_0")}, {}, "1"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(0)); } // Basic CDS/EDS update that warms and makes active a single cluster. @@ -71,16 +73,16 @@ TEST_P(AdsIntegrationTest, BasicClusterInitialWarmingWithResourceWrapper) { sendDiscoveryResponse( cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", {{"test", Protobuf::Any()}}); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); sendDiscoveryResponse( eds_type_url, {buildClusterLoadAssignment("cluster_0")}, {buildClusterLoadAssignment("cluster_0")}, {}, "1", {{"test", Protobuf::Any()}}); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(0)); } // Tests that the Envoy xDS client can handle updates to a subset of the subscribed resources from @@ -98,9 +100,9 @@ TEST_P(AdsIntegrationTest, UpdateToSubsetOfResources) { EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); sendDiscoveryResponse(cds_type_url, {cluster_0, cluster_1}, {cluster_0, cluster_1}, {}, "1"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(2)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.warming_state", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {cluster_0.name(), cluster_1.name()}, {cluster_0.name(), cluster_1.name()}, {})); auto cla_0 = buildClusterLoadAssignment(cluster_0.name()); @@ -108,10 +110,10 @@ TEST_P(AdsIntegrationTest, UpdateToSubsetOfResources) { sendDiscoveryResponse( eds_type_url, {cla_0, cla_1}, {cla_0, cla_1}, {}, "1"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 0); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(4)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(0)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(0)); // Send an update for one of the ClusterLoadAssignments only. cla_0.mutable_endpoints(0)->mutable_lb_endpoints(0)->mutable_load_balancing_weight()->set_value( @@ -121,10 +123,10 @@ TEST_P(AdsIntegrationTest, UpdateToSubsetOfResources) { // Verify that getting an update for only one of the ClusterLoadAssignment resources does not // delete the other. We use cluster membership health as a proxy for this. - test_server_->waitForCounterEq("cluster.cluster_0.update_success", 2); - test_server_->waitForCounterEq("cluster.cluster_1.update_success", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.membership_healthy", 1); + test_server_->waitForCounter("cluster.cluster_0.update_success", Eq(2)); + test_server_->waitForCounter("cluster.cluster_1.update_success", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.membership_healthy", Eq(1)); } // Update the only warming cluster. Verify that the new cluster is still warming and the cluster @@ -138,7 +140,7 @@ TEST_P(AdsIntegrationTest, ClusterInitializationUpdateTheOnlyWarmingCluster) { EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); sendDiscoveryResponse( cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); // Update lb policy to MAGLEV so that cluster update is not skipped due to the same hash. sendDiscoveryResponse( cds_type_url, {buildCluster("cluster_0", envoy::config::cluster::v3::Cluster::MAGLEV)}, @@ -148,8 +150,8 @@ TEST_P(AdsIntegrationTest, ClusterInitializationUpdateTheOnlyWarmingCluster) { eds_type_url, {buildClusterLoadAssignment("cluster_0")}, {buildClusterLoadAssignment("cluster_0")}, {}, "1"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); } // Primary cluster is warming during cluster initialization. Update the cluster with immediate ready @@ -189,15 +191,15 @@ TEST_P(AdsIntegrationTest, TestPrimaryClusterWarmClusterInitialization) { ASSERT_TRUE(fake_upstreams_.back()->waitForRawConnection(fake_upstream_connection)); // fake_cluster is in warming. - test_server_->waitForGaugeGe("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Ge(1)); // Now replace the warming cluster by the config which will turn ready immediately. sendDiscoveryResponse(cds_type_url, {active_cluster}, {active_cluster}, {}, "2"); // All clusters are ready. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); } // Two cluster warming, update one of them. Verify that the clusters are eventually initialized. @@ -216,7 +218,7 @@ TEST_P(AdsIntegrationTest, ClusterInitializationUpdateOneOfThe2Warming) { buildCluster("cluster_0"), buildCluster("cluster_1")}, {}, "1"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(2)); // Update lb policy to MAGLEV so that cluster update is not skipped due to the same hash. sendDiscoveryResponse( @@ -235,8 +237,8 @@ TEST_P(AdsIntegrationTest, ClusterInitializationUpdateOneOfThe2Warming) { {buildClusterLoadAssignment("cluster_0"), buildClusterLoadAssignment("cluster_1")}, {buildClusterLoadAssignment("cluster_0"), buildClusterLoadAssignment("cluster_1")}, {}, "1"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(4)); } // Verify that Delta SDS Removals don't result in a NACK. @@ -280,7 +282,7 @@ TEST_P(AdsIntegrationTest, DeltaSdsRemovals) { compareDeltaDiscoveryRequest(sds_type_url, {"validation_context"}, {}, {}, {}, false)); // Cluster should start off warming as the secret is being requested. - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(1)); // Ack the original CDS sub. EXPECT_TRUE(compareDeltaDiscoveryRequest(cds_type_url, {}, {}, {}, {}, false)); @@ -291,7 +293,7 @@ TEST_P(AdsIntegrationTest, DeltaSdsRemovals) { // The cluster shouldn't be warming anymore since the server signaled // that the requested resource doesn't exist. - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 0); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(0)); // Ack the original LDS subscription. EXPECT_TRUE(compareDeltaDiscoveryRequest(lds_type_url, {}, {}, {}, {}, false)); @@ -323,7 +325,7 @@ TEST_P(AdsIntegrationTest, DeltaSdsRemovals) { // Follow that up with a secret removal. sendDeltaDiscoveryResponse( sds_type_url, {}, {"validation_context"}, "3"); - test_server_->waitForCounterEq("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Eq(1)); // Ack the CDS removal. EXPECT_TRUE(compareDeltaDiscoveryRequest(cds_type_url, {}, {}, {}, {}, false)); // Should be an ACK, not a NACK since the SDS removal is ignored. @@ -365,9 +367,9 @@ TEST_P(AdsIntegrationTest, ClusterSharingSecretWarming) { EXPECT_TRUE(compareDiscoveryRequest(sds_type_url, "", {"validation_context"}, {"validation_context"}, {})); - test_server_->waitForGaugeGe("cluster_manager.warming_clusters", 2); - test_server_->waitForGaugeEq("cluster.cluster_0.warming_state", 1); - test_server_->waitForGaugeEq("cluster.cluster_1.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Ge(2)); + test_server_->waitForGauge("cluster.cluster_0.warming_state", Eq(1)); + test_server_->waitForGauge("cluster.cluster_1.warming_state", Eq(1)); envoy::extensions::transport_sockets::tls::v3::Secret validation_context; TestUtility::loadFromYaml(fmt::format(R"EOF( @@ -382,7 +384,7 @@ TEST_P(AdsIntegrationTest, ClusterSharingSecretWarming) { sendDiscoveryResponse( sds_type_url, {validation_context}, {validation_context}, {}, "1"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); } // Make sure two clusters with different secrets send only a single SDS request. @@ -422,7 +424,7 @@ TEST_P(AdsIntegrationTest, SecretsPausedDuringCDS) { EXPECT_TRUE(compareDiscoveryRequest(sds_type_url, "", {"validation_context_0", "validation_context_1"}, {"validation_context_0", "validation_context_1"}, {})); - test_server_->waitForGaugeGe("cluster_manager.warming_clusters", 2); + test_server_->waitForGauge("cluster_manager.warming_clusters", Ge(2)); std::vector validation_contexts; for (int i = 0; i < 2; ++i) { @@ -442,7 +444,7 @@ TEST_P(AdsIntegrationTest, SecretsPausedDuringCDS) { sendDiscoveryResponse( sds_type_url, validation_contexts, validation_contexts, {}, "1"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); } // Validate basic config delivery and upgrade with RateLimiting. @@ -522,7 +524,7 @@ TEST_P(AdsIntegrationTest, Failure) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); } @@ -626,7 +628,7 @@ TEST_P(AdsIntegrationTest, DuplicateWarmingListeners) { {buildListener("duplicae_listener", "route_config_0"), buildListener("duplicae_listener", "route_config_0")}, {}, "1"); - test_server_->waitForCounterGe("listener_manager.lds.update_rejected", 1); + test_server_->waitForCounter("listener_manager.lds.update_rejected", Ge(1)); } // Validate that the use of V2 transport version is rejected by default. @@ -643,7 +645,7 @@ TEST_P(AdsIntegrationTest, DEPRECATED_FEATURE_TEST(RejectV2TransportConfigByDefa setGrpcService(*grpc_service, "ads_cluster", xds_upstream_->localAddress()); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {cluster}, {cluster}, {}, "1"); - test_server_->waitForCounterGe("cluster_manager.cds.update_rejected", 1); + test_server_->waitForCounter("cluster_manager.cds.update_rejected", Ge(1)); } // Regression test for the use-after-free crash when processing RDS update (#3953). @@ -664,7 +666,7 @@ TEST_P(AdsIntegrationTest, RdsAfterLdsWithNoRdsChanges) { Config::TestTypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_0", "cluster_0")}, {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Validate that we can process a request. makeSingleRequest(); @@ -674,7 +676,7 @@ TEST_P(AdsIntegrationTest, RdsAfterLdsWithNoRdsChanges) { Config::TestTypeUrl::get().Listener, {buildListener("listener_0", "route_config_0", "rds_crash")}, {buildListener("listener_0", "route_config_0", "rds_crash")}, {}, "2"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); // Update existing RDS (no changes). sendDiscoveryResponse( @@ -720,7 +722,7 @@ TEST_P(AdsIntegrationTest, CdsEdsReplacementWarming) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); sendDiscoveryResponse( @@ -782,7 +784,7 @@ TEST_P(AdsIntegrationTest, CdsKeepEdsAfterWarmingFailure) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); // Update a cluster's field (connect_timeout) so the cluster in Envoy will be explicitly updated. @@ -799,7 +801,7 @@ TEST_P(AdsIntegrationTest, CdsKeepEdsAfterWarmingFailure) { // Avoid sending an EDS update, and wait for EDS update timeout (that results in // a cluster update without resources). - test_server_->waitForCounterGe("cluster.cluster_0.init_fetch_timeout", 1); + test_server_->waitForCounter("cluster.cluster_0.init_fetch_timeout", Ge(1)); if (sotw_or_delta_ == Grpc::SotwOrDelta::Sotw) { // Expect another EDS request after the previous one wasn't answered and timed out. EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().ClusterLoadAssignment, "1", @@ -846,7 +848,7 @@ TEST_P(AdsIntegrationTest, CdsKeepEdsDropOverloadAfterWarmingFailure) { {buildRouteConfig("route_config_0", "cluster_0")}, {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Send a HTTP request and verify it is dropped with unconditional_drop_overload. makeSingleRequestWithDropOverload(); @@ -856,7 +858,7 @@ TEST_P(AdsIntegrationTest, CdsKeepEdsDropOverloadAfterWarmingFailure) { {cluster}, {cluster}, {}, "2"); // Avoid sending an EDS update, and wait for EDS update timeout (that results in // a cluster update without resources). - test_server_->waitForCounterGe("cluster.cluster_0.init_fetch_timeout", 1); + test_server_->waitForCounter("cluster.cluster_0.init_fetch_timeout", Ge(1)); // Envoy uses the cached resource. EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.assignment_use_cached")->value()); // Send a HTTP request again and verify it is dropped with unconditional_drop_overload. @@ -910,7 +912,7 @@ TEST_P(AdsIntegrationTest, DoubleClustersCachedLoadAssignment) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "1", {}, {}, {})); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); // Update a field of the clusters (connect_timeout) so the clusters in Envoy will be explicitly @@ -929,7 +931,7 @@ TEST_P(AdsIntegrationTest, DoubleClustersCachedLoadAssignment) { // Avoid sending an EDS update, and wait for EDS update timeout (that results in // a cluster update without resources). - test_server_->waitForCounterGe("cluster.cluster_0.init_fetch_timeout", 1); + test_server_->waitForCounter("cluster.cluster_0.init_fetch_timeout", Ge(1)); if (sotw_or_delta_ == Grpc::SotwOrDelta::Sotw) { // Expect another EDS request after the previous one wasn't answered and timed out. @@ -951,7 +953,7 @@ TEST_P(AdsIntegrationTest, DoubleClustersCachedLoadAssignment) { Config::TestTypeUrl::get().ClusterLoadAssignment, {cla_0}, {cla_0}, {}, "2"); // Wait for ingesting the update. - test_server_->waitForCounterEq("cluster.cluster_0.update_success", 2); + test_server_->waitForCounter("cluster.cluster_0.update_success", Eq(2)); // A single message should be successfully sent to the upstream. makeSingleRequest(); @@ -970,7 +972,7 @@ TEST_P(AdsIntegrationTest, DuplicateInitialClusters) { {buildCluster("duplicate_cluster"), buildCluster("duplicate_cluster")}, {buildCluster("duplicate_cluster"), buildCluster("duplicate_cluster")}, {}, "1"); - test_server_->waitForCounterGe("cluster_manager.cds.update_rejected", 1); + test_server_->waitForCounter("cluster_manager.cds.update_rejected", Ge(1)); } // Validate that the request with duplicate clusters in the subsequent requests (warming clusters) @@ -1009,7 +1011,7 @@ TEST_P(AdsIntegrationTest, DuplicateWarmingClusters) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); // Send duplicate warming clusters and validate that the update is rejected. @@ -1017,7 +1019,7 @@ TEST_P(AdsIntegrationTest, DuplicateWarmingClusters) { Config::TestTypeUrl::get().Cluster, {buildCluster("duplicate_cluster"), buildCluster("duplicate_cluster")}, {buildCluster("duplicate_cluster"), buildCluster("duplicate_cluster")}, {}, "2"); - test_server_->waitForCounterGe("cluster_manager.cds.update_rejected", 1); + test_server_->waitForCounter("cluster_manager.cds.update_rejected", Ge(1)); } // Verify CDS is paused during cluster warming. @@ -1055,7 +1057,7 @@ TEST_P(AdsIntegrationTest, CdsPausedDuringWarming) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); // Send the first warming cluster. @@ -1063,8 +1065,8 @@ TEST_P(AdsIntegrationTest, CdsPausedDuringWarming) { Config::TestTypeUrl::get().Cluster, {buildCluster("warming_cluster_1")}, {buildCluster("warming_cluster_1")}, {"cluster_0"}, "2"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - test_server_->waitForGaugeEq("cluster.warming_cluster_1.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); + test_server_->waitForGauge("cluster.warming_cluster_1.warming_state", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().ClusterLoadAssignment, "1", {"warming_cluster_1"}, {"warming_cluster_1"}, {"cluster_0"})); @@ -1074,8 +1076,8 @@ TEST_P(AdsIntegrationTest, CdsPausedDuringWarming) { Config::TestTypeUrl::get().Cluster, {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, {}, "3"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); - test_server_->waitForGaugeEq("cluster.warming_cluster_2.warming_state", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(2)); + test_server_->waitForGauge("cluster.warming_cluster_2.warming_state", Eq(1)); // We would've got a Cluster discovery request with version 2 here, had the CDS not been paused. EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().ClusterLoadAssignment, "1", @@ -1092,9 +1094,9 @@ TEST_P(AdsIntegrationTest, CdsPausedDuringWarming) { {"cluster_0"}, "2"); // Validate that clusters are warmed. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeEq("cluster.warming_cluster_1.warming_state", 0); - test_server_->waitForGaugeEq("cluster.warming_cluster_2.warming_state", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster.warming_cluster_1.warming_state", Eq(0)); + test_server_->waitForGauge("cluster.warming_cluster_2.warming_state", Eq(0)); // CDS is resumed and EDS response was acknowledged. // TODO (dmitri-d) remove the conditional when legacy mux implementations are removed. @@ -1143,7 +1145,7 @@ TEST_P(AdsIntegrationTest, RemoveWarmingCluster) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); // Send the first warming cluster. @@ -1151,7 +1153,7 @@ TEST_P(AdsIntegrationTest, RemoveWarmingCluster) { Config::TestTypeUrl::get().Cluster, {buildCluster("warming_cluster_1")}, {buildCluster("warming_cluster_1")}, {"cluster_0"}, "2"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().ClusterLoadAssignment, "1", {"warming_cluster_1"}, {"warming_cluster_1"}, {"cluster_0"})); @@ -1162,7 +1164,7 @@ TEST_P(AdsIntegrationTest, RemoveWarmingCluster) { {buildCluster("warming_cluster_2")}, // Delta: remove warming_cluster_1. {"warming_cluster_1"}, "3"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); // We would've got a Cluster discovery request with version 2 here, had the CDS not been paused. EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().ClusterLoadAssignment, "1", @@ -1177,8 +1179,8 @@ TEST_P(AdsIntegrationTest, RemoveWarmingCluster) { {buildClusterLoadAssignment("warming_cluster_2")}, {"cluster_0"}, "2"); // Validate that all clusters are warmed. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); // CDS is resumed and EDS response was acknowledged. // TODO (dmitri-d) remove the conditional when legacy mux implementations are removed. @@ -1227,7 +1229,7 @@ TEST_P(AdsIntegrationTest, RemoveWarmingListener) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); // Send a listener without its route, so it will be added as warming. @@ -1236,7 +1238,7 @@ TEST_P(AdsIntegrationTest, RemoveWarmingListener) { {buildListener("listener_0", "route_config_0"), buildListener("warming_listener_1", "nonexistent_route")}, {buildListener("warming_listener_1", "nonexistent_route")}, {}, "2"); - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", 1); + test_server_->waitForGauge("listener_manager.total_listeners_warming", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"nonexistent_route", "route_config_0"}, {"nonexistent_route"}, {})); @@ -1251,8 +1253,8 @@ TEST_P(AdsIntegrationTest, RemoveWarmingListener) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "3", {}, {}, {})); // The warming listener should be successfully removed. - test_server_->waitForCounterEq("listener_manager.listener_removed", 1); - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", 0); + test_server_->waitForCounter("listener_manager.listener_removed", Eq(1)); + test_server_->waitForGauge("listener_manager.total_listeners_warming", Eq(0)); } // Verify cluster warming is finished only on named EDS response. @@ -1290,14 +1292,14 @@ TEST_P(AdsIntegrationTest, ClusterWarmingOnNamedResponse) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); // Send the first warming cluster. sendDiscoveryResponse( Config::TestTypeUrl::get().Cluster, {buildCluster("warming_cluster_1")}, {buildCluster("warming_cluster_1")}, {"cluster_0"}, "2"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().ClusterLoadAssignment, "1", {"warming_cluster_1"}, {"warming_cluster_1"}, {"cluster_0"})); @@ -1307,7 +1309,7 @@ TEST_P(AdsIntegrationTest, ClusterWarmingOnNamedResponse) { Config::TestTypeUrl::get().Cluster, {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, {}, "3"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(2)); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().ClusterLoadAssignment, "1", {"warming_cluster_2", "warming_cluster_1"}, @@ -1321,7 +1323,7 @@ TEST_P(AdsIntegrationTest, ClusterWarmingOnNamedResponse) { // Envoy will not finish warming of the second cluster because of the missing load assignments // i,e. no named EDS response. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); // Disconnect and reconnect the stream. xds_stream_->finishGrpcStream(Grpc::Status::Internal); @@ -1332,7 +1334,7 @@ TEST_P(AdsIntegrationTest, ClusterWarmingOnNamedResponse) { // Envoy will not finish warming of the second cluster because of the missing load assignments // i,e. no named EDS response even after disconnect and reconnect. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); // Finish warming the second cluster. sendDiscoveryResponse( @@ -1340,7 +1342,7 @@ TEST_P(AdsIntegrationTest, ClusterWarmingOnNamedResponse) { {buildClusterLoadAssignment("warming_cluster_2")}, {buildClusterLoadAssignment("warming_cluster_2")}, {}, "3"); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); } // This test validates two cases. @@ -1364,7 +1366,7 @@ TEST_P(AdsIntegrationTest, ListenerWarmingOnNamedResponse) { {buildRouteConfig("route_config_0", "cluster_0")}, {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Validate that we can process a request. makeSingleRequest(); @@ -1382,8 +1384,8 @@ TEST_P(AdsIntegrationTest, ListenerWarmingOnNamedResponse) { {buildListener("listener_0", "route_config_0", "rds_test")}, {}, "2"); // Validate that listener is updated correctly and does not get in to warming state. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", 0); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); + test_server_->waitForGauge("listener_manager.total_listeners_warming", Eq(0)); // Update listener with a new route. sendDiscoveryResponse( @@ -1392,16 +1394,16 @@ TEST_P(AdsIntegrationTest, ListenerWarmingOnNamedResponse) { {buildListener("listener_0", "route_config_1", "rds_test")}, {}, "2"); // Validate that the listener gets in to warming state waiting for RDS. - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", 1); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForGauge("listener_manager.total_listeners_warming", Eq(1)); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); // Send the new route and validate that listener finishes warming. sendDiscoveryResponse( Config::TestTypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_1", "cluster_1")}, {buildRouteConfig("route_config_1", "cluster_1")}, {}, "2"); - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", 0); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 3); + test_server_->waitForGauge("listener_manager.total_listeners_warming", Eq(0)); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(3)); } // Regression test for the use-after-free crash when processing RDS update (#3953). @@ -1422,7 +1424,7 @@ TEST_P(AdsIntegrationTest, RdsAfterLdsWithRdsChange) { Config::TestTypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_0", "cluster_0")}, {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Validate that we can process a request. makeSingleRequest(); @@ -1438,7 +1440,7 @@ TEST_P(AdsIntegrationTest, RdsAfterLdsWithRdsChange) { Config::TestTypeUrl::get().Listener, {buildListener("listener_0", "route_config_0", "rds_crash")}, {buildListener("listener_0", "route_config_0", "rds_crash")}, {}, "2"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); // Update existing RDS (migrate traffic to cluster_1). sendDiscoveryResponse( @@ -1447,7 +1449,7 @@ TEST_P(AdsIntegrationTest, RdsAfterLdsWithRdsChange) { {buildRouteConfig("route_config_0", "cluster_1")}, {}, "2"); // Validate that we can process a request after RDS update - test_server_->waitForCounterGe("http.ads_test.rds.route_config_0.config_reload", 2); + test_server_->waitForCounter("http.ads_test.rds.route_config_0.config_reload", Ge(2)); makeSingleRequest(); } @@ -1496,7 +1498,7 @@ TEST_P(AdsIntegrationTest, RdsAfterLdsInvalidated) { // Wait for initial listener to be created successfully. Any subsequent listeners will then use // the dynamic InitManager (see ListenerImpl::initManager). - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // STEP 2: Listener with dynamic InitManager // ----------------------------------------- @@ -1535,7 +1537,7 @@ TEST_P(AdsIntegrationTest, RdsAfterLdsInvalidated) { {buildRouteConfig("route_config_1", "cluster_0")}, {buildRouteConfig("route_config_1", "cluster_0")}, {"route_config_0"}, "1"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); } class AdsFailIntegrationTest : public AdsDeltaSotwIntegrationSubStateParamTest, @@ -1721,7 +1723,7 @@ TEST_P(AdsIntegrationTest, ListenerDrainBeforeServerStart) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "", {"route_config_0"}, {"route_config_0"}, {})); - test_server_->waitForGaugeGe("listener_manager.total_listeners_active", 1); + test_server_->waitForGauge("listener_manager.total_listeners_active", Ge(1)); // Before server is started, even though listeners are added to active list // we mark them as "warming" in config dump since they're not initialized yet. ASSERT_EQ(getListenersConfigDump().dynamic_listeners().size(), 1); @@ -1731,7 +1733,7 @@ TEST_P(AdsIntegrationTest, ListenerDrainBeforeServerStart) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "1", {}, {}, {})); sendDiscoveryResponse(Config::TestTypeUrl::get().Listener, {}, {}, {"listener_0"}, "2"); - test_server_->waitForGaugeEq("listener_manager.total_listeners_active", 0); + test_server_->waitForGauge("listener_manager.total_listeners_active", Eq(0)); } // Validate that Node message is well formed. @@ -1914,7 +1916,7 @@ class AdsIntegrationTestWithRtds : public AdsIntegrationTest { sendDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {some_rtds_layer}, {some_rtds_layer}, {}, "1"); - test_server_->waitForCounterGe("runtime.load_success", 1); + test_server_->waitForCounter("runtime.load_success", Ge(1)); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "", {}, {}, {})); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Runtime, "1", {"ads_rtds_layer"}, {}, {})); @@ -1960,7 +1962,7 @@ class AdsIntegrationTestWithRtdsAndSecondaryClusters : public AdsIntegrationTest sendDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {some_rtds_layer}, {some_rtds_layer}, {}, "1"); - test_server_->waitForCounterGe("runtime.load_success", 1); + test_server_->waitForCounter("runtime.load_success", Ge(1)); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().ClusterLoadAssignment, "", {"eds_cluster"}, {"eds_cluster"}, {}, false)); sendDiscoveryResponse( @@ -2149,7 +2151,7 @@ TEST_P(XdsTpAdsIntegrationTest, Basic) { EXPECT_TRUE( compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {}, {}, {})); - test_server_->waitForCounterEq("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Eq(1)); makeSingleRequest(); // Add a second listener in the foo namespace. @@ -2171,7 +2173,7 @@ TEST_P(XdsTpAdsIntegrationTest, Basic) { EXPECT_TRUE( compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "2", {}, {}, {})); - test_server_->waitForCounterEq("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Eq(2)); makeSingleRequest(); // Updates only apply to the Delta protocol, not SotW. @@ -2183,7 +2185,7 @@ TEST_P(XdsTpAdsIntegrationTest, Basic) { sendDiscoveryResponse( Config::TestTypeUrl::get().Listener, {baz_listener}, {}, {}, "3"); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "3", {}, {}, {})); - test_server_->waitForCounterEq("listener_manager.listener_removed", 1); + test_server_->waitForCounter("listener_manager.listener_removed", Eq(1)); makeSingleRequest(); } else { // Update bar listener in the foo namespace. @@ -2191,14 +2193,14 @@ TEST_P(XdsTpAdsIntegrationTest, Basic) { Config::TestTypeUrl::get().Listener, {}, {buildListener(bar_listener, route_name_1)}, {}, "3"); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "3", {}, {}, {})); - test_server_->waitForCounterEq("listener_manager.listener_in_place_updated", 1); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Eq(1)); makeSingleRequest(); // Remove bar listener from the foo namespace. sendDiscoveryResponse( Config::TestTypeUrl::get().Listener, {}, {}, {bar_listener}, "3"); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "4", {}, {}, {})); - test_server_->waitForCounterEq("listener_manager.listener_removed", 1); + test_server_->waitForCounter("listener_manager.listener_removed", Eq(1)); makeSingleRequest(); } } @@ -2242,7 +2244,7 @@ TEST_P(XdsTpAdsIntegrationTest, BasicWithLeds) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "1", {}, {}, {})); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); // Receive LEDS request, and send 2 endpoints. EXPECT_TRUE(compareDiscoveryRequest( @@ -2261,8 +2263,8 @@ TEST_P(XdsTpAdsIntegrationTest, BasicWithLeds) { // Receive the EDS ack. EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "1", {}, {}, {})); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); // LDS/RDS xDS initialization (LDS via xdstp:// glob collection) EXPECT_TRUE( @@ -2318,7 +2320,7 @@ TEST_P(XdsTpAdsIntegrationTest, LedsClusterWarmingUpdatingEds) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "1", {}, {}, {})); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); // Receive LEDS request, and send an updated EDS response. EXPECT_TRUE(compareDiscoveryRequest( @@ -2368,8 +2370,8 @@ TEST_P(XdsTpAdsIntegrationTest, LedsClusterWarmingUpdatingEds) { {}); // The cluster should be warmed up. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); // Receive the LEDS ack. EXPECT_TRUE(compareDiscoveryRequest(leds_type_url, "3", {}, {}, {})); @@ -2426,7 +2428,7 @@ TEST_P(XdsTpAdsIntegrationTest, LedsClusterWarmingUpdatingCds) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "1", {}, {}, {})); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); // Receive LEDS request, and send an updated CDS response (removing previous // cluster and adding a new one). @@ -2499,8 +2501,8 @@ TEST_P(XdsTpAdsIntegrationTest, LedsClusterWarmingUpdatingCds) { buildLbEndpointResource(endpoint2_name_cluster2, "2")}, {}); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); // LDS/RDS xDS initialization (LDS via xdstp:// glob collection) EXPECT_TRUE( @@ -2565,16 +2567,16 @@ TEST_P(XdsTpAdsIntegrationTest, LedsTimeout) { {})); // The cluster should be warming. Wait until initial fetch timeout. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); - test_server_->waitForCounterEq( + test_server_->waitForCounter( "cluster.xdstp_test/envoy.config.cluster.v3.Cluster/foo-cluster/" "baz?xds.node.cluster=cluster_name&xds.node.id=node_name.leds.init_fetch_timeout", - 1); + Eq(1)); // After timeout the cluster should be active, not warming. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); // Receive the EDS ack. EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "1", {}, {}, {})); @@ -2652,7 +2654,7 @@ TEST_P(XdsTpAdsIntegrationTest, EdsAlternatingLedsUsage) { EXPECT_TRUE( compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {}, {}, {})); - test_server_->waitForCounterEq("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Eq(1)); makeSingleRequest(); // Send a new EDS update that uses LEDS. @@ -2778,7 +2780,7 @@ TEST_P(AdsIntegrationTest, SrdsPausedDuringLds) { sendDiscoveryResponse(lds_type_url, listeners, listeners, {}, "1"); - test_server_->waitForCounterEq("listener_manager.listener_added", 2); + test_server_->waitForCounter("listener_manager.listener_added", Eq(2)); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "1", {}, {}, {})); @@ -2819,7 +2821,7 @@ TEST_P(AdsIntegrationTest, SrdsPausedDuringLds) { EXPECT_TRUE(compareDiscoveryRequest(rds_type_url, "1", {"route_0", "route_1"}, {}, {})); - test_server_->waitForCounterEq("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Eq(2)); } // ADS integration tests that exercise the ADS-replacement mechanism. @@ -3115,7 +3117,7 @@ TEST_P(AdsIntegrationTest, MultipleVhdsOverAds) { {}, "1"); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().VirtualHost, "", {}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 3); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(3)); auto foo_request_headers = Http::TestRequestHeaderMapImpl{ {":method", "GET"}, {":path", "/foo"}, {":scheme", "http"}, {":authority", "foo.com"}}; @@ -3157,7 +3159,7 @@ TEST_P(AdsIntegrationTest, MultipleVhdsOverAds) { sendDiscoveryResponse( Config::TestTypeUrl::get().VirtualHost, {}, {}, {"route_config_0/foo"}, "1"); - test_server_->waitForCounterGe("http.ads_test.rds.vhds.route_config_0.config_reload", 2); + test_server_->waitForCounter("http.ads_test.rds.vhds.route_config_0.config_reload", Ge(2)); send_request_and_verify("http1", foo_request_headers, true); send_request_and_verify("http1", bar_request_headers); @@ -3211,7 +3213,7 @@ TEST_P(AdsIntegrationTest, VHDSUpdatesAfterListenerRemoval) { Config::TestTypeUrl::get().VirtualHost, {}, {buildVirtualHost("route_config_0/foo", "foo.com", "/foo", "cluster_0")}, {}, "1"); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().VirtualHost, "", {}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); registerTestServerPorts({"http0", "http1"}); auto send_request_and_verify = [this](const std::string& port_name, @@ -3243,7 +3245,7 @@ TEST_P(AdsIntegrationTest, VHDSUpdatesAfterListenerRemoval) { sendDiscoveryResponse(Config::TestTypeUrl::get().Listener, {}, {}, {"listener_1"}, "1"); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "", {}, {}, {})); - test_server_->waitForGaugeEq("listener_manager.total_listeners_draining", 0); + test_server_->waitForGauge("listener_manager.total_listeners_draining", Eq(0)); // Verify that listener_0 still works. send_request_and_verify("http0", Http::TestRequestHeaderMapImpl{{":method", "GET"}, @@ -3309,7 +3311,7 @@ TEST_P(AdsIntegrationTest, NewListenerUsesLocalRouteConfig) { Config::TestTypeUrl::get().VirtualHost, {}, {buildVirtualHost("route_config_0/foo", "foo.com", "/foo", "cluster_0")}, {}, "1"); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().VirtualHost, "", {}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); registerTestServerPorts({"http0"}); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http0")))); auto response = sendRequestAndWaitForResponse( @@ -3325,7 +3327,7 @@ TEST_P(AdsIntegrationTest, NewListenerUsesLocalRouteConfig) { Config::TestTypeUrl::get().Listener, {}, {buildListener("listener_1", "route_config_0")}, {}, "1"); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "", {}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); registerTestServerPorts({"http0", "http1"}); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http1")))); response = sendRequestAndWaitForResponse( diff --git a/test/integration/ads_xdstp_config_sources_integration_test.cc b/test/integration/ads_xdstp_config_sources_integration_test.cc index 2dd5debee8206..d741f6da59bea 100644 --- a/test/integration/ads_xdstp_config_sources_integration_test.cc +++ b/test/integration/ads_xdstp_config_sources_integration_test.cc @@ -1,6 +1,7 @@ #include "test/integration/ads_xdstp_config_sources_integration.h" using testing::AssertionResult; +using testing::Ge; namespace Envoy { @@ -65,7 +66,7 @@ TEST_P(AdsXdsTpConfigsIntegrationTest, CdsPointsToAuthorityEds) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); } @@ -105,7 +106,7 @@ TEST_P(AdsXdsTpConfigsIntegrationTest, UpdateAuthorityEds) { Config::TestTypeUrl::get().ClusterLoadAssignment, "1", {"xdstp://authority1.com/envoy.config.endpoint.v3.ClusterLoadAssignment/clusters/cluster1"}, {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", authority1_xds_stream_.get())); - test_server_->waitForCounterGe("cluster.cluster_1.update_success", 1); + test_server_->waitForCounter("cluster.cluster_1.update_success", Ge(1)); // Send the Listener and route config using the old ADS. EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "", {}, {}, {})); @@ -122,7 +123,7 @@ TEST_P(AdsXdsTpConfigsIntegrationTest, UpdateAuthorityEds) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Update the EDS config. cla.mutable_endpoints(0)->mutable_load_balancing_weight()->set_value(50); @@ -137,7 +138,7 @@ TEST_P(AdsXdsTpConfigsIntegrationTest, UpdateAuthorityEds) { {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", authority1_xds_stream_.get())); // Ensure that the EDS update was successful. - test_server_->waitForCounterGe("cluster.cluster_1.update_success", 2); + test_server_->waitForCounter("cluster.cluster_1.update_success", Ge(2)); makeSingleRequest(); } @@ -183,7 +184,7 @@ TEST_P(AdsXdsTpConfigsIntegrationTest, UpdateAuthorityToFetchEds) { Config::TestTypeUrl::get().ClusterLoadAssignment, "1", {"xdstp://authority1.com/envoy.config.endpoint.v3.ClusterLoadAssignment/clusters/cluster1"}, {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", authority1_xds_stream_.get())); - test_server_->waitForCounterGe("cluster.cluster_1.update_success", 1); + test_server_->waitForCounter("cluster.cluster_1.update_success", Ge(1)); // Send the Listener and route config using the old ADS. EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Listener, "", {}, {}, {})); @@ -200,7 +201,7 @@ TEST_P(AdsXdsTpConfigsIntegrationTest, UpdateAuthorityToFetchEds) { EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "1", {"route_config_0"}, {}, {})); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); makeSingleRequest(); // Update the cluster's load-assignment to a different resource authority. @@ -239,7 +240,7 @@ TEST_P(AdsXdsTpConfigsIntegrationTest, UpdateAuthorityToFetchEds) { "envoy.config.endpoint.v3.ClusterLoadAssignment/clusters/cluster1"}, {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", default_authority_xds_stream_.get())); - test_server_->waitForCounterGe("cluster.cluster_1.update_success", 2); + test_server_->waitForCounter("cluster.cluster_1.update_success", Ge(2)); // Authority1 subscription is removed. EXPECT_TRUE(compareDiscoveryRequest( diff --git a/test/integration/alpn_integration_test.cc b/test/integration/alpn_integration_test.cc index 1254721306811..4d279953009d0 100644 --- a/test/integration/alpn_integration_test.cc +++ b/test/integration/alpn_integration_test.cc @@ -3,6 +3,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -102,8 +104,8 @@ TEST_P(AlpnIntegrationTest, Http1RememberLimits) { default_request_headers_.addCopy(AutonomousStream::CLOSE_AFTER_RESPONSE, "yes"); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); ASSERT_TRUE(response->waitForEndStream()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); fake_upstreams_.clear(); { IntegrationCodecClientPtr codec_client1 = makeHttpConnection(lookupPort("http")); @@ -111,7 +113,7 @@ TEST_P(AlpnIntegrationTest, Http1RememberLimits) { IntegrationCodecClientPtr codec_client2 = makeHttpConnection(lookupPort("http")); auto response2 = codec_client2->makeHeaderOnlyRequest(default_request_headers_); // Envoy should attempt to establish 2 new connections, one for each stream. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 3); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(3)); codec_client1->close(); codec_client2->close(); } @@ -138,12 +140,12 @@ TEST_P(AlpnIntegrationTest, Http2RememberSettings) { upstream_request_->postToConnectionThread([this, &settings_buffer]() { fake_upstream_connection_->connection().write(settings_buffer, false); }); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_rx_bytes_total", - bytes_read + settings_data.size()); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", + Ge(bytes_read + settings_data.size())); // Close the connection. ASSERT_TRUE(fake_upstream_connection_->close()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); { absl::MutexLock l(fake_upstreams_[0]->lock()); @@ -152,7 +154,7 @@ TEST_P(AlpnIntegrationTest, Http2RememberSettings) { IntegrationCodecClientPtr codec_client2 = makeHttpConnection(lookupPort("http")); auto response2 = codec_client2->makeHeaderOnlyRequest(default_request_headers_); // Envoy should attempt to establish 2 new connections, one for each stream. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 3); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(3)); codec_client1->close(); codec_client2->close(); } @@ -203,15 +205,15 @@ TEST_P(AlpnIntegrationTest, DisconnectDuringHandshake) { // Connect and wait for the upstream connection to be established. codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); // Close the downstream connection and wait for the upstream stream to go away. codec_client_->close(); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_cancelled", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_cancelled", Eq(1)); // Allow the connection to complete. unblock_accept.Notify(); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_http2_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_http2_total", Ge(1)); } } // namespace diff --git a/test/integration/buffer_accounting_integration_test.cc b/test/integration/buffer_accounting_integration_test.cc index e7a4bcd042494..e27c0334aa754 100644 --- a/test/integration/buffer_accounting_integration_test.cc +++ b/test/integration/buffer_accounting_integration_test.cc @@ -24,6 +24,8 @@ #include "integration_stream_decoder.h" #include "socket_interface_swap.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -110,8 +112,8 @@ class Http2BufferWatermarksTest for (uint32_t idx = 0; idx < num_responses; ++idx) { responses.emplace_back(codec_client_->makeRequestWithBody(header_map, request_body_size)); if (!cluster_to_wait_for.empty()) { - test_server_->waitForGaugeEq( - absl::StrCat("cluster.", cluster_to_wait_for, ".upstream_rq_active"), idx + 1); + test_server_->waitForGauge( + absl::StrCat("cluster.", cluster_to_wait_for, ".upstream_rq_active"), Eq(idx + 1)); } } @@ -413,7 +415,7 @@ TEST_P(Http2BufferWatermarksTest, ShouldTrackAllocatedBytesToShadowUpstream) { } write_matcher_->setResumeWrites(); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_completed", num_requests); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_completed", Eq(num_requests)); } TEST_P(Http2BufferWatermarksTest, ShouldTrackAllocatedBytesToDownstream) { @@ -646,7 +648,8 @@ class TcpTunnelingWatermarkIntegrationTest : public Http2BufferWatermarksTest { write_matcher_->setWriteReturnsEgain(); ASSERT_TRUE(tcp_client_->write(std::string(524288, 'a'), false)); - test_server_->waitForCounterEq("tcp.tcp_stats.downstream_flow_control_paused_reading_total", 1); + test_server_->waitForCounter("tcp.tcp_stats.downstream_flow_control_paused_reading_total", + Eq(1)); tcp_client_->close(); } @@ -726,14 +729,14 @@ TEST_P(Http2OverloadManagerIntegrationTest, // Set the pressure so the overload action kicks in updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.overload_actions.reset_high_memory_stream.scale_percent", 62); + test_server_->waitForGauge( + "overload.envoy.overload_actions.reset_high_memory_stream.scale_percent", Eq(62)); // Wait for the proxy to notice and take action for the overload by only // resetting the largest stream. if (streamBufferAccounting()) { - test_server_->waitForCounterGe("http.config_test.downstream_rq_rx_reset", 1); - test_server_->waitForCounterGe("envoy.overload_actions.reset_high_memory_stream.count", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_rx_reset", Ge(1)); + test_server_->waitForCounter("envoy.overload_actions.reset_high_memory_stream.count", Ge(1)); EXPECT_TRUE(largest_request_response->waitForReset()); EXPECT_TRUE(largest_request_response->reset()); @@ -745,8 +748,8 @@ TEST_P(Http2OverloadManagerIntegrationTest, // Wait for the proxy to notice and take action for the overload. if (streamBufferAccounting()) { - test_server_->waitForCounterGe("http.config_test.downstream_rq_rx_reset", 2); - test_server_->waitForCounterGe("envoy.overload_actions.reset_high_memory_stream.count", 2); + test_server_->waitForCounter("http.config_test.downstream_rq_rx_reset", Ge(2)); + test_server_->waitForCounter("envoy.overload_actions.reset_high_memory_stream.count", Ge(2)); EXPECT_TRUE(medium_request_response->waitForReset()); EXPECT_TRUE(medium_request_response->reset()); @@ -755,8 +758,8 @@ TEST_P(Http2OverloadManagerIntegrationTest, // Reduce resource pressure updateResource(0.80); - test_server_->waitForGaugeEq( - "overload.envoy.overload_actions.reset_high_memory_stream.scale_percent", 0); + test_server_->waitForGauge( + "overload.envoy.overload_actions.reset_high_memory_stream.scale_percent", Eq(0)); // Resume writes to upstream, any request streams that survive can go through. write_matcher_->setResumeWrites(); @@ -826,26 +829,26 @@ TEST_P(Http2OverloadManagerIntegrationTest, // Set the pressure so the overload action kills largest response updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.overload_actions.reset_high_memory_stream.scale_percent", 62); + test_server_->waitForGauge( + "overload.envoy.overload_actions.reset_high_memory_stream.scale_percent", Eq(62)); if (streamBufferAccounting()) { - test_server_->waitForCounterGe("http.config_test.downstream_rq_rx_reset", 1); - test_server_->waitForCounterGe("envoy.overload_actions.reset_high_memory_stream.count", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_rx_reset", Ge(1)); + test_server_->waitForCounter("envoy.overload_actions.reset_high_memory_stream.count", Ge(1)); ASSERT_TRUE(upstream_request_for_largest_response->waitForReset()); } // Set the pressure so the overload action kills medium response updateResource(0.96); if (streamBufferAccounting()) { - test_server_->waitForCounterGe("http.config_test.downstream_rq_rx_reset", 2); - test_server_->waitForCounterGe("envoy.overload_actions.reset_high_memory_stream.count", 2); + test_server_->waitForCounter("http.config_test.downstream_rq_rx_reset", Ge(2)); + test_server_->waitForCounter("envoy.overload_actions.reset_high_memory_stream.count", Ge(2)); ASSERT_TRUE(upstream_request_for_medium_response->waitForReset()); } // Reduce resource pressure updateResource(0.80); - test_server_->waitForGaugeEq( - "overload.envoy.overload_actions.reset_high_memory_stream.scale_percent", 0); + test_server_->waitForGauge( + "overload.envoy.overload_actions.reset_high_memory_stream.scale_percent", Eq(0)); // Resume writes to downstream, any responses that survive can go through. write_matcher_->setResumeWrites(); @@ -903,17 +906,17 @@ class Http2DeferredProcessingIntegrationTest : public Http2BufferWatermarksTest codec_client_->sendData(request_encoder, 1000, false); // Wait for an upstream request to have our reach its buffer limit and read // disable. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_backed_up_total", 1); - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_paused_reading_total", - 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_backed_up_total", Eq(1)); + test_server_->waitForCounter("http.config_test.downstream_flow_control_paused_reading_total", + Eq(1)); codec_client_->sendData(request_encoder, 1000, true); // Verify codec received but is buffered as we're still read disabled. buffer_factory_->waitUntilTotalBufferedExceeds(2000); - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_resumed_reading_total", - 0); + test_server_->waitForCounter("http.config_test.downstream_flow_control_resumed_reading_total", + Eq(0)); EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(1, [](const StreamTee& tee) { absl::MutexLock l{tee.mutex_}; EXPECT_EQ(tee.request_body_.length(), 1000); @@ -955,9 +958,9 @@ class Http2DeferredProcessingIntegrationTest : public Http2BufferWatermarksTest // Wait for an upstream response to have our reach its buffer limit and read // disable. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_paused_reading_total", - 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_paused_reading_total", + Eq(1)); upstream_request->encodeData(500, true); ASSERT_TRUE(upstream_request->complete()); @@ -965,9 +968,9 @@ class Http2DeferredProcessingIntegrationTest : public Http2BufferWatermarksTest // Verify codec received and has buffered onStreamClose for upstream as we're still read // disabled. buffer_factory_->waitUntilTotalBufferedExceeds(1500); - test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", - 0); + test_server_->waitForGauge("cluster.cluster_0.http2.deferred_stream_close", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + Eq(0)); EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(1, [](const StreamTee& tee) { absl::MutexLock l{tee.mutex_}; EXPECT_EQ(tee.response_body_.length(), 1000); @@ -997,7 +1000,7 @@ class Http2DeferredProcessingIntegrationTest : public Http2BufferWatermarksTest for (int i = 0; i < num_requests; ++i) { responses.emplace_back(codec_client_->startRequest(default_request_headers_)); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", i + 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(i + 1)); } return responses; @@ -1037,17 +1040,17 @@ TEST_P(Http2DeferredProcessingIntegrationTest, CanBufferInDownstreamCodec) { codec_client_->sendData(request_encoder, 1000, false); // Wait for an upstream request to have our reach its buffer limit and read // disable. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_backed_up_total", 1); - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_paused_reading_total", - 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_backed_up_total", Eq(1)); + test_server_->waitForCounter("http.config_test.downstream_flow_control_paused_reading_total", + Eq(1)); codec_client_->sendData(request_encoder, 1000, true); // Verify codec received but is buffered as we're still read disabled. buffer_factory_->waitUntilTotalBufferedExceeds(2000); - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_resumed_reading_total", - 0); + test_server_->waitForCounter("http.config_test.downstream_flow_control_resumed_reading_total", + Eq(0)); EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(1, [](const StreamTee& tee) { absl::MutexLock l{tee.mutex_}; EXPECT_EQ(tee.request_body_.length(), 1000); @@ -1061,8 +1064,8 @@ TEST_P(Http2DeferredProcessingIntegrationTest, CanBufferInDownstreamCodec) { upstream_request->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); ASSERT_TRUE(response_decoder->waitForEndStream()); ASSERT_TRUE(upstream_request->complete()); - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_resumed_reading_total", - 1); + test_server_->waitForCounter("http.config_test.downstream_flow_control_resumed_reading_total", + Eq(1)); } TEST_P(Http2DeferredProcessingIntegrationTest, CanBufferInUpstreamCodec) { @@ -1082,15 +1085,16 @@ TEST_P(Http2DeferredProcessingIntegrationTest, CanBufferInUpstreamCodec) { // Wait for an upstream response to have our reach its buffer limit and read // disable. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_paused_reading_total", + Eq(1)); upstream_request->encodeData(500, false); // Verify codec received but is buffered as we're still read disabled. buffer_factory_->waitUntilTotalBufferedExceeds(1500); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", - 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + Eq(0)); EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(1, [](const StreamTee& tee) { absl::MutexLock l{tee.mutex_}; EXPECT_EQ(tee.response_body_.length(), 1000); @@ -1103,8 +1107,8 @@ TEST_P(Http2DeferredProcessingIntegrationTest, CanBufferInUpstreamCodec) { upstream_request->encodeData(1, true); ASSERT_TRUE(response_decoder->waitForEndStream()); ASSERT_TRUE(upstream_request->complete()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", - 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + Eq(1)); } TEST_P(Http2DeferredProcessingIntegrationTest, CanDeferOnStreamCloseForUpstream) { @@ -1124,15 +1128,16 @@ TEST_P(Http2DeferredProcessingIntegrationTest, CanDeferOnStreamCloseForUpstream) // Wait for an upstream response to have our reach its buffer limit and read // disable. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_paused_reading_total", + Eq(1)); upstream_request->encodeData(500, true); // Verify codec received and has buffered onStreamClose for upstream as we're still read disabled. buffer_factory_->waitUntilTotalBufferedExceeds(1500); - test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", - 0); + test_server_->waitForGauge("cluster.cluster_0.http2.deferred_stream_close", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + Eq(0)); EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(1, [](const StreamTee& tee) { absl::MutexLock l{tee.mutex_}; EXPECT_EQ(tee.response_body_.length(), 1000); @@ -1143,9 +1148,9 @@ TEST_P(Http2DeferredProcessingIntegrationTest, CanDeferOnStreamCloseForUpstream) ASSERT_TRUE(response_decoder->waitForEndStream()); ASSERT_TRUE(upstream_request->complete()); - test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 0); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", - 1); + test_server_->waitForGauge("cluster.cluster_0.http2.deferred_stream_close", Eq(0)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + Eq(1)); } TEST_P(Http2DeferredProcessingIntegrationTest, @@ -1166,8 +1171,9 @@ TEST_P(Http2DeferredProcessingIntegrationTest, // Wait for an upstream response to have our reach its buffer limit and read // disable. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_paused_reading_total", + Eq(1)); auto close_if_over_9000 = [](StreamTee& tee, Http::StreamEncoderFilterCallbacks* encoder_cbs) @@ -1186,9 +1192,9 @@ TEST_P(Http2DeferredProcessingIntegrationTest, // Verify codec received and has buffered onStreamClose for upstream as we're still read disabled. buffer_factory_->waitUntilTotalBufferedExceeds(9001); - test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", - 0); + test_server_->waitForGauge("cluster.cluster_0.http2.deferred_stream_close", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + Eq(0)); EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(1, [](const StreamTee& tee) { absl::MutexLock l{tee.mutex_}; EXPECT_EQ(tee.response_body_.length(), 9000); @@ -1199,9 +1205,9 @@ TEST_P(Http2DeferredProcessingIntegrationTest, ASSERT_TRUE(response_decoder->waitForReset()); ASSERT_TRUE(upstream_request->complete()); - test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 0); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", - 1); + test_server_->waitForGauge("cluster.cluster_0.http2.deferred_stream_close", Eq(0)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + Eq(1)); } TEST_P(Http2DeferredProcessingIntegrationTest, @@ -1225,17 +1231,18 @@ TEST_P(Http2DeferredProcessingIntegrationTest, // Wait for an upstream response to have our reach its buffer limit and read // disable. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_paused_reading_total", + Eq(1)); upstream_request->encodeData(500, true); ASSERT_TRUE(upstream_request->complete()); // Verify codec received and has buffered onStreamClose for upstream as we're still read disabled. buffer_factory_->waitUntilTotalBufferedExceeds(1500); - test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", - 0); + test_server_->waitForGauge("cluster.cluster_0.http2.deferred_stream_close", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + Eq(0)); EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(1, [](const StreamTee& tee) { absl::MutexLock l{tee.mutex_}; EXPECT_EQ(tee.response_body_.length(), 1000); @@ -1243,10 +1250,10 @@ TEST_P(Http2DeferredProcessingIntegrationTest, // Downstream sends a RST, we should clean up the buffered upstream. codec_client_->sendReset(request_encoder); - test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 0); + test_server_->waitForGauge("cluster.cluster_0.http2.deferred_stream_close", Eq(0)); // Resetting the upstream stream doesn't increment this count. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", - 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + Eq(0)); } TEST_P(Http2DeferredProcessingIntegrationTest, CanRoundRobinBetweenStreams) { @@ -1260,24 +1267,24 @@ TEST_P(Http2DeferredProcessingIntegrationTest, CanRoundRobinBetweenStreams) { codec_client_ = makeHttpConnection(lookupPort("http")); const uint32_t num_requests = 4; auto request_response_pairs = startRequests(num_requests); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 4); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(4)); writeToRequests(request_response_pairs, {2500, 2500, 2500, 2510}, false); // Wait for an upstream request to have our reach its buffer limit and read // disable. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_backed_up_total", 4); - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_paused_reading_total", - 4); - test_server_->waitForCounterGe("http.config_test.downstream_cx_rx_bytes_total", 10000); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_backed_up_total", Eq(4)); + test_server_->waitForCounter("http.config_test.downstream_flow_control_paused_reading_total", + Eq(4)); + test_server_->waitForCounter("http.config_test.downstream_cx_rx_bytes_total", Ge(10000)); // Subsequent data should be buffered at downstream codec for deferred // processing. writeToRequests(request_response_pairs, {2500, 2500, 2500, 2500}, false); // Verify codec received but is buffered as we're still read disabled. - test_server_->waitForCounterGe("http.config_test.downstream_cx_rx_bytes_total", 20000); - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_resumed_reading_total", - 0); + test_server_->waitForCounter("http.config_test.downstream_cx_rx_bytes_total", Ge(20000)); + test_server_->waitForCounter("http.config_test.downstream_flow_control_resumed_reading_total", + Eq(0)); // Track the order of when streams invoke decodeData to understand the // ordering in deferred processing. @@ -1299,8 +1306,8 @@ TEST_P(Http2DeferredProcessingIntegrationTest, CanRoundRobinBetweenStreams) { // Allow draining to the upstream, this will trigger deferred processing. write_matcher_->setResumeWrites(); - test_server_->waitForCounterGe("http.config_test.downstream_flow_control_resumed_reading_total", - 4); + test_server_->waitForCounter("http.config_test.downstream_flow_control_resumed_reading_total", + Ge(4)); std::vector upstream_requests; waitForNextUpstreamConnection(std::vector{0}, TestUtility::DefaultTimeout, @@ -1320,7 +1327,7 @@ TEST_P(Http2DeferredProcessingIntegrationTest, CanRoundRobinBetweenStreams) { waitForNumTurns(turns, mu, 8); writeToRequests(request_response_pairs, {2500, 2500, 2500, 2500}, true); - test_server_->waitForCounterGe("http.config_test.downstream_cx_rx_bytes_total", 40000); + test_server_->waitForCounter("http.config_test.downstream_cx_rx_bytes_total", Ge(40000)); // Allow draining to upstream, this will trigger second round of deferred // processing. Check drain to upstream. @@ -1360,7 +1367,7 @@ TEST_P(Http2DeferredProcessingIntegrationTest, RoundRobinWithStreamsExiting) { const uint32_t response_body_size = 14000; auto responses = sendRequests(num_requests, request_body_size, response_body_size); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 3); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(3)); // Tracking turns and block writes to downstream on end stream. absl::Mutex mu; std::vector turns; @@ -1390,16 +1397,18 @@ TEST_P(Http2DeferredProcessingIntegrationTest, RoundRobinWithStreamsExiting) { upstream_requests.emplace_back(std::move(upstream_request_)); upstream_requests[i]->encodeHeaders(default_response_headers_, false); upstream_requests[i]->encodeData(4000, false); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_rx_bytes_total", 4000 * (i + 1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", + Ge(4000 * (i + 1))); } - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_paused_reading_total", 3); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_paused_reading_total", + Eq(3)); // Data that should be buffered by the upstream codec. upstream_requests[0]->encodeData(4000, false); upstream_requests[1]->encodeData(10000, true); upstream_requests[2]->encodeData(10000, true); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_rx_bytes_total", 36000); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", Ge(36000)); // Enable writes, check drainage. write_matcher_->setResumeWrites(); @@ -1438,7 +1447,7 @@ TEST_P(Http2DeferredProcessingIntegrationTest, RoundRobinWithStreamsExiting) { // process. We expect that the 3rd stream, which didn't get a turn above will // get to go and exit. upstream_requests[0]->encodeData(6000, true); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_rx_bytes_total", 42000); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", Ge(42000)); write_matcher_->setResumeWrites(); waitForNumTurns(turns, mu, 6); @@ -1493,7 +1502,7 @@ TEST_P(Http2DeferredProcessingIntegrationTest, ChunkProcessesStreams) { codec_client_ = makeHttpConnection(lookupPort("http")); const uint32_t num_requests = 3; auto request_response_pairs = startRequests(num_requests); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 3); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(3)); // Track which stream processed what amount of the request data at a // particular point. @@ -1523,21 +1532,21 @@ TEST_P(Http2DeferredProcessingIntegrationTest, ChunkProcessesStreams) { // Wait for an upstream request to have our reach its buffer limit and read // disable. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_backed_up_total", 3); - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_paused_reading_total", - 3); - test_server_->waitForCounterGe("http.config_test.downstream_cx_rx_bytes_total", 85000); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_backed_up_total", Eq(3)); + test_server_->waitForCounter("http.config_test.downstream_flow_control_paused_reading_total", + Eq(3)); + test_server_->waitForCounter("http.config_test.downstream_cx_rx_bytes_total", Ge(85000)); // Only the first stream should have drained its data, the second streams data // is stuck in the connection output buffer. This read-disables all the // streams flowing to that connection, allowing us to queue additional // data for the third stream. write_matcher_->setResumeWrites(); - test_server_->waitForCounterGe("http.config_test.downstream_flow_control_resumed_reading_total", - 3); - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_paused_reading_total", - 6); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_tx_bytes_total", 10000); + test_server_->waitForCounter("http.config_test.downstream_flow_control_resumed_reading_total", + Ge(3)); + test_server_->waitForCounter("http.config_test.downstream_flow_control_paused_reading_total", + Eq(6)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_tx_bytes_total", Ge(10000)); std::vector upstream_requests; waitForNextUpstreamConnection(std::vector{0}, TestUtility::DefaultTimeout, @@ -1553,7 +1562,7 @@ TEST_P(Http2DeferredProcessingIntegrationTest, ChunkProcessesStreams) { // Should be able to write to third stream with data. Buffer::OwnedImpl data(std::string(65535, 'C')); codec_client_->sendData(request_response_pairs[2].first, 65535, true); - test_server_->waitForCounterGe("http.config_test.downstream_cx_rx_bytes_total", 150000); + test_server_->waitForCounter("http.config_test.downstream_cx_rx_bytes_total", Ge(150000)); // Enabling again should cause us to chunk this write to third stream. write_matcher_->setResumeWrites(); diff --git a/test/integration/cds_integration_test.cc b/test/integration/cds_integration_test.cc index 4b84e83e936df..f0a6e416629ef 100644 --- a/test/integration/cds_integration_test.cc +++ b/test/integration/cds_integration_test.cc @@ -19,6 +19,8 @@ #include "gtest/gtest.h" using testing::AssertionResult; +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -107,7 +109,7 @@ class CdsIntegrationTest : public Grpc::DeltaSotwDeferredClustersIntegrationPara // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse describing cluster_1 that we sent. // 2 because the statically specified CDS server itself counts as a cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); // Wait for our statically specified listener to become ready, and register its port in the // test framework's downstream listener port map. @@ -174,16 +176,16 @@ TEST_P(CdsIntegrationTest, CdsClusterUpDownUp) { initialize(); if (useDeferredCluster()) { - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 0); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(0)); } else { - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 2); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(2)); } testRouterHeaderOnlyRequestAndResponse(nullptr, UpstreamIndex1, "/cluster1"); - test_server_->waitForCounterGe("cluster_manager.cluster_added", 1); + test_server_->waitForCounter("cluster_manager.cluster_added", Ge(1)); if (useDeferredCluster()) { - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 1); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(1)); } else { EXPECT_EQ( test_server_->gauge("thread_local_cluster_manager.worker_0.clusters_inflated")->value(), 2); @@ -194,12 +196,12 @@ TEST_P(CdsIntegrationTest, CdsClusterUpDownUp) { sendClusterDiscoveryResponse({}, {}, {ClusterName1}, "42"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); if (useDeferredCluster()) { - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 0); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(0)); } else { - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 1); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(1)); } // Now that cluster_1 is gone, the listener (with its routing to cluster_1) should 503. @@ -219,18 +221,18 @@ TEST_P(CdsIntegrationTest, CdsClusterUpDownUp) { // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse describing cluster_1 that we sent. Again, 2 includes CDS server. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(2)); if (useDeferredCluster()) { EXPECT_EQ( test_server_->gauge("thread_local_cluster_manager.worker_0.clusters_inflated")->value(), 0); } else { - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 2); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(2)); } // Does *not* call our initialize(). testRouterHeaderOnlyRequestAndResponse(nullptr, UpstreamIndex1, "/cluster1"); if (useDeferredCluster()) { - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 1); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(1)); } cleanupUpstreamAndDownstream(); @@ -239,7 +241,7 @@ TEST_P(CdsIntegrationTest, CdsClusterUpDownUp) { // Make sure that clusters won't create new connections on teardown. TEST_P(CdsIntegrationTest, CdsClusterTeardownWhileConnecting) { initialize(); - test_server_->waitForCounterGe("cluster_manager.cluster_added", 1); + test_server_->waitForCounter("cluster_manager.cluster_added", Ge(1)); test_server_->waitForCounterExists("cluster.cluster_1.upstream_cx_total"); Stats::CounterSharedPtr cx_counter = test_server_->counter("cluster.cluster_1.upstream_cx_total"); // Confirm no upstream connection is attempted so far. @@ -258,7 +260,7 @@ TEST_P(CdsIntegrationTest, CdsClusterTeardownWhileConnecting) { sendClusterDiscoveryResponse({}, {}, {ClusterName1}, "42"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); codec_client_->sendReset(encoder_decoder.first); cleanupUpstreamAndDownstream(); @@ -276,7 +278,7 @@ class DeferredCreationClusterStatsTest : public CdsIntegrationTest { enable_deferred_creation_stats); }); CdsIntegrationTest::initialize(); - test_server_->waitForCounterGe("cluster_manager.cluster_added", 1); + test_server_->waitForCounter("cluster_manager.cluster_added", Ge(1)); } void sendRequestToClusterAndWaitForResponse() { @@ -299,8 +301,8 @@ class DeferredCreationClusterStatsTest : public CdsIntegrationTest { void removeClusters(const std::vector& removed) { uint64_t cluster_removed = test_server_->counter("cluster_manager.cluster_removed")->value(); sendClusterDiscoveryResponse({}, {}, removed, "42"); - test_server_->waitForCounterGe("cluster_manager.cluster_removed", - cluster_removed + removed.size()); + test_server_->waitForCounter("cluster_manager.cluster_removed", + Ge(cluster_removed + removed.size())); } }; @@ -323,15 +325,15 @@ TEST_P(DeferredCreationClusterStatsTest, EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 1); updateCluster(); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 2); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(2)); // cluster_1 traffic stats not lost. EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 1); removeClusters({ClusterName1}); // update_success is 3: initialize(), update cluster1. and remove cluster1. - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 3); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(3)); sendClusterDiscoveryResponse({cluster2_}, {cluster2_}, {}, "43"); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 4); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(4)); EXPECT_EQ(test_server_->counter("cluster_manager.cluster_added")->value(), 3); // Now the cluster_1 stats are gone, as well as the lazy init wrapper. test_server_->waitForCounterNonexistent("cluster.cluster_1.upstream_cx_total", @@ -355,15 +357,15 @@ TEST_P(DeferredCreationClusterStatsTest, EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 1); updateCluster(); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 2); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(2)); // cluster_1 traffic stats not lost. EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 1); removeClusters({ClusterName1}); // update_success is 3: initialize(), update cluster1. and remove cluster1. - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 3); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(3)); sendClusterDiscoveryResponse({cluster2_}, {cluster2_}, {}, "43"); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 4); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(4)); EXPECT_EQ(test_server_->counter("cluster_manager.cluster_added")->value(), 3); // Now the cluster_1 stats are gone. test_server_->waitForCounterNonexistent("cluster.cluster_1.upstream_cx_total", @@ -384,9 +386,9 @@ TEST_P(DeferredCreationClusterStatsTest, EXPECT_EQ(test_server_->gauge("cluster.cluster_1.ClusterTrafficStats.initialized")->value(), 1); EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 1); removeClusters({ClusterName1}); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 2); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(2)); sendClusterDiscoveryResponse({cluster2_}, {cluster2_}, {}, "43"); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 3); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(3)); EXPECT_EQ(test_server_->counter("cluster_manager.cluster_added")->value(), 3); // No cluster_2 traffic stats. EXPECT_EQ(test_server_->gauge("cluster.cluster_2.ClusterTrafficStats.initialized")->value(), 0); @@ -397,7 +399,7 @@ TEST_P(DeferredCreationClusterStatsTest, EXPECT_EQ(test_server_->gauge("cluster.cluster_1.ClusterTrafficStats.initialized"), nullptr); // Now add cluster1 back. updateCluster(); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 4); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(4)); // Now the cluster_1.ClusterTrafficStats.initialized gauge is 0, since it didn't see previous // stats. EXPECT_EQ(test_server_->gauge("cluster.cluster_1.ClusterTrafficStats.initialized")->value(), 0); @@ -419,9 +421,9 @@ TEST_P(DeferredCreationClusterStatsTest, // cluster_1 trafficStats updated. EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 1); removeClusters({ClusterName1}); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 2); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(2)); sendClusterDiscoveryResponse({cluster2_}, {cluster2_}, {}, "43"); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 3); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(3)); EXPECT_EQ(test_server_->counter("cluster_manager.cluster_added")->value(), 3); // cluster_2 traffic stats created. EXPECT_EQ(test_server_->counter("cluster.cluster_2.upstream_cx_total")->value(), 0); @@ -430,7 +432,7 @@ TEST_P(DeferredCreationClusterStatsTest, TestUtility::DefaultTimeout); // Now add cluster1 back. updateCluster(); - test_server_->waitForCounterGe("cluster_manager.cds.update_success", 4); + test_server_->waitForCounter("cluster_manager.cds.update_success", Ge(4)); EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 0); sendRequestToClusterAndWaitForResponse(); EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 1); @@ -440,14 +442,14 @@ TEST_P(DeferredCreationClusterStatsTest, TEST_P(CdsIntegrationTest, CdsClusterWithThreadAwareLbCycleUpDownUp) { // Calls our initialize(), which includes establishing a listener, route, and cluster. testRouterHeaderOnlyRequestAndResponse(nullptr, UpstreamIndex1, "/cluster1"); - test_server_->waitForCounterGe("cluster_manager.cluster_added", 1); + test_server_->waitForCounter("cluster_manager.cluster_added", Ge(1)); // Tell Envoy that cluster_1 is gone. EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "55", {}, {}, {})); sendClusterDiscoveryResponse({}, {}, {ClusterName1}, "42"); // Make sure that Envoy's ClusterManager has made use of the DiscoveryResponse that says // cluster_1 is gone. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); // Update cluster1_ to use MAGLEV load balancer policy. cluster1_ = ConfigHelper::buildStaticCluster( @@ -483,7 +485,7 @@ TEST_P(CdsIntegrationTest, TwoClusters) { sendDiscoveryResponse( Config::TestTypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster2_}, {}, "42"); // The '3' includes the fake CDS server. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); // A request for cluster_2 should be fine. testRouterHeaderOnlyRequestAndResponse(nullptr, UpstreamIndex2, "/cluster2"); @@ -495,7 +497,7 @@ TEST_P(CdsIntegrationTest, TwoClusters) { sendClusterDiscoveryResponse({cluster2_}, {}, {ClusterName1}, "43"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); // Even with cluster_1 gone, a request for cluster_2 should be fine. testRouterHeaderOnlyRequestAndResponse(nullptr, UpstreamIndex2, "/cluster2"); @@ -509,7 +511,7 @@ TEST_P(CdsIntegrationTest, TwoClusters) { // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse describing cluster_1 that we sent. Again, 3 includes CDS server. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); // Does *not* call our initialize(). testRouterHeaderOnlyRequestAndResponse(nullptr, UpstreamIndex1, "/cluster1"); @@ -535,10 +537,10 @@ TEST_P(CdsIntegrationTest, TwoClustersAndRedirects) { sendDiscoveryResponse( Config::TestTypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster2_}, {}, "42"); // The '3' includes the fake CDS server. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); // Tell Envoy that cluster_1 is gone. sendClusterDiscoveryResponse({cluster2_}, {}, {ClusterName1}, "43"); - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); default_request_headers_.setPath("/cluster2"); @@ -596,7 +598,7 @@ TEST_P(CdsIntegrationTest, VersionsRememberedAfterReconnect) { sendDeltaDiscoveryResponse( Config::TestTypeUrl::get().Cluster, {cluster2_}, {}, "42"); // The '3' includes the fake CDS server. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); // A request for cluster_1 should be fine. testRouterHeaderOnlyRequestAndResponse(nullptr, UpstreamIndex1, "/cluster1"); @@ -677,14 +679,14 @@ TEST_P(CdsIntegrationTest, CdsClusterDownWithLotsOfIdleConnections) { EXPECT_EQ("200", responses[i]->headers().getStatusValue()); } - test_server_->waitForCounterGe("cluster_manager.cluster_added", 1); + test_server_->waitForCounter("cluster_manager.cluster_added", Ge(1)); // Tell Envoy that cluster_1 is gone. Envoy will try to close all idle connections EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "55", {}, {}, {})); sendClusterDiscoveryResponse({}, {}, {ClusterName1}, "42"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); // If we made it this far then everything is ok. for (int i = 0; i < num_requests; ++i) { @@ -730,7 +732,7 @@ TEST_P(CdsIntegrationTest, DISABLED_CdsClusterDownWithLotsOfConnectingConnection ->set_seconds(600); }); initialize(); - test_server_->waitForCounterGe("cluster_manager.cluster_added", 1); + test_server_->waitForCounter("cluster_manager.cluster_added", Ge(1)); std::vector responses; codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); // Stop upstream at UpstreamIndex1 dispatcher, to prevent it from accepting TCP connections. @@ -748,14 +750,14 @@ TEST_P(CdsIntegrationTest, DISABLED_CdsClusterDownWithLotsOfConnectingConnection } // Wait for Envoy to try to establish all expected connections - test_server_->waitForCounterEq("cluster.cluster_1.upstream_cx_total", num_requests); + test_server_->waitForCounter("cluster.cluster_1.upstream_cx_total", Eq(num_requests)); // Tell Envoy that cluster_1 is gone. Envoy will try to close all pending connections EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "55", {}, {}, {})); sendClusterDiscoveryResponse({}, {}, {ClusterName1}, "42"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. - test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); + test_server_->waitForCounter("cluster_manager.cluster_removed", Ge(1)); cleanupUpstreamAndDownstream(); ASSERT_TRUE(codec_client_->waitForDisconnect()); diff --git a/test/integration/circuit_breakers_integration_test.cc b/test/integration/circuit_breakers_integration_test.cc index fd093e53944f6..c9a86fcb51962 100644 --- a/test/integration/circuit_breakers_integration_test.cc +++ b/test/integration/circuit_breakers_integration_test.cc @@ -2,6 +2,8 @@ #include "test/integration/http_protocol_integration.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -51,13 +53,13 @@ TEST_P(CircuitBreakersIntegrationTest, CircuitBreakersWithOutlierDetection) { auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 0); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_pending_active", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(0)); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_pending_active", Eq(0)); ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ("503", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_503", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_503", Ge(1)); EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_active_overflow")->value(), 1); EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_pending_overflow")->value(), 0); @@ -98,13 +100,13 @@ TEST_P(CircuitBreakersIntegrationTest, CircuitBreakerRuntime) { auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 0); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_pending_active", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(0)); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_pending_active", Eq(0)); ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ("503", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_503", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_503", Ge(1)); EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_active_overflow")->value(), 1); EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_pending_overflow")->value(), 0); @@ -167,13 +169,13 @@ TEST_P(CircuitBreakersIntegrationTest, CircuitBreakerRuntimeProto) { auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 0); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_pending_active", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(0)); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_pending_active", Eq(0)); ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ("503", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_503", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_503", Ge(1)); EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_active_overflow")->value(), 1); EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_rq_pending_overflow")->value(), 0); diff --git a/test/integration/cluster_http_protocol_options_integration_test.cc b/test/integration/cluster_http_protocol_options_integration_test.cc index 131828435a4e7..dbddc27ee5590 100644 --- a/test/integration/cluster_http_protocol_options_integration_test.cc +++ b/test/integration/cluster_http_protocol_options_integration_test.cc @@ -10,6 +10,7 @@ #include "test/integration/http_integration.h" #include "test/test_common/test_runtime.h" +using testing::Ge; namespace Envoy { namespace { @@ -106,7 +107,7 @@ class ClusterHttpProtocolOptionsIntegrationTest EXPECT_EQ(10U, response->body().size()); // Wait for mirror request to complete. - test_server_->waitForCounterGe("cluster.cluster_1.internal.upstream_rq_completed", 1); + test_server_->waitForCounter("cluster.cluster_1.internal.upstream_rq_completed", Ge(1)); // Verify both clusters received requests. upstream_headers_ = @@ -232,7 +233,7 @@ TEST_P(ClusterHttpProtocolOptionsIntegrationTest, ClusterMirroringWithHeaderMuta EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_1.internal.upstream_rq_completed", 1); + test_server_->waitForCounter("cluster.cluster_1.internal.upstream_rq_completed", Ge(1)); upstream_headers_ = reinterpret_cast(fake_upstreams_[0].get())->lastRequestHeaders(); @@ -311,7 +312,7 @@ TEST_P(ClusterHttpProtocolOptionsIntegrationTest, ClusterMirroringDisabledShadow EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_1.internal.upstream_rq_completed", 1); + test_server_->waitForCounter("cluster.cluster_1.internal.upstream_rq_completed", Ge(1)); upstream_headers_ = reinterpret_cast(fake_upstreams_[0].get())->lastRequestHeaders(); @@ -358,7 +359,7 @@ TEST_P(ClusterHttpProtocolOptionsIntegrationTest, PrecedenceRouteOnlyMirroring) EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_1.internal.upstream_rq_completed", 1); + test_server_->waitForCounter("cluster.cluster_1.internal.upstream_rq_completed", Ge(1)); cleanupUpstreamAndDownstream(); @@ -398,7 +399,7 @@ TEST_P(ClusterHttpProtocolOptionsIntegrationTest, PrecedenceClusterOnlyMirroring EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_1.internal.upstream_rq_completed", 1); + test_server_->waitForCounter("cluster.cluster_1.internal.upstream_rq_completed", Ge(1)); cleanupUpstreamAndDownstream(); @@ -458,7 +459,7 @@ TEST_P(ClusterHttpProtocolOptionsIntegrationTest, PrecedenceClusterOverridesRout EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_2.internal.upstream_rq_completed", 1); + test_server_->waitForCounter("cluster.cluster_2.internal.upstream_rq_completed", Ge(1)); cleanupUpstreamAndDownstream(); @@ -524,8 +525,8 @@ TEST_P(ClusterHttpProtocolOptionsIntegrationTest, EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_3.internal.upstream_rq_completed", 1); - test_server_->waitForCounterGe("cluster.cluster_4.internal.upstream_rq_completed", 1); + test_server_->waitForCounter("cluster.cluster_3.internal.upstream_rq_completed", Ge(1)); + test_server_->waitForCounter("cluster.cluster_4.internal.upstream_rq_completed", Ge(1)); cleanupUpstreamAndDownstream(); diff --git a/test/integration/custom_cluster_integration_test.cc b/test/integration/custom_cluster_integration_test.cc index 1ba5c02c5b07b..8830fe7b63392 100644 --- a/test/integration/custom_cluster_integration_test.cc +++ b/test/integration/custom_cluster_integration_test.cc @@ -50,7 +50,7 @@ class CustomClusterIntegrationTest : public testing::TestWithParammutable_cluster_type()->CopyFrom(cluster_type); }); HttpIntegrationTest::initialize(); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 1); + test_server_->waitForGauge("cluster_manager.active_clusters", testing::Ge(1)); } Network::Address::IpVersion ipVersion() const { return version_; } diff --git a/test/integration/cx_limit_integration_test.cc b/test/integration/cx_limit_integration_test.cc index f6609713e3c8c..b196abed441cf 100644 --- a/test/integration/cx_limit_integration_test.cc +++ b/test/integration/cx_limit_integration_test.cc @@ -11,6 +11,7 @@ #include "gtest/gtest.h" +using testing::Eq; namespace Envoy { namespace { @@ -112,7 +113,7 @@ class ConnectionLimitIntegrationTest : public testing::TestWithParamwaitForCounterEq(counter_prefix + check_stat, 1); + test_server_->waitForCounter(counter_prefix + check_stat, Eq(1)); for (auto& tcp_client : tcp_clients) { tcp_client->close(); @@ -230,10 +231,10 @@ TEST_P(ConnectionLimitIntegrationTest, TestGlobalLimitOptOut) { const std::string counter_prefix = (isV4 ? "listener.127.0.0.1_0." : "listener.[__1]_0."); // listener_0 does not hit any connection limits - test_server_->waitForCounterEq(counter_prefix + "downstream_global_cx_overflow", 0); - test_server_->waitForCounterEq(counter_prefix + "downstream_cx_overflow", 0); - test_server_->waitForCounterEq("listener.admin.downstream_global_cx_overflow", 0); - test_server_->waitForCounterEq("listener.admin.downstream_cx_overflow", 0); + test_server_->waitForCounter(counter_prefix + "downstream_global_cx_overflow", Eq(0)); + test_server_->waitForCounter(counter_prefix + "downstream_cx_overflow", Eq(0)); + test_server_->waitForCounter("listener.admin.downstream_global_cx_overflow", Eq(0)); + test_server_->waitForCounter("listener.admin.downstream_cx_overflow", Eq(0)); for (auto& tcp_client : tcp_clients) { tcp_client->close(); @@ -282,8 +283,8 @@ TEST_P(ConnectionLimitIntegrationTest, TestListenerLimitWithGlobalOptOut) { const std::string counter_prefix = (isV4 ? "listener.127.0.0.1_0." : "listener.[__1]_0."); // listener_0 does hits the listener connection limit - test_server_->waitForCounterEq(counter_prefix + "downstream_global_cx_overflow", 0); - test_server_->waitForCounterEq(counter_prefix + "downstream_cx_overflow", 1); + test_server_->waitForCounter(counter_prefix + "downstream_global_cx_overflow", Eq(0)); + test_server_->waitForCounter(counter_prefix + "downstream_cx_overflow", Eq(1)); for (auto& tcp_client : tcp_clients) { tcp_client->close(); diff --git a/test/integration/drain_close_integration_test.cc b/test/integration/drain_close_integration_test.cc index 9232d3d5c29a8..10626a98385ff 100644 --- a/test/integration/drain_close_integration_test.cc +++ b/test/integration/drain_close_integration_test.cc @@ -5,6 +5,7 @@ namespace Envoy { namespace { using DrainCloseIntegrationTest = HttpProtocolIntegrationTest; +using testing::Eq; TEST_P(DrainCloseIntegrationTest, DrainCloseGradual) { autonomous_upstream_ = true; @@ -124,7 +125,7 @@ TEST_P(DrainCloseIntegrationTest, AdminGracefulDrain) { lookupPort("admin"), "POST", "/drain_listeners", "", downstreamProtocol(), version_); EXPECT_EQ(admin_response->headers().Status()->value().getStringView(), "200"); - test_server_->waitForCounterEq("listener_manager.listener_stopped", 1); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(1)); ASSERT_TRUE(waitForPortAvailable(http_port)); } @@ -163,7 +164,7 @@ TEST_P(DrainCloseIntegrationTest, RepeatedAdminGracefulDrain) { lookupPort("admin"), "POST", "/drain_listeners", "", downstreamProtocol(), version_); EXPECT_EQ(admin_response->headers().Status()->value().getStringView(), "200"); - test_server_->waitForCounterEq("listener_manager.listener_stopped", 1); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(1)); ASSERT_TRUE(waitForPortAvailable(http_port)); } @@ -217,7 +218,7 @@ TEST_P(DrainCloseIntegrationTest, AdminGracefulDrainSkipExit) { lookupPort("admin"), "POST", "/drain_listeners", "", downstreamProtocol(), version_); EXPECT_EQ(admin_response->headers().Status()->value().getStringView(), "200"); - test_server_->waitForCounterEq("listener_manager.listener_stopped", 1); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(1)); ASSERT_TRUE(waitForPortAvailable(http_port)); } diff --git a/test/integration/eds_integration_test.cc b/test/integration/eds_integration_test.cc index e7484ab42aa81..40a143d58c8d4 100644 --- a/test/integration/eds_integration_test.cc +++ b/test/integration/eds_integration_test.cc @@ -14,6 +14,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -206,7 +208,7 @@ class EdsIntegrationTest } cds_helper_.setCds({cluster_}); initialize(); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); } void initializeTest(bool http_active_hc) { initializeTest(http_active_hc, nullptr); } @@ -223,7 +225,7 @@ class EdsIntegrationTest // Check deferred. if (deferred_cluster_creation_) { - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 0); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(0)); } BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( lookupPort("http"), "GET", "/cluster_0", "", downstream_protocol_, version_, "foo.com"); @@ -274,14 +276,14 @@ TEST_P(EdsIntegrationTest, Http2HcClusterRewarming) { // Wait for the first HC and verify the host is healthy. This should warm the initial cluster. waitForNextUpstreamRequest(); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); // Trigger a CDS update. This should cause a new cluster to require warming, blocked on the host // being health checked. cluster_.mutable_circuit_breakers()->add_thresholds()->mutable_max_connections()->set_value(100); cds_helper_.setCds({cluster_}); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster_manager.warming_clusters")->value()); // We need to do a bunch of work to get a hold of second hc connection. @@ -299,7 +301,7 @@ TEST_P(EdsIntegrationTest, Http2HcClusterRewarming) { // Respond with a health check. This will cause the previous cluster to be destroyed inline as // part of processing the response. upstream_request->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "503"}}, true); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); EXPECT_EQ(0, test_server_->gauge("cluster_manager.warming_clusters")->value()); // Since the second connection is not managed by the integration test base we need to close it @@ -324,7 +326,7 @@ TEST_P(EdsIntegrationTest, EndpointDisableActiveHCFlag) { waitForNextUpstreamRequest(); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); @@ -362,7 +364,7 @@ TEST_P(EdsIntegrationTest, RemoveAfterHcFail) { // Wait for the first HC and verify the host is healthy. waitForNextUpstreamRequest(); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); // Clear out the host and verify the host is still healthy. @@ -375,7 +377,7 @@ TEST_P(EdsIntegrationTest, RemoveAfterHcFail) { waitForNextUpstreamRequest(); upstream_request_->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "503"}, {"connection", "close"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 0); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(0)); EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_total")->value()); } @@ -414,8 +416,8 @@ TEST_P(EdsIntegrationTest, LocalityUpdateActiveHealthStatusReuse) { // Wait for the first HC to arrive to the upstream. waitForNextUpstreamRequest(); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForCounterEq("cluster.cluster_0.health_check.attempt", 1); - test_server_->waitForCounterGe("cluster.cluster_0.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_0.health_check.attempt", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.health_check.success", Ge(1)); cleanupUpstreamAndDownstream(); // After the first hc there should be no upstream healthy host, and any @@ -430,7 +432,7 @@ TEST_P(EdsIntegrationTest, LocalityUpdateActiveHealthStatusReuse) { response.reset(); // Wait for scheduled updates (update-merge-window). - test_server_->waitForCounterEq("cluster_manager.cluster_updated_via_merge", 1); + test_server_->waitForCounter("cluster_manager.cluster_updated_via_merge", Eq(1)); // Now the upstream should be healthy. // There should only be 1 endpoint, and it should be healthy (according to EDS). @@ -457,7 +459,7 @@ TEST_P(EdsIntegrationTest, LocalityUpdateActiveHealthStatusReuse) { // There should be 3 membership changes (+2 compared to the previous, as the // memberships of priority 0 and 1 have changed). - test_server_->waitForCounterEq("cluster.cluster_0.membership_change", 3); + test_server_->waitForCounter("cluster.cluster_0.membership_change", Eq(3)); // There should still be be 1 endpoint, and it should stay healthy. EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); @@ -480,7 +482,7 @@ TEST_P(EdsIntegrationTest, LocalityUpdateActiveHealthStatusReuse) { cleanupUpstreamAndDownstream(); // There should be 3 membership changes (+1 compared to the previous, as the // membership of the first locality has changed). - test_server_->waitForCounterEq("cluster.cluster_0.membership_change", 4); + test_server_->waitForCounter("cluster.cluster_0.membership_change", Eq(4)); // There should still be be 1 endpoint, and it should stay healthy. EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); @@ -503,13 +505,13 @@ TEST_P(EdsIntegrationTest, FinishWarmingIgnoreHealthCheck) { // being health checked. cluster_.mutable_circuit_breakers()->add_thresholds()->mutable_max_connections()->set_value(100); cds_helper_.setCds({cluster_}); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(1)); // Clear out the host before the health check finishes (regardless of success/error/timeout) and // ensure that warming_clusters goes to 0 to avoid a permanent warming state. options.total_endpoints = 0; setEndpoints(options, true, false); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); } // Verifies that endpoints are ignored until health checked when configured to. @@ -528,7 +530,7 @@ TEST_P(EdsIntegrationTest, EndpointWarmingSuccessfulHc) { // The other endpoint should still be excluded. waitForNextUpstreamRequest(0); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_excluded", 0); + test_server_->waitForGauge("cluster.cluster_0.membership_excluded", Eq(0)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); } @@ -550,7 +552,7 @@ TEST_P(EdsIntegrationTest, EndpointWarmingFailedHc) { // The other endpoint should still be excluded. waitForNextUpstreamRequest(0); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "503"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_excluded", 0); + test_server_->waitForGauge("cluster.cluster_0.membership_excluded", Eq(0)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); } @@ -735,14 +737,14 @@ TEST_P(EdsIntegrationTest, DataplaneTrafficForDeferredCluster) { setEndpoints(options); // Check deferred. - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 0); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(0)); BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( lookupPort("http"), "GET", "/cluster_0", "", downstream_protocol_, version_, "foo.com"); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); cleanupUpstreamAndDownstream(); - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 1); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(1)); const auto& active_cluster_map = test_server_->server().clusterManager().clusters().active_clusters_; @@ -772,13 +774,13 @@ TEST_P(EdsIntegrationTest, DataplaneTrafficAfterEdsUpdateOfInitializedCluster) { options.healthy_endpoints = 1; setEndpoints(options); - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 0); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(0)); BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( lookupPort("http"), "GET", "/cluster_0", "", downstream_protocol_, version_, "foo.com"); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); cleanupUpstreamAndDownstream(); - test_server_->waitForGaugeEq("thread_local_cluster_manager.worker_0.clusters_inflated", 1); + test_server_->waitForGauge("thread_local_cluster_manager.worker_0.clusters_inflated", Eq(1)); const auto& active_cluster_map = test_server_->server().clusterManager().clusters().active_clusters_; { @@ -824,7 +826,7 @@ TEST_P(EdsIntegrationTest, LoadBalancerRejectsEndpoints) { options.healthy_endpoints = 4; options.load_balancing_weight = UINT32_MAX - 1; setEndpoints(options, true, false); - test_server_->waitForCounterGe("cluster.cluster_0.update_rejected", 1); + test_server_->waitForCounter("cluster.cluster_0.update_rejected", Ge(1)); } TEST_P(EdsIntegrationTest, LoadBalancerRejectsEndpointsWithHealthcheck) { @@ -847,7 +849,7 @@ TEST_P(EdsIntegrationTest, LoadBalancerRejectsEndpointsWithHealthcheck) { options.healthy_endpoints = 4; options.load_balancing_weight = UINT32_MAX - 1; setEndpoints(options, true, false); - test_server_->waitForCounterGe("cluster.cluster_0.update_rejected", 1); + test_server_->waitForCounter("cluster.cluster_0.update_rejected", Ge(1)); } } // namespace diff --git a/test/integration/extension_discovery_integration_test.cc b/test/integration/extension_discovery_integration_test.cc index 0766b32a9bbee..1fa1e05901676 100644 --- a/test/integration/extension_discovery_integration_test.cc +++ b/test/integration/extension_discovery_integration_test.cc @@ -13,6 +13,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -377,13 +379,13 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccess) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponse("foo", "1", denyPrivateConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); { @@ -405,7 +407,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccess) { // Update again but keep the connection. { sendHttpFilterEcdsResponse("foo", "2", allowAllConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 2); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(2)); auto response = codec_client_->makeHeaderOnlyRequest(banned_request_headers); ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); @@ -417,13 +419,13 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponse("foo", "1", denyPrivateConfig(), true); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); { @@ -446,7 +448,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { { // Wait until the the TTL for the resource expires, which will trigger a config load to remove // the resource. - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 2); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(2)); auto response = codec_client_->makeHeaderOnlyRequest(banned_request_headers); ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); @@ -457,7 +459,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { // Reinstate the previous configuration. sendHttpFilterEcdsResponse("foo", "1", denyPrivateConfig(), true); // Wait until the new configuration has been applied. - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 3); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(3)); auto response = codec_client_->makeHeaderOnlyRequest(banned_request_headers); ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); @@ -469,13 +471,13 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtlWithDefault) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponse("foo", "1", allowAllConfig(), true); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); @@ -491,7 +493,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithTtlWithDefault) { { // Wait until the the TTL for the resource expires, which will trigger a config load to remove // the resource. - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 2); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(2)); auto response = codec_client_->makeHeaderOnlyRequest(banned_request_headers); ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); @@ -503,13 +505,13 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicSuccessWithMatcher) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponseWithFullYaml("foo", "1", denyPrivateConfigWithMatcher()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); { @@ -545,13 +547,13 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicDefaultMatcher) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false, true, false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponse("foo", "1", invalidConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_fail", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_fail", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); { @@ -582,20 +584,20 @@ TEST_P(ExtensionDiscoveryIntegrationTest, ReuseExtensionConfig) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponse("foo", "1", allowAllConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); // Update listener and expect it to be warm and use active configuration from the subscription // instead of the default config. listener_config_.set_traffic_direction(envoy::config::core::v3::TrafficDirection::OUTBOUND); sendLdsResponse("updated"); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 2); - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", 0); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(2)); + test_server_->waitForGauge("listener_manager.total_listeners_warming", Eq(0)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); Http::TestRequestHeaderMapImpl request_headers{ @@ -604,7 +606,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, ReuseExtensionConfig) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterEq("extension_config_discovery.http_filter.foo.config_conflict", 0); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_conflict", Eq(0)); } // Validate that a listener update falls back to the default extension configuration @@ -613,13 +615,13 @@ TEST_P(ExtensionDiscoveryIntegrationTest, ReuseExtensionConfigInvalid) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponseWithFullYaml("foo", "1", denyPrivateConfigWithMatcher()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); // Remove matcher filter type URL, invalidating the subscription last config. auto hcm_config = MessageUtil::anyConvert< @@ -632,8 +634,8 @@ TEST_P(ExtensionDiscoveryIntegrationTest, ReuseExtensionConfigInvalid) { hcm_config); sendLdsResponse("updated"); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 2); - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", 0); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(2)); + test_server_->waitForGauge("listener_manager.total_listeners_warming", Eq(0)); // Should be using the default config (403) codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); @@ -643,20 +645,20 @@ TEST_P(ExtensionDiscoveryIntegrationTest, ReuseExtensionConfigInvalid) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ("403", response->headers().getStatusValue()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_conflict", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_conflict", Ge(1)); } TEST_P(ExtensionDiscoveryIntegrationTest, BasicFailWithDefault) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponse("foo", "1", invalidConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_fail", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_fail", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); Http::TestRequestHeaderMapImpl request_headers{ @@ -671,13 +673,13 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicFailWithoutDefault) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponse("foo", "1", invalidConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_fail", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_fail", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); Http::TestRequestHeaderMapImpl request_headers{ @@ -692,11 +694,11 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicWithoutWarming) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("bar", true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); registerTestServerPorts({"http"}); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); // Initial request uses the default config. Http::TestRequestHeaderMapImpl request_headers{ @@ -710,7 +712,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicWithoutWarming) { // Update should cause a different response. sendHttpFilterEcdsResponse("bar", "1", denyPrivateConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.bar.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.bar.config_reload", Ge(1)); { auto response = codec_client_->makeHeaderOnlyRequest(request_headers); ASSERT_TRUE(response->waitForEndStream()); @@ -723,15 +725,15 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicWithoutWarmingFail) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("bar", true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); registerTestServerPorts({"http"}); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); // Update should not cause a different response. sendHttpFilterEcdsResponse("bar", "1", invalidConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.bar.config_fail", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.bar.config_fail", Ge(1)); Http::TestRequestHeaderMapImpl request_headers{ {":method", "GET"}, {":path", "/"}, {":scheme", "http"}, {":authority", "host"}}; auto response = codec_client_->makeHeaderOnlyRequest(request_headers); @@ -745,13 +747,13 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicTwoSubscriptionsSameName) { addDynamicFilter("baz", true); addDynamicFilter("baz", false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponse("baz", "1", denyPrivateConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.baz.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.baz.config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); Http::TestRequestHeaderMapImpl request_headers{ @@ -770,7 +772,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, DestroyDuringInit) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); test_server_.reset(); auto result = ecds_connection_->waitForDisconnect(); @@ -784,13 +786,13 @@ TEST_P(ExtensionDiscoveryIntegrationTest, BasicFailTerminalFilterNotAtEndOfFilte on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponse("foo", "1", terminalFilterConfig(), false, false); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_fail", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_fail", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); Http::TestRequestHeaderMapImpl request_headers{ @@ -807,13 +809,13 @@ TEST_P(ExtensionDiscoveryIntegrationTest, ReloadBoth) { on_server_init_function_ = [&]() { waitXdsStream(); }; addDynamicFilter("foo", false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); sendHttpFilterEcdsResponse("foo", "1", denyPrivateConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); Http::TestRequestHeaderMapImpl banned_request_headers{ {":method", "GET"}, {":path", "/private/key"}, {":scheme", "http"}, {":authority", "host"}}; @@ -829,9 +831,9 @@ TEST_P(ExtensionDiscoveryIntegrationTest, ReloadBoth) { // Rename the listener to force delete the first listener and wait for the deletion. listener_config_.set_name("updated"); sendLdsResponse("updated"); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 2); - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", 0); - test_server_->waitForGaugeEq("listener_manager.total_listeners_draining", 0); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(2)); + test_server_->waitForGauge("listener_manager.total_listeners_warming", Eq(0)); + test_server_->waitForGauge("listener_manager.total_listeners_draining", Eq(0)); // Verify ECDS is still applied on the new listener. registerTestServerPorts({"http"}); @@ -846,7 +848,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, ReloadBoth) { // Update ECDS but keep the connection. { sendHttpFilterEcdsResponse("foo", "2", allowAllConfig()); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 2); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(2)); auto response = codec_client_->makeHeaderOnlyRequest(banned_request_headers); ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); @@ -866,7 +868,7 @@ TEST_P(ExtensionDiscoveryIntegrationTest, ConfigDumpWithTwoSubscriptionTypes) { addDynamicListenerFilter("bar"); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); registerTestServerPorts({"http"}); @@ -874,9 +876,9 @@ TEST_P(ExtensionDiscoveryIntegrationTest, ConfigDumpWithTwoSubscriptionTypes) { sendHttpFilterEcdsResponse("foo", "1", denyPrivateConfig()); // Send configuration update for listener ECDS filter. sendListenerFilterEcdsResponse("bar", "2", 7); - test_server_->waitForCounterGe("extension_config_discovery.http_filter.foo.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.http_filter.foo.config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); // Get config_dump and verify HTTP and Listener ECDS filters are dumped correctly. diff --git a/test/integration/filter_integration_test.cc b/test/integration/filter_integration_test.cc index 150190a360e86..612bcc836ffdc 100644 --- a/test/integration/filter_integration_test.cc +++ b/test/integration/filter_integration_test.cc @@ -573,7 +573,7 @@ TEST_P(FilterIntegrationTest, HittingEncoderFilterLimit) { // sure this path does standard HCM header transformations. EXPECT_TRUE(response->headers().Date() != nullptr); EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("500")); - test_server_->waitForCounterEq("http.config_test.downstream_rq_5xx", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_5xx", testing::Eq(1)); } TEST_P(FilterIntegrationTest, LocalReplyDuringEncoding) { diff --git a/test/integration/filter_manager_integration_test.cc b/test/integration/filter_manager_integration_test.cc index ed13201b0aede..56580887759a4 100644 --- a/test/integration/filter_manager_integration_test.cc +++ b/test/integration/filter_manager_integration_test.cc @@ -734,7 +734,7 @@ TEST_F(NetworkFilterAccessLogIntegrationTest, AccessLogHandlerCalled) { tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - test_server_->waitForCounterGe("test_access_log_filter.log_called", 1); + test_server_->waitForCounter("test_access_log_filter.log_called", testing::Ge(1)); } } // namespace diff --git a/test/integration/hds_integration_test.cc b/test/integration/hds_integration_test.cc index ce2190c753d39..26d82f43a71ba 100644 --- a/test/integration/hds_integration_test.cc +++ b/test/integration/hds_integration_test.cc @@ -23,6 +23,7 @@ #include "gtest/gtest.h" #include "src/proto/grpc/health/v1/health.pb.h" +using testing::Ge; namespace Envoy { namespace { @@ -418,7 +419,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyHttp) { makeHttpHealthCheckSpecifier(envoy::type::v3::CodecClientType::HTTP1, false); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a health check message to an endpoint healthcheckEndpoints(); @@ -460,7 +461,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointTimeoutHttp) { // Server asks for health checking hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a health check message to an endpoint ASSERT_TRUE(host_upstream_->waitForRawConnection(host_fake_raw_connection_)); @@ -492,7 +493,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointUnhealthyHttp) { // Server asks for health checking hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a health check message to an endpoint healthcheckEndpoints(); @@ -535,7 +536,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointTimeoutTcp) { hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoys asks the endpoint if it's healthy ASSERT_TRUE(host_upstream_->waitForRawConnection(host_fake_raw_connection_)); @@ -564,7 +565,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyTcp) { server_health_check_specifier_ = makeTcpHealthCheckSpecifier(); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy asks the endpoint if it's healthy ASSERT_TRUE(host_upstream_->waitForRawConnection(host_fake_raw_connection_)); @@ -598,7 +599,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointUnhealthyTcp) { ->set_seconds(2); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy asks the endpoint if it's healthy ASSERT_TRUE(host_upstream_->waitForRawConnection(host_fake_raw_connection_)); @@ -635,7 +636,7 @@ TEST_P(HdsIntegrationTest, TwoEndpointsSameLocality) { // Server asks for health checking hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); healthcheckEndpoints("anna"); @@ -698,7 +699,7 @@ TEST_P(HdsIntegrationTest, TwoEndpointsDifferentLocality) { // Server asks for health checking hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends health check messages to two endpoints healthcheckEndpoints("anna"); @@ -759,7 +760,7 @@ TEST_P(HdsIntegrationTest, TwoEndpointsDifferentClusters) { // Server asks for health checking hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends health check messages to two endpoints healthcheckEndpoints("cat"); @@ -824,7 +825,7 @@ TEST_P(HdsIntegrationTest, TestUpdateMessage) { makeHttpHealthCheckSpecifier(envoy::type::v3::CodecClientType::HTTP1, false); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a health check message to an endpoint healthcheckEndpoints(); @@ -864,7 +865,7 @@ TEST_P(HdsIntegrationTest, TestUpdateMessage) { // Server asks for health checking with the new message hds_stream_->sendGrpcMessage(new_message); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a health check message to an endpoint ASSERT_TRUE(host2_upstream_->waitForHttpConnection(*dispatcher_, host2_fake_connection_)); @@ -902,7 +903,7 @@ TEST_P(HdsIntegrationTest, TestUpdateChangesTimer) { makeHttpHealthCheckSpecifier(envoy::type::v3::CodecClientType::HTTP1, false); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); healthcheckEndpoints(); @@ -915,7 +916,7 @@ TEST_P(HdsIntegrationTest, TestUpdateChangesTimer) { // Server asks for health checking with the new message hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // A response should not be received until the new timer is completed ASSERT_FALSE( @@ -943,7 +944,7 @@ TEST_P(HdsIntegrationTest, TestDefaultTimer) { server_health_check_specifier_.clear_interval(); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); healthcheckEndpoints(); @@ -977,7 +978,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyTlsHttp2) { makeHttpHealthCheckSpecifier(envoy::type::v3::CodecClientType::HTTP2, true); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a health check message to an endpoint healthcheckEndpoints(); @@ -1014,7 +1015,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyTlsHttp1) { makeHttpHealthCheckSpecifier(envoy::type::v3::CodecClientType::HTTP1, true); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a health check message to an endpoint healthcheckEndpoints(); @@ -1052,7 +1053,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointUnhealthyTlsMissingSocketMatch) { hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a health check message to an endpoint ASSERT_TRUE(host_upstream_->waitForRawConnection(host_fake_raw_connection_)); @@ -1089,7 +1090,7 @@ TEST_P(HdsIntegrationTest, UpdateEndpoints) { // Server asks for health checking hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends health check messages to two endpoints healthcheckEndpoints("cat"); @@ -1153,7 +1154,7 @@ TEST_P(HdsIntegrationTest, UpdateEndpoints) { hds_stream_->sendGrpcMessage(server_health_check_specifier_); // TODO: add stats reporting and verification for Clusters added/removed/reused and Endpoints // added/removed/reused. - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Set up second endpoint again. ASSERT_TRUE(host2_upstream_->waitForHttpConnection(*dispatcher_, host2_fake_connection_)); @@ -1215,7 +1216,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyHttpCustomPort) { hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy doesn't sends a health check message to cluster EXPECT_FALSE(host_upstream_->waitForHttpConnection(*dispatcher_, host_fake_connection_, @@ -1256,7 +1257,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointUnhealthyHttpCustomPort) { // Server asks for health checking hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy doesn't sends a health check message to cluster EXPECT_FALSE(host_upstream_->waitForHttpConnection(*dispatcher_, host_fake_connection_, @@ -1292,7 +1293,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyHttpHdsReconnect) { makeHttpHealthCheckSpecifier(envoy::type::v3::CodecClientType::HTTP1, false); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a health check message to an endpoint healthcheckEndpoints(); @@ -1321,7 +1322,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyHttpHdsReconnect) { makeHttpHealthCheckSpecifier(envoy::type::v3::CodecClientType::HTTP1, false); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Receive updates until the one we expect arrives waitForEndpointHealthResponse(envoy::config::core::v3::HEALTHY); @@ -1345,7 +1346,7 @@ TEST_P(HdsIntegrationTest, RemoveClusterDuringHealthCheck) { makeHttpHealthCheckSpecifier(envoy::type::v3::CodecClientType::HTTP1, false); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a health check message to an endpoint healthcheckEndpoints(); @@ -1354,7 +1355,7 @@ TEST_P(HdsIntegrationTest, RemoveClusterDuringHealthCheck) { server_health_check_specifier_.mutable_interval()->set_nanos(100000000); // 0.1 seconds hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // As the HDS cluster is destroyed, existing connections should be closed. EXPECT_TRUE(host_fake_connection_->waitForDisconnect()); @@ -1382,7 +1383,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyGrpc) { server_health_check_specifier_ = makeGrpcHealthCheckSpecifier(); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); - test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); + test_server_->waitForCounter("hds_delegate.requests", Ge(++hds_requests_)); // Envoy sends a gRPC health check message to the endpoint healthcheckEndpointsGrpc(); diff --git a/test/integration/health_check_integration_test.cc b/test/integration/health_check_integration_test.cc index 03b13d1aaf19b..9c2407ef7c837 100644 --- a/test/integration/health_check_integration_test.cc +++ b/test/integration/health_check_integration_test.cc @@ -13,6 +13,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -74,7 +76,7 @@ class HealthCheckIntegrationTestBase : public HttpIntegrationTest { acceptXdsConnection(); // Expect 1 for the statically specified CDS server. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 1); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(1)); registerTestServerPorts({"http"}); @@ -241,7 +243,7 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointHealthyHttp) { clusters_[cluster_idx].host_stream_->encodeData(1024, true); // Verify that Envoy detected the health check response. - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -257,7 +259,7 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointUnhealthyHttp) { Http::TestResponseHeaderMapImpl{{":status", "503"}}, false); clusters_[cluster_idx].host_stream_->encodeData(1024, true); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -278,14 +280,14 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointUnhealthyThresholdHttp) { clusters_[cluster_idx].host_stream_->encodeData(0, true); // Wait for health check - test_server_->waitForCounterEq("cluster.cluster_1.health_check.attempt", 1); - test_server_->waitForCounterEq("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.attempt", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Eq(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); - test_server_->waitForGaugeEq("cluster.cluster_1.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_1.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_1.membership_total")->value()); // Wait until the next attempt is made. - test_server_->waitForCounterEq("cluster.cluster_1.health_check.attempt", 2); + test_server_->waitForCounter("cluster.cluster_1.health_check.attempt", Eq(2)); // Respond with retriable status ASSERT_TRUE(clusters_[cluster_idx].host_fake_connection_->waitForNewStream( @@ -301,13 +303,13 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointUnhealthyThresholdHttp) { clusters_[cluster_idx].host_stream_->encodeData(0, true); // Wait for second health check - test_server_->waitForCounterEq("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Eq(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_1.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_1.membership_healthy")->value()); // Wait until the next attempt is made. - test_server_->waitForCounterEq("cluster.cluster_1.health_check.attempt", 3); + test_server_->waitForCounter("cluster.cluster_1.health_check.attempt", Eq(3)); // Respond with retriable status a second time, matching unhealthy threshold ASSERT_TRUE(clusters_[cluster_idx].host_fake_connection_->waitForNewStream( @@ -323,13 +325,13 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointUnhealthyThresholdHttp) { clusters_[cluster_idx].host_stream_->encodeData(0, true); // Wait for third health check - test_server_->waitForCounterEq("cluster.cluster_1.health_check.failure", 2); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Eq(2)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); - test_server_->waitForGaugeEq("cluster.cluster_1.membership_healthy", 0); + test_server_->waitForGauge("cluster.cluster_1.membership_healthy", Eq(0)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_1.membership_total")->value()); // Wait until the next attempt is made. - test_server_->waitForCounterEq("cluster.cluster_1.health_check.attempt", 4); + test_server_->waitForCounter("cluster.cluster_1.health_check.attempt", Eq(4)); // Respond with healthy status again. ASSERT_TRUE(clusters_[cluster_idx].host_fake_connection_->waitForNewStream( @@ -345,9 +347,9 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointUnhealthyThresholdHttp) { clusters_[cluster_idx].host_stream_->encodeData(0, true); // Wait for fourth health check - test_server_->waitForCounterEq("cluster.cluster_1.health_check.success", 2); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Eq(2)); EXPECT_EQ(2, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); - test_server_->waitForGaugeEq("cluster.cluster_1.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_1.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_1.membership_total")->value()); } @@ -366,10 +368,10 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointExpectedAndRetriablePrecede clusters_[cluster_idx].host_stream_->encodeData(0, true); // Wait for health check - test_server_->waitForCounterEq("cluster.cluster_1.health_check.attempt", 1); - test_server_->waitForCounterEq("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.attempt", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Eq(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); - test_server_->waitForGaugeEq("cluster.cluster_1.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_1.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_1.membership_total")->value()); } @@ -390,16 +392,16 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointImmediateHealthcheckFailHtt false); clusters_[cluster_idx].host_stream_->encodeData(1024, true); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.attempt")->value()); - test_server_->waitForGaugeEq("cluster.cluster_1.membership_excluded", 1); + test_server_->waitForGauge("cluster.cluster_1.membership_excluded", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_1.membership_total")->value()); EXPECT_EQ(0, test_server_->gauge("cluster.cluster_1.membership_healthy")->value()); // Wait until the next attempt is made. - test_server_->waitForCounterEq("cluster.cluster_1.health_check.attempt", 2); + test_server_->waitForCounter("cluster.cluster_1.health_check.attempt", Eq(2)); ASSERT_TRUE(clusters_[cluster_idx].host_fake_connection_->waitForNewStream( *dispatcher_, clusters_[cluster_idx].host_stream_)); @@ -413,10 +415,10 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointImmediateHealthcheckFailHtt clusters_[cluster_idx].host_stream_->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); - test_server_->waitForGaugeEq("cluster.cluster_1.membership_excluded", 0); + test_server_->waitForGauge("cluster.cluster_1.membership_excluded", Eq(0)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_1.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_1.membership_healthy")->value()); } @@ -431,7 +433,7 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointTimeoutHttp) { timeSystem().advanceTimeWait(std::chrono::seconds(30)); // Endpoint doesn't reply, and a healthcheck failure occurs (due to timeout). - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -454,7 +456,7 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointGoAway) { clusters_[cluster_idx].host_stream_->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); ASSERT_TRUE(clusters_[cluster_idx].host_fake_connection_->waitForDisconnect()); @@ -476,7 +478,7 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointGoAway) { clusters_[cluster_idx].host_stream_->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 2); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(2)); EXPECT_EQ(2, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -515,7 +517,7 @@ TEST_P(RealTimeHttpHealthCheckIntegrationTest, SingleEndpointGoAwayError) { clusters_[cluster_idx].host_fake_connection_->encodeProtocolError(); ASSERT_TRUE(clusters_[cluster_idx].host_fake_connection_->waitForDisconnect()); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); @@ -536,7 +538,7 @@ TEST_P(RealTimeHttpHealthCheckIntegrationTest, SingleEndpointGoAwayError) { clusters_[cluster_idx].host_stream_->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -626,7 +628,7 @@ TEST_P(TcpHealthCheckIntegrationTest, SingleEndpointHealthyTcp) { AssertionResult result = clusters_[cluster_idx].host_fake_raw_connection_->write("Pong"); RELEASE_ASSERT(result, result.message()); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -645,7 +647,7 @@ TEST_P(TcpHealthCheckIntegrationTest, SingleEndpointWrongResponseTcp) { // Increase time until timeout (30s). timeSystem().advanceTimeWait(std::chrono::seconds(30)); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -662,7 +664,7 @@ TEST_P(TcpHealthCheckIntegrationTest, SingleEndpointHealthyTcpWithProxyProtocolV AssertionResult result = clusters_[cluster_idx].host_fake_raw_connection_->write("Pong"); RELEASE_ASSERT(result, result.message()); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -678,7 +680,7 @@ TEST_P(TcpHealthCheckIntegrationTest, SingleEndpointHealthyTcpWithProxyProtocolV AssertionResult result = clusters_[cluster_idx].host_fake_raw_connection_->write("Pong"); RELEASE_ASSERT(result, result.message()); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -692,7 +694,7 @@ TEST_P(TcpHealthCheckIntegrationTest, SingleEndpointTimeoutTcp) { // Increase time until timeout (30s). timeSystem().advanceTimeWait(std::chrono::seconds(30)); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -768,7 +770,7 @@ TEST_P(GrpcHealthCheckIntegrationTest, SingleEndpointServingGrpc) { {":status", "200"}, {"content-type", Http::Headers::get().ContentTypeValues.Grpc}}, response); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -790,7 +792,7 @@ TEST_P(GrpcHealthCheckIntegrationTest, SingleEndpointNotServingGrpc) { {":status", "200"}, {"content-type", Http::Headers::get().ContentTypeValues.Grpc}}, response); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -805,7 +807,7 @@ TEST_P(GrpcHealthCheckIntegrationTest, SingleEndpointTimeoutGrpc) { // Increase time until timeout (30s). timeSystem().advanceTimeWait(std::chrono::seconds(30)); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -828,7 +830,7 @@ TEST_P(GrpcHealthCheckIntegrationTest, SingleEndpointServiceUnknownGrpc) { {":status", "200"}, {"content-type", Http::Headers::get().ContentTypeValues.Grpc}}, response); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -851,7 +853,7 @@ TEST_P(GrpcHealthCheckIntegrationTest, SingleEndpointUnknownStatusGrpc) { {":status", "200"}, {"content-type", Http::Headers::get().ContentTypeValues.Grpc}}, response); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -916,7 +918,7 @@ TEST_P(ExternalHealthCheckIntegrationTest, SingleEndpointHealthyExternal) { AssertionResult result = clusters_[cluster_idx].external_host_fake_raw_connection_->write("Pong"); RELEASE_ASSERT(result, result.message()); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -936,7 +938,7 @@ TEST_P(ExternalHealthCheckIntegrationTest, SingleEndpointWrongResponseExternal) // Increase time until timeout (30s). timeSystem().advanceTimeWait(std::chrono::seconds(30)); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -950,7 +952,7 @@ TEST_P(ExternalHealthCheckIntegrationTest, SingleEndpointTimeoutExternal) { // Increase time until timeout (30s). timeSystem().advanceTimeWait(std::chrono::seconds(30)); - test_server_->waitForCounterGe("cluster.cluster_1.health_check.failure", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.failure", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -1006,7 +1008,7 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointHealthyHttpWithPayload) { cluster_data.host_stream_->encodeData(1024, true); // Verify that Envoy detected the health check response. - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } @@ -1061,7 +1063,7 @@ TEST_P(HttpHealthCheckIntegrationTest, SingleEndpointHealthyHttpWithBinaryPayloa cluster_data.host_stream_->encodeData(1024, true); // Verify that Envoy detected the health check response. - test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + test_server_->waitForCounter("cluster.cluster_1.health_check.success", Ge(1)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } diff --git a/test/integration/http2_flood_integration_test.cc b/test/integration/http2_flood_integration_test.cc index 283d75844a587..e06c5bf4767af 100644 --- a/test/integration/http2_flood_integration_test.cc +++ b/test/integration/http2_flood_integration_test.cc @@ -22,6 +22,8 @@ #include "gtest/gtest.h" +using ::testing::Eq; +using ::testing::Ge; using ::testing::HasSubstr; namespace Envoy { @@ -159,7 +161,7 @@ void Http2FloodMitigationTest::floodServer(const Http2Frame& frame, const std::s tcp_client_->waitForDisconnect(); EXPECT_EQ(1, test_server_->counter(flood_stat)->value()); - test_server_->waitForCounterGe("http.config_test.downstream_cx_delayed_close_timeout", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_delayed_close_timeout", Ge(1)); } // Send header only request, flood client, and verify that the upstream is disconnected and client @@ -239,9 +241,9 @@ void Http2FloodMitigationTest::prefillOutboundDownstreamQueue(uint32_t data_fram // Wait for some data to arrive and then wait for the upstream_rq_active to flip to 0 to indicate // that the first request has completed. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_rx_bytes_total", 10000); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 0); - test_server_->waitForGaugeEq("http2.outbound_frames_active", data_frame_count + 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", Ge(10000)); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(0)); + test_server_->waitForGauge("http2.outbound_frames_active", Eq(data_frame_count + 1)); // Verify that pre-fill did not trigger flood protection EXPECT_EQ(0, test_server_->counter("http2.outbound_flood")->value()); } @@ -285,7 +287,7 @@ Http2FloodMitigationTest::prefillOutboundUpstreamQueue(uint32_t frame_count) { auto* upstream = fake_upstreams_.front().get(); EXPECT_TRUE(upstream->rawWriteConnection(0, std::string(buf.begin(), buf.end()))); // Wait for pre-fill data to arrive to Envoy - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_rx_bytes_total", 500); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", Ge(500)); // Verify that pre-fill did not kill upstream connection. EXPECT_TRUE(fake_upstream_connection_->connected()); return response; @@ -841,7 +843,7 @@ TEST_P(Http2FloodMitigationTest, RstStreamOnUpstreamRemoteCloseBeforeResponseHea sendFrame(request2); // Wait for it to be proxied - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_total", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_total", Ge(2)); // Disconnect upstream connection. Since there no response headers were sent yet the router // filter will send 503 with body and then RST_STREAM. With these 3 frames the downstream outbound @@ -1488,16 +1490,16 @@ TEST_P(Http2FloodMitigationTest, UpstreamRstStreamStormOnDownstreamCloseRegressi {Http2Frame::Header("no_end_stream", "1")}); sendFrame(request); } - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", num_requests, - TestUtility::DefaultTimeout); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(num_requests), + TestUtility::DefaultTimeout); // Disconnect downstream connection. Envoy should send RST_STREAM to cancel active upstream // requests. tcp_client_->close(); // Wait until the disconnect is detected and all upstream connections have been closed. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 0, - TestUtility::DefaultTimeout); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(0), + TestUtility::DefaultTimeout); // The disconnect shouldn't trigger an outbound control frame flood. EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.http2.outbound_control_flood")->value()); diff --git a/test/integration/http_conn_pool_integration_test.cc b/test/integration/http_conn_pool_integration_test.cc index a15d46b329889..77c00633d7560 100644 --- a/test/integration/http_conn_pool_integration_test.cc +++ b/test/integration/http_conn_pool_integration_test.cc @@ -2,6 +2,7 @@ #include "test/integration/http_protocol_integration.h" +using testing::Eq; namespace Envoy { namespace { @@ -52,7 +53,7 @@ TEST_P(HttpConnPoolIntegrationTest, PoolCleanupAfterLocalClose) { waitForNextUpstreamRequest(); // Validate that the circuit breaker config is setup as we expect. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.cx_pool_open", 1); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.cx_pool_open", Eq(1)); upstream_request_->encodeHeaders(default_response_headers_, false); upstream_request_->encodeData(512, true); @@ -64,7 +65,7 @@ TEST_P(HttpConnPoolIntegrationTest, PoolCleanupAfterLocalClose) { ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); // Validate that the pool is deleted when it becomes idle. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.cx_pool_open", 0); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.cx_pool_open", Eq(0)); } // Tests that conn pools are cleaned up after becoming idle due to a RemoteClose @@ -76,7 +77,7 @@ TEST_P(HttpConnPoolIntegrationTest, PoolCleanupAfterRemoteClose) { waitForNextUpstreamRequest(); // Validate that the circuit breaker config is setup as we expect. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.cx_pool_open", 1); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.cx_pool_open", Eq(1)); upstream_request_->encodeHeaders(default_response_headers_, false); upstream_request_->encodeData(512, true); @@ -88,7 +89,7 @@ TEST_P(HttpConnPoolIntegrationTest, PoolCleanupAfterRemoteClose) { ASSERT_TRUE(fake_upstream_connection_->close()); // Validate that the pool is deleted when it becomes idle. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.cx_pool_open", 0); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.cx_pool_open", Eq(0)); } // Verify that the drainConnections() cluster manager API works correctly. @@ -100,7 +101,7 @@ TEST_P(HttpConnPoolIntegrationTest, PoolDrainAfterDrainApiSpecificCluster) { waitForNextUpstreamRequest(); // Validate that the circuit breaker config is setup as we expect. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.cx_pool_open", 1); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.cx_pool_open", Eq(1)); upstream_request_->encodeHeaders(default_response_headers_, false); upstream_request_->encodeData(512, true); @@ -116,7 +117,7 @@ TEST_P(HttpConnPoolIntegrationTest, PoolDrainAfterDrainApiSpecificCluster) { ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); // Validate that the pool is deleted when it becomes idle. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.cx_pool_open", 0); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.cx_pool_open", Eq(0)); } // Verify the drainConnections() with a predicate is able to filter host drains. @@ -148,13 +149,13 @@ TEST_P(HttpConnPoolIntegrationTest, DrainConnectionsWithPredicate) { EXPECT_TRUE(response->complete()); // Now do a drain that matches. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.cx_pool_open", 1); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.cx_pool_open", Eq(1)); test_server_->server().dispatcher().post([this] { test_server_->server().clusterManager().drainConnections( "cluster_0", [](const Upstream::Host&) { return true; }); }); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.cx_pool_open", 0); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.cx_pool_open", Eq(0)); } // Verify that the drainConnections() cluster manager API works correctly. @@ -181,7 +182,7 @@ TEST_P(HttpConnPoolIntegrationTest, PoolDrainAfterDrainApiAllClusters) { waitForNextUpstreamRequest(); // Validate that the circuit breaker config is setup as we expect. - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.cx_pool_open", 1); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.cx_pool_open", Eq(1)); upstream_request_->encodeHeaders(default_response_headers_, false); upstream_request_->encodeData(512, true); @@ -200,7 +201,7 @@ TEST_P(HttpConnPoolIntegrationTest, PoolDrainAfterDrainApiAllClusters) { waitForNextUpstreamRequest(1); // Validate that the circuit breaker config is setup as we expect. - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.cx_pool_open", 1); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.cx_pool_open", Eq(1)); upstream_request_->encodeHeaders(default_response_headers_, false); upstream_request_->encodeData(512, true); @@ -218,8 +219,8 @@ TEST_P(HttpConnPoolIntegrationTest, PoolDrainAfterDrainApiAllClusters) { ASSERT_TRUE(first_connection->waitForDisconnect()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForGaugeEq("cluster.cluster_0.circuit_breakers.default.cx_pool_open", 0); - test_server_->waitForGaugeEq("cluster.cluster_1.circuit_breakers.default.cx_pool_open", 0); + test_server_->waitForGauge("cluster.cluster_0.circuit_breakers.default.cx_pool_open", Eq(0)); + test_server_->waitForGauge("cluster.cluster_1.circuit_breakers.default.cx_pool_open", Eq(0)); } } // namespace diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index d59e06f72bf7b..43e25a86f21f4 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -53,6 +53,7 @@ namespace Envoy { namespace { +using testing::Eq; using testing::HasSubstr; envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager::CodecType @@ -786,15 +787,15 @@ void HttpIntegrationTest::testRouterVirtualClusters() { auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); checkSimpleRequestSuccess(0, 0, response.get()); - test_server_->waitForCounterEq("vhost.integration.vcluster.test_vcluster.upstream_rq_total", 1); - test_server_->waitForCounterEq("vhost.integration.vcluster.other.upstream_rq_total", 0); + test_server_->waitForCounter("vhost.integration.vcluster.test_vcluster.upstream_rq_total", Eq(1)); + test_server_->waitForCounter("vhost.integration.vcluster.other.upstream_rq_total", Eq(0)); auto response2 = sendRequestAndWaitForResponse(default_request_headers_, 0, default_response_headers_, 0); checkSimpleRequestSuccess(0, 0, response2.get()); - test_server_->waitForCounterEq("vhost.integration.vcluster.test_vcluster.upstream_rq_total", 1); - test_server_->waitForCounterEq("vhost.integration.vcluster.other.upstream_rq_total", 1); + test_server_->waitForCounter("vhost.integration.vcluster.test_vcluster.upstream_rq_total", Eq(1)); + test_server_->waitForCounter("vhost.integration.vcluster.other.upstream_rq_total", Eq(1)); } // Make sure route level stats are generated correctly. @@ -819,8 +820,8 @@ void HttpIntegrationTest::testRouteStats() { auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); checkSimpleRequestSuccess(0, 0, response.get()); - test_server_->waitForCounterEq("vhost.integration.route.test_route.upstream_rq_total", 1); - test_server_->waitForCounterEq("vhost.integration.route.test_route.upstream_rq_completed", 1); + test_server_->waitForCounter("vhost.integration.route.test_route.upstream_rq_total", Eq(1)); + test_server_->waitForCounter("vhost.integration.route.test_route.upstream_rq_completed", Eq(1)); } void HttpIntegrationTest::testRouterUpstreamDisconnectBeforeRequestComplete() { @@ -1718,7 +1719,7 @@ void HttpIntegrationTest::testAdminDrain(Http::CodecType admin_request_type) { EXPECT_THAT(response->headers(), Http::HttpStatusIs("200")); // Validate that the listeners have been stopped. - test_server_->waitForCounterEq("listener_manager.listener_stopped", 1); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(1)); // Validate that port is closed and can be bound by other sockets. // This does not work for HTTP/3 because the port is not closed until the listener is completely diff --git a/test/integration/http_timeout_integration_test.cc b/test/integration/http_timeout_integration_test.cc index e5284423b46a8..af07d481f2d5c 100644 --- a/test/integration/http_timeout_integration_test.cc +++ b/test/integration/http_timeout_integration_test.cc @@ -616,7 +616,7 @@ TEST_P(HttpTimeoutIntegrationTest, RequestHeaderTimeout) { while (!connection_driver->allBytesSent()) { ASSERT_TRUE(connection_driver->run(Event::Dispatcher::RunType::NonBlock)); } - test_server_->waitForGaugeGe("http.config_test.downstream_rq_active", 1); + test_server_->waitForGauge("http.config_test.downstream_rq_active", testing::Ge(1)); ASSERT_FALSE(connection_driver->closed()); timeSystem().advanceTimeWait(std::chrono::milliseconds(1001)); diff --git a/test/integration/idle_timeout_integration_test.cc b/test/integration/idle_timeout_integration_test.cc index 09dc7d35298b0..f7e0bde8240a7 100644 --- a/test/integration/idle_timeout_integration_test.cc +++ b/test/integration/idle_timeout_integration_test.cc @@ -13,6 +13,7 @@ #include "test/test_common/test_time.h" #include "test/test_common/utility.h" +using testing::Ge; using testing::HasSubstr; namespace Envoy { @@ -163,12 +164,12 @@ TEST_P(IdleTimeoutIntegrationTest, TimeoutBasic) { EXPECT_TRUE(upstream_request_->complete()); EXPECT_TRUE(response->complete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_200", Ge(1)); // Do not send any requests and validate if idle time out kicks in. ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_idle_timeout", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_idle_timeout", Ge(1)); } // Tests idle timeout behaviour with multiple requests and validates that idle timer kicks in @@ -199,8 +200,8 @@ TEST_P(IdleTimeoutIntegrationTest, IdleTimeoutWithTwoRequests) { EXPECT_TRUE(upstream_request_->complete()); EXPECT_TRUE(response->complete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_200", Ge(1)); // Request 2. response = codec_client_->makeRequestWithBody(default_request_headers_, 512); @@ -211,12 +212,12 @@ TEST_P(IdleTimeoutIntegrationTest, IdleTimeoutWithTwoRequests) { EXPECT_TRUE(upstream_request_->complete()); EXPECT_TRUE(response->complete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_200", Ge(2)); // Do not send any requests and validate if idle time out kicks in. ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_idle_timeout", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_idle_timeout", Ge(1)); } // Max connection duration reached after a connection is created. @@ -241,12 +242,12 @@ TEST_P(IdleTimeoutIntegrationTest, MaxConnectionDurationBasic) { EXPECT_TRUE(upstream_request_->complete()); EXPECT_TRUE(response->complete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_200", Ge(1)); // Do not send any requests and validate that the max connection duration is reached. ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_max_duration_reached", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_max_duration_reached", Ge(1)); } // Per-stream idle timeout after having sent downstream headers. @@ -418,7 +419,7 @@ TEST_P(IdleTimeoutIntegrationTest, PerTryIdleTimeoutAfterUpstreamHeaders) { upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); waitForTimeout(*response); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_per_try_idle_timeout", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_per_try_idle_timeout", Ge(1)); EXPECT_TRUE(upstream_request_->complete()); EXPECT_EQ(0U, upstream_request_->bodyLength()); @@ -721,8 +722,8 @@ TEST_P(UpstreamHttpIdleTimeoutTest, UpstreamConnectionIdleTimeout) { ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); // Wait for stat - test_server_->waitForCounterGe("cluster.cluster_0.upstream_idle_timeout_verifier.on_idle_timeout", - 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_idle_timeout_verifier.on_idle_timeout", + Ge(1)); } } // namespace diff --git a/test/integration/integration_admin_test.cc b/test/integration/integration_admin_test.cc index 3f3a8d4ebb30d..1e54fb0a45384 100644 --- a/test/integration/integration_admin_test.cc +++ b/test/integration/integration_admin_test.cc @@ -461,7 +461,7 @@ TEST_P(IntegrationAdminTest, Admin) { EXPECT_EQ("text/plain; charset=UTF-8", contentType(response)); EXPECT_EQ("OK\n", response->body()); - test_server_->waitForCounterEq("listener_manager.listener_stopped", 1); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(1)); } // Test Prometheus protobuf format with content negotiation via Accept header, and @@ -515,7 +515,7 @@ TEST_P(IntegrationAdminTest, AdminDrainInboundOnly) { EXPECT_EQ("OK\n", response->body()); // Validate that the inbound listener has been stopped. - test_server_->waitForCounterEq("listener_manager.listener_stopped", 1); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(1)); } TEST_P(IntegrationAdminTest, AdminDrainInboundOnlyIdempotent) { @@ -570,7 +570,7 @@ TEST_P(IntegrationAdminTest, AdminDrainInboundOnlyIdempotent) { // Validate that the inbound listener has been stopped and that we don't double count // the inbound_only drain and the universal drain. - test_server_->waitForCounterEq("listener_manager.listener_stopped", 1); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(1)); } // Validates that the inbound only query param only drains inbound listeners when graceful is set. @@ -610,7 +610,7 @@ TEST_P(IntegrationAdminTest, AdminDrainInboundOnlyGracefulConnectionCloseForInbo } // Validate that the inbound listener has been stopped. - test_server_->waitForCounterEq("listener_manager.listener_stopped", 1); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(1)); } // Validates that the inbound only query param only drains inbound listeners when graceful is set. @@ -651,7 +651,7 @@ TEST_P(IntegrationAdminTest, AdminDrainInboundOnlyGracefulNoConnectionCloseForOu } // Validate that the inbound listener has been stopped. - test_server_->waitForCounterEq("listener_manager.listener_stopped", 0); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(0)); } TEST_P(IntegrationAdminTest, AdminOnDestroyCallbacks) { @@ -803,7 +803,7 @@ TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(DISABLED_ExcludeMult // TODO(ambuc): Find a cleaner way to test this. This test has an unfortunate compromise: // `listener_manager.listener_create_success` must be instantiated, because BaseIntegrationTest -// blocks on its creation (see waitForCounterGe and the suite of waitFor* functions). +// blocks on its creation (see waitForCounter and the suite of waitFor* functions). // If this invariant is changed, this test must be rewritten. TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(IncludeExact)) { // Stats matching does not play well with LDS, at least in test. See #7215. diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index 68b09793930a2..c4a087fee7954 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -32,6 +32,8 @@ using Envoy::Http::HttpStatusIs; using testing::Combine; using testing::ContainsRegex; using testing::EndsWith; +using testing::Eq; +using testing::Ge; using testing::HasSubstr; using testing::Not; using testing::StartsWith; @@ -84,7 +86,7 @@ TEST_P(IntegrationTest, BadPrebindSocketOptionWithReusePort) { socket_option->set_int_value(10000); // Invalid value. }); initialize(); - test_server_->waitForCounterGe("listener_manager.listener_create_failure", 1); + test_server_->waitForCounter("listener_manager.listener_create_failure", Ge(1)); } // Verify that we gracefully handle an invalid post-bind socket option when using reuse_port. @@ -105,7 +107,7 @@ TEST_P(IntegrationTest, BadPostbindSocketOptionWithReusePort) { socket_option->set_int_value(10000); // Invalid value. }); initialize(); - test_server_->waitForCounterGe("listener_manager.listener_create_failure", 1); + test_server_->waitForCounter("listener_manager.listener_create_failure", Ge(1)); } // Verify that we gracefully handle an invalid post-listen socket option. @@ -121,7 +123,7 @@ TEST_P(IntegrationTest, BadPostListenSocketOption) { socket_option->set_int_value(10000); // Invalid value. }); initialize(); - test_server_->waitForCounterGe("listener_manager.listener_create_failure", 1); + test_server_->waitForCounter("listener_manager.listener_create_failure", Ge(1)); } // Make sure we have correctly specified per-worker performance stats. @@ -137,15 +139,19 @@ TEST_P(IntegrationTest, PerWorkerStatsAndBalancing) { // Per-worker listener stats. auto check_listener_stats = [this](uint64_t cx_active, uint64_t cx_total) { if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForGaugeEq("listener.127.0.0.1_0.worker_0.downstream_cx_active", cx_active); - test_server_->waitForGaugeEq("listener.127.0.0.1_0.worker_1.downstream_cx_active", cx_active); - test_server_->waitForCounterEq("listener.127.0.0.1_0.worker_0.downstream_cx_total", cx_total); - test_server_->waitForCounterEq("listener.127.0.0.1_0.worker_1.downstream_cx_total", cx_total); + test_server_->waitForGauge("listener.127.0.0.1_0.worker_0.downstream_cx_active", + Eq(cx_active)); + test_server_->waitForGauge("listener.127.0.0.1_0.worker_1.downstream_cx_active", + Eq(cx_active)); + test_server_->waitForCounter("listener.127.0.0.1_0.worker_0.downstream_cx_total", + Eq(cx_total)); + test_server_->waitForCounter("listener.127.0.0.1_0.worker_1.downstream_cx_total", + Eq(cx_total)); } else { - test_server_->waitForGaugeEq("listener.[__1]_0.worker_0.downstream_cx_active", cx_active); - test_server_->waitForGaugeEq("listener.[__1]_0.worker_1.downstream_cx_active", cx_active); - test_server_->waitForCounterEq("listener.[__1]_0.worker_0.downstream_cx_total", cx_total); - test_server_->waitForCounterEq("listener.[__1]_0.worker_1.downstream_cx_total", cx_total); + test_server_->waitForGauge("listener.[__1]_0.worker_0.downstream_cx_active", Eq(cx_active)); + test_server_->waitForGauge("listener.[__1]_0.worker_1.downstream_cx_active", Eq(cx_active)); + test_server_->waitForCounter("listener.[__1]_0.worker_0.downstream_cx_total", Eq(cx_total)); + test_server_->waitForCounter("listener.[__1]_0.worker_1.downstream_cx_total", Eq(cx_total)); } }; check_listener_stats(0, 0); @@ -207,15 +213,19 @@ TEST_P(IntegrationTest, ConnectionBalanceFactory) { auto check_listener_stats = [this](uint64_t cx_active, uint64_t cx_total) { if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForGaugeEq("listener.127.0.0.1_0.worker_0.downstream_cx_active", cx_active); - test_server_->waitForGaugeEq("listener.127.0.0.1_0.worker_1.downstream_cx_active", cx_active); - test_server_->waitForCounterEq("listener.127.0.0.1_0.worker_0.downstream_cx_total", cx_total); - test_server_->waitForCounterEq("listener.127.0.0.1_0.worker_1.downstream_cx_total", cx_total); + test_server_->waitForGauge("listener.127.0.0.1_0.worker_0.downstream_cx_active", + Eq(cx_active)); + test_server_->waitForGauge("listener.127.0.0.1_0.worker_1.downstream_cx_active", + Eq(cx_active)); + test_server_->waitForCounter("listener.127.0.0.1_0.worker_0.downstream_cx_total", + Eq(cx_total)); + test_server_->waitForCounter("listener.127.0.0.1_0.worker_1.downstream_cx_total", + Eq(cx_total)); } else { - test_server_->waitForGaugeEq("listener.[__1]_0.worker_0.downstream_cx_active", cx_active); - test_server_->waitForGaugeEq("listener.[__1]_0.worker_1.downstream_cx_active", cx_active); - test_server_->waitForCounterEq("listener.[__1]_0.worker_0.downstream_cx_total", cx_total); - test_server_->waitForCounterEq("listener.[__1]_0.worker_1.downstream_cx_total", cx_total); + test_server_->waitForGauge("listener.[__1]_0.worker_0.downstream_cx_active", Eq(cx_active)); + test_server_->waitForGauge("listener.[__1]_0.worker_1.downstream_cx_active", Eq(cx_active)); + test_server_->waitForCounter("listener.[__1]_0.worker_0.downstream_cx_total", Eq(cx_total)); + test_server_->waitForCounter("listener.[__1]_0.worker_1.downstream_cx_total", Eq(cx_total)); } }; check_listener_stats(0, 0); @@ -253,8 +263,8 @@ TEST_P(IntegrationTest, AllWorkersAreHandlingLoad) { worker1_stat_name = "listener.[__1]_0.worker_1.downstream_cx_total"; } - test_server_->waitForCounterEq(worker0_stat_name, 0); - test_server_->waitForCounterEq(worker1_stat_name, 0); + test_server_->waitForCounter(worker0_stat_name, Eq(0)); + test_server_->waitForCounter(worker1_stat_name, Eq(0)); // We set the counters for the two workers to see how many connections each handles. uint64_t w0_ctr = 0; @@ -845,8 +855,8 @@ TEST_P(IntegrationTest, UpstreamDisconnectWithTwoRequests) { auto response2 = codec_client2->makeRequestWithBody(default_request_headers_, 512); // Validate one request active, the other pending. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_pending_active", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_pending_active", Eq(1)); // Response 1. upstream_request_->encodeHeaders(default_response_headers_, false); @@ -857,8 +867,8 @@ TEST_P(IntegrationTest, UpstreamDisconnectWithTwoRequests) { EXPECT_TRUE(upstream_request_->complete()); EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_200", Ge(1)); // Response 2. ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); @@ -872,8 +882,8 @@ TEST_P(IntegrationTest, UpstreamDisconnectWithTwoRequests) { EXPECT_TRUE(upstream_request_->complete()); EXPECT_TRUE(response2->complete()); EXPECT_EQ("200", response2->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 2); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(2)); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_200", Ge(2)); } TEST_P(IntegrationTest, TestSmuggling) { @@ -1902,13 +1912,13 @@ TEST_P(IntegrationTest, NoConnectionPoolsFree) { auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); // Validate none active. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 0); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_pending_active", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(0)); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_pending_active", Eq(0)); ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ("503", response->headers().getStatusValue()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_503", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_503", Ge(1)); EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_cx_pool_overflow")->value(), 1); } diff --git a/test/integration/leds_integration_test.cc b/test/integration/leds_integration_test.cc index 6e2fb32115e10..abcb5823b95dd 100644 --- a/test/integration/leds_integration_test.cc +++ b/test/integration/leds_integration_test.cc @@ -11,6 +11,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -389,7 +391,7 @@ TEST_P(LedsIntegrationTest, BasicLeds) { // Send an endpoint update with an unknown state using LEDS. setEndpoints({}, {}, {}, {0}, {}); - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 1); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(1)); // Waiting for the endpoint to become Healthy to end warming and move the cluster to active. EXPECT_EQ(1, test_server_->gauge("cluster_manager.warming_clusters")->value()); EXPECT_EQ(2, test_server_->gauge("cluster_manager.active_clusters")->value()); @@ -403,7 +405,7 @@ TEST_P(LedsIntegrationTest, BasicLeds) { waitForHealthCheck(0); hosts_upstreams_info_[0].defaultStream()->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); // Wait for our statically specified listener to become ready, and register its port in the @@ -412,8 +414,8 @@ TEST_P(LedsIntegrationTest, BasicLeds) { registerTestServerPorts({"http"}); // The endpoint sent a valid health-check so the cluster should be active. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); } @@ -425,7 +427,7 @@ TEST_P(LedsIntegrationTest, LedsAdd) { // Send an endpoint update with an unknown state using LEDS. setEndpoints({}, {}, {}, {0}, {}); - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 1); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(1)); // Waiting for the endpoint to become Healthy to end warming and move the cluster to active. EXPECT_EQ(1, test_server_->gauge("cluster_manager.warming_clusters")->value()); EXPECT_EQ(2, test_server_->gauge("cluster_manager.active_clusters")->value()); @@ -439,7 +441,7 @@ TEST_P(LedsIntegrationTest, LedsAdd) { waitForHealthCheck(0); hosts_upstreams_info_[0].defaultStream()->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); // Wait for our statically specified listener to become ready, and register its port in the @@ -454,7 +456,7 @@ TEST_P(LedsIntegrationTest, LedsAdd) { // Add 2 more endpoints using LEDS in unknown state. setEndpoints({}, {}, {}, {1, 2}, {}); - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 2); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(2)); // There should be additional 2 backends in the cluster, and only one healthy. EXPECT_EQ(3, test_server_->gauge("cluster.cluster_0.membership_total")->value()); @@ -468,7 +470,7 @@ TEST_P(LedsIntegrationTest, LedsAdd) { } // Verify that Envoy observes the healthy endpoints. - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 3); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(3)); EXPECT_EQ(3, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(3, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); } @@ -480,7 +482,7 @@ TEST_P(LedsIntegrationTest, LedsUpdateSameEndpoint) { // Send an endpoint update with an unknown state using LEDS. setEndpoints({}, {}, {}, {0}, {}); - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 1); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(1)); // Waiting for the endpoint to become Healthy to end warming and move the cluster to active. EXPECT_EQ(1, test_server_->gauge("cluster_manager.warming_clusters")->value()); EXPECT_EQ(2, test_server_->gauge("cluster_manager.active_clusters")->value()); @@ -494,7 +496,7 @@ TEST_P(LedsIntegrationTest, LedsUpdateSameEndpoint) { waitForHealthCheck(0); hosts_upstreams_info_[0].defaultStream()->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); // Wait for our statically specified listener to become ready, and register its port in the @@ -510,14 +512,14 @@ TEST_P(LedsIntegrationTest, LedsUpdateSameEndpoint) { // be healthy, as the active health check cleared it. setEndpoints({}, {}, {}, {0}, {}); - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 2); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(2)); // There should be additional 2 backends in the cluster, and only one healthy. EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); // Verify that Envoy observes the healthy endpoint. - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); } @@ -531,7 +533,7 @@ TEST_P(LedsIntegrationTest, EndpointRemoval) { // Send 2 endpoints update with an unknown state using LEDS. setEndpoints({}, {}, {}, {0, 1}, {}); - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 1); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(1)); // Waiting for the endpoint to become Healthy to end warming and move the cluster to active. EXPECT_EQ(3, test_server_->gauge("cluster_manager.active_clusters")->value()); @@ -545,7 +547,7 @@ TEST_P(LedsIntegrationTest, EndpointRemoval) { // Remove one of the endpoints. setEndpoints({}, {}, {}, {}, {0}); - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 2); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(2)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); } @@ -557,7 +559,7 @@ TEST_P(LedsIntegrationTest, UnknownEndpointRemoval) { // Send 2 endpoints update with an unknown state using LEDS. setEndpoints({}, {}, {}, {0}, {}); - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 1); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(1)); // Waiting for the endpoint to become Healthy to end warming and move the cluster to active. EXPECT_EQ(1, test_server_->gauge("cluster_manager.warming_clusters")->value()); EXPECT_EQ(2, test_server_->gauge("cluster_manager.active_clusters")->value()); @@ -571,7 +573,7 @@ TEST_P(LedsIntegrationTest, UnknownEndpointRemoval) { waitForHealthCheck(0); hosts_upstreams_info_[0].defaultStream()->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); // Wait for our statically specified listener to become ready, and register its port in the @@ -580,14 +582,14 @@ TEST_P(LedsIntegrationTest, UnknownEndpointRemoval) { registerTestServerPorts({"http"}); // The endpoints sent valid health-checks so the cluster should be active. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); // Remove one of the endpoints. setEndpoints({}, {}, {}, {}, {2}); - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 2); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(2)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.leds.update_rejected")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); @@ -607,7 +609,7 @@ TEST_P(LedsIntegrationTest, MoveEndpointsBetweenLocalities) { // The update only updates the first locality, but the cluster should still be // in warmed state. - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 1); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(1)); EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster_manager.warming_clusters")->value()); EXPECT_EQ(2, test_server_->gauge("cluster_manager.active_clusters")->value()); @@ -616,8 +618,8 @@ TEST_P(LedsIntegrationTest, MoveEndpointsBetweenLocalities) { setEndpoints({}, {}, {}, {1, 2}, {}, 1); // All localities should have endpoints so the cluster warm-up should be over. - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 2); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(2)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); // There should be a single backend in the cluster, all healthy as there isn't // active health-check. @@ -634,7 +636,7 @@ TEST_P(LedsIntegrationTest, MoveEndpointsBetweenLocalities) { setEndpoints({}, {}, {}, {}, {2}, 1); // Wait for the additional 2 LEDS updates. - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 4); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(4)); EXPECT_EQ(3, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(3, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); @@ -644,7 +646,7 @@ TEST_P(LedsIntegrationTest, MoveEndpointsBetweenLocalities) { setEndpoints({}, {}, {}, {0}, {}, 1); // Wait for the additional 2 LEDS updates. - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 6); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(6)); EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); @@ -663,7 +665,7 @@ TEST_P(LedsIntegrationTest, LocalitiesShareEndpoint) { // The update only updates the first locality, but the cluster should still be // in warmed state. - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 1); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(1)); EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster_manager.warming_clusters")->value()); EXPECT_EQ(2, test_server_->gauge("cluster_manager.active_clusters")->value()); @@ -672,8 +674,8 @@ TEST_P(LedsIntegrationTest, LocalitiesShareEndpoint) { setEndpoints({}, {}, {}, {1}, {}, 1); // All localities should have endpoints so the cluster warm-up should be over. - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 2); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(2)); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); // There should be 2 hosts in the cluster, all healthy as there isn't active health-check. EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_total")->value()); @@ -686,7 +688,7 @@ TEST_P(LedsIntegrationTest, LocalitiesShareEndpoint) { // Send a LEDS update to locality 1 with the same endpoint that is in locality 0. setEndpoints({}, {}, {}, {0}, {}, 1); - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 3); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(3)); // There should be 2 hosts in the cluster, all healthy as there isn't active health-check. EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_total")->value()); @@ -696,7 +698,7 @@ TEST_P(LedsIntegrationTest, LocalitiesShareEndpoint) { setEndpoints({}, {}, {}, {}, {0}, 0); // Wait for the additional LEDS update. - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 4); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(4)); // There are 2 endpoints left in locality 1. EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_total")->value()); @@ -716,7 +718,7 @@ TEST_P(LedsIntegrationTest, RemoveAfterHcFail) { waitForHealthCheck(0); hosts_upstreams_info_[0].defaultStream()->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 1); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(1)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); // Clear out the host and verify the host is still healthy. @@ -730,7 +732,7 @@ TEST_P(LedsIntegrationTest, RemoveAfterHcFail) { waitForHealthCheck(0); hosts_upstreams_info_[0].defaultStream()->encodeHeaders( Http::TestResponseHeaderMapImpl{{":status", "503"}, {"connection", "close"}}, true); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_healthy", 0); + test_server_->waitForGauge("cluster.cluster_0.membership_healthy", Eq(0)); EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_total")->value()); } @@ -801,7 +803,7 @@ TEST_P(LedsIntegrationTest, LedsSameAddressEndpoints) { leds_upstream_info_.stream_by_resource_name_[localities_prefixes_[0]].get())); // Verify that the update is successful. - test_server_->waitForCounterEq("cluster.cluster_0.leds.update_success", 1); + test_server_->waitForCounter("cluster.cluster_0.leds.update_success", Eq(1)); // Wait for our statically specified listener to become ready, and register its port in the // test framework's downstream listener port map. @@ -809,8 +811,8 @@ TEST_P(LedsIntegrationTest, LedsSameAddressEndpoints) { registerTestServerPorts({"http"}); // Verify that only one endpoint was processed. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.warming_clusters", Eq(0)); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); } diff --git a/test/integration/listener_extension_discovery_integration_test.cc b/test/integration/listener_extension_discovery_integration_test.cc index 14dd941c7f6fc..e5977d64f1fad 100644 --- a/test/integration/listener_extension_discovery_integration_test.cc +++ b/test/integration/listener_extension_discovery_integration_test.cc @@ -12,6 +12,9 @@ #include "gtest/gtest.h" namespace Envoy { + +using testing::Ge; + namespace { enum class ListenerMatcherType { NULLMATCHER, ANYMATCHER, NOTANYMATCHER }; @@ -270,7 +273,7 @@ class ListenerExtensionDiscoveryIntegrationTest // upstream. void sendDataVerifyResults(uint32_t drain_bytes) { test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); @@ -327,14 +330,14 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicSuccess) { // Send 1st config update to have listener filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(5); // Send 2nd config update to have listener filter drain 3 bytes of data. sendXdsResponse(filter_name_, "2", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(2)); sendDataVerifyResults(3); } @@ -347,8 +350,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicSuccessWithAnyMatcher) { // Send config update to have listener filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); // Send data, verify the listener filter doesn't drain any data. sendDataVerifyResults(0); } @@ -362,8 +365,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicSuccessWithNotAnyMatcher) // Send config update to have listener filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); // Send data, verify the listener filter drains five bytes data. sendDataVerifyResults(5); } @@ -377,21 +380,21 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { // Send 1st config update with TTL 1s, and have listener filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(5); // Wait for configuration expired. Then start a TCP connection. // The missing config listener filter will be installed to handle the connection. - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(2)); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); auto result = tcp_client->write(data_); if (result) { tcp_client->waitForDisconnect(); } // The extension_config_missing stats counter increases by 1. - test_server_->waitForCounterGe("listener.listener_stat.extension_config_missing", 1); + test_server_->waitForCounter("listener.listener_stat.extension_config_missing", Ge(1)); // Send the data again. The extension_config_missing stats counter increases to 2. tcp_client = makeTcpConnection(lookupPort(port_name_)); @@ -399,7 +402,7 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { if (result) { tcp_client->waitForDisconnect(); } - test_server_->waitForCounterGe("listener.listener_stat.extension_config_missing", 2); + test_server_->waitForCounter("listener.listener_stat.extension_config_missing", Ge(2)); } TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicSuccessWithTtlWithDefault) { @@ -411,13 +414,13 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicSuccessWithTtlWithDefault // Send 1st config update with TTL 1s, and have listener filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(5); // Wait for configuration expired. The default filter will be installed. - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(2)); // Start a TCP connection. The default filter drain 2 bytes. sendDataVerifyResults(default_drain_bytes_); } @@ -431,8 +434,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicFailWithDefault) { // Send config update with invalid config (drain_bytes has to >=2). sendXdsResponse(filter_name_, "1", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_fail", Ge(1)); // The default filter will be installed. Start a TCP connection. The default filter drain 2 bytes. sendDataVerifyResults(default_drain_bytes_); } @@ -446,8 +449,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicFailWithoutDefault) { // Send config update with invalid config (drain_bytes has to >=2). sendXdsResponse(filter_name_, "1", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_fail", Ge(1)); // The missing config filter will be installed and close the connection when a correction is // created. IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); @@ -456,7 +459,7 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicFailWithoutDefault) { tcp_client->waitForDisconnect(); } // The extension_config_missing stats counter increases by 1. - test_server_->waitForCounterGe("listener.listener_stat.extension_config_missing", 1); + test_server_->waitForCounter("listener.listener_stat.extension_config_missing", Ge(1)); } TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicWithoutWarming) { @@ -468,8 +471,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicWithoutWarming) { sendDataVerifyResults(default_drain_bytes_); // Send update should cause a different response. sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(3); } @@ -482,8 +485,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicWithoutWarmingConfigFail) sendDataVerifyResults(default_drain_bytes_); // Send config update with invalid config (drain_bytes has to >=2). sendXdsResponse(filter_name_, "1", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_fail", Ge(1)); sendDataVerifyResults(default_drain_bytes_); } @@ -495,8 +498,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, TwoSubscriptionsSameName) { EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); // Each filter drain 3 bytes. sendDataVerifyResults(6); } @@ -512,20 +515,20 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, TwoSubscriptionsDifferentName) // Send 1st config update. sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe("extension_config_discovery.tcp_listener_filter.foo.config_reload", - 1); - test_server_->waitForCounterGe("extension_config_discovery.tcp_listener_filter.bar.config_reload", - 1); + test_server_->waitForCounter("extension_config_discovery.tcp_listener_filter.foo.config_reload", + Ge(1)); + test_server_->waitForCounter("extension_config_discovery.tcp_listener_filter.bar.config_reload", + Ge(1)); // The two filters drain 3 + 4 bytes. sendDataVerifyResults(7); // Send 2nd config update. sendXdsResponse("foo", "2", 4); sendXdsResponse("bar", "2", 5, false, true); - test_server_->waitForCounterGe("extension_config_discovery.tcp_listener_filter.foo.config_reload", - 2); - test_server_->waitForCounterGe("extension_config_discovery.tcp_listener_filter.bar.config_reload", - 2); + test_server_->waitForCounter("extension_config_discovery.tcp_listener_filter.foo.config_reload", + Ge(2)); + test_server_->waitForCounter("extension_config_discovery.tcp_listener_filter.bar.config_reload", + Ge(2)); // The two filters drain 4 + 5 bytes. sendDataVerifyResults(9); } @@ -561,8 +564,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, TwoDynamicTwoStaticFilterMixed EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); // filter drain 3 + 2 + 3 + 2 bytes. sendDataVerifyResults(10); } @@ -577,8 +580,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, DynamicStaticFilterMixedDiffer EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 2); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); // filter drain 2 + 2 + 2 + 2 bytes. sendDataVerifyResults(8); } @@ -593,8 +596,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, DynamicStaticFilterMixedWithMa EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); // The filters with any matcher configured are disabled. They totally drain 2 + 3 bytes. sendDataVerifyResults(5); } @@ -625,8 +628,8 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicSuccessWithConfigDump) { // Send 1st config update to have listener filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); // Verify ECDS config dump are working correctly. BufferingStreamDecoderPtr response; @@ -667,11 +670,11 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, ConfigDumpWithFilterConfigRemo EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send config update with TTL 1s. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(1)); // Wait for configuration expired. - test_server_->waitForCounterGe( - "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.tcp_listener_filter." + filter_name_ + ".config_reload", Ge(2)); BufferingStreamDecoderPtr response; EXPECT_EQ("200", request("admin", "GET", "/config_dump?resource=ecds_filters", response)); @@ -695,10 +698,10 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, TwoSubscriptionsSameFilterType sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe("extension_config_discovery.tcp_listener_filter.foo.config_reload", - 1); - test_server_->waitForCounterGe("extension_config_discovery.tcp_listener_filter.bar.config_reload", - 1); + test_server_->waitForCounter("extension_config_discovery.tcp_listener_filter.foo.config_reload", + Ge(1)); + test_server_->waitForCounter("extension_config_discovery.tcp_listener_filter.bar.config_reload", + Ge(1)); // Verify ECDS config dump are working correctly. BufferingStreamDecoderPtr response; EXPECT_EQ("200", request("admin", "GET", "/config_dump", response)); @@ -742,10 +745,10 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, TwoSubscriptionsConfigDumpWith sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe("extension_config_discovery.tcp_listener_filter.foo.config_reload", - 1); - test_server_->waitForCounterGe("extension_config_discovery.tcp_listener_filter.bar.config_reload", - 1); + test_server_->waitForCounter("extension_config_discovery.tcp_listener_filter.foo.config_reload", + Ge(1)); + test_server_->waitForCounter("extension_config_discovery.tcp_listener_filter.bar.config_reload", + Ge(1)); BufferingStreamDecoderPtr response; EXPECT_EQ("200", request("admin", "GET", "/config_dump?resource=ecds_filters&name_regex=.a.", response)); @@ -838,10 +841,10 @@ TEST_P(QuicListenerExtensionDiscoveryIntegrationTest, EcdsBasicSuccess) { // Send 1st config update to have listener filter add "abc" in filter state. sendXdsResponse(filter_name_, "v1", "abc"); - test_server_->waitForCounterGe( - "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); testRouterHeaderOnlyRequestAndResponse(); @@ -867,8 +870,8 @@ TEST_P(QuicListenerExtensionDiscoveryIntegrationTest, EcdsBasicSuccess) { // and allow connection migration. sendXdsResponse(filter_name_, "v2", "def", /*allow_server_migration=*/false, /*allow_client_migration*/ true); - test_server_->waitForCounterGe( - "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_reload", Ge(2)); testRouterHeaderOnlyRequestAndResponse(); log = waitForAccessLog(access_log_name_, 1); EXPECT_THAT(log, testing::HasSubstr("200 \"def\"")); @@ -901,12 +904,12 @@ TEST_P(QuicListenerExtensionDiscoveryIntegrationTest, BadEcdsUpdateWithoutDefaul // This is a bad config with empty value string. sendXdsResponse(filter_name_, "v1", ""); - test_server_->waitForCounterGe( - "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_fail", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_fail", Ge(1)); + test_server_->waitForCounter( + "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_reload", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); Network::ClientConnectionPtr conn = makeClientConnection(lookupPort(port_name_)); @@ -919,11 +922,11 @@ TEST_P(QuicListenerExtensionDiscoveryIntegrationTest, BadEcdsUpdateWithoutDefaul EXPECT_EQ("QUIC_NO_ERROR with details: Closed by application with reason: no filter chain found", codec->connection()->transportFailureReason()); // The extension_config_missing stats counter increases by 1. - test_server_->waitForCounterGe("listener.listener_stat.extension_config_missing", 1); + test_server_->waitForCounter("listener.listener_stat.extension_config_missing", Ge(1)); sendXdsResponse(filter_name_, "v2", "abc"); - test_server_->waitForCounterGe( - "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_reload", Ge(2)); testRouterHeaderOnlyRequestAndResponse(); codec_client_->close(); std::string log = waitForAccessLog(access_log_name_, 0); @@ -943,8 +946,8 @@ TEST_P(QuicListenerExtensionDiscoveryIntegrationTest, ConfigDump) { // Send 1st config update to have listener filter add "abc" in filter state. sendXdsResponse(filter_name_, "v1", "abc", /*allow_server_migration=*/false, /*allow_client_migration*/ true); - test_server_->waitForCounterGe( - "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.quic_listener_filter." + filter_name_ + ".config_reload", Ge(1)); // Verify ECDS config dump are working correctly. BufferingStreamDecoderPtr response; diff --git a/test/integration/listener_lds_integration_test.cc b/test/integration/listener_lds_integration_test.cc index bc543cfab2a36..d0b6d0524342a 100644 --- a/test/integration/listener_lds_integration_test.cc +++ b/test/integration/listener_lds_integration_test.cc @@ -23,6 +23,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; using testing::StartsWith; namespace Envoy { @@ -268,7 +270,7 @@ TEST_P(ListenerIntegrationTest, CleanlyRejectsUnknownFilterConfigProto) { }; initialize(); registerTestServerPorts({listener_name_}); - test_server_->waitForCounterGe("listener_manager.lds.update_rejected", 1); + test_server_->waitForCounter("listener_manager.lds.update_rejected", Ge(1)); } TEST_P(ListenerIntegrationTest, RejectsUnsupportedTypedPerFilterConfig) { @@ -314,7 +316,7 @@ TEST_P(ListenerIntegrationTest, RejectsUnsupportedTypedPerFilterConfig) { }; initialize(); registerTestServerPorts({listener_name_}); - test_server_->waitForCounterGe("listener_manager.lds.update_rejected", 1); + test_server_->waitForCounter("listener_manager.lds.update_rejected", Ge(1)); } TEST_P(ListenerIntegrationTest, RejectsUnknownHttpFilter) { @@ -353,7 +355,7 @@ TEST_P(ListenerIntegrationTest, RejectsUnknownHttpFilter) { }; initialize(); registerTestServerPorts({listener_name_}); - test_server_->waitForCounterGe("listener_manager.lds.update_rejected", 1); + test_server_->waitForCounter("listener_manager.lds.update_rejected", Ge(1)); } TEST_P(ListenerIntegrationTest, IgnoreUnknownOptionalHttpFilter) { @@ -393,7 +395,7 @@ TEST_P(ListenerIntegrationTest, IgnoreUnknownOptionalHttpFilter) { }; initialize(); registerTestServerPorts({listener_name_}); - test_server_->waitForCounterGe("listener_manager.lds.update_rejected", 0); + test_server_->waitForCounter("listener_manager.lds.update_rejected", Ge(0)); } // Tests that a LDS deletion before Server initManager been initialized will not block the Server @@ -406,7 +408,7 @@ TEST_P(ListenerIntegrationTest, RemoveLastUninitializedListener) { }; initialize(); registerTestServerPorts({listener_name_}); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't push any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -414,7 +416,7 @@ TEST_P(ListenerIntegrationTest, RemoveLastUninitializedListener) { // This actually deletes the only listener. sendLdsResponse(std::vector{}, "2"); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 2); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(2)); EXPECT_EQ(test_server_->server().listenerManager().listeners().size(), 0); // Server instance is ready now because the listener's destruction marked the listener // initialized. @@ -428,7 +430,7 @@ TEST_P(ListenerMultiAddressesIntegrationTest, BasicSuccessWithMultiAddresses) { createRdsStream(route_table_name_); }; initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't pushed any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -445,14 +447,14 @@ TEST_P(ListenerMultiAddressesIntegrationTest, BasicSuccessWithMultiAddresses) { route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); - test_server_->waitForCounterGe( - fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + test_server_->waitForCounter( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), Ge(1)); // Now testing-listener-0 finishes initialization, Server initManager will be ready. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Request is sent to cluster_0. int response_size = 800; @@ -502,7 +504,7 @@ TEST_P(ListenerMultiAddressesIntegrationTest, BasicSuccessWithMultiAddressesAndS createRdsStream(route_table_name_); }; initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't pushed any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -519,14 +521,14 @@ TEST_P(ListenerMultiAddressesIntegrationTest, BasicSuccessWithMultiAddressesAndS route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); - test_server_->waitForCounterGe( - fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + test_server_->waitForCounter( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), Ge(1)); // Now testing-listener-0 finishes initialization, Server initManager will be ready. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Request is sent to cluster_0. int response_size = 800; @@ -584,7 +586,7 @@ TEST_P(ListenerMultiAddressesIntegrationTest, createRdsStream(route_table_name_); }; initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't pushed any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -601,14 +603,14 @@ TEST_P(ListenerMultiAddressesIntegrationTest, route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); - test_server_->waitForCounterGe( - fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + test_server_->waitForCounter( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), Ge(1)); // Now testing-listener-0 finishes initialization, Server initManager will be ready. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Request is sent to cluster_0. int response_size = 800; @@ -654,7 +656,7 @@ TEST_P(ListenerIntegrationTest, BasicSuccess) { createRdsStream(route_table_name_); }; initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't pushed any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -671,14 +673,14 @@ TEST_P(ListenerIntegrationTest, BasicSuccess) { route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); - test_server_->waitForCounterGe( - fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + test_server_->waitForCounter( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), Ge(1)); // Now testing-listener-0 finishes initialization, Server initManager will be ready. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Request is sent to cluster_0. codec_client_ = makeHttpConnection(lookupPort(listener_name_)); @@ -705,7 +707,7 @@ TEST_P(ListenerIntegrationTest, MultipleLdsUpdatesSharingListenSocketFactory) { createRdsStream(route_table_name_); }; initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't pushed any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -722,21 +724,21 @@ TEST_P(ListenerIntegrationTest, MultipleLdsUpdatesSharingListenSocketFactory) { route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); - test_server_->waitForCounterGe( - fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + test_server_->waitForCounter( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), Ge(1)); // Now testing-listener-0 finishes initialization, Server initManager will be ready. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Make a connection to the listener from version 1. codec_client_ = makeHttpConnection(lookupPort(listener_name_)); // Ensure Envoy has accepted the connection before starting reloads. if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForCounterGe("listener.127.0.0.1_0.downstream_cx_total", 1); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_cx_total", Ge(1)); } else { - test_server_->waitForCounterGe("listener.[__1]_0.downstream_cx_total", 1); + test_server_->waitForCounter("listener.[__1]_0.downstream_cx_total", Ge(1)); } for (int version = 2; version <= 10; version++) { @@ -749,7 +751,7 @@ TEST_P(ListenerIntegrationTest, MultipleLdsUpdatesSharingListenSocketFactory) { sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), absl::StrCat(version)); - test_server_->waitForCounterGe("listener_manager.listener_create_success", version); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(version)); // Wait for the client to be disconnected. ASSERT_TRUE(codec_client_->waitForDisconnect()); @@ -782,7 +784,7 @@ TEST_P(ListenerMultiAddressesIntegrationTest, #if defined(__aarch64__) return; #endif - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't pushed any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -799,14 +801,14 @@ TEST_P(ListenerMultiAddressesIntegrationTest, route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); - test_server_->waitForCounterGe( - fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + test_server_->waitForCounter( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), Ge(1)); // Now testing-listener-0 finishes initialization, Server initManager will be ready. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Make a connection to the listener from version 1. codec_client_ = makeHttpConnection(lookupPort("address1")); @@ -820,7 +822,7 @@ TEST_P(ListenerMultiAddressesIntegrationTest, sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), absl::StrCat(version)); - test_server_->waitForCounterGe("listener_manager.listener_create_success", version); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(version)); // Wait for the client to be disconnected. ASSERT_TRUE(codec_client_->waitForDisconnect()); @@ -863,7 +865,7 @@ TEST_P(ListenerMultiAddressesIntegrationTest, MultipleAddressesListenerInPlaceUp }; setDrainTime(std::chrono::seconds(30)); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't pushed any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -880,23 +882,23 @@ TEST_P(ListenerMultiAddressesIntegrationTest, MultipleAddressesListenerInPlaceUp route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); - test_server_->waitForCounterGe( - fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + test_server_->waitForCounter( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), Ge(1)); // Now testing-listener-0 finishes initialization, Server initManager will be ready. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Trigger a listener in-place updating. listener_config_.mutable_filter_chains(0)->mutable_filters(0)->set_name("http_filter"); sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2"); sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "2"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); - test_server_->waitForCounterEq("listener_manager.listener_in_place_updated", 1); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Eq(1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(1)); const uint32_t response_size = 800; const uint32_t request_size = 10; @@ -940,7 +942,7 @@ TEST_P(ListenerIntegrationTest, RemoveListenerAfterInPlaceUpdate) { }; setDrainTime(std::chrono::seconds(30)); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't pushed any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -957,23 +959,23 @@ TEST_P(ListenerIntegrationTest, RemoveListenerAfterInPlaceUpdate) { route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); - test_server_->waitForCounterGe( - fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + test_server_->waitForCounter( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), Ge(1)); // Now testing-listener-0 finishes initialization, Server initManager will be ready. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Trigger a listener in-place updating. listener_config_.mutable_filter_chains(0)->mutable_filters(0)->set_name("http_filter"); sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2"); sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "2"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); - test_server_->waitForCounterEq("listener_manager.listener_in_place_updated", 1); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Eq(1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(1)); // Make a new connection to the new listener. codec_client_ = makeHttpConnection(lookupPort(listener_name_)); @@ -992,8 +994,8 @@ TEST_P(ListenerIntegrationTest, RemoveListenerAfterInPlaceUpdate) { // Remove the active listener. sendLdsResponse(std::vector{}, "3"); - test_server_->waitForGaugeEq("listener_manager.total_listeners_active", 0); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 1); + test_server_->waitForGauge("listener_manager.total_listeners_active", Eq(0)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(1)); // All the listen socket are closed. include the sockets in the active listener and // the sockets in the filter chain draining listener. The new connection should be reset. @@ -1011,7 +1013,7 @@ TEST_P(ListenerIntegrationTest, RemoveListenerAfterInPlaceUpdate) { } // Ensure the old listener is still in filter chain draining. - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 1); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(1)); } // Create a listener, then do two in-place updates for the listener. @@ -1025,7 +1027,7 @@ TEST_P(ListenerIntegrationTest, RemoveListenerAfterMultipleInPlaceUpdate) { }; setDrainTime(std::chrono::seconds(30)); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't pushed any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -1042,32 +1044,32 @@ TEST_P(ListenerIntegrationTest, RemoveListenerAfterMultipleInPlaceUpdate) { route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); - test_server_->waitForCounterGe( - fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + test_server_->waitForCounter( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), Ge(1)); // Now testing-listener-0 finishes initialization, Server initManager will be ready. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Trigger a listener in-place updating. listener_config_.mutable_filter_chains(0)->mutable_filters(0)->set_name("http_filter"); sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2"); sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "2"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); - test_server_->waitForCounterEq("listener_manager.listener_in_place_updated", 1); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Eq(1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(1)); // Trigger second listener in-place updating. listener_config_.mutable_filter_chains(0)->mutable_filters(0)->set_name("http_filter2"); sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2"); sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "2"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 3); - test_server_->waitForCounterEq("listener_manager.listener_in_place_updated", 2); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(3)); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Eq(2)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(2)); // Make a new connection to the new listener. codec_client_ = makeHttpConnection(lookupPort(listener_name_)); @@ -1086,8 +1088,8 @@ TEST_P(ListenerIntegrationTest, RemoveListenerAfterMultipleInPlaceUpdate) { // Remove the active listener. sendLdsResponse(std::vector{}, "3"); - test_server_->waitForGaugeEq("listener_manager.total_listeners_active", 0); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 2); + test_server_->waitForGauge("listener_manager.total_listeners_active", Eq(0)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(2)); // All the listen socket are closed. include the sockets in the active listener and // the sockets in the filter chain draining listener. The new connection should be reset. @@ -1105,7 +1107,7 @@ TEST_P(ListenerIntegrationTest, RemoveListenerAfterMultipleInPlaceUpdate) { } // Ensure the old listener is still in filter chain draining. - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 2); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(2)); } TEST_P(ListenerIntegrationTest, ChangeListenerAddress) { @@ -1115,7 +1117,7 @@ TEST_P(ListenerIntegrationTest, ChangeListenerAddress) { createRdsStream(route_table_name_); }; initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // testing-listener-0 is not initialized as we haven't pushed any RDS yet. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Workers not started, the LDS added listener 0 is in active_listeners_ list. @@ -1132,14 +1134,14 @@ TEST_P(ListenerIntegrationTest, ChangeListenerAddress) { route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "1"); - test_server_->waitForCounterGe( - fmt::format("http.config_test.rds.{}.update_success", route_table_name_), 1); + test_server_->waitForCounter( + fmt::format("http.config_test.rds.{}.update_success", route_table_name_), Ge(1)); // Now testing-listener-0 finishes initialization, Server initManager will be ready. EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); const uint32_t old_port = lookupPort(listener_name_); // Make a connection to the listener from version 1. codec_client_ = makeHttpConnection(old_port); @@ -1150,7 +1152,7 @@ TEST_P(ListenerIntegrationTest, ChangeListenerAddress) { sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2"); sendRdsResponse(fmt::format(route_config_tmpl, route_table_name_, "cluster_0"), "2"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); registerTestServerPorts({listener_name_}); // Verify that the listener was updated and that the next connection will be to the new listener. // (Note that connecting to 127.0.0.1 works whether the listener address is 127.0.0.1 or 0.0.0.0.) @@ -1450,7 +1452,8 @@ TEST_P(ListenerFilterIntegrationTest, InspectDataFiltersClientCloseConnectionWit if (result == true) { tcp_client->waitForDisconnect(); } - test_server_->waitForCounterEq("listener.listener_0.downstream_listener_filter_remote_close", 1); + test_server_->waitForCounter("listener.listener_0.downstream_listener_filter_remote_close", + Eq(1)); #endif } @@ -1504,10 +1507,10 @@ TEST_P(ListenerFilterIntegrationTest, UpdateListenerFilterOrder) { }; use_lds_ = false; initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Workers not started, the LDS added test_listener is in active_listeners_ list. EXPECT_EQ(test_server_->server().listenerManager().listeners().size(), 1); registerTestServerPorts({"test_listener"}); @@ -1528,8 +1531,8 @@ TEST_P(ListenerFilterIntegrationTest, UpdateListenerFilterOrder) { listener_config_.mutable_listener_filters()->SwapElements(0, 1); ENVOY_LOG_MISC(debug, "listener config: {}", listener_config_.DebugString()); sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); - test_server_->waitForCounterEq("listener_manager.listener_in_place_updated", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Eq(1)); IntegrationTcpClientPtr tcp_client2 = makeTcpConnection(lookupPort("test_listener")); ASSERT_TRUE(tcp_client2->write(data)); @@ -1579,10 +1582,10 @@ TEST_P(ListenerFilterIntegrationTest, UpdateListenerWithDifferentSocketOptions) }; use_lds_ = false; initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Workers not started, the LDS added test_listener is in active_listeners_ list. EXPECT_EQ(test_server_->server().listenerManager().listeners().size(), 1); registerTestServerPorts({"test_listener"}); @@ -1603,7 +1606,7 @@ TEST_P(ListenerFilterIntegrationTest, UpdateListenerWithDifferentSocketOptions) listener_config_.mutable_socket_options(0)->set_int_value(4); ENVOY_LOG_MISC(debug, "listener config: {}", listener_config_.DebugString()); sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); // We want to test update listener with same address but different socket options. But // the test is using zero port address. It turns out when create a new socket on zero @@ -1662,10 +1665,10 @@ TEST_P(ListenerFilterIntegrationTest, }; use_lds_ = false; initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Workers not started, the LDS added test_listener is in active_listeners_ list. EXPECT_EQ(test_server_->server().listenerManager().listeners().size(), 1); registerTestServerPorts({"test_listener"}); @@ -1677,7 +1680,7 @@ TEST_P(ListenerFilterIntegrationTest, ASSERT_TRUE(fake_upstream_connection->waitForData(data.size(), &data)); tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - test_server_->waitForGaugeEq("listener.listener_stat.downstream_cx_active", 0); + test_server_->waitForGauge("listener.listener_stat.downstream_cx_active", Eq(0)); auto* socket_option = listener_config_.add_socket_options(); socket_option->set_level(IPPROTO_IP); @@ -1693,7 +1696,7 @@ TEST_P(ListenerFilterIntegrationTest, "duplicate address", { sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2"); - test_server_->waitForCounterGe("listener_manager.lds.update_rejected", 1); + test_server_->waitForCounter("listener_manager.lds.update_rejected", Ge(1)); IntegrationTcpClientPtr tcp_client2 = makeTcpConnection(lookupPort("test_listener")); ASSERT_TRUE(tcp_client2->write(data)); FakeRawConnectionPtr fake_upstream_connection2; @@ -1744,10 +1747,10 @@ TEST_P(ListenerFilterIntegrationTest, BasicSuccessWithMultiAddressesAndKeepalive }; use_lds_ = false; initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); test_server_->waitUntilListenersReady(); // NOTE: The line above doesn't tell you if listener is up and listening. - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Workers not started, the LDS added test_listener is in active_listeners_ list. EXPECT_EQ(test_server_->server().listenerManager().listeners().size(), 1); registerTestServerPorts({"test_listener_1"}); @@ -1787,7 +1790,7 @@ TEST_P(ListenerFilterIntegrationTest, BasicSuccessWithMultiAddressesAndKeepalive ->set_value(0); sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2"); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); registerTestServerPorts({"test_listener_1"}); IntegrationTcpClientPtr tcp_client2 = makeTcpConnection(lookupPort("test_listener_1")); @@ -1932,8 +1935,8 @@ TEST_P(RebalancerTest, BindToPortUpdate) { // Create an LDS response with the new config, and reload config. new_config_helper.setLds("1"); - test_server_->waitForCounterEq("listener_manager.listener_modified", 1); - test_server_->waitForGaugeEq("listener_manager.total_listeners_draining", 0); + test_server_->waitForCounter("listener_manager.listener_modified", Eq(1)); + test_server_->waitForGauge("listener_manager.total_listeners_draining", Eq(0)); verifyBalance(); } diff --git a/test/integration/load_stats_integration_test.cc b/test/integration/load_stats_integration_test.cc index 5449c87832032..9be5fe9e4c937 100644 --- a/test/integration/load_stats_integration_test.cc +++ b/test/integration/load_stats_integration_test.cc @@ -16,6 +16,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -389,7 +391,7 @@ class LoadStatsIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, loadstats_response.set_report_endpoint_granularity(report_endpoint_granularity); loadstats_stream_->sendGrpcMessage(loadstats_response); // Wait until the request has been received by Envoy. - test_server_->waitForCounterGe("load_reporter.requests", ++load_requests_); + test_server_->waitForCounter("load_reporter.requests", Ge(++load_requests_)); } envoy::config::endpoint::v3::UpstreamLocalityStats localityStats(const std::string& sub_zone, @@ -448,7 +450,7 @@ class LoadStatsIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, initiateClientConnection(); waitForUpstreamResponse(endpoint_index, response_code, send_orca_load_report); cleanupUpstreamAndDownstream(); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(0)); } void updateDropOverloadConfig() { @@ -534,7 +536,7 @@ TEST_P(LoadStatsIntegrationTest, Success) { // Change to 50/50 for the failover clusters. updateClusterLoadAssignment({}, {}, {{3}}, {{4}}); requestLoadStatsResponse({"cluster_0"}); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_total", 2); + test_server_->waitForGauge("cluster.cluster_0.membership_total", Eq(2)); for (uint32_t i = 0; i < 4; ++i) { sendAndReceiveUpstream(i % 2 + 3); @@ -875,7 +877,7 @@ TEST_P(LoadStatsIntegrationTest, SuccessWithCustomMetrics) { // Change to 50/50 for the failover clusters. updateClusterLoadAssignment({}, {}, {{3}}, {{4}}); requestLoadStatsResponse({"cluster_0"}); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_total", 2); + test_server_->waitForGauge("cluster.cluster_0.membership_total", Eq(2)); for (uint32_t i = 0; i < 4; ++i) { sendAndReceiveUpstream(i % 2 + 3, 200, true); @@ -999,7 +1001,7 @@ TEST_P(LoadStatsIntegrationTest, EndpointLevelStatsReportingSuccessAndFailure) { // Configure cluster_0 with one endpoint (service_upstream_[0], which is fake_upstreams_[1]) // in the "winter" locality. updateClusterLoadAssignment({{0}}, {}, {}, {}); - test_server_->waitForGaugeEq("cluster.cluster_0.membership_total", 1); + test_server_->waitForGauge("cluster.cluster_0.membership_total", Eq(1)); sendAndReceiveUpstream(0, 200, false /*send_orca_load_report*/); sendAndReceiveUpstream(0, 503, false /*send_orca_load_report*/); diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index e3a29eaf91cb5..dfad5a80eb51d 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -31,6 +31,8 @@ #include "absl/synchronization/mutex.h" #include "gtest/gtest.h" +using ::testing::Eq; +using ::testing::Ge; using ::testing::HasSubstr; using ::testing::MatchesRegex; @@ -249,7 +251,7 @@ TEST_P(MultiplexedIntegrationTest, CodecStreamIdleTimeout) { std::string flush_timeout_counter(downstreamProtocol() == Http::CodecType::HTTP3 ? "http3.tx_flush_timeout" : "http2.tx_flush_timeout"); - test_server_->waitForCounterEq(flush_timeout_counter, 1); + test_server_->waitForCounter(flush_timeout_counter, Eq(1)); ASSERT_TRUE(response->waitForReset()); } @@ -294,7 +296,7 @@ TEST_P(MultiplexedIntegrationTest, CodecStreamIdleTimeoutOverride) { std::string flush_timeout_counter(downstreamProtocol() == Http::CodecType::HTTP3 ? "http3.tx_flush_timeout" : "http2.tx_flush_timeout"); - test_server_->waitForCounterEq(flush_timeout_counter, 1); + test_server_->waitForCounter(flush_timeout_counter, Eq(1)); ASSERT_TRUE(response->waitForReset()); } @@ -321,8 +323,8 @@ TEST_P(MultiplexedIntegrationTest, Http2DownstreamKeepalive) { // This call is NOT running the event loop of the client, so downstream PINGs will // not receive a response. - test_server_->waitForCounterEq("http2.keepalive_timeout", 1, - std::chrono::milliseconds(timeout_ms * 2)); + test_server_->waitForCounter("http2.keepalive_timeout", Eq(1), + std::chrono::milliseconds(timeout_ms * 2)); ASSERT_TRUE(response->waitForReset()); } @@ -509,7 +511,7 @@ TEST_P(MetadataIntegrationTest, ProxyMetadataInResponse) { // The downstream codec should send one. std::string counter = absl::StrCat("cluster.cluster_0.", upstreamProtocolStatsRoot(), ".rx_reset"); - test_server_->waitForCounterEq(counter, 1); + test_server_->waitForCounter(counter, Eq(1)); } TEST_P(MetadataIntegrationTest, ProxyMultipleMetadata) { @@ -1309,8 +1311,8 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTime, GoAwayAfterTooManyResets) { // Envoy should disconnect client due to premature reset check ASSERT_TRUE(codec_client_->waitForDisconnect()); - test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", total_streams); - test_server_->waitForCounterEq("http.config_test.downstream_rq_too_many_premature_resets", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_rx_reset", Eq(total_streams)); + test_server_->waitForCounter("http.config_test.downstream_rq_too_many_premature_resets", Eq(1)); } TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, GoAwayQuicklyAfterTooManyResets) { @@ -1335,8 +1337,8 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, GoAwayQuicklyAfterT // Envoy should disconnect client due to premature reset check ASSERT_TRUE(codec_client_->waitForDisconnect()); - test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", num_reset_streams); - test_server_->waitForCounterEq("http.config_test.downstream_rq_too_many_premature_resets", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_rx_reset", Eq(num_reset_streams)); + test_server_->waitForCounter("http.config_test.downstream_rq_too_many_premature_resets", Eq(1)); } TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, TooManyRequestResetAndNoRecursion) { @@ -1379,9 +1381,9 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, TooManyRequestReset // Send and wait encoder_decoders.emplace_back(codec_client_->startRequest(headers)); } - test_server_->waitForCounterEq("http.config_test.downstream_rq_total", - pending_streams_per_iteration * (i + 1), - TestUtility::DefaultTimeout * 5); + test_server_->waitForCounter("http.config_test.downstream_rq_total", + Eq(pending_streams_per_iteration * (i + 1)), + TestUtility::DefaultTimeout * 5); } // Reset 50 streams and then the connection should be closed because too much premature resets. @@ -1397,10 +1399,10 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, TooManyRequestReset // Envoy should disconnect client due to premature reset check ASSERT_TRUE(codec_client_->waitForDisconnect()); - test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", pending_streams + 50, - TestUtility::DefaultTimeout * 5); + test_server_->waitForCounter("http.config_test.downstream_rq_rx_reset", Eq(pending_streams + 50), + TestUtility::DefaultTimeout * 5); // If there is recursion, this result won't be 1. - test_server_->waitForCounterEq("http.config_test.downstream_rq_too_many_premature_resets", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_too_many_premature_resets", Eq(1)); } TEST_P(MultiplexedIntegrationTestWithSimulatedTime, DontGoAwayAfterTooManyResetsForLongStreams) { @@ -1426,11 +1428,11 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTime, DontGoAwayAfterTooManyResets auto encoder_decoder = codec_client_->startRequest(headers); request_encoder_ = &encoder_decoder.first; auto response = std::move(encoder_decoder.second); - test_server_->waitForCounterEq(request_counter, i + 1); + test_server_->waitForCounter(request_counter, Eq(i + 1)); timeSystem().advanceTimeWait(std::chrono::seconds(2 * stream_lifetime_seconds)); codec_client_->sendReset(*request_encoder_); ASSERT_TRUE(response->waitForReset()); - test_server_->waitForCounterEq(reset_counter, i + 1); + test_server_->waitForCounter(reset_counter, Eq(i + 1)); } } @@ -1592,7 +1594,7 @@ TEST_P(MultiplexedIntegrationTest, IdleTimeoutWithSimultaneousRequests) { // Do not send any requests and validate idle timeout kicks in after both the requests are done. ASSERT_TRUE(fake_upstream_connection1->waitForDisconnect()); ASSERT_TRUE(fake_upstream_connection2->waitForDisconnect()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_idle_timeout", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_idle_timeout", Ge(2)); } // Test request mirroring / shadowing with an HTTP/2 downstream and a request with a body. @@ -2326,7 +2328,7 @@ TEST_P(Http2FrameIntegrationTest, UpstreamRemoteMalformedFrameEndstreamWith1xxHe const Http2Frame settings_frame = Http2Frame::makeEmptySettingsFrame(); ASSERT_TRUE(fake_upstream_connection->write(std::string(settings_frame))); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); // A malformed frame is translated to 103 header with END_STREAM by the underlying codec. // Typically we should get a protocol error, but this should not crash Envoy. @@ -2346,7 +2348,7 @@ TEST_P(Http2FrameIntegrationTest, UpstreamRemoteMalformedFrameEndstreamWith1xxHe EXPECT_EQ(Http2Frame::Type::Headers, response.type()); tcp_client_->close(); - test_server_->waitForGaugeEq("http.config_test.downstream_rq_active", 0); + test_server_->waitForGauge("http.config_test.downstream_rq_active", Eq(0)); } TEST_P(Http2FrameIntegrationTest, MaxConcurrentStreamsIsRespected) { @@ -2366,7 +2368,7 @@ TEST_P(Http2FrameIntegrationTest, MaxConcurrentStreamsIsRespected) { ASSERT_TRUE(tcp_client_->write(buffer, false, false)); tcp_client_->waitForDisconnect(); - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy_local", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy_local", Ge(1)); } // Regression test. @@ -2412,10 +2414,10 @@ TEST_P(Http2FrameIntegrationTest, AdjustUpstreamSettingsMaxStreams) { Http2Frame::SettingsFlags::None, {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 1}}); std::string settings_data(settings_frame); ASSERT_TRUE(fake_upstream_connection1->write(settings_data)); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_rx_bytes_total", - settings_data.size()); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", + Ge(settings_data.size())); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(1)); // Start another request, it should create another upstream connection because of the max // concurrent streams of upstream connection created above. @@ -2423,8 +2425,8 @@ TEST_P(Http2FrameIntegrationTest, AdjustUpstreamSettingsMaxStreams) { sendFrame(Http2Frame::makePostRequest(3, "host", "/path/to/long/url")); ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection2)); ASSERT_TRUE(fake_upstream_connection2->write(std::string(settings_frame))); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 2); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 2); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(2)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(2)); // Adjust the max concurrent streams of one connection created above to 2. auto bytes_read = test_server_->counter("cluster.cluster_0.upstream_cx_rx_bytes_total"); @@ -2432,19 +2434,19 @@ TEST_P(Http2FrameIntegrationTest, AdjustUpstreamSettingsMaxStreams) { Http2Frame::SettingsFlags::None, {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 3}}); std::string settings_data2(settings_frame2); ASSERT_TRUE(fake_upstream_connection1->write(settings_data2)); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_rx_bytes_total", - bytes_read + settings_data2.size()); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", + Ge(bytes_read + settings_data2.size())); // Now create another request. sendFrame(Http2Frame::makePostRequest(5, "host", "/path/to/long/url")); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 3); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 2); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(3)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(2)); // The configured max concurrent streams is 2, even the SETTINGS frame above wants to // set the max concurrent streams to 3, it still reaches the upper bound. So the new request // below should result in the third connection. sendFrame(Http2Frame::makePostRequest(7, "host", "/path/to/long/url")); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 4); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 3); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(4)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(3)); // Cleanup. tcp_client_->close(); @@ -2460,7 +2462,7 @@ TEST_P(Http2FrameIntegrationTest, UpstreamSettingsMaxStreamsAfterGoAway) { ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); const Http2Frame settings_frame = Http2Frame::makeEmptySettingsFrame(); ASSERT_TRUE(fake_upstream_connection->write(std::string(settings_frame))); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); // Send RST_STREAM, GOAWAY and SETTINGS(0 max streams) const Http2Frame rst_stream = @@ -2473,7 +2475,7 @@ TEST_P(Http2FrameIntegrationTest, UpstreamSettingsMaxStreamsAfterGoAway) { absl::StrCat(std::string(rst_stream), std::string(go_away_frame), std::string(settings_max_connections_frame)))); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_close_notify", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_close_notify", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.upstream_cx_protocol_error")->value()); // Cleanup. @@ -2490,7 +2492,7 @@ TEST_P(Http2FrameIntegrationTest, UpstreamGoAway) { ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); const Http2Frame settings_frame = Http2Frame::makeEmptySettingsFrame(); ASSERT_TRUE(fake_upstream_connection->write(std::string(settings_frame))); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); const Http2Frame rst_stream = Http2Frame::makeResetStreamFrame(client_stream_idx, Http2Frame::ErrorCode::FlowControlError); @@ -2500,7 +2502,7 @@ TEST_P(Http2FrameIntegrationTest, UpstreamGoAway) { absl::StrCat(std::string(rst_stream), std::string(go_away_frame)))); ASSERT_TRUE(fake_upstream_connection->close()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_close_notify", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_close_notify", Ge(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.upstream_cx_protocol_error")->value()); // Cleanup. @@ -2518,11 +2520,11 @@ TEST_P(Http2FrameIntegrationTest, UpstreamProtocolError) { ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); const Http2Frame settings_frame = Http2Frame::makeEmptySettingsFrame(); ASSERT_TRUE(fake_upstream_connection->write(std::string(settings_frame))); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); ASSERT_TRUE(fake_upstream_connection->write("abcdefg this is not a valid h2 frame")); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_protocol_error", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_protocol_error", Ge(1)); // Cleanup. tcp_client_->close(); @@ -2540,14 +2542,14 @@ TEST_P(Http2FrameIntegrationTest, UpstreamWindowUpdateAfterGoAway) { ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); const Http2Frame settings_frame = Http2Frame::makeEmptySettingsFrame(); ASSERT_TRUE(fake_upstream_connection->write(std::string(settings_frame))); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); // Start a second request and wait for it to reach the upstream. This is to exercise the case // where numActiveRequests > 0 at the time that GOAWAY is received from upstream, which is needed // to replicate the original assertion failure. sendFrame( Http2Frame::makePostRequest(Http2Frame::makeClientStreamId(1), "host", "/path/to/long/url")); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 2); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(2)); // Send RST_STREAM, GOAWAY followed by WINDOW_UPDATE const Http2Frame rst_stream = @@ -2561,7 +2563,7 @@ TEST_P(Http2FrameIntegrationTest, UpstreamWindowUpdateAfterGoAway) { ASSERT_TRUE(fake_upstream_connection->write(absl::StrCat( std::string(rst_stream), std::string(go_away_frame), std::string(window_update_frame)))); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_close_notify", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_close_notify", Ge(1)); // Cleanup. tcp_client_->close(); @@ -3061,8 +3063,8 @@ TEST_P(Http2FrameIntegrationTest, MultipleHeaderOnlyRequestsFollowedByReset) { } ASSERT_TRUE(tcp_client_->write(buffer, false, false)); - test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", - kRequestsSentPerIOCycle); + test_server_->waitForCounter("http.config_test.downstream_rq_rx_reset", + Eq(kRequestsSentPerIOCycle)); // Client should remain connected ASSERT_TRUE(tcp_client_->connected()); tcp_client_->close(); @@ -3092,7 +3094,7 @@ TEST_P(Http2FrameIntegrationTest, ResettingDeferredRequestsTriggersPrematureRese ASSERT_TRUE(tcp_client_->write(buffer, false, false)); // Envoy should close the client connection due to too many premature resets tcp_client_->waitForDisconnect(); - test_server_->waitForCounterEq("http.config_test.downstream_rq_too_many_premature_resets", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_too_many_premature_resets", Eq(1)); tcp_client_->close(); } @@ -3131,8 +3133,8 @@ TEST_P(Http2FrameIntegrationTest, CloseConnectionWithDeferredStreams) { tcp_client_->close(); // Test that Envoy can clean-up deferred streams // Make the timeout longer to accommodate non optimized builds - test_server_->waitForCounterEq("http.config_test.downstream_rq_rx_reset", kRequestsSentPerIOCycle, - TestUtility::DefaultTimeout * 10); + test_server_->waitForCounter("http.config_test.downstream_rq_rx_reset", + Eq(kRequestsSentPerIOCycle), TestUtility::DefaultTimeout * 10); } INSTANTIATE_TEST_SUITE_P(IpVersions, Http2FrameIntegrationTest, @@ -3508,22 +3510,23 @@ TEST_P(SocketSwappableMultiplexedIntegrationTest, BackedUpDownstreamConnectionCl auto response_decoder = codec_client_->makeRequestWithBody(default_request_headers_, 10); waitForNextUpstreamRequest(); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); upstream_request_->encodeData(1000, false); // We should trigger pause at least once, and eventually have at least 1k // bytes buffered. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); - test_server_->waitForGaugeGe("http.config_test.downstream_cx_tx_bytes_buffered", 1000); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_paused_reading_total", + Ge(1)); + test_server_->waitForGauge("http.config_test.downstream_cx_tx_bytes_buffered", Ge(1000)); // Close downstream, check cleanup. codec_client_->close(); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 0); - test_server_->waitForGaugeEq("http.config_test.downstream_rq_active", 0); - test_server_->waitForGaugeEq("http.config_test.downstream_cx_tx_bytes_buffered", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(0)); + test_server_->waitForGauge("http.config_test.downstream_rq_active", Eq(0)); + test_server_->waitForGauge("http.config_test.downstream_cx_tx_bytes_buffered", Eq(0)); } TEST_P(SocketSwappableMultiplexedIntegrationTest, BackedUpUpstreamConnectionClose) { @@ -3541,19 +3544,19 @@ TEST_P(SocketSwappableMultiplexedIntegrationTest, BackedUpUpstreamConnectionClos // We should trigger pause at least once, and eventually have at least 1k // bytes buffered. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_flow_control_backed_up_total", 1); - test_server_->waitForCounterGe("http.config_test.downstream_flow_control_paused_reading_total", - 1); - test_server_->waitForGaugeGe("cluster.cluster_0.upstream_cx_tx_bytes_buffered", 1000); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_backed_up_total", Ge(1)); + test_server_->waitForCounter("http.config_test.downstream_flow_control_paused_reading_total", + Ge(1)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_tx_bytes_buffered", Ge(1000)); // Close upstream, check cleanup. fake_upstreams_[0].reset(); ASSERT_TRUE(response_decoder->waitForAnyTermination()); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 0); - test_server_->waitForGaugeEq("http.config_test.downstream_rq_active", 0); - test_server_->waitForGaugeGe("cluster.cluster_0.upstream_cx_tx_bytes_buffered", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(0)); + test_server_->waitForGauge("http.config_test.downstream_rq_active", Eq(0)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_tx_bytes_buffered", Ge(0)); } TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, ResetPropogation) { diff --git a/test/integration/multiplexed_upstream_integration_test.cc b/test/integration/multiplexed_upstream_integration_test.cc index d0b86907b9918..5d30cec07c3f0 100644 --- a/test/integration/multiplexed_upstream_integration_test.cc +++ b/test/integration/multiplexed_upstream_integration_test.cc @@ -14,6 +14,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { class MultiplexedUpstreamIntegrationTest : public HttpProtocolIntegrationTest { @@ -694,7 +696,7 @@ TEST_P(MultiplexedUpstreamIntegrationTest, UpstreamGoaway) { // Send a goaway from upstream. fake_upstream_connection_->encodeGoAway(); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_close_notify", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_close_notify", Ge(1)); // A new request should result in a new connection auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -769,10 +771,10 @@ TEST_P(MultiplexedUpstreamIntegrationTest, DefaultAllowsUpstreamSafeRequestsUsin ASSERT_TRUE(fake_upstream_connection_->close()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); fake_upstream_connection_.reset(); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); EXPECT_EQ(0u, test_server_->counter("cluster.cluster_0.upstream_cx_connect_with_0_rtt")->value()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Ge(1)); default_request_headers_.addCopy("second_request", "1"); auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -817,7 +819,7 @@ TEST_P(MultiplexedUpstreamIntegrationTest, DisableUpstreamEarlyData) { ASSERT_TRUE(fake_upstream_connection_->close()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); fake_upstream_connection_.reset(); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); EXPECT_EQ(0u, test_server_->counter("cluster.cluster_0.upstream_cx_connect_with_0_rtt")->value()); @@ -855,7 +857,7 @@ TEST_P(MultiplexedUpstreamIntegrationTest, UpstreamEarlyDataRejected) { ASSERT_TRUE(fake_upstream_connection_->close()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); fake_upstream_connection_.reset(); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); EXPECT_EQ(0u, test_server_->counter("cluster.cluster_0.upstream_cx_connect_with_0_rtt")->value()); @@ -958,7 +960,7 @@ TEST_P(MultiplexedUpstreamIntegrationTest, UpstreamDisconnectDuringEarlyData) { ASSERT_TRUE(fake_upstream_connection_->close()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); fake_upstream_connection_.reset(); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); EXPECT_EQ(0u, test_server_->counter("cluster.cluster_0.upstream_cx_connect_with_0_rtt")->value()); @@ -1002,7 +1004,7 @@ TEST_P(MultiplexedUpstreamIntegrationTest, DownstreamDisconnectDuringEarlyData) ASSERT_TRUE(fake_upstream_connection_->close()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); fake_upstream_connection_.reset(); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); EXPECT_EQ(0u, test_server_->counter("cluster.cluster_0.upstream_cx_connect_with_0_rtt")->value()); @@ -1017,12 +1019,12 @@ TEST_P(MultiplexedUpstreamIntegrationTest, DownstreamDisconnectDuringEarlyData) {"second-request", "1"}}); // Even though the fake upstream is not responding, the 2 GET requests should still be forwarded // as early data. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_total", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_total", Eq(2)); EXPECT_EQ(1u, test_server_->counter("cluster.cluster_0.upstream_cx_connect_with_0_rtt")->value()); EXPECT_LE(1u, test_server_->counter("cluster.cluster_0.upstream_rq_0rtt")->value()); codec_client_->close(); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_tx_reset", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_tx_reset", Eq(1)); } // Release the upstream lock and finish the handshake. waitForNextUpstreamConnection(std::vector{0}, TestUtility::DefaultTimeout, @@ -1052,7 +1054,7 @@ TEST_P(MultiplexedUpstreamIntegrationTest, ConnPoolQueuingNonSafeRequest) { ASSERT_TRUE(fake_upstream_connection_->close()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); fake_upstream_connection_.reset(); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); EXPECT_EQ(0u, test_server_->counter("cluster.cluster_0.upstream_cx_connect_with_0_rtt")->value()); @@ -1083,7 +1085,7 @@ TEST_P(MultiplexedUpstreamIntegrationTest, ConnPoolQueuingNonSafeRequest) { {"forth-request", "1"}}); // Even though the fake upstream is not responding, the 2 GET requests should still be forwarded // as early data. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_total", 3); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_total", Eq(3)); EXPECT_EQ(1u, test_server_->counter("cluster.cluster_0.upstream_cx_connect_with_0_rtt")->value()); EXPECT_LE(2u, test_server_->counter("cluster.cluster_0.upstream_rq_0rtt")->value()); diff --git a/test/integration/network_extension_discovery_integration_test.cc b/test/integration/network_extension_discovery_integration_test.cc index d1df64e133baa..37db6ccae6a15 100644 --- a/test/integration/network_extension_discovery_integration_test.cc +++ b/test/integration/network_extension_discovery_integration_test.cc @@ -9,6 +9,7 @@ #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace { @@ -245,7 +246,7 @@ class NetworkExtensionDiscoveryIntegrationTest : public Grpc::GrpcClientIntegrat void sendDataVerifyResults(uint32_t bytes_drained) { test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); ASSERT_TRUE(tcp_client->write(data_)); @@ -324,19 +325,19 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicSuccess) { addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send 1st config update to have filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(5); // Send 2nd config update to have filter drain 3 bytes of data. sendXdsResponse(filter_name_, "2", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(2)); sendDataVerifyResults(3); } @@ -346,19 +347,19 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { addDynamicFilter(filter_name_, false, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send 1st config update with TTL 1s, and have network filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(5); // Wait for configuration expired. Then start a TCP connection. // The missing config network filter will be installed to handle the connection. - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(2)); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); auto result = tcp_client->write(data_); @@ -367,12 +368,12 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { } // The network_extension_config_missing stats counter increases by 1. - test_server_->waitForCounterGe("listener.listener_stat.network_extension_config_missing", 1); + test_server_->waitForCounter("listener.listener_stat.network_extension_config_missing", Ge(1)); // Reinstate the configuration. sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 3); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(3)); sendDataVerifyResults(3); } @@ -382,18 +383,18 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicSuccessWithTtlWithDefault) addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send 1st config update with TTL 1s, and have network filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(5); // Wait for configuration expired. The default filter will be installed. - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(2)); // Start a TCP connection. The default filter drains 2 bytes. sendDataVerifyResults(2); } @@ -404,13 +405,13 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicFailWithDefault) { addDynamicFilter(filter_name_, false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send config update with invalid config (bytes_to_drain needs to be >=2). sendXdsResponse(filter_name_, "1", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", Ge(1)); // The default filter will be installed. Start a TCP connection. The default filter drain 2 bytes. sendDataVerifyResults(2); } @@ -421,13 +422,13 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicFailWithoutDefault) { addDynamicFilter(filter_name_, false, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send config update with invalid config (drain_bytes has to >=2). sendXdsResponse(filter_name_, "1", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", Ge(1)); // New connections will close since there's no valid configuration. IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); @@ -437,7 +438,7 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicFailWithoutDefault) { } // The network_extension_config_missing stats counter increases by 1. - test_server_->waitForCounterGe("listener.listener_stat.network_extension_config_missing", 1); + test_server_->waitForCounter("listener.listener_stat.network_extension_config_missing", Ge(1)); } TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicWithoutWarming) { @@ -446,15 +447,15 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicWithoutWarming) { addDynamicFilter(filter_name_, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // Send data without send config update. sendDataVerifyResults(default_bytes_to_drain_); // Send update should cause a different response. sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(1)); sendDataVerifyResults(3); } @@ -464,15 +465,15 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicWithoutWarmingConfigFail) addDynamicFilter(filter_name_, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); // Send data without send config update. sendDataVerifyResults(default_bytes_to_drain_); // Send config update with invalid config (drain_bytes has to >=2). sendXdsResponse(filter_name_, "1", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", Ge(1)); sendDataVerifyResults(default_bytes_to_drain_); } @@ -483,12 +484,12 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsSameName) { addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(1)); // Each filter drain 3 bytes. sendDataVerifyResults(6); @@ -502,22 +503,26 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsDifferentName) addDynamicFilter("bar", false, true, false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send 1st config update. sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe("extension_config_discovery.network_filter.foo.config_reload", 1); - test_server_->waitForCounterGe("extension_config_discovery.network_filter.bar.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.network_filter.foo.config_reload", + Ge(1)); + test_server_->waitForCounter("extension_config_discovery.network_filter.bar.config_reload", + Ge(1)); // The two filters drain 3 + 4 bytes. sendDataVerifyResults(7); // Send 2nd config update. sendXdsResponse("foo", "2", 4); sendXdsResponse("bar", "2", 5, false, true); - test_server_->waitForCounterGe("extension_config_discovery.network_filter.foo.config_reload", 2); - test_server_->waitForCounterGe("extension_config_discovery.network_filter.bar.config_reload", 2); + test_server_->waitForCounter("extension_config_discovery.network_filter.foo.config_reload", + Ge(2)); + test_server_->waitForCounter("extension_config_discovery.network_filter.bar.config_reload", + Ge(2)); // The two filters drain 4 + 5 bytes. sendDataVerifyResults(9); } @@ -532,12 +537,12 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoDynamicTwoStaticFilterMixed) addStaticFilter("foobar", 2); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(1)); // filter drain 3 + 2 + 3 + 2 bytes. sendDataVerifyResults(10); } @@ -551,12 +556,12 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, DynamicStaticFilterMixedDiffere addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 2); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(1)); // filter drain 2 + 2 + 2 + 2 bytes. sendDataVerifyResults(8); } @@ -571,7 +576,7 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, DestroyDuringInit) { addDynamicFilter(filter_name_, false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); test_server_.reset(); @@ -589,14 +594,14 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicFailTerminalFilterNotAtEnd addDynamicFilter(filter_name_, false, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse(filter_name_, "1", 5, false, false, true); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_fail", Ge(1)); test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); @@ -608,7 +613,7 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicFailTerminalFilterNotAtEnd } // The network_extension_config_missing stats counter increases by 1. - test_server_->waitForCounterGe("listener.listener_stat.network_extension_config_missing", 1); + test_server_->waitForCounter("listener.listener_stat.network_extension_config_missing", Ge(1)); } // Basic ECDS config dump test with one filter. @@ -619,13 +624,13 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, BasicSuccessWithConfigDump) { addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send 1st config update to have network filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(1)); // Verify ECDS config dump are working correctly. BufferingStreamDecoderPtr response; @@ -664,16 +669,16 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, ConfigDumpWithFilterConfigRemov addDynamicFilter(filter_name_, false, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send config update with TTL 1s. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(1)); // Wait for configuration expired. - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(2)); BufferingStreamDecoderPtr response; EXPECT_EQ("200", request("admin", "GET", "/config_dump?resource=ecds_filters", response)); @@ -695,13 +700,15 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsSameFilterTypeW addDynamicFilter("bar", false, true, false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe("extension_config_discovery.network_filter.foo.config_reload", 1); - test_server_->waitForCounterGe("extension_config_discovery.network_filter.bar.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.network_filter.foo.config_reload", + Ge(1)); + test_server_->waitForCounter("extension_config_discovery.network_filter.bar.config_reload", + Ge(1)); // Verify ECDS config dump are working correctly. BufferingStreamDecoderPtr response; @@ -744,13 +751,15 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsConfigDumpWithR addDynamicFilter("bar", false, true, false, true); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe("extension_config_discovery.network_filter.foo.config_reload", 1); - test_server_->waitForCounterGe("extension_config_discovery.network_filter.bar.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.network_filter.foo.config_reload", + Ge(1)); + test_server_->waitForCounter("extension_config_discovery.network_filter.bar.config_reload", + Ge(1)); BufferingStreamDecoderPtr response; EXPECT_EQ("200", request("admin", "GET", "/config_dump?resource=ecds_filters&name_regex=.a.", response)); @@ -775,14 +784,14 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, ConfigUpdateDoesNotApplyToExist addDynamicFilter(filter_name_, false); initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initializing); // Send config update to have filter drain 5 bytes of data. uint32_t bytes_to_drain = 5; sendXdsResponse(filter_name_, "1", bytes_to_drain); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(1)); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); FakeRawConnectionPtr fake_upstream_connection; @@ -790,8 +799,8 @@ TEST_P(NetworkExtensionDiscoveryIntegrationTest, ConfigUpdateDoesNotApplyToExist // Send 2nd config update to have filter drain 3 bytes of data. sendXdsResponse(filter_name_, "2", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.network_filter." + filter_name_ + ".config_reload", Ge(2)); ASSERT_TRUE(tcp_client->write(data_)); std::string received_data; diff --git a/test/integration/overload_integration_test.cc b/test/integration/overload_integration_test.cc index 6b45429ccd435..693607e47d7bf 100644 --- a/test/integration/overload_integration_test.cc +++ b/test/integration/overload_integration_test.cc @@ -13,6 +13,8 @@ #include "absl/strings/str_cat.h" +using testing::Eq; +using testing::Ge; using testing::InvokeWithoutArgs; namespace Envoy { @@ -78,7 +80,8 @@ TEST_P(OverloadIntegrationTest, CloseStreamsWhenOverloaded) { // Put envoy in overloaded state and check that it drops new requests. // Test both header-only and header+body requests since the code paths are slightly different. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.stop_accepting_requests.active", 1); + test_server_->waitForGauge("overload.envoy.overload_actions.stop_accepting_requests.active", + Eq(1)); Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/test/long/url"}, @@ -107,7 +110,8 @@ TEST_P(OverloadIntegrationTest, CloseStreamsWhenOverloaded) { // Deactivate overload state and check that new requests are accepted. updateResource(0.8); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.stop_accepting_requests.active", 0); + test_server_->waitForGauge("overload.envoy.overload_actions.stop_accepting_requests.active", + Eq(0)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); @@ -134,7 +138,8 @@ TEST_P(OverloadIntegrationTest, AppendLocalOverloadHeader) { // correctly added. Test both header-only and header+body requests since the code paths are // slightly different. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.stop_accepting_requests.active", 1); + test_server_->waitForGauge("overload.envoy.overload_actions.stop_accepting_requests.active", + Eq(1)); Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/test/long/url"}, @@ -185,7 +190,8 @@ TEST_P(OverloadIntegrationTest, DisableKeepaliveWhenOverloaded) { // Put envoy in overloaded state and check that it disables keepalive updateResource(0.8); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.disable_http_keepalive.active", 1); + test_server_->waitForGauge("overload.envoy.overload_actions.disable_http_keepalive.active", + Eq(1)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, @@ -201,7 +207,8 @@ TEST_P(OverloadIntegrationTest, DisableKeepaliveWhenOverloaded) { // Deactivate overload state and check that keepalive is not disabled updateResource(0.7); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.disable_http_keepalive.active", 0); + test_server_->waitForGauge("overload.envoy.overload_actions.disable_http_keepalive.active", + Eq(0)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); response = sendRequestAndWaitForResponse(request_headers, 1, default_response_headers_, 1); @@ -223,8 +230,8 @@ TEST_P(OverloadIntegrationTest, StopAcceptingConnectionsWhenOverloaded) { // Put envoy in overloaded state and check that it doesn't accept the new client connection. updateResource(0.95); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.stop_accepting_connections.active", - 1); + test_server_->waitForGauge("overload.envoy.overload_actions.stop_accepting_connections.active", + Eq(1)); IntegrationStreamDecoderPtr response; if (downstreamProtocol() == Http::CodecClient::Type::HTTP3) { // For HTTP/3, excess connections are force-rejected. @@ -242,8 +249,8 @@ TEST_P(OverloadIntegrationTest, StopAcceptingConnectionsWhenOverloaded) { // Reduce load a little to allow the connection to be accepted. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.stop_accepting_connections.active", - 0); + test_server_->waitForGauge("overload.envoy.overload_actions.stop_accepting_connections.active", + Eq(0)); if (downstreamProtocol() == Http::CodecClient::Type::HTTP3) { codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); response = codec_client_->makeRequestWithBody(default_request_headers_, 10); @@ -273,7 +280,8 @@ TEST_P(OverloadIntegrationTest, BypassOverloadManagerTest) { // Put envoy in overloaded state and validate that it doesn't drop new requests // because we chose to bypass the overload manager on this listener. updateResource(1); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.stop_accepting_requests.active", 1); + test_server_->waitForGauge("overload.envoy.overload_actions.stop_accepting_requests.active", + Eq(1)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto response = @@ -317,7 +325,7 @@ TEST_P(OverloadIntegrationTest, CloseIdleQuicConnectionsWhenOverloaded) { // 1. Establish a QUIC connection codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); // Wait for the connection to be fully established. - test_server_->waitForCounterGe("http.config_test.downstream_cx_http3_total", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_http3_total", Ge(1)); // 2. Send a request and wait for the response to complete. Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, @@ -334,8 +342,8 @@ TEST_P(OverloadIntegrationTest, CloseIdleQuicConnectionsWhenOverloaded) { // 2. Trigger the overload state updateResource(0.95); // Set pressure to 0.95, above the 0.9 saturation threshold - test_server_->waitForGaugeEq("overload.envoy.overload_actions.close_idle_http_connections.active", - 1); + test_server_->waitForGauge("overload.envoy.overload_actions.close_idle_http_connections.active", + Eq(1)); // 3. Advance time to trigger the check_idle_connection_timer (which runs every 100ms). timeSystem().advanceTimeWait(std::chrono::milliseconds(100)); @@ -345,14 +353,14 @@ TEST_P(OverloadIntegrationTest, CloseIdleQuicConnectionsWhenOverloaded) { // Check that the close reason was correct (this stat is incremented in // EnvoyQuicDispatcher) - test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_destroy", Ge(1)); codec_client_->close(); // Deactivate overload state updateResource(0.7); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.close_idle_http_connections.active", - 0); + test_server_->waitForGauge("overload.envoy.overload_actions.close_idle_http_connections.active", + Eq(0)); } class Http2RawFrameOverloadIntegrationTest : public BaseOverloadIntegrationTest, @@ -434,17 +442,18 @@ TEST_P(OverloadScaledTimerIntegrationTest, CloseIdleHttpConnections) { // Set the load so the timer is reduced but not to the minimum value. updateResource(0.8); - test_server_->waitForGaugeGe("overload.envoy.overload_actions.reduce_timeouts.scale_percent", 50); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Ge(50)); // Advancing past the minimum time shouldn't close the connection. timeSystem().advanceTimeWait(std::chrono::seconds(5)); // Increase load so that the minimum time has now elapsed. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.reduce_timeouts.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Eq(100)); // Wait for the proxy to notice and take action for the overload. - test_server_->waitForCounterGe("http.config_test.downstream_cx_idle_timeout", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_idle_timeout", Ge(1)); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); if (GetParam().downstream_protocol == Http::CodecType::HTTP1) { @@ -500,17 +509,18 @@ TEST_P(OverloadScaledTimerIntegrationTest, MaxConnectionDuration) { // Set the load so the timer is reduced but not to the minimum value. updateResource(0.8); - test_server_->waitForGaugeGe("overload.envoy.overload_actions.reduce_timeouts.scale_percent", 50); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Ge(50)); // Advancing past the minimum time shouldn't close the connection. timeSystem().advanceTimeWait(std::chrono::seconds(5)); // Increase load so that the minimum time has now elapsed. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.reduce_timeouts.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Eq(100)); // Wait for the proxy to notice and take action for the overload. - test_server_->waitForCounterGe("http.config_test.downstream_cx_max_duration_reached", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_max_duration_reached", Ge(1)); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); if (GetParam().downstream_protocol == Http::CodecType::HTTP1) { @@ -570,17 +580,18 @@ TEST_P(OverloadScaledTimerIntegrationTest, Http1SafeMaxConnectionDuration) { // Set the load so the timer is reduced but not to the minimum value. updateResource(0.8); - test_server_->waitForGaugeGe("overload.envoy.overload_actions.reduce_timeouts.scale_percent", 50); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Ge(50)); // Advancing past the minimum time shouldn't close the connection. timeSystem().advanceTimeWait(std::chrono::seconds(5)); // Increase load so that the minimum time has now elapsed. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.reduce_timeouts.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Eq(100)); // Wait for the proxy to notice and take action for the overload. - test_server_->waitForCounterGe("http.config_test.downstream_cx_max_duration_reached", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_max_duration_reached", Ge(1)); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); if (GetParam().downstream_protocol == Http::CodecType::HTTP1) { @@ -628,7 +639,8 @@ TEST_P(OverloadScaledTimerIntegrationTest, HTTP3CloseIdleHttpConnectionsDuringHa // Set the load so the timer is reduced but not to the minimum value. updateResource(0.8); - test_server_->waitForGaugeGe("overload.envoy.overload_actions.reduce_timeouts.scale_percent", 50); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Ge(50)); // Create an HTTP connection without finishing the handshake. codec_client_ = makeRawHttpConnection(makeClientConnection((lookupPort("http"))), absl::nullopt, absl::nullopt, @@ -643,8 +655,8 @@ TEST_P(OverloadScaledTimerIntegrationTest, HTTP3CloseIdleHttpConnectionsDuringHa // Increase load more so that the timer is reduced to the minimum. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.reduce_timeouts.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Eq(100)); // Create another HTTP connection without finishing handshake. IntegrationCodecClientPtr codec_client2 = @@ -653,7 +665,7 @@ TEST_P(OverloadScaledTimerIntegrationTest, HTTP3CloseIdleHttpConnectionsDuringHa EXPECT_FALSE(codec_client2->connected()); // Advancing past the minimum time and wait for the proxy to notice and close both connections. timeSystem().advanceTimeWait(std::chrono::seconds(3)); - test_server_->waitForCounterGe("http.config_test.downstream_cx_idle_timeout", 2); + test_server_->waitForCounter("http.config_test.downstream_cx_idle_timeout", Ge(2)); ASSERT_TRUE(codec_client_->waitForDisconnect()); EXPECT_FALSE(codec_client_->sawGoAway()); EXPECT_FALSE(codec_client2->connected()); @@ -692,7 +704,8 @@ TEST_P(OverloadScaledTimerIntegrationTest, HTTP3CloseMaxDurationHttpConnectionsD // Set the load so the timer is reduced but not to the minimum value. updateResource(0.8); - test_server_->waitForGaugeGe("overload.envoy.overload_actions.reduce_timeouts.scale_percent", 50); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Ge(50)); // Create an HTTP connection without finishing the handshake. codec_client_ = makeRawHttpConnection(makeClientConnection((lookupPort("http"))), absl::nullopt, absl::nullopt, @@ -707,8 +720,8 @@ TEST_P(OverloadScaledTimerIntegrationTest, HTTP3CloseMaxDurationHttpConnectionsD // Increase load more so that the timer is reduced to the minimum. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.reduce_timeouts.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Eq(100)); // Create another HTTP connection without finishing handshake. IntegrationCodecClientPtr codec_client2 = @@ -717,7 +730,7 @@ TEST_P(OverloadScaledTimerIntegrationTest, HTTP3CloseMaxDurationHttpConnectionsD EXPECT_FALSE(codec_client2->connected()); // Advancing past the minimum time and wait for the proxy to notice and close both connections. timeSystem().advanceTimeWait(std::chrono::seconds(3)); - test_server_->waitForCounterGe("http.config_test.downstream_cx_max_duration_reached", 2); + test_server_->waitForCounter("http.config_test.downstream_cx_max_duration_reached", Ge(2)); ASSERT_TRUE(codec_client_->waitForDisconnect()); EXPECT_FALSE(codec_client_->sawGoAway()); EXPECT_FALSE(codec_client2->connected()); @@ -749,17 +762,18 @@ TEST_P(OverloadScaledTimerIntegrationTest, CloseIdleHttpStream) { // Set the load so the timer is reduced but not to the minimum value. updateResource(0.8); - test_server_->waitForGaugeGe("overload.envoy.overload_actions.reduce_timeouts.scale_percent", 50); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Ge(50)); // Advancing past the minimum time shouldn't end the stream. timeSystem().advanceTimeWait(std::chrono::seconds(5)); // Increase load so that the minimum time has now elapsed. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.reduce_timeouts.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Eq(100)); // Wait for the proxy to notice and take action for the overload. - test_server_->waitForCounterGe("http.config_test.downstream_rq_idle_timeout", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_idle_timeout", Ge(1)); ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ(response->headers().getStatusValue(), "504"); @@ -782,8 +796,8 @@ TEST_F(Http2RawFrameOverloadIntegrationTest, FlushTimeoutWhenDownstreamBlocked) // Simulate increased load so the timer is reduced to the minimum value. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.reduce_timeouts.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Eq(100)); // Send a headers-only request. sendFrame(Http2Frame::makeRequest(1, /*host=*/"sni.lyft.com", /*path=*/"/test/long/url")); @@ -806,7 +820,7 @@ TEST_F(Http2RawFrameOverloadIntegrationTest, FlushTimeoutWhenDownstreamBlocked) // The client DOES NOT send a window update, so eventually Envoy's flush timer will fire... timeSystem().advanceTimeWait(std::chrono::seconds(2)); - test_server_->waitForCounterGe("http2.tx_flush_timeout", 1); + test_server_->waitForCounter("http2.tx_flush_timeout", Ge(1)); // ... Which will cause the stream to be reset. Http2Frame reset_frame = readFrame(); @@ -814,7 +828,7 @@ TEST_F(Http2RawFrameOverloadIntegrationTest, FlushTimeoutWhenDownstreamBlocked) EXPECT_EQ(reset_frame.type(), Http2Frame::Type::RstStream); tcp_client_->close(); - test_server_->waitForGaugeEq("http.config_test.downstream_rq_active", 0); + test_server_->waitForGauge("http.config_test.downstream_rq_active", Eq(0)); } TEST_P(OverloadScaledTimerIntegrationTest, TlsHandshakeTimeout) { @@ -880,7 +894,8 @@ TEST_P(OverloadScaledTimerIntegrationTest, TlsHandshakeTimeout) { // Set the load so the timer is reduced but not to the minimum value. updateResource(0.8); - test_server_->waitForGaugeGe("overload.envoy.overload_actions.reduce_timeouts.scale_percent", 50); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Ge(50)); // Advancing past the minimum time shouldn't close the connection, but it shouldn't complete it // either. @@ -891,8 +906,8 @@ TEST_P(OverloadScaledTimerIntegrationTest, TlsHandshakeTimeout) { // seconds. Increase the load so that the minimum time has now elapsed. This should cause Envoy to // close the connection on its end. updateResource(0.9); - test_server_->waitForGaugeEq("overload.envoy.overload_actions.reduce_timeouts.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.overload_actions.reduce_timeouts.scale_percent", + Eq(100)); // The bad client will continue attempting to read, eventually noticing the remote close and // closing the connection. @@ -960,22 +975,22 @@ TEST_P(LoadShedPointIntegrationTest, ListenerAcceptShedsLoad) { // Put envoy in overloaded state and check that it rejects the new client connection. updateResource(0.95); - test_server_->waitForGaugeEq("overload.envoy.load_shed_points.tcp_listener_accept.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.load_shed_points.tcp_listener_accept.scale_percent", + Eq(100)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_cx_overload_reject", 1); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_cx_overload_reject", Eq(1)); } else { - test_server_->waitForCounterEq("listener.[__1]_0.downstream_cx_overload_reject", 1); + test_server_->waitForCounter("listener.[__1]_0.downstream_cx_overload_reject", Eq(1)); } ASSERT_TRUE(codec_client_->waitForDisconnect()); // Disable overload, we should allow connections. updateResource(0.80); - test_server_->waitForGaugeEq("overload.envoy.load_shed_points.tcp_listener_accept.scale_percent", - 0); + test_server_->waitForGauge("overload.envoy.load_shed_points.tcp_listener_accept.scale_percent", + Eq(0)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -996,28 +1011,30 @@ TEST_P(LoadShedPointIntegrationTest, AcceptNewHttpStreamShedsLoad) { // Put envoy in overloaded state and check that it sends a local reply for the // new stream. updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http_connection_manager_decode_headers.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http_connection_manager_decode_headers.scale_percent", + Eq(100)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto response_that_will_be_local_reply = codec_client_->makeHeaderOnlyRequest(default_request_headers_); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(1)); ASSERT_TRUE(response_that_will_be_local_reply->waitForEndStream()); EXPECT_EQ(response_that_will_be_local_reply->headers().getStatusValue(), "503"); // Disable overload, Envoy should proxy the request. updateResource(0.80); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http_connection_manager_decode_headers.scale_percent", 0); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http_connection_manager_decode_headers.scale_percent", + Eq(0)); auto response_that_will_be_proxied = codec_client_->makeHeaderOnlyRequest(default_request_headers_); ASSERT_TRUE(response_that_will_be_proxied->waitForEndStream()); EXPECT_EQ(response_that_will_be_proxied->headers().getStatusValue(), "200"); // Should not be incremented as we didn't reject the request. - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(1)); } TEST_P(LoadShedPointIntegrationTest, Http1ServerDispatchAbortShedsLoadWhenNewRequest) { @@ -1034,26 +1051,26 @@ TEST_P(LoadShedPointIntegrationTest, Http1ServerDispatchAbortShedsLoadWhenNewReq threshold: value: 0.90 )EOF")); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 0); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(0)); // Put envoy in overloaded state and check that the dispatch fails. updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http1_server_abort_dispatch.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http1_server_abort_dispatch.scale_percent", Eq(100)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto [encoder, decoder] = codec_client_->startRequest(default_request_headers_); // We should get rejected local reply and connection close. - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(1)); ASSERT_TRUE(decoder->waitForEndStream()); EXPECT_EQ(decoder->headers().getStatusValue(), "500"); ASSERT_TRUE(codec_client_->waitForDisconnect()); // Disable overload, we should allow connections. updateResource(0.80); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http1_server_abort_dispatch.scale_percent", 0); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http1_server_abort_dispatch.scale_percent", Eq(0)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); ASSERT_TRUE(response->waitForEndStream()); @@ -1078,7 +1095,7 @@ TEST_P(LoadShedPointIntegrationTest, threshold: value: 0.90 )EOF")); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 0); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(0)); // Start the 100-continue request codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); @@ -1100,13 +1117,13 @@ TEST_P(LoadShedPointIntegrationTest, // Put envoy in overloaded state and check that it rejects the continuing // dispatch. updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http1_server_abort_dispatch.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http1_server_abort_dispatch.scale_percent", Eq(100)); codec_client_->sendData(*request_encoder_, 10, true); ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ(response->headers().getStatusValue(), "500"); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(1)); } TEST_P(LoadShedPointIntegrationTest, Http1ServerDispatchShouldNotAbortEncodingUpstreamResponse) { @@ -1122,7 +1139,7 @@ TEST_P(LoadShedPointIntegrationTest, Http1ServerDispatchShouldNotAbortEncodingUp threshold: value: 0.90 )EOF")); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 0); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(0)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -1133,13 +1150,13 @@ TEST_P(LoadShedPointIntegrationTest, Http1ServerDispatchShouldNotAbortEncodingUp // Put envoy in overloaded state, the response should succeed. updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http1_server_abort_dispatch.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http1_server_abort_dispatch.scale_percent", Eq(100)); upstream_request_->encodeData(100, true); ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ(response->headers().getStatusValue(), "200"); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 0); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(0)); } TEST_P(LoadShedPointIntegrationTest, Http1ServerDispatchAbortClosesConnectionWhenResponseStarted) { @@ -1155,7 +1172,7 @@ TEST_P(LoadShedPointIntegrationTest, Http1ServerDispatchAbortClosesConnectionWhe threshold: value: 0.90 )EOF")); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 0); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(0)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto encoder_decoder = codec_client_->startRequest(Http::TestRequestHeaderMapImpl{ @@ -1175,15 +1192,15 @@ TEST_P(LoadShedPointIntegrationTest, Http1ServerDispatchAbortClosesConnectionWhe // Put envoy in overloaded state, the next dispatch should fail. updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http1_server_abort_dispatch.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http1_server_abort_dispatch.scale_percent", Eq(100)); Buffer::OwnedImpl data("hello world"); request_encoder_->encodeData(data, true); ASSERT_TRUE(codec_client_->waitForDisconnect()); EXPECT_FALSE(response->complete()); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(1)); } TEST_P(LoadShedPointIntegrationTest, Http2ServerDispatchSendsGoAwayCompletingPendingRequests) { @@ -1204,12 +1221,12 @@ TEST_P(LoadShedPointIntegrationTest, Http2ServerDispatchSendsGoAwayCompletingPen codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto [first_request_encoder, first_request_decoder] = codec_client_->startRequest(default_request_headers_); - test_server_->waitForCounterEq("http.config_test.downstream_rq_http2_total", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_http2_total", Eq(1)); // Put envoy in overloaded state to send GOAWAY frames. updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http2_server_go_away_on_dispatch.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http2_server_go_away_on_dispatch.scale_percent", Eq(100)); auto second_request_decoder = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -1220,7 +1237,7 @@ TEST_P(LoadShedPointIntegrationTest, Http2ServerDispatchSendsGoAwayCompletingPen ASSERT_TRUE(first_request_decoder->waitForEndStream()); EXPECT_TRUE(codec_client_->sawGoAway()); - test_server_->waitForCounterEq("http2.goaway_sent", 1); + test_server_->waitForCounter("http2.goaway_sent", Eq(1)); // The GOAWAY gets submitted with the first created stream as the last stream // that will be processed on this connection, so the second stream's frames @@ -1228,8 +1245,8 @@ TEST_P(LoadShedPointIntegrationTest, Http2ServerDispatchSendsGoAwayCompletingPen EXPECT_FALSE(second_request_decoder->complete()); updateResource(0.80); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http2_server_go_away_on_dispatch.scale_percent", 0); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http2_server_go_away_on_dispatch.scale_percent", Eq(0)); } TEST_P(LoadShedPointIntegrationTest, Http2ServerDispatchSendsGoAwayAndClosesConnection) { @@ -1251,21 +1268,21 @@ TEST_P(LoadShedPointIntegrationTest, Http2ServerDispatchSendsGoAwayAndClosesConn codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto [first_request_encoder, first_request_decoder] = codec_client_->startRequest(default_request_headers_); - test_server_->waitForCounterEq("http.config_test.downstream_rq_http2_total", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_http2_total", Eq(1)); // Put envoy in overloaded state to send GOAWAY frames and close the connection. updateResource(0.95); - test_server_->waitForGaugeEq( + test_server_->waitForGauge( "overload.envoy.load_shed_points.http2_server_go_away_and_close_on_dispatch.scale_percent", - 100); + Eq(100)); auto second_request_decoder = codec_client_->makeHeaderOnlyRequest(default_request_headers_); // The downstream should receive the GOAWAY and the connection should be closed. ASSERT_TRUE(codec_client_->waitForDisconnect()); EXPECT_TRUE(codec_client_->sawGoAway()); - test_server_->waitForCounterEq("http2.goaway_sent", 1); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 1); + test_server_->waitForCounter("http2.goaway_sent", Eq(1)); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(1)); // The second request will not complete. EXPECT_FALSE(second_request_decoder->complete()); @@ -1288,20 +1305,20 @@ TEST_P(LoadShedPointIntegrationTest, HttpConnectionMnagerCloseConnectionCreating codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.hcm_ondata_creating_codec.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.hcm_ondata_creating_codec.scale_percent", Eq(100)); auto encoder_decoder = codec_client_->startRequest(default_request_headers_); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(1)); ASSERT_TRUE(codec_client_->waitForDisconnect()); updateResource(0.80); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.hcm_ondata_creating_codec.scale_percent", 0); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.hcm_ondata_creating_codec.scale_percent", Eq(0)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); - test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(1)); ASSERT_TRUE(response->waitForEndStream()); EXPECT_EQ(response->headers().getStatusValue(), "200"); } @@ -1321,15 +1338,15 @@ TEST_P(LoadShedPointIntegrationTest, HttpDownstreamFilterLoadShed) { // Put envoy in overloaded state and check that it sends a local reply from router. updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http_downstream_filter_check.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http_downstream_filter_check.scale_percent", Eq(100)); auto response_with_local_reply = codec_client_->makeHeaderOnlyRequest(default_request_headers_); ASSERT_TRUE(response_with_local_reply->waitForEndStream()); EXPECT_EQ(response_with_local_reply->headers().getStatusValue(), "503"); updateResource(0.80); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http_downstream_filter_check.scale_percent", 0); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http_downstream_filter_check.scale_percent", Eq(0)); auto response_that_is_proxied = codec_client_->makeHeaderOnlyRequest(default_request_headers_); ASSERT_TRUE(response_that_is_proxied->waitForEndStream()); @@ -1347,8 +1364,8 @@ TEST_P(LoadShedPointIntegrationTest, ConnectionPoolNewConnectionLoadShed) { )EOF")); updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.connection_pool_new_connection.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.connection_pool_new_connection.scale_percent", Eq(100)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -1376,8 +1393,8 @@ TEST_P(LoadShedPointIntegrationTest, ConnectionPoolLoadShedWithExistingConnectio EXPECT_EQ(response1->headers().getStatusValue(), "200"); updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.connection_pool_new_connection.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.connection_pool_new_connection.scale_percent", Eq(100)); auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); waitForNextUpstreamRequest(); @@ -1405,8 +1422,9 @@ TEST_P(LoadShedPointIntegrationTest, HttpConnManagerDoesNotShedLoadWhenBypassed) // Put envoy in overloaded state and check that // the listener that bypasses the overload manager does not reject the new request. updateResource(0.95); - test_server_->waitForGaugeEq( - "overload.envoy.load_shed_points.http_connection_manager_decode_headers.scale_percent", 100); + test_server_->waitForGauge( + "overload.envoy.load_shed_points.http_connection_manager_decode_headers.scale_percent", + Eq(100)); auto codec_client = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto response_that_is_proxied = codec_client->makeHeaderOnlyRequest(default_request_headers_); ASSERT_TRUE(response_that_is_proxied->waitForEndStream()); @@ -1439,8 +1457,8 @@ TEST_P(LoadShedPointIntegrationTest, ListenerAcceptDoesNotShedLoadWhenBypassed) // Put envoy in overloaded state and check that it does not reject the new client connection // on the listener that bypasses overload manager. updateResource(0.95); - test_server_->waitForGaugeEq("overload.envoy.load_shed_points.tcp_listener_accept.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.load_shed_points.tcp_listener_accept.scale_percent", + Eq(100)); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -1451,9 +1469,9 @@ TEST_P(LoadShedPointIntegrationTest, ListenerAcceptDoesNotShedLoadWhenBypassed) // on the other listener though, we should reject the connection codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http_2")))); if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_cx_overload_reject", 1); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_cx_overload_reject", Eq(1)); } else { - test_server_->waitForCounterEq("listener.[__1]_0.downstream_cx_overload_reject", 1); + test_server_->waitForCounter("listener.[__1]_0.downstream_cx_overload_reject", Eq(1)); } ASSERT_TRUE(codec_client_->waitForDisconnect()); } @@ -1477,14 +1495,14 @@ TEST_P(LoadShedPointIntegrationTest, Http3ServerDispatchSendsGoAwayAndClosesConn codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto [first_request_encoder, first_request_decoder] = codec_client_->startRequest(default_request_headers_); - test_server_->waitForCounterEq("http.config_test.downstream_rq_http3_total", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_http3_total", Eq(1)); // Put envoy in overloaded state to send GOAWAY frames and close the // connection. updateResource(0.95); - test_server_->waitForGaugeEq("overload.envoy.load_shed_points.http3_server_go_away_and_close_on_" - "dispatch.scale_percent", - 100); + test_server_->waitForGauge("overload.envoy.load_shed_points.http3_server_go_away_and_close_on_" + "dispatch.scale_percent", + Eq(100)); auto second_request_decoder = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -1515,14 +1533,14 @@ TEST_P(LoadShedPointIntegrationTest, Http3ServerDispatchSendsGoAwayCompletingPen codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto [first_request_encoder, first_request_decoder] = codec_client_->startRequest(default_request_headers_); - test_server_->waitForCounterEq("http.config_test.downstream_rq_http3_total", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_http3_total", Eq(1)); // Put envoy in overloaded state to send GOAWAY frames. updateResource(0.95); - test_server_->waitForGaugeEq( + test_server_->waitForGauge( "overload.envoy.load_shed_points.http3_server_go_away_on_dispatch.scale_" "percent", - 100); + Eq(100)); auto second_request_decoder = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -1543,10 +1561,10 @@ TEST_P(LoadShedPointIntegrationTest, Http3ServerDispatchSendsGoAwayCompletingPen ASSERT_TRUE(codec_client_->waitForDisconnect()); updateResource(0.80); - test_server_->waitForGaugeEq( + test_server_->waitForGauge( "overload.envoy.load_shed_points.http3_server_go_away_on_dispatch.scale_" "percent", - 0); + Eq(0)); } // Verifies that worker thread watchdog configuration is correctly applied and triggers megamiss @@ -1573,9 +1591,9 @@ TEST_P(OverloadIntegrationTest, WorkerWatchdogMegaMiss) { auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); // Verify that the worker-specific megamiss counter is incremented. - test_server_->waitForCounterGe("server.worker_0.watchdog_mega_miss", 1); + test_server_->waitForCounter("server.worker_0.watchdog_mega_miss", Ge(1)); // Verify that the global workers megamiss counter is incremented. - test_server_->waitForCounterGe("workers.watchdog_mega_miss", 1); + test_server_->waitForCounter("workers.watchdog_mega_miss", Ge(1)); EXPECT_TRUE(response->waitForEndStream(std::chrono::seconds(20))); EXPECT_TRUE(response->complete()); diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 91a3ba50f52cd..a78d3ac715c4b 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -50,6 +50,8 @@ #include "source/common/quic/client_connection_factory_impl.h" #endif +using testing::Eq; +using testing::Ge; using testing::HasSubstr; using testing::Not; @@ -138,7 +140,7 @@ TEST_P(ProtocolIntegrationTest, UpstreamRequestsPerConnectionMetricHandshakeFail codec_client_->close(); // Wait for connection failure to be recorded - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_connect_fail", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_connect_fail", Ge(1)); // Verify that NO upstream_rq_per_cx histogram samples were recorded // because hasHandshakeCompleted() returned false (connection never established) @@ -1342,7 +1344,7 @@ TEST_P(ProtocolIntegrationTest, RetryStreamingCancelDueToBufferOverflow) { EXPECT_TRUE(response->complete()); EXPECT_EQ("507", response->headers().getStatusValue()); - test_server_->waitForCounterEq("cluster.cluster_0.retry_or_shadow_abandoned", 1); + test_server_->waitForCounter("cluster.cluster_0.retry_or_shadow_abandoned", Eq(1)); cleanupUpstreamAndDownstream(); } @@ -1693,7 +1695,7 @@ TEST_P(ProtocolIntegrationTest, HittingEncoderFilterLimit) { // sure this path does standard HCM header transformations. EXPECT_TRUE(response->headers().Date() != nullptr); EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("500")); - test_server_->waitForCounterEq("http.config_test.downstream_rq_5xx", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_5xx", Eq(1)); } // The downstream connection is closed when it is read disabled, and on OSX the @@ -1809,7 +1811,7 @@ TEST_P(ProtocolIntegrationTest, BasicMaxStreamDuration) { ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_max_duration_reached", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_max_duration_reached", Ge(1)); if (downstream_protocol_ == Http::CodecType::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); @@ -1832,7 +1834,7 @@ TEST_P(ProtocolIntegrationTest, BasicDynamicMaxStreamDuration) { ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_max_duration_reached", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_max_duration_reached", Ge(1)); if (downstream_protocol_ == Http::CodecType::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); @@ -1882,7 +1884,7 @@ TEST_P(ProtocolIntegrationTest, MaxStreamDurationWithRetryPolicy) { ASSERT_TRUE(upstream_request_->waitForReset()); } - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_max_duration_reached", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_max_duration_reached", Ge(1)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); @@ -1931,11 +1933,11 @@ TEST_P(ProtocolIntegrationTest, MaxStreamDurationWithRetryPolicyWhenRetryUpstrea ASSERT_TRUE(upstream_request_->waitForReset()); } - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_max_duration_reached", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_max_duration_reached", Ge(1)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_max_duration_reached", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_max_duration_reached", Ge(2)); if (downstream_protocol_ == Http::CodecType::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); ASSERT_TRUE(response->complete()); @@ -2221,7 +2223,7 @@ TEST_P(ProtocolIntegrationTest, 304WithBody) { // Ideally if we got the body with the headers we would instead reset the // stream, but it turns out that's complicated so instead we consistently // forward the headers and error out after. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_protocol_error", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_protocol_error", Ge(1)); } // Only for HTTP/2 and Http/3, where streams are ended with an explicit end-stream so we @@ -2437,7 +2439,7 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidContentLength) { if (downstream_protocol_ == Http::CodecType::HTTP1) { ASSERT_TRUE(response->complete()); EXPECT_EQ("400", response->headers().getStatusValue()); - test_server_->waitForCounterGe("http.config_test.downstream_rq_4xx", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_4xx", Ge(1)); } else { ASSERT_TRUE(response->reset()); EXPECT_EQ(Http::StreamResetReason::ConnectionTermination, response->resetReason()); @@ -3250,20 +3252,20 @@ TEST_P(ProtocolIntegrationTest, Http1SafeConnDurationTimeout) { EXPECT_TRUE(upstream_request_->complete()); EXPECT_TRUE(response->complete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_200", Ge(1)); if (downstream_protocol_ != Http::CodecType::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect(std::chrono::milliseconds(10000))); - test_server_->waitForCounterGe("http.config_test.downstream_cx_max_duration_reached", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_max_duration_reached", Ge(1)); EXPECT_EQ(test_server_->gauge("http.config_test.downstream_cx_http1_soft_drain")->value(), 0); // The rest of the test is only for http1. return; } // Wait until after the max connection duration - test_server_->waitForCounterGe("http.config_test.downstream_cx_max_duration_reached", 1); - test_server_->waitForGaugeGe("http.config_test.downstream_cx_http1_soft_drain", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_max_duration_reached", Ge(1)); + test_server_->waitForGauge("http.config_test.downstream_cx_http1_soft_drain", Ge(1)); // Envoy now waits for one more request/response over this connection before sending the // connection:close header and closing the connection. No matter how long the request/response @@ -3307,11 +3309,11 @@ TEST_P(ProtocolIntegrationTest, ConnDurationTimeoutBasic) { EXPECT_TRUE(upstream_request_->complete()); EXPECT_TRUE(response->complete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_200", Ge(1)); ASSERT_TRUE(codec_client_->waitForDisconnect(std::chrono::milliseconds(10000))); - test_server_->waitForCounterGe("http.config_test.downstream_cx_max_duration_reached", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_max_duration_reached", Ge(1)); } // Test inflight request is processed correctly when timeout fires during request processing. @@ -3327,7 +3329,7 @@ TEST_P(ProtocolIntegrationTest, ConnDurationInflightRequest) { waitForNextUpstreamRequest(); // block and wait for counter to increase - test_server_->waitForCounterGe("http.config_test.downstream_cx_max_duration_reached", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_max_duration_reached", Ge(1)); // ensure request processed correctly upstream_request_->encodeHeaders(default_response_headers_, false); @@ -3336,8 +3338,8 @@ TEST_P(ProtocolIntegrationTest, ConnDurationInflightRequest) { EXPECT_TRUE(upstream_request_->complete()); EXPECT_TRUE(response->complete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_200", Ge(1)); ASSERT_TRUE(codec_client_->waitForDisconnect(std::chrono::milliseconds(10000))); } @@ -3352,7 +3354,7 @@ TEST_P(DownstreamProtocolIntegrationTest, ConnDurationTimeoutNoHttpRequest) { codec_client_ = makeHttpConnection(lookupPort("http")); ASSERT_TRUE(codec_client_->waitForDisconnect(std::chrono::milliseconds(10000))); - test_server_->waitForCounterGe("http.config_test.downstream_cx_max_duration_reached", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_max_duration_reached", Ge(1)); } TEST_P(ProtocolIntegrationTest, TestPreconnect) { @@ -3391,8 +3393,8 @@ TEST_P(ProtocolIntegrationTest, TestPreconnect) { // Preconnect is set to 2. Http 1 allows 1 request per connection so it requires 2 connections, // and http is configured for 4 so it requires only 1 connection. uint32_t expected_upstream_cx = (upstreamProtocol() == Http::CodecType::HTTP1) ? 2 : 1; - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", expected_upstream_cx); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", expected_upstream_cx); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(expected_upstream_cx)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(expected_upstream_cx)); // Make several non-concurrent requests. The concurrency is only 1, so there should only be 1 or 2 // upstream connections, already established by the first request. @@ -3401,8 +3403,8 @@ TEST_P(ProtocolIntegrationTest, TestPreconnect) { ASSERT_TRUE(response->waitForEndStream()); } - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", expected_upstream_cx); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", expected_upstream_cx); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(expected_upstream_cx)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(expected_upstream_cx)); if (GetParam().downstream_protocol == Http::CodecType::HTTP1) { // The rest of the test requires multiple concurrent requests and isn't written to use multiple @@ -3434,9 +3436,9 @@ TEST_P(ProtocolIntegrationTest, TestPreconnect) { ? (concurrent_requests * 2) : (concurrent_requests * 2 / 4); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", expected_upstream_cx); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", expected_upstream_cx); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", concurrent_requests); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(expected_upstream_cx)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(expected_upstream_cx)); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(concurrent_requests)); for (auto& response : responses) { codec_client_->sendData(response.first, 0, true); @@ -3457,7 +3459,7 @@ TEST_P(DownstreamProtocolIntegrationTest, BasicMaxStreamTimeout) { ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); - test_server_->waitForCounterGe("http.config_test.downstream_rq_max_duration_reached", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_max_duration_reached", Ge(1)); ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ("408", response->headers().getStatusValue()); @@ -3488,7 +3490,7 @@ TEST_P(ProtocolIntegrationTest, MaxStreamTimeoutWhenRequestIsNotComplete) { ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); - test_server_->waitForCounterGe("http.config_test.downstream_rq_max_duration_reached", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_max_duration_reached", Ge(1)); ASSERT_TRUE(response->waitForEndStream()); EXPECT_TRUE(upstream_request_->complete()); @@ -3553,7 +3555,7 @@ TEST_P(DownstreamProtocolIntegrationTest, MaxRequestsPerConnectionVsMaxConnectio EXPECT_EQ(test_server_->counter("http.config_test.downstream_cx_max_requests_reached")->value(), 0); - test_server_->waitForCounterGe("http.config_test.downstream_cx_max_duration_reached", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_max_duration_reached", Ge(1)); // http1 is not closed at this point because envoy needs to send a response with the // connection:close response header to be able to safely close the connection. For other protocols // it's safe for envoy to just close the connection, so they do so. @@ -3613,7 +3615,7 @@ TEST_P(DownstreamProtocolIntegrationTest, MaxRequestsPerConnectionVsMaxStreamDur 1); // Don't send a response. HCM should sendLocalReply after max stream duration has elapsed. - test_server_->waitForCounterGe("http.config_test.downstream_rq_max_duration_reached", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_max_duration_reached", Ge(1)); if (downstream_protocol_ == Http::CodecType::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); @@ -4357,7 +4359,7 @@ TEST_P( // The request will go through all of Envoy's stream layer, but not be // serialized on the wire due to flow control. codec_client_->sendData(encoder, request_size, true); - test_server_->waitForCounterGe("http.config_test.downstream_cx_rx_bytes_total", request_size); + test_server_->waitForCounter("http.config_test.downstream_cx_rx_bytes_total", Ge(request_size)); // Now that the downstream request has fully made it to Envoy, encode the response. upstream_request_->encodeHeaders( @@ -4370,14 +4372,14 @@ TEST_P( response->waitForHeaders(); // HCM thinks we have no active stream now. - test_server_->waitForCounterEq("http.config_test.downstream_rq_completed", 1); - test_server_->waitForGaugeEq("http.config_test.downstream_rq_active", 0); + test_server_->waitForCounter("http.config_test.downstream_rq_completed", Eq(1)); + test_server_->waitForGauge("http.config_test.downstream_rq_active", Eq(0)); // There is no reset for the cluster yet. if (upstreamProtocol() == Envoy::Http::CodecType::HTTP2) { - test_server_->waitForCounterEq("cluster.cluster_0.http2.rx_reset", 0); + test_server_->waitForCounter("cluster.cluster_0.http2.rx_reset", Eq(0)); } else { - test_server_->waitForCounterEq("cluster.cluster_0.http3.rx_reset", 0); + test_server_->waitForCounter("cluster.cluster_0.http3.rx_reset", Eq(0)); } // Send the reset stream to Envoy's upstream codec client. Envoy protocol @@ -4386,9 +4388,9 @@ TEST_P( upstream_request_->encodeResetStream(); if (upstreamProtocol() == Envoy::Http::CodecType::HTTP2) { - test_server_->waitForCounterEq("cluster.cluster_0.http2.rx_reset", 1); + test_server_->waitForCounter("cluster.cluster_0.http2.rx_reset", Eq(1)); } else { - test_server_->waitForCounterEq("cluster.cluster_0.http3.rx_reset", 1); + test_server_->waitForCounter("cluster.cluster_0.http3.rx_reset", Eq(1)); } } @@ -5048,7 +5050,7 @@ TEST_P(DownstreamProtocolIntegrationTest, HandleDownstreamSocketFail) { ? "listener.127.0.0.1_0.http3.downstream.tx." : "listener.[__1]_0.http3.downstream.tx."; std::string error_code = "quic_connection_close_error_code_QUIC_PACKET_WRITE_ERROR"; - test_server_->waitForCounterEq(absl::StrCat(counter_scope, error_code), 1); + test_server_->waitForCounter(absl::StrCat(counter_scope, error_code), Eq(1)); codec_client_->close(); } else { ASSERT_TRUE(codec_client_->waitForDisconnect()); @@ -5150,9 +5152,9 @@ TEST_P(ProtocolIntegrationTest, HandleUpstreamSocketCreationFail) { codec_client_ = makeHttpConnection(lookupPort("http")); if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForCounterGe("listener.127.0.0.1_0.downstream_cx_total", 1); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_cx_total", Ge(1)); } else { - test_server_->waitForCounterGe("listener.[__1]_0.downstream_cx_total", 1); + test_server_->waitForCounter("listener.[__1]_0.downstream_cx_total", Ge(1)); } EXPECT_ENVOY_BUG( @@ -5253,7 +5255,7 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidRequestHeaderName) { if (downstream_protocol_ == Http::CodecType::HTTP1) { ASSERT_TRUE(response->complete()); EXPECT_EQ("400", response->headers().getStatusValue()); - test_server_->waitForCounterGe("http.config_test.downstream_rq_4xx", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_4xx", Ge(1)); } else { // H/2 codec does not send 400 on protocol errors EXPECT_EQ(Http::StreamResetReason::ConnectionTermination, response->resetReason()); @@ -5286,7 +5288,7 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidRequestHeaderNameStreamError) { if (downstream_protocol_ == Http::CodecType::HTTP1) { ASSERT_TRUE(response->complete()); EXPECT_EQ("400", response->headers().getStatusValue()); - test_server_->waitForCounterGe("http.config_test.downstream_rq_4xx", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_4xx", Ge(1)); } else { // H/2 codec does not send 400 on protocol errors EXPECT_EQ(Http::StreamResetReason::ProtocolError, response->resetReason()); @@ -5314,7 +5316,7 @@ TEST_P(ProtocolIntegrationTest, InvalidResponseHeaderName) { ASSERT_TRUE(response->complete()); EXPECT_EQ("502", response->headers().getStatusValue()); - test_server_->waitForCounterGe("http.config_test.downstream_rq_5xx", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_5xx", Ge(1)); if (upstreamProtocol() == Http::CodecType::HTTP3) { EXPECT_EQ(waitForAccessLog(access_log_name_), "upstream_reset_before_response_started{protocol_" @@ -5347,7 +5349,7 @@ TEST_P(ProtocolIntegrationTest, InvalidResponseHeaderNameStreamError) { ASSERT_TRUE(response->complete()); EXPECT_EQ("502", response->headers().getStatusValue()); - test_server_->waitForCounterGe("http.config_test.downstream_rq_5xx", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_5xx", Ge(1)); std::string error_message = upstreamProtocol() == Http::CodecType::HTTP3 ? "upstream_reset_before_response_started{protocol_error|QUIC_" @@ -5925,7 +5927,7 @@ TEST_P(DownstreamProtocolIntegrationTest, DownstreamCxStats) { EXPECT_EQ("200", response->headers().getStatusValue()); EXPECT_EQ(512U, response->body().size()); - test_server_->waitForCounterGe("http.config_test.downstream_cx_tx_bytes_total", 512); + test_server_->waitForCounter("http.config_test.downstream_cx_tx_bytes_total", Ge(512)); } // When upstream protocol is HTTP1, an OPTIONS request with no body will not diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index bea58ca4ad299..e49111ec2a2d8 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -12,6 +12,8 @@ namespace Envoy { using Extensions::TransportSockets::Tls::ContextImplPeer; +using testing::Eq; +using testing::Ge; namespace Quic { @@ -96,7 +98,7 @@ TEST_P(QuicHttpIntegrationTest, RuntimeEnableDraft29) { upstream_request_->encodeHeaders(default_response_headers_, true); ASSERT_TRUE(response->waitForEndStream()); codec_client_->close(); - test_server_->waitForCounterEq("http3.quic_version_h3_29", 1u); + test_server_->waitForCounter("http3.quic_version_h3_29", Eq(1u)); } TEST_P(QuicHttpIntegrationTest, CertCompressionEnabled) { @@ -146,17 +148,17 @@ TEST_P(QuicHttpIntegrationTest, ZeroRtt) { // Close the second connection. codec_client_->close(); if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForCounterEq( + test_server_->waitForCounter( "listener.127.0.0.1_0.http3.downstream.rx.quic_connection_close_error_" "code_QUIC_NO_ERROR", - 2u); + Eq(2u)); } else { - test_server_->waitForCounterEq("listener.[__1]_0.http3.downstream.rx.quic_connection_close_" - "error_code_QUIC_NO_ERROR", - 2u); + test_server_->waitForCounter("listener.[__1]_0.http3.downstream.rx.quic_connection_close_" + "error_code_QUIC_NO_ERROR", + Eq(2u)); } - test_server_->waitForCounterEq("http3.quic_version_rfc_v1", 2u); + test_server_->waitForCounter("http3.quic_version_rfc_v1", Eq(2u)); // Start the third connection. codec_client_ = makeRawHttp3Connection(makeClientConnection((lookupPort("http"))), absl::nullopt, @@ -588,7 +590,7 @@ TEST_P(QuicHttpIntegrationTest, ResetRequestWithInvalidCharacter) { ? "listener.127.0.0.1_0.http3.downstream.tx." : "listener.[__1]_0.http3.downstream.tx."; std::string error_code = "quic_connection_close_error_code_QUIC_HTTP_FRAME_ERROR"; - test_server_->waitForCounterEq(absl::StrCat(counter_scope, error_code), 1U); + test_server_->waitForCounter(absl::StrCat(counter_scope, error_code), Eq(1U)); } TEST_P(QuicHttpIntegrationTest, Http3ClientKeepalive) { @@ -821,7 +823,7 @@ TEST_P(QuicHttpIntegrationTest, MultipleNetworkFilters) { codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); waitForNextUpstreamRequest(); - test_server_->waitForCounterEq("test_network_filter.on_new_connection", 1); + test_server_->waitForCounter("test_network_filter.on_new_connection", Eq(1)); EXPECT_EQ(test_server_->counter("test_network_filter.on_data")->value(), 0); codec_client_->close(); } @@ -1339,9 +1341,9 @@ TEST_P(QuicInplaceLdsIntegrationTest, ReloadConfigUpdateNonDefaultFilterChain) { }); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); - test_server_->waitForGaugeGe("listener_manager.total_filter_chains_draining", 1); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 0); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Ge(1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(0)); makeRequestAndWaitForResponse(*codec_client_0); EXPECT_TRUE(codec_client_1->sawGoAway()); codec_client_1->close(); @@ -1353,8 +1355,8 @@ TEST_P(QuicInplaceLdsIntegrationTest, ReloadConfigUpdateNonDefaultFilterChain) { // Update filter chain again to add back filter_chain_1. config_helper_.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 2); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 3); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(2)); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(3)); auto codec_client_3 = makeHttpConnection(makeClientConnectionWithHost(lookupPort("http"), "lyft.com")); @@ -1381,10 +1383,10 @@ TEST_P(QuicInplaceLdsIntegrationTest, ReloadConfigUpdateDefaultFilterChain) { }); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); - test_server_->waitForGaugeGe("listener_manager.total_filter_chains_draining", 1); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Ge(1)); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 0); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(0)); // This connection should pick up the default filter chain. auto codec_client_default = makeHttpConnection(makeClientConnectionWithHost(lookupPort("http"), "lyft.com")); @@ -1401,9 +1403,9 @@ TEST_P(QuicInplaceLdsIntegrationTest, ReloadConfigUpdateDefaultFilterChain) { }); new_config_helper1.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 2); - test_server_->waitForGaugeGe("listener_manager.total_filter_chains_draining", 1); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 0); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(2)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Ge(1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(0)); makeRequestAndWaitForResponse(*codec_client_0); EXPECT_TRUE(codec_client_default->sawGoAway()); @@ -1423,9 +1425,9 @@ TEST_P(QuicInplaceLdsIntegrationTest, ReloadConfigUpdateDefaultFilterChain) { }); new_config_helper2.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 3); - test_server_->waitForGaugeGe("listener_manager.total_filter_chains_draining", 1); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 0); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(3)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Ge(1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(0)); makeRequestAndWaitForResponse(*codec_client_0); codec_client_0->close(); @@ -1448,10 +1450,10 @@ TEST_P(QuicInplaceLdsIntegrationTest, EnableAndDisableEarlyData) { configureEarlyData(false, &new_config_helper); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); - test_server_->waitForGaugeGe("listener_manager.total_filter_chains_draining", 1); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Ge(1)); - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 0); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(0)); // The 2nd connection should try to do 0-RTT but get rejected and QUICHE will transparently retry // the request after handshake completes. auto codec_client_2 = makeRawHttp3Connection( @@ -1475,9 +1477,9 @@ TEST_P(QuicInplaceLdsIntegrationTest, StatelessResetOldConnection) { designated_connection_ids_.push_back(quic_connection_->connection_id()); codec_client0->close(); if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForGaugeEq("listener.127.0.0.1_0.downstream_cx_active", 0u); + test_server_->waitForGauge("listener.127.0.0.1_0.downstream_cx_active", Eq(0u)); } else { - test_server_->waitForGaugeEq("listener.[__1]_0.downstream_cx_active", 0u); + test_server_->waitForGauge("listener.[__1]_0.downstream_cx_active", Eq(0u)); } // This new connection would be reset. @@ -1489,11 +1491,11 @@ TEST_P(QuicInplaceLdsIntegrationTest, StatelessResetOldConnection) { static_cast(codec_client1->connection())->error(); EXPECT_TRUE(error == quic::QUIC_NETWORK_IDLE_TIMEOUT || error == quic::QUIC_HANDSHAKE_TIMEOUT); if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForCounterGe( - "listener.127.0.0.1_0.quic.dispatcher.stateless_reset_packets_sent", 1u); + test_server_->waitForCounter( + "listener.127.0.0.1_0.quic.dispatcher.stateless_reset_packets_sent", Ge(1u)); } else { - test_server_->waitForCounterGe("listener.[__1]_0.quic.dispatcher.stateless_reset_packets_sent", - 1u); + test_server_->waitForCounter("listener.[__1]_0.quic.dispatcher.stateless_reset_packets_sent", + Ge(1u)); } } @@ -1566,8 +1568,8 @@ TEST_P(QuicHttpIntegrationSPATest, UsesPreferredAddress) { // Most v6 platform doesn't support two loopback interfaces. EXPECT_EQ("127.0.0.2", quic_connection_->peer_address().host().ToString()); EXPECT_EQ("127.0.0.1", quic_connection_->self_address().host().ToString()); - test_server_->waitForCounterGe( - "listener.0.0.0.0_0.quic.connection.num_packets_rx_on_preferred_address", 2u); + test_server_->waitForCounter( + "listener.0.0.0.0_0.quic.connection.num_packets_rx_on_preferred_address", Ge(2u)); } } @@ -1658,8 +1660,8 @@ TEST_P(QuicHttpIntegrationSPATest, UsesPreferredAddressDNAT) { if (version_ == Network::Address::IpVersion::v4) { // Most v6 platform doesn't support two loopback interfaces. EXPECT_EQ("1.2.3.4", quic_connection_->peer_address().host().ToString()); - test_server_->waitForCounterGe( - "listener.0.0.0.0_0.quic.connection.num_packets_rx_on_preferred_address", 2u); + test_server_->waitForCounter( + "listener.0.0.0.0_0.quic.connection.num_packets_rx_on_preferred_address", Ge(2u)); } // Close connections before `SocketInterfaceSwap` goes out of scope to ensure packets aren't @@ -1786,8 +1788,8 @@ TEST_P(QuicHttpIntegrationSPATest, UsesPreferredAddressDualStack) { ASSERT_TRUE(response->complete()); EXPECT_EQ("127.0.0.2", quic_connection_->peer_address().host().ToString()); - test_server_->waitForCounterGe( - "listener.[__]_0.quic.connection.num_packets_rx_on_preferred_address", 2u); + test_server_->waitForCounter( + "listener.[__]_0.quic.connection.num_packets_rx_on_preferred_address", Ge(2u)); } TEST_P(QuicHttpIntegrationTest, PreferredAddressDroppedByIncompatibleListenerFilter) { @@ -1971,7 +1973,8 @@ TEST_P(QuicHttpIntegrationTest, ConnectionDebugVisitor) { quic::ConnectionCloseSourceToString(quic::ConnectionCloseSource::FROM_PEER)), { quic_session->close(Network::ConnectionCloseType::NoFlush); - test_server_->waitForGaugeEq(fmt::format("listener.{}.downstream_cx_active", listener), 0u); + test_server_->waitForGauge(fmt::format("listener.{}.downstream_cx_active", listener), + Eq(0u)); }); } diff --git a/test/integration/quic_http_integration_test.h b/test/integration/quic_http_integration_test.h index f208cf616cbba..bffc2cd221049 100644 --- a/test/integration/quic_http_integration_test.h +++ b/test/integration/quic_http_integration_test.h @@ -40,6 +40,7 @@ #include "quiche/quic/test_tools/quic_session_peer.h" #include "quiche/quic/test_tools/quic_test_utils.h" +using testing::Eq; namespace Envoy { namespace Quic { class CodecClientCallbacksForTest : public Http::CodecClientCallbacks { @@ -466,24 +467,25 @@ class QuicHttpIntegrationTestBase : public HttpIntegrationTest { constexpr auto timeout_first = std::chrono::seconds(15 * TIMEOUT_FACTOR); constexpr auto timeout_subsequent = std::chrono::milliseconds(10 * TIMEOUT_FACTOR); if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_cx_total", 8u, timeout_first); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_cx_total", Eq(8u), + timeout_first); } else { - test_server_->waitForCounterEq("listener.[__1]_0.downstream_cx_total", 8u, timeout_first); + test_server_->waitForCounter("listener.[__1]_0.downstream_cx_total", Eq(8u), timeout_first); } for (size_t i = 0; i < concurrency_; ++i) { if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForGaugeEq( - fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_active", i), 1u, + test_server_->waitForGauge( + fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_active", i), Eq(1u), timeout_subsequent); - test_server_->waitForCounterEq( - fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_total", i), 1u, + test_server_->waitForCounter( + fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_total", i), Eq(1u), timeout_subsequent); } else { - test_server_->waitForGaugeEq( - fmt::format("listener.[__1]_0.worker_{}.downstream_cx_active", i), 1u, + test_server_->waitForGauge( + fmt::format("listener.[__1]_0.worker_{}.downstream_cx_active", i), Eq(1u), timeout_subsequent); - test_server_->waitForCounterEq( - fmt::format("listener.[__1]_0.worker_{}.downstream_cx_total", i), 1u, + test_server_->waitForCounter( + fmt::format("listener.[__1]_0.worker_{}.downstream_cx_total", i), Eq(1u), timeout_subsequent); } } @@ -570,25 +572,25 @@ class QuicHttpMultiAddressesIntegrationTest constexpr auto timeout_first = std::chrono::seconds(15 * TIMEOUT_FACTOR); constexpr auto timeout_subsequent = std::chrono::milliseconds(10 * TIMEOUT_FACTOR); if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForCounterEq("listener.127.0.0.1_0.downstream_cx_total", 16u, - timeout_first); + test_server_->waitForCounter("listener.127.0.0.1_0.downstream_cx_total", Eq(16u), + timeout_first); } else { - test_server_->waitForCounterEq("listener.[__1]_0.downstream_cx_total", 16u, timeout_first); + test_server_->waitForCounter("listener.[__1]_0.downstream_cx_total", Eq(16u), timeout_first); } for (size_t i = 0; i < concurrency_; ++i) { if (version_ == Network::Address::IpVersion::v4) { - test_server_->waitForGaugeEq( - fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_active", i), 2u, + test_server_->waitForGauge( + fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_active", i), Eq(2u), timeout_subsequent); - test_server_->waitForCounterEq( - fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_total", i), 2u, + test_server_->waitForCounter( + fmt::format("listener.127.0.0.1_0.worker_{}.downstream_cx_total", i), Eq(2u), timeout_subsequent); } else { - test_server_->waitForGaugeEq( - fmt::format("listener.[__1]_0.worker_{}.downstream_cx_active", i), 2u, + test_server_->waitForGauge( + fmt::format("listener.[__1]_0.worker_{}.downstream_cx_active", i), Eq(2u), timeout_subsequent); - test_server_->waitForCounterEq( - fmt::format("listener.[__1]_0.worker_{}.downstream_cx_total", i), 2u, + test_server_->waitForCounter( + fmt::format("listener.[__1]_0.worker_{}.downstream_cx_total", i), Eq(2u), timeout_subsequent); } } diff --git a/test/integration/redirect_integration_test.cc b/test/integration/redirect_integration_test.cc index 947f5d815142a..b0220a72c2e9f 100644 --- a/test/integration/redirect_integration_test.cc +++ b/test/integration/redirect_integration_test.cc @@ -783,7 +783,8 @@ TEST_P(RedirectIntegrationTest, InternalRedirectHandledByDirectResponse) { ASSERT_TRUE(response->waitForEndStream()); ASSERT_TRUE(response->complete()); EXPECT_EQ("204", response->headers().getStatusValue()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_internal_redirect_succeeded_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_internal_redirect_succeeded_total", + testing::Eq(1)); // 302 was never returned downstream EXPECT_EQ(0, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_2xx")->value()); diff --git a/test/integration/rtds_integration_test.cc b/test/integration/rtds_integration_test.cc index c245b15f68843..f0d1edaabb3c0 100644 --- a/test/integration/rtds_integration_test.cc +++ b/test/integration/rtds_integration_test.cc @@ -7,6 +7,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -206,7 +208,7 @@ TEST_P(RtdsIntegrationTest, FileRtdsReload) { TestEnvironment::createSymlink(temp_path + "/data_2", temp_path + "/..data.new"); TestEnvironment::renameFile(temp_path + "/..data.new", temp_path + "/..data"); - test_server_->waitForCounterGe("runtime.load_success", initial_load_success_ + 1); + test_server_->waitForCounter("runtime.load_success", Ge(initial_load_success_ + 1)); EXPECT_EQ(1, test_server_->gauge("runtime.num_layers")->value()); EXPECT_EQ(1, test_server_->gauge("runtime.num_keys")->value()); EXPECT_EQ("bar", getRuntimeKey("foo")); @@ -233,7 +235,7 @@ TEST_P(RtdsIntegrationTest, RtdsReload) { )EOF"); sendDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {some_rtds_layer}, {some_rtds_layer}, {}, "1"); - test_server_->waitForCounterGe("runtime.load_success", initial_load_success_ + 1); + test_server_->waitForCounter("runtime.load_success", Ge(initial_load_success_ + 1)); EXPECT_EQ("bar", getRuntimeKey("foo")); EXPECT_EQ("yar", getRuntimeKey("bar")); @@ -253,7 +255,7 @@ TEST_P(RtdsIntegrationTest, RtdsReload) { )EOF"); sendDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {some_rtds_layer}, {some_rtds_layer}, {}, "2"); - test_server_->waitForCounterGe("runtime.load_success", initial_load_success_ + 2); + test_server_->waitForCounter("runtime.load_success", Ge(initial_load_success_ + 2)); EXPECT_EQ("whatevs", getRuntimeKey("foo")); EXPECT_EQ("yar", getRuntimeKey("bar")); @@ -289,7 +291,7 @@ TEST_P(RtdsIntegrationTest, RtdsUpdate) { sendDiscoveryResponse(Config::TestTypeUrl::get().Runtime, {some_rtds_layer}, {some_rtds_layer}, {}, "1", {{"test", Protobuf::Any()}}); - test_server_->waitForCounterGe("runtime.load_success", initial_load_success_ + 1); + test_server_->waitForCounter("runtime.load_success", Ge(initial_load_success_ + 1)); EXPECT_EQ("bar", getRuntimeKey("foo")); EXPECT_EQ("yar", getRuntimeKey("bar")); @@ -333,7 +335,7 @@ TEST_P(RtdsIntegrationTest, RtdsAfterAsyncPrimaryClusterInitialization) { // Respond to the initial health check, which should complete initialization of primary clusters. waitForNextUpstreamRequest(); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); - test_server_->waitForGaugeEq("cluster.dummy_cluster.membership_healthy", 1); + test_server_->waitForGauge("cluster.dummy_cluster.membership_healthy", Eq(1)); // After this xDS connection should be established. Verify that dynamic runtime values are loaded. acceptXdsConnection(); @@ -347,7 +349,7 @@ TEST_P(RtdsIntegrationTest, RtdsAfterAsyncPrimaryClusterInitialization) { )EOF"); sendDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {some_rtds_layer}, {some_rtds_layer}, {}, "1"); - test_server_->waitForCounterGe("runtime.load_success", initial_load_success_ + 1); + test_server_->waitForCounter("runtime.load_success", Ge(initial_load_success_ + 1)); EXPECT_EQ("bar", getRuntimeKey("foo")); EXPECT_EQ("yar", getRuntimeKey("bar")); diff --git a/test/integration/scoped_rds_integration_test.cc b/test/integration/scoped_rds_integration_test.cc index 6d971d7bec667..dbe6a8cd605e9 100644 --- a/test/integration/scoped_rds_integration_test.cc +++ b/test/integration/scoped_rds_integration_test.cc @@ -20,6 +20,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -224,7 +226,7 @@ route_configuration_name: {} cleanupUpstreamAndDownstream(); // Test "foo-route" and 'bar-route' both gets routed to cluster_0. - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_success", Ge(1)); for (const std::string& scope_key : std::vector{"foo-route", "bar-route"}) { sendRequestAndVerifyResponse( Http::TestRequestHeaderMapImpl{{":method", "GET"}, @@ -235,29 +237,31 @@ route_configuration_name: {} 456, Http::TestResponseHeaderMapImpl{{":status", "200"}, {"service", scope_key}}, 123, /*cluster_0*/ 0); } - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_attempt", - // update_attempt only increase after a response - isDelta() ? 1 : 2); - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 1); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_attempt", + Ge( // update_attempt only increase after a response + isDelta() ? 1 : 2)); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_success", + Ge(1)); // The version gauge should be set to xxHash64("1"). - test_server_->waitForGaugeEq("http.config_test.scoped_rds.foo-scoped-routes.version", - 13237225503670494420UL); + test_server_->waitForGauge("http.config_test.scoped_rds.foo-scoped-routes.version", + Eq(13237225503670494420UL)); // Add a new scope scope_route3 with a brand new RouteConfiguration foo_route2. const std::string scope_route3 = fmt::format(scope_tmpl, "foo_scope3", "foo_route2", "baz-route"); sendSrdsResponse({scope_route1, scope_route2, scope_route3}, /*added*/ {scope_route3}, {}, "2"); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_attempt", 2); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_attempt", Ge(2)); sendRdsResponse(fmt::format(route_config_tmpl, "foo_route1", "cluster_1"), "3"); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 2); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_success", Ge(2)); createRdsStream("foo_route2"); - test_server_->waitForCounterGe("http.config_test.rds.foo_route2.update_attempt", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route2.update_attempt", Ge(1)); sendRdsResponse(fmt::format(route_config_tmpl, "foo_route2", "cluster_0"), "1"); - test_server_->waitForCounterGe("http.config_test.rds.foo_route2.update_success", 1); - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 2); + test_server_->waitForCounter("http.config_test.rds.foo_route2.update_success", Ge(1)); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_success", + Ge(2)); // The version gauge should be set to xxHash64("2"). - test_server_->waitForGaugeEq("http.config_test.scoped_rds.foo-scoped-routes.version", - 6927017134761466251UL); + test_server_->waitForGauge("http.config_test.scoped_rds.foo-scoped-routes.version", + Eq(6927017134761466251UL)); // After RDS update, requests within scope 'foo_scope1' or 'foo_scope2' get routed to // 'cluster_1'. for (const std::string& scope_key : std::vector{"foo-route", "bar-route"}) { @@ -282,7 +286,8 @@ route_configuration_name: {} // Delete foo_scope1 and requests within the scope gets 400s. sendSrdsResponse({scope_route2, scope_route3}, {}, {"foo_scope1"}, "3"); - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 3); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_success", + Ge(3)); codec_client_ = makeHttpConnection(lookupPort("http")); response = codec_client_->makeHeaderOnlyRequest( Http::TestRequestHeaderMapImpl{{":method", "GET"}, @@ -297,7 +302,8 @@ route_configuration_name: {} const std::string& scope_route4 = fmt::format(scope_tmpl, "foo_scope4", "foo_route4", "xyz-route"); sendSrdsResponse({scope_route3, scope_route2, scope_route4}, {scope_route4}, {}, "4"); - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 4); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_success", + Ge(4)); codec_client_ = makeHttpConnection(lookupPort("http")); response = codec_client_->makeHeaderOnlyRequest( Http::TestRequestHeaderMapImpl{{":method", "GET"}, @@ -312,10 +318,10 @@ route_configuration_name: {} cleanupUpstreamAndDownstream(); // RDS updated foo_route4, requests with scope key "xyz-route" now hit cluster_1. - test_server_->waitForCounterGe("http.config_test.rds.foo_route4.update_attempt", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route4.update_attempt", Ge(1)); createRdsStream("foo_route4"); sendRdsResponse(fmt::format(route_config_tmpl, "foo_route4", "cluster_1"), "3"); - test_server_->waitForCounterGe("http.config_test.rds.foo_route4.update_success", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route4.update_success", Ge(1)); sendRequestAndVerifyResponse( Http::TestRequestHeaderMapImpl{{":method", "GET"}, {":path", "/meh"}, @@ -385,11 +391,12 @@ route_configuration_name: route_config // Replace foo-scope with a scoped route using an RDS subscription. sendSrdsResponse({scoped_route_with_rds_subscription}, {scoped_route_with_rds_subscription}, {}, "2"); - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 2); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_success", + Ge(2)); createRdsStream("route_config"); - test_server_->waitForCounterGe("http.config_test.rds.route_config.update_attempt", 1); + test_server_->waitForCounter("http.config_test.rds.route_config.update_attempt", Ge(1)); sendRdsResponse(route_config, "1"); - test_server_->waitForCounterGe("http.config_test.rds.route_config.update_success", 1); + test_server_->waitForCounter("http.config_test.rds.route_config.update_success", Ge(1)); // foo-route now goes to cluster_1. sendRequestAndVerifyResponse( @@ -404,7 +411,8 @@ route_configuration_name: route_config // Replace foo-scope with bar-scope, which uses an inlined RouteConfiguration. sendSrdsResponse({scoped_route2}, {scoped_route2}, {}, "3"); - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 3); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_success", + Ge(3)); // foo-route now returns 404. codec_client_ = makeHttpConnection(lookupPort("http")); @@ -446,8 +454,8 @@ route_configuration_name: foo_route1 }; initialize(); - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_rejected", - 1); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_rejected", + Ge(1)); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeHeaderOnlyRequest( Http::TestRequestHeaderMapImpl{{":method", "GET"}, @@ -468,7 +476,7 @@ route_configuration_name: foo_route1 - string_key: foo )EOF"; sendSrdsResponse({scope_route2}, {scope_route2}, {}, "1"); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_attempt", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_attempt", Ge(1)); createRdsStream("foo_route1"); constexpr absl::string_view route_config_tmpl = R"EOF( name: {} @@ -480,7 +488,7 @@ route_configuration_name: foo_route1 route: {{ cluster: {} }} )EOF"; sendRdsResponse(fmt::format(route_config_tmpl, "foo_route1", "cluster_0"), "1"); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_success", Ge(1)); sendRequestAndVerifyResponse( Http::TestRequestHeaderMapImpl{{":method", "GET"}, {":path", "/meh"}, @@ -525,7 +533,7 @@ route_configuration_name: {} initialize(); registerTestServerPorts({"http"}); - test_server_->waitForCounterGe("http.config_test.rds.foo_route.update_rejected", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route.update_rejected", Ge(1)); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeHeaderOnlyRequest( Http::TestRequestHeaderMapImpl{{":method", "GET"}, @@ -563,8 +571,8 @@ route_configuration_name: foo_route1 - string_key: foo )EOF"; sendSrdsResponse({}, {scope_route2}, {}, "2"); - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_rejected", - 1); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_rejected", + Ge(1)); sendSrdsResponse({}, {}, {"foo_scope1", "foo_scope2"}, "3"); } @@ -658,7 +666,7 @@ route_configuration_name: {} verifyResponse(std::move(response), "404", Http::TestResponseHeaderMapImpl{}, ""); cleanupUpstreamAndDownstream(); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_success", Ge(1)); // listener_0 can match using "=" as separator codec_client_ = makeHttpConnection(lookupPort("http")); @@ -689,7 +697,7 @@ route_configuration_name: {} verifyResponse(std::move(response), "404", Http::TestResponseHeaderMapImpl{}, ""); cleanupUpstreamAndDownstream(); - test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 1); + test_server_->waitForCounter("http.config_test.rds.foo_route1.update_success", Ge(1)); // listener_1 can match using "," as separator codec_client_ = makeHttpConnection(lookupPort("listener_1")); @@ -708,13 +716,14 @@ route_configuration_name: {} EXPECT_EQ(456, upstream_request_->bodyLength()); cleanupUpstreamAndDownstream(); - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_attempt", - // update_attempt only increase after a response - isDelta() ? 1 : 2); - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 1); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_attempt", + Ge( // update_attempt only increase after a response + isDelta() ? 1 : 2)); + test_server_->waitForCounter("http.config_test.scoped_rds.foo-scoped-routes.update_success", + Ge(1)); // The version gauge should be set to xxHash64("1"). - test_server_->waitForGaugeEq("http.config_test.scoped_rds.foo-scoped-routes.version", - 13237225503670494420UL); + test_server_->waitForGauge("http.config_test.scoped_rds.foo-scoped-routes.version", + Eq(13237225503670494420UL)); } } // namespace diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index bc654c9baae54..e3ae0bd74bb13 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -44,6 +44,8 @@ #include "integration.h" #include "utility.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace Ssl { @@ -361,11 +363,11 @@ version_info: "0" } void waitForSdsUpdateStats(size_t times) { - test_server_->waitForCounterGe( + test_server_->waitForCounter( listenerStatPrefix(test_quic_ ? "quic_server_transport_socket_factory.context_config_update_by_sds" : "server_ssl_socket_factory.ssl_context_update_by_sds"), - times, std::chrono::milliseconds(5000)); + Ge(times), std::chrono::milliseconds(5000)); } void TearDown() override { @@ -516,7 +518,7 @@ TEST_P(SdsDynamicKeyRotationIntegrationTest, EmptyRotation) { // Rotate to an empty directory, this should fail. TestEnvironment::renameFile(TestEnvironment::temporaryPath("root/empty"), TestEnvironment::temporaryPath("root/current")); - test_server_->waitForCounterEq("sds.server_cert_rsa.key_rotation_failed", 1); + test_server_->waitForCounter("sds.server_cert_rsa.key_rotation_failed", Eq(1)); waitForSdsUpdateStats(1); // The rotation is not a SDS attempt, so no change to these stats. EXPECT_EQ(1, test_server_->counter("sds.server_cert_rsa.update_success")->value()); @@ -899,8 +901,8 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, BasicWithSharedSecret) { // Wait for "ssl_context_updated_by_sds" counters to indicate that both resources // depending on the verification_secret were updated. - test_server_->waitForCounterGe( - "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + test_server_->waitForCounter( + "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", Ge(1)); waitForSdsUpdateStats(1); ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { @@ -926,8 +928,8 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, CombinedValidationContextW // Wait for "ssl_context_updated_by_sds" counters to indicate that both resources // depending on the verification_secret were updated. - test_server_->waitForCounterGe( - "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + test_server_->waitForCounter( + "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", Ge(1)); waitForSdsUpdateStats(1); ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { @@ -1031,8 +1033,8 @@ TEST_P(SdsDynamicUpstreamIntegrationTest, BasicSuccess) { // the testing request will be called, even though in the pre_worker_function, a good sds is // send, the cluster will be updated with good secret, the testing request may fail if it is // before context is updated. Hence, need to wait for context_update counter. - test_server_->waitForCounterGe( - "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + test_server_->waitForCounter( + "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", Ge(1)); testRouterHeaderOnlyRequestAndResponse(/*create_connection=*/nullptr, dataPlaneUpstreamIndex()); @@ -1065,14 +1067,14 @@ TEST_P(SdsDynamicUpstreamIntegrationTest, WrongSecretFirst) { ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); } - test_server_->waitForCounterGe("sds.client_cert.update_rejected", 1); + test_server_->waitForCounter("sds.client_cert.update_rejected", Ge(1)); EXPECT_EQ(0, test_server_->counter("sds.client_cert.update_success")->value()); sendSdsResponse(getClientSecret()); - test_server_->waitForCounterGe( - "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + test_server_->waitForCounter( + "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", Ge(1)); - test_server_->waitForCounterGe("sds.client_cert.update_success", 1); + test_server_->waitForCounter("sds.client_cert.update_success", Ge(1)); EXPECT_EQ(1, test_server_->counter("sds.client_cert.update_rejected")->value()); // Verify the update succeeded. @@ -1216,15 +1218,15 @@ TEST_P(SdsCdsIntegrationTest, BasicSuccess) { }; initialize(); - test_server_->waitForCounterGe( - "cluster.dynamic.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + test_server_->waitForCounter( + "cluster.dynamic.client_ssl_socket_factory.ssl_context_update_by_sds", Ge(1)); // The 4 clusters are CDS,SDS,static and dynamic cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(4)); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {}, {}, {}, "42"); // Successfully removed the dynamic cluster. - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); } // Test SDS cluster with dynamic cluster i.e. declared via CDS. @@ -1401,19 +1403,19 @@ TEST_P(SdsDynamicClusterIntegrationTest, BasicSuccess) { } // Validate that Envoy accepts SDS as dynamic cluster and moves to Live state. - test_server_->waitForGaugeGe("server.state", 0); - test_server_->waitForGaugeGe("server.live", 1); + test_server_->waitForGauge("server.state", Ge(0)); + test_server_->waitForGauge("server.live", Ge(1)); // Validate that the sds update was successful. - test_server_->waitForCounterGe("sds.client_cert.update_success", 1); + test_server_->waitForCounter("sds.client_cert.update_success", Ge(1)); // The 4 clusters are CDS,SDS,static and dynamic cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(4)); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {}, {}, {}, "42"); // Successfully removed the dynamic cluster. - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(2)); } // Validate that Envoy accepts bootstrap EDS cluster in SDS ApiConfigSource. @@ -1445,19 +1447,19 @@ TEST_P(SdsDynamicClusterIntegrationTest, EdsBootStrapCluster) { } // Validate that Envoy accepts SDS as dynamic cluster and moves to Live state. - test_server_->waitForGaugeGe("server.state", 0); - test_server_->waitForGaugeGe("server.live", 1); + test_server_->waitForGauge("server.state", Ge(0)); + test_server_->waitForGauge("server.live", Ge(1)); // Validate that the sds update was successful. - test_server_->waitForCounterGe("sds.client_cert.update_success", 1); + test_server_->waitForCounter("sds.client_cert.update_success", Ge(1)); // The 4 clusters are CDS,SDS,static and dynamic cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(4)); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {}, {}, {}, "42"); // Successfully removed the dynamic cluster. - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); } // Validate that Envoy starts fine with a non-existent CDS cluster in SDS ApiConfigSource. @@ -1478,19 +1480,19 @@ TEST_P(SdsDynamicClusterIntegrationTest, ClusterRefersNonExistentSdsCluster) { initialize(); // Validate that Envoy accepts SDS as dynamic cluster and moves to Live state. - test_server_->waitForGaugeGe("server.state", 0); - test_server_->waitForGaugeGe("server.live", 1); + test_server_->waitForGauge("server.state", Ge(0)); + test_server_->waitForGauge("server.live", Ge(1)); // Validate that the secret update failed because SDS cluster is not found. - test_server_->waitForCounterGe("sds.client_cert.update_failure", 1); + test_server_->waitForCounter("sds.client_cert.update_failure", Ge(1)); // The 3 clusters are CDS, static and dynamic cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {}, {}, {}, "42"); // Successfully removed the dynamic cluster. - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(2)); } // Validate that Envoy starts fine with a cyclic dependency between CDS and SDS clusters @@ -1512,17 +1514,17 @@ TEST_P(SdsDynamicClusterIntegrationTest, CdsSdsCyclicDependency) { initialize(); // Validate that Envoy accepts SDS as dynamic cluster and moves to Live state. - test_server_->waitForGaugeGe("server.state", 0); - test_server_->waitForGaugeGe("server.live", 1); - test_server_->waitForCounterGe("sds.client_cert.update_failure", 1); + test_server_->waitForGauge("server.state", Ge(0)); + test_server_->waitForGauge("server.live", Ge(1)); + test_server_->waitForCounter("sds.client_cert.update_failure", Ge(1)); // The 3 clusters are CDS, static and dynamic cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {}, {}, {}, "42"); // Successfully removed the dynamic cluster. - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(2)); } // This test verifies that health checks do NOT start before SDS secrets are delivered. @@ -1567,7 +1569,7 @@ TEST_P(SdsDynamicClusterIntegrationTest, ClusterWarmingWhileHealthCheckBlocksOnS initialize(); // Wait for the dynamic cluster to exist and be in warming state. - test_server_->waitForGaugeEq("cluster.dynamic.warming_state", 1); + test_server_->waitForGauge("cluster.dynamic.warming_state", Eq(1)); // Health checks must NOT have started while cluster is warming. // Note: The counter may exist but its value should be 0. @@ -1579,12 +1581,12 @@ TEST_P(SdsDynamicClusterIntegrationTest, ClusterWarmingWhileHealthCheckBlocksOnS sendSdsResponse2(getClientSecret(), *sds_stream_); // After SDS secrets are delivered, health checks should start. - test_server_->waitForCounterGe("cluster.dynamic.health_check.attempt", 1); + test_server_->waitForCounter("cluster.dynamic.health_check.attempt", Ge(1)); // Clean up. sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {}, {}, {}, "42"); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(2)); } // This test verifies that with the runtime guard DISABLED, health checks starts and cluster becomes @@ -1631,22 +1633,22 @@ TEST_P(SdsDynamicClusterIntegrationTest, ClusterWarmingWhileHealthCheckBlocksOnS initialize(); // Wait for the dynamic cluster to exist and be in warming state. - test_server_->waitForGaugeEq("cluster.dynamic.warming_state", 1); + test_server_->waitForGauge("cluster.dynamic.warming_state", Eq(1)); // With the runtime guard DISABLED, health checks start immediately (old behavior). - test_server_->waitForCounterGe("cluster.dynamic.health_check.attempt", 1); - test_server_->waitForGaugeEq("cluster.dynamic.membership_healthy", 0); + test_server_->waitForCounter("cluster.dynamic.health_check.attempt", Ge(1)); + test_server_->waitForGauge("cluster.dynamic.membership_healthy", Eq(0)); // Now send the SDS response with actual client certificate. sendSdsResponse2(getClientSecret(), *sds_stream_); // After SDS secrets are delivered, the cluster finishes warming. - test_server_->waitForGaugeEq("cluster.dynamic.warming_state", 0); + test_server_->waitForGauge("cluster.dynamic.warming_state", Eq(0)); // Clean up. sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {}, {}, {}, "42"); - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 2); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(2)); } class SdsDynamicDownstreamPrivateKeyIntegrationTest : public SdsDynamicDownstreamIntegrationTest { @@ -1776,15 +1778,15 @@ BORINGSSL_TEST_P(SdsCdsPrivateKeyIntegrationTest, BasicSdsCdsPrivateKeyProvider) }; initialize(); - test_server_->waitForCounterGe( - "cluster.dynamic.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + test_server_->waitForCounter( + "cluster.dynamic.client_ssl_socket_factory.ssl_context_update_by_sds", Ge(1)); // The 4 clusters are CDS,SDS,static and dynamic cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(4)); sendDiscoveryResponse(Config::TestTypeUrl::get().Cluster, {}, {}, {}, "42"); // Successfully removed the dynamic cluster. - test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Eq(3)); } } // namespace Ssl diff --git a/test/integration/server.h b/test/integration/server.h index a2e9c3c09ecbc..b1e10d224eb14 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -467,26 +467,40 @@ class IntegrationTestServer : public Logger::Loggable, Buffer::WatermarkFactorySharedPtr watermark_factory, bool use_bootstrap_node_metadata, bool use_admin_server); - void waitForCounterEq(const std::string& name, uint64_t value, - std::chrono::milliseconds timeout = TestUtility::DefaultTimeout, - Event::Dispatcher* dispatcher = nullptr) override { - ASSERT_TRUE( - TestUtility::waitForCounterEq(statStore(), name, value, time_system_, timeout, dispatcher)); + void waitForCounter(const std::string& name, testing::Matcher value_matcher, + std::chrono::milliseconds timeout = TestUtility::DefaultTimeout, + Event::Dispatcher* dispatcher = nullptr) override { + ASSERT_TRUE(TestUtility::waitForCounter(statStore(), name, value_matcher, time_system_, timeout, + dispatcher)); } - void waitForCounterGe(const std::string& name, uint64_t value, - std::chrono::milliseconds timeout = TestUtility::DefaultTimeout) override { - ASSERT_TRUE(TestUtility::waitForCounterGe(statStore(), name, value, time_system_, timeout)); + template void waitForCounterEq(Args&&...) { + static_assert(DeprecatedStatWaitHelpers::always_false, + "IntegrationTestServer::waitForCounterEq was removed; use " + "IntegrationTestServer::waitForCounter(name, testing::Eq(value), ...) instead."); } - void waitForGaugeEq(const std::string& name, uint64_t value, - std::chrono::milliseconds timeout = TestUtility::DefaultTimeout) override { - ASSERT_TRUE(TestUtility::waitForGaugeEq(statStore(), name, value, time_system_, timeout)); + template void waitForCounterGe(Args&&...) { + static_assert(DeprecatedStatWaitHelpers::always_false, + "IntegrationTestServer::waitForCounterGe was removed; use " + "IntegrationTestServer::waitForCounter(name, testing::Ge(value), ...) instead."); } - void waitForGaugeGe(const std::string& name, uint64_t value, - std::chrono::milliseconds timeout = TestUtility::DefaultTimeout) override { - ASSERT_TRUE(TestUtility::waitForGaugeGe(statStore(), name, value, time_system_, timeout)); + void waitForGauge(const std::string& name, testing::Matcher value_matcher, + std::chrono::milliseconds timeout = TestUtility::DefaultTimeout) override { + ASSERT_TRUE(TestUtility::waitForGauge(statStore(), name, value_matcher, time_system_, timeout)); + } + + template void waitForGaugeEq(Args&&...) { + static_assert(DeprecatedStatWaitHelpers::always_false, + "IntegrationTestServer::waitForGaugeEq was removed; use " + "IntegrationTestServer::waitForGauge(name, testing::Eq(value), ...) instead."); + } + + template void waitForGaugeGe(Args&&...) { + static_assert(DeprecatedStatWaitHelpers::always_false, + "IntegrationTestServer::waitForGaugeGe was removed; use " + "IntegrationTestServer::waitForGauge(name, testing::Ge(value), ...) instead."); } void waitForCounterExists(const std::string& name) override { diff --git a/test/integration/server_stats.h b/test/integration/server_stats.h index 45d08d3bdbb29..6b4759d72669d 100644 --- a/test/integration/server_stats.h +++ b/test/integration/server_stats.h @@ -3,6 +3,8 @@ #include "envoy/event/dispatcher.h" #include "envoy/stats/stats.h" +#include "gmock/gmock.h" + namespace Envoy { // Abstract interface for IntegrationTestServer stats methods. @@ -11,26 +13,15 @@ class IntegrationTestServerStats { virtual ~IntegrationTestServerStats() = default; /** - * Wait for a counter to == a given value. + * Wait for a counter to match a given value matcher. * @param name counter name. - * @param value target value. + * @param value_matcher supplies the value matcher for the counter. * @param timeout amount of time to wait before asserting false, or 0 for no timeout. * @param dispatcher the dispatcher to run non-blocking periodically during the wait. */ - virtual void - waitForCounterEq(const std::string& name, uint64_t value, - std::chrono::milliseconds timeout = std::chrono::milliseconds::zero(), - Event::Dispatcher* dispatcher = nullptr) PURE; - - /** - * Wait for a counter to >= a given value. - * @param name counter name. - * @param value target value. - * @param timeout amount of time to wait before asserting false, or 0 for no timeout. - */ - virtual void - waitForCounterGe(const std::string& name, uint64_t value, - std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) PURE; + virtual void waitForCounter(const std::string& name, testing::Matcher value_matcher, + std::chrono::milliseconds timeout = std::chrono::milliseconds::zero(), + Event::Dispatcher* dispatcher = nullptr) PURE; /** * Wait for a counter to exist. @@ -65,24 +56,14 @@ class IntegrationTestServerStats { std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) PURE; /** - * Wait for a gauge to >= a given value. - * @param name gauge name. - * @param value target value. - * @param timeout amount of time to wait before asserting false, or 0 for no timeout. - */ - virtual void - waitForGaugeGe(const std::string& name, uint64_t value, - std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) PURE; - - /** - * Wait for a gauge to == a given value. + * Wait for a gauge to match a given value matcher. * @param name gauge name. - * @param value target value. + * @param value_matcher supplies the value matcher for the gauge. * @param timeout amount of time to wait before asserting false, or 0 for no timeout. */ virtual void - waitForGaugeEq(const std::string& name, uint64_t value, - std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) PURE; + waitForGauge(const std::string& name, testing::Matcher value_matcher, + std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) PURE; /** * Wait for a gauge to be destroyed. Note that MockStatStore does not destroy stat. diff --git a/test/integration/shadow_policy_integration_test.cc b/test/integration/shadow_policy_integration_test.cc index 954cf3234367e..d83612507099f 100644 --- a/test/integration/shadow_policy_integration_test.cc +++ b/test/integration/shadow_policy_integration_test.cc @@ -11,6 +11,8 @@ #include "test/integration/socket_interface_swap.h" #include "test/test_common/test_runtime.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -84,8 +86,8 @@ class ShadowPolicyIntegrationTest if (filter_name_ != "add-body-filter") { EXPECT_EQ(10U, response->body().size()); } - test_server_->waitForCounterGe("cluster.cluster_1.internal.upstream_rq_completed", - times_called); + test_server_->waitForCounter("cluster.cluster_1.internal.upstream_rq_completed", + Ge(times_called)); upstream_headers_ = reinterpret_cast(fake_upstreams_[0].get())->lastRequestHeaders(); @@ -121,7 +123,7 @@ TEST_P(ShadowPolicyIntegrationTest, Basic) { sendRequestAndValidateResponse(1); sendRequestAndValidateResponse(2); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_200", 2); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_200", Eq(2)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_cx_total")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.upstream_cx_total")->value()); } @@ -141,7 +143,7 @@ TEST_P(ShadowPolicyIntegrationTest, BasicWithLimits) { sendRequestAndValidateResponse(1); sendRequestAndValidateResponse(2); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_200", 2); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_200", Eq(2)); EXPECT_EQ(2, test_server_->counter("cluster.cluster_0.upstream_cx_total")->value()); // https://github.com/envoyproxy/envoy/issues/26820 EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.upstream_cx_total")->value()); @@ -291,8 +293,8 @@ TEST_P(ShadowPolicyIntegrationTest, RequestMirrorPolicyWithShadowUpstreamReset) // Send upstream reset on shadow request. upstream_request_shadow->encodeResetStream(); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_rx_reset", 1, - std::chrono::milliseconds(1000)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_rx_reset", Eq(1), + std::chrono::milliseconds(1000)); codec_client_->sendData(encoder, 20, true); ASSERT_TRUE(upstream_request_main->waitForData(*dispatcher_, 20)); @@ -544,7 +546,7 @@ TEST_P(ShadowPolicyIntegrationTest, MainRequestOverBufferLimit) { EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_cx_total")->value(), 1); EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 1); // The encoder-decoder-buffer-filter will buffer too much data triggering a local reply. - test_server_->waitForCounterEq("http.config_test.downstream_rq_4xx", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_4xx", Eq(1)); } TEST_P(ShadowPolicyIntegrationTest, ShadowRequestOverBufferLimit) { @@ -683,8 +685,8 @@ TEST_P(ShadowPolicyIntegrationTest, BackedUpConnectionBeforeShadowBegins) { auto main_response = std::move(result.second); // Connecting to the shadow stream should cause backpressure due to connection backup. - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_paused_reading_total", 1, - std::chrono::milliseconds(500)); + test_server_->waitForCounter("http.config_test.downstream_flow_control_paused_reading_total", + Eq(1), std::chrono::milliseconds(500)); codec_client_->sendData(encoder, 1023, false); @@ -703,7 +705,7 @@ TEST_P(ShadowPolicyIntegrationTest, BackedUpConnectionBeforeShadowBegins) { EXPECT_EQ(shadow_direct_response->headers().getStatusValue(), "200"); // Two requests were sent over a single connection to cluster_1. - test_server_->waitForCounterGe("cluster.cluster_1.upstream_rq_completed", 2); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_completed", Ge(2)); EXPECT_EQ(test_server_->counter("cluster.cluster_1.upstream_cx_total")->value(), 1); EXPECT_EQ(test_server_->counter("http.config_test.downstream_flow_control_paused_reading_total") ->value(), @@ -748,8 +750,8 @@ TEST_P(ShadowPolicyIntegrationTest, RequestMirrorPolicyWithShadowBackpressure) { // This will result in one call of high watermark on the shadow stream, as // end_stream will not trigger watermark calls. codec_client_->sendData(encoder, 2048, false); - test_server_->waitForCounterGe("http.config_test.downstream_flow_control_paused_reading_total", - 1); + test_server_->waitForCounter("http.config_test.downstream_flow_control_paused_reading_total", + Ge(1)); codec_client_->sendData(encoder, 2048, true); ASSERT_TRUE(upstream_request_main->waitForData(*dispatcher_, 2048 * 2)); ASSERT_TRUE(upstream_request_shadow->waitForData(*dispatcher_, 2048 * 2)); @@ -769,13 +771,13 @@ TEST_P(ShadowPolicyIntegrationTest, RequestMirrorPolicyWithShadowBackpressure) { cleanupUpstreamAndDownstream(); - test_server_->waitForCounterEq("http.config_test.downstream_flow_control_paused_reading_total", - 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_cx_total", 1); + test_server_->waitForCounter("http.config_test.downstream_flow_control_paused_reading_total", + Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_cx_total", Eq(1)); // Main cluster saw no reset; shadow cluster saw remote reset. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_completed", 1); - test_server_->waitForCounterEq("cluster.cluster_1.upstream_rq_completed", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_completed", Eq(1)); + test_server_->waitForCounter("cluster.cluster_1.upstream_rq_completed", Eq(1)); } // Test request mirroring / shadowing with the cluster name in policy. diff --git a/test/integration/tcp_async_client_integration_test.cc b/test/integration/tcp_async_client_integration_test.cc index 4342fd0d1cc9c..19ec244cc2728 100644 --- a/test/integration/tcp_async_client_integration_test.cc +++ b/test/integration/tcp_async_client_integration_test.cc @@ -5,6 +5,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -67,23 +69,23 @@ TEST_P(TcpAsyncClientIntegrationTest, SingleRequest) { std::string response("response"); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); - test_server_->waitForCounterEq("test_network_async_tcp_filter.on_new_connection", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 1); + test_server_->waitForCounter("test_network_async_tcp_filter.on_new_connection", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(1)); test_server_->waitForNumHistogramSamplesGe("cluster.cluster_0.upstream_cx_connect_ms", 1); ASSERT_TRUE(tcp_client->write(request, true)); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_tx_bytes_total", request.size()); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_tx_bytes_total", Eq(request.size())); FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); ASSERT_TRUE(fake_upstream_connection->waitForData(request.size())); ASSERT_TRUE(fake_upstream_connection->write(response, true)); - test_server_->waitForCounterGe("test_network_async_tcp_filter.on_receive_async_data", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_rx_bytes_total", response.size()); + test_server_->waitForCounter("test_network_async_tcp_filter.on_receive_async_data", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", Eq(response.size())); ASSERT_TRUE(tcp_client->waitForData(response.size())); tcp_client->close(); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy_local", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy_local", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(0)); test_server_->waitForNumHistogramSamplesGe("cluster.cluster_0.upstream_cx_length_ms", 1); } @@ -113,9 +115,9 @@ TEST_P(TcpAsyncClientIntegrationTest, MultipleRequestFrames) { // client receives the first data frame. Sending them in a tight sequence also // works, but the onData calling times could be changed due to the event loop. ASSERT_TRUE(fake_upstream_connection->write(response_1, false)); - test_server_->waitForCounterGe("test_network_async_tcp_filter.on_receive_async_data", 1); + test_server_->waitForCounter("test_network_async_tcp_filter.on_receive_async_data", Ge(1)); ASSERT_TRUE(fake_upstream_connection->write(response_2, true)); - test_server_->waitForCounterGe("test_network_async_tcp_filter.on_receive_async_data", 2); + test_server_->waitForCounter("test_network_async_tcp_filter.on_receive_async_data", Ge(2)); tcp_client->waitForData(response_1 + response_2, true); tcp_client->close(); } @@ -139,9 +141,9 @@ TEST_P(TcpAsyncClientIntegrationTest, MultipleResponseFrames) { // get response 1 ASSERT_TRUE(fake_upstream_connection->write(response_1, false)); - test_server_->waitForCounterGe("test_network_async_tcp_filter.on_receive_async_data", 1); + test_server_->waitForCounter("test_network_async_tcp_filter.on_receive_async_data", Ge(1)); ASSERT_TRUE(fake_upstream_connection->write(response_2, true)); - test_server_->waitForCounterGe("test_network_async_tcp_filter.on_receive_async_data", 2); + test_server_->waitForCounter("test_network_async_tcp_filter.on_receive_async_data", Ge(2)); tcp_client->waitForData(response_1 + response_2, true); tcp_client->close(); } @@ -155,26 +157,26 @@ TEST_P(TcpAsyncClientIntegrationTest, Reconnect) { IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); ASSERT_TRUE(tcp_client->write("hello1", false)); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(1)); FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); ASSERT_TRUE(fake_upstream_connection->waitForData( [&](const std::string& data) -> bool { return data == "hello1"; })); ASSERT_TRUE(fake_upstream_connection->close()); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(0)); // We use the same tcp_client to ensure that a new upstream connection is created. ASSERT_TRUE(tcp_client->write("hello2", false)); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 2); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(2)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(1)); FakeRawConnectionPtr fake_upstream_connection2; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection2)); ASSERT_TRUE(fake_upstream_connection2->waitForData( [&](const std::string& data) -> bool { return data == "hello2"; })); tcp_client->close(); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(0)); } TEST_P(TcpAsyncClientIntegrationTest, ClientTearDown) { @@ -200,25 +202,25 @@ TEST_P(TcpAsyncClientIntegrationTest, TestClientCloseRST) { std::string response("response"); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); - test_server_->waitForCounterEq("test_network_async_tcp_filter.on_new_connection", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 1); + test_server_->waitForCounter("test_network_async_tcp_filter.on_new_connection", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(1)); test_server_->waitForNumHistogramSamplesGe("cluster.cluster_0.upstream_cx_connect_ms", 1); ASSERT_TRUE(tcp_client->write(request, false)); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_tx_bytes_total", request.size()); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_tx_bytes_total", Eq(request.size())); FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); ASSERT_TRUE(fake_upstream_connection->waitForData(request.size())); ASSERT_TRUE(fake_upstream_connection->write(response, false)); - test_server_->waitForCounterGe("test_network_async_tcp_filter.on_receive_async_data", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_rx_bytes_total", response.size()); + test_server_->waitForCounter("test_network_async_tcp_filter.on_receive_async_data", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", Eq(response.size())); ASSERT_TRUE(tcp_client->waitForData(response.size())); tcp_client->close(Network::ConnectionCloseType::AbortReset); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy_local", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy_local", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(0)); test_server_->waitForNumHistogramSamplesGe("cluster.cluster_0.upstream_cx_length_ms", 1); EXPECT_EQ(waitForAccessLog(access_log_path_, 0, true), "DS_CLOSE_TYPE=RemoteReset US_CLOSE_TYPE=-"); @@ -233,25 +235,25 @@ TEST_P(TcpAsyncClientIntegrationTest, TestUpstreamCloseRST) { std::string response("response"); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); - test_server_->waitForCounterEq("test_network_async_tcp_filter.on_new_connection", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 1); + test_server_->waitForCounter("test_network_async_tcp_filter.on_new_connection", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(1)); test_server_->waitForNumHistogramSamplesGe("cluster.cluster_0.upstream_cx_connect_ms", 1); ASSERT_TRUE(tcp_client->write(request, false)); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_tx_bytes_total", request.size()); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_tx_bytes_total", Eq(request.size())); FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); ASSERT_TRUE(fake_upstream_connection->waitForData(request.size())); ASSERT_TRUE(fake_upstream_connection->write(response, false)); - test_server_->waitForCounterGe("test_network_async_tcp_filter.on_receive_async_data", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_rx_bytes_total", response.size()); + test_server_->waitForCounter("test_network_async_tcp_filter.on_receive_async_data", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_rx_bytes_total", Eq(response.size())); ASSERT_TRUE(tcp_client->waitForData(response.size())); ASSERT_TRUE(fake_upstream_connection->close(Network::ConnectionCloseType::AbortReset)); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy_remote", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy_remote", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(0)); test_server_->waitForNumHistogramSamplesGe("cluster.cluster_0.upstream_cx_length_ms", 1); EXPECT_EQ(waitForAccessLog(access_log_path_, 0, true), "DS_CLOSE_TYPE=LocalReset US_CLOSE_TYPE=-"); @@ -271,7 +273,7 @@ TEST_P(TcpAsyncClientIntegrationTest, TestDownstremHalfClosedThenRST) { // It is half-closed for downstream. ASSERT_TRUE(tcp_client->write(request, true)); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_tx_bytes_total", request.size()); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_tx_bytes_total", Eq(request.size())); FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); ASSERT_TRUE(fake_upstream_connection->waitForData(request.size())); @@ -284,10 +286,10 @@ TEST_P(TcpAsyncClientIntegrationTest, TestDownstremHalfClosedThenRST) { // RemoteClose event from downstream rather than RemoteReset. ASSERT_TRUE(fake_upstream_connection->write(response, false)); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy_local", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy_local", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Eq(1)); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(0)); test_server_->waitForNumHistogramSamplesGe("cluster.cluster_0.upstream_cx_length_ms", 1); // As a basic half close process, the connection is already half closed in Envoy before. diff --git a/test/integration/tcp_conn_pool_integration_test.cc b/test/integration/tcp_conn_pool_integration_test.cc index 5bf016e8a888e..e54765600009c 100644 --- a/test/integration/tcp_conn_pool_integration_test.cc +++ b/test/integration/tcp_conn_pool_integration_test.cc @@ -8,6 +8,7 @@ #include "test/test_common/registry.h" #include "test/test_common/utility.h" +using testing::Eq; namespace Envoy { namespace { @@ -222,7 +223,7 @@ TEST_P(TcpConnPoolIntegrationTest, PoolCleanupEnabled) { ASSERT_TRUE(fake_upstream_connection2->waitForData(request2.size(), &data)); EXPECT_EQ(request2, data); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 2); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(2)); // Send response 2. ASSERT_TRUE(fake_upstream_connection2->write(response2)); @@ -233,7 +234,7 @@ TEST_P(TcpConnPoolIntegrationTest, PoolCleanupEnabled) { ASSERT_TRUE(fake_upstream_connection1->write(response1)); ASSERT_TRUE(fake_upstream_connection1->close()); tcp_client->waitForData(response1, false); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(0)); // After both requests were completed, the pool went idle and was cleaned up. Request 3 causes a // new pool to be created. Seeing a new pool created is a proxy for directly observing that an @@ -246,7 +247,7 @@ TEST_P(TcpConnPoolIntegrationTest, PoolCleanupEnabled) { ASSERT_TRUE(tcp_client->write(request3)); FakeRawConnectionPtr fake_upstream_connection3; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection3)); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 1); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(1)); ASSERT_TRUE(fake_upstream_connection3->waitForData(request3.size(), &data)); EXPECT_EQ(request3, data); @@ -254,7 +255,7 @@ TEST_P(TcpConnPoolIntegrationTest, PoolCleanupEnabled) { ASSERT_TRUE(fake_upstream_connection3->close()); tcp_client->waitForData(response3, false); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", Eq(0)); tcp_client->close(); }); diff --git a/test/integration/tcp_proxy_integration_test.cc b/test/integration/tcp_proxy_integration_test.cc index 5dbbd43a7da72..9fc9fe8b475f9 100644 --- a/test/integration/tcp_proxy_integration_test.cc +++ b/test/integration/tcp_proxy_integration_test.cc @@ -29,6 +29,8 @@ using testing::_; using testing::AtLeast; +using testing::Eq; +using testing::Ge; using testing::Invoke; using testing::MatchesRegex; using testing::NiceMock; @@ -69,8 +71,8 @@ TEST_P(TcpProxyIntegrationTest, TcpProxyUpstreamWritesFirst) { ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); // Any time an associated connection is destroyed, it increments both counters. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy_with_active_rq", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy_with_active_rq", Ge(1)); IntegrationTcpClientPtr tcp_client2 = makeTcpConnection(lookupPort("tcp_proxy")); FakeRawConnectionPtr fake_upstream_connection2; @@ -97,8 +99,8 @@ TEST_P(TcpProxyIntegrationTest, TcpProxyUpstreamTls) { EXPECT_EQ("world", tcp_client->data()); // Any time an associated connection is destroyed, it increments both counters. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy_with_active_rq", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy_with_active_rq", Ge(1)); } // Test proxying data in both directions, and that all data is flushed properly @@ -283,7 +285,8 @@ TEST_P(TcpProxyIntegrationTest, TcpProxyDownstreamFlush) { ASSERT_TRUE(fake_upstream_connection->write(data, true)); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_paused_reading_total", + Ge(1)); EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_flow_control_resumed_reading_total") ->value(), 0); @@ -322,7 +325,7 @@ TEST_P(TcpProxyIntegrationTest, TcpProxyUpstreamFlush) { ASSERT_TRUE(tcp_client->write(data, true, true, std::chrono::milliseconds(30000))); - test_server_->waitForGaugeEq("tcp.tcpproxy_stats.upstream_flush_active", 1); + test_server_->waitForGauge("tcp.tcpproxy_stats.upstream_flush_active", Eq(1)); ASSERT_TRUE(fake_upstream_connection->readDisable(false)); ASSERT_TRUE(fake_upstream_connection->waitForData(data.size())); ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); @@ -330,7 +333,7 @@ TEST_P(TcpProxyIntegrationTest, TcpProxyUpstreamFlush) { tcp_client->waitForHalfClose(); EXPECT_EQ(test_server_->counter("tcp.tcpproxy_stats.upstream_flush_total")->value(), 1); - test_server_->waitForGaugeEq("tcp.tcpproxy_stats.upstream_flush_active", 0); + test_server_->waitForGauge("tcp.tcpproxy_stats.upstream_flush_active", Eq(0)); } // Test that Envoy doesn't crash or assert when shutting down with an upstream flush active @@ -353,7 +356,7 @@ TEST_P(TcpProxyIntegrationTest, TcpProxyUpstreamFlushEnvoyExit) { ASSERT_TRUE(tcp_client->write(data, true)); - test_server_->waitForGaugeEq("tcp.tcpproxy_stats.upstream_flush_active", 1); + test_server_->waitForGauge("tcp.tcpproxy_stats.upstream_flush_active", Eq(1)); test_server_.reset(); ASSERT_TRUE(fake_upstream_connection->close()); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); @@ -572,15 +575,15 @@ TEST_P(TcpProxyIntegrationTest, AccessLogUpstreamDetectedCloseType) { ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); // Wait for the upstream to close to ensure we get the correct close type. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy_remote", 1, - TestUtility::DefaultTimeout * 100); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy_remote", Ge(1), + TestUtility::DefaultTimeout * 100); // Downstream should be closed by proxy. tcp_client->close(); // Guarantee client is done writing to the log. auto log_result = waitForAccessLog(access_log_path); - EXPECT_THAT(log_result, testing::Eq("RemoteReset")); + EXPECT_THAT(log_result, Eq("RemoteReset")); } // Verifies that upstream RST is propagated to downstream as RST (default behavior). @@ -624,7 +627,7 @@ TEST_P(TcpProxyIntegrationTest, UpstreamRstPropagation) { tcp_client->waitForDisconnect(); auto log_result = waitForAccessLog(access_log_path); - EXPECT_THAT(log_result, testing::Eq("RemoteReset")); + EXPECT_THAT(log_result, Eq("RemoteReset")); } #endif @@ -975,12 +978,12 @@ TEST_P(TcpProxyIntegrationTest, TestPerClientIdletimeout) { initialize(); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); // Platforms could prevent ActiveTcpClient construction unless we explicitly wait for it. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(1)); tcp_client->close(); // Two pre-connections are closed by idle timers. - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Ge(2)); } TEST_P(TcpProxyIntegrationTest, TestIdletimeoutWithLargeOutstandingData) { @@ -1350,7 +1353,7 @@ void TcpProxyMetadataMatchIntegrationTest::expectEndpointToMatchRoute( ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); tcp_client->waitForDisconnect(); - test_server_->waitForCounterGe("cluster.cluster_0.lb_subsets_selected", 1); + test_server_->waitForCounter("cluster.cluster_0.lb_subsets_selected", Ge(1)); } // Verifies connection failure. @@ -1363,8 +1366,8 @@ void TcpProxyMetadataMatchIntegrationTest::expectEndpointNotToMatchRoute( // e.g. on 'envoy-linux (bazel compile_time_options)' and 'envoy-linux (bazel release)' // tcp_client->waitForDisconnect(); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_none_healthy", 1); - test_server_->waitForCounterEq("cluster.cluster_0.lb_subsets_selected", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_none_healthy", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.lb_subsets_selected", Eq(0)); tcp_client->close(); } @@ -1837,7 +1840,7 @@ TEST_P(MysqlIntegrationTest, DisconnectDetected) { // Close the prefetched connection. ASSERT_TRUE(fake_upstream_connection1->close()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Ge(1)); tcp_client->close(); } @@ -1983,11 +1986,11 @@ TEST_P(TcpProxyReceiveBeforeConnectIntegrationTest, ReceiveBeforeConnectEarlyDat // Until total data size > 6 is received, the PauseFilter stops the iteration. Downstream counter // is incremented, but no connection attempt to upstream is made. ASSERT_TRUE(tcp_client->write("hello")); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.downstream_cx_total", 1); + test_server_->waitForCounter("tcp.tcpproxy_stats.downstream_cx_total", Eq(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.upstream_cx_total")->value()); ASSERT_TRUE(tcp_client->write("world")); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.early_data_received_count_total", 1); + test_server_->waitForCounter("tcp.tcpproxy_stats.early_data_received_count_total", Eq(1)); ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_cx_total")->value()); @@ -2023,13 +2026,13 @@ TEST_P(TcpProxyReceiveBeforeConnectIntegrationTest, UpstreamBufferHighWatermark) // PauseFilter stops the iteration until sufficient data is received. ASSERT_TRUE(tcp_client->write(data.substr(0, upstream_buffer_limit - 1))); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.downstream_cx_total", 1); + test_server_->waitForCounter("tcp.tcpproxy_stats.downstream_cx_total", Eq(1)); EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.upstream_cx_total")->value()); // Downstream sends more data. PauseFilter allows the iteration to continue, upstream connection // is established. The buffered early data is sent to the upstream. ASSERT_TRUE(tcp_client->write(data.substr(upstream_buffer_limit - 1))); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.early_data_received_count_total", 1); + test_server_->waitForCounter("tcp.tcpproxy_stats.early_data_received_count_total", Eq(1)); ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_cx_total")->value()); diff --git a/test/integration/tcp_proxy_many_connections_test.cc b/test/integration/tcp_proxy_many_connections_test.cc index e838f073e7503..ffdd429ad5505 100644 --- a/test/integration/tcp_proxy_many_connections_test.cc +++ b/test/integration/tcp_proxy_many_connections_test.cc @@ -58,8 +58,8 @@ TEST_P(TcpProxyManyConnectionsTest, TcpProxyManyConnections) { for (int i = 0; i < num_connections; ++i) { clients[i] = makeTcpConnection(lookupPort("tcp_proxy")); - test_server_->waitForGaugeGe("cluster.cluster_0.upstream_cx_active", i, - TestUtility::DefaultTimeout * timeout_scaling_factor_); + test_server_->waitForGauge("cluster.cluster_0.upstream_cx_active", testing::Ge(i), + TestUtility::DefaultTimeout * timeout_scaling_factor_); } for (int i = 0; i < num_connections; ++i) { IntegrationTcpClientPtr& tcp_client = clients[i]; diff --git a/test/integration/tcp_proxy_odcds_integration_test.cc b/test/integration/tcp_proxy_odcds_integration_test.cc index 43fcf3ffbec28..38baa774461b6 100644 --- a/test/integration/tcp_proxy_odcds_integration_test.cc +++ b/test/integration/tcp_proxy_odcds_integration_test.cc @@ -16,6 +16,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -138,7 +140,7 @@ TEST_P(TcpProxyOdcdsIntegrationTest, SingleTcpClient) { result = xds_connection_->waitForNewStream(*dispatcher_, odcds_stream_); RELEASE_ASSERT(result, result.message()); odcds_stream_->startGrpcStream(); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.on_demand_cluster_attempt", 1); + test_server_->waitForCounter("tcp.tcpproxy_stats.on_demand_cluster_attempt", Eq(1)); // Verify the on-demand CDS request and respond with the prepared `new_cluster`. EXPECT_TRUE(compareDeltaDiscoveryRequest(Config::TestTypeUrl::get().Cluster, {"new_cluster"}, {}, odcds_stream_.get())); @@ -188,8 +190,8 @@ TEST_P(TcpProxyOdcdsIntegrationTest, RepeatedRequest) { EXPECT_TRUE(compareDeltaDiscoveryRequest(Config::TestTypeUrl::get().Cluster, {"new_cluster"}, {}, odcds_stream_.get())); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.on_demand_cluster_attempt", - expected_upstream_connections); + test_server_->waitForCounter("tcp.tcpproxy_stats.on_demand_cluster_attempt", + Eq(expected_upstream_connections)); sendDeltaDiscoveryResponse( Config::TestTypeUrl::get().Cluster, {new_cluster_}, {}, "1", odcds_stream_.get()); @@ -304,7 +306,7 @@ TEST_P(TcpProxyOdcdsIntegrationTest, ShutdownAllConnectionsOnClusterLookupTimeou // TODO: assert there is no more on-demand cds request since the first cluster is in flight. IntegrationTcpClientPtr tcp_client_2 = makeTcpConnection(lookupPort("tcp_proxy")); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.on_demand_cluster_attempt", 2); + test_server_->waitForCounter("tcp.tcpproxy_stats.on_demand_cluster_attempt", Eq(2)); tcp_client_1->waitForHalfClose(true); tcp_client_2->waitForHalfClose(true); @@ -453,7 +455,7 @@ TEST_P(TcpProxyOdcdsAdsIntegrationTest, NoCdsConfigOnDemandDiscoveryBasic) { // Establish a tcp request to the Envoy. IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.on_demand_cluster_attempt", 1); + test_server_->waitForCounter("tcp.tcpproxy_stats.on_demand_cluster_attempt", Eq(1)); // Verify the on-demand CDS request and respond with the prepared `new_cluster`. EXPECT_TRUE(compareDeltaDiscoveryRequest(Config::TestTypeUrl::get().Cluster, {"new_cluster"}, {}, odcds_stream_.get())); @@ -496,7 +498,7 @@ TEST_P(TcpProxyOdcdsAdsIntegrationTest, NoCdsConfigOnDemandDiscoveryTwoRequests) RELEASE_ASSERT(result, result.message()); odcds_stream_->startGrpcStream(); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.on_demand_cluster_attempt", 2); + test_server_->waitForCounter("tcp.tcpproxy_stats.on_demand_cluster_attempt", Eq(2)); // Verify the on-demand CDS request for "new_cluster". EXPECT_TRUE(compareDeltaDiscoveryRequest(Config::TestTypeUrl::get().Cluster, {"new_cluster"}, {}, @@ -547,7 +549,7 @@ TEST_P(TcpProxyOdcdsAdsIntegrationTest, NoCdsConfigOnDemandDiscoveryTwoListeners // Establish tcp request to first listener (cluster1). tcp_clients_.push_back(makeTcpConnection(lookupPort("tcp_proxy"))); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.on_demand_cluster_attempt", 1); + test_server_->waitForCounter("tcp.tcpproxy_stats.on_demand_cluster_attempt", Eq(1)); // Expect the ADS server to receive the request, but don't send the response yet. EXPECT_TRUE(compareDeltaDiscoveryRequest(Config::TestTypeUrl::get().Cluster, {"new_cluster1"}, {}, @@ -556,7 +558,7 @@ TEST_P(TcpProxyOdcdsAdsIntegrationTest, NoCdsConfigOnDemandDiscoveryTwoListeners // Establish tcp request to second listener (cluster2). tcp_clients_.push_back(makeTcpConnection(lookupPort("tcp_proxy_2"))); - test_server_->waitForCounterEq("tcp.tcpproxy_stats.on_demand_cluster_attempt", 2); + test_server_->waitForCounter("tcp.tcpproxy_stats.on_demand_cluster_attempt", Eq(2)); // Expect the ADS server to receive the request for new_cluster2. EXPECT_TRUE(compareDeltaDiscoveryRequest(Config::TestTypeUrl::get().Cluster, {"new_cluster2"}, {}, @@ -588,7 +590,7 @@ TEST_P(TcpProxyOdcdsAdsIntegrationTest, NoCdsConfigOnDemandDiscoveryTwoListeners tcp_clients_[0]->close(); tcp_clients_[1]->close(); - test_server_->waitForCounterGe("tcp.tcpproxy_stats.on_demand_cluster_success", 2); + test_server_->waitForCounter("tcp.tcpproxy_stats.on_demand_cluster_success", Ge(2)); } } // namespace diff --git a/test/integration/tcp_tunneling_integration_test.cc b/test/integration/tcp_tunneling_integration_test.cc index 88ad6f5c899a3..85a4fd38e7255 100644 --- a/test/integration/tcp_tunneling_integration_test.cc +++ b/test/integration/tcp_tunneling_integration_test.cc @@ -18,6 +18,11 @@ #include "gtest/gtest.h" namespace Envoy { + +using Params = std::tuple; +using testing::Eq; +using testing::Ge; + namespace { // Terminating CONNECT and sending raw TCP upstream. @@ -166,7 +171,7 @@ TEST_P(ConnectTerminationIntegrationTest, Basic) { setUpConnection(); sendBidirectionalDataAndCleanShutdown(); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_total", Ge(2)); cleanupUpstreamAndDownstream(); setUpConnection(); @@ -468,7 +473,7 @@ TEST_P(ConnectTerminationIntegrationTest, BasicMaxStreamDuration) { setUpConnection(); sendBidirectionalData(); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_max_duration_reached", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_max_duration_reached", Ge(1)); if (downstream_protocol_ == Http::CodecType::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); @@ -583,8 +588,6 @@ INSTANTIATE_TEST_SUITE_P(HttpAndIpVersions, ConnectTerminationIntegrationTest, {Http::CodecType::HTTP1})), HttpProtocolIntegrationTest::protocolTestParamsToString); -using Params = std::tuple; - // Test with proxy protocol headers. class ProxyProtocolConnectTerminationIntegrationTest : public ConnectTerminationIntegrationTest { protected: @@ -1581,9 +1584,9 @@ TEST_P(TcpTunnelingIntegrationTest, HeaderEvaluatorConfigUpdate) { }); new_config_helper.setLds("1"); - test_server_->waitForCounterEq("listener_manager.listener_modified", 1); - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", 0); - test_server_->waitForGaugeEq("listener_manager.total_listeners_draining", 0); + test_server_->waitForCounter("listener_manager.listener_modified", Eq(1)); + test_server_->waitForGauge("listener_manager.total_listeners_warming", Eq(0)); + test_server_->waitForGauge("listener_manager.total_listeners_draining", Eq(0)); // Start a connection, and verify the upgrade headers are received upstream. auto tcp_client_2 = makeTcpConnection(lookupPort("tcp_proxy")); @@ -1622,7 +1625,7 @@ TEST_P(TcpTunnelingIntegrationTest, Goaway) { setUpConnection(fake_upstream_connection_); sendBidiData(fake_upstream_connection_, true); closeConnection(fake_upstream_connection_); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Ge(1)); // Make sure a subsequent connection can be established successfully. FakeHttpConnectionPtr fake_upstream_connection; @@ -1632,7 +1635,7 @@ TEST_P(TcpTunnelingIntegrationTest, Goaway) { // Make sure the last stream is finished before doing test teardown. fake_upstream_connection->encodeGoAway(); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy", 2); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Ge(2)); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); } @@ -2044,7 +2047,8 @@ TEST_P(TcpTunnelingIntegrationTest, TcpProxyDownstreamFlush) { upstream_request_->encodeData(data, true); } - test_server_->waitForCounterGe("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_flow_control_paused_reading_total", + Ge(1)); tcp_client_->readDisable(false); tcp_client_->waitForData(data); tcp_client_->waitForHalfClose(); @@ -2583,7 +2587,7 @@ TEST_P(TcpTunnelingIntegrationTestSimTime, TestIdletimeoutWithLargeOutstandingDa // Advance simulated time to trigger the idle timeout deterministically. timeSystem().advanceTimeAndRun(std::chrono::seconds(idle_timeout), *dispatcher_, Event::Dispatcher::RunType::NonBlock); - test_server_->waitForCounterGe("tcp.tcp_stats.idle_timeout", 1); + test_server_->waitForCounter("tcp.tcp_stats.idle_timeout", Ge(1)); tcp_client_->waitForDisconnect(); if (upstreamProtocol() == Http::CodecType::HTTP1) { @@ -2632,7 +2636,7 @@ TEST_P(TcpTunnelingIntegrationTestSimTime, timeSystem().advanceTimeAndRun(std::chrono::seconds(idle_timeout), *dispatcher_, Event::Dispatcher::RunType::NonBlock); - test_server_->waitForCounterGe("tcp.tcp_stats.idle_timeout", 1); + test_server_->waitForCounter("tcp.tcp_stats.idle_timeout", Ge(1)); tcp_client_->waitForHalfClose(); if (upstreamProtocol() == Http::CodecType::HTTP1) { diff --git a/test/integration/udp_tunneling_integration_test.cc b/test/integration/udp_tunneling_integration_test.cc index 635c84c91a5f7..e23c6819f53b1 100644 --- a/test/integration/udp_tunneling_integration_test.cc +++ b/test/integration/udp_tunneling_integration_test.cc @@ -15,6 +15,8 @@ #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -190,7 +192,7 @@ TEST_P(ConnectUdpTerminationIntegrationTest, MaxStreamDuration) { setUpConnection(); exchangeValidCapsules(); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_max_duration_reached", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_max_duration_reached", Ge(1)); if (downstream_protocol_ == Http::CodecType::HTTP1) { ASSERT_TRUE(codec_client_->waitForDisconnect()); @@ -498,8 +500,8 @@ name: udp_proxy // Send upgrade headers downstream, fully establishing the connection. upstream_request_->encodeHeaders(response_headers_, false); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", tunnels_count); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(tunnels_count)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); } void sendCapsuleDownstream(const std::string datagram, bool end_stream = false) { @@ -540,7 +542,7 @@ name: udp_proxy void drainListeners() { test_server_->server().dispatcher().post([this]() { test_server_->server().drainListeners(); }); - test_server_->waitForCounterEq("listener_manager.listener_stopped", 1); + test_server_->waitForCounter("listener_manager.listener_stopped", Eq(1)); } TestConfig config_; @@ -566,7 +568,7 @@ TEST_P(UdpTunnelingIntegrationTest, BasicFlowWithBuffering) { sendCapsuleDownstream("response1", false); sendCapsuleDownstream("response2", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(UdpTunnelingIntegrationTest, BasicFlowNoBuffering) { @@ -581,7 +583,7 @@ TEST_P(UdpTunnelingIntegrationTest, BasicFlowNoBuffering) { ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram2}))); sendCapsuleDownstream("response", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(UdpTunnelingIntegrationTest, BasicFlowWithPost) { @@ -594,7 +596,7 @@ TEST_P(UdpTunnelingIntegrationTest, BasicFlowWithPost) { // Wait for buffered datagram. ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram1}))); sendCapsuleDownstream("response", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(UdpTunnelingIntegrationTest, TwoConsecutiveDownstreamSessions) { @@ -604,10 +606,10 @@ TEST_P(UdpTunnelingIntegrationTest, TwoConsecutiveDownstreamSessions) { establishConnection("hello1"); sendCapsuleDownstream("response2", true); // Will end first session. - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); establishConnection("hello2", 2); // Will create another session. sendCapsuleDownstream("response2", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(UdpTunnelingIntegrationTest, IdleTimeoutWithUpstreamConnectionAndResponseHeaders) { @@ -643,9 +645,9 @@ TEST_P(UdpTunnelingIntegrationTest, IdleTimeoutWithUpstreamConnectionAndResponse ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram}))); sendCapsuleDownstream("response1", false); - test_server_->waitForCounterEq("udp.foo.idle_timeout", 1); + test_server_->waitForCounter("udp.foo.idle_timeout", Eq(1)); ASSERT_TRUE(upstream_request_->waitForReset()); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); EXPECT_THAT(waitForAccessLog(access_log_filename), testing::HasSubstr(StreamInfo::ResponseFlagUtils::STREAM_IDLE_TIMEOUT)); @@ -685,11 +687,11 @@ TEST_P(UdpTunnelingIntegrationTest, IdleTimeoutWithUpstreamConnectionNoResponseH ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); expectRequestHeaders(upstream_request_->headers()); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); - test_server_->waitForCounterEq("udp.foo.idle_timeout", 1); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); + test_server_->waitForCounter("udp.foo.idle_timeout", Eq(1)); ASSERT_TRUE(upstream_request_->waitForReset()); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(0)); EXPECT_THAT(waitForAccessLog(access_log_filename), testing::HasSubstr(StreamInfo::ResponseFlagUtils::STREAM_IDLE_TIMEOUT)); @@ -740,10 +742,10 @@ TEST_P(UdpTunnelingIntegrationTest, IdleTimeoutNoUpstreamConnection) { // Drainer filter will stop the iteration until the session idle timeout, so no connection to // upstream will be created. client_->write("hello", *listener_address_); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); - test_server_->waitForCounterEq("udp.foo.idle_timeout", 1); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); + test_server_->waitForCounter("udp.foo.idle_timeout", Eq(1)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(0)); EXPECT_THAT(waitForAccessLog(access_log_filename), testing::HasSubstr(StreamInfo::ResponseFlagUtils::STREAM_IDLE_TIMEOUT)); @@ -758,16 +760,16 @@ TEST_P(UdpTunnelingIntegrationTest, BufferOverflowDueToCapacity) { // we expect the second one to be dropped. client_->write("hello1", *listener_address_); client_->write("hello2", *listener_address_); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", 1); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", Eq(1)); // "hello3" will drop because it's sent before the tunnel is established, and the buffer is full. establishConnection("hello3"); // Wait for the buffered datagram. ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({"hello1"}))); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", 2); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", Eq(2)); sendCapsuleDownstream("response", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(UdpTunnelingIntegrationTest, BufferOverflowDueToSize) { @@ -779,16 +781,16 @@ TEST_P(UdpTunnelingIntegrationTest, BufferOverflowDueToSize) { // we expect the second one to be dropped. client_->write("hello1", *listener_address_); client_->write("hello2", *listener_address_); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", 1); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", Eq(1)); // "hello3" will drop because it's sent before the tunnel is established, and the buffer is full. establishConnection("hello3"); // Wait for the buffered datagram. ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({"hello1"}))); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", 2); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_buffer_overflow", Eq(2)); sendCapsuleDownstream("response", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(UdpTunnelingIntegrationTest, ConnectionReuse) { @@ -811,8 +813,8 @@ TEST_P(UdpTunnelingIntegrationTest, ConnectionReuse) { // Send upgrade headers downstream, fully establishing the connection. upstream_request2->encodeHeaders(response_headers_, false); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 2); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 2); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(2)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(2)); // Wait for buffered datagram for each stream. ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({"hello_1"}))); @@ -821,14 +823,14 @@ TEST_P(UdpTunnelingIntegrationTest, ConnectionReuse) { // Send capsule from upstream over the first stream, and close it. sendCapsuleDownstream("response_1", true); // First stream is closed so we expect active sessions to decrease to 1. - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); // Send capsule from upstream over the second stream, and close it. upstream_request2->encodeData(encapsulate("response_2"), true); Network::UdpRecvData response_datagram; client2.recv(response_datagram); EXPECT_EQ("response_2", response_datagram.buffer_->toString()); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(UdpTunnelingIntegrationTest, FailureOnBadResponseHeaders) { @@ -868,10 +870,10 @@ TEST_P(UdpTunnelingIntegrationTest, FailureOnBadResponseHeaders) { Http::TestResponseHeaderMapImpl response_headers{{":status", "404"}}; upstream_request_->encodeHeaders(response_headers, true); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_connect_attempts_exceeded", 1); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_failure", 1); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 0); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_connect_attempts_exceeded", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_failure", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(0)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); const std::string expected_log = "1 " + std::string(StreamInfo::ResponseFlagUtils::UPSTREAM_CONNECTION_FAILURE) + "," + @@ -917,8 +919,8 @@ TEST_P(UdpTunnelingIntegrationTest, Http::TestResponseHeaderMapImpl fail_response_headers{{":status", "404"}}; upstream_request_->encodeHeaders(fail_response_headers, true); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_retry", 1); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_retry", Eq(1)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); // The request is retried, expect new downstream headers ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); @@ -928,9 +930,9 @@ TEST_P(UdpTunnelingIntegrationTest, // Send upgrade headers downstream, fully establishing the connection. upstream_request_->encodeHeaders(response_headers_, false); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 1); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(1)); sendCapsuleDownstream("response", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); const std::string expected_log = "2 " + std::string(StreamInfo::ResponseFlagUtils::UPSTREAM_CONNECTION_FAILURE); @@ -986,8 +988,8 @@ TEST_P(UdpTunnelingIntegrationTest, Http::TestResponseHeaderMapImpl fail_response_headers{{":status", "404"}}; upstream_request_->encodeHeaders(fail_response_headers, true); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_retry", 1); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_retry", Eq(1)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); // The request is retried, expect new downstream headers ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); @@ -997,9 +999,9 @@ TEST_P(UdpTunnelingIntegrationTest, // Send upgrade headers downstream, fully establishing the connection. upstream_request_->encodeHeaders(response_headers_, false); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 1); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(1)); sendCapsuleDownstream("response", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); const std::string expected_log = "2 " + std::string(StreamInfo::ResponseFlagUtils::UPSTREAM_CONNECTION_FAILURE); @@ -1042,27 +1044,27 @@ TEST_P(UdpTunnelingIntegrationTest, ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); expectRequestHeaders(upstream_request_->headers()); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); // Close the upstream connection before sending response headers. ASSERT_TRUE(fake_upstream_connection_->close()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); // Retry to create a new stream on new connection and not the closed one. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_retry", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_retry", Eq(1)); ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); expectRequestHeaders(upstream_request_->headers()); upstream_request_->encodeHeaders(response_headers_, false); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 1); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(1)); // Wait for datagram. ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram}))); sendCapsuleDownstream("response", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); const std::string expected_log = "2 " + std::string(StreamInfo::ResponseFlagUtils::UPSTREAM_CONNECTION_FAILURE); @@ -1116,27 +1118,27 @@ TEST_P(UdpTunnelingIntegrationTest, ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); expectRequestHeaders(upstream_request_->headers()); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); // Close the upstream connection before sending response headers. ASSERT_TRUE(fake_upstream_connection_->close()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); // Retry to create a new stream on new connection and not the closed one. - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_retry", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_retry", Eq(1)); ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); expectRequestHeaders(upstream_request_->headers()); upstream_request_->encodeHeaders(response_headers_, false); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 1); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(1)); // Wait for datagram. ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram}))); sendCapsuleDownstream("response", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); const std::string expected_log = "2 " + std::string(StreamInfo::ResponseFlagUtils::UPSTREAM_CONNECTION_FAILURE); @@ -1180,7 +1182,7 @@ TEST_P(UdpTunnelingIntegrationTest, PropagateValidResponseHeaders) { ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram}))); sendCapsuleDownstream("response", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); // Verify response header value is in the access log. EXPECT_THAT(waitForAccessLog(access_log_filename), testing::HasSubstr("capsule-protocol")); @@ -1225,10 +1227,10 @@ TEST_P(UdpTunnelingIntegrationTest, PropagateInvalidResponseHeaders) { Http::TestResponseHeaderMapImpl response_headers{{":status", "404"}}; upstream_request_->encodeHeaders(response_headers, true); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_connect_attempts_exceeded", 1); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_failure", 1); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 0); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_connect_attempts_exceeded", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_failure", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(0)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); // Verify response header value is in the access log. EXPECT_THAT(waitForAccessLog(access_log_filename), testing::HasSubstr("404")); @@ -1273,8 +1275,8 @@ TEST_P(UdpTunnelingIntegrationTest, PropagateInvalidResponseHeadersWithRetry) { Http::TestResponseHeaderMapImpl response_headers{{":status", "404"}}; upstream_request_->encodeHeaders(response_headers, true); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_retry", 1); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_retry", Eq(1)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); // Since retry is enabled, a new request is expected to be sent by the UDP proxy. ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); @@ -1283,9 +1285,9 @@ TEST_P(UdpTunnelingIntegrationTest, PropagateInvalidResponseHeadersWithRetry) { upstream_request_->encodeHeaders(response_headers, true); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_failure", 1); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 0); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_failure", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(0)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); // Verify response header value is in the access log. EXPECT_THAT(waitForAccessLog(access_log_filename), testing::HasSubstr("404")); @@ -1332,7 +1334,7 @@ TEST_P(UdpTunnelingIntegrationTest, PropagateResponseTrailers) { Http::TestResponseTrailerMapImpl response_trailers{{"test-trailer-name", trailer_value}}; upstream_request_->encodeTrailers(response_trailers); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); // Verify response trailer value is in the access log. EXPECT_THAT(waitForAccessLog(access_log_filename), testing::HasSubstr(trailer_value)); @@ -1387,7 +1389,7 @@ TEST_P(UdpTunnelingIntegrationTest, FlushAccessLogOnTunnelConnected) { ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, expectedCapsules({datagram}))); sendCapsuleDownstream("response", true); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(UdpTunnelingIntegrationTest, DontFlushTunnelConnectedAccessLogWithInvalidResponseHeaders) { @@ -1432,10 +1434,10 @@ TEST_P(UdpTunnelingIntegrationTest, DontFlushTunnelConnectedAccessLogWithInvalid Http::TestResponseHeaderMapImpl response_headers{{":status", "404"}}; upstream_request_->encodeHeaders(response_headers, true); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_connect_attempts_exceeded", 1); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_failure", 1); - test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_tunnel_success", 0); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_connect_attempts_exceeded", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_failure", Eq(1)); + test_server_->waitForCounter("cluster.cluster_0.udp.sess_tunnel_success", Eq(0)); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); // Verify that UdpTunnelUpstreamConnected access log wasn't flushed. const std::string access_log = waitForAccessLog(access_log_filename); @@ -1555,7 +1557,7 @@ TEST_P(UdpTunnelingIntegrationTest, BytesMeterAccessLog) { auto expected_received_wire_bytes = expected_response_wire_size + response_capsule_size; EXPECT_EQ(std::to_string(expected_received_wire_bytes), access_log_parts[6]); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } TEST_P(UdpTunnelingIntegrationTest, DrainListenersWhileTunnelingActiveSessionIsStillActive) { @@ -1570,11 +1572,11 @@ TEST_P(UdpTunnelingIntegrationTest, DrainListenersWhileTunnelingActiveSessionIsS // Send a response and keep the session alive. sendCapsuleDownstream("response", false); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 1); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(1)); // Drain listeners while udp session is still active. drainListeners(); - test_server_->waitForGaugeEq("udp.foo.downstream_sess_active", 0); + test_server_->waitForGauge("udp.foo.downstream_sess_active", Eq(0)); } INSTANTIATE_TEST_SUITE_P(IpAndHttpVersions, UdpTunnelingIntegrationTest, diff --git a/test/integration/upstream_http_filter_integration_test.cc b/test/integration/upstream_http_filter_integration_test.cc index be4b42086fca3..dd179e754749a 100644 --- a/test/integration/upstream_http_filter_integration_test.cc +++ b/test/integration/upstream_http_filter_integration_test.cc @@ -31,6 +31,8 @@ constexpr absl::string_view expected_types[] = { using HttpFilterProto = envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter; +using testing::Eq; +using testing::Ge; using testing::Not; class UpstreamHttpFilterIntegrationTestBase : public HttpIntegrationTest { @@ -464,7 +466,7 @@ class UpstreamHttpExtensionDiscoveryIntegrationTestBase }); HttpIntegrationTest::initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); registerTestServerPorts({"http"}); } @@ -626,15 +628,15 @@ TEST_P(DynamicRouterOrClusterFiltersIntegrationTest, BasicSuccess) { // Send 1st config update. sendXdsResponse(filter_name_, "1", default_header_key_, "test-val1"); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", Eq(1)); auto headers2 = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers2, "test-val1"); // Send 2nd config update. sendXdsResponse(filter_name_, "1", default_header_key_, "test-val2"); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", Eq(2)); auto headers3 = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers3, "test-val2"); }; @@ -647,14 +649,14 @@ TEST_P(DynamicRouterOrClusterFiltersIntegrationTest, BasicSuccessWithTtl) { // Send 1st config update. sendXdsResponse(filter_name_, "1", default_header_key_, "test-val1", true); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", Eq(1)); auto headers1 = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers1, "test-val1"); // Wait for configuration expiry, the default configuration should be applied. - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", Eq(2)); auto headers2 = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers2, default_header_value_); }; @@ -667,8 +669,8 @@ TEST_P(DynamicRouterOrClusterFiltersIntegrationTest, BasicWithConfigFail) { // Send config update with invalid config (header value length has to >=2). sendXdsResponse(filter_name_, "1", default_header_key_, "x"); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_fail", Eq(1)); auto headers = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers, default_header_value_); }; @@ -681,8 +683,8 @@ TEST_P(DynamicRouterOrClusterFiltersIntegrationTest, TwoSubscriptionsSameName) { initialize(); sendXdsResponse(filter_name_, "1", default_header_key_, "test-val"); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", Eq(1)); auto headers = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers, "test-val,test-val"); } @@ -701,10 +703,10 @@ TEST_P(DynamicRouterOrClusterFiltersIntegrationTest, TwoSubscriptionsDifferentNa // Send 1st config update. sendXdsResponse("foo", "1", "header-key1", "test-val1"); sendXdsResponse("bar", "1", "header-key2", "test-val1", false, true); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter.foo.config_reload", 1); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter.bar.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.upstream_http_filter.foo.config_reload", + Eq(1)); + test_server_->waitForCounter("extension_config_discovery.upstream_http_filter.bar.config_reload", + Eq(1)); auto headers2 = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers2, "header-key1", "test-val1"); expectHeaderKeyAndValue(headers2, "header-key2", "test-val1"); @@ -712,10 +714,10 @@ TEST_P(DynamicRouterOrClusterFiltersIntegrationTest, TwoSubscriptionsDifferentNa // Send 2nd config update. sendXdsResponse("foo", "2", "header-key1", "test-val2"); sendXdsResponse("bar", "2", "header-key2", "test-val2", false, true); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter.foo.config_reload", 2); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter.bar.config_reload", 2); + test_server_->waitForCounter("extension_config_discovery.upstream_http_filter.foo.config_reload", + Eq(2)); + test_server_->waitForCounter("extension_config_discovery.upstream_http_filter.bar.config_reload", + Eq(2)); auto headers3 = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers3, "header-key1", "test-val2"); expectHeaderKeyAndValue(headers3, "header-key2", "test-val2"); @@ -731,8 +733,8 @@ TEST_P(DynamicRouterOrClusterFiltersIntegrationTest, TwoDynamicTwoStaticFilterMi initialize(); sendXdsResponse(filter_name_, "1", default_header_key_, "xds-val"); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", Eq(1)); auto headers = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers, default_header_key_, "xds-val,static-val1,xds-val"); expectHeaderKeyAndValue(headers, "header2", "static-val2"); @@ -748,8 +750,8 @@ TEST_P(DynamicRouterOrClusterFiltersIntegrationTest, DynamicStaticFilterMixedDif initialize(); sendXdsResponse(filter_name_, "1", default_header_key_, "xds-val"); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", Eq(1)); auto headers = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers, default_header_key_, "static-val1,xds-val,xds-val"); expectHeaderKeyAndValue(headers, "header2", "static-val2"); @@ -775,8 +777,8 @@ TEST_P(DynamicRouterOrClusterFiltersIntegrationTest, UpdateDuringConnection) { // Send config update. sendXdsResponse(filter_name_, "1", default_header_key_, "xds-val"); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", Eq(1)); IntegrationStreamDecoderPtr response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); @@ -802,8 +804,8 @@ TEST_P(DynamicRouterOrClusterFiltersIntegrationTest, BasicSuccessWithConfigDump) // Send 1st config update. sendXdsResponse(filter_name_, "1", default_header_key_, "xds-val"); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", Eq(1)); // Verify ECDS config dump are working correctly. BufferingStreamDecoderPtr response; @@ -878,8 +880,8 @@ TEST_P(DynamicRouterAndClusterFiltersIntegrationTest, DynamicRouterAndClusterSam // Send 1st config update. sendXdsResponse(filter_name_, "1", default_header_key_, "test-val1"); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_http_filter." + filter_name_ + ".config_reload", Eq(1)); auto headers2 = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers2, "test-val1"); }; @@ -900,10 +902,10 @@ TEST_P(DynamicRouterAndClusterFiltersIntegrationTest, DynamicRouterAndClusterDif // Send 1st config update. sendXdsResponse("foo", "1", default_header_key_, "value-from-cluster"); sendXdsResponse("bar", "1", default_header_key_, "value-from-router", false, true); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter.foo.config_reload", 1); - test_server_->waitForCounterEq( - "extension_config_discovery.upstream_http_filter.bar.config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.upstream_http_filter.foo.config_reload", + Eq(1)); + test_server_->waitForCounter("extension_config_discovery.upstream_http_filter.bar.config_reload", + Eq(1)); auto headers2 = sendRequestAndGetHeaders(); expectHeaderKeyAndValue(headers2, "value-from-cluster"); }; diff --git a/test/integration/upstream_network_filter_integration_test.cc b/test/integration/upstream_network_filter_integration_test.cc index 114b68c57769d..ad0c2d3fe97d5 100644 --- a/test/integration/upstream_network_filter_integration_test.cc +++ b/test/integration/upstream_network_filter_integration_test.cc @@ -12,6 +12,7 @@ #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace { @@ -46,7 +47,7 @@ class UpstreamNetworkFiltersIntegrationTestBase : public BaseIntegrationTest { void sendDataVerifyResults(uint32_t bytes_drained) { test_server_->waitUntilListenersReady(); - test_server_->waitForGaugeGe("listener_manager.workers_started", 1); + test_server_->waitForGauge("listener_manager.workers_started", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); ASSERT_TRUE(tcp_client->write(data_)); @@ -222,7 +223,7 @@ class UpstreamNetworkExtensionDiscoveryIntegrationTest }); BaseIntegrationTest::initialize(); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 1); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(1)); EXPECT_EQ(test_server_->server().initManager().state(), Init::Manager::State::Initialized); registerTestServerPorts({port_name_}); } @@ -376,14 +377,16 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, BasicSuccess) { // Send 1st config update to have filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(1)); sendDataVerifyResults(5); // Send 2nd config update to have filter drain 3 bytes of data. sendXdsResponse(filter_name_, "2", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(2)); sendDataVerifyResults(3); } @@ -394,21 +397,24 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, BasicSuccessWithTtl) { // Send 1st config update with TTL 1s, and have network filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(1)); sendDataVerifyResults(5); // Wait for configuration expired. Then start a TCP connection. // Since the configuration has expired and there is no default configuration applied, // The upstream network filter will be skipped. - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(2)); sendDataVerifyResults(2); // Reinstate the configuration. sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 3); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(3)); sendDataVerifyResults(3); } @@ -419,8 +425,8 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, BasicWithConfigFail) { // Send config update with invalid config (bytes_to_drain needs to be >=2). sendXdsResponse(filter_name_, "1", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_fail", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_fail", Ge(1)); // The default filter will be installed. Start a TCP connection. The default filter drain 2 bytes. sendDataVerifyResults(2); } @@ -432,8 +438,9 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsSameNam initialize(); sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(1)); // Each filter drain 3 bytes. sendDataVerifyResults(6); @@ -449,20 +456,20 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, TwoSubscriptionsDiffere // Send 1st config update. sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter.foo.config_reload", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter.bar.config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_network_filter.foo.config_reload", Ge(1)); + test_server_->waitForCounter( + "extension_config_discovery.upstream_network_filter.bar.config_reload", Ge(1)); // The two filters drain 3 + 4 bytes. sendDataVerifyResults(7); // Send 2nd config update. sendXdsResponse("foo", "2", 4); sendXdsResponse("bar", "2", 5, false, true); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter.foo.config_reload", 2); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter.bar.config_reload", 2); + test_server_->waitForCounter( + "extension_config_discovery.upstream_network_filter.foo.config_reload", Ge(2)); + test_server_->waitForCounter( + "extension_config_discovery.upstream_network_filter.bar.config_reload", Ge(2)); // The two filters drain 4 + 5 bytes. sendDataVerifyResults(9); } @@ -477,8 +484,9 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, TwoDynamicTwoStaticFilt initialize(); sendXdsResponse(filter_name_, "1", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(1)); // filter drain 3 + 2 + 3 + 2 bytes. sendDataVerifyResults(10); } @@ -492,8 +500,9 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, DynamicStaticFilterMixe initialize(); sendXdsResponse(filter_name_, "1", 2); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(1)); // filter drain 2 + 2 + 2 + 2 bytes. sendDataVerifyResults(8); } @@ -507,8 +516,9 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, BasicSuccessWithConfigD // Send 1st config update to have network filter drain 5 bytes of data. sendXdsResponse(filter_name_, "1", 5); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(1)); // Verify ECDS config dump are working correctly. BufferingStreamDecoderPtr response; @@ -548,11 +558,13 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, ConfigDumpWithFilterCon // Send config update with TTL 1s. sendXdsResponse(filter_name_, "1", 5, true); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(1)); // Wait for configuration expired. - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(2)); BufferingStreamDecoderPtr response; EXPECT_EQ("200", request("admin", "GET", "/config_dump?resource=ecds_filters", response)); @@ -576,10 +588,10 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter.foo.config_reload", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter.bar.config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_network_filter.foo.config_reload", Ge(1)); + test_server_->waitForCounter( + "extension_config_discovery.upstream_network_filter.bar.config_reload", Ge(1)); // Verify ECDS config dump are working correctly. BufferingStreamDecoderPtr response; @@ -624,10 +636,10 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, sendXdsResponse("foo", "1", 3); sendXdsResponse("bar", "1", 4, false, true); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter.foo.config_reload", 1); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter.bar.config_reload", 1); + test_server_->waitForCounter( + "extension_config_discovery.upstream_network_filter.foo.config_reload", Ge(1)); + test_server_->waitForCounter( + "extension_config_discovery.upstream_network_filter.bar.config_reload", Ge(1)); BufferingStreamDecoderPtr response; EXPECT_EQ("200", request("admin", "GET", "/config_dump?resource=ecds_filters&name_regex=.a.", response)); @@ -655,8 +667,9 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, // Send config update to have filter drain 5 bytes of data. uint32_t bytes_to_drain = 5; sendXdsResponse(filter_name_, "1", bytes_to_drain); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 1); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(1)); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort(port_name_)); FakeRawConnectionPtr fake_upstream_connection; @@ -664,8 +677,9 @@ TEST_P(UpstreamNetworkExtensionDiscoveryIntegrationTest, // Send 2nd config update to have filter drain 3 bytes of data. sendXdsResponse(filter_name_, "2", 3); - test_server_->waitForCounterGe( - "extension_config_discovery.upstream_network_filter." + filter_name_ + ".config_reload", 2); + test_server_->waitForCounter("extension_config_discovery.upstream_network_filter." + + filter_name_ + ".config_reload", + Ge(2)); ASSERT_TRUE(tcp_client->write(data_)); std::string received_data; @@ -758,9 +772,9 @@ class UpstreamNetworkFilterAccessLogIntegrationTest } void verifyLog() { - test_server_->waitForCounterGe("test_access_log_filter.on_new_connection", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForCounterGe("test_access_log_filter.log_called", 1); + test_server_->waitForCounter("test_access_log_filter.on_new_connection", Ge(1)); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Ge(1)); + test_server_->waitForCounter("test_access_log_filter.log_called", Ge(1)); } }; diff --git a/test/integration/websocket_integration_test.cc b/test/integration/websocket_integration_test.cc index 146e376718dde..c1343969f17f0 100644 --- a/test/integration/websocket_integration_test.cc +++ b/test/integration/websocket_integration_test.cc @@ -17,6 +17,8 @@ #include "absl/strings/str_cat.h" #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -181,8 +183,8 @@ void WebsocketIntegrationTest::performUpgrade( auto encoder_decoder = codec_client_->startRequest(upgrade_request_headers); request_encoder_ = &encoder_decoder.first; response_ = std::move(encoder_decoder.second); - test_server_->waitForCounterGe("http.config_test.downstream_cx_upgrades_total", 1); - test_server_->waitForGaugeGe("http.config_test.downstream_cx_upgrades_active", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_upgrades_total", Ge(1)); + test_server_->waitForGauge("http.config_test.downstream_cx_upgrades_active", Ge(1)); // Verify the upgrade was received upstream. ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); @@ -228,7 +230,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketConnectionDownstreamDisconnect) { ASSERT_TRUE(upstream_request_->waitForData(*dispatcher_, "hellobye!")); ASSERT_TRUE(waitForUpstreamDisconnectOrReset()); - test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 0); + test_server_->waitForGauge("http.config_test.downstream_cx_upgrades_active", Eq(0)); } TEST_P(WebsocketIntegrationTest, PortStrippingForHttp2) { @@ -271,7 +273,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketConnectionIdleTimeout) { performUpgrade(upgradeRequestHeaders(), upgradeResponseHeaders()); sendBidirectionalData(); - test_server_->waitForCounterGe("http.config_test.downstream_rq_idle_timeout", 1); + test_server_->waitForCounter("http.config_test.downstream_rq_idle_timeout", Ge(1)); waitForClientDisconnectOrReset(); ASSERT_TRUE(waitForUpstreamDisconnectOrReset()); } @@ -619,8 +621,8 @@ TEST_P(WebsocketIntegrationTest, Http1UpgradeStatusCodeOK) { EXPECT_EQ("upgrade", response_->headers().Connection()->value().getStringView()); EXPECT_EQ("websocket", response_->headers().Upgrade()->value().getStringView()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); + test_server_->waitForGauge("http.config_test.downstream_cx_upgrades_active", Eq(0)); ASSERT_TRUE(codec_client_->waitForDisconnect()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); } @@ -641,7 +643,7 @@ TEST_P(WebsocketIntegrationTest, NonHttp1UpgradeStatusCodeOK) { // HTTP2 upstream response 200 is converted to 101. EXPECT_EQ("101", response_->headers().Status()->value().getStringView()); - test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 1); + test_server_->waitForGauge("http.config_test.downstream_cx_upgrades_active", Eq(1)); codec_client_->close(); } @@ -661,7 +663,7 @@ TEST_P(WebsocketIntegrationTest, NoHttp1UpstreamUpgradeStatus201) { performUpgrade(upgradeRequestHeaders(), correct_status_response_headers, true); EXPECT_EQ("201", response_->headers().Status()->value().getStringView()); - test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 1); + test_server_->waitForGauge("http.config_test.downstream_cx_upgrades_active", Eq(1)); codec_client_->close(); } @@ -690,8 +692,8 @@ TEST_P(WebsocketIntegrationTest, Http1UpgradeStatusCodeUpgradeRequired) { EXPECT_EQ("upgrade", response_->headers().Connection()->value().getStringView()); EXPECT_EQ("websocket", response_->headers().Upgrade()->value().getStringView()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); - test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 0); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(1)); + test_server_->waitForGauge("http.config_test.downstream_cx_upgrades_active", Eq(0)); ASSERT_TRUE(codec_client_->waitForDisconnect()); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); } @@ -718,8 +720,8 @@ TEST_P(WebsocketIntegrationTest, Http1UpgradeStatus5OOWithFilterChain) { performUpgrade(upgradeRequestHeaders(), in_correct_status_response_headers, true); EXPECT_EQ("500", response_->headers().Status()->value().getStringView()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 0); - test_server_->waitForGaugeEq("http.config_test.downstream_cx_upgrades_active", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_cx_destroy", Eq(0)); + test_server_->waitForGauge("http.config_test.downstream_cx_upgrades_active", Eq(1)); codec_client_->close(); ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); } @@ -748,8 +750,8 @@ TEST_P(WebsocketIntegrationTest, Http1UpgradeRetryWithFilterChain) { auto encoder_decoder = codec_client_->startRequest(upgradeRequestHeaders()); request_encoder_ = &encoder_decoder.first; response_ = std::move(encoder_decoder.second); - test_server_->waitForCounterGe("http.config_test.downstream_cx_upgrades_total", 1); - test_server_->waitForGaugeGe("http.config_test.downstream_cx_upgrades_active", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_upgrades_total", Ge(1)); + test_server_->waitForGauge("http.config_test.downstream_cx_upgrades_active", Ge(1)); // Verify the first upgrade was received upstream. ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); @@ -769,7 +771,7 @@ TEST_P(WebsocketIntegrationTest, Http1UpgradeRetryWithFilterChain) { ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); validateUpgradeRequestHeaders(upstream_request_->headers(), upgradeRequestHeaders()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_retry", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_retry", Eq(1)); // Send the successful 101 upgrade response on the second try auto success_response_headers = upgradeResponseHeaders(); @@ -781,7 +783,7 @@ TEST_P(WebsocketIntegrationTest, Http1UpgradeRetryWithFilterChain) { EXPECT_EQ("upgrade", response_->headers().Connection()->value().getStringView()); EXPECT_EQ("websocket", response_->headers().Upgrade()->value().getStringView()); validateUpgradeResponseHeaders(response_->headers(), success_response_headers); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_retry_success", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_retry_success", Eq(1)); // Verify successful websocket connection by sending bidirectional data sendBidirectionalData(); @@ -859,13 +861,13 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradePerTryTimeout) { auto encoder_decoder = codec_client_->startRequest(upgradeRequestHeaders()); request_encoder_ = &encoder_decoder.first; response_ = std::move(encoder_decoder.second); - test_server_->waitForCounterGe("http.config_test.downstream_cx_upgrades_total", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_upgrades_total", Ge(1)); ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_per_try_timeout", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_per_try_timeout", Ge(1)); ASSERT_TRUE(response_->waitForEndStream()); EXPECT_EQ("504", response_->headers().getStatusValue()); @@ -894,13 +896,13 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradeRouteTimeout) { auto encoder_decoder = codec_client_->startRequest(upgradeRequestHeaders()); request_encoder_ = &encoder_decoder.first; response_ = std::move(encoder_decoder.second); - test_server_->waitForCounterGe("http.config_test.downstream_cx_upgrades_total", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_upgrades_total", Ge(1)); ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_timeout", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_timeout", Ge(1)); ASSERT_TRUE(response_->waitForEndStream()); EXPECT_EQ("504", response_->headers().getStatusValue()); @@ -930,7 +932,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradeRouteTimeoutWithRetries) { auto encoder_decoder = codec_client_->startRequest(upgradeRequestHeaders()); request_encoder_ = &encoder_decoder.first; response_ = std::move(encoder_decoder.second); - test_server_->waitForCounterGe("http.config_test.downstream_cx_upgrades_total", 1); + test_server_->waitForCounter("http.config_test.downstream_cx_upgrades_total", Ge(1)); // First attempt - send 500 to trigger retry ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); @@ -940,7 +942,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradeRouteTimeoutWithRetries) { // Wait for the first request to be reset or disconnected ASSERT_TRUE(waitForUpstreamDisconnectOrReset()); - test_server_->waitForCounterEq("cluster.cluster_0.upstream_rq_retry", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_retry", Eq(1)); // Second attempt - wait for new connection or reuse existing one FakeHttpConnectionPtr fake_upstream_connection2; @@ -956,7 +958,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradeRouteTimeoutWithRetries) { ASSERT_TRUE(upstream_request2->waitForHeadersComplete()); // Route timeout should still fire after retry - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_timeout", 1); + test_server_->waitForCounter("cluster.cluster_0.upstream_rq_timeout", Ge(1)); ASSERT_TRUE(response_->waitForEndStream()); EXPECT_EQ("504", response_->headers().getStatusValue()); diff --git a/test/integration/weighted_cluster_integration_test.cc b/test/integration/weighted_cluster_integration_test.cc index fb405b30119df..232092a330428 100644 --- a/test/integration/weighted_cluster_integration_test.cc +++ b/test/integration/weighted_cluster_integration_test.cc @@ -132,7 +132,7 @@ class WeightedClusterIntegrationTest : public testing::TestWithParamwaitForGaugeEq(target_name, 0); + test_server_->waitForGauge(target_name, testing::Eq(0)); } private: diff --git a/test/integration/xds_config_tracker_integration_test.cc b/test/integration/xds_config_tracker_integration_test.cc index 069da0d2037dc..1edf86c916e79 100644 --- a/test/integration/xds_config_tracker_integration_test.cc +++ b/test/integration/xds_config_tracker_integration_test.cc @@ -17,6 +17,8 @@ #include "absl/synchronization/mutex.h" #include "gtest/gtest.h" +using testing::Eq; +using testing::Ge; namespace Envoy { namespace { @@ -203,11 +205,11 @@ TEST_P(XdsConfigTrackerIntegrationTest, XdsConfigTrackerSuccessCount) { Config::TestTypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster1_, cluster2_}, {}, "1"); // 3 because the statically specified CDS server itself counts as a cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); // onConfigAccepted is called when all the resources are accepted. - test_server_->waitForCounterEq("test_xds_tracker.on_config_accepted", 1); - test_server_->waitForCounterEq("test_xds_tracker.on_config_metadata_read", 0); + test_server_->waitForCounter("test_xds_tracker.on_config_accepted", Eq(1)); + test_server_->waitForCounter("test_xds_tracker.on_config_metadata_read", Eq(0)); EXPECT_EQ(1, test_server_->counter("test_xds_tracker.on_config_accepted")->value()); } @@ -228,11 +230,11 @@ TEST_P(XdsConfigTrackerIntegrationTest, XdsConfigTrackerSuccessCountWithWrapper) {{kTestKey, packed_value}}); // 3 because the statically specified CDS server itself counts as a cluster. - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 3); + test_server_->waitForGauge("cluster_manager.active_clusters", Ge(3)); // onConfigAccepted is called when all the resources are accepted. - test_server_->waitForCounterEq("test_xds_tracker.on_config_accepted", 1); - test_server_->waitForCounterEq("test_xds_tracker.on_config_metadata_read", 2); + test_server_->waitForCounter("test_xds_tracker.on_config_accepted", Eq(1)); + test_server_->waitForCounter("test_xds_tracker.on_config_metadata_read", Eq(2)); EXPECT_EQ(1, test_server_->counter("test_xds_tracker.on_config_accepted")->value()); } @@ -259,7 +261,7 @@ TEST_P(XdsConfigTrackerIntegrationTest, XdsConfigTrackerFailureCount) { Config::TestTypeUrl::get().Cluster, {route_config}, {route_config}, {}, "3"); // Resources are rejected because Message's TypeUrl != Resource's - test_server_->waitForCounterEq("test_xds_tracker.on_config_rejected", 1); + test_server_->waitForCounter("test_xds_tracker.on_config_rejected", Eq(1)); EXPECT_EQ(1, test_server_->counter("test_xds_tracker.on_config_rejected")->value()); } @@ -279,13 +281,13 @@ TEST_P(XdsConfigTrackerIntegrationTest, XdsConfigTrackerPartialUpdate) { // For SotW, both clusters are accepted, but the internal exception is not empty. if (sotw_or_delta_ == Grpc::SotwOrDelta::Delta || sotw_or_delta_ == Grpc::SotwOrDelta::UnifiedDelta) { - test_server_->waitForCounterGe("cluster_manager.cluster_added", 1); + test_server_->waitForCounter("cluster_manager.cluster_added", Ge(1)); } else { - test_server_->waitForCounterGe("cluster_manager.cluster_added", 3); + test_server_->waitForCounter("cluster_manager.cluster_added", Ge(3)); } // onConfigRejected is called if there is any exception even some resources are accepted. - test_server_->waitForCounterEq("test_xds_tracker.on_config_rejected", 1); + test_server_->waitForCounter("test_xds_tracker.on_config_rejected", Eq(1)); EXPECT_EQ(1, test_server_->counter("test_xds_tracker.on_config_rejected")->value()); // onConfigAccepted is called only when all the resources in a response are successfully ingested. diff --git a/test/integration/xds_delegate_extension_integration_test.cc b/test/integration/xds_delegate_extension_integration_test.cc index 279b8654ba903..dd05f4d671fb5 100644 --- a/test/integration/xds_delegate_extension_integration_test.cc +++ b/test/integration/xds_delegate_extension_integration_test.cc @@ -15,6 +15,7 @@ #include "absl/synchronization/mutex.h" #include "gtest/gtest.h" +using testing::Ge; namespace Envoy { namespace { @@ -219,7 +220,7 @@ TEST_P(XdsDelegateExtensionIntegrationTest, XdsResourcesDelegateOnConfigUpdated) )EOF"); sendDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {some_rtds_layer}, {some_rtds_layer}, {}, "1"); - test_server_->waitForCounterGe("runtime.load_success", initial_load_success_ + 1); + test_server_->waitForCounter("runtime.load_success", Ge(initial_load_success_ + 1)); int expected_on_config_updated_count = ++current_on_config_updated_count; waitforOnConfigUpdatedCount(expected_on_config_updated_count); @@ -236,7 +237,7 @@ TEST_P(XdsDelegateExtensionIntegrationTest, XdsResourcesDelegateOnConfigUpdated) )EOF"); sendDiscoveryResponse( Config::TestTypeUrl::get().Runtime, {some_rtds_layer}, {some_rtds_layer}, {}, "2"); - test_server_->waitForCounterGe("runtime.load_success", initial_load_success_ + 2); + test_server_->waitForCounter("runtime.load_success", Ge(initial_load_success_ + 2)); expected_on_config_updated_count = ++current_on_config_updated_count; waitforOnConfigUpdatedCount(expected_on_config_updated_count); diff --git a/test/integration/xds_integration_test.cc b/test/integration/xds_integration_test.cc index 07329d618449c..b37c5c7e46fb8 100644 --- a/test/integration/xds_integration_test.cc +++ b/test/integration/xds_integration_test.cc @@ -22,6 +22,8 @@ namespace Envoy { namespace { +using testing::Eq; +using testing::Ge; using testing::HasSubstr; // This is a minimal litmus test for the v3 xDS APIs. @@ -262,7 +264,7 @@ TEST_P(LdsInplaceUpdateTcpProxyIntegrationTest, ReloadConfigDeletingFilterChain) } }); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); while (!client_conn_1->closed()) { dispatcher_->run(Event::Dispatcher::RunType::NonBlock); @@ -288,7 +290,7 @@ TEST_P(LdsInplaceUpdateTcpProxyIntegrationTest, ReloadConfigDeletingFilterChain) TEST_P(LdsInplaceUpdateTcpProxyIntegrationTest, ReloadConfigAddingFilterChain) { setUpstreamCount(2); initialize(); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); std::string response_0; auto client_conn_0 = createConnectionAndWrite("alpn0", "hello", response_0); @@ -325,8 +327,8 @@ TEST_P(LdsInplaceUpdateTcpProxyIntegrationTest, ReloadConfigAddingFilterChain) { } }); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); std::string response_2; auto client_conn_2 = createConnectionAndWrite("alpn2", "hello2", response_2); @@ -529,16 +531,16 @@ TEST_P(LdsInplaceUpdateHttpIntegrationTest, ReloadConfigDeletingFilterChain) { }); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); - test_server_->waitForGaugeGe("listener_manager.total_filter_chains_draining", 1); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Ge(1)); - test_server_->waitForGaugeGe("http.hcm0.downstream_cx_active", 1); - test_server_->waitForGaugeGe("http.hcm1.downstream_cx_active", 1); + test_server_->waitForGauge("http.hcm0.downstream_cx_active", Ge(1)); + test_server_->waitForGauge("http.hcm1.downstream_cx_active", Ge(1)); expectResponseHeaderConnectionClose(*codec_client_1, true); expectResponseHeaderConnectionClose(*codec_client_default, true); - test_server_->waitForGaugeGe("listener_manager.total_filter_chains_draining", 0); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Ge(0)); expectResponseHeaderConnectionClose(*codec_client_0, false); expectConnectionServed(); @@ -552,11 +554,11 @@ TEST_P(LdsInplaceUpdateHttpIntegrationTest, ReloadConfigDeletingFilterChain) { // chain 2 and default filter chain. TEST_P(LdsInplaceUpdateHttpIntegrationTest, ReloadConfigAddingFilterChain) { inplaceInitialize(); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); auto codec_client_0 = createHttpCodec("alpn0"); Cleanup cleanup0([c0 = codec_client_0.get()]() { c0->close(); }); - test_server_->waitForGaugeGe("http.hcm0.downstream_cx_active", 1); + test_server_->waitForGauge("http.hcm0.downstream_cx_active", Ge(1)); ConfigHelper new_config_helper(version_, config_helper_.bootstrap()); new_config_helper.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) @@ -575,14 +577,14 @@ TEST_P(LdsInplaceUpdateHttpIntegrationTest, ReloadConfigAddingFilterChain) { default_filter_chain->set_name("default"); }); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); auto codec_client_2 = createHttpCodec("alpn2"); auto codec_client_default = createHttpCodec("alpndefault"); // 1 connection from filter chain 0 and 1 connection from filter chain 2. - test_server_->waitForGaugeGe("http.hcm0.downstream_cx_active", 2); + test_server_->waitForGauge("http.hcm0.downstream_cx_active", Ge(2)); Cleanup cleanup2([c2 = codec_client_2.get(), c_default = codec_client_default.get()]() { c2->close(); @@ -598,7 +600,7 @@ TEST_P(LdsInplaceUpdateHttpIntegrationTest, ReloadConfigAddingFilterChain) { // chain updates. TEST_P(LdsInplaceUpdateHttpIntegrationTest, ReloadConfigUpdatingDefaultFilterChain) { inplaceInitialize(true); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); auto codec_client_default = createHttpCodec("alpndefault"); Cleanup cleanup0([c_default = codec_client_default.get()]() { c_default->close(); }); @@ -610,8 +612,8 @@ TEST_P(LdsInplaceUpdateHttpIntegrationTest, ReloadConfigUpdatingDefaultFilterCha default_filter_chain->set_name("default_filter_chain_v3"); }); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(2)); auto codec_client_default_v3 = createHttpCodec("alpndefaultv3"); @@ -637,7 +639,7 @@ TEST_P(LdsInplaceUpdateHttpIntegrationTest, OverlappingFilterChainServesNewConne }); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); expectResponseHeaderConnectionClose(*codec_client_0, false); expectConnectionServed(); } @@ -686,8 +688,8 @@ TEST_P(LdsIntegrationTest, ReloadConfig) { // Create an LDS response with the new config, and reload config. new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); - test_server_->waitForCounterGe("listener_manager.lds.update_success", 2); + test_server_->waitForCounter("listener_manager.listener_in_place_updated", Ge(1)); + test_server_->waitForCounter("listener_manager.lds.update_success", Ge(2)); // HTTP 1.0 should now be enabled. std::string response2; @@ -721,7 +723,7 @@ TEST_P(LdsIntegrationTest, NewListenerWithBadPostListenSocketOption) { socket_option->set_int_value(10000); // Invalid value. }); new_config_helper.setLds("1"); - test_server_->waitForCounterGe("listener_manager.listener_create_failure", 1); + test_server_->waitForCounter("listener_manager.listener_create_failure", Ge(1)); } // Sample test making sure our config framework informs on listener failure. @@ -774,7 +776,7 @@ TEST_P(LdsStsIntegrationTest, DISABLED_TcpListenerRemoveFilterChainCalledAfterLi } // Wait for the filter chain removal at worker thread. When the value drops from 1, all pending // removal at the worker is completed. This is the end of the in place update. - test_server_->waitForGaugeEq("listener_manager.total_filter_chains_draining", 0); + test_server_->waitForGauge("listener_manager.total_filter_chains_draining", Eq(0)); } constexpr char XDS_CLUSTER_NAME_1[] = "xds_cluster_1.lyft.com"; @@ -969,10 +971,10 @@ TEST_P(XdsSotwMultipleAuthoritiesTest, SameResourceNameAndTypeFromMultipleAuthor initialize(); // Wait until the discovery responses have been processed. - test_server_->waitForCounterGe( - "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); - test_server_->waitForCounterGe( - "cluster.cluster_1.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + test_server_->waitForCounter( + "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", Ge(1)); + test_server_->waitForCounter( + "cluster.cluster_1.client_ssl_socket_factory.ssl_context_update_by_sds", Ge(1)); auto config_dump = getSecretsFromConfigDump(); // Two xDS resources with the same name and same type. diff --git a/test/integration/xdstp_config_sources_integration_test.cc b/test/integration/xdstp_config_sources_integration_test.cc index d22b3c9376eb0..13bb5bfc021f3 100644 --- a/test/integration/xdstp_config_sources_integration_test.cc +++ b/test/integration/xdstp_config_sources_integration_test.cc @@ -24,6 +24,8 @@ #include "gtest/gtest.h" using testing::AssertionResult; +using testing::Eq; +using testing::Ge; namespace Envoy { @@ -99,7 +101,7 @@ TEST_P(XdsTpConfigsIntegrationTest, EdsOnlyConfigAuthority1) { initialize(); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Try to send a request and see that it reaches the backend (backend 3). testRouterHeaderOnlyRequestAndResponse(nullptr, 3); cleanupUpstreamAndDownstream(); @@ -163,12 +165,12 @@ TEST_P(XdsTpConfigsIntegrationTest, EdsOnlyConfigAuthority1Update) { initialize(); - test_server_->waitForCounterEq( + test_server_->waitForCounter( "cluster.xdstp_authority1.com/envoy.config.endpoint.v3.ClusterLoadAssignment/clusters/" "cluster1.update_success", - 1); + Eq(1)); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Try to send a request and see that it reaches the backend (backend 3). testRouterHeaderOnlyRequestAndResponse(nullptr, 3); cleanupUpstreamAndDownstream(); @@ -190,10 +192,10 @@ TEST_P(XdsTpConfigsIntegrationTest, EdsOnlyConfigAuthority1Update) { {"xdstp://authority1.com/envoy.config.endpoint.v3.ClusterLoadAssignment/clusters/cluster1"}, {}, {}, false, Grpc::Status::WellKnownGrpcStatus::Ok, "", authority1_xds_stream_.get())); - test_server_->waitForCounterEq( + test_server_->waitForCounter( "cluster.xdstp_authority1.com/envoy.config.endpoint.v3.ClusterLoadAssignment/clusters/" "cluster1.update_success", - 2); + Eq(2)); } // Validate that a bootstrap cluster that has an xds-tp based config EDS source @@ -260,7 +262,7 @@ TEST_P(XdsTpConfigsIntegrationTest, EdsOnlyConfigDefaultSource) { initialize(); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Try to send a request and see that it reaches the backend (backend 3). testRouterHeaderOnlyRequestAndResponse(nullptr, 3); cleanupUpstreamAndDownstream(); @@ -343,7 +345,7 @@ TEST_P(XdsTpConfigsIntegrationTest, TwoClustersWithEdsOnlyConfigAuthority1) { initialize(); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); EXPECT_EQ(5, test_server_->gauge("cluster_manager.active_clusters")->value()); // Try to send a request and see that it reaches the backend (backend 3). testRouterHeaderOnlyRequestAndResponse(nullptr, 3); @@ -388,7 +390,7 @@ TEST_P(XdsTpConfigsIntegrationTest, RdsOnlyConfigAuthority1) { }; initialize(); - test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + test_server_->waitForCounter("listener_manager.listener_create_success", Ge(1)); // Try to send a request and see that it reaches the backend (backend 0). testRouterHeaderOnlyRequestAndResponse(nullptr); cleanupUpstreamAndDownstream(); diff --git a/test/server/config_validation/xds_fuzz.cc b/test/server/config_validation/xds_fuzz.cc index 864472986c4fb..ace6d3571dd8b 100644 --- a/test/server/config_validation/xds_fuzz.cc +++ b/test/server/config_validation/xds_fuzz.cc @@ -8,6 +8,7 @@ #include "source/common/common/matchers.h" +using testing::Eq; namespace Envoy { // Helper functions to build API responses. @@ -263,7 +264,7 @@ void XdsFuzzTest::replay() { addListener(listener_name, route_name); if (!sent_listener) { addRoute(route_name); - test_server_->waitForCounterEq("listener_manager.listener_create_success", 1, timeout_); + test_server_->waitForCounter("listener_manager.listener_create_success", Eq(1), timeout_); } sent_listener = true; break; @@ -290,20 +291,20 @@ void XdsFuzzTest::replay() { } if (sent_listener) { // Wait for all of the updates to take effect. - test_server_->waitForGaugeEq("listener_manager.total_listeners_warming", - verifier_.numWarming(), timeout_); - test_server_->waitForGaugeEq("listener_manager.total_listeners_active", verifier_.numActive(), + test_server_->waitForGauge("listener_manager.total_listeners_warming", + Eq(verifier_.numWarming()), timeout_); + test_server_->waitForGauge("listener_manager.total_listeners_active", + Eq(verifier_.numActive()), timeout_); + test_server_->waitForGauge("listener_manager.total_listeners_draining", + Eq(verifier_.numDraining()), timeout_); + test_server_->waitForCounter("listener_manager.listener_modified", + Eq(verifier_.numModified()), timeout_); + test_server_->waitForCounter("listener_manager.listener_added", Eq(verifier_.numAdded()), + timeout_); + test_server_->waitForCounter("listener_manager.listener_removed", Eq(verifier_.numRemoved()), + timeout_); + test_server_->waitForCounter("listener_manager.lds.update_success", Eq(lds_update_success_), timeout_); - test_server_->waitForGaugeEq("listener_manager.total_listeners_draining", - verifier_.numDraining(), timeout_); - test_server_->waitForCounterEq("listener_manager.listener_modified", verifier_.numModified(), - timeout_); - test_server_->waitForCounterEq("listener_manager.listener_added", verifier_.numAdded(), - timeout_); - test_server_->waitForCounterEq("listener_manager.listener_removed", verifier_.numRemoved(), - timeout_); - test_server_->waitForCounterEq("listener_manager.lds.update_success", lds_update_success_, - timeout_); } logState(); } diff --git a/test/server/server_test.cc b/test/server/server_test.cc index bcf6804f1596b..85735ed503749 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -47,6 +47,7 @@ using testing::_; using testing::Assign; +using testing::Eq; using testing::HasSubstr; using testing::InSequence; using testing::Invoke; @@ -554,7 +555,7 @@ TEST_P(ServerInstanceImplTest, StatsFlushWhenServerIsStillInitializing) { startTestServer("test/server/test_data/server/stats_sink_bootstrap.yaml", true); // Wait till stats are flushed to custom sink and validate that the actual flush happens. - EXPECT_TRUE(TestUtility::waitForCounterEq(stats_store_, "stats.flushed", 1, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(stats_store_, "stats.flushed", Eq(1), time_system_)); EXPECT_EQ(3L, TestUtility::findGauge(stats_store_, "server.state")->value()); EXPECT_EQ(Init::Manager::State::Initializing, server_->initManager().state()); @@ -1023,12 +1024,12 @@ TEST_P(ServerInstanceImplTest, ConcurrentFlushes) { server_->flushStats(); }); - EXPECT_TRUE( - TestUtility::waitForCounterEq(stats_store_, "server.dropped_stat_flushes", 2, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(stats_store_, "server.dropped_stat_flushes", Eq(2), + time_system_)); server_->dispatcher().post([&] { stats_store_.runMergeCallback(); }); - EXPECT_TRUE(TestUtility::waitForCounterEq(stats_store_, "stats.flushed", 1, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(stats_store_, "stats.flushed", Eq(1), time_system_)); // Trigger another flush after the first one finished. This should go through an no drops should // be recorded. @@ -1036,10 +1037,10 @@ TEST_P(ServerInstanceImplTest, ConcurrentFlushes) { server_->dispatcher().post([&] { stats_store_.runMergeCallback(); }); - EXPECT_TRUE(TestUtility::waitForCounterEq(stats_store_, "stats.flushed", 2, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(stats_store_, "stats.flushed", Eq(2), time_system_)); - EXPECT_TRUE( - TestUtility::waitForCounterEq(stats_store_, "server.dropped_stat_flushes", 2, time_system_)); + EXPECT_TRUE(TestUtility::waitForCounter(stats_store_, "server.dropped_stat_flushes", Eq(2), + time_system_)); server_->dispatcher().post([&] { server_->shutdown(); }); server_thread->join(); diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index fef5a184cf7f2..e458d77110a87 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -161,74 +161,48 @@ Stats::ParentHistogramSharedPtr TestUtility::findHistogram(Stats::Store& store, return findByName(store.histograms(), name); } -AssertionResult TestUtility::waitForCounterEq(Stats::Store& store, const std::string& name, - uint64_t value, Event::TestTimeSystem& time_system, - std::chrono::milliseconds timeout, - Event::Dispatcher* dispatcher) { +AssertionResult TestUtility::waitForCounter(Stats::Store& store, const std::string& name, + testing::Matcher value_matcher, + Event::TestTimeSystem& time_system, + std::chrono::milliseconds timeout, + Event::Dispatcher* dispatcher) { Event::TestTimeSystem::RealTimeBound bound(timeout); - while (findCounter(store, name) == nullptr || findCounter(store, name)->value() != value) { + while (true) { + Stats::CounterSharedPtr counter = findCounter(store, name); + if (counter != nullptr && value_matcher.Matches(counter->value())) { + return AssertionSuccess(); + } time_system.advanceTimeWait(std::chrono::milliseconds(10)); if (timeout != std::chrono::milliseconds::zero() && !bound.withinBound()) { - std::string current_value; - if (findCounter(store, name)) { - current_value = absl::StrCat(findCounter(store, name)->value()); - } else { - current_value = "nil"; - } - return AssertionFailure() << fmt::format( - "timed out waiting for {} to be {}, current value {}", name, value, current_value); + counter = findCounter(store, name); + const std::string current_value = counter != nullptr ? absl::StrCat(counter->value()) : "nil"; + return AssertionFailure() << "timed out waiting for " << name << " to " << value_matcher + << ": current value " << current_value; } if (dispatcher != nullptr) { dispatcher->run(Event::Dispatcher::RunType::NonBlock); } } - return AssertionSuccess(); -} - -AssertionResult TestUtility::waitForCounterGe(Stats::Store& store, const std::string& name, - uint64_t value, Event::TestTimeSystem& time_system, - std::chrono::milliseconds timeout) { - Event::TestTimeSystem::RealTimeBound bound(timeout); - while (findCounter(store, name) == nullptr || findCounter(store, name)->value() < value) { - time_system.advanceTimeWait(std::chrono::milliseconds(10)); - if (timeout != std::chrono::milliseconds::zero() && !bound.withinBound()) { - return AssertionFailure() << fmt::format("timed out waiting for {} to be >= {}", name, value); - } - } - return AssertionSuccess(); } -AssertionResult TestUtility::waitForGaugeGe(Stats::Store& store, const std::string& name, - uint64_t value, Event::TestTimeSystem& time_system, - std::chrono::milliseconds timeout) { +AssertionResult TestUtility::waitForGauge(Stats::Store& store, const std::string& name, + testing::Matcher value_matcher, + Event::TestTimeSystem& time_system, + std::chrono::milliseconds timeout) { Event::TestTimeSystem::RealTimeBound bound(timeout); - while (findGauge(store, name) == nullptr || findGauge(store, name)->value() < value) { - time_system.advanceTimeWait(std::chrono::milliseconds(10)); - if (timeout != std::chrono::milliseconds::zero() && !bound.withinBound()) { - return AssertionFailure() << fmt::format("timed out waiting for {} to be {}", name, value); + while (true) { + Stats::GaugeSharedPtr gauge = findGauge(store, name); + if (gauge != nullptr && value_matcher.Matches(gauge->value())) { + return AssertionSuccess(); } - } - return AssertionSuccess(); -} - -AssertionResult TestUtility::waitForGaugeEq(Stats::Store& store, const std::string& name, - uint64_t value, Event::TestTimeSystem& time_system, - std::chrono::milliseconds timeout) { - Event::TestTimeSystem::RealTimeBound bound(timeout); - while (findGauge(store, name) == nullptr || findGauge(store, name)->value() != value) { time_system.advanceTimeWait(std::chrono::milliseconds(10)); if (timeout != std::chrono::milliseconds::zero() && !bound.withinBound()) { - std::string current_value; - if (findGauge(store, name)) { - current_value = absl::StrCat(findGauge(store, name)->value()); - } else { - current_value = "nil"; - } - return AssertionFailure() << fmt::format( - "timed out waiting for {} to be {}, current value {}", name, value, current_value); + gauge = findGauge(store, name); + const std::string current_value = gauge != nullptr ? absl::StrCat(gauge->value()) : "nil"; + return AssertionFailure() << "timed out waiting for " << name << " to " << value_matcher + << ": current value " << current_value; } } - return AssertionSuccess(); } AssertionResult TestUtility::waitForProactiveOverloadResourceUsageEq( diff --git a/test/test_common/utility.h b/test/test_common/utility.h index 75d2b0268519c..2170287a13763 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -48,6 +48,12 @@ using testing::Invoke; // NOLINT(misc-unused-using-decls) namespace Envoy { +namespace DeprecatedStatWaitHelpers { + +template constexpr bool always_false = false; + +} // namespace DeprecatedStatWaitHelpers + #if defined(__has_feature) && \ (__has_feature(thread_sanitizer) || __has_feature(memory_sanitizer) || \ __has_feature(address_sanitizer)) @@ -225,36 +231,35 @@ class TestUtility { const std::string& name); /** - * Wait for a counter to == a given value. + * Wait for a counter to match a given value matcher. * @param store supplies the stats store. * @param name supplies the name of the counter to wait for. - * @param value supplies the value of the counter. + * @param value_matcher supplies the value matcher for the counter. * @param time_system the time system to use for waiting. * @param timeout the maximum time to wait before timing out, or 0 for no timeout. * @param dispatcher the dispatcher to run non-blocking periodically during the wait. - * @return AssertionSuccess() if the counter was == to the value within the timeout, else + * @return AssertionSuccess() if the counter matched within the timeout, else * AssertionFailure(). */ static AssertionResult - waitForCounterEq(Stats::Store& store, const std::string& name, uint64_t value, - Event::TestTimeSystem& time_system, - std::chrono::milliseconds timeout = std::chrono::milliseconds::zero(), - Event::Dispatcher* dispatcher = nullptr); + waitForCounter(Stats::Store& store, const std::string& name, + testing::Matcher value_matcher, Event::TestTimeSystem& time_system, + std::chrono::milliseconds timeout = std::chrono::milliseconds::zero(), + Event::Dispatcher* dispatcher = nullptr); - /** - * Wait for a counter to >= a given value. - * @param store supplies the stats store. - * @param name counter name. - * @param value target value. - * @param time_system the time system to use for waiting. - * @param timeout the maximum time to wait before timing out, or 0 for no timeout. - * @return AssertionSuccess() if the counter was >= the value within the timeout, else - * AssertionFailure(). - */ - static AssertionResult - waitForCounterGe(Stats::Store& store, const std::string& name, uint64_t value, - Event::TestTimeSystem& time_system, - std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()); + template static AssertionResult waitForCounterEq(Args&&...) { + static_assert(DeprecatedStatWaitHelpers::always_false, + "TestUtility::waitForCounterEq was removed; use " + "TestUtility::waitForCounter(..., testing::Eq(value), ...) instead."); + return AssertionFailure(); + } + + template static AssertionResult waitForCounterGe(Args&&...) { + static_assert(DeprecatedStatWaitHelpers::always_false, + "TestUtility::waitForCounterGe was removed; use " + "TestUtility::waitForCounter(..., testing::Ge(value), ...) instead."); + return AssertionFailure(); + } /** * Wait for a proactive resource usage in the overload manager to be == a given value. @@ -274,34 +279,33 @@ class TestUtility { std::chrono::milliseconds timeout); /** - * Wait for a gauge to >= a given value. + * Wait for a gauge to match a given value matcher. * @param store supplies the stats store. * @param name gauge name. - * @param value target value. + * @param value_matcher supplies the value matcher for the gauge. * @param time_system the time system to use for waiting. * @param timeout the maximum time to wait before timing out, or 0 for no timeout. - * @return AssertionSuccess() if the counter gauge >= to the value within the timeout, else + * @return AssertionSuccess() if the gauge matched within the timeout, else * AssertionFailure(). */ static AssertionResult - waitForGaugeGe(Stats::Store& store, const std::string& name, uint64_t value, - Event::TestTimeSystem& time_system, - std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()); + waitForGauge(Stats::Store& store, const std::string& name, + testing::Matcher value_matcher, Event::TestTimeSystem& time_system, + std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()); - /** - * Wait for a gauge to == a given value. - * @param store supplies the stats store. - * @param name gauge name. - * @param value target value. - * @param time_system the time system to use for waiting. - * @param timeout the maximum time to wait before timing out, or 0 for no timeout. - * @return AssertionSuccess() if the gauge was == to the value within the timeout, else - * AssertionFailure(). - */ - static AssertionResult - waitForGaugeEq(Stats::Store& store, const std::string& name, uint64_t value, - Event::TestTimeSystem& time_system, - std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()); + template static AssertionResult waitForGaugeEq(Args&&...) { + static_assert(DeprecatedStatWaitHelpers::always_false, + "TestUtility::waitForGaugeEq was removed; use " + "TestUtility::waitForGauge(..., testing::Eq(value), ...) instead."); + return AssertionFailure(); + } + + template static AssertionResult waitForGaugeGe(Args&&...) { + static_assert(DeprecatedStatWaitHelpers::always_false, + "TestUtility::waitForGaugeGe was removed; use " + "TestUtility::waitForGauge(..., testing::Ge(value), ...) instead."); + return AssertionFailure(); + } /** * Wait for a gauge to be destroyed. From 0d0d6f3cbdd47ae2332a0bb57f477b8e23896fab Mon Sep 17 00:00:00 2001 From: aakugan <164153358+aakugan@users.noreply.github.com> Date: Fri, 8 May 2026 10:21:28 +0530 Subject: [PATCH 260/750] reverse_tunnels: fix ReverseConnectionIOHandle cleanup/close and improve coverage (#44856) ## Commit Message Fix ReverseConnectionIOHandle cleanup/close ## Additional Description We have some local load tests which failed when we tried using lds to remove a lot of listeners one of the possible reasons seems to be this -> double close of fds which causes issues like EBADF elsewhere removed the double closes in the reverseconnectioniohandle. Minor: moved from ::close to the os sys calls singleton. ## Testing Added unit tests and if it matters the local tests became more stable. Signed-off-by: aakugan --- .../downstream_socket_interface/BUILD | 1 + .../reverse_connection_io_handle.cc | 66 ++++--------- .../reverse_connection_io_handle_test.cc | 98 ++++++++++++++++--- ...reverse_tunnel_initiator_extension_test.cc | 17 ++++ 4 files changed, 122 insertions(+), 60 deletions(-) diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD index 428cb5333787e..4aae074a27a60 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/BUILD @@ -74,6 +74,7 @@ envoy_cc_library( "//envoy/stats:stats_interface", "//envoy/stats:stats_macros", "//envoy/upstream:cluster_manager_interface", + "//source/common/api:os_sys_calls_lib", "//source/common/buffer:buffer_lib", "//source/common/common:logger_lib", "//source/common/event:real_time_system_lib", diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.cc b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.cc index 1349ccd787047..f1bb46e5b2c93 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.cc +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.cc @@ -4,12 +4,12 @@ #include #include -#include "envoy/event/deferred_deletable.h" #include "envoy/event/timer.h" #include "envoy/network/address.h" #include "envoy/network/connection.h" #include "envoy/upstream/cluster_manager.h" +#include "source/common/api/os_sys_calls_impl.h" #include "source/common/buffer/buffer_impl.h" #include "source/common/common/logger.h" #include "source/common/event/real_time_system.h" @@ -57,6 +57,7 @@ void ReverseConnectionIOHandle::cleanup() { "trigger_pipe_write_fd_={}, trigger_pipe_read_fd_={}", trigger_pipe_write_fd_, trigger_pipe_read_fd_); resetFileEvents(); + SET_SOCKET_INVALID(trigger_pipe_read_fd_); // Clean up pipe trigger mechanism first to prevent use-after-free. ENVOY_LOG_MISC(trace, @@ -64,47 +65,17 @@ void ReverseConnectionIOHandle::cleanup() { "trigger_pipe_write_fd_={}, trigger_pipe_read_fd_={}", trigger_pipe_write_fd_, trigger_pipe_read_fd_); if (trigger_pipe_write_fd_ >= 0) { - ::close(trigger_pipe_write_fd_); + Api::OsSysCallsSingleton::get().close(trigger_pipe_write_fd_); trigger_pipe_write_fd_ = -1; } - if (trigger_pipe_read_fd_ >= 0) { - ::close(trigger_pipe_read_fd_); - trigger_pipe_read_fd_ = -1; - } - - // Reset the retry timer. Don't call enabled() here — it asserts - // dispatcher_.isThreadSafe(), which fails when the destructor runs on the main - // thread during shutdown (the timer was created on a worker thread). - if (rev_conn_retry_timer_) { - ENVOY_LOG_MISC(trace, "reverse_tunnel: resetting retry timer."); - rev_conn_retry_timer_.reset(); - } - - // Graceful shutdown of connection wrappers with exception safety. - ENVOY_LOG_MISC(debug, "Gracefully shutting down {} connection wrappers.", - connection_wrappers_.size()); - - // Move wrappers for deferred cleanup. - std::vector> wrappers_to_delete; - for (auto& wrapper : connection_wrappers_) { - if (wrapper) { - ENVOY_LOG(debug, "Moving connection wrapper for deferred cleanup."); - wrappers_to_delete.push_back(std::move(wrapper)); - } - } - // Clear containers safely. - connection_wrappers_.clear(); - conn_wrapper_to_host_map_.clear(); - - // Clean up wrappers with safe deletion. - for (auto& wrapper : wrappers_to_delete) { - if (wrapper && isThreadLocalDispatcherAvailable()) { - getThreadLocalDispatcher().deferredDelete(std::move(wrapper)); - } else { - // Direct cleanup when dispatcher not available. - wrapper.reset(); - } + // If initializeFileEvent() ran, fd_ was reassigned to trigger_pipe_read_fd_ and the base class + // will close that. We must close original_socket_fd_ explicitly since nothing else owns it. + // This guards against cleanup() being called without close() (e.g. destructor-only path). + if (original_socket_fd_ != fd_ && original_socket_fd_ >= 0) { + ENVOY_LOG(debug, "cleanup: closing original socket FD: {}.", original_socket_fd_); + Api::OsSysCallsSingleton::get().close(original_socket_fd_); + original_socket_fd_ = -1; } // Clear cluster to hosts mapping. @@ -342,12 +313,14 @@ ReverseConnectionIOHandle::connect(Envoy::Network::Address::InstanceConstSharedP Api::IoCallUint64Result ReverseConnectionIOHandle::close() { ENVOY_LOG(error, "reverse_tunnel: performing graceful shutdown."); - // Clean up original socket FD - if (original_socket_fd_ != -1) { + // If initializeFileEvent() ran, fd_ was reassigned to trigger_pipe_read_fd_ and the base class + // will close that. We must close original_socket_fd_ explicitly since nothing else owns it. + // If initializeFileEvent() did not run, fd_ == original_socket_fd_ and the base class handles it. + if (original_socket_fd_ != fd_ && original_socket_fd_ >= 0) { ENVOY_LOG(error, "Closing original socket FD: {}.", original_socket_fd_); - ::close(original_socket_fd_); - original_socket_fd_ = -1; + Api::OsSysCallsSingleton::get().close(original_socket_fd_); } + SET_SOCKET_INVALID(original_socket_fd_); // CRITICAL: If we're using pipe trigger FD, let the IoSocketHandleImpl::close() // close it and cleanup() set the pipe FDs to -1. @@ -1214,12 +1187,7 @@ void ReverseConnectionIOHandle::onConnectionDone(const std::string& error, if (wrapper_vector_it != connection_wrappers_.end()) { auto wrapper_to_delete = std::move(*wrapper_vector_it); connection_wrappers_.erase(wrapper_vector_it); - - // Use deferred deletion to prevent crash during cleanup. - std::unique_ptr deletable_wrapper( - static_cast(wrapper_to_delete.release())); - getThreadLocalDispatcher().deferredDelete(std::move(deletable_wrapper)); - ENVOY_LOG(debug, "reverse_tunnel: Deferred delete of connection wrapper"); + getThreadLocalDispatcher().deferredDelete(std::move(wrapper_to_delete)); } } diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc index ed3763718b194..e5d23f499634e 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc @@ -78,6 +78,9 @@ class ReverseConnectionIOHandleTest : public testing::Test { } extension_.reset(); socket_interface_.reset(); + while (dispatcher_.to_delete_.size()) { + dispatcher_.to_delete_.pop_front(); + } } // Helper to create a ReverseConnectionIOHandle with specified configuration. @@ -290,9 +293,16 @@ class ReverseConnectionIOHandleTest : public testing::Test { return mock_host; } + std::unique_ptr> getDeletableConn() { + auto mock_connection = std::make_unique>(); + EXPECT_CALL(*mock_connection, dispatcher()).WillRepeatedly(ReturnRef(dispatcher_)); + + return mock_connection; + } + // Helper method to set up mock connection with proper socket expectations. std::unique_ptr> setupMockConnection() { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(); // Create a mock socket for the connection. auto mock_socket_ptr = std::make_unique>(); @@ -1182,7 +1192,7 @@ TEST_F(ReverseConnectionIOHandleTest, InitiateOneReverseConnectionSuccess) { addHostConnectionInfo("192.168.1.1", "test-cluster", 1); // Set up mock for successful connection. - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(); Upstream::MockHost::MockCreateConnectionData success_conn_data; success_conn_data.connection_ = mock_connection.get(); success_conn_data.host_description_ = mock_host; @@ -1257,7 +1267,7 @@ TEST_F(ReverseConnectionIOHandleTest, InitiateReverseConnectionWithCustomScope) addHostConnectionInfo("192.168.1.1", "test-cluster", 1); // Set up mock for successful connection. - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(); Upstream::MockHost::MockCreateConnectionData success_conn_data; success_conn_data.connection_ = mock_connection.get(); success_conn_data.host_description_ = mock_host; @@ -1373,6 +1383,27 @@ TEST_F(ReverseConnectionIOHandleTest, InitiateOneReverseConnectionNonExistentClu EXPECT_EQ(wrapper_to_host_map.size(), 0); } +// Null cluster info returns false without crashing. +TEST_F(ReverseConnectionIOHandleTest, InitiateOneReverseConnectionNullClusterInfo) { + setupThreadLocalSlot(); + + auto config = createDefaultTestConfig(); + io_handle_ = createTestIOHandle(config); + EXPECT_NE(io_handle_, nullptr); + + auto mock_thread_local_cluster = std::make_shared>(); + EXPECT_CALL(cluster_manager_, getThreadLocalCluster("test-cluster")) + .WillRepeatedly(Return(mock_thread_local_cluster.get())); + EXPECT_CALL(*mock_thread_local_cluster, info()).WillRepeatedly(Return(nullptr)); + + auto mock_host = createMockHost("192.168.1.1"); + bool result = initiateOneReverseConnection("test-cluster", "192.168.1.1", mock_host); + EXPECT_FALSE(result); + + auto stat_map = extension_->getCrossWorkerStatMap(); + EXPECT_EQ(stat_map["test_scope.reverse_connections.host.192.168.1.1.cannot_connect"], 1); +} + // Test mixed success and failure scenarios for multiple connection attempts. TEST_F(ReverseConnectionIOHandleTest, InitiateMultipleConnectionsMixedResults) { // Set up thread local slot first so stats can be properly tracked. @@ -1417,8 +1448,8 @@ TEST_F(ReverseConnectionIOHandleTest, InitiateMultipleConnectionsMixedResults) { // 3. Third host: successful connection // Prepare mock connections that will be transferred to the wrappers. - auto mock_connection1 = std::make_unique>(); - auto mock_connection3 = std::make_unique>(); + auto mock_connection1 = getDeletableConn(); + auto mock_connection3 = getDeletableConn(); // Set up connection info for the connections. auto local_address = std::make_shared("10.0.0.2", 40000); @@ -1591,12 +1622,12 @@ TEST_F(ReverseConnectionIOHandleTest, RemoveStaleHostAndCloseConnections) { EXPECT_CALL(*mock_priority_set, crossPriorityHostMap()).WillRepeatedly(Return(host_map)); // Set up successful connections for both hosts. - auto mock_connection1 = std::make_unique>(); + auto mock_connection1 = getDeletableConn(); Upstream::MockHost::MockCreateConnectionData success_conn_data1; success_conn_data1.connection_ = mock_connection1.get(); success_conn_data1.host_description_ = mock_host1; - auto mock_connection2 = std::make_unique>(); + auto mock_connection2 = getDeletableConn(); Upstream::MockHost::MockCreateConnectionData success_conn_data2; success_conn_data2.connection_ = mock_connection2.get(); success_conn_data2.host_description_ = mock_host2; @@ -2441,14 +2472,14 @@ TEST_F(ReverseConnectionIOHandleTest, CleanupClosesEstablishedConnections) { // Create two mock connections and add them to the established queue. // 1) An open connection should be closed with FlushWrite. { - auto open_conn = std::make_unique>(); + auto open_conn = getDeletableConn(); EXPECT_CALL(*open_conn, state()).WillOnce(Return(Network::Connection::State::Open)); EXPECT_CALL(*open_conn, close(Network::ConnectionCloseType::FlushWrite)); addConnectionToEstablishedQueue(std::move(open_conn)); } // 2) A closed connection should not be closed again. { - auto closed_conn = std::make_unique>(); + auto closed_conn = getDeletableConn(); EXPECT_CALL(*closed_conn, state()).WillOnce(Return(Network::Connection::State::Closed)); // No close() expected for closed connection. addConnectionToEstablishedQueue(std::move(closed_conn)); @@ -2905,7 +2936,7 @@ TEST_F(ReverseConnectionIOHandleTest, AcceptMethodSocketAndFdFailures) { // Test Case 1: Original socket not available or not open. { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(); // Create a mock socket that returns isOpen() = false. auto mock_socket_ptr = std::make_unique>(); @@ -2955,7 +2986,7 @@ TEST_F(ReverseConnectionIOHandleTest, AcceptMethodSocketAndFdFailures) { // Test Case 2: Failed to duplicate file descriptor. { - auto mock_connection = std::make_unique>(); + auto mock_connection = getDeletableConn(); // Create a mock socket with IO handle that fails to duplicate. auto mock_socket_ptr = std::make_unique>(); @@ -3156,6 +3187,51 @@ TEST_F(ReverseConnectionIOHandleTest, OnConnectionDoneTlsConnectionDynamicCastFa EXPECT_EQ(stat_map["test_scope.reverse_connections.cluster.test-cluster.connected"], 1); } +// Verify ReverseConnectionIOHandle::close() doesn't double-close original_socket_fd_ when it equals +// fd_. +TEST_F(ReverseConnectionIOHandleTest, CloseNoDoubleCloseWhenOriginalEqualsFd) { + auto config = createDefaultTestConfig(); + int test_fd = ::socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GE(test_fd, 0); + + NiceMock mock_os_syscalls; + TestThreadsafeSingletonInjector injector(&mock_os_syscalls); + EXPECT_CALL(mock_os_syscalls, close(test_fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); + + auto handle = std::make_unique(test_fd, config, cluster_manager_, + extension_.get(), *stats_scope_); + handle->close(); + handle.reset(); +} + +// Verify that after initializeFileEvent (pipe created), close+destructor closes each FD exactly +// once. After initializeFileEvent: fd_ = pipe_read_fd, original_socket_fd_ = original_fd, +// pipe_write_fd separate. close() should close original_fd once (manual) and pipe_read_fd once (via +// IoSocketHandleImpl::close). cleanup() closes pipe_write_fd once. +TEST_F(ReverseConnectionIOHandleTest, CloseNoDoubleCloseWithPipeFds) { + auto config = createDefaultTestConfig(); + io_handle_ = createTestIOHandle(config); + + NiceMock mock_os_syscalls; + TestThreadsafeSingletonInjector injector(&mock_os_syscalls); + EXPECT_CALL(mock_os_syscalls, close(io_handle_->fdDoNotUse())) + .WillOnce(Return(Api::SysCallIntResult{0, 0})); + + Event::FileReadyCb mock_callback = [](uint32_t) -> absl::Status { return absl::OkStatus(); }; + io_handle_->initializeFileEvent(dispatcher_, mock_callback, Event::FileTriggerType::Level, + Event::FileReadyType::Read); + + ASSERT_TRUE(isTriggerPipeReady()); + + os_fd_t pipe_read_fd = getTriggerPipeReadFd(); + os_fd_t pipe_write_fd = getTriggerPipeWriteFd(); + EXPECT_CALL(mock_os_syscalls, close(pipe_read_fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); + EXPECT_CALL(mock_os_syscalls, close(pipe_write_fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); + + io_handle_->close(); + io_handle_.reset(); +} + } // namespace ReverseConnection } // namespace Bootstrap } // namespace Extensions diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc index 977ec6072d485..2a11f311a3964 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_tunnel_initiator_extension_test.cc @@ -368,6 +368,23 @@ TEST_F(ReverseTunnelInitiatorExtensionTest, UpdateConnectionStatsWithDetailedSta EXPECT_FALSE(found_detailed_stats); } +// incrementHandshakeStats should no-op when detailed stats are disabled. +TEST_F(ReverseTunnelInitiatorExtensionTest, IncrementHandshakeStatsWithDetailedStatsDisabled) { + envoy::extensions::bootstrap::reverse_tunnel::downstream_socket_interface::v3:: + DownstreamReverseConnectionSocketInterface no_stats_config; + no_stats_config.set_stat_prefix("reverse_connections"); + no_stats_config.set_enable_detailed_stats(false); + + auto no_stats_extension = + std::make_unique(context_, no_stats_config); + + no_stats_extension->incrementHandshakeStats("cluster1", true, ""); + no_stats_extension->incrementHandshakeStats("cluster2", false, "timeout"); + + auto cross_worker_stat_map = no_stats_extension->getCrossWorkerStatMap(); + EXPECT_TRUE(cross_worker_stat_map.empty()); +} + // Test per-worker stats aggregation for one thread only (test thread) TEST_F(ReverseTunnelInitiatorExtensionTest, GetPerWorkerStatMapSingleThread) { // Set up thread local slot first. From 4a9081f5e75ebc8044c6fd54b5aaa8e6c059033e Mon Sep 17 00:00:00 2001 From: code Date: Fri, 8 May 2026 16:44:08 +0800 Subject: [PATCH 261/750] tcp proxy: add new option to let tcp proxy check the drain close status (#44567) Commit Message: tcp proxy: add new option to let tcp proxy check the drain close status Additional Description: To close https://github.com/envoyproxy/envoy/issues/44419. There are some other solutions in my mind, like to register a callback for every connection to the drain close manager. But it will introduce huge complexity to ensure all these callbacks are called correctly in correct threads and have impact to our core code tree. So, I finally select the simplest one. This is clean, safe, easy to review/maintain, and kept similar logic with HCM. Risk Level: low. touch core code but guarded by new proto API. Testing: unit. Docs Changes: n/a. Release Notes: added. Platform Specific Features: n/a. --------- Signed-off-by: wbpcode/wangbaiping --- .../network/tcp_proxy/v3/tcp_proxy.proto | 9 ++- changelogs/current.yaml | 6 ++ .../network_filters/tcp_proxy_filter.rst | 1 + envoy/stream_info/stream_info.h | 1 + source/common/tcp_proxy/tcp_proxy.cc | 23 ++++++- source/common/tcp_proxy/tcp_proxy.h | 8 +++ test/common/tcp_proxy/tcp_proxy_test.cc | 68 +++++++++++++++++++ .../filters/network/tcp_proxy/config_test.cc | 13 ++++ test/mocks/server/factory_context.cc | 4 ++ test/mocks/server/factory_context.h | 2 + 10 files changed, 133 insertions(+), 2 deletions(-) diff --git a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto index b3dd0f63191c2..e6d0737829277 100644 --- a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto +++ b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto @@ -81,7 +81,7 @@ enum ProxyProtocolTlvMergePolicy { APPEND_IF_EXISTS_OR_ADD = 2; } -// [#next-free-field: 24] +// [#next-free-field: 25] message TcpProxy { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.tcp_proxy.v2.TcpProxy"; @@ -397,4 +397,11 @@ message TcpProxy { // Use this carefully with server-first protocols. The upstream may send data before // receiving anything from downstream, which could fill the early data buffer. google.protobuf.UInt32Value max_early_data_bytes = 22 [(validate.rules).uint32 = {lte: 1048576}]; + + // If set to ``true``, the TCP proxy checks if the downstream connection was marked as drained + // after each read or write. When drain close is requested for the listener's traffic direction, + // the downstream connection is closed with ``FlushWrite``. + // + // This is disabled by default for backward compatibility. + google.protobuf.BoolValue check_drain_close = 24; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index caff0a0e7376a..e354271d2ed21 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -72,6 +72,12 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: tcp_proxy + change: | + Added :ref:`check_drain_close + ` to the TCP proxy + filter to close downstream connections with ``FlushWrite`` when the drain manager requests drain close during + downstream read or write handling. - area: stat_sinks change: | Added :ref:`max_data_points_per_request diff --git a/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst index afbf63c685808..662f324bb06b2 100644 --- a/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst +++ b/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst @@ -210,6 +210,7 @@ The downstream statistics are rooted at *tcp..* with the following downstream_cx_total, Counter, Total number of connections handled by the filter downstream_cx_no_route, Counter, Number of connections for which no matching route was found or the cluster for the route was not found + downstream_cx_drain_close, Counter, Total number of connections closed due to drain close downstream_cx_tx_bytes_total, Counter, Total bytes written to the downstream connection downstream_cx_tx_bytes_buffered, Gauge, Total bytes currently buffered to the downstream connection downstream_cx_rx_bytes_total, Counter, Total bytes read from the downstream connection diff --git a/envoy/stream_info/stream_info.h b/envoy/stream_info/stream_info.h index 5675997e9def8..09c2d077740c5 100644 --- a/envoy/stream_info/stream_info.h +++ b/envoy/stream_info/stream_info.h @@ -283,6 +283,7 @@ struct LocalCloseReasonValues { const std::string TransportSocketTimeout = "transport_socket_timeout"; const std::string TriggeredDelayedCloseTimeout = "triggered_delayed_close_timeout"; const std::string TcpProxyInitializationFailure = "tcp_initializion_failure:"; + const std::string TcpProxyDrainClose = "tcp_proxy_drain_close"; const std::string TcpSessionIdleTimeout = "tcp_session_idle_timeout"; const std::string MaxConnectionDurationReached = "max_connection_duration_reached"; const std::string ClosingUpstreamTcpDueToDownstreamRemoteClose = diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index edc244c73acae..e07abe5d9808f 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -220,7 +220,13 @@ Config::Config(const envoy::extensions::filters::network::tcp_proxy::v3::TcpProx upstream_drain_manager_slot_(context.serverFactoryContext().threadLocal().allocateSlot()), shared_config_(std::make_shared(config, context)), random_generator_(context.serverFactoryContext().api().randomGenerator()), - regex_engine_(context.serverFactoryContext().regexEngine()) { + regex_engine_(context.serverFactoryContext().regexEngine()), + drain_decision_(context.drainDecision()), + drain_close_scope_(context.listenerInfo().direction() == + envoy::config::core::v3::TrafficDirection::INBOUND + ? Network::DrainDirection::InboundOnly + : Network::DrainDirection::All), + check_drain_close_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, check_drain_close, false)) { upstream_drain_manager_slot_->set([](Event::Dispatcher&) { ThreadLocal::ThreadLocalObjectSharedPtr drain_manager = std::make_shared(); @@ -1112,6 +1118,7 @@ Network::FilterStatus Filter::onData(Buffer::Instance& data, bool end_stream) { // Before there is an upstream the connection should be readDisabled. If the upstream is // destroyed, there should be no further reads as well. ASSERT(0 == data.length()); + maybeCloseDownstreamForDrainClose(); return Network::FilterStatus::StopIteration; } @@ -1248,6 +1255,20 @@ void Filter::onUpstreamData(Buffer::Instance& data, bool end_stream) { read_callbacks_->connection().write(data, end_stream); ASSERT(0 == data.length()); resetIdleTimer(); // TODO(ggreenway) PERF: do we need to reset timer on both send and receive? + maybeCloseDownstreamForDrainClose(); +} + +void Filter::maybeCloseDownstreamForDrainClose() { + if (!config_->checkDrainClose() || downstream_closed_ || + read_callbacks_->connection().state() != Network::Connection::State::Open || + !config_->drainDecision().drainClose(config_->drainCloseScope())) { + return; + } + + ENVOY_CONN_LOG(debug, "drain closing tcp_proxy connection", read_callbacks_->connection()); + config_->stats().downstream_cx_drain_close_.inc(); + read_callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite, + StreamInfo::LocalCloseReasons::get().TcpProxyDrainClose); } void Filter::onUpstreamEvent(Network::ConnectionEvent event) { diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index f6c82791b29a3..546f0c2ec5aba 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -59,6 +59,7 @@ constexpr absl::string_view ReceiveBeforeConnectKey = "envoy.tcp_proxy.receive_b * All tcp proxy stats. @see stats_macros.h */ #define ALL_TCP_PROXY_STATS(COUNTER, GAUGE) \ + COUNTER(downstream_cx_drain_close) \ COUNTER(downstream_cx_no_route) \ COUNTER(downstream_cx_rx_bytes_total) \ COUNTER(downstream_cx_total) \ @@ -377,6 +378,9 @@ class Config { } const absl::optional& maxEarlyDataBytes() const { return max_early_data_bytes_; } + bool checkDrainClose() const { return check_drain_close_; } + const Network::DrainDecision& drainDecision() const { return drain_decision_; } + Network::DrainDirection drainCloseScope() const { return drain_close_scope_; } private: struct SimpleRouteImpl : public Route { @@ -433,6 +437,9 @@ class Config { envoy::extensions::filters::network::tcp_proxy::v3::UpstreamConnectMode upstream_connect_mode_{ envoy::extensions::filters::network::tcp_proxy::v3::IMMEDIATE}; absl::optional max_early_data_bytes_; + const Network::DrainDecision& drain_decision_; + const Network::DrainDirection drain_close_scope_{}; + const bool check_drain_close_{false}; }; using ConfigSharedPtr = std::shared_ptr; @@ -690,6 +697,7 @@ class Filter : public Network::ReadFilter, void onDownstreamEvent(Network::ConnectionEvent event); void onUpstreamData(Buffer::Instance& data, bool end_stream); void onUpstreamEvent(Network::ConnectionEvent event); + void maybeCloseDownstreamForDrainClose(); void onUpstreamConnection(); void onIdleTimeout(); void resetIdleTimer(); diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 3a26636f44bf1..9ab2ebd03a053 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -265,6 +265,74 @@ TEST_P(TcpProxyTest, HalfCloseProxy) { upstream_callbacks_->onEvent(Network::ConnectionEvent::RemoteClose); } +TEST_P(TcpProxyTest, DrainCloseIgnoredWhenFlagDisabled) { + setup(1); + + EXPECT_CALL(factory_context_.drain_manager_, drainClose(Network::DrainDirection::All)).Times(0); + EXPECT_CALL(filter_callbacks_.connection_, close(_, _)).Times(0); + + raiseEventUpstreamConnected(0); + + Buffer::OwnedImpl buffer("hello"); + EXPECT_CALL(*upstream_connections_.at(0), write(BufferEqual(&buffer), false)); + filter_->onData(buffer, false); +} + +TEST_P(TcpProxyTest, DrainCloseAfterDownstreamRead) { + auto config = defaultConfig(); + config.mutable_check_drain_close()->set_value(true); + setup(1, config); + + EXPECT_CALL(factory_context_.drain_manager_, drainClose(Network::DrainDirection::All)) + .WillOnce(Return(true)); + EXPECT_CALL(filter_callbacks_.connection_, + close(Network::ConnectionCloseType::FlushWrite, + StreamInfo::LocalCloseReasons::get().TcpProxyDrainClose)); + + raiseEventUpstreamConnected(0); + + Buffer::OwnedImpl buffer("hello"); + EXPECT_CALL(*upstream_connections_.at(0), write(BufferEqual(&buffer), false)); + filter_->onData(buffer, false); +} + +TEST_P(TcpProxyTest, DrainCloseUsesInboundOnlyScopeForInboundListeners) { + auto config = defaultConfig(); + config.mutable_check_drain_close()->set_value(true); + EXPECT_CALL(factory_context_.listener_info_, direction()) + .WillRepeatedly(Return(envoy::config::core::v3::TrafficDirection::INBOUND)); + setup(1, config); + + EXPECT_CALL(factory_context_.drain_manager_, drainClose(Network::DrainDirection::InboundOnly)) + .WillOnce(Return(true)); + EXPECT_CALL(filter_callbacks_.connection_, + close(Network::ConnectionCloseType::FlushWrite, + StreamInfo::LocalCloseReasons::get().TcpProxyDrainClose)); + + raiseEventUpstreamConnected(0); + + Buffer::OwnedImpl buffer("hello"); + EXPECT_CALL(*upstream_connections_.at(0), write(BufferEqual(&buffer), false)); + filter_->onData(buffer, false); +} + +TEST_P(TcpProxyTest, DrainCloseAfterDownstreamWrite) { + auto config = defaultConfig(); + config.mutable_check_drain_close()->set_value(true); + setup(1, config); + + raiseEventUpstreamConnected(0); + + Buffer::OwnedImpl buffer("world"); + EXPECT_CALL(factory_context_.drain_manager_, drainClose(Network::DrainDirection::All)) + .WillOnce(Return(true)); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferEqual(&buffer), false)); + EXPECT_CALL(filter_callbacks_.connection_, + close(Network::ConnectionCloseType::FlushWrite, + StreamInfo::LocalCloseReasons::get().TcpProxyDrainClose)); + upstream_callbacks_->onUpstreamData(buffer, false); +} + // Test with an explicitly configured upstream. TEST_P(TcpProxyTest, ExplicitFactory) { // Explicitly configure an HTTP upstream, to test factory creation. diff --git a/test/extensions/filters/network/tcp_proxy/config_test.cc b/test/extensions/filters/network/tcp_proxy/config_test.cc index e42a52e9c86a6..32b3114ab1570 100644 --- a/test/extensions/filters/network/tcp_proxy/config_test.cc +++ b/test/extensions/filters/network/tcp_proxy/config_test.cc @@ -75,6 +75,19 @@ TEST(ConfigTest, ConfigTest) { cb(connection); } +TEST(ConfigTest, ConfigWithDrainCloseCheck) { + NiceMock context; + ConfigFactory factory; + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = + *dynamic_cast( + factory.createEmptyConfigProto().get()); + config.set_stat_prefix("prefix"); + config.set_cluster("cluster"); + config.mutable_check_drain_close()->set_value(true); + + EXPECT_TRUE(factory.createFilterFactoryFromProto(config, context).ok()); +} + TEST(ConfigTest, TunnelingConfigWithFormatters) { Envoy::Formatter::TestCommandFactory test_factory; Registry::InjectFactory register_factory(test_factory); diff --git a/test/mocks/server/factory_context.cc b/test/mocks/server/factory_context.cc index c138d0f176a14..ca4ffda9597da 100644 --- a/test/mocks/server/factory_context.cc +++ b/test/mocks/server/factory_context.cc @@ -11,6 +11,7 @@ namespace Envoy { namespace Server { namespace Configuration { +using ::testing::Return; using ::testing::ReturnRef; MockFactoryContext::MockFactoryContext() { @@ -22,6 +23,9 @@ MockFactoryContext::MockFactoryContext() { ON_CALL(*this, drainDecision()).WillByDefault(ReturnRef(drain_manager_)); ON_CALL(*this, listenerScope()).WillByDefault(ReturnRef(*listener_store_.rootScope())); + ON_CALL(*this, listenerInfo()).WillByDefault(ReturnRef(listener_info_)); + ON_CALL(listener_info_, direction()) + .WillByDefault(Return(envoy::config::core::v3::TrafficDirection::UNSPECIFIED)); } MockFactoryContext::~MockFactoryContext() = default; diff --git a/test/mocks/server/factory_context.h b/test/mocks/server/factory_context.h index 9fe92ab4d0379..d163510ed89ac 100644 --- a/test/mocks/server/factory_context.h +++ b/test/mocks/server/factory_context.h @@ -5,6 +5,7 @@ #include "source/common/router/context_impl.h" #include "source/common/tls/context_manager_impl.h" +#include "test/mocks/network/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/drain_manager.h" #include "test/mocks/server/instance.h" @@ -39,6 +40,7 @@ class MockFactoryContext : public virtual ListenerFactoryContext { Stats::IsolatedStoreImpl listener_store_; Stats::Scope& listener_scope_{*listener_store_.rootScope()}; testing::NiceMock drain_manager_; + testing::NiceMock listener_info_; }; class MockUpstreamFactoryContext : public UpstreamFactoryContext { From 26457687b2f086e40687d6d7d188cb16682195d4 Mon Sep 17 00:00:00 2001 From: Doron Hogery Date: Fri, 8 May 2026 18:57:11 +0300 Subject: [PATCH 262/750] tls: correctly send and receive resets for aborted connections (#44149) SslSocket now detects ECONNRESET by reading the system error code from BoringSSL's error queue (ERR_peek_error) instead of checking errno. The BIO layer (io_handle_bio.cc) stores the system error via ERR_put_error when IoHandle I/O fails, ensuring the error is captured at the syscall boundary in os_sys_calls_impl.cc. This brings SslSocket to parity with RawBufferSocket which already reports ConnectionReset for ECONNRESET. When a connection is closed with ``ConnectionCloseType::AbortReset``, ``SslSocket`` also skips the TLS ``close_notify`` shutdown so the peer reliably observes a TCP RST instead of racing a graceful close against the reset. Signed-off-by: Doron Hogery --- changelogs/current.yaml | 9 + envoy/network/transport_socket.h | 5 +- source/common/network/connection_impl.cc | 7 +- source/common/network/raw_buffer_socket.h | 2 +- source/common/runtime/runtime_features.cc | 1 + source/common/tls/BUILD | 1 + source/common/tls/io_handle_bio.cc | 20 +- source/common/tls/ssl_socket.cc | 22 +- source/common/tls/ssl_socket.h | 5 +- .../transport_sockets/alts/tsi_socket.cc | 2 +- .../transport_sockets/alts/tsi_socket.h | 2 +- .../transport_sockets/common/passthrough.cc | 4 +- .../transport_sockets/common/passthrough.h | 2 +- .../starttls/starttls_socket.h | 4 +- .../extensions/transport_sockets/tap/tap.cc | 4 +- source/extensions/transport_sockets/tap/tap.h | 2 +- .../transport_sockets/tcp_stats/tcp_stats.cc | 4 +- .../transport_sockets/tcp_stats/tcp_stats.h | 2 +- test/common/network/connection_impl_test.cc | 24 +- .../tls/integration/ssl_integration_test.cc | 50 +++- test/common/tls/ssl_socket_test.cc | 222 ++++++++++++++++++ .../transport_sockets/alts/tsi_socket_test.cc | 4 +- .../common/passthrough_test.cc | 5 +- .../starttls/starttls_socket_test.cc | 10 +- .../tcp_stats/tcp_stats_test.cc | 6 +- .../upstream_access_log_integration_test.cc | 4 +- test/mocks/network/transport_socket.h | 2 +- 27 files changed, 372 insertions(+), 53 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index e354271d2ed21..0fc3636aa24fb 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -34,6 +34,15 @@ minor_behavior_changes: Optimized prometheus stats endpoint. Users should see a roughly 30-40% latency improvement in calls to the endpoint for cases where the scrape results in lots of cluster stats. There should be no visible changes to users, or incompatibilities. +- area: tls + change: | + ``SslSocket`` now reports ``ECONNRESET`` as ``ConnectionReset`` by reading the system error + code from BoringSSL's error queue, matching ``RawBufferSocket`` behavior. When a connection + is closed with ``ConnectionCloseType::AbortReset``, ``SslSocket`` also skips the TLS + ``close_notify`` shutdown so the peer reliably observes a TCP RST instead of racing a + graceful close against the reset. + This behavioral change can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.ssl_socket_report_connection_reset`` to ``false``. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/envoy/network/transport_socket.h b/envoy/network/transport_socket.h index f79fe3566c340..0b2bb54a18c20 100644 --- a/envoy/network/transport_socket.h +++ b/envoy/network/transport_socket.h @@ -161,8 +161,11 @@ class TransportSocket { /** * Closes the transport socket. * @param event supplies the connection event that is closing the socket. + * @param abort_reset if true, the connection is being torn down with a TCP RST and the + * transport socket should skip any graceful shutdown (e.g. TLS close_notify) so the + * peer reliably observes the reset rather than racing it against a clean close. */ - virtual void closeSocket(Network::ConnectionEvent event) PURE; + virtual void closeSocket(Network::ConnectionEvent event, bool abort_reset = false) PURE; /** * @param buffer supplies the buffer to read to. diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index 820b8ebe3fd03..f125a5ce69248 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -359,7 +359,9 @@ void ConnectionImpl::closeSocket(ConnectionEvent close_type) { } ENVOY_CONN_LOG(debug, "closing socket: {}", *this, static_cast(close_type)); - transport_socket_->closeSocket(close_type); + const bool abort_reset = detected_close_type_ == StreamInfo::DetectedCloseType::RemoteReset || + detected_close_type_ == StreamInfo::DetectedCloseType::LocalReset; + transport_socket_->closeSocket(close_type, abort_reset); // Drain input and output buffers. updateReadBufferStats(0, 0); @@ -373,8 +375,7 @@ void ConnectionImpl::closeSocket(ConnectionEvent close_type) { connection_stats_.reset(); - if (detected_close_type_ == StreamInfo::DetectedCloseType::RemoteReset || - detected_close_type_ == StreamInfo::DetectedCloseType::LocalReset) { + if (abort_reset) { #if ENVOY_PLATFORM_ENABLE_SEND_RST const bool ok = Network::Socket::applyOptions( Network::SocketOptionFactory::buildZeroSoLingerOptions(), *socket_, diff --git a/source/common/network/raw_buffer_socket.h b/source/common/network/raw_buffer_socket.h index 8d1f89c2d9ff8..33e2388b97685 100644 --- a/source/common/network/raw_buffer_socket.h +++ b/source/common/network/raw_buffer_socket.h @@ -17,7 +17,7 @@ class RawBufferSocket : public TransportSocket, protected Logger::Loggablereadv(outl, &slice, 1); + auto* io_handle = bio_io_handle(b); + auto result = io_handle->readv(outl, &slice, 1); BIO_clear_retry_flags(b); if (!result.ok()) { auto err = result.err_->getErrorCode(); @@ -38,6 +42,20 @@ int io_handle_read(BIO* b, char* out, int outl) { } return -1; } + // On EOF (readv returned 0 successfully), check SO_ERROR for a pending TCP RST. + // Some peers tear the connection down with RST instead of FIN; readv returns 0 + // in that case but a connection error is pending on the socket. Push it onto + // the error queue so the SSL layer can detect it via drainErrorQueue(). + if (result.return_value_ == 0 && + Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.ssl_socket_report_connection_reset")) { + int so_error = 0; + socklen_t so_error_len = sizeof(so_error); + auto opt_result = io_handle->getOption(SOL_SOCKET, SO_ERROR, &so_error, &so_error_len); + if (opt_result.return_value_ == 0 && so_error != 0) { + ERR_put_error(ERR_LIB_SYS, 0, so_error, __FILE__, __LINE__); + } + } return result.return_value_; } diff --git a/source/common/tls/ssl_socket.cc b/source/common/tls/ssl_socket.cc index ac7020744134e..b4993844a958d 100644 --- a/source/common/tls/ssl_socket.cc +++ b/source/common/tls/ssl_socket.cc @@ -6,6 +6,7 @@ #include "source/common/common/empty_string.h" #include "source/common/common/hex.h" #include "source/common/http/headers.h" +#include "source/common/runtime/runtime_features.h" #include "source/common/tls/io_handle_bio.h" #include "source/common/tls/ssl_handshaker.h" #include "source/common/tls/utility.h" @@ -169,7 +170,7 @@ Network::IoResult SslSocket::doRead(Buffer::Instance& read_buffer) { ENVOY_CONN_LOG(trace, "ssl read {} bytes", callbacks_->connection(), bytes_read); - return {action, bytes_read, end_stream}; + return {action, bytes_read, end_stream, detected_io_error_}; } void SslSocket::onPrivateKeyMethodComplete() { resumeHandshake(); } @@ -224,6 +225,11 @@ void SslSocket::drainErrorQueue() { saw_cert_verify_failed = true; } } else if (ERR_GET_LIB(err) == ERR_LIB_SYS) { + if (ERR_GET_REASON(err) == ECONNRESET && + Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.ssl_socket_report_connection_reset")) { + detected_io_error_ = Api::IoError::IoErrorCode::ConnectionReset; + } // Any syscall errors that result in connection closure are already tracked in other // connection related stats. We will still retain the specific syscall failure for // transport failure reasons. @@ -320,7 +326,7 @@ Network::IoResult SslSocket::doWrite(Buffer::Instance& write_buffer, bool end_st // Renegotiation has started. We don't handle renegotiation so just fall through. default: drainErrorQueue(); - return {PostIoAction::Close, total_bytes_written, false}; + return {PostIoAction::Close, total_bytes_written, false, detected_io_error_}; } break; @@ -369,12 +375,22 @@ void SslSocket::shutdownBasic() { } } -void SslSocket::closeSocket(Network::ConnectionEvent) { +void SslSocket::closeSocket(Network::ConnectionEvent, bool abort_reset) { // Unregister the SSL connection object from private key method providers. for (auto const& provider : ctx_->getPrivateKeyMethodProviders()) { provider->unregisterPrivateKeyMethod(rawSsl()); } + // When the connection is being torn down with a TCP RST, skip the TLS shutdown + // (close_notify). Sending close_notify alongside a RST sends contradictory signals + // to the peer (graceful close vs. reset) and races the alert against the RST, + // making peer-side reset detection unreliable. Skipping close_notify ensures the + // peer reliably observes a connection reset. + if (abort_reset && Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.ssl_socket_report_connection_reset")) { + return; + } + // Attempt to send a shutdown before closing the socket. It's possible this won't go out if // there is no room on the socket. We can extend the state machine to handle this at some point // if needed. diff --git a/source/common/tls/ssl_socket.h b/source/common/tls/ssl_socket.h index fa8dd582e4529..e2431aa41e1dd 100644 --- a/source/common/tls/ssl_socket.h +++ b/source/common/tls/ssl_socket.h @@ -59,7 +59,7 @@ class SslSocket : public Network::TransportSocket, std::string protocol() const override; absl::string_view failureReason() const override; bool canFlushClose() override { return info_->state() == Ssl::SocketState::HandshakeComplete; } - void closeSocket(Network::ConnectionEvent close_type) override; + void closeSocket(Network::ConnectionEvent close_type, bool abort_reset) override; Network::IoResult doRead(Buffer::Instance& read_buffer) override; Network::IoResult doWrite(Buffer::Instance& write_buffer, bool end_stream) override; void onConnected() override; @@ -104,6 +104,7 @@ class SslSocket : public Network::TransportSocket, ContextImplSharedPtr ctx_; uint64_t bytes_to_retry_{}; std::string failure_reason_; + absl::optional detected_io_error_; SslHandshakerImplSharedPtr info_; }; @@ -114,7 +115,7 @@ class InvalidSslSocket : public Network::TransportSocket { void setTransportSocketCallbacks(Network::TransportSocketCallbacks&) override {} std::string protocol() const override { return EMPTY_STRING; } bool canFlushClose() override { return true; } - void closeSocket(Network::ConnectionEvent) override {} + void closeSocket(Network::ConnectionEvent, bool) override {} Network::IoResult doRead(Buffer::Instance&) override { return {Network::PostIoAction::Close, 0, false}; } diff --git a/source/extensions/transport_sockets/alts/tsi_socket.cc b/source/extensions/transport_sockets/alts/tsi_socket.cc index cf343e66082dd..ccc8993c1ad62 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.cc +++ b/source/extensions/transport_sockets/alts/tsi_socket.cc @@ -331,7 +331,7 @@ Network::IoResult TsiSocket::doWrite(Buffer::Instance& buffer, bool end_stream) } } -void TsiSocket::closeSocket(Network::ConnectionEvent) { +void TsiSocket::closeSocket(Network::ConnectionEvent, bool) { ENVOY_CONN_LOG(debug, "TSI: closing socket", callbacks_->connection()); if (handshaker_) { handshaker_.release()->deferredDelete(); diff --git a/source/extensions/transport_sockets/alts/tsi_socket.h b/source/extensions/transport_sockets/alts/tsi_socket.h index c44fb60bcb149..f8937f09286af 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.h +++ b/source/extensions/transport_sockets/alts/tsi_socket.h @@ -71,7 +71,7 @@ class TsiSocket : public Network::TransportSocket, bool startSecureTransport() override { return false; } void configureInitialCongestionWindow(uint64_t, std::chrono::microseconds) override {} Network::IoResult doWrite(Buffer::Instance& buffer, bool end_stream) override; - void closeSocket(Network::ConnectionEvent event) override; + void closeSocket(Network::ConnectionEvent event, bool abort_reset) override; Network::IoResult doRead(Buffer::Instance& buffer) override; void onConnected() override; diff --git a/source/extensions/transport_sockets/common/passthrough.cc b/source/extensions/transport_sockets/common/passthrough.cc index 37f06165f4a1a..e7241fdd2eddc 100644 --- a/source/extensions/transport_sockets/common/passthrough.cc +++ b/source/extensions/transport_sockets/common/passthrough.cc @@ -28,8 +28,8 @@ Api::SysCallIntResult PassthroughSocket::connect(Network::ConnectionSocket& sock return transport_socket_->connect(socket); } -void PassthroughSocket::closeSocket(Network::ConnectionEvent event) { - transport_socket_->closeSocket(event); +void PassthroughSocket::closeSocket(Network::ConnectionEvent event, bool abort_reset) { + transport_socket_->closeSocket(event, abort_reset); } Network::IoResult PassthroughSocket::doRead(Buffer::Instance& buffer) { diff --git a/source/extensions/transport_sockets/common/passthrough.h b/source/extensions/transport_sockets/common/passthrough.h index d48d0e1c113ce..adad52985feb7 100644 --- a/source/extensions/transport_sockets/common/passthrough.h +++ b/source/extensions/transport_sockets/common/passthrough.h @@ -72,7 +72,7 @@ class PassthroughSocket : public Network::TransportSocket { absl::string_view failureReason() const override; bool canFlushClose() override; Api::SysCallIntResult connect(Network::ConnectionSocket& socket) override; - void closeSocket(Network::ConnectionEvent event) override; + void closeSocket(Network::ConnectionEvent event, bool abort_reset) override; Network::IoResult doRead(Buffer::Instance& buffer) override; Network::IoResult doWrite(Buffer::Instance& buffer, bool end_stream) override; void onConnected() override; diff --git a/source/extensions/transport_sockets/starttls/starttls_socket.h b/source/extensions/transport_sockets/starttls/starttls_socket.h index cf39ed69ca47d..9a67d0c71441d 100644 --- a/source/extensions/transport_sockets/starttls/starttls_socket.h +++ b/source/extensions/transport_sockets/starttls/starttls_socket.h @@ -36,8 +36,8 @@ class StartTlsSocket : public Network::TransportSocket, Logger::LoggablecanFlushClose(); } Ssl::ConnectionInfoConstSharedPtr ssl() const override { return active_socket_->ssl(); } - void closeSocket(Network::ConnectionEvent event) override { - return active_socket_->closeSocket(event); + void closeSocket(Network::ConnectionEvent event, bool abort_reset) override { + return active_socket_->closeSocket(event, abort_reset); } Network::IoResult doRead(Buffer::Instance& buffer) override { diff --git a/source/extensions/transport_sockets/tap/tap.cc b/source/extensions/transport_sockets/tap/tap.cc index 77ee47f47d890..604d3b2c2cb7c 100644 --- a/source/extensions/transport_sockets/tap/tap.cc +++ b/source/extensions/transport_sockets/tap/tap.cc @@ -36,12 +36,12 @@ void TapSocket::setTransportSocketCallbacks(Network::TransportSocketCallbacks& c : nullptr; } -void TapSocket::closeSocket(Network::ConnectionEvent event) { +void TapSocket::closeSocket(Network::ConnectionEvent event, bool abort_reset) { if (tapper_ != nullptr) { tapper_->closeSocket(event); } - transport_socket_->closeSocket(event); + transport_socket_->closeSocket(event, abort_reset); } Network::IoResult TapSocket::doRead(Buffer::Instance& buffer) { diff --git a/source/extensions/transport_sockets/tap/tap.h b/source/extensions/transport_sockets/tap/tap.h index 7c0c9894983e6..8c5231900dce6 100644 --- a/source/extensions/transport_sockets/tap/tap.h +++ b/source/extensions/transport_sockets/tap/tap.h @@ -21,7 +21,7 @@ class TapSocket : public TransportSockets::PassthroughSocket { // Network::TransportSocket void setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) override; - void closeSocket(Network::ConnectionEvent event) override; + void closeSocket(Network::ConnectionEvent event, bool abort_reset) override; Network::IoResult doRead(Buffer::Instance& buffer) override; Network::IoResult doWrite(Buffer::Instance& buffer, bool end_stream) override; diff --git a/source/extensions/transport_sockets/tcp_stats/tcp_stats.cc b/source/extensions/transport_sockets/tcp_stats/tcp_stats.cc index 66f5ea0a71244..7a3524688671f 100644 --- a/source/extensions/transport_sockets/tcp_stats/tcp_stats.cc +++ b/source/extensions/transport_sockets/tcp_stats/tcp_stats.cc @@ -55,7 +55,7 @@ void TcpStatsSocket::onConnected() { transport_socket_->onConnected(); } -void TcpStatsSocket::closeSocket(Network::ConnectionEvent event) { +void TcpStatsSocket::closeSocket(Network::ConnectionEvent event, bool abort_reset) { // Record final values. recordStats(); @@ -71,7 +71,7 @@ void TcpStatsSocket::closeSocket(Network::ConnectionEvent event) { timer_->disableTimer(); } - transport_socket_->closeSocket(event); + transport_socket_->closeSocket(event, abort_reset); } absl::optional TcpStatsSocket::querySocketInfo() { diff --git a/source/extensions/transport_sockets/tcp_stats/tcp_stats.h b/source/extensions/transport_sockets/tcp_stats/tcp_stats.h index f786a063b3761..3117539993cf9 100644 --- a/source/extensions/transport_sockets/tcp_stats/tcp_stats.h +++ b/source/extensions/transport_sockets/tcp_stats/tcp_stats.h @@ -60,7 +60,7 @@ class TcpStatsSocket : public TransportSockets::PassthroughSocket, // Network::TransportSocket void setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) override; void onConnected() override; - void closeSocket(Network::ConnectionEvent event) override; + void closeSocket(Network::ConnectionEvent event, bool abort_reset) override; private: absl::optional querySocketInfo(); diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index ccea8c2f29e9d..9b42bb6342be2 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -501,7 +501,7 @@ TEST_P(ConnectionImplTest, SetServerTransportSocketTimeout) { Stats::MockCounter timeout_counter; EXPECT_CALL(timeout_counter, inc()); server_connection->setTransportSocketConnectTimeout(std::chrono::seconds(3), timeout_counter); - EXPECT_CALL(*transport_socket, closeSocket(ConnectionEvent::LocalClose)); + EXPECT_CALL(*transport_socket, closeSocket(ConnectionEvent::LocalClose, false)); mock_timer->invokeCallback(); EXPECT_THAT(stream_info_.connectionTerminationDetails(), Optional(HasSubstr("transport socket timeout"))); @@ -4018,7 +4018,7 @@ TEST_F(MockTransportConnectionImplTest, BufferHighWatermarkTimeoutClosesConnecti // Timer callback fires - closeSocket is called first, then during buffer drain enabled() is // checked - EXPECT_CALL(*transport_socket_, closeSocket(ConnectionEvent::LocalClose)); + EXPECT_CALL(*transport_socket_, closeSocket(ConnectionEvent::LocalClose, false)); EXPECT_CALL(*buffer_timer, enabled()).WillOnce(Return(false)); buffer_timer->invokeCallback(); EXPECT_EQ(static_cast(*connection_).localCloseReason(), @@ -4065,7 +4065,7 @@ TEST_F(MockTransportConnectionImplTest, BufferHighWatermarkTimeoutCancelledOnDra EXPECT_CALL(*buffer_timer, disableTimer()); EXPECT_TRUE(file_ready_cb_(Event::FileReadyType::Write).ok()); - EXPECT_CALL(*transport_socket_, closeSocket(_)); + EXPECT_CALL(*transport_socket_, closeSocket(_, _)); connection_->close(ConnectionCloseType::NoFlush); } @@ -4204,7 +4204,7 @@ TEST_F(PostCloseConnectionImplTest, ReadAfterCloseFlushWriteDelayIgnored) { })); EXPECT_TRUE(file_ready_cb_(Event::FileReadyType::Read).ok()); // Deferred close. - EXPECT_CALL(*transport_socket_, closeSocket(_)); + EXPECT_CALL(*transport_socket_, closeSocket(_, _)); } // Test that if a read event occurs after @@ -4231,7 +4231,7 @@ TEST_F(PostCloseConnectionImplTest, ReadAfterCloseFlushWriteDelayIgnoredWithFilt })); EXPECT_TRUE(file_ready_cb_(Event::FileReadyType::Read).ok()); // Deferred close. - EXPECT_CALL(*transport_socket_, closeSocket(_)); + EXPECT_CALL(*transport_socket_, closeSocket(_, _)); } // Test that if a read event occurs after @@ -4262,7 +4262,7 @@ TEST_F(PostCloseConnectionImplTest, ReadAfterCloseFlushWriteDelayIgnoredWithWrit EXPECT_CALL(*transport_socket_, doWrite(_, true)) .WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); // Deferred close. - EXPECT_CALL(*transport_socket_, closeSocket(_)); + EXPECT_CALL(*transport_socket_, closeSocket(_, _)); } // Test that if a read event occurs after @@ -4292,7 +4292,7 @@ TEST_F(PostCloseConnectionImplTest, ReadAfterCloseFlushWriteDelayIgnoredCanFlush EXPECT_TRUE(file_ready_cb_(Event::FileReadyType::Read).ok()); // Deferred close. - EXPECT_CALL(*transport_socket_, closeSocket(_)); + EXPECT_CALL(*transport_socket_, closeSocket(_, _)); } // Test that if a read event occurs after close(ConnectionCloseType::NoFlush), @@ -4303,7 +4303,7 @@ TEST_F(PostCloseConnectionImplTest, NoReadAfterCloseNoFlush) { initialize(); // Immediate connection close. - EXPECT_CALL(*transport_socket_, closeSocket(_)); + EXPECT_CALL(*transport_socket_, closeSocket(_, _)); connection_->close(ConnectionCloseType::NoFlush); // We don't even see a doRead(), let alone an onData() callback. @@ -4320,7 +4320,7 @@ TEST_F(PostCloseConnectionImplTest, NoReadAfterCloseFlushWrite) { initialize(); // Connection flush and close. - EXPECT_CALL(*transport_socket_, closeSocket(_)); + EXPECT_CALL(*transport_socket_, closeSocket(_, _)); connection_->close(ConnectionCloseType::FlushWrite); // We don't even see a doRead(), let alone an onData() callback. @@ -4341,7 +4341,7 @@ TEST_F(PostCloseConnectionImplTest, NoReadAfterCloseFlushWriteWriteData) { // it will be flushed here. EXPECT_CALL(*transport_socket_, doWrite(_, true)) .WillOnce(Return(IoResult{PostIoAction::KeepOpen, 0, false})); - EXPECT_CALL(*transport_socket_, closeSocket(_)); + EXPECT_CALL(*transport_socket_, closeSocket(_, _)); connection_->close(ConnectionCloseType::FlushWrite); // We don't even see a doRead(), let alone an onData() callback. @@ -4359,7 +4359,7 @@ TEST_F(PostCloseConnectionImplTest, CloseAbort) { // Connection abort. We have data written above in writeSomeData(), // it won't be written and flushed due to ``ConnectionCloseType::Abort``. EXPECT_CALL(*transport_socket_, doWrite(_, true)).Times(0); - EXPECT_CALL(*transport_socket_, closeSocket(_)); + EXPECT_CALL(*transport_socket_, closeSocket(_, _)); connection_->close(ConnectionCloseType::Abort); } @@ -4372,7 +4372,7 @@ TEST_F(PostCloseConnectionImplTest, AbortReset) { // Connection abort. We have data written above in writeSomeData(), // it won't be written and flushed due to ``ConnectionCloseType::AbortReset``. EXPECT_CALL(*transport_socket_, doWrite(_, true)).Times(0); - EXPECT_CALL(*transport_socket_, closeSocket(_)); + EXPECT_CALL(*transport_socket_, closeSocket(_, _)); connection_->close(ConnectionCloseType::AbortReset); } diff --git a/test/common/tls/integration/ssl_integration_test.cc b/test/common/tls/integration/ssl_integration_test.cc index bd56b5412a8b3..8ce4ea26700da 100644 --- a/test/common/tls/integration/ssl_integration_test.cc +++ b/test/common/tls/integration/ssl_integration_test.cc @@ -148,7 +148,7 @@ TEST_P(SslIntegrationTest, StatsTagExtraction) { for (const Stats::CounterSharedPtr& counter : test_server_->counters()) { // Useful for debugging when the test is failing. - if (counter->name().find("ssl") != std::string::npos) { + if (absl::StrContains(counter->name(), "ssl")) { ENVOY_LOG_MISC(critical, "Found ssl metric: {}", counter->name()); } auto it = expected_counters.find(counter->name()); @@ -388,6 +388,52 @@ TEST_P(SslIntegrationTest, LogPeerIpSanUnsupportedIpVersion) { EXPECT_EQ(result, "1.2.3.4,0:1:2:3::4"); } +#if ENVOY_PLATFORM_ENABLE_SEND_RST +TEST_P(SslIntegrationTest, TlsDownstreamReset) { + useListenerAccessLog("DS_CLOSE_TYPE=%DOWNSTREAM_DETECTED_CLOSE_TYPE%"); + initialize(); + + Network::ClientConnectionPtr connection = makeSslClientConnection({}); + ConnectionStatusCallbacks callbacks; + connection->addConnectionCallbacks(callbacks); + connection->connect(); + + while (!callbacks.connected()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + // Abort the connection with AbortReset. SslSocket skips the TLS close_notify + // shutdown when the connection is being torn down with a RST so the server + // reliably observes the reset and reports RemoteReset in the access log. + connection->close(Network::ConnectionCloseType::AbortReset); + + auto result = waitForAccessLog(listener_access_log_name_); + EXPECT_THAT(result, testing::HasSubstr("DS_CLOSE_TYPE=RemoteReset")); +} +#else +// On platforms that do not support sending a TCP RST (no SO_LINGER=0 path), +// AbortReset must still complete cleanly: ConnectionImpl falls back to a +// regular close and the peer observes a graceful close (not a RemoteReset). +TEST_P(SslIntegrationTest, TlsDownstreamResetUnsupported) { + useListenerAccessLog("DS_CLOSE_TYPE=%DOWNSTREAM_DETECTED_CLOSE_TYPE%"); + initialize(); + + Network::ClientConnectionPtr connection = makeSslClientConnection({}); + ConnectionStatusCallbacks callbacks; + connection->addConnectionCallbacks(callbacks); + connection->connect(); + + while (!callbacks.connected()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + connection->close(Network::ConnectionCloseType::AbortReset); + + auto result = waitForAccessLog(listener_access_log_name_); + EXPECT_THAT(result, testing::Not(testing::HasSubstr("DS_CLOSE_TYPE=RemoteReset"))); +} +#endif + // This test is disabled because it uses the timed_cert_validator which we don't support. BORINGSSL_TEST_P(SslIntegrationTest, AsyncCertValidationSucceeds) { // Config client to use an async cert validator which defer the actual validation by 5ms. @@ -990,7 +1036,7 @@ TEST_P(SslCertficateIntegrationTest, ServerRsaServerEcdsaP384EcdsaClientAllCurve testRouterRequestAndResponseWithBody(1024, 512, false, false, &creator); for (const Stats::CounterSharedPtr& counter : test_server_->counters()) { // Useful for debugging when the test is failing. - if (counter->name().find("ssl") != std::string::npos) { + if (absl::StrContains(counter->name(), "ssl")) { ENVOY_LOG_MISC(critical, "Found ssl metric: {}", counter->name()); } } diff --git a/test/common/tls/ssl_socket_test.cc b/test/common/tls/ssl_socket_test.cc index d57c631bd618d..6f730cabd5b4b 100644 --- a/test/common/tls/ssl_socket_test.cc +++ b/test/common/tls/ssl_socket_test.cc @@ -2,6 +2,7 @@ #include #include +#include "envoy/common/platform.h" #include "envoy/config/listener/v3/listener.pb.h" #include "envoy/config/listener/v3/listener_components.pb.h" #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" @@ -72,6 +73,7 @@ using testing::ContainsRegex; using testing::DoAll; using testing::InSequence; using testing::Invoke; +using testing::InvokeWithoutArgs; using testing::NiceMock; using testing::Return; using testing::ReturnRef; @@ -8251,6 +8253,226 @@ TEST_P(SslSocketTest, CertificateCompressionDisabled) { testUtilV2(test_options); } +#if ENVOY_PLATFORM_ENABLE_SEND_RST +// Verify that when a peer aborts the connection with a TCP RST (via Network::ConnectionCloseType:: +// AbortReset), the SslSocket skips the TLS close_notify shutdown and the remote side detects the +// close as RemoteReset rather than a graceful close. +TEST_P(SslSocketTest, TlsConnectionResetDetection) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_dns_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_dns_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/common/tls/test_data/ca_cert.pem" +)EOF"; + + const std::string client_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_uri_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_uri_key.pem" +)EOF"; + + testing::NiceMock + transport_socket_factory_context; + ON_CALL(transport_socket_factory_context.server_context_, api()).WillByDefault(ReturnRef(*api_)); + + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext server_tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(server_ctx_yaml), server_tls_context); + auto server_cfg = + THROW_OR_RETURN_VALUE(ServerContextConfigImpl::create( + server_tls_context, transport_socket_factory_context, {}, false), + std::unique_ptr); + NiceMock server_factory_context; + ContextManagerImpl manager(server_factory_context); + Stats::TestUtil::TestStore server_stats_store; + auto server_ssl_socket_factory = + THROW_OR_RETURN_VALUE(ServerSslSocketFactory::create(std::move(server_cfg), manager, + *server_stats_store.rootScope()), + std::unique_ptr); + + auto socket = std::make_shared( + Network::Test::getCanonicalLoopbackAddress(version_)); + const auto local_address = socket->connectionInfoProvider().localAddress(); + Network::MockTcpListenerCallbacks listener_callbacks; + NiceMock listener_config; + Server::ThreadLocalOverloadStateOptRef overload_state; + Network::ListenerPtr listener = createListener(std::move(socket), listener_callbacks, runtime_, + listener_config, overload_state, *dispatcher_); + + testing::NiceMock + client_factory_context; + ON_CALL(client_factory_context.server_context_, api()).WillByDefault(ReturnRef(*api_)); + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext client_tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(client_ctx_yaml), client_tls_context); + auto client_cfg = *ClientContextConfigImpl::create(client_tls_context, client_factory_context); + Stats::TestUtil::TestStore client_stats_store; + auto client_ssl_socket_factory = *ClientSslSocketFactory::create(std::move(client_cfg), manager, + *client_stats_store.rootScope()); + + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( + local_address, Network::Address::InstanceConstSharedPtr(), + client_ssl_socket_factory->createTransportSocket(nullptr, nullptr), nullptr, nullptr); + + Network::ConnectionPtr server_connection; + Network::MockConnectionCallbacks server_connection_callbacks; + NiceMock stream_info; + + EXPECT_CALL(listener_callbacks, onAccept_(_)) + .WillOnce(Invoke([&](Network::ConnectionSocketPtr& accepted_socket) -> void { + server_connection = dispatcher_->createServerConnection( + std::move(accepted_socket), + server_ssl_socket_factory->createDownstreamTransportSocket(), stream_info); + server_connection->addConnectionCallbacks(server_connection_callbacks); + })); + EXPECT_CALL(listener_callbacks, recordConnectionsAcceptedOnSocketEvent(_)); + + Network::MockConnectionCallbacks client_connection_callbacks; + client_connection->addConnectionCallbacks(client_connection_callbacks); + client_connection->connect(); + + size_t connect_count = 0; + auto on_connected = [&]() { + if (++connect_count == 2) { + server_connection->close(Network::ConnectionCloseType::AbortReset); + } + }; + + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)) + .WillOnce(InvokeWithoutArgs(on_connected)); + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)) + .WillOnce(InvokeWithoutArgs(on_connected)); + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) + .WillOnce(InvokeWithoutArgs([&]() -> void { + EXPECT_EQ(client_connection->detectedCloseType(), + StreamInfo::DetectedCloseType::RemoteReset); + dispatcher_->exit(); + })); + + dispatcher_->run(Event::Dispatcher::RunType::Block); +} +#endif + +// Verify that when the runtime feature is disabled, connection reset is NOT reported +// even when the peer aborts the connection with AbortReset. The test runs on all platforms; +// without `ENVOY_PLATFORM_ENABLE_SEND_RST` the AbortReset degrades to a graceful close, which +// also does not yield RemoteReset. +TEST_P(SslSocketTest, TlsConnectionResetDetectionDisabledByRuntime) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.ssl_socket_report_connection_reset", "false"}}); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_dns_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_dns_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/common/tls/test_data/ca_cert.pem" +)EOF"; + + const std::string client_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_uri_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/common/tls/test_data/san_uri_key.pem" +)EOF"; + + testing::NiceMock + transport_socket_factory_context; + ON_CALL(transport_socket_factory_context.server_context_, api()).WillByDefault(ReturnRef(*api_)); + + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext server_tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(server_ctx_yaml), server_tls_context); + auto server_cfg = + THROW_OR_RETURN_VALUE(ServerContextConfigImpl::create( + server_tls_context, transport_socket_factory_context, {}, false), + std::unique_ptr); + NiceMock server_factory_context; + ContextManagerImpl manager(server_factory_context); + Stats::TestUtil::TestStore server_stats_store; + auto server_ssl_socket_factory = + THROW_OR_RETURN_VALUE(ServerSslSocketFactory::create(std::move(server_cfg), manager, + *server_stats_store.rootScope()), + std::unique_ptr); + + auto socket = std::make_shared( + Network::Test::getCanonicalLoopbackAddress(version_)); + const auto local_address = socket->connectionInfoProvider().localAddress(); + Network::MockTcpListenerCallbacks listener_callbacks; + NiceMock listener_config; + Server::ThreadLocalOverloadStateOptRef overload_state; + Network::ListenerPtr listener = createListener(std::move(socket), listener_callbacks, runtime_, + listener_config, overload_state, *dispatcher_); + + testing::NiceMock + client_factory_context; + ON_CALL(client_factory_context.server_context_, api()).WillByDefault(ReturnRef(*api_)); + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext client_tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(client_ctx_yaml), client_tls_context); + auto client_cfg = *ClientContextConfigImpl::create(client_tls_context, client_factory_context); + Stats::TestUtil::TestStore client_stats_store; + auto client_ssl_socket_factory = *ClientSslSocketFactory::create(std::move(client_cfg), manager, + *client_stats_store.rootScope()); + + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( + local_address, Network::Address::InstanceConstSharedPtr(), + client_ssl_socket_factory->createTransportSocket(nullptr, nullptr), nullptr, nullptr); + + Network::ConnectionPtr server_connection; + Network::MockConnectionCallbacks server_connection_callbacks; + NiceMock stream_info; + + EXPECT_CALL(listener_callbacks, onAccept_(_)) + .WillOnce(Invoke([&](Network::ConnectionSocketPtr& accepted_socket) -> void { + server_connection = dispatcher_->createServerConnection( + std::move(accepted_socket), + server_ssl_socket_factory->createDownstreamTransportSocket(), stream_info); + server_connection->addConnectionCallbacks(server_connection_callbacks); + })); + EXPECT_CALL(listener_callbacks, recordConnectionsAcceptedOnSocketEvent(_)); + + Network::MockConnectionCallbacks client_connection_callbacks; + client_connection->addConnectionCallbacks(client_connection_callbacks); + client_connection->connect(); + + size_t connect_count = 0; + auto on_connected = [&]() { + if (++connect_count == 2) { + server_connection->close(Network::ConnectionCloseType::AbortReset); + } + }; + + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)) + .WillOnce(InvokeWithoutArgs(on_connected)); + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)) + .WillOnce(InvokeWithoutArgs(on_connected)); + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) + .WillOnce(InvokeWithoutArgs([&]() -> void { + // With the runtime feature disabled, reset detection should not be reported. + EXPECT_NE(client_connection->detectedCloseType(), + StreamInfo::DetectedCloseType::RemoteReset); + dispatcher_->exit(); + })); + + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/test/extensions/transport_sockets/alts/tsi_socket_test.cc b/test/extensions/transport_sockets/alts/tsi_socket_test.cc index a0ca23e81e719..10677556f143f 100644 --- a/test/extensions/transport_sockets/alts/tsi_socket_test.cc +++ b/test/extensions/transport_sockets/alts/tsi_socket_test.cc @@ -176,10 +176,10 @@ class TsiSocketTest : public testing::TestWithParam void TearDown() override { if (client_.tsi_socket_ != nullptr) { - client_.tsi_socket_->closeSocket(Network::ConnectionEvent::LocalClose); + client_.tsi_socket_->closeSocket(Network::ConnectionEvent::LocalClose, false); } if (server_.tsi_socket_ != nullptr) { - server_.tsi_socket_->closeSocket(Network::ConnectionEvent::RemoteClose); + server_.tsi_socket_->closeSocket(Network::ConnectionEvent::RemoteClose, false); } if (handshaker_server_thread_) { handshaker_server_->Shutdown(std::chrono::system_clock::now()); // NO_CHECK_FORMAT(real_time) diff --git a/test/extensions/transport_sockets/common/passthrough_test.cc b/test/extensions/transport_sockets/common/passthrough_test.cc index 0244ed0de8973..9f5bfb9b75f71 100644 --- a/test/extensions/transport_sockets/common/passthrough_test.cc +++ b/test/extensions/transport_sockets/common/passthrough_test.cc @@ -64,8 +64,9 @@ TEST_F(PassthroughTest, CanFlushCloseDefersToInnerSocket) { // Test closeSocket method defers to inner socket TEST_F(PassthroughTest, CloseSocketDefersToInnerSocket) { - EXPECT_CALL(*inner_socket_, closeSocket(testing::Eq(Network::ConnectionEvent::LocalClose))); - passthrough_socket_->closeSocket(Network::ConnectionEvent::LocalClose); + EXPECT_CALL(*inner_socket_, + closeSocket(testing::Eq(Network::ConnectionEvent::LocalClose), false)); + passthrough_socket_->closeSocket(Network::ConnectionEvent::LocalClose, false); } // Test doRead method defers to inner socket diff --git a/test/extensions/transport_sockets/starttls/starttls_socket_test.cc b/test/extensions/transport_sockets/starttls/starttls_socket_test.cc index efe9a7656245a..50ccf68a2d29e 100644 --- a/test/extensions/transport_sockets/starttls/starttls_socket_test.cc +++ b/test/extensions/transport_sockets/starttls/starttls_socket_test.cc @@ -58,9 +58,9 @@ TEST(StartTlsTest, BasicSwitch) { EXPECT_CALL(*ssl_socket, ssl()).Times(0); socket->ssl(); - EXPECT_CALL(*raw_socket, closeSocket(Network::ConnectionEvent::RemoteClose)); - EXPECT_CALL(*ssl_socket, closeSocket(Network::ConnectionEvent::RemoteClose)).Times(0); - socket->closeSocket(Network::ConnectionEvent::RemoteClose); + EXPECT_CALL(*raw_socket, closeSocket(Network::ConnectionEvent::RemoteClose, false)); + EXPECT_CALL(*ssl_socket, closeSocket(Network::ConnectionEvent::RemoteClose, false)).Times(0); + socket->closeSocket(Network::ConnectionEvent::RemoteClose, false); EXPECT_CALL(*raw_socket, doRead(_)); EXPECT_CALL(*ssl_socket, doRead(_)).Times(0); @@ -101,8 +101,8 @@ TEST(StartTlsTest, BasicSwitch) { EXPECT_CALL(*ssl_socket, ssl()); socket->ssl(); - EXPECT_CALL(*ssl_socket, closeSocket(Network::ConnectionEvent::RemoteClose)); - socket->closeSocket(Network::ConnectionEvent::RemoteClose); + EXPECT_CALL(*ssl_socket, closeSocket(Network::ConnectionEvent::RemoteClose, false)); + socket->closeSocket(Network::ConnectionEvent::RemoteClose, false); EXPECT_CALL(*ssl_socket, doRead(_)); socket->doRead(buf); diff --git a/test/extensions/transport_sockets/tcp_stats/tcp_stats_test.cc b/test/extensions/transport_sockets/tcp_stats/tcp_stats_test.cc index d7586c795076b..6ddfbefe348a2 100644 --- a/test/extensions/transport_sockets/tcp_stats/tcp_stats_test.cc +++ b/test/extensions/transport_sockets/tcp_stats/tcp_stats_test.cc @@ -106,7 +106,7 @@ TEST_F(TcpStatsTest, Periodic) { EXPECT_EQ(42, gaugeValue("cx_tx_unsent_bytes")); EXPECT_CALL(*timer_, disableTimer()); - tcp_stats_socket_->closeSocket(Network::ConnectionEvent::RemoteClose); + tcp_stats_socket_->closeSocket(Network::ConnectionEvent::RemoteClose, false); } // Validate that stats are updated when the connection is closed. Gauges should be set to zero, @@ -117,8 +117,8 @@ TEST_F(TcpStatsTest, CloseSocket) { tcp_info_.tcpi_segs_out = 42; tcp_info_.tcpi_notsent_bytes = 1; tcp_info_.tcpi_unacked = 2; - EXPECT_CALL(*inner_socket_, closeSocket(Network::ConnectionEvent::RemoteClose)); - tcp_stats_socket_->closeSocket(Network::ConnectionEvent::RemoteClose); + EXPECT_CALL(*inner_socket_, closeSocket(Network::ConnectionEvent::RemoteClose, false)); + tcp_stats_socket_->closeSocket(Network::ConnectionEvent::RemoteClose, false); EXPECT_EQ(42, counterValue("cx_tx_segments")); EXPECT_EQ(0, gaugeValue("cx_tx_unsent_bytes")); EXPECT_EQ(0, gaugeValue("cx_tx_unacked_segments")); diff --git a/test/integration/upstream_access_log_integration_test.cc b/test/integration/upstream_access_log_integration_test.cc index 02d420217d921..e89ba6a0d2e0c 100644 --- a/test/integration/upstream_access_log_integration_test.cc +++ b/test/integration/upstream_access_log_integration_test.cc @@ -50,8 +50,8 @@ class Socket : public Extensions::TransportSockets::PassthroughSocket { transport_socket_->onConnected(); } - void closeSocket(Network::ConnectionEvent event) override { - transport_socket_->closeSocket(event); + void closeSocket(Network::ConnectionEvent event, bool abort_reset) override { + transport_socket_->closeSocket(event, abort_reset); } Network::TransportSocketCallbacks* callbacks_{}; diff --git a/test/mocks/network/transport_socket.h b/test/mocks/network/transport_socket.h index 23de4ab97cdc2..9fc0e9d6c374d 100644 --- a/test/mocks/network/transport_socket.h +++ b/test/mocks/network/transport_socket.h @@ -28,7 +28,7 @@ class MockTransportSocket : public TransportSocket { MOCK_METHOD(absl::string_view, failureReason, (), (const)); MOCK_METHOD(bool, canFlushClose, ()); MOCK_METHOD(Api::SysCallIntResult, connect, (Network::ConnectionSocket & socket)); - MOCK_METHOD(void, closeSocket, (Network::ConnectionEvent event)); + MOCK_METHOD(void, closeSocket, (Network::ConnectionEvent event, bool abort_reset)); MOCK_METHOD(IoResult, doRead, (Buffer::Instance & buffer)); MOCK_METHOD(IoResult, doWrite, (Buffer::Instance & buffer, bool end_stream)); MOCK_METHOD(void, onConnected, ()); From b64a2bcbd428a4d5b6436c2d43e93a9ceba02cdc Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 May 2026 17:03:21 +0100 Subject: [PATCH 263/750] build/deps: Fix build image updater to work with semvers (#44947) Signed-off-by: Ryan Northey --- .github/workflows/envoy-dependency.yml | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index a9aea749b90fe..41f5259d91acd 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -146,12 +146,6 @@ jobs: path: envoy fetch-depth: 0 token: ${{ steps.appauth.outputs.token }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Checkout Envoy build tools repository - with: - repository: envoyproxy/envoy-build-tools - path: build-tools - fetch-depth: 0 - run: | shas=( sha-ci @@ -171,16 +165,29 @@ jobs: working-directory: envoy - run: | if [[ -z "$CONTAINER_TAG" ]]; then - # get current build image version - CONTAINER_TAG=$(git log -1 --pretty=format:"%H" "./docker") + # Source of truth: the latest `docker-v*` GitHub release in + # envoyproxy/toolshed. Strip only the `docker-` prefix - the + # published images keep the leading `v`, eg + # `envoyproxy/envoy-build:ci-v0.1.3`. + CONTAINER_TAG=$( + gh api -H "Accept: application/vnd.github+json" \ + --paginate \ + /repos/envoyproxy/toolshed/releases \ + --jq 'map(select(.tag_name | startswith("docker-v"))) | .[0].tag_name // empty' \ + | head -n1 \ + | sed 's/^docker-//') + fi + if [[ -z "$CONTAINER_TAG" ]]; then + echo "ERROR: Could not determine build image tag from envoyproxy/toolshed releases" >&2 + exit 1 fi echo "tag=${CONTAINER_TAG}" >> "$GITHUB_OUTPUT" - echo "tag_short=${CONTAINER_TAG::7}" >> "$GITHUB_OUTPUT" + echo "tag_short=${CONTAINER_TAG}" >> "$GITHUB_OUTPUT" env: CONTAINER_TAG: ${{ inputs.version }} + GH_TOKEN: ${{ steps.appauth.outputs.token }} id: build-tools name: Build image SHA - working-directory: build-tools - name: Check Docker SHAs id: build-images From 73ba3fc2b32745b0b24fea81c6d5688fccbdcc0c Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 May 2026 17:07:11 +0100 Subject: [PATCH 264/750] deps/go: Bump golang.org/x/net -> v0.53.0 (#44948) remediate GO-2026-4918 Signed-off-by: Ryan Northey --- go.mod | 8 ++++---- go.sum | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index ef30f56402dc9..55cd7556d85e6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/envoyproxy/envoy -go 1.24.6 +go 1.25.0 require ( github.com/envoyproxy/go-control-plane/envoy v1.37.0 @@ -12,9 +12,9 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/prometheus/client_model v0.6.2 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.79.3 // indirect diff --git a/go.sum b/go.sum index cb9130f2ad13d..91bc2f759b517 100644 --- a/go.sum +++ b/go.sum @@ -32,12 +32,12 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= From 4d8ac9cd70c4b98b6dc293d5ef2f8a1364b93984 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Fri, 8 May 2026 12:54:46 -0400 Subject: [PATCH 265/750] ci: fix verify_distro Docker Hub rate limit by adding read-only login (#44602) Docker Hub imposes rate limits on unauthenticated pulls, which causes the verify_distro job (and potentially others) to fail when pulling base images like debian:bullseye-slim and ubuntu:20.04. Changes: - Add a conditional `docker login` step in _run.yml using a read-only DOCKERHUB_TOKEN, so all CI jobs pull with authentication. - Thread the `dockerhub-token` secret through every workflow that calls _run.yml (directly or via intermediate reusable workflows). - Rename the existing write-capable DOCKERHUB_PASSWORD to DOCKERHUB_WRITE_TOKEN to clearly distinguish it from the read-only token. The write token is only used for publishing container images. Required GitHub repo secret changes: - Rename DOCKERHUB_PASSWORD -> DOCKERHUB_WRITE_TOKEN - Create DOCKERHUB_TOKEN with a read-only Docker Hub access token Signed-off-by: Jonh Wendell --- .github/workflows/_check_build.yml | 2 ++ .github/workflows/_check_coverage.yml | 3 +++ .github/workflows/_check_runtime.yml | 4 ++++ .github/workflows/_check_san.yml | 4 ++++ .github/workflows/_mobile_container_ci.yml | 10 ++++++---- .github/workflows/_precheck_deps.yml | 4 ++++ .github/workflows/_precheck_external.yml | 4 ++++ .github/workflows/_precheck_format.yml | 4 ++++ .github/workflows/_precheck_publish.yml | 3 +++ .github/workflows/_publish_build.yml | 12 +++++++++--- .github/workflows/_publish_release.yml | 13 ++++++++----- .github/workflows/_publish_release_container.yml | 4 ++-- .github/workflows/_publish_verify.yml | 8 ++++++++ .github/workflows/_run.yml | 15 +++++++++++++++ .github/workflows/envoy-checks.yml | 6 ++++++ .github/workflows/envoy-deflake.yml | 2 ++ .github/workflows/envoy-macos.yml | 2 ++ .github/workflows/envoy-prechecks.yml | 7 +++++++ .github/workflows/envoy-publish.yml | 14 +++++++++----- .github/workflows/mobile-android_build.yml | 6 ++++++ .github/workflows/mobile-android_tests.yml | 2 ++ .github/workflows/mobile-asan.yml | 2 ++ .github/workflows/mobile-cc_tests.yml | 4 ++++ .github/workflows/mobile-coverage.yml | 2 ++ .github/workflows/mobile-docs.yml | 1 + .github/workflows/mobile-format.yml | 2 ++ .github/workflows/mobile-ios_build.yml | 2 ++ .github/workflows/mobile-ios_tests.yml | 2 ++ .github/workflows/mobile-perf.yml | 4 ++++ .github/workflows/mobile-python.yml | 2 ++ .github/workflows/mobile-release.yml | 4 ++++ .github/workflows/mobile-tsan.yml | 2 ++ 32 files changed, 137 insertions(+), 19 deletions(-) diff --git a/.github/workflows/_check_build.yml b/.github/workflows/_check_build.yml index dc22bc92d5496..4a4492cbe96bf 100644 --- a/.github/workflows/_check_build.yml +++ b/.github/workflows/_check_build.yml @@ -6,6 +6,7 @@ permissions: on: workflow_call: secrets: + dockerhub-token: slack-bot-token: inputs: request: @@ -23,6 +24,7 @@ concurrency: jobs: build: secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} slack-bot-token: ${{ secrets.slack-bot-token }} permissions: actions: read diff --git a/.github/workflows/_check_coverage.yml b/.github/workflows/_check_coverage.yml index c722c73df1c6c..3ee0ee1888fa6 100644 --- a/.github/workflows/_check_coverage.yml +++ b/.github/workflows/_check_coverage.yml @@ -6,6 +6,7 @@ permissions: on: workflow_call: secrets: + dockerhub-token: gcp-key: required: true @@ -24,6 +25,8 @@ concurrency: jobs: coverage: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read diff --git a/.github/workflows/_check_runtime.yml b/.github/workflows/_check_runtime.yml index 9048b7bb03088..99a70c4db920d 100644 --- a/.github/workflows/_check_runtime.yml +++ b/.github/workflows/_check_runtime.yml @@ -5,6 +5,8 @@ permissions: on: workflow_call: + secrets: + dockerhub-token: inputs: request: type: string @@ -20,6 +22,8 @@ concurrency: jobs: runtime: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read diff --git a/.github/workflows/_check_san.yml b/.github/workflows/_check_san.yml index a306167844317..132e31a6a745c 100644 --- a/.github/workflows/_check_san.yml +++ b/.github/workflows/_check_san.yml @@ -5,6 +5,8 @@ permissions: on: workflow_call: + secrets: + dockerhub-token: inputs: request: type: string @@ -20,6 +22,8 @@ concurrency: jobs: san: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read diff --git a/.github/workflows/_mobile_container_ci.yml b/.github/workflows/_mobile_container_ci.yml index bd7d1eb7024c5..49ac85083de0f 100644 --- a/.github/workflows/_mobile_container_ci.yml +++ b/.github/workflows/_mobile_container_ci.yml @@ -8,6 +8,7 @@ on: secrets: app-id: app-key: + dockerhub-token: rbe-key: ssh-key-extra: inputs: @@ -132,14 +133,15 @@ on: jobs: ci: - uses: ./.github/workflows/_run.yml - name: ${{ inputs.target }} + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} + ssh-key-extra: ${{ secrets.ssh-key-extra }} permissions: actions: read contents: read packages: read - secrets: - ssh-key-extra: ${{ secrets.ssh-key-extra }} + uses: ./.github/workflows/_run.yml + name: ${{ inputs.target }} with: args: ${{ inputs.args }} rbe: ${{ inputs.rbe }} diff --git a/.github/workflows/_precheck_deps.yml b/.github/workflows/_precheck_deps.yml index cdd1d274c5d4c..9a528acc0cfab 100644 --- a/.github/workflows/_precheck_deps.yml +++ b/.github/workflows/_precheck_deps.yml @@ -5,6 +5,8 @@ permissions: on: workflow_call: + secrets: + dockerhub-token: inputs: dependency-review: type: boolean @@ -23,6 +25,8 @@ concurrency: jobs: deps: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read diff --git a/.github/workflows/_precheck_external.yml b/.github/workflows/_precheck_external.yml index 00144f9dd26f8..915157206b3af 100644 --- a/.github/workflows/_precheck_external.yml +++ b/.github/workflows/_precheck_external.yml @@ -5,6 +5,8 @@ permissions: on: workflow_call: + secrets: + dockerhub-token: inputs: request: type: string @@ -20,6 +22,8 @@ concurrency: jobs: external: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read diff --git a/.github/workflows/_precheck_format.yml b/.github/workflows/_precheck_format.yml index ae63eb19277fc..5a9ec6a4fa2f9 100644 --- a/.github/workflows/_precheck_format.yml +++ b/.github/workflows/_precheck_format.yml @@ -5,6 +5,8 @@ permissions: on: workflow_call: + secrets: + dockerhub-token: inputs: request: type: string @@ -21,6 +23,8 @@ concurrency: jobs: format: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read diff --git a/.github/workflows/_precheck_publish.yml b/.github/workflows/_precheck_publish.yml index 76429979f0f8b..e891b56aa0d1d 100644 --- a/.github/workflows/_precheck_publish.yml +++ b/.github/workflows/_precheck_publish.yml @@ -6,6 +6,7 @@ permissions: on: workflow_call: secrets: + dockerhub-token: gcp-key: required: true inputs: @@ -23,6 +24,8 @@ concurrency: jobs: publish: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read diff --git a/.github/workflows/_publish_build.yml b/.github/workflows/_publish_build.yml index 9c21b91cd31fb..8976aa87d6583 100644 --- a/.github/workflows/_publish_build.yml +++ b/.github/workflows/_publish_build.yml @@ -6,6 +6,7 @@ permissions: on: workflow_call: secrets: + dockerhub-token: gpg-key: required: true gpg-key-password: @@ -33,6 +34,8 @@ concurrency: jobs: binary: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read @@ -58,6 +61,8 @@ jobs: upload-path: container/envoy/${{ inputs.arch }}/bin/ docker: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read @@ -87,13 +92,14 @@ jobs: runs-on: ${{ inputs.arch == 'arm64' && (vars.ENVOY_ARM_VM || 'ubuntu-24.04-arm') || null }} distribution: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} + gpg-key: ${{ secrets.gpg-key }} + gpg-key-password: ${{ secrets.gpg-key-password }} permissions: actions: read contents: read packages: read - secrets: - gpg-key: ${{ secrets.gpg-key }} - gpg-key-password: ${{ secrets.gpg-key-password }} name: Packages needs: - binary diff --git a/.github/workflows/_publish_release.yml b/.github/workflows/_publish_release.yml index b47c726bf426a..b49edc72ca7c4 100644 --- a/.github/workflows/_publish_release.yml +++ b/.github/workflows/_publish_release.yml @@ -6,7 +6,8 @@ permissions: on: workflow_call: secrets: - dockerhub-password: + dockerhub-token: + dockerhub-write-token: ENVOY_CI_SYNC_APP_ID: ENVOY_CI_SYNC_APP_KEY: ENVOY_CI_PUBLISH_APP_ID: @@ -37,13 +38,14 @@ concurrency: jobs: sign: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} + gpg-key: ${{ secrets.gpg-key }} + gpg-key-password: ${{ secrets.gpg-key-password }} permissions: actions: read contents: read packages: read - secrets: - gpg-key: ${{ secrets.gpg-key }} - gpg-key-password: ${{ secrets.gpg-key-password }} if: ${{ vars.ENVOY_CI_RELEASE || github.repository == 'envoyproxy/envoy' }} name: Sign packages uses: ./.github/workflows/_run.yml @@ -77,7 +79,7 @@ jobs: container: secrets: - dockerhub-password: ${{ secrets.dockerhub-password }} + dockerhub-write-token: ${{ secrets.dockerhub-write-token }} permissions: actions: read contents: read @@ -99,6 +101,7 @@ jobs: secrets: app-id: ${{ inputs.trusted && secrets.ENVOY_CI_PUBLISH_APP_ID || '' }} app-key: ${{ inputs.trusted && secrets.ENVOY_CI_PUBLISH_APP_KEY || '' }} + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read diff --git a/.github/workflows/_publish_release_container.yml b/.github/workflows/_publish_release_container.yml index 1e050b276dab0..1374d55b42e97 100644 --- a/.github/workflows/_publish_release_container.yml +++ b/.github/workflows/_publish_release_container.yml @@ -6,7 +6,7 @@ permissions: on: workflow_call: secrets: - dockerhub-password: + dockerhub-write-token: inputs: dev: required: true @@ -233,4 +233,4 @@ jobs: manifest-config: ${{ steps.dev-config.outputs.value || steps.release-config.outputs.value }} dry-run: ${{ ! inputs.trusted || (inputs.target-branch != 'main' && inputs.dev) }} dockerhub-username: ${{ inputs.trusted && inputs.dockerhub-username || '' }} - dockerhub-password: ${{ inputs.trusted && secrets.dockerhub-password || '' }} + dockerhub-password: ${{ inputs.trusted && secrets.dockerhub-write-token || '' }} diff --git a/.github/workflows/_publish_verify.yml b/.github/workflows/_publish_verify.yml index b45e7adc5f712..83a50ecdb3f70 100644 --- a/.github/workflows/_publish_verify.yml +++ b/.github/workflows/_publish_verify.yml @@ -5,6 +5,8 @@ permissions: on: workflow_call: + secrets: + dockerhub-token: inputs: request: type: string @@ -24,6 +26,8 @@ concurrency: jobs: examples: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read @@ -76,6 +80,8 @@ jobs: shell: bash distroless: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read @@ -134,6 +140,8 @@ jobs: shell: bash distro: + secrets: + dockerhub-token: ${{ secrets.dockerhub-token }} permissions: actions: read contents: read diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 0604009aaffd9..03e95c7d33270 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -8,6 +8,7 @@ on: secrets: app-id: app-key: + dockerhub-token: gpg-key: gpg-key-password: slack-bot-token: @@ -388,6 +389,20 @@ jobs: env: BAZELRC_CONTENT: ${{ vars.ENVOY_CI_BAZELRC }} + - id: dockerhub + run: | + if [[ -n "$DOCKERHUB_TOKEN" ]]; then + echo "login=true" >> "$GITHUB_OUTPUT" + fi + env: + DOCKERHUB_TOKEN: ${{ secrets.dockerhub-token }} + - name: Login to Docker Hub (read-only) + if: steps.dockerhub.outputs.login == 'true' + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + username: ${{ inputs.dockerhub-username }} + password: ${{ secrets.dockerhub-token }} + # NOTE: This is where untrusted code can be run!!! # Only post-failure notification steps (which don't use any repo code) should follow this step. - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 diff --git a/.github/workflows/envoy-checks.yml b/.github/workflows/envoy-checks.yml index 78277cccd1185..75ec8c3eaac05 100644 --- a/.github/workflows/envoy-checks.yml +++ b/.github/workflows/envoy-checks.yml @@ -47,6 +47,7 @@ jobs: build: secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }} permissions: actions: read @@ -64,6 +65,7 @@ jobs: coverage: secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} gcp-key: ${{ fromJSON(needs.load.outputs.trusted) && secrets.GCP_SERVICE_ACCOUNT_KEY_TRUSTED || secrets.GCP_SERVICE_ACCOUNT_KEY }} permissions: actions: read @@ -80,6 +82,8 @@ jobs: trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} runtime: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read @@ -95,6 +99,8 @@ jobs: trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} san: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/envoy-deflake.yml b/.github/workflows/envoy-deflake.yml index aef3a52d53a3b..9d547e6777e81 100644 --- a/.github/workflows/envoy-deflake.yml +++ b/.github/workflows/envoy-deflake.yml @@ -35,6 +35,8 @@ jobs: uses: ./.github/workflows/_load_env.yml deflake: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/envoy-macos.yml b/.github/workflows/envoy-macos.yml index 8fc8d4b9965d4..a460a4a66be92 100644 --- a/.github/workflows/envoy-macos.yml +++ b/.github/workflows/envoy-macos.yml @@ -39,6 +39,8 @@ jobs: check-name: macos macos: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/envoy-prechecks.yml b/.github/workflows/envoy-prechecks.yml index b3b0b19c0b5f1..9115f3d1bc6c9 100644 --- a/.github/workflows/envoy-prechecks.yml +++ b/.github/workflows/envoy-prechecks.yml @@ -42,6 +42,8 @@ jobs: check-name: prechecks format: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read @@ -57,6 +59,8 @@ jobs: trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} deps: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read @@ -74,6 +78,7 @@ jobs: publish: secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} gcp-key: >- ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) @@ -94,6 +99,8 @@ jobs: trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} external: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/envoy-publish.yml b/.github/workflows/envoy-publish.yml index d488589c95e85..441dab09abf27 100644 --- a/.github/workflows/envoy-publish.yml +++ b/.github/workflows/envoy-publish.yml @@ -49,11 +49,8 @@ jobs: # head-sha: ${{ github.sha }} build: - permissions: - actions: read - contents: read - packages: read secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} gpg-key: >- ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) @@ -64,6 +61,10 @@ jobs: && fromJSON(needs.load.outputs.trusted) && secrets.ENVOY_GPG_MAINTAINER_KEY_PASSWORD || secrets.ENVOY_GPG_SNAKEOIL_KEY_PASSWORD }} + permissions: + actions: read + contents: read + packages: read if: ${{ fromJSON(needs.load.outputs.request).run.release || fromJSON(needs.load.outputs.request).run.verify }} needs: - load @@ -82,7 +83,8 @@ jobs: release: secrets: - dockerhub-password: ${{ secrets.DOCKERHUB_PASSWORD }} + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} + dockerhub-write-token: ${{ secrets.DOCKERHUB_WRITE_TOKEN }} ENVOY_CI_SYNC_APP_ID: >- ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) @@ -129,6 +131,8 @@ jobs: trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} verify: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index ffb3887aa9d43..b86db3c471b7e 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -39,6 +39,8 @@ jobs: check-name: mobile-android build: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read @@ -63,6 +65,8 @@ jobs: target: build kotlin-hello-world: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read @@ -115,6 +119,8 @@ jobs: working-directory: mobile apps: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-android_tests.yml b/.github/workflows/mobile-android_tests.yml index 67b48f624a808..efef5a0953fef 100644 --- a/.github/workflows/mobile-android_tests.yml +++ b/.github/workflows/mobile-android_tests.yml @@ -39,6 +39,8 @@ jobs: check-name: mobile-android-tests linux: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-asan.yml b/.github/workflows/mobile-asan.yml index 81dffcf3d8f1b..d846b415f1dc0 100644 --- a/.github/workflows/mobile-asan.yml +++ b/.github/workflows/mobile-asan.yml @@ -39,6 +39,8 @@ jobs: check-name: mobile-asan asan: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-cc_tests.yml b/.github/workflows/mobile-cc_tests.yml index 9f92b15051735..9bdc0a7cb0972 100644 --- a/.github/workflows/mobile-cc_tests.yml +++ b/.github/workflows/mobile-cc_tests.yml @@ -39,6 +39,8 @@ jobs: check-name: mobile-cc cc-tests: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read @@ -59,6 +61,8 @@ jobs: target: cc-tests cc-xds-integration-tests: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-coverage.yml b/.github/workflows/mobile-coverage.yml index 5a20cda9ec4f0..4c46407b6a09d 100644 --- a/.github/workflows/mobile-coverage.yml +++ b/.github/workflows/mobile-coverage.yml @@ -39,6 +39,8 @@ jobs: check-name: mobile-coverage coverage: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-docs.yml b/.github/workflows/mobile-docs.yml index f044859efa54a..0ca823198e3dd 100644 --- a/.github/workflows/mobile-docs.yml +++ b/.github/workflows/mobile-docs.yml @@ -40,6 +40,7 @@ jobs: docs: secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} ssh-key-extra: ${{ needs.load.outputs.trusted && secrets.ENVOY_MOBILE_WEBSITE_DEPLOY_KEY || '' }} permissions: actions: read diff --git a/.github/workflows/mobile-format.yml b/.github/workflows/mobile-format.yml index e259bf1b3c744..fe88f65f8aee7 100644 --- a/.github/workflows/mobile-format.yml +++ b/.github/workflows/mobile-format.yml @@ -39,6 +39,8 @@ jobs: check-name: mobile-format container: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index ca964cf26616c..15a64d00a0215 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -39,6 +39,8 @@ jobs: check-name: mobile-ios build: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-ios_tests.yml b/.github/workflows/mobile-ios_tests.yml index 3556c94686f78..0c4ef8b6ebe48 100644 --- a/.github/workflows/mobile-ios_tests.yml +++ b/.github/workflows/mobile-ios_tests.yml @@ -39,6 +39,8 @@ jobs: check-name: mobile-ios-tests tests: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-perf.yml b/.github/workflows/mobile-perf.yml index ba63d77844e3a..28edb20beb7f4 100644 --- a/.github/workflows/mobile-perf.yml +++ b/.github/workflows/mobile-perf.yml @@ -39,6 +39,8 @@ jobs: check-name: mobile-perf build: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read @@ -85,6 +87,8 @@ jobs: target: size-main compare: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-python.yml b/.github/workflows/mobile-python.yml index 034bf28434949..adb8945eece2b 100644 --- a/.github/workflows/mobile-python.yml +++ b/.github/workflows/mobile-python.yml @@ -39,6 +39,8 @@ jobs: check-name: mobile-python python-tests: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index e1018192a7e8f..19021db0b559b 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -29,6 +29,8 @@ jobs: uses: ./.github/workflows/_load_env.yml android-release: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read @@ -161,6 +163,8 @@ jobs: ${{ matrix.output }}-javadoc.jar.asc python-release: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read diff --git a/.github/workflows/mobile-tsan.yml b/.github/workflows/mobile-tsan.yml index 04aed68674ef1..0e8cf77253804 100644 --- a/.github/workflows/mobile-tsan.yml +++ b/.github/workflows/mobile-tsan.yml @@ -40,6 +40,8 @@ jobs: run-id: ${{ github.event.workflow_run.id }} tsan: + secrets: + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} permissions: actions: read contents: read From 11ef3f552e283a0610144b5f50da02793bcdbc97 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Fri, 8 May 2026 13:05:39 -0400 Subject: [PATCH 266/750] ci: fix Docker Hub login missing username (#44953) The dockerhub-username input was removed from _run.yml in #44927, but the Docker Hub read-only login step still referenced it, causing all CI jobs to fail with "Username required". Signed-off-by: Jonh Wendell --- .github/workflows/_run.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 03e95c7d33270..567e8ca5c6dcd 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -400,7 +400,7 @@ jobs: if: steps.dockerhub.outputs.login == 'true' uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: - username: ${{ inputs.dockerhub-username }} + username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.dockerhub-token }} # NOTE: This is where untrusted code can be run!!! From 6c5ca01c218ededcd0c400cc9f493f215d97e091 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Fri, 8 May 2026 13:27:06 -0400 Subject: [PATCH 267/750] ci: skip Docker Hub login on macOS (#44954) The docker/login-action requires Docker, which is not available on macOS runners. Gate both the token check and login steps on `container-command != ""'`. --------- Signed-off-by: Jonh Wendell --- .github/workflows/_run.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 567e8ca5c6dcd..56ebeb49f6bc8 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -390,6 +390,7 @@ jobs: BAZELRC_CONTENT: ${{ vars.ENVOY_CI_BAZELRC }} - id: dockerhub + if: inputs.container-command != '' run: | if [[ -n "$DOCKERHUB_TOKEN" ]]; then echo "login=true" >> "$GITHUB_OUTPUT" @@ -397,7 +398,7 @@ jobs: env: DOCKERHUB_TOKEN: ${{ secrets.dockerhub-token }} - name: Login to Docker Hub (read-only) - if: steps.dockerhub.outputs.login == 'true' + if: inputs.container-command != '' && steps.dockerhub.outputs.login == 'true' uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ vars.DOCKERHUB_USERNAME }} From ee0bbe201be6d3c5ccd43b14a758f2e7538922ea Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 May 2026 18:34:20 +0100 Subject: [PATCH 268/750] ci: Remove legacy docker testing (#44952) Signed-off-by: Ryan Northey --- ci/test/docker/linux/dev/main | 85 --------------- ci/test/docker/linux/dev/other | 32 ------ ci/test/docker/linux/dev/release | 32 ------ ci/test/docker/linux/dev/tag | 32 ------ ci/test/docker/linux/nondev/main | 81 -------------- ci/test/docker/linux/nondev/other | 32 ------ ci/test/docker/linux/nondev/release | 81 -------------- ci/test/docker/linux/nondev/tag | 32 ------ ci/test_docker_ci.sh | 161 ---------------------------- 9 files changed, 568 deletions(-) delete mode 100644 ci/test/docker/linux/dev/main delete mode 100644 ci/test/docker/linux/dev/other delete mode 100644 ci/test/docker/linux/dev/release delete mode 100644 ci/test/docker/linux/dev/tag delete mode 100644 ci/test/docker/linux/nondev/main delete mode 100644 ci/test/docker/linux/nondev/other delete mode 100644 ci/test/docker/linux/nondev/release delete mode 100644 ci/test/docker/linux/nondev/tag delete mode 100755 ci/test_docker_ci.sh diff --git a/ci/test/docker/linux/dev/main b/ci/test/docker/linux/dev/main deleted file mode 100644 index 011ce8eca4989..0000000000000 --- a/ci/test/docker/linux/dev/main +++ /dev/null @@ -1,85 +0,0 @@ ->> BUILDX: install -> docker run --rm --privileged tonistiigi/binfmt --install all -> docker buildx rm multi-builder 2> /dev/null || : -> docker buildx create --use --name multi-builder --platform linux/arm64,linux/amd64 ->> LOGIN ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy.tar -t mocktest/repo-dev:MOCKSHA . ->> PUSH: oci-archive:/non/existent/test/path/envoy.tar -> docker://docker.io/mocktest/repo-dev:MOCKSHA -> skopeo copy --all oci-archive:/non/existent/test/path/envoy.tar docker://docker.io/mocktest/repo-dev:MOCKSHA ->> TAG: mocktest/repo-dev:MOCKSHA -> mocktest/repo:dev-MOCKSHA -> docker buildx imagetools create docker.io/mocktest/repo-dev:MOCKSHA --tag docker.io/mocktest/repo:dev-MOCKSHA ->> TAG: mocktest/repo-dev:MOCKSHA -> mocktest/repo-dev:latest -> docker buildx imagetools create docker.io/mocktest/repo-dev:MOCKSHA --tag docker.io/mocktest/repo-dev:latest ->> TAG: mocktest/repo-dev:MOCKSHA -> mocktest/repo:dev -> docker buildx imagetools create docker.io/mocktest/repo-dev:MOCKSHA --tag docker.io/mocktest/repo:dev ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD+PUSH: mocktest/repo-debug-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false --push -t mocktest/repo-debug-dev:MOCKSHA . ->> TAG: mocktest/repo-debug-dev:MOCKSHA -> mocktest/repo:debug-dev-MOCKSHA -> docker buildx imagetools create docker.io/mocktest/repo-debug-dev:MOCKSHA --tag docker.io/mocktest/repo:debug-dev-MOCKSHA ->> TAG: mocktest/repo-debug-dev:MOCKSHA -> mocktest/repo-debug-dev:latest -> docker buildx imagetools create docker.io/mocktest/repo-debug-dev:MOCKSHA --tag docker.io/mocktest/repo-debug-dev:latest ->> TAG: mocktest/repo-debug-dev:MOCKSHA -> mocktest/repo:debug-dev -> docker buildx imagetools create docker.io/mocktest/repo-debug-dev:MOCKSHA --tag docker.io/mocktest/repo:debug-dev ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-contrib.tar -t mocktest/repo-contrib-dev:MOCKSHA . ->> PUSH: oci-archive:/non/existent/test/path/envoy-contrib.tar -> docker://docker.io/mocktest/repo-contrib-dev:MOCKSHA -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-contrib.tar docker://docker.io/mocktest/repo-contrib-dev:MOCKSHA ->> TAG: mocktest/repo-contrib-dev:MOCKSHA -> mocktest/repo:contrib-dev-MOCKSHA -> docker buildx imagetools create docker.io/mocktest/repo-contrib-dev:MOCKSHA --tag docker.io/mocktest/repo:contrib-dev-MOCKSHA ->> TAG: mocktest/repo-contrib-dev:MOCKSHA -> mocktest/repo-contrib-dev:latest -> docker buildx imagetools create docker.io/mocktest/repo-contrib-dev:MOCKSHA --tag docker.io/mocktest/repo-contrib-dev:latest ->> TAG: mocktest/repo-contrib-dev:MOCKSHA -> mocktest/repo:contrib-dev -> docker buildx imagetools create docker.io/mocktest/repo-contrib-dev:MOCKSHA --tag docker.io/mocktest/repo:contrib-dev ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD+PUSH: mocktest/repo-contrib-debug-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false --push -t mocktest/repo-contrib-debug-dev:MOCKSHA . ->> TAG: mocktest/repo-contrib-debug-dev:MOCKSHA -> mocktest/repo:contrib-debug-dev-MOCKSHA -> docker buildx imagetools create docker.io/mocktest/repo-contrib-debug-dev:MOCKSHA --tag docker.io/mocktest/repo:contrib-debug-dev-MOCKSHA ->> TAG: mocktest/repo-contrib-debug-dev:MOCKSHA -> mocktest/repo-contrib-debug-dev:latest -> docker buildx imagetools create docker.io/mocktest/repo-contrib-debug-dev:MOCKSHA --tag docker.io/mocktest/repo-contrib-debug-dev:latest ->> TAG: mocktest/repo-contrib-debug-dev:MOCKSHA -> mocktest/repo:contrib-debug-dev -> docker buildx imagetools create docker.io/mocktest/repo-contrib-debug-dev:MOCKSHA --tag docker.io/mocktest/repo:contrib-debug-dev ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-distroless-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-distroless --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-distroless.tar -t mocktest/repo-distroless-dev:MOCKSHA . ->> PUSH: oci-archive:/non/existent/test/path/envoy-distroless.tar -> docker://docker.io/mocktest/repo-distroless-dev:MOCKSHA -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-distroless.tar docker://docker.io/mocktest/repo-distroless-dev:MOCKSHA ->> TAG: mocktest/repo-distroless-dev:MOCKSHA -> mocktest/repo:distroless-dev-MOCKSHA -> docker buildx imagetools create docker.io/mocktest/repo-distroless-dev:MOCKSHA --tag docker.io/mocktest/repo:distroless-dev-MOCKSHA ->> TAG: mocktest/repo-distroless-dev:MOCKSHA -> mocktest/repo-distroless-dev:latest -> docker buildx imagetools create docker.io/mocktest/repo-distroless-dev:MOCKSHA --tag docker.io/mocktest/repo-distroless-dev:latest ->> TAG: mocktest/repo-distroless-dev:MOCKSHA -> mocktest/repo:distroless-dev -> docker buildx imagetools create docker.io/mocktest/repo-distroless-dev:MOCKSHA --tag docker.io/mocktest/repo:distroless-dev ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-google-vrp-dev:MOCKSHA -> docker buildx build --platform linux/amd64 -f ci/Dockerfile-envoy --target envoy-google-vrp --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-google-vrp.tar -t mocktest/repo-google-vrp-dev:MOCKSHA . ->> PUSH: oci-archive:/non/existent/test/path/envoy-google-vrp.tar -> docker://docker.io/mocktest/repo-google-vrp-dev:MOCKSHA -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-google-vrp.tar docker://docker.io/mocktest/repo-google-vrp-dev:MOCKSHA ->> TAG: mocktest/repo-google-vrp-dev:MOCKSHA -> mocktest/repo:google-vrp-dev-MOCKSHA -> docker buildx imagetools create docker.io/mocktest/repo-google-vrp-dev:MOCKSHA --tag docker.io/mocktest/repo:google-vrp-dev-MOCKSHA ->> TAG: mocktest/repo-google-vrp-dev:MOCKSHA -> mocktest/repo-google-vrp-dev:latest -> docker buildx imagetools create docker.io/mocktest/repo-google-vrp-dev:MOCKSHA --tag docker.io/mocktest/repo-google-vrp-dev:latest ->> TAG: mocktest/repo-google-vrp-dev:MOCKSHA -> mocktest/repo:google-vrp-dev -> docker buildx imagetools create docker.io/mocktest/repo-google-vrp-dev:MOCKSHA --tag docker.io/mocktest/repo:google-vrp-dev ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-tools-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-tools --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-tools.tar -t mocktest/repo-tools-dev:MOCKSHA . ->> PUSH: oci-archive:/non/existent/test/path/envoy-tools.tar -> docker://docker.io/mocktest/repo-tools-dev:MOCKSHA -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-tools.tar docker://docker.io/mocktest/repo-tools-dev:MOCKSHA ->> TAG: mocktest/repo-tools-dev:MOCKSHA -> mocktest/repo:tools-dev-MOCKSHA -> docker buildx imagetools create docker.io/mocktest/repo-tools-dev:MOCKSHA --tag docker.io/mocktest/repo:tools-dev-MOCKSHA ->> TAG: mocktest/repo-tools-dev:MOCKSHA -> mocktest/repo-tools-dev:latest -> docker buildx imagetools create docker.io/mocktest/repo-tools-dev:MOCKSHA --tag docker.io/mocktest/repo-tools-dev:latest ->> TAG: mocktest/repo-tools-dev:MOCKSHA -> mocktest/repo:tools-dev -> docker buildx imagetools create docker.io/mocktest/repo-tools-dev:MOCKSHA --tag docker.io/mocktest/repo:tools-dev diff --git a/ci/test/docker/linux/dev/other b/ci/test/docker/linux/dev/other deleted file mode 100644 index c5b2f33b032a2..0000000000000 --- a/ci/test/docker/linux/dev/other +++ /dev/null @@ -1,32 +0,0 @@ ->> BUILDX: install -> docker run --rm --privileged tonistiigi/binfmt --install all -> docker buildx rm multi-builder 2> /dev/null || : -> docker buildx create --use --name multi-builder --platform linux/arm64,linux/amd64 ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy.tar -t mocktest/repo-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-debug-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false -t mocktest/repo-debug-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-contrib.tar -t mocktest/repo-contrib-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib-debug-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false -t mocktest/repo-contrib-debug-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-distroless-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-distroless --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-distroless.tar -t mocktest/repo-distroless-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-google-vrp-dev:MOCKSHA -> docker buildx build --platform linux/amd64 -f ci/Dockerfile-envoy --target envoy-google-vrp --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-google-vrp.tar -t mocktest/repo-google-vrp-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-tools-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-tools --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-tools.tar -t mocktest/repo-tools-dev:MOCKSHA . diff --git a/ci/test/docker/linux/dev/release b/ci/test/docker/linux/dev/release deleted file mode 100644 index c5b2f33b032a2..0000000000000 --- a/ci/test/docker/linux/dev/release +++ /dev/null @@ -1,32 +0,0 @@ ->> BUILDX: install -> docker run --rm --privileged tonistiigi/binfmt --install all -> docker buildx rm multi-builder 2> /dev/null || : -> docker buildx create --use --name multi-builder --platform linux/arm64,linux/amd64 ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy.tar -t mocktest/repo-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-debug-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false -t mocktest/repo-debug-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-contrib.tar -t mocktest/repo-contrib-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib-debug-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false -t mocktest/repo-contrib-debug-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-distroless-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-distroless --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-distroless.tar -t mocktest/repo-distroless-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-google-vrp-dev:MOCKSHA -> docker buildx build --platform linux/amd64 -f ci/Dockerfile-envoy --target envoy-google-vrp --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-google-vrp.tar -t mocktest/repo-google-vrp-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-tools-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-tools --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-tools.tar -t mocktest/repo-tools-dev:MOCKSHA . diff --git a/ci/test/docker/linux/dev/tag b/ci/test/docker/linux/dev/tag deleted file mode 100644 index c5b2f33b032a2..0000000000000 --- a/ci/test/docker/linux/dev/tag +++ /dev/null @@ -1,32 +0,0 @@ ->> BUILDX: install -> docker run --rm --privileged tonistiigi/binfmt --install all -> docker buildx rm multi-builder 2> /dev/null || : -> docker buildx create --use --name multi-builder --platform linux/arm64,linux/amd64 ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy.tar -t mocktest/repo-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-debug-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false -t mocktest/repo-debug-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-contrib.tar -t mocktest/repo-contrib-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib-debug-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false -t mocktest/repo-contrib-debug-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-distroless-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-distroless --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-distroless.tar -t mocktest/repo-distroless-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-google-vrp-dev:MOCKSHA -> docker buildx build --platform linux/amd64 -f ci/Dockerfile-envoy --target envoy-google-vrp --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-google-vrp.tar -t mocktest/repo-google-vrp-dev:MOCKSHA . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-tools-dev:MOCKSHA -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-tools --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-tools.tar -t mocktest/repo-tools-dev:MOCKSHA . diff --git a/ci/test/docker/linux/nondev/main b/ci/test/docker/linux/nondev/main deleted file mode 100644 index 84e08062a9adf..0000000000000 --- a/ci/test/docker/linux/nondev/main +++ /dev/null @@ -1,81 +0,0 @@ ->> BUILDX: install -> docker run --rm --privileged tonistiigi/binfmt --install all -> docker buildx rm multi-builder 2> /dev/null || : -> docker buildx create --use --name multi-builder --platform linux/arm64,linux/amd64 ->> LOGIN ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo:v1.73.0 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy.tar -t mocktest/repo:v1.73.0 . ->> PUSH: oci-archive:/non/existent/test/path/envoy.tar -> docker://docker.io/mocktest/repo:v1.73.0 -> skopeo copy --all oci-archive:/non/existent/test/path/envoy.tar docker://docker.io/mocktest/repo:v1.73.0 ->> TAG: mocktest/repo:v1.73.0 -> mocktest/repo:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo:v1.73.0 --tag docker.io/mocktest/repo:v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD+PUSH: mocktest/repo-debug:v1.73.0 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false --push -t mocktest/repo-debug:v1.73.0 . ->> TAG: mocktest/repo-debug:v1.73.0 -> mocktest/repo:debug-v1.73.0 -> docker buildx imagetools create docker.io/mocktest/repo-debug:v1.73.0 --tag docker.io/mocktest/repo:debug-v1.73.0 ->> TAG: mocktest/repo-debug:v1.73.0 -> mocktest/repo-debug:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-debug:v1.73.0 --tag docker.io/mocktest/repo-debug:v1.73-latest ->> TAG: mocktest/repo-debug:v1.73.0 -> mocktest/repo:debug-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-debug:v1.73.0 --tag docker.io/mocktest/repo:debug-v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib:v1.73.0 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-contrib.tar -t mocktest/repo-contrib:v1.73.0 . ->> PUSH: oci-archive:/non/existent/test/path/envoy-contrib.tar -> docker://docker.io/mocktest/repo-contrib:v1.73.0 -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-contrib.tar docker://docker.io/mocktest/repo-contrib:v1.73.0 ->> TAG: mocktest/repo-contrib:v1.73.0 -> mocktest/repo:contrib-v1.73.0 -> docker buildx imagetools create docker.io/mocktest/repo-contrib:v1.73.0 --tag docker.io/mocktest/repo:contrib-v1.73.0 ->> TAG: mocktest/repo-contrib:v1.73.0 -> mocktest/repo-contrib:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-contrib:v1.73.0 --tag docker.io/mocktest/repo-contrib:v1.73-latest ->> TAG: mocktest/repo-contrib:v1.73.0 -> mocktest/repo:contrib-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-contrib:v1.73.0 --tag docker.io/mocktest/repo:contrib-v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD+PUSH: mocktest/repo-contrib-debug:v1.73.0 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false --push -t mocktest/repo-contrib-debug:v1.73.0 . ->> TAG: mocktest/repo-contrib-debug:v1.73.0 -> mocktest/repo:contrib-debug-v1.73.0 -> docker buildx imagetools create docker.io/mocktest/repo-contrib-debug:v1.73.0 --tag docker.io/mocktest/repo:contrib-debug-v1.73.0 ->> TAG: mocktest/repo-contrib-debug:v1.73.0 -> mocktest/repo-contrib-debug:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-contrib-debug:v1.73.0 --tag docker.io/mocktest/repo-contrib-debug:v1.73-latest ->> TAG: mocktest/repo-contrib-debug:v1.73.0 -> mocktest/repo:contrib-debug-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-contrib-debug:v1.73.0 --tag docker.io/mocktest/repo:contrib-debug-v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-distroless:v1.73.0 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-distroless --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-distroless.tar -t mocktest/repo-distroless:v1.73.0 . ->> PUSH: oci-archive:/non/existent/test/path/envoy-distroless.tar -> docker://docker.io/mocktest/repo-distroless:v1.73.0 -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-distroless.tar docker://docker.io/mocktest/repo-distroless:v1.73.0 ->> TAG: mocktest/repo-distroless:v1.73.0 -> mocktest/repo:distroless-v1.73.0 -> docker buildx imagetools create docker.io/mocktest/repo-distroless:v1.73.0 --tag docker.io/mocktest/repo:distroless-v1.73.0 ->> TAG: mocktest/repo-distroless:v1.73.0 -> mocktest/repo-distroless:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-distroless:v1.73.0 --tag docker.io/mocktest/repo-distroless:v1.73-latest ->> TAG: mocktest/repo-distroless:v1.73.0 -> mocktest/repo:distroless-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-distroless:v1.73.0 --tag docker.io/mocktest/repo:distroless-v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-google-vrp:v1.73.0 -> docker buildx build --platform linux/amd64 -f ci/Dockerfile-envoy --target envoy-google-vrp --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-google-vrp.tar -t mocktest/repo-google-vrp:v1.73.0 . ->> PUSH: oci-archive:/non/existent/test/path/envoy-google-vrp.tar -> docker://docker.io/mocktest/repo-google-vrp:v1.73.0 -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-google-vrp.tar docker://docker.io/mocktest/repo-google-vrp:v1.73.0 ->> TAG: mocktest/repo-google-vrp:v1.73.0 -> mocktest/repo:google-vrp-v1.73.0 -> docker buildx imagetools create docker.io/mocktest/repo-google-vrp:v1.73.0 --tag docker.io/mocktest/repo:google-vrp-v1.73.0 ->> TAG: mocktest/repo-google-vrp:v1.73.0 -> mocktest/repo-google-vrp:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-google-vrp:v1.73.0 --tag docker.io/mocktest/repo-google-vrp:v1.73-latest ->> TAG: mocktest/repo-google-vrp:v1.73.0 -> mocktest/repo:google-vrp-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-google-vrp:v1.73.0 --tag docker.io/mocktest/repo:google-vrp-v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-tools:v1.73.0 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-tools --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-tools.tar -t mocktest/repo-tools:v1.73.0 . ->> PUSH: oci-archive:/non/existent/test/path/envoy-tools.tar -> docker://docker.io/mocktest/repo-tools:v1.73.0 -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-tools.tar docker://docker.io/mocktest/repo-tools:v1.73.0 ->> TAG: mocktest/repo-tools:v1.73.0 -> mocktest/repo:tools-v1.73.0 -> docker buildx imagetools create docker.io/mocktest/repo-tools:v1.73.0 --tag docker.io/mocktest/repo:tools-v1.73.0 ->> TAG: mocktest/repo-tools:v1.73.0 -> mocktest/repo-tools:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-tools:v1.73.0 --tag docker.io/mocktest/repo-tools:v1.73-latest ->> TAG: mocktest/repo-tools:v1.73.0 -> mocktest/repo:tools-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-tools:v1.73.0 --tag docker.io/mocktest/repo:tools-v1.73-latest diff --git a/ci/test/docker/linux/nondev/other b/ci/test/docker/linux/nondev/other deleted file mode 100644 index 71a775ce4671a..0000000000000 --- a/ci/test/docker/linux/nondev/other +++ /dev/null @@ -1,32 +0,0 @@ ->> BUILDX: install -> docker run --rm --privileged tonistiigi/binfmt --install all -> docker buildx rm multi-builder 2> /dev/null || : -> docker buildx create --use --name multi-builder --platform linux/arm64,linux/amd64 ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy.tar -t mocktest/repo:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-debug:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false -t mocktest/repo-debug:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-contrib.tar -t mocktest/repo-contrib:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib-debug:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false -t mocktest/repo-contrib-debug:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-distroless:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-distroless --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-distroless.tar -t mocktest/repo-distroless:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-google-vrp:v1.73.3 -> docker buildx build --platform linux/amd64 -f ci/Dockerfile-envoy --target envoy-google-vrp --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-google-vrp.tar -t mocktest/repo-google-vrp:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-tools:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-tools --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-tools.tar -t mocktest/repo-tools:v1.73.3 . diff --git a/ci/test/docker/linux/nondev/release b/ci/test/docker/linux/nondev/release deleted file mode 100644 index b3482396fbfee..0000000000000 --- a/ci/test/docker/linux/nondev/release +++ /dev/null @@ -1,81 +0,0 @@ ->> BUILDX: install -> docker run --rm --privileged tonistiigi/binfmt --install all -> docker buildx rm multi-builder 2> /dev/null || : -> docker buildx create --use --name multi-builder --platform linux/arm64,linux/amd64 ->> LOGIN ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy.tar -t mocktest/repo:v1.73.3 . ->> PUSH: oci-archive:/non/existent/test/path/envoy.tar -> docker://docker.io/mocktest/repo:v1.73.3 -> skopeo copy --all oci-archive:/non/existent/test/path/envoy.tar docker://docker.io/mocktest/repo:v1.73.3 ->> TAG: mocktest/repo:v1.73.3 -> mocktest/repo:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo:v1.73.3 --tag docker.io/mocktest/repo:v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD+PUSH: mocktest/repo-debug:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false --push -t mocktest/repo-debug:v1.73.3 . ->> TAG: mocktest/repo-debug:v1.73.3 -> mocktest/repo:debug-v1.73.3 -> docker buildx imagetools create docker.io/mocktest/repo-debug:v1.73.3 --tag docker.io/mocktest/repo:debug-v1.73.3 ->> TAG: mocktest/repo-debug:v1.73.3 -> mocktest/repo-debug:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-debug:v1.73.3 --tag docker.io/mocktest/repo-debug:v1.73-latest ->> TAG: mocktest/repo-debug:v1.73.3 -> mocktest/repo:debug-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-debug:v1.73.3 --tag docker.io/mocktest/repo:debug-v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-contrib.tar -t mocktest/repo-contrib:v1.73.3 . ->> PUSH: oci-archive:/non/existent/test/path/envoy-contrib.tar -> docker://docker.io/mocktest/repo-contrib:v1.73.3 -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-contrib.tar docker://docker.io/mocktest/repo-contrib:v1.73.3 ->> TAG: mocktest/repo-contrib:v1.73.3 -> mocktest/repo:contrib-v1.73.3 -> docker buildx imagetools create docker.io/mocktest/repo-contrib:v1.73.3 --tag docker.io/mocktest/repo:contrib-v1.73.3 ->> TAG: mocktest/repo-contrib:v1.73.3 -> mocktest/repo-contrib:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-contrib:v1.73.3 --tag docker.io/mocktest/repo-contrib:v1.73-latest ->> TAG: mocktest/repo-contrib:v1.73.3 -> mocktest/repo:contrib-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-contrib:v1.73.3 --tag docker.io/mocktest/repo:contrib-v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD+PUSH: mocktest/repo-contrib-debug:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false --push -t mocktest/repo-contrib-debug:v1.73.3 . ->> TAG: mocktest/repo-contrib-debug:v1.73.3 -> mocktest/repo:contrib-debug-v1.73.3 -> docker buildx imagetools create docker.io/mocktest/repo-contrib-debug:v1.73.3 --tag docker.io/mocktest/repo:contrib-debug-v1.73.3 ->> TAG: mocktest/repo-contrib-debug:v1.73.3 -> mocktest/repo-contrib-debug:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-contrib-debug:v1.73.3 --tag docker.io/mocktest/repo-contrib-debug:v1.73-latest ->> TAG: mocktest/repo-contrib-debug:v1.73.3 -> mocktest/repo:contrib-debug-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-contrib-debug:v1.73.3 --tag docker.io/mocktest/repo:contrib-debug-v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-distroless:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-distroless --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-distroless.tar -t mocktest/repo-distroless:v1.73.3 . ->> PUSH: oci-archive:/non/existent/test/path/envoy-distroless.tar -> docker://docker.io/mocktest/repo-distroless:v1.73.3 -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-distroless.tar docker://docker.io/mocktest/repo-distroless:v1.73.3 ->> TAG: mocktest/repo-distroless:v1.73.3 -> mocktest/repo:distroless-v1.73.3 -> docker buildx imagetools create docker.io/mocktest/repo-distroless:v1.73.3 --tag docker.io/mocktest/repo:distroless-v1.73.3 ->> TAG: mocktest/repo-distroless:v1.73.3 -> mocktest/repo-distroless:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-distroless:v1.73.3 --tag docker.io/mocktest/repo-distroless:v1.73-latest ->> TAG: mocktest/repo-distroless:v1.73.3 -> mocktest/repo:distroless-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-distroless:v1.73.3 --tag docker.io/mocktest/repo:distroless-v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-google-vrp:v1.73.3 -> docker buildx build --platform linux/amd64 -f ci/Dockerfile-envoy --target envoy-google-vrp --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-google-vrp.tar -t mocktest/repo-google-vrp:v1.73.3 . ->> PUSH: oci-archive:/non/existent/test/path/envoy-google-vrp.tar -> docker://docker.io/mocktest/repo-google-vrp:v1.73.3 -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-google-vrp.tar docker://docker.io/mocktest/repo-google-vrp:v1.73.3 ->> TAG: mocktest/repo-google-vrp:v1.73.3 -> mocktest/repo:google-vrp-v1.73.3 -> docker buildx imagetools create docker.io/mocktest/repo-google-vrp:v1.73.3 --tag docker.io/mocktest/repo:google-vrp-v1.73.3 ->> TAG: mocktest/repo-google-vrp:v1.73.3 -> mocktest/repo-google-vrp:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-google-vrp:v1.73.3 --tag docker.io/mocktest/repo-google-vrp:v1.73-latest ->> TAG: mocktest/repo-google-vrp:v1.73.3 -> mocktest/repo:google-vrp-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-google-vrp:v1.73.3 --tag docker.io/mocktest/repo:google-vrp-v1.73-latest ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-tools:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-tools --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-tools.tar -t mocktest/repo-tools:v1.73.3 . ->> PUSH: oci-archive:/non/existent/test/path/envoy-tools.tar -> docker://docker.io/mocktest/repo-tools:v1.73.3 -> skopeo copy --all oci-archive:/non/existent/test/path/envoy-tools.tar docker://docker.io/mocktest/repo-tools:v1.73.3 ->> TAG: mocktest/repo-tools:v1.73.3 -> mocktest/repo:tools-v1.73.3 -> docker buildx imagetools create docker.io/mocktest/repo-tools:v1.73.3 --tag docker.io/mocktest/repo:tools-v1.73.3 ->> TAG: mocktest/repo-tools:v1.73.3 -> mocktest/repo-tools:v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-tools:v1.73.3 --tag docker.io/mocktest/repo-tools:v1.73-latest ->> TAG: mocktest/repo-tools:v1.73.3 -> mocktest/repo:tools-v1.73-latest -> docker buildx imagetools create docker.io/mocktest/repo-tools:v1.73.3 --tag docker.io/mocktest/repo:tools-v1.73-latest diff --git a/ci/test/docker/linux/nondev/tag b/ci/test/docker/linux/nondev/tag deleted file mode 100644 index 71a775ce4671a..0000000000000 --- a/ci/test/docker/linux/nondev/tag +++ /dev/null @@ -1,32 +0,0 @@ ->> BUILDX: install -> docker run --rm --privileged tonistiigi/binfmt --install all -> docker buildx rm multi-builder 2> /dev/null || : -> docker buildx create --use --name multi-builder --platform linux/arm64,linux/amd64 ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy.tar -t mocktest/repo:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-debug:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false -t mocktest/repo-debug:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-contrib.tar -t mocktest/repo-contrib:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-contrib-debug:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy --build-arg ENVOY_BINARY=envoy-contrib --build-arg ENVOY_BINARY_PREFIX=dbg/ --sbom=false --provenance=false -t mocktest/repo-contrib-debug:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-distroless:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-distroless --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-distroless.tar -t mocktest/repo-distroless:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-google-vrp:v1.73.3 -> docker buildx build --platform linux/amd64 -f ci/Dockerfile-envoy --target envoy-google-vrp --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-google-vrp.tar -t mocktest/repo-google-vrp:v1.73.3 . ->> BUILDX: use multi-builder -> docker buildx use multi-builder ->> BUILD: mocktest/repo-tools:v1.73.3 -> docker buildx build --platform linux/arm64,linux/amd64 -f ci/Dockerfile-envoy --target envoy-tools --sbom=false --provenance=false -o type=oci,dest=/non/existent/test/path/envoy-tools.tar -t mocktest/repo-tools:v1.73.3 . diff --git a/ci/test_docker_ci.sh b/ci/test_docker_ci.sh deleted file mode 100755 index 35b73cce63a69..0000000000000 --- a/ci/test_docker_ci.sh +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env bash - -# Run this with `./ci/test_docker_ci.sh`. -# -# Compares against data stored in `ci/test/docker` -# -# To commit what is currently produced use: -# `DOCKER_CI_TEST_COMMIT=1 ./ci/test_docker_ci.sh` -# - -TESTS_MATCHING="$1" - -FAILED=() -DOCKER_CI_FIX="${DOCKER_CI_FIX:-}" -DOCKER_CI_FIX_DIFF="${DOCKER_CI_FIX_DIFF:-}" - -VERSION="1.73" -RELEASE_BRANCH="refs/heads/release/vXXX" -TAG_BRANCH="refs/tags/vXXX" -MAIN_BRANCH="refs/heads/main" -OTHER_BRANCH="refs/heads/something/else" - -PLATFORMS=(linux) -TEST_TYPES=(dev nondev) -BRANCH_TYPES=(tag release main other) - - -_test () { - local test_type="$1" branch_type="$2" version branch platform name testdata - local platform="${3:-linux}" - - name="${platform}_${test_type}_${branch_type}" - testdata="ci/test/docker/${platform}/${test_type}/${branch_type}" - - if ! test_matches "${name}"; then - return - fi - - if [[ "$branch_type" == "release" ]]; then - version="${VERSION}.3" - branch="$RELEASE_BRANCH" - elif [[ "$branch_type" == "tag" ]]; then - version="${VERSION}.3" - branch="$TAG_BRANCH" - elif [[ "$branch_type" == "other" ]]; then - version="${VERSION}.3" - branch="$OTHER_BRANCH" - else - branch="$MAIN_BRANCH" - version="${VERSION}.0" - fi - if [[ "$test_type" == "dev" ]]; then - version="${version}-dev" - fi - - export ENVOY_VERSION="${version}" - export CI_BRANCH="$branch" - # this should be ignored if the non-push - export DOCKERHUB_USERNAME=DHUSER - export DOCKERHUB_PASSWORD=DHPASSWORD - export DOCKER_CI_DRYRUN=1 - export ENVOY_DOCKER_IMAGE_DIRECTORY=/non/existent/test/path - export DOCKER_IMAGE_PREFIX=mocktest/repo - - if [[ "$DOCKER_CI_TEST_COMMIT" ]]; then - echo "COMMIT(${name}): > ${testdata}" - echo " ENVOY_VERSION=${version} ENVOY_DOCKER_IMAGE_DIRECTORY=/non/existent/test/path CI_BRANCH=${branch} DOCKER_CI_DRYRUN=1 ./distribution/docker/docker_ci.sh | grep -E \"^>\"" - ./distribution/docker/docker_ci.sh | grep -E "^>" > "$testdata" - return - fi - - echo "TEST(${name}): <> ${testdata}" - echo " ENVOY_VERSION=${version} ENVOY_DOCKER_IMAGE_DIRECTORY=/non/existent/test/path CI_BRANCH=${branch} DOCKER_CI_DRYRUN=1 ./distribution/docker/docker_ci.sh | grep -E \"^>\"" - generated="$(mktemp)" - - ./distribution/docker/docker_ci.sh | grep -E "^>" > "$generated" - - cmp --silent "$testdata" "$generated" || { - echo "files are different" >&2 - diff "$testdata" "$generated" >&2 - echo >&2 - echo "--------------------------" >&2 - cat "$generated" - echo >&2 - FAILED+=("$name") - echo >&2 - echo "--------------------------" >&2 - } - - rm "$generated" -} - -test_matches () { - local test_type="$1" - if [[ -z "$TESTS_MATCHING" ]]; then - return 0 - fi - if [[ "$test_type" =~ $TESTS_MATCHING ]]; then - return 0 - fi - return 1 -} - -run_tests () { - local platform test_type branch_type - - for platform in "${PLATFORMS[@]}"; do - for test_type in "${TEST_TYPES[@]}"; do - for branch_type in "${BRANCH_TYPES[@]}"; do - _test "$test_type" "$branch_type" "$platform" - done - done - done -} - -handle_failure () { - local platform test_type branch_type - if [[ "${#FAILED[@]}" -eq 0 ]]; then - return - fi - echo >&2 - echo "----------------------" >&2 - echo "FAILED" >&2 - - for FAILURE in "${FAILED[@]}"; do - echo "$FAILURE" >&2 - if [[ -n "$DOCKER_CI_FIX" ]]; then - platform="$(echo "$FAILURE" | cut -d_ -f1)" - test_type="$(echo "$FAILURE" | cut -d_ -f2)" - branch_type="$(echo "$FAILURE" | cut -d_ -f3)" - DOCKER_CI_TEST_COMMIT=1 _test "$test_type" "$branch_type" "$platform" - fi - done - - if [[ -n "$DOCKER_CI_FIX" ]]; then - git add -N ci/test/docker - echo >&2 - echo "----------------------" >&2 - echo "DIFF APPLIED" >&2 - echo >&2 - git diff ci/test/docker >&2 - - if [[ -n "$DOCKER_CI_FIX_DIFF" ]]; then - git diff ci/test/docker > "$DOCKER_CI_FIX_DIFF" - fi - echo "----------------------" >&2 - if [[ -e "$DOCKER_CI_FIX_DIFF" ]]; then - echo >&2 - echo "Diff file with fixes will be uploaded. Please check the artefacts for this PR run in the azure pipeline." >&2 - echo >&2 - echo "Fixes will need to be checked before being committed." >&2 - fi - fi - - return 1 -} - -run_tests -handle_failure || { - exit 1 -} From 36bc431b9b0497ebcaf40aca6d455fc8a7e5fb8f Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Fri, 8 May 2026 14:43:10 -0400 Subject: [PATCH 269/750] srds: reduce memory by using string_view as a key instead of string (#44909) Signed-off-by: Adi Suissa-Peleg --- source/common/router/scoped_config_impl.cc | 4 +++- source/common/router/scoped_config_impl.h | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/source/common/router/scoped_config_impl.cc b/source/common/router/scoped_config_impl.cc index ca963a5689eef..5b39ff196901d 100644 --- a/source/common/router/scoped_config_impl.cc +++ b/source/common/router/scoped_config_impl.cc @@ -129,8 +129,10 @@ void ScopedConfigImpl::addOrUpdateRoutingScopes( if (iter != scoped_route_info_by_name_.end()) { ASSERT(scoped_route_info_by_key_.contains(iter->second->scopeKey().hash())); scoped_route_info_by_key_.erase(iter->second->scopeKey().hash()); + // Explicitly erase from _name_ map to avoid dangling string_view on overwrite. + scoped_route_info_by_name_.erase(iter); } - scoped_route_info_by_name_[scoped_route_info->scopeName()] = scoped_route_info; + scoped_route_info_by_name_.emplace(scoped_route_info->scopeName(), scoped_route_info); scoped_route_info_by_key_[scoped_route_info->scopeKey().hash()] = scoped_route_info; } } diff --git a/source/common/router/scoped_config_impl.h b/source/common/router/scoped_config_impl.h index ee134aaf66eb1..d233bd204024b 100644 --- a/source/common/router/scoped_config_impl.h +++ b/source/common/router/scoped_config_impl.h @@ -123,7 +123,10 @@ class ScopedConfigImpl : public ScopedConfig { private: // From scope name to cached ScopedRouteInfo. - absl::flat_hash_map scoped_route_info_by_name_; + // WARNING: The absl::string_view keys point to strings owned by the ScopedRouteInfo + // objects stored as values. To avoid dangling pointers, any update to this map MUST + // first erase the old entry before inserting a new one. + absl::flat_hash_map scoped_route_info_by_name_; // Hash by ScopeKey hash to lookup in constant time. absl::flat_hash_map scoped_route_info_by_key_; }; From 5511b9114c888a537e8fa6e939e018085910845b Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 May 2026 20:01:03 +0100 Subject: [PATCH 270/750] ci: Bump toolshed actions -> 0.4.14 (#44957) Signed-off-by: Ryan Northey --- .github/workflows/_check_coverage.yml | 4 +-- .github/workflows/_cve_fetch.yml | 2 +- .github/workflows/_cve_scan.yml | 2 +- .github/workflows/_finish.yml | 8 ++--- .github/workflows/_load.yml | 10 +++--- .github/workflows/_load_env.yml | 8 ++--- .github/workflows/_precheck_publish.yml | 2 +- .github/workflows/_publish_release.yml | 4 +-- .../workflows/_publish_release_container.yml | 6 ++-- .github/workflows/_publish_verify.yml | 2 +- .github/workflows/_request.yml | 24 ++++++------- .github/workflows/_request_cache_bazel.yml | 10 +++--- .github/workflows/_request_cache_docker.yml | 10 +++--- .github/workflows/_request_checks.yml | 10 +++--- .github/workflows/_run.yml | 28 +++++++-------- .github/workflows/_upload_gcs.yml | 6 ++-- .github/workflows/codeql-daily.yml | 4 +-- .github/workflows/codeql-push.yml | 4 +-- .github/workflows/command.yml | 6 ++-- .github/workflows/envoy-dependency.yml | 18 +++++----- .github/workflows/envoy-release.yml | 34 +++++++++---------- .github/workflows/envoy-sync.yml | 4 +-- .github/workflows/mobile-android_build.yml | 12 +++---- .github/workflows/mobile-ios_build.yml | 4 +-- 24 files changed, 111 insertions(+), 111 deletions(-) diff --git a/.github/workflows/_check_coverage.yml b/.github/workflows/_check_coverage.yml index 3ee0ee1888fa6..9b5174074dc8f 100644 --- a/.github/workflows/_check_coverage.yml +++ b/.github/workflows/_check_coverage.yml @@ -61,7 +61,7 @@ jobs: upload-name: coverage upload-path: generated/coverage/html steps-post: | - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: output-path: generated/coverage/html/gcs-metadata.json input-format: yaml @@ -82,7 +82,7 @@ jobs: - target: fuzz_coverage name: Fuzz coverage steps-post: | - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: output-path: generated/fuzz_coverage/html/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_cve_fetch.yml b/.github/workflows/_cve_fetch.yml index 13c95bb755b35..e4a7a706d0427 100644 --- a/.github/workflows/_cve_fetch.yml +++ b/.github/workflows/_cve_fetch.yml @@ -35,7 +35,7 @@ jobs: else echo "weekly_run=false" >> $GITHUB_OUTPUT fi - - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/gcp/setup@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_cve_scan.yml b/.github/workflows/_cve_scan.yml index f08900578d5ae..a6b3e9dc9beae 100644 --- a/.github/workflows/_cve_scan.yml +++ b/.github/workflows/_cve_scan.yml @@ -28,7 +28,7 @@ jobs: id: vars run: | echo "cve-data-path=${{ inputs.cve-data-path }}" > $GITHUB_OUTPUT - - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/gcp/setup@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_finish.yml b/.github/workflows/_finish.yml index 8b6b4d81928ee..dc80af51c7c9c 100644 --- a/.github/workflows/_finish.yml +++ b/.github/workflows/_finish.yml @@ -36,7 +36,7 @@ jobs: actions: read contents: read steps: - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Incoming data id: needs with: @@ -87,7 +87,7 @@ jobs: summary: "Check has finished", text: $text}}}} - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Print summary with: input: ${{ toJSON(steps.needs.outputs.value).summary-title }} @@ -95,13 +95,13 @@ jobs: "## \(.)" options: -Rr output-path: GITHUB_STEP_SUMMARY - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checks@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Update check with: action: update diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index d3ce020ab86de..3533629d06389 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -102,7 +102,7 @@ jobs: # Handle any failure in triggering job # Remove any `checks` we dont care about # Prepare a check request - - uses: envoyproxy/toolshed/actions/github/env/load@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/env/load@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Load env id: data with: @@ -113,13 +113,13 @@ jobs: GH_TOKEN: ${{ github.token }} # Update the check - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checks@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Update check if: ${{ fromJSON(steps.data.outputs.data).data.check.action == 'RUN' }} with: @@ -127,7 +127,7 @@ jobs: checks: ${{ toJSON(fromJSON(steps.data.outputs.data).checks) }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Print request summary with: input: | @@ -147,7 +147,7 @@ jobs: | $summary.summary as $summary | "${{ inputs.template-request-summary }}" - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: request-output name: Load request with: diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index a8036596341fa..d3539ec7a10b5 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -62,18 +62,18 @@ jobs: request: ${{ steps.env.outputs.data }} trusted: true steps: - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: checkout name: Checkout Envoy repository - name: Generate environment variables - uses: envoyproxy/toolshed/actions/envoy/ci/env@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/envoy/ci/env@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: env with: branch-name: ${{ inputs.branch-name }} @@ -85,7 +85,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/env/summary@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} diff --git a/.github/workflows/_precheck_publish.yml b/.github/workflows/_precheck_publish.yml index e891b56aa0d1d..ec1f484931b10 100644 --- a/.github/workflows/_precheck_publish.yml +++ b/.github/workflows/_precheck_publish.yml @@ -88,7 +88,7 @@ jobs: upload-name: docs upload-path: generated/docs steps-post: | - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: output-path: generated/docs/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_publish_release.yml b/.github/workflows/_publish_release.yml index b49edc72ca7c4..992ca240beb3e 100644 --- a/.github/workflows/_publish_release.yml +++ b/.github/workflows/_publish_release.yml @@ -142,12 +142,12 @@ jobs: needs: - release steps: - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/actions/dispatch@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/dispatch@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: ref: main repository: ${{ fromJSON(inputs.request).request.version.dev && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} diff --git a/.github/workflows/_publish_release_container.yml b/.github/workflows/_publish_release_container.yml index 1374d55b42e97..f6577e97bb4da 100644 --- a/.github/workflows/_publish_release_container.yml +++ b/.github/workflows/_publish_release_container.yml @@ -58,7 +58,7 @@ jobs: - name: Generate manifest configuration (dev) id: dev-config if: ${{ inputs.dev && inputs.target-branch == 'main' }} - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: input-format: yaml filter: >- @@ -137,7 +137,7 @@ jobs: - tools-dev-${{ github.sha }} - name: Generate manifest configuration (release) - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: release-config if: ${{ ! inputs.dev || ! inputs.target-branch != 'main' }} with: @@ -227,7 +227,7 @@ jobs: - tools-v${{ inputs.version-major }}.${{ inputs.version-minor }}-latest - name: Collect and push OCI artifacts - uses: envoyproxy/toolshed/actions/oci/collector@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/oci/collector@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: artifacts-pattern: oci.* manifest-config: ${{ steps.dev-config.outputs.value || steps.release-config.outputs.value }} diff --git a/.github/workflows/_publish_verify.yml b/.github/workflows/_publish_verify.yml index 83a50ecdb3f70..de3ce661beb7a 100644 --- a/.github/workflows/_publish_verify.yml +++ b/.github/workflows/_publish_verify.yml @@ -114,7 +114,7 @@ jobs: export NO_BUILD_SETUP=1 steps-pre: | - id: version-support - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: input: | version_major: ${{ fromJSON(inputs.request).request.version.major }} diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index 8d293dee26424..ca536f759411f 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -55,14 +55,14 @@ jobs: caches: ${{ steps.caches.outputs.value }} config: ${{ steps.config.outputs.config }} steps: - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: checkout name: Checkout Envoy repository (requested) with: @@ -76,7 +76,7 @@ jobs: # *ALL* variables collected should be treated as untrusted and should be sanitized before # use - name: Generate environment variables from commit - uses: envoyproxy/toolshed/actions/envoy/ci/request@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/envoy/ci/request@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: env with: branch-name: ${{ steps.checkout.outputs.branch-name }} @@ -87,7 +87,7 @@ jobs: vars: ${{ toJSON(vars) }} working-directory: requested - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -95,7 +95,7 @@ jobs: config: | fetch-depth: 1 path: target - - uses: envoyproxy/toolshed/actions/hashfiles@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/hashfiles@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: bazel-cache-hash name: Bazel cache hash with: @@ -104,7 +104,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/env/summary@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} @@ -120,28 +120,28 @@ jobs: target-branch: ${{ fromJSON(steps.env.outputs.data).request.target-branch }} - id: cache-id-bazel-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-arm64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-arm64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-docs-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-docs-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-external-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-external-x64 wf-path: .github/workflows/request.yml - name: Environment data - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: data with: input: | @@ -189,7 +189,7 @@ jobs: path: /tmp/cache key: ${{ fromJSON(steps.data.outputs.value).request.build-image.default }}-arm64 - name: Caches - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: caches with: input-format: yaml diff --git a/.github/workflows/_request_cache_bazel.yml b/.github/workflows/_request_cache_bazel.yml index 6237eeb0091df..efcf7467b09ee 100644 --- a/.github/workflows/_request_cache_bazel.yml +++ b/.github/workflows/_request_cache_bazel.yml @@ -59,7 +59,7 @@ jobs: && ! fromJSON(inputs.caches).bazel[format('{0}-{1}', inputs.output-base, inputs.arch)]) }} steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bind-mounts@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: mounts: | - src: /mnt/workspace @@ -73,9 +73,9 @@ jobs: target: /build chown: "runner:docker" - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/diskspace@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 if: inputs.arch == 'x64' && github.event.repository.private - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -83,14 +83,14 @@ jobs: config: | fetch-depth: 1 - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/cache/prime@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/cache/prime@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: bazel-cache name: Prime Bazel cache with: diff --git a/.github/workflows/_request_cache_docker.yml b/.github/workflows/_request_cache_docker.yml index 3fd5228e9feb5..cf19417e3f044 100644 --- a/.github/workflows/_request_cache_docker.yml +++ b/.github/workflows/_request_cache_docker.yml @@ -39,7 +39,7 @@ on: # For a job that does, you can restore with something like: # # steps: -# - uses: envoyproxy/toolshed/actions/docker/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 +# - uses: envoyproxy/toolshed/actions/docker/cache/restore@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 # with: # key: "${{ needs.env.outputs.build-image }}" # @@ -51,13 +51,13 @@ jobs: name: "[${{ inputs.arch }}] Prime Docker cache" if: ${{ ! fromJSON(inputs.caches).docker[inputs.arch] }} steps: - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/docker/cache/prime@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/docker/cache/prime@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: docker name: Prime Docker cache (${{ inputs.image-tag }}${{ inputs.cache-suffix }}) with: @@ -65,7 +65,7 @@ jobs: key-suffix: ${{ inputs.cache-suffix }} lock-token: ${{ steps.appauth.outputs.token }} lock-repository: ${{ inputs.lock-repository }} - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: data name: Cache data with: @@ -73,7 +73,7 @@ jobs: input: | cached: ${{ steps.docker.outputs.cached }} key: ${{ inputs.image-tag }}${{ inputs.cache-suffix }} - - uses: envoyproxy/toolshed/actions/json/table@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/json/table@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Summary with: json: ${{ steps.data.outputs.value }} diff --git a/.github/workflows/_request_checks.yml b/.github/workflows/_request_checks.yml index 55e894fcd1764..cb96cbae9cac0 100644 --- a/.github/workflows/_request_checks.yml +++ b/.github/workflows/_request_checks.yml @@ -55,7 +55,7 @@ jobs: runs-on: ${{ fromJSON(inputs.env).config.ci.agent-ubuntu }} name: Start checks steps: - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: check-config name: Prepare check data with: @@ -78,13 +78,13 @@ jobs: | .skipped.output.summary = "${{ inputs.skipped-summary }}" | .skipped.output.text = "" - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checks@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Start checks id: checks with: @@ -95,7 +95,7 @@ jobs: ${{ fromJSON(inputs.env).summary.summary }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/json/table@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/json/table@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Summary with: collapse-open: true @@ -119,7 +119,7 @@ jobs: output-path: GITHUB_STEP_SUMMARY title: Checks started/skipped - - uses: envoyproxy/toolshed/actions/github/env/save@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/env/save@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Save env id: data with: diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 56ebeb49f6bc8..d1877999f2f7f 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -146,7 +146,7 @@ on: summary-post: type: string default: | - - uses: envoyproxy/toolshed/actions/envoy/run/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/run/summary@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: context: %{{ inputs.context }} steps-pre: @@ -215,7 +215,7 @@ jobs: name: ${{ inputs.target-suffix && format('[{0}] ', inputs.target-suffix) || '' }}${{ inputs.command }} ${{ inputs.target }} timeout-minutes: ${{ inputs.timeout-minutes }} steps: - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: started name: Create timestamp with: @@ -223,7 +223,7 @@ jobs: filter: | now # This controls which input vars are exposed to the run action (and related steps) - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Context id: context with: @@ -256,14 +256,14 @@ jobs: fi echo "mnt-available=$MNT_AVAILABLE" >> "$GITHUB_OUTPUT" id: disk - - uses: envoyproxy/toolshed/actions/github/remnt@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/remnt@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 if: steps.disk.outputs.should-remnt == 'true' - - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bind-mounts@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 if: inputs.bind-mount && steps.disk.outputs.mnt-available == 'true' with: mounts: ${{ inputs.bind-mounts }} - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/diskspace@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 if: inputs.diskspace-hack || steps.disk.outputs.mnt-available != 'true' with: to_remove: ${{ inputs.diskspace-hack-paths }} @@ -271,7 +271,7 @@ jobs: mount df -h - - uses: envoyproxy/toolshed/actions/bson@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bson@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Configure Docker if: runner.os == 'Linux' with: @@ -287,7 +287,7 @@ jobs: | "${{ inputs.template-docker-configure }}" # Caches - - uses: envoyproxy/toolshed/actions/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/cache/restore@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 if: >- fromJSON(inputs.bazel-cache) name: >- @@ -328,12 +328,12 @@ jobs: key: ${{ inputs.cache-build-image }}${{ inputs.cache-build-image-key-suffix }} - if: ${{ inputs.cache-build-image && steps.cache-lookup.outputs.cache-hit == 'true' }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/actions/docker/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/docker/cache/restore@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: image-tag: ${{ inputs.cache-build-image }} key-suffix: ${{ inputs.cache-build-image-key-suffix }} - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: appauth name: Appauth if: ${{ inputs.trusted }} @@ -344,7 +344,7 @@ jobs: # - the workaround is to allow the token to be passed through. token: ${{ github.token }} token-ok: true - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: checkout name: Checkout Envoy repository with: @@ -360,7 +360,7 @@ jobs: token: ${{ inputs.trusted && steps.appauth.outputs.token || github.token }} # This is currently only use by mobile-docs and can be removed once they are updated to the newer website - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: checkout-extra name: Checkout extra repository (for publishing) if: ${{ inputs.checkout-extra }} @@ -369,7 +369,7 @@ jobs: ssh-key: ${{ inputs.trusted && inputs.ssh-key-extra || '' }} - name: Import GPG key - uses: envoyproxy/toolshed/actions/gpg/import@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/gpg/import@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 if: ${{ inputs.import-gpg }} with: key: ${{ secrets.gpg-key }} @@ -406,7 +406,7 @@ jobs: # NOTE: This is where untrusted code can be run!!! # Only post-failure notification steps (which don't use any repo code) should follow this step. - - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/run@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: ci-run name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: diff --git a/.github/workflows/_upload_gcs.yml b/.github/workflows/_upload_gcs.yml index 556c3ec314567..1e156e414440f 100644 --- a/.github/workflows/_upload_gcs.yml +++ b/.github/workflows/_upload_gcs.yml @@ -29,17 +29,17 @@ jobs: with: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: metadata with: input: ${{ matrix.artifact }}/gcs-metadata.json input-format: json-path - run: | rm -rf ${{ matrix.artifact }}/gcs-metadata.json - - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/gcp/setup@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: key: ${{ secrets.gcp-key }} - - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: bucket: ${{ fromJSON(steps.metadata.outputs.value).bucket }} path: ${{ matrix.artifact }} diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 5baf6be36d540..d0ea7bef34c1c 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -28,7 +28,7 @@ jobs: steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bind-mounts@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 if: | ! github.event.repository.private with: @@ -43,7 +43,7 @@ jobs: if: | env.BUILD_TARGETS != '' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/diskspace@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 932cb66523f26..d3e8fced3ae07 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-22.04 if: github.repository == 'envoyproxy/envoy' steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bind-mounts@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 if: ! github.event.repository.private with: mounts: | @@ -64,7 +64,7 @@ jobs: if: | env.CPP_CHANGED == 'true' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/diskspace@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/command.yml b/.github/workflows/command.yml index bded5e0902aaa..779caf34b296d 100644 --- a/.github/workflows/command.yml +++ b/.github/workflows/command.yml @@ -28,7 +28,7 @@ jobs: && github.actor != 'dependabot[bot]' }} steps: - - uses: envoyproxy/toolshed/actions/github/command@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/command@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Parse command from comment id: command with: @@ -37,14 +37,14 @@ jobs: ^/(retest) # /retest - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 if: ${{ steps.command.outputs.command == 'retest' }} id: appauth-retest name: Appauth (retest) with: key: ${{ secrets.ENVOY_CI_APP_KEY }} app_id: ${{ secrets.ENVOY_CI_APP_ID }} - - uses: envoyproxy/toolshed/actions/retest@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/retest@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 if: ${{ steps.command.outputs.command == 'retest' }} name: Retest with: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 41f5259d91acd..4e51214d6e813 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -53,16 +53,16 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/bson@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bson@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: update name: Update dependency (${{ inputs.dependency }}) with: @@ -97,13 +97,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/actions/upload/diff@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/upload/diff@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.update.outputs.output }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/pr@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: base: main body: | @@ -134,11 +134,11 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: checkout name: Checkout Envoy repository with: @@ -191,7 +191,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/actions/docker/shas@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/docker/shas@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: images: | sha-ci: docker.io/envoyproxy/envoy-build:ci-${{ steps.build-tools.outputs.tag }} @@ -229,7 +229,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/pr@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 319eae3a9ce01..094352cbeda7c 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -60,14 +60,14 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} @@ -88,10 +88,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/actions/email/validate@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/email/validate@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/run@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Create release with: source: | @@ -116,7 +116,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/pr@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: base: ${{ github.ref_name }} commit: false @@ -142,19 +142,19 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/run@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Re-open branch with: command: >- @@ -169,7 +169,7 @@ jobs: name: Dev version id: dev - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/pr@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: base: ${{ github.ref_name }} commit: false @@ -193,20 +193,20 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/run@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 name: Sync version histories with: command: >- @@ -216,7 +216,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/pr@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: append-commit-message: true base: ${{ github.ref_name }} @@ -243,13 +243,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: config: | fetch-depth: 0 @@ -279,13 +279,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - name: Checkout repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index 887d1b2d94c1d..ac742ea29af01 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -43,13 +43,13 @@ jobs: echo "skip=true" >> "$GITHUB_OUTPUT" fi - if: steps.tip.outputs.skip != 'true' - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - if: steps.tip.outputs.skip != 'true' - uses: envoyproxy/toolshed/actions/dispatch@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/dispatch@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index b86db3c471b7e..c7c1ec34dc4ff 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -107,9 +107,9 @@ jobs: target: kotlin-hello-world runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: apk: bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity @@ -152,7 +152,7 @@ jobs: target: ${{ matrix.target }} runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 steps-post: ${{ matrix.steps-post }} timeout-minutes: 50 trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} @@ -163,7 +163,7 @@ jobs: include: - name: java-hello-world steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: apk: bazel-bin/examples/java/hello_world/hello_envoy.apk app: io.envoyproxy.envoymobile.helloenvoy/.MainActivity @@ -186,7 +186,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/baseline:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: apk: bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity @@ -203,7 +203,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/experimental:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 with: apk: bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 15a64d00a0215..f8ef0b05c9270 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -103,7 +103,7 @@ jobs: # source ./ci/mac_ci_setup.sh # bazel shutdown # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} @@ -148,7 +148,7 @@ jobs: # source: | # source ./ci/mac_ci_setup.sh # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} From 3eaa049df9bed77d4e40f60e7050b0bebd3c158f Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 May 2026 20:08:37 +0100 Subject: [PATCH 271/750] Revert "ci: Bump toolshed actions -> 0.4.14 (#44957)" (#44958) This reverts commit 5511b9114c888a537e8fa6e939e018085910845b. Signed-off-by: Ryan Northey --- .github/workflows/_check_coverage.yml | 4 +-- .github/workflows/_cve_fetch.yml | 2 +- .github/workflows/_cve_scan.yml | 2 +- .github/workflows/_finish.yml | 8 ++--- .github/workflows/_load.yml | 10 +++--- .github/workflows/_load_env.yml | 8 ++--- .github/workflows/_precheck_publish.yml | 2 +- .github/workflows/_publish_release.yml | 4 +-- .../workflows/_publish_release_container.yml | 6 ++-- .github/workflows/_publish_verify.yml | 2 +- .github/workflows/_request.yml | 24 ++++++------- .github/workflows/_request_cache_bazel.yml | 10 +++--- .github/workflows/_request_cache_docker.yml | 10 +++--- .github/workflows/_request_checks.yml | 10 +++--- .github/workflows/_run.yml | 28 +++++++-------- .github/workflows/_upload_gcs.yml | 6 ++-- .github/workflows/codeql-daily.yml | 4 +-- .github/workflows/codeql-push.yml | 4 +-- .github/workflows/command.yml | 6 ++-- .github/workflows/envoy-dependency.yml | 18 +++++----- .github/workflows/envoy-release.yml | 34 +++++++++---------- .github/workflows/envoy-sync.yml | 4 +-- .github/workflows/mobile-android_build.yml | 12 +++---- .github/workflows/mobile-ios_build.yml | 4 +-- 24 files changed, 111 insertions(+), 111 deletions(-) diff --git a/.github/workflows/_check_coverage.yml b/.github/workflows/_check_coverage.yml index 9b5174074dc8f..3ee0ee1888fa6 100644 --- a/.github/workflows/_check_coverage.yml +++ b/.github/workflows/_check_coverage.yml @@ -61,7 +61,7 @@ jobs: upload-name: coverage upload-path: generated/coverage/html steps-post: | - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: output-path: generated/coverage/html/gcs-metadata.json input-format: yaml @@ -82,7 +82,7 @@ jobs: - target: fuzz_coverage name: Fuzz coverage steps-post: | - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: output-path: generated/fuzz_coverage/html/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_cve_fetch.yml b/.github/workflows/_cve_fetch.yml index e4a7a706d0427..13c95bb755b35 100644 --- a/.github/workflows/_cve_fetch.yml +++ b/.github/workflows/_cve_fetch.yml @@ -35,7 +35,7 @@ jobs: else echo "weekly_run=false" >> $GITHUB_OUTPUT fi - - uses: envoyproxy/toolshed/actions/gcp/setup@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_cve_scan.yml b/.github/workflows/_cve_scan.yml index a6b3e9dc9beae..f08900578d5ae 100644 --- a/.github/workflows/_cve_scan.yml +++ b/.github/workflows/_cve_scan.yml @@ -28,7 +28,7 @@ jobs: id: vars run: | echo "cve-data-path=${{ inputs.cve-data-path }}" > $GITHUB_OUTPUT - - uses: envoyproxy/toolshed/actions/gcp/setup@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_finish.yml b/.github/workflows/_finish.yml index dc80af51c7c9c..8b6b4d81928ee 100644 --- a/.github/workflows/_finish.yml +++ b/.github/workflows/_finish.yml @@ -36,7 +36,7 @@ jobs: actions: read contents: read steps: - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Incoming data id: needs with: @@ -87,7 +87,7 @@ jobs: summary: "Check has finished", text: $text}}}} - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Print summary with: input: ${{ toJSON(steps.needs.outputs.value).summary-title }} @@ -95,13 +95,13 @@ jobs: "## \(.)" options: -Rr output-path: GITHUB_STEP_SUMMARY - - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Update check with: action: update diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index 3533629d06389..d3ce020ab86de 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -102,7 +102,7 @@ jobs: # Handle any failure in triggering job # Remove any `checks` we dont care about # Prepare a check request - - uses: envoyproxy/toolshed/actions/github/env/load@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/env/load@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Load env id: data with: @@ -113,13 +113,13 @@ jobs: GH_TOKEN: ${{ github.token }} # Update the check - - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Update check if: ${{ fromJSON(steps.data.outputs.data).data.check.action == 'RUN' }} with: @@ -127,7 +127,7 @@ jobs: checks: ${{ toJSON(fromJSON(steps.data.outputs.data).checks) }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Print request summary with: input: | @@ -147,7 +147,7 @@ jobs: | $summary.summary as $summary | "${{ inputs.template-request-summary }}" - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: request-output name: Load request with: diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index d3539ec7a10b5..a8036596341fa 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -62,18 +62,18 @@ jobs: request: ${{ steps.env.outputs.data }} trusted: true steps: - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout name: Checkout Envoy repository - name: Generate environment variables - uses: envoyproxy/toolshed/actions/envoy/ci/env@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/envoy/ci/env@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: env with: branch-name: ${{ inputs.branch-name }} @@ -85,7 +85,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/env/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} diff --git a/.github/workflows/_precheck_publish.yml b/.github/workflows/_precheck_publish.yml index ec1f484931b10..e891b56aa0d1d 100644 --- a/.github/workflows/_precheck_publish.yml +++ b/.github/workflows/_precheck_publish.yml @@ -88,7 +88,7 @@ jobs: upload-name: docs upload-path: generated/docs steps-post: | - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: output-path: generated/docs/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_publish_release.yml b/.github/workflows/_publish_release.yml index 992ca240beb3e..b49edc72ca7c4 100644 --- a/.github/workflows/_publish_release.yml +++ b/.github/workflows/_publish_release.yml @@ -142,12 +142,12 @@ jobs: needs: - release steps: - - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/actions/dispatch@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/dispatch@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: ref: main repository: ${{ fromJSON(inputs.request).request.version.dev && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} diff --git a/.github/workflows/_publish_release_container.yml b/.github/workflows/_publish_release_container.yml index f6577e97bb4da..1374d55b42e97 100644 --- a/.github/workflows/_publish_release_container.yml +++ b/.github/workflows/_publish_release_container.yml @@ -58,7 +58,7 @@ jobs: - name: Generate manifest configuration (dev) id: dev-config if: ${{ inputs.dev && inputs.target-branch == 'main' }} - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: input-format: yaml filter: >- @@ -137,7 +137,7 @@ jobs: - tools-dev-${{ github.sha }} - name: Generate manifest configuration (release) - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: release-config if: ${{ ! inputs.dev || ! inputs.target-branch != 'main' }} with: @@ -227,7 +227,7 @@ jobs: - tools-v${{ inputs.version-major }}.${{ inputs.version-minor }}-latest - name: Collect and push OCI artifacts - uses: envoyproxy/toolshed/actions/oci/collector@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/oci/collector@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: artifacts-pattern: oci.* manifest-config: ${{ steps.dev-config.outputs.value || steps.release-config.outputs.value }} diff --git a/.github/workflows/_publish_verify.yml b/.github/workflows/_publish_verify.yml index de3ce661beb7a..83a50ecdb3f70 100644 --- a/.github/workflows/_publish_verify.yml +++ b/.github/workflows/_publish_verify.yml @@ -114,7 +114,7 @@ jobs: export NO_BUILD_SETUP=1 steps-pre: | - id: version-support - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: input: | version_major: ${{ fromJSON(inputs.request).request.version.major }} diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index ca536f759411f..8d293dee26424 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -55,14 +55,14 @@ jobs: caches: ${{ steps.caches.outputs.value }} config: ${{ steps.config.outputs.config }} steps: - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout name: Checkout Envoy repository (requested) with: @@ -76,7 +76,7 @@ jobs: # *ALL* variables collected should be treated as untrusted and should be sanitized before # use - name: Generate environment variables from commit - uses: envoyproxy/toolshed/actions/envoy/ci/request@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/envoy/ci/request@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: env with: branch-name: ${{ steps.checkout.outputs.branch-name }} @@ -87,7 +87,7 @@ jobs: vars: ${{ toJSON(vars) }} working-directory: requested - - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -95,7 +95,7 @@ jobs: config: | fetch-depth: 1 path: target - - uses: envoyproxy/toolshed/actions/hashfiles@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/hashfiles@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: bazel-cache-hash name: Bazel cache hash with: @@ -104,7 +104,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/env/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} @@ -120,28 +120,28 @@ jobs: target-branch: ${{ fromJSON(steps.env.outputs.data).request.target-branch }} - id: cache-id-bazel-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-arm64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-arm64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-docs-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-docs-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-external-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-external-x64 wf-path: .github/workflows/request.yml - name: Environment data - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: data with: input: | @@ -189,7 +189,7 @@ jobs: path: /tmp/cache key: ${{ fromJSON(steps.data.outputs.value).request.build-image.default }}-arm64 - name: Caches - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: caches with: input-format: yaml diff --git a/.github/workflows/_request_cache_bazel.yml b/.github/workflows/_request_cache_bazel.yml index efcf7467b09ee..6237eeb0091df 100644 --- a/.github/workflows/_request_cache_bazel.yml +++ b/.github/workflows/_request_cache_bazel.yml @@ -59,7 +59,7 @@ jobs: && ! fromJSON(inputs.caches).bazel[format('{0}-{1}', inputs.output-base, inputs.arch)]) }} steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: mounts: | - src: /mnt/workspace @@ -73,9 +73,9 @@ jobs: target: /build chown: "runner:docker" - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: inputs.arch == 'x64' && github.event.repository.private - - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -83,14 +83,14 @@ jobs: config: | fetch-depth: 1 - - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/cache/prime@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/cache/prime@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: bazel-cache name: Prime Bazel cache with: diff --git a/.github/workflows/_request_cache_docker.yml b/.github/workflows/_request_cache_docker.yml index cf19417e3f044..3fd5228e9feb5 100644 --- a/.github/workflows/_request_cache_docker.yml +++ b/.github/workflows/_request_cache_docker.yml @@ -39,7 +39,7 @@ on: # For a job that does, you can restore with something like: # # steps: -# - uses: envoyproxy/toolshed/actions/docker/cache/restore@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 +# - uses: envoyproxy/toolshed/actions/docker/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 # with: # key: "${{ needs.env.outputs.build-image }}" # @@ -51,13 +51,13 @@ jobs: name: "[${{ inputs.arch }}] Prime Docker cache" if: ${{ ! fromJSON(inputs.caches).docker[inputs.arch] }} steps: - - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/docker/cache/prime@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/docker/cache/prime@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: docker name: Prime Docker cache (${{ inputs.image-tag }}${{ inputs.cache-suffix }}) with: @@ -65,7 +65,7 @@ jobs: key-suffix: ${{ inputs.cache-suffix }} lock-token: ${{ steps.appauth.outputs.token }} lock-repository: ${{ inputs.lock-repository }} - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: data name: Cache data with: @@ -73,7 +73,7 @@ jobs: input: | cached: ${{ steps.docker.outputs.cached }} key: ${{ inputs.image-tag }}${{ inputs.cache-suffix }} - - uses: envoyproxy/toolshed/actions/json/table@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/json/table@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Summary with: json: ${{ steps.data.outputs.value }} diff --git a/.github/workflows/_request_checks.yml b/.github/workflows/_request_checks.yml index cb96cbae9cac0..55e894fcd1764 100644 --- a/.github/workflows/_request_checks.yml +++ b/.github/workflows/_request_checks.yml @@ -55,7 +55,7 @@ jobs: runs-on: ${{ fromJSON(inputs.env).config.ci.agent-ubuntu }} name: Start checks steps: - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: check-config name: Prepare check data with: @@ -78,13 +78,13 @@ jobs: | .skipped.output.summary = "${{ inputs.skipped-summary }}" | .skipped.output.text = "" - - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Start checks id: checks with: @@ -95,7 +95,7 @@ jobs: ${{ fromJSON(inputs.env).summary.summary }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/json/table@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/json/table@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Summary with: collapse-open: true @@ -119,7 +119,7 @@ jobs: output-path: GITHUB_STEP_SUMMARY title: Checks started/skipped - - uses: envoyproxy/toolshed/actions/github/env/save@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/env/save@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Save env id: data with: diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index d1877999f2f7f..56ebeb49f6bc8 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -146,7 +146,7 @@ on: summary-post: type: string default: | - - uses: envoyproxy/toolshed/actions/envoy/run/summary@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/envoy/run/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: context: %{{ inputs.context }} steps-pre: @@ -215,7 +215,7 @@ jobs: name: ${{ inputs.target-suffix && format('[{0}] ', inputs.target-suffix) || '' }}${{ inputs.command }} ${{ inputs.target }} timeout-minutes: ${{ inputs.timeout-minutes }} steps: - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: started name: Create timestamp with: @@ -223,7 +223,7 @@ jobs: filter: | now # This controls which input vars are exposed to the run action (and related steps) - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Context id: context with: @@ -256,14 +256,14 @@ jobs: fi echo "mnt-available=$MNT_AVAILABLE" >> "$GITHUB_OUTPUT" id: disk - - uses: envoyproxy/toolshed/actions/github/remnt@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/remnt@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: steps.disk.outputs.should-remnt == 'true' - - uses: envoyproxy/toolshed/actions/bind-mounts@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: inputs.bind-mount && steps.disk.outputs.mnt-available == 'true' with: mounts: ${{ inputs.bind-mounts }} - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: inputs.diskspace-hack || steps.disk.outputs.mnt-available != 'true' with: to_remove: ${{ inputs.diskspace-hack-paths }} @@ -271,7 +271,7 @@ jobs: mount df -h - - uses: envoyproxy/toolshed/actions/bson@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/bson@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Configure Docker if: runner.os == 'Linux' with: @@ -287,7 +287,7 @@ jobs: | "${{ inputs.template-docker-configure }}" # Caches - - uses: envoyproxy/toolshed/actions/cache/restore@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: >- fromJSON(inputs.bazel-cache) name: >- @@ -328,12 +328,12 @@ jobs: key: ${{ inputs.cache-build-image }}${{ inputs.cache-build-image-key-suffix }} - if: ${{ inputs.cache-build-image && steps.cache-lookup.outputs.cache-hit == 'true' }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/actions/docker/cache/restore@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/docker/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: image-tag: ${{ inputs.cache-build-image }} key-suffix: ${{ inputs.cache-build-image-key-suffix }} - - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: appauth name: Appauth if: ${{ inputs.trusted }} @@ -344,7 +344,7 @@ jobs: # - the workaround is to allow the token to be passed through. token: ${{ github.token }} token-ok: true - - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout name: Checkout Envoy repository with: @@ -360,7 +360,7 @@ jobs: token: ${{ inputs.trusted && steps.appauth.outputs.token || github.token }} # This is currently only use by mobile-docs and can be removed once they are updated to the newer website - - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout-extra name: Checkout extra repository (for publishing) if: ${{ inputs.checkout-extra }} @@ -369,7 +369,7 @@ jobs: ssh-key: ${{ inputs.trusted && inputs.ssh-key-extra || '' }} - name: Import GPG key - uses: envoyproxy/toolshed/actions/gpg/import@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/gpg/import@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: ${{ inputs.import-gpg }} with: key: ${{ secrets.gpg-key }} @@ -406,7 +406,7 @@ jobs: # NOTE: This is where untrusted code can be run!!! # Only post-failure notification steps (which don't use any repo code) should follow this step. - - uses: envoyproxy/toolshed/actions/github/run@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: ci-run name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: diff --git a/.github/workflows/_upload_gcs.yml b/.github/workflows/_upload_gcs.yml index 1e156e414440f..556c3ec314567 100644 --- a/.github/workflows/_upload_gcs.yml +++ b/.github/workflows/_upload_gcs.yml @@ -29,17 +29,17 @@ jobs: with: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} - - uses: envoyproxy/toolshed/actions/jq@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: metadata with: input: ${{ matrix.artifact }}/gcs-metadata.json input-format: json-path - run: | rm -rf ${{ matrix.artifact }}/gcs-metadata.json - - uses: envoyproxy/toolshed/actions/gcp/setup@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: key: ${{ secrets.gcp-key }} - - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: bucket: ${{ fromJSON(steps.metadata.outputs.value).bucket }} path: ${{ matrix.artifact }} diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index d0ea7bef34c1c..5baf6be36d540 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -28,7 +28,7 @@ jobs: steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: | ! github.event.repository.private with: @@ -43,7 +43,7 @@ jobs: if: | env.BUILD_TARGETS != '' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index d3e8fced3ae07..932cb66523f26 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-22.04 if: github.repository == 'envoyproxy/envoy' steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: ! github.event.repository.private with: mounts: | @@ -64,7 +64,7 @@ jobs: if: | env.CPP_CHANGED == 'true' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/command.yml b/.github/workflows/command.yml index 779caf34b296d..bded5e0902aaa 100644 --- a/.github/workflows/command.yml +++ b/.github/workflows/command.yml @@ -28,7 +28,7 @@ jobs: && github.actor != 'dependabot[bot]' }} steps: - - uses: envoyproxy/toolshed/actions/github/command@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/command@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Parse command from comment id: command with: @@ -37,14 +37,14 @@ jobs: ^/(retest) # /retest - - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: ${{ steps.command.outputs.command == 'retest' }} id: appauth-retest name: Appauth (retest) with: key: ${{ secrets.ENVOY_CI_APP_KEY }} app_id: ${{ secrets.ENVOY_CI_APP_ID }} - - uses: envoyproxy/toolshed/actions/retest@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/retest@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 if: ${{ steps.command.outputs.command == 'retest' }} name: Retest with: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 4e51214d6e813..41f5259d91acd 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -53,16 +53,16 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/bson@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/bson@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: update name: Update dependency (${{ inputs.dependency }}) with: @@ -97,13 +97,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/actions/upload/diff@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/upload/diff@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.update.outputs.output }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/actions/github/pr@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: base: main body: | @@ -134,11 +134,11 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: checkout name: Checkout Envoy repository with: @@ -191,7 +191,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/actions/docker/shas@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/docker/shas@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: images: | sha-ci: docker.io/envoyproxy/envoy-build:ci-${{ steps.build-tools.outputs.tag }} @@ -229,7 +229,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 094352cbeda7c..319eae3a9ce01 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -60,14 +60,14 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} @@ -88,10 +88,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/actions/email/validate@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/email/validate@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/actions/github/run@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Create release with: source: | @@ -116,7 +116,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: base: ${{ github.ref_name }} commit: false @@ -142,19 +142,19 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Re-open branch with: command: >- @@ -169,7 +169,7 @@ jobs: name: Dev version id: dev - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: base: ${{ github.ref_name }} commit: false @@ -193,20 +193,20 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 name: Sync version histories with: command: >- @@ -216,7 +216,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: append-commit-message: true base: ${{ github.ref_name }} @@ -243,13 +243,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: config: | fetch-depth: 0 @@ -279,13 +279,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - name: Checkout repository - uses: envoyproxy/toolshed/actions/github/checkout@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index ac742ea29af01..887d1b2d94c1d 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -43,13 +43,13 @@ jobs: echo "skip=true" >> "$GITHUB_OUTPUT" fi - if: steps.tip.outputs.skip != 'true' - uses: envoyproxy/toolshed/actions/appauth@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - if: steps.tip.outputs.skip != 'true' - uses: envoyproxy/toolshed/actions/dispatch@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + uses: envoyproxy/toolshed/actions/dispatch@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index c7c1ec34dc4ff..b86db3c471b7e 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -107,9 +107,9 @@ jobs: target: kotlin-hello-world runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: apk: bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity @@ -152,7 +152,7 @@ jobs: target: ${{ matrix.target }} runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 steps-post: ${{ matrix.steps-post }} timeout-minutes: 50 trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} @@ -163,7 +163,7 @@ jobs: include: - name: java-hello-world steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: apk: bazel-bin/examples/java/hello_world/hello_envoy.apk app: io.envoyproxy.envoymobile.helloenvoy/.MainActivity @@ -186,7 +186,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/baseline:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: apk: bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity @@ -203,7 +203,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/experimental:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 with: apk: bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index f8ef0b05c9270..15a64d00a0215 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -103,7 +103,7 @@ jobs: # source ./ci/mac_ci_setup.sh # bazel shutdown # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} @@ -148,7 +148,7 @@ jobs: # source: | # source ./ci/mac_ci_setup.sh # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@7e03a14db2f1a03ef4a272b3a01d8006c1d99c43 # actions-v0.4.14 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} From c751c60c9c4a51eff25ba8a042e13ccc5ff670a4 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 May 2026 20:33:46 +0100 Subject: [PATCH 272/750] ci/sync: Disable envoy-filter-example (#44959) due to brown out, probs best we leave disabled Signed-off-by: Ryan Northey --- .github/workflows/envoy-sync.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index 887d1b2d94c1d..eee736da174c9 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -26,7 +26,6 @@ jobs: matrix: downstream: - go-control-plane - - envoy-filter-example - data-plane-api - mobile-website steps: From c31462b7aa5f87de2f786665cd5e9d0038546fdb Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 May 2026 21:41:54 +0100 Subject: [PATCH 273/750] repo: Minor README cleanups (#44961) Signed-off-by: Ryan Northey --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 65b72123f778f..0d3c209801eba 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,6 @@ involved and how Envoy plays a role, read the CNCF [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1266/badge)](https://bestpractices.coreinfrastructure.org/projects/1266) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/envoyproxy/envoy/badge)](https://securityscorecards.dev/viewer/?uri=github.com/envoyproxy/envoy) [![CLOMonitor](https://img.shields.io/endpoint?url=https://clomonitor.io/api/projects/cncf/envoy/badge)](https://clomonitor.io/projects/cncf/envoy) -[![Azure Pipelines](https://dev.azure.com/cncf/envoy/_apis/build/status/11?branchName=main)](https://dev.azure.com/cncf/envoy/_build/latest?definitionId=11&branchName=main) -[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/envoy.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:envoy) -[![Jenkins](https://powerci.osuosl.org/buildStatus/icon?job=build-envoy-static-master&subject=ppc64le%20build)](https://powerci.osuosl.org/job/build-envoy-static-master/) -[![Jenkins](https://ibmz-ci.osuosl.org/buildStatus/icon?job=Envoy_IBMZ_CI&subject=s390x%20build)](https://ibmz-ci.osuosl.org/job/Envoy_IBMZ_CI/) ## Documentation @@ -94,10 +90,20 @@ Alternatively, you may email [envoy-security@googlegroups.com](mailto:envoy-secu For further details please see our complete [security release process](SECURITY.md). +### OSS fuzzing + +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/envoy.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:envoy) + ### ppc64le builds Builds for the ppc64le architecture or using aws-lc are not covered by the envoy security policy. The ppc64le architecture is currently best-effort and not maintained by the Envoy maintainers. +[![Jenkins](https://powerci.osuosl.org/buildStatus/icon?job=build-envoy-static-master&subject=ppc64le%20build)](https://powerci.osuosl.org/job/build-envoy-static-master/) + +### Other builds + +[![Jenkins](https://ibmz-ci.osuosl.org/buildStatus/icon?job=Envoy_IBMZ_CI&subject=s390x%20build)](https://ibmz-ci.osuosl.org/job/Envoy_IBMZ_CI/) + ## Releases For further details please see our [release process](https://github.com/envoyproxy/envoy/blob/main/RELEASES.md). From cd9fafaae805445114bcfae9c8eeeebfb7aa63b7 Mon Sep 17 00:00:00 2001 From: code Date: Sat, 9 May 2026 09:25:16 +0800 Subject: [PATCH 274/750] tool: improve the refresh_compdb and clang-tidy (#44942) Commit Message: tool: improve the refresh_compdb and clang-tidy Additional Description: 1. now the refresh_compdb needn't to hack option because the build_setup.sh could be used to set base directory correctly based on CI target. 2. add a tool to clang-tidy to generate the merged clang-tidy-fixes.yaml which should be pretty helpful for us to address clang tidy problem with AI's help. Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: wbpcode/wangbaiping Signed-off-by: code Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- ci/README.md | 2 +- ci/build_setup.sh | 3 + ci/do_ci.sh | 35 ++-- tools/clang-tidy/BUILD | 12 ++ tools/clang-tidy/collect_fixes.py | 262 ++++++++++++++++++++++++++++++ 5 files changed, 290 insertions(+), 24 deletions(-) create mode 100644 tools/clang-tidy/collect_fixes.py diff --git a/ci/README.md b/ci/README.md index bda32113f52e5..cbbb967df9604 100644 --- a/ci/README.md +++ b/ci/README.md @@ -187,7 +187,7 @@ The `./ci/run_envoy_docker.sh './ci/do_ci.sh '` targets are: * `fuzz ` — build and run a specified fuzz test or test dir under `-c dbg --config=asan-fuzzer` with clang. If specifying a single fuzz test, must use the full target name with "_with_libfuzzer" for ``. * `compile_time_options` — build Envoy and run tests with various compile-time options toggled to their non-default state, to ensure they still build. * `compile_time_options ` — build Envoy and run a specified test or test dir with various compile-time options toggled to their non-default state, to ensure they still build. -* `clang_tidy ` — build and run clang-tidy specified source files, if no files specified, runs against the diff with the last GitHub commit. +* `clang-tidy ` — build and run clang-tidy on the specified Bazel targets. If no targets are provided, it uses `CLANG_TIDY_TARGETS` when set, otherwise the default target set. * `check_proto_format`— check configuration, formatting and build issues in API proto files. * `fix_proto_format`— fix configuration, formatting and build issues in API proto files. * `check_and_fix_proto_format` — check and fix configuration, fomatting and build issues in API proto files. diff --git a/ci/build_setup.sh b/ci/build_setup.sh index 4bd4b6fe9e10d..81c418340d090 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -82,6 +82,9 @@ case $CI_TARGET in external) ENVOY_OUTPUT_BASE_DIR="${ENVOY_OUTPUT_BASE_DIR:-external}" ;; + refresh_compdb|pre_refresh_compdb) + ENVOY_OUTPUT_BASE_DIR="${ENVOY_OUTPUT_BASE_DIR:-base-envoy-compdb}" + ;; *) ENVOY_OUTPUT_BASE_DIR="${ENVOY_OUTPUT_BASE_DIR:-base}" ;; diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 196b332f9d949..06c5a80f7c307 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -420,8 +420,10 @@ case $CI_TARGET in export FIX_YAML="${ENVOY_TEST_TMPDIR}/lint-fixes/clang-tidy-fixes.yaml" export CLANG_TIDY_APPLY_FIXES=1 mkdir -p "${ENVOY_TEST_TMPDIR}/lint-fixes" - if [[ -n "$CLANG_TIDY_TARGETS" ]]; then - read -ra CLANG_TIDY_TARGETS <<< "${CLANG_TIDY_TARGETS}" + if [[ $# -ge 1 ]]; then + CLANG_TIDY_TARGETS=("$@") + elif [[ -n "${CLANG_TIDY_TARGETS[*]}" ]]; then + read -ra CLANG_TIDY_TARGETS <<< "${CLANG_TIDY_TARGETS[*]}" else CLANG_TIDY_TARGETS=( //contrib/... @@ -434,6 +436,14 @@ case $CI_TARGET in "${BAZEL_BUILD_OPTIONS[@]}" \ --config=clang-tidy \ "${CLANG_TIDY_TARGETS[@]}" + echo "Collecting clang-tidy fixes into ${ENVOY_SRCDIR}/clang-tidy-fixes.yaml" + bazel run \ + "${BAZEL_BUILD_OPTIONS[@]}" \ + //tools/clang-tidy:collect_fixes \ + -- \ + --repository="envoy" \ + --output="${ENVOY_SRCDIR}/clang-tidy-fixes.yaml" \ + "${CLANG_TIDY_TARGETS[@]}" ;; clean|expunge) @@ -1047,17 +1057,6 @@ case $CI_TARGET in refresh_compdb) setup_clang_toolchain - # Override the BAZEL_STARTUP_OPTIONS to setting different output directory. - # So the compdb headers won't be overwritten by another bazel run. - for i in "${!BAZEL_STARTUP_OPTIONS[@]}"; do - if [[ ${BAZEL_STARTUP_OPTIONS[i]} == "--output_base"* ]]; then - COMPDB_OUTPUT_BASE="${BAZEL_STARTUP_OPTIONS[i]}"-envoy-compdb - BAZEL_STARTUP_OPTIONS[i]="${COMPDB_OUTPUT_BASE}" - BAZEL_STARTUP_OPTION_LIST="${BAZEL_STARTUP_OPTIONS[*]}" - export BAZEL_STARTUP_OPTION_LIST - fi - done - if [[ -z "${SKIP_PROTO_FORMAT}" ]]; then "${CURRENT_SCRIPT_DIR}/../tools/proto_format/proto_format.sh" fix fi @@ -1072,16 +1071,6 @@ case $CI_TARGET in pre_refresh_compdb) setup_clang_toolchain - # Override the BAZEL_STARTUP_OPTIONS to setting different output directory. - # So the compdb headers won't be overwritten by another bazel run. - for i in "${!BAZEL_STARTUP_OPTIONS[@]}"; do - if [[ ${BAZEL_STARTUP_OPTIONS[i]} == "--output_base"* ]]; then - COMPDB_OUTPUT_BASE="${BAZEL_STARTUP_OPTIONS[i]}"-envoy-compdb - BAZEL_STARTUP_OPTIONS[i]="${COMPDB_OUTPUT_BASE}" - BAZEL_STARTUP_OPTION_LIST="${BAZEL_STARTUP_OPTIONS[*]}" - export BAZEL_STARTUP_OPTION_LIST - fi - done # Ensure that LLVM toolchain is downloaded by using clangd target. # This is used during devcontainer bootstrap. bazel build @llvm_toolchain//:clangd diff --git a/tools/clang-tidy/BUILD b/tools/clang-tidy/BUILD index 3ecbe4142a78f..c1af8d6a88409 100644 --- a/tools/clang-tidy/BUILD +++ b/tools/clang-tidy/BUILD @@ -1,3 +1,6 @@ +load("@base_pip3//:requirements.bzl", "requirement") +load("@envoy_repo//:path.bzl", "PATH") +load("@rules_python//python:defs.bzl", "py_binary") load(":clang_tidy.bzl", "clang_tidy") licenses(["notice"]) # Apache 2 @@ -7,3 +10,12 @@ clang_tidy( # TODO: make this work with toolchain_roots target = "@llvm_toolchain_llvm//:clang-tidy", ) + +py_binary( + name = "collect_fixes", + srcs = ["collect_fixes.py"], + args = ["--workspace_root=%s" % PATH], + main = "collect_fixes.py", + visibility = ["//visibility:public"], + deps = [requirement("pyyaml")], +) diff --git a/tools/clang-tidy/collect_fixes.py b/tools/clang-tidy/collect_fixes.py new file mode 100644 index 0000000000000..f668544aabcc2 --- /dev/null +++ b/tools/clang-tidy/collect_fixes.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 + +import argparse +import os +from pathlib import Path +import re +import shlex +import subprocess +import sys + +import yaml + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Collect clang-tidy YAML fix files for specific Bazel targets and merge them into " + "a single clang-tidy-fixes.yaml file at the workspace root." + ) + ) + parser.add_argument( + "--workspace_root", + type=Path, + default=Path.cwd(), + help="Workspace root where clang-tidy-fixes.yaml will be written.", + ) + parser.add_argument( + "--output", + type=Path, + help="Optional explicit output path. Defaults to /clang-tidy-fixes.yaml.", + ) + parser.add_argument( + "--repository", + help="Bazel repository name for the current workspace, for example 'envoy'.", + ) + parser.add_argument( + "targets", + nargs="+", + help=( + "Bazel targets or target patterns to collect, for example " + "//source/common/stats:symbol_table_lib or //source/common/..." + ), + ) + return parser.parse_args() + + +def get_bazel_startup_options() -> list[str]: + return shlex.split(os.environ.get("BAZEL_STARTUP_OPTION_LIST", "")) + + +def get_bazel_build_options() -> list[str]: + return shlex.split(os.environ.get("BAZEL_BUILD_OPTION_LIST", "")) + + +def get_output_base(workspace_root: Path) -> Path: + return Path( + subprocess.check_output( + [ + "bazel", + *get_bazel_startup_options(), + "info", + *get_bazel_build_options(), + "output_base", + ], + text=True, + cwd=workspace_root, + ).strip() + ) + + +def search_roots(workspace_root: Path) -> list[Path]: + # Use 'bazel info bazel-bin' to get the correct output directory for the current architecture and configuration. + bazel_bin = subprocess.check_output( + [ + "bazel", + *get_bazel_startup_options(), + "info", + *get_bazel_build_options(), + "bazel-bin", + ], + text=True, + cwd=workspace_root, + ).strip() + return [Path(bazel_bin)] + + +def parse_target(target: str) -> tuple[str | None, str, str | None, bool]: + repository = None + if target.startswith("@"): + repository, separator, target = target[1:].partition("//") + if not separator: + raise ValueError(f"Unsupported Bazel target: {target}") + target = f"//{target}" + + if not target.startswith("//"): + raise ValueError(f"Unsupported Bazel target: {target}") + + label = target[2:] + if label == "...": + return repository, "", None, True + + if label.endswith("/..."): + return repository, label[:-4], None, True + + if ":" in label: + package_path, target_name = label.split(":", 1) + if target_name in ("all", "*"): + return repository, package_path, None, False + return repository, package_path, target_name, False + + return repository, label, None, False + + +def target_patterns(target: str, repository_name: str | None) -> list[str]: + repository, package_path, target_name, recursive = parse_target(target) + escaped_package_path = package_path.strip("/") + target_root = escaped_package_path + tidy_root = escaped_package_path + + if repository is not None and repository != repository_name: + # External targets such as @envoy_api//bazel/foo are emitted under: + # external/envoy_api/bazel/foo/bazel_clang_tidy_external/envoy_api/bazel/foo/ + target_root = f"external/{repository}/{escaped_package_path}" if escaped_package_path else f"external/{repository}" + tidy_root = target_root + + if recursive: + # //source/common/... -> source/common/**/bazel_clang_tidy_*/**/*.clang-tidy.yaml + # This keeps the search inside the requested subtree while still allowing nested packages. + prefix = f"{target_root}/" if target_root else "" + return [f"{prefix}**/bazel_clang_tidy_*/**/*.clang-tidy.yaml"] + + # A package such as //source/common/stats is emitted under: + # source/common/stats/bazel_clang_tidy_source/common/stats/ + # The aspect mirrors the package path after the bazel_clang_tidy_ prefix. + # External packages use the same convention, but rooted under external//... + tidy_dir = ( + f"{target_root}/bazel_clang_tidy_{tidy_root}" + if target_root + else "bazel_clang_tidy" + ) + if target_name is None: + # //pkg or //pkg:all -> collect every clang-tidy YAML produced for that package. + return [f"{tidy_dir}/**/*.clang-tidy.yaml"] + + # //pkg:lib -> only YAML files emitted for that target, e.g. + # foo.cc.lib.clang-tidy.yaml + return [f"{tidy_dir}/**/*.{target_name}.clang-tidy.yaml"] + + +def collect_fix_files( + roots: list[Path], targets: list[str], repository_name: str | None +) -> list[Path]: + seen: set[Path] = set() + results: list[Path] = [] + + for root in roots: + if not root.exists(): + continue + for target in targets: + for pattern in target_patterns(target, repository_name): + for candidate in sorted(root.glob(pattern)): + resolved = candidate.resolve() + if resolved in seen: + continue + seen.add(resolved) + results.append(candidate) + return results + + +def external_repository_from_path(path: str) -> str | None: + match = re.search(r"(^|/)external/([^/]+)/", path) + if match is None: + return None + + return match.group(2) + + +def is_fixable_path(path: str, repository_name: str | None) -> bool: + external_repository = external_repository_from_path(path) + if external_repository is None: + return True + + return external_repository in {"envoy_api", repository_name} + + +def filter_document_diagnostics(document: dict, repository_name: str | None) -> dict | None: + diagnostics = document.get("Diagnostics") + if not isinstance(diagnostics, list): + return None + + filtered_diagnostics = [] + for diagnostic in diagnostics: + if not isinstance(diagnostic, dict): + continue + diagnostic_message = diagnostic.get("DiagnosticMessage") + if not isinstance(diagnostic_message, dict): + continue + file_path = diagnostic_message.get("FilePath") + if not isinstance(file_path, str): + continue + if is_fixable_path(file_path, repository_name): + filtered_diagnostics.append(diagnostic) + + if not filtered_diagnostics: + return None + + filtered_document = dict(document) + filtered_document["Diagnostics"] = filtered_diagnostics + return filtered_document + + +def merge_file_contents(fix_files: list[Path], repository_name: str | None) -> str: + merged_documents = [] + for fix_file in fix_files: + with fix_file.open(encoding="utf-8") as stream: + for document in yaml.safe_load_all(stream): + if not isinstance(document, dict): + continue + filtered_document = filter_document_diagnostics(document, repository_name) + if filtered_document is not None: + merged_documents.append(filtered_document) + + if not merged_documents: + return "" + + return yaml.safe_dump_all(merged_documents, explicit_start=True, sort_keys=False) + + +def main() -> int: + args = parse_args() + workspace_root = args.workspace_root.resolve() + output = args.output.resolve() if args.output else workspace_root / "clang-tidy-fixes.yaml" + + try: + roots = search_roots(workspace_root) + fix_files = collect_fix_files(roots, args.targets, args.repository) + except subprocess.CalledProcessError as error: + print(f"Failed to query Bazel output_base: {error}", file=sys.stderr) + return 1 + except ValueError as error: + print(str(error), file=sys.stderr) + return 1 + + if not fix_files: + searched = ", ".join(str(root) for root in roots) + requested = ", ".join(args.targets) + print( + f"No clang-tidy YAML files found for targets [{requested}] under: {searched}", + file=sys.stderr, + ) + return 1 + + merged = merge_file_contents(fix_files, args.repository) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(merged, encoding="utf-8") + + print(f"Wrote {output} from {len(fix_files)} clang-tidy YAML files.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 3ddecad194fc2ac5a6bd3559f2d03b94df5330b6 Mon Sep 17 00:00:00 2001 From: Ethan Truong Date: Sat, 9 May 2026 11:07:31 -0400 Subject: [PATCH 275/750] ext_authz: Improve ext_authz code coverage (#44900) | Component / File | Baseline Coverage (Before) | New Coverage (After) | | - | - | - | | source/extensions/filters/common/ext_authz | 99.7% (679/681 lines) | 100% (681/681 lines) | | source/extensions/filters/http/ext_authz | 96.7% (1287/1331 lines) | 99.5% (1324/1331 lines) | | source/extensions/filters/network/ext_authz | 98% (197/201 lines) | 100% (201/201 lines) | Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Ethan Truong --- .../ext_authz/ext_authz_http_impl_test.cc | 215 +++++++++ .../filters/http/ext_authz/ext_authz_test.cc | 412 ++++++++++++++++++ .../network/ext_authz/ext_authz_test.cc | 26 ++ 3 files changed, 653 insertions(+) diff --git a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc index 767c68cde2002..2ac1724aaa324 100644 --- a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc +++ b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc @@ -967,6 +967,221 @@ TEST_F(ExtAuthzHttpClientTest, MultipleSetCookieHeadersOnSuccess) { client_->onSuccess(async_request_, std::move(message_response)); } +TEST_F(ExtAuthzHttpClientTest, AppendMatchers) { + const std::string yaml = R"EOF( + http_service: + server_uri: + uri: "ext_authz:9000" + cluster: "ext_authz" + timeout: 0.25s + authorization_response: + allowed_upstream_headers_to_append: + patterns: + - exact: "x-append-me" + )EOF"; + + initialize(yaml); + + auto message_response = std::make_unique( + Http::createHeaderMap( + {{Http::LowerCaseString(":status"), "200"}, + {Http::LowerCaseString("x-append-me"), "value1"}})); + + Response expected_response = TestCommon::makeAuthzResponse(CheckStatus::OK, Http::Code::OK); + expected_response.headers_to_add = {{"x-append-me", "value1"}}; + + envoy::service::auth::v3::CheckRequest request; + client_->check(request_callbacks_, request, parent_span_, stream_info_); + + EXPECT_CALL(request_callbacks_, + onComplete_(WhenDynamicCastTo(AuthzOkResponse(expected_response)))); + client_->onSuccess(async_request_, std::move(message_response)); +} + +TEST_F(ExtAuthzHttpClientTest, PathPrefix) { + const std::string yaml = R"EOF( + http_service: + server_uri: + uri: "ext_authz:9000" + cluster: "ext_authz" + timeout: 0.25s + path_prefix: "/prefix" + )EOF"; + + initialize(yaml); + + // Explicitly set up getThreadLocalCluster. + ON_CALL(cm_, getThreadLocalCluster(absl::string_view("ext_authz"))) + .WillByDefault(Return(&cm_.thread_local_cluster_)); + + envoy::service::auth::v3::CheckRequest request; + auto* http_request = request.mutable_attributes()->mutable_request()->mutable_http(); + (*http_request->mutable_headers())[":path"] = "/original"; + + EXPECT_CALL(async_client_, send_(_, _, _)) + .WillOnce(Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks&, + const Http::AsyncClient::RequestOptions&) { + EXPECT_EQ("/prefix/original", message->headers().getPathValue()); + return &async_request_; + })); + + client_->check(request_callbacks_, request, parent_span_, stream_info_); + + // Complete the request. + Http::ResponseMessagePtr response_message( + new Http::ResponseMessageImpl(Http::createHeaderMap( + {{Http::LowerCaseString(":status"), "200"}}))); + EXPECT_CALL(request_callbacks_, onComplete_(_)); + client_->onSuccess(async_request_, std::move(response_message)); +} + +TEST_F(ExtAuthzHttpClientTest, PathOverrideReal) { + const std::string yaml = R"EOF( + http_service: + server_uri: + uri: "ext_authz:9000" + cluster: "ext_authz" + timeout: 0.25s + )EOF"; + + initialize(yaml); + + // Explicitly set up getThreadLocalCluster. + ON_CALL(cm_, getThreadLocalCluster(absl::string_view("ext_authz"))) + .WillByDefault(Return(&cm_.thread_local_cluster_)); + + envoy::service::auth::v3::CheckRequest request; + auto* http_request = request.mutable_attributes()->mutable_request()->mutable_http(); + (*http_request->mutable_headers())[":path"] = "/original"; + + EXPECT_CALL(async_client_, send_(_, _, _)) + .WillOnce(Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks&, + const Http::AsyncClient::RequestOptions&) { + EXPECT_EQ("/original", message->headers().getPathValue()); + return &async_request_; + })); + + client_->check(request_callbacks_, request, parent_span_, stream_info_); + + // Complete the request. + Http::ResponseMessagePtr response_message( + new Http::ResponseMessageImpl(Http::createHeaderMap( + {{Http::LowerCaseString(":status"), "200"}}))); + EXPECT_CALL(request_callbacks_, onComplete_(_)); + client_->onSuccess(async_request_, std::move(response_message)); +} + +TEST_F(ExtAuthzHttpClientTest, PathTransformationsWithEncodeRawHeaders) { + const std::string yaml = R"EOF( + http_service: + server_uri: + uri: "ext_authz:9000" + cluster: "ext_authz" + timeout: 0.25s + encode_raw_headers: true + )EOF"; + + initialize(yaml); + + // Explicitly set up getThreadLocalCluster. + ON_CALL(cm_, getThreadLocalCluster(absl::string_view("ext_authz"))) + .WillByDefault(Return(&cm_.thread_local_cluster_)); + + // Test case 1: Path prefix. + { + envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config{}; + TestUtility::loadFromYaml(yaml, proto_config); + // Don't set path_prefix in proto_config, but pass it to constructor (simulating ext_authz.cc + // behavior). + auto config = std::make_shared(proto_config, 250, "/prefix", factory_context_); + auto client = std::make_unique(cm_, config); + + envoy::service::auth::v3::CheckRequest request; + auto* http_request = request.mutable_attributes()->mutable_request()->mutable_http(); + auto* header_map = http_request->mutable_header_map(); + auto* header = header_map->add_headers(); + header->set_key(":path"); + header->set_raw_value("/original"); + + EXPECT_CALL(async_client_, send_(_, _, _)) + .WillOnce(Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks&, + const Http::AsyncClient::RequestOptions&) { + EXPECT_EQ("/prefix/original", message->headers().getPathValue()); + return &async_request_; + })); + + client->check(request_callbacks_, request, parent_span_, stream_info_); + + // Cleanup. + EXPECT_CALL(request_callbacks_, onComplete_(_)); + client->onSuccess(async_request_, std::make_unique( + Http::createHeaderMap( + {{Http::LowerCaseString(":status"), "200"}}))); + } + + // Test case 2: Path override. + { + envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config{}; + TestUtility::loadFromYaml(yaml, proto_config); + proto_config.mutable_http_service()->set_path_override("/override"); + // Pass empty path_prefix to avoid conflict with path_override. + auto config = std::make_shared(proto_config, 250, "", factory_context_); + auto client = std::make_unique(cm_, config); + + envoy::service::auth::v3::CheckRequest request; + auto* http_request = request.mutable_attributes()->mutable_request()->mutable_http(); + auto* header_map = http_request->mutable_header_map(); + auto* header = header_map->add_headers(); + header->set_key(":path"); + header->set_raw_value("/original"); + + EXPECT_CALL(async_client_, send_(_, _, _)) + .WillOnce(Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks&, + const Http::AsyncClient::RequestOptions&) { + EXPECT_EQ("/override", message->headers().getPathValue()); + return &async_request_; + })); + + client->check(request_callbacks_, request, parent_span_, stream_info_); + + // Cleanup. + EXPECT_CALL(request_callbacks_, onComplete_(_)); + client->onSuccess(async_request_, std::make_unique( + Http::createHeaderMap( + {{Http::LowerCaseString(":status"), "200"}}))); + } + + // Test case 3: No transformation. + { + envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config{}; + TestUtility::loadFromYaml(yaml, proto_config); + auto config = std::make_shared(proto_config, 250, "", factory_context_); + auto client = std::make_unique(cm_, config); + + envoy::service::auth::v3::CheckRequest request; + auto* http_request = request.mutable_attributes()->mutable_request()->mutable_http(); + auto* header_map = http_request->mutable_header_map(); + auto* header = header_map->add_headers(); + header->set_key(":path"); + header->set_raw_value("/original"); + + EXPECT_CALL(async_client_, send_(_, _, _)) + .WillOnce(Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks&, + const Http::AsyncClient::RequestOptions&) { + EXPECT_EQ("/original", message->headers().getPathValue()); + return &async_request_; + })); + + client->check(request_callbacks_, request, parent_span_, stream_info_); + + // Cleanup. + EXPECT_CALL(request_callbacks_, onComplete_(_)); + client->onSuccess(async_request_, std::make_unique( + Http::createHeaderMap( + {{Http::LowerCaseString(":status"), "200"}}))); + } +} + } // namespace } // namespace ExtAuthz } // namespace Common diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index 8ee5de2d7fdb9..56fc0012413b4 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -1703,6 +1703,89 @@ TEST_F(HttpFilterTest, ErrorResponseHeaderLimitsEnforcedWithMock) { EXPECT_GT(config_->stats().omitted_response_headers_.value(), 0); } +// Test that error response headers are limited in headers_to_set when the limit is hit. +TEST_F(HttpFilterTest, ErrorResponseHeaderLimitsEnforcedInSet) { + InSequence s; + + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + failure_mode_allow: false + enforce_response_header_limits: true + )EOF"); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::Error; + response.status_code = Http::Code::InternalServerError; + response.body = "{\"error\": \"service error\"}"; + // Add 3 headers to set. + response.headers_to_set.push_back({"x-error-1", "value1"}); + response.headers_to_set.push_back({"x-error-2", "value2"}); + response.headers_to_set.push_back({"x-error-3", "value3"}); + + prepareCheck(); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { + callbacks.onComplete(std::make_unique(response)); + })); + + EXPECT_CALL(decoder_filter_callbacks_, sendLocalReply(_, _, _, _, _)) + .WillOnce( + Invoke([&](Http::Code, absl::string_view, + std::function modify_headers, + const absl::optional, absl::string_view) -> void { + // Create a ResponseHeaderMap with a limit of 1 to trigger the break in headers_to_set. + Http::TestResponseHeaderMapImpl response_headers({}, 99999, /*max_headers_count=*/1); + if (modify_headers) { + modify_headers(response_headers); + } + // With a limit of 1, we should only have 1 header added. + EXPECT_EQ(response_headers.size(), 1); + EXPECT_TRUE(response_headers.has("x-error-1")); + EXPECT_FALSE(response_headers.has("x-error-2")); + EXPECT_FALSE(response_headers.has("x-error-3")); + })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + EXPECT_EQ(1U, config_->stats().error_.value()); + // Verify that omitted_response_headers_ stat was incremented. + EXPECT_GT(config_->stats().omitted_response_headers_.value(), 0); +} + +// Test for ExtAuthzLoggingInfo clear methods. +TEST(ExtAuthzLoggingInfoTest, ClearMethods) { + ExtAuthzLoggingInfo logging_info(absl::nullopt); + logging_info.setLatency(std::chrono::microseconds(100)); + logging_info.setBytesSent(10); + logging_info.setBytesReceived(20); + logging_info.setClusterInfo(std::make_shared>()); + logging_info.setUpstreamHost(std::make_shared>()); + + EXPECT_TRUE(logging_info.latency().has_value()); + EXPECT_TRUE(logging_info.bytesSent().has_value()); + EXPECT_TRUE(logging_info.bytesReceived().has_value()); + EXPECT_NE(nullptr, logging_info.clusterInfo()); + EXPECT_NE(nullptr, logging_info.upstreamHost()); + + logging_info.clearLatency(); + logging_info.clearBytesSent(); + logging_info.clearBytesReceived(); + logging_info.clearClusterInfo(); + logging_info.clearUpstreamHost(); + + EXPECT_FALSE(logging_info.latency().has_value()); + EXPECT_FALSE(logging_info.bytesSent().has_value()); + EXPECT_FALSE(logging_info.bytesReceived().has_value()); + EXPECT_EQ(nullptr, logging_info.clusterInfo()); + EXPECT_EQ(nullptr, logging_info.upstreamHost()); +} + // Test that error response headers are limited in headers_to_append when the limit is hit. TEST_F(HttpFilterTest, ErrorResponseHeaderLimitsEnforcedInAppend) { InSequence s; @@ -6663,6 +6746,40 @@ TEST_F(HttpFilterTest, ShadowModeErrorSetsFilterStateAndContinues) { EXPECT_EQ(1U, config_->stats().error_.value()); } +// Verifies that shadow mode correctly captures a custom status code on error. +TEST_F(HttpFilterTest, ShadowModeErrorWithCustomStatusCode) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + shadow_mode: true + )EOF"); + + prepareCheck(); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::Error; + response.status_code = Http::Code::ServiceUnavailable; + request_callbacks_->onComplete(std::make_unique(response)); + + const auto* shadow = + decoder_filter_callbacks_.streamInfo().filterState()->getDataReadOnly( + kShadowFilterStateKey); + ASSERT_NE(shadow, nullptr); + EXPECT_EQ(shadow->statusCode(), Http::Code::ServiceUnavailable); +} + // Verify that in shadow mode an OK response sets FilterState and continues as normal. TEST_F(HttpFilterTest, ShadowModeOkSetsFilterState) { InSequence s; @@ -6966,6 +7083,301 @@ TEST_F(HttpFilterTest, ShadowModeWithRequestBody) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); } +// Verifies that encode1xxHeaders returns Continue. +TEST_F(HttpFilterTest, Encode1xxHeaders) { + initialize(getFilterConfig(false, false)); + Http::TestResponseHeaderMapImpl headers{{":status", "100"}}; + EXPECT_EQ(Http::Filter1xxHeadersStatus::Continue, filter_->encode1xxHeaders(headers)); +} + +// Verifies that per-route gRPC client creation logic is hit. +TEST_F(HttpFilterTest, CreatePerRouteGrpcClientWithServerContext) { + initialize(getFilterConfig(false, false)); + prepareCheck(); + + // Create per-route configuration with gRPC service override. + envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; + auto* grpc_service = per_route_config.mutable_check_settings()->mutable_grpc_service(); + grpc_service->mutable_envoy_grpc()->set_cluster_name("per_route_cluster"); + + FilterConfigPerRoute per_route_filter_config(per_route_config); + ON_CALL(decoder_filter_callbacks_, perFilterConfigs()) + .WillByDefault(Return(Router::RouteSpecificFilterConfigs{&per_route_filter_config})); + + // Initialize filter with server context. + auto new_client = std::make_unique(); + auto test_filter = std::make_unique(config_, std::move(new_client), factory_context_); + test_filter->setDecoderFilterCallbacks(decoder_filter_callbacks_); + + // Mock cluster manager to return a client. + auto mock_async_client = std::make_shared(); + EXPECT_CALL(factory_context_.cluster_manager_.async_client_manager_, + getOrCreateRawAsyncClientWithHashKey(_, _, _)) + .WillOnce(Return(absl::StatusOr(mock_async_client))); + + // Expect the check call on the newly created gRPC client. + Grpc::MockAsyncRequest async_request; + EXPECT_CALL(*mock_async_client, sendRaw(_, _, _, _, _, _)).WillOnce(Return(&async_request)); + + // This should trigger createPerRouteGrpcClient and hit the creation logic. + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + test_filter->decodeHeaders(request_headers_, false)); + + // Cancel the request before destruction to avoid assertion failure in GrpcClientImpl. + EXPECT_CALL(async_request, cancel()); + test_filter->onDestroy(); +} + +// Verifies that per-route HTTP client creation logic is hit. +TEST_F(HttpFilterTest, CreatePerRouteHttpClientWithServerContext) { + initialize(getFilterConfig(false, true)); + prepareCheck(); + + // Create per-route configuration with HTTP service override. + envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; + auto* http_service = per_route_config.mutable_check_settings()->mutable_http_service(); + http_service->mutable_server_uri()->set_uri("https://per-route.example.com"); + http_service->mutable_server_uri()->set_cluster("per_route_cluster"); + + FilterConfigPerRoute per_route_filter_config(per_route_config); + ON_CALL(decoder_filter_callbacks_, perFilterConfigs()) + .WillByDefault(Return(Router::RouteSpecificFilterConfigs{&per_route_filter_config})); + + // Initialize filter with server context. + auto new_client = std::make_unique(); + auto test_filter = std::make_unique(config_, std::move(new_client), factory_context_); + test_filter->setDecoderFilterCallbacks(decoder_filter_callbacks_); + + // Mock cluster manager to return a cluster for the HTTP client. + EXPECT_CALL(factory_context_.cluster_manager_, + getThreadLocalCluster(absl::string_view("per_route_cluster"))); + + // This should trigger createPerRouteHttpClient and hit the creation logic. + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + test_filter->decodeHeaders(request_headers_, false)); +} + +// Verifies that valid error response mutations are allowed when validate_mutations is true. +TEST_F(HttpFilterTest, ValidErrorResponseWithMutations) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + validate_mutations: true + )EOF"); + + prepareCheck(); + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce( + Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { request_callbacks_ = &callbacks; })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); + + Filters::Common::ExtAuthz::Response response{}; + response.status = Filters::Common::ExtAuthz::CheckStatus::Error; + response.status_code = Http::Code::ServiceUnavailable; + // Valid mutations. + response.headers_to_set = {{"x-error-header", "value"}}; + response.headers_to_append = {{"x-error-append", "value"}}; + + // Should succeed and NOT clear attributes because mutations are valid. + EXPECT_CALL(decoder_filter_callbacks_, sendLocalReply(Http::Code::ServiceUnavailable, _, _, _, _)) + .WillOnce( + Invoke([&](Http::Code, absl::string_view, + std::function modify_headers, + const absl::optional, absl::string_view) -> void { + Http::TestResponseHeaderMapImpl response_headers; + modify_headers(response_headers); + EXPECT_EQ("value", response_headers.get_("x-error-header")); + EXPECT_EQ("value", response_headers.get_("x-error-append")); + })); + + request_callbacks_->onComplete(std::make_unique(response)); +} + +// Verifies that destination labels are correctly extracted from bootstrap metadata. +TEST_F(HttpFilterTest, DestinationLabelsFromBootstrap) { + const std::string yaml = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + bootstrap_metadata_labels_key: "labels" + )EOF"; + + // Set up bootstrap metadata. + auto* metadata_fields = + factory_context_.bootstrap_.mutable_node()->mutable_metadata()->mutable_fields(); + Protobuf::Struct labels_struct; + (*labels_struct.mutable_fields())["app"] = ValueUtil::stringValue("envoy"); + (*metadata_fields)["labels"] = ValueUtil::structValue(labels_struct); + + initialize(std::string(yaml)); + prepareCheck(); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks&, + const envoy::service::auth::v3::CheckRequest& check_param, + Tracing::Span&, const StreamInfo::StreamInfo&) -> void { + EXPECT_EQ("envoy", check_param.attributes().destination().labels().at("app")); + })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); +} + +// Verifies that the filter falls back to the default client if per-route gRPC client creation +// fails. +TEST_F(HttpFilterTest, PerRouteGrpcClientCreationFailure) { + initialize(getFilterConfig(false, false)); + prepareCheck(); + + // Create per-route configuration with gRPC service override. + envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; + auto* grpc_service = per_route_config.mutable_check_settings()->mutable_grpc_service(); + grpc_service->mutable_envoy_grpc()->set_cluster_name("per_route_cluster"); + + FilterConfigPerRoute per_route_filter_config(per_route_config); + ON_CALL(decoder_filter_callbacks_, perFilterConfigs()) + .WillByDefault(Return(Router::RouteSpecificFilterConfigs{&per_route_filter_config})); + + // Mock cluster manager to return an error status. + EXPECT_CALL(factory_context_.cluster_manager_.async_client_manager_, + getOrCreateRawAsyncClientWithHashKey(_, _, _)) + .WillOnce(Return(absl::InternalError("failed"))); + + // It should fall back to the default client (client_). + EXPECT_CALL(*client_, check(_, _, _, _)); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); +} + +// Verifies that the filter handles invalid append actions when validation is disabled. +TEST_F(HttpFilterTest, SawInvalidAppendActionsNoValidation) { + initialize(getFilterConfig(false, false)); + prepareCheck(); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { + auto response = std::make_unique(); + response->status = Filters::Common::ExtAuthz::CheckStatus::OK; + response->saw_invalid_append_actions = true; + callbacks.onComplete(std::move(response)); + })); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); +} + +// Verifies that the filter ignores invalid header removal when validation is enabled. +TEST_F(HttpFilterTest, InvalidHeaderRemovalIgnored) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + validate_mutations: true + )EOF"); + + prepareCheck(); + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { + auto response = std::make_unique(); + response->status = Filters::Common::ExtAuthz::CheckStatus::OK; + response->headers_to_remove = {"invalid\nheader"}; + callbacks.onComplete(std::move(response)); + })); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); +} + +// Verifies that adding query parameters that exceed header limits results in rejection. +TEST_F(HttpFilterTest, RequestHeaderLimitsReachedWithQueryParameters) { + initialize(getFilterConfig(false, false)); + prepareCheck(); + + // Create a header map with a small limit (1KB). + Http::TestRequestHeaderMapImpl limited_request_headers({}, 1, 100); + limited_request_headers.addCopy(Http::Headers::get().Host, "example.com"); + limited_request_headers.addCopy(Http::Headers::get().Method, "GET"); + limited_request_headers.addCopy(Http::Headers::get().Path, "/test"); + limited_request_headers.addCopy(Http::Headers::get().Scheme, "https"); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { + auto response = std::make_unique(); + response->status = Filters::Common::ExtAuthz::CheckStatus::OK; + // This will make the path > 1KB. + response->query_parameters_to_set = {{"foo", std::string(2048, 'a')}}; + callbacks.onComplete(std::move(response)); + })); + + EXPECT_CALL(decoder_filter_callbacks_, + sendLocalReply(Http::Code::InternalServerError, _, _, _, _)); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(limited_request_headers, false)); + EXPECT_EQ(1U, config_->stats().request_header_limits_reached_.value()); +} + +// Verifies that the filter handles cases where connection metadata is empty for the requested +// namespaces. +TEST_F(HttpFilterTest, ConnectionWithEmptyMetadata) { + const std::string yaml = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + metadata_context_namespaces: ["envoy.lb"] + typed_metadata_context_namespaces: ["envoy.lb"] + )EOF"; + + initialize(std::string(yaml)); + prepareCheck(); + + // No need to mock connection(), it returns a valid mock by default. + // The default mock connection has empty dynamic metadata. + + EXPECT_CALL(*client_, check(_, _, _, _)); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(request_headers_, false)); +} + +// Verifies that header limit reached is handled during header removal if already over limit. +TEST_F(HttpFilterTest, RequestHeaderLimitsReachedDuringRemoval) { + initialize(getFilterConfig(false, false)); + prepareCheck(); + + // Create a header map with a small limit (1KB). + Http::TestRequestHeaderMapImpl limited_request_headers({}, 1, 100); + limited_request_headers.addCopy(Http::Headers::get().Host, "example.com"); + limited_request_headers.addCopy(Http::Headers::get().Method, "GET"); + // Huge path to exceed 1KB. + limited_request_headers.addCopy(Http::Headers::get().Path, "/test?" + std::string(2048, 'a')); + limited_request_headers.addCopy(Http::Headers::get().Scheme, "https"); + limited_request_headers.addCopy(Http::LowerCaseString("remove-me"), "value"); + + EXPECT_CALL(*client_, check(_, _, _, _)) + .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, + const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, + const StreamInfo::StreamInfo&) -> void { + auto response = std::make_unique(); + response->status = Filters::Common::ExtAuthz::CheckStatus::OK; + response->headers_to_remove = {"remove-me"}; + callbacks.onComplete(std::move(response)); + })); + + EXPECT_CALL(decoder_filter_callbacks_, + sendLocalReply(Http::Code::InternalServerError, _, _, _, _)); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter_->decodeHeaders(limited_request_headers, false)); + EXPECT_EQ(1U, config_->stats().request_header_limits_reached_.value()); +} + } // namespace } // namespace ExtAuthz } // namespace HttpFilters diff --git a/test/extensions/filters/network/ext_authz/ext_authz_test.cc b/test/extensions/filters/network/ext_authz/ext_authz_test.cc index b13f6543babfb..8ba122bc7dfea 100644 --- a/test/extensions/filters/network/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/network/ext_authz/ext_authz_test.cc @@ -183,6 +183,32 @@ TEST_F(ExtAuthzFilterTest, OKWithOnData) { expectOKWithOnData(); } +// Verifies that labels are correctly extracted from the bootstrap metadata. +TEST_F(ExtAuthzFilterTest, BootstrapLabelsExtraction) { + const std::string yaml = R"EOF( +grpc_service: + envoy_grpc: + cluster_name: ext_authz_server +stat_prefix: name +bootstrap_metadata_labels_key: "labels_key" +)EOF"; + + Protobuf::Struct labels_struct; + auto& fields = *labels_struct.mutable_fields(); + fields["label1"] = ValueUtil::stringValue("value1"); + fields["label2"] = ValueUtil::stringValue("value2"); + + auto& node_metadata_fields = + *context_.bootstrap_.mutable_node()->mutable_metadata()->mutable_fields(); + node_metadata_fields["labels_key"].mutable_struct_value()->CopyFrom(labels_struct); + + initialize(yaml); + + EXPECT_EQ(2, config_->destinationLabels().size()); + EXPECT_EQ("value1", config_->destinationLabels().at("label1")); + EXPECT_EQ("value2", config_->destinationLabels().at("label2")); +} + TEST_F(ExtAuthzFilterTest, DeniedWithOnData) { initialize(default_yaml_string_); InSequence s; From 3d6e5f90a10a546b13bfc329a8aaf6d9548e0771 Mon Sep 17 00:00:00 2001 From: code Date: Sun, 10 May 2026 00:36:22 +0800 Subject: [PATCH 276/750] clang tidy: fix virtual method specifier of source (#44979) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Apply clang-tidy fixes for virtual method specifier issues (e.g. `override`/`final` placement) across `source/`. Part of a series of small, focused clang-tidy cleanup PRs split out from a larger branch. ## Risk Level Low — mechanical clang-tidy refactor; no behavior changes intended. ## Testing CI. ## Docs Changes None. ## Release Notes None. Signed-off-by: wbpcode/wangbaiping Signed-off-by: wbpcode/wangbaiping --- .../http/route_config_update_requster.h | 4 +--- source/common/jwt/simple_lru_cache_inl.h | 4 ++-- source/common/secret/sds_api.h | 2 +- .../fluentd/fluentd_access_log_impl.h | 2 +- .../dynamic_modules/extension_config.h | 2 +- .../upstream_socket_manager.h | 2 +- source/extensions/clusters/dns/dns_cluster.cc | 2 +- .../extensions/clusters/redis/redis_cluster.h | 2 +- .../extensions/common/fluentd/fluentd_base.h | 4 ++-- .../grpc/grpc_mux_failover.h | 2 +- .../filters/common/ext_proc/grpc_client.h | 4 ++-- .../http/cache_v2/cache_sessions_impl.h | 2 +- .../filters/http/mcp_router/filter_config.h | 24 ++++++++++--------- .../mapped_attribute_builder.cc | 2 -- .../filter_state_override/config.cc | 2 +- .../input_matchers/cel_matcher/matcher.cc | 2 -- .../cpu_utilization/linux_cpu_stats_reader.h | 2 +- .../open_telemetry/open_telemetry_impl.cc | 2 -- .../filter_state_override/config.cc | 5 ++-- .../tls/cert_mappers/sni/config.cc | 2 +- .../tls/cert_mappers/static_name/config.cc | 4 ++-- 21 files changed, 36 insertions(+), 41 deletions(-) diff --git a/source/common/http/route_config_update_requster.h b/source/common/http/route_config_update_requster.h index 740908e1f4a68..1c3a0315400ba 100644 --- a/source/common/http/route_config_update_requster.h +++ b/source/common/http/route_config_update_requster.h @@ -45,9 +45,7 @@ class RdsRouteConfigUpdateRequester : public RouteConfigUpdateRequester { class RdsRouteConfigUpdateRequesterFactory : public RouteConfigUpdateRequesterFactory { public: // UntypedFactory - virtual std::string name() const override { - return "envoy.route_config_update_requester.default"; - } + std::string name() const override { return "envoy.route_config_update_requester.default"; } std::unique_ptr createRouteConfigUpdateRequester(Router::RouteConfigProvider* route_config_provider) override { diff --git a/source/common/jwt/simple_lru_cache_inl.h b/source/common/jwt/simple_lru_cache_inl.h index 2c47601537d55..2487274c45312 100644 --- a/source/common/jwt/simple_lru_cache_inl.h +++ b/source/common/jwt/simple_lru_cache_inl.h @@ -1018,7 +1018,7 @@ class SimpleLRUCache total_units) {} protected: - virtual void removeElement(Value* value) { delete value; } + void removeElement(Value* value) override { delete value; } private: GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(SimpleLRUCache); @@ -1038,7 +1038,7 @@ class SimpleLRUCacheWithDeleter : Base(total_units), deleter_(deleter) {} protected: - virtual void removeElement(Value* value) { deleter_(value); } + void removeElement(Value* value) override { deleter_(value); } private: Deleter deleter_; diff --git a/source/common/secret/sds_api.h b/source/common/secret/sds_api.h index 9b7eb849a2728..6ca6e1fa592bb 100644 --- a/source/common/secret/sds_api.h +++ b/source/common/secret/sds_api.h @@ -148,7 +148,7 @@ class DynamicSecretProvider : public SdsApi, public SecretProvider { : SdsApi(sds_config, sds_config_name, subscription_factory, time_source, validation_visitor, stats, std::move(destructor_cb), dispatcher, api, warm) {} - virtual const SecretType* secret() const override PURE; + const SecretType* secret() const override PURE; ABSL_MUST_USE_RESULT Common::CallbackHandlePtr addValidationCallback(std::function callback) override { diff --git a/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h b/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h index 84ac1bf6a7121..cc496a42aff27 100644 --- a/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h +++ b/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h @@ -26,7 +26,7 @@ class FluentdAccessLoggerImpl : public FluentdBase { Event::Dispatcher& dispatcher, const FluentdAccessLogConfig& config, BackOffStrategyPtr backoff_strategy, Stats::Scope& parent_scope); - void packMessage(MessagePackPacker& packer); + void packMessage(MessagePackPacker& packer) override; }; using FluentdAccessLoggerWeakPtr = std::weak_ptr; diff --git a/source/extensions/bootstrap/dynamic_modules/extension_config.h b/source/extensions/bootstrap/dynamic_modules/extension_config.h index 8ffa2aee9e131..2bd1ff495a279 100644 --- a/source/extensions/bootstrap/dynamic_modules/extension_config.h +++ b/source/extensions/bootstrap/dynamic_modules/extension_config.h @@ -93,7 +93,7 @@ class DynamicModuleBootstrapExtensionConfig Server::Configuration::ServerFactoryContext& context, Stats::Store& stats_store); - ~DynamicModuleBootstrapExtensionConfig(); + ~DynamicModuleBootstrapExtensionConfig() override; /** * This is called when an event is scheduled via diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h index f10c3de9c7d1a..f4923ddc28393 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h @@ -38,7 +38,7 @@ class UpstreamSocketManager : public ThreadLocal::ThreadLocalObject, UpstreamSocketManager(Event::Dispatcher& dispatcher, ReverseTunnelAcceptorExtension* extension = nullptr); - ~UpstreamSocketManager(); + ~UpstreamSocketManager() override; /** * Add accepted connection to socket manager. diff --git a/source/extensions/clusters/dns/dns_cluster.cc b/source/extensions/clusters/dns/dns_cluster.cc index 7067c3e17d48b..3986249b95c34 100644 --- a/source/extensions/clusters/dns/dns_cluster.cc +++ b/source/extensions/clusters/dns/dns_cluster.cc @@ -53,7 +53,7 @@ class LegacyDnsClusterFactory : public ClusterFactoryImplBase { LegacyDnsClusterFactory(const std::string& name, bool set_all_addresses_in_single_endpoint) : ClusterFactoryImplBase(name), set_all_addresses_in_single_endpoint_(set_all_addresses_in_single_endpoint) {} - virtual absl::StatusOr> + absl::StatusOr> createClusterImpl(const envoy::config::cluster::v3::Cluster& cluster, ClusterFactoryContext& context) override { absl::StatusOr dns_resolver_or_error = diff --git a/source/extensions/clusters/redis/redis_cluster.h b/source/extensions/clusters/redis/redis_cluster.h index 6296df049ab3f..c789e81542bfe 100644 --- a/source/extensions/clusters/redis/redis_cluster.h +++ b/source/extensions/clusters/redis/redis_cluster.h @@ -90,7 +90,7 @@ namespace Redis { class RedisCluster : public Upstream::BaseDynamicClusterImpl { public: - ~RedisCluster(); + ~RedisCluster() override; static absl::StatusOr> create(const envoy::config::cluster::v3::Cluster& cluster, const envoy::extensions::clusters::redis::v3::RedisClusterConfig& redis_cluster, diff --git a/source/extensions/common/fluentd/fluentd_base.h b/source/extensions/common/fluentd/fluentd_base.h index 9ef1fcb696a39..982495026be48 100644 --- a/source/extensions/common/fluentd/fluentd_base.h +++ b/source/extensions/common/fluentd/fluentd_base.h @@ -78,7 +78,7 @@ class FluentdBase : public Tcp::AsyncTcpClientCallbacks, const std::string& stat_prefix, BackOffStrategyPtr backoff_strategy, uint64_t buffer_flush_interval, uint64_t max_buffer_size); - virtual ~FluentdBase() = default; + ~FluentdBase() override = default; // Tcp::AsyncTcpClientCallbacks void onEvent(Network::ConnectionEvent event) override; @@ -138,7 +138,7 @@ class FluentdCacheBase : public FluentdCache { }); } - virtual ~FluentdCacheBase() = default; + ~FluentdCacheBase() override = default; SharedPtrType getOrCreate(const std::shared_ptr& config, Random::RandomGenerator& random, diff --git a/source/extensions/config_subscription/grpc/grpc_mux_failover.h b/source/extensions/config_subscription/grpc/grpc_mux_failover.h index f43b83e1250c8..fb971ac460c45 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_failover.h +++ b/source/extensions/config_subscription/grpc/grpc_mux_failover.h @@ -85,7 +85,7 @@ class GrpcMuxFailover : public GrpcStreamInterface, } } - virtual ~GrpcMuxFailover() = default; + ~GrpcMuxFailover() override = default; // Attempts to establish a new stream to the either the primary or failover source. void establishNewStream() override { diff --git a/source/extensions/filters/common/ext_proc/grpc_client.h b/source/extensions/filters/common/ext_proc/grpc_client.h index 3e10e1ff01e1c..60cf540cb1ff5 100644 --- a/source/extensions/filters/common/ext_proc/grpc_client.h +++ b/source/extensions/filters/common/ext_proc/grpc_client.h @@ -16,7 +16,7 @@ namespace ExternalProcessing { */ template class ProcessorCallbacks : public RequestCallbacks { public: - virtual ~ProcessorCallbacks() = default; + ~ProcessorCallbacks() override = default; virtual void onReceiveMessage(std::unique_ptr&& response) PURE; virtual void onGrpcError(Grpc::Status::GrpcStatus error, const std::string& message) PURE; virtual void onGrpcClose() PURE; @@ -48,7 +48,7 @@ using ProcessorStreamPtr = std::unique_ptr class ProcessorClient : public ClientBase { public: - virtual ~ProcessorClient() = default; + ~ProcessorClient() override = default; virtual ProcessorStreamPtr start(ProcessorCallbacks& callbacks, const Grpc::GrpcServiceConfigWithHashKey& config_with_hash_key, diff --git a/source/extensions/filters/http/cache_v2/cache_sessions_impl.h b/source/extensions/filters/http/cache_v2/cache_sessions_impl.h index 22f8904be76e7..db023884e0415 100644 --- a/source/extensions/filters/http/cache_v2/cache_sessions_impl.h +++ b/source/extensions/filters/http/cache_v2/cache_sessions_impl.h @@ -66,7 +66,7 @@ class CacheSession : public Logger::Loggable, ABSL_LOCKS_EXCLUDED(mu_); void clearUncacheableState() ABSL_LOCKS_EXCLUDED(mu_); - ~CacheSession(); + ~CacheSession() override; class Subscriber { public: diff --git a/source/extensions/filters/http/mcp_router/filter_config.h b/source/extensions/filters/http/mcp_router/filter_config.h index 7aa83950fbbda..d80c91ec5ebdd 100644 --- a/source/extensions/filters/http/mcp_router/filter_config.h +++ b/source/extensions/filters/http/mcp_router/filter_config.h @@ -109,23 +109,25 @@ class McpRouterConfigImpl : public McpRouterConfig { const std::string& stats_prefix, Stats::Scope& scope, Server::Configuration::FactoryContext& context); - const std::vector& backends() const { return backends_; } - bool isMultiplexing() const { return backends_.size() > 1; } - const std::string& defaultBackendName() const { return default_backend_name_; } - Server::Configuration::FactoryContext& factoryContext() const { return factory_context_; } - const McpBackendConfig* findBackend(const std::string& name) const; + const std::vector& backends() const override { return backends_; } + bool isMultiplexing() const override { return backends_.size() > 1; } + const std::string& defaultBackendName() const override { return default_backend_name_; } + Server::Configuration::FactoryContext& factoryContext() const override { + return factory_context_; + } + const McpBackendConfig* findBackend(const std::string& name) const override; - bool hasSessionIdentity() const { + bool hasSessionIdentity() const override { return !absl::holds_alternative(session_identity_.subject_source); } - const SubjectSource& subjectSource() const { return session_identity_.subject_source; } - ValidationMode validationMode() const { return session_identity_.validation_mode; } - bool shouldEnforceValidation() const { + const SubjectSource& subjectSource() const override { return session_identity_.subject_source; } + ValidationMode validationMode() const override { return session_identity_.validation_mode; } + bool shouldEnforceValidation() const override { return session_identity_.validation_mode == ValidationMode::Enforce; } - const std::string& metadataNamespace() const { return metadata_namespace_; } + const std::string& metadataNamespace() const override { return metadata_namespace_; } - McpRouterStats& stats() { return stats_; } + McpRouterStats& stats() override { return stats_; } private: std::vector backends_; diff --git a/source/extensions/http/ext_proc/processing_request_modifiers/mapped_attribute_builder/mapped_attribute_builder.cc b/source/extensions/http/ext_proc/processing_request_modifiers/mapped_attribute_builder/mapped_attribute_builder.cc index 3d741da41024c..2147d6f6cae3c 100644 --- a/source/extensions/http/ext_proc/processing_request_modifiers/mapped_attribute_builder/mapped_attribute_builder.cc +++ b/source/extensions/http/ext_proc/processing_request_modifiers/mapped_attribute_builder/mapped_attribute_builder.cc @@ -11,8 +11,6 @@ namespace Envoy { namespace Http { namespace ExternalProcessing { -using envoy::service::ext_proc::v3::ProcessingRequest; - // Helper function to convert proto map values to a unique vector of strings. // The order of elements in the returned vector is not guaranteed. Protobuf::RepeatedPtrField diff --git a/source/extensions/local_address_selectors/filter_state_override/config.cc b/source/extensions/local_address_selectors/filter_state_override/config.cc index 942b855a7289a..1c381b8dd917f 100644 --- a/source/extensions/local_address_selectors/filter_state_override/config.cc +++ b/source/extensions/local_address_selectors/filter_state_override/config.cc @@ -31,7 +31,7 @@ class NamespaceLocalAddressSelector : public Upstream::UpstreamLocalAddressSelec Upstream::UpstreamLocalAddress getUpstreamLocalAddress( const Network::Address::InstanceConstSharedPtr& endpoint_address, const Network::ConnectionSocket::OptionsSharedPtr& socket_options, - OptRef transport_socket_options) const { + OptRef transport_socket_options) const override { const auto upstream_address = inner_->getUpstreamLocalAddress(endpoint_address, socket_options, transport_socket_options); if (transport_socket_options && upstream_address.address_) { diff --git a/source/extensions/matching/input_matchers/cel_matcher/matcher.cc b/source/extensions/matching/input_matchers/cel_matcher/matcher.cc index 9bb7d110052e1..8fd6338f1d1e8 100644 --- a/source/extensions/matching/input_matchers/cel_matcher/matcher.cc +++ b/source/extensions/matching/input_matchers/cel_matcher/matcher.cc @@ -9,8 +9,6 @@ namespace InputMatchers { namespace CelMatcher { using ::Envoy::Extensions::Matching::Http::CelInput::CelMatchData; -using ::xds::type::v3::CelExpression; - CelInputMatcher::CelInputMatcher(CelMatcherSharedPtr cel_matcher, Filters::Common::Expr::BuilderInstanceSharedConstPtr builder) : compiled_expr_([&]() { diff --git a/source/extensions/resource_monitors/cpu_utilization/linux_cpu_stats_reader.h b/source/extensions/resource_monitors/cpu_utilization/linux_cpu_stats_reader.h index e9fbe2565393e..63f97b8346076 100644 --- a/source/extensions/resource_monitors/cpu_utilization/linux_cpu_stats_reader.h +++ b/source/extensions/resource_monitors/cpu_utilization/linux_cpu_stats_reader.h @@ -55,7 +55,7 @@ class LinuxContainerCpuStatsReader : public CpuStatsReader { public: using ContainerStatsReaderPtr = std::unique_ptr; - virtual ~LinuxContainerCpuStatsReader() = default; + ~LinuxContainerCpuStatsReader() override = default; /** * Create the appropriate cgroup stats reader. diff --git a/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.cc b/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.cc index ad586815f0d08..87f66248286df 100644 --- a/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.cc +++ b/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.cc @@ -10,9 +10,7 @@ namespace OpenTelemetry { using ::opentelemetry::proto::metrics::v1::AggregationTemporality; using ::opentelemetry::proto::metrics::v1::HistogramDataPoint; -using ::opentelemetry::proto::metrics::v1::Metric; using ::opentelemetry::proto::metrics::v1::NumberDataPoint; -using ::opentelemetry::proto::metrics::v1::ResourceMetrics; using MetricsExportRequest = opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest; diff --git a/source/extensions/transport_sockets/tls/cert_mappers/filter_state_override/config.cc b/source/extensions/transport_sockets/tls/cert_mappers/filter_state_override/config.cc index 87af7b8678093..0fb9cd0b654e8 100644 --- a/source/extensions/transport_sockets/tls/cert_mappers/filter_state_override/config.cc +++ b/source/extensions/transport_sockets/tls/cert_mappers/filter_state_override/config.cc @@ -15,8 +15,9 @@ namespace { class Mapper : public Ssl::UpstreamTlsCertificateMapper { public: explicit Mapper(const std::string& default_value) : default_value_(default_value) {} - std::string deriveFromServerHello(const SSL&, - const Network::TransportSocketOptionsConstSharedPtr& options) { + std::string + deriveFromServerHello(const SSL&, + const Network::TransportSocketOptionsConstSharedPtr& options) override { if (options) { for (const auto& obj : options->downstreamSharedFilterStateObjects()) { if (obj.name_ == "envoy.tls.certificate_mappers.on_demand_secret") { diff --git a/source/extensions/transport_sockets/tls/cert_mappers/sni/config.cc b/source/extensions/transport_sockets/tls/cert_mappers/sni/config.cc index a03a4b2e36e4b..64ef7fbc87361 100644 --- a/source/extensions/transport_sockets/tls/cert_mappers/sni/config.cc +++ b/source/extensions/transport_sockets/tls/cert_mappers/sni/config.cc @@ -13,7 +13,7 @@ namespace { class SNIMapper : public Ssl::TlsCertificateMapper { public: explicit SNIMapper(const std::string& default_value) : default_value_(default_value) {} - std::string deriveFromClientHello(const SSL_CLIENT_HELLO& ssl_client_hello) { + std::string deriveFromClientHello(const SSL_CLIENT_HELLO& ssl_client_hello) override { absl::string_view sni = absl::NullSafeStringView( SSL_get_servername(ssl_client_hello.ssl, TLSEXT_NAMETYPE_host_name)); return sni.empty() ? default_value_ : std::string(sni); diff --git a/source/extensions/transport_sockets/tls/cert_mappers/static_name/config.cc b/source/extensions/transport_sockets/tls/cert_mappers/static_name/config.cc index 15e47da819712..6ed2750b369a9 100644 --- a/source/extensions/transport_sockets/tls/cert_mappers/static_name/config.cc +++ b/source/extensions/transport_sockets/tls/cert_mappers/static_name/config.cc @@ -12,9 +12,9 @@ class StaticNameMapper : public Ssl::TlsCertificateMapper, public Ssl::UpstreamTlsCertificateMapper { public: explicit StaticNameMapper(const std::string& name) : name_(name) {} - std::string deriveFromClientHello(const SSL_CLIENT_HELLO&) { return name_; } + std::string deriveFromClientHello(const SSL_CLIENT_HELLO&) override { return name_; } std::string deriveFromServerHello(const SSL&, - const Network::TransportSocketOptionsConstSharedPtr&) { + const Network::TransportSocketOptionsConstSharedPtr&) override { return name_; } From 85c2262e609e33ac8f9886083ef07c9dcadc21d7 Mon Sep 17 00:00:00 2001 From: code Date: Sun, 10 May 2026 23:01:40 +0800 Subject: [PATCH 277/750] clang tidy: fix readability issues of source (#44978) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Apply clang-tidy fixes for readability issues across `source/`. Part of a series of small, focused clang-tidy cleanup PRs split out from a larger branch. ## Risk Level Low — mechanical clang-tidy refactor; no behavior changes intended. ## Testing CI. ## Docs Changes None. ## Release Notes None. Signed-off-by: wbpcode/wangbaiping Signed-off-by: wbpcode/wangbaiping --- envoy/matcher/matcher.h | 8 ++-- source/common/buffer/buffer_impl.cc | 2 +- .../common/scope_tracked_object_stack.h | 2 + source/common/event/dispatcher_impl.cc | 1 + .../formatter/substitution_format_utility.cc | 4 +- source/common/http/header_map_impl.cc | 1 + source/common/http/http2/codec_impl.cc | 1 + .../http/http_server_properties_cache_impl.cc | 2 +- source/common/jwt/simple_lru_cache_inl.h | 38 ++++++++++++------- source/common/router/config_impl.cc | 36 ++++++++++++------ source/common/router/router.cc | 4 +- source/common/router/scoped_config_impl.cc | 2 +- source/common/tcp_proxy/tcp_proxy.cc | 2 +- .../common/thread_local/thread_local_impl.cc | 1 + .../tls/cert_validator/default_validator.cc | 2 +- .../common/tls/connection_info_impl_base.cc | 1 + source/common/tls/ocsp/asn1_utility.cc | 2 +- source/common/upstream/upstream_impl.cc | 6 +-- .../open_telemetry/otlp_log_utils.cc | 1 + .../upstream_socket_manager.cc | 2 +- .../clusters/dynamic_modules/cluster.cc | 2 +- .../reverse_connection/reverse_connection.cc | 4 +- .../config_subscription/grpc/grpc_mux_impl.h | 2 +- .../grpc/new_grpc_mux_impl.h | 2 +- .../json/json_content_parser_impl.cc | 3 +- .../extensions/filters/http/a2a/a2a_filter.cc | 3 +- .../http/cache_v2/cache_sessions_impl.cc | 8 ++-- .../grpc_json_reverse_transcoder/filter.cc | 9 +++-- .../filter_config.cc | 1 - .../grpc_json_transcoder/http_body_utils.cc | 1 + .../extensions/filters/http/mcp/mcp_filter.cc | 3 +- .../filters/http/mcp_router/mcp_router.cc | 3 +- .../filters/http/transform/transform.cc | 2 +- .../listener/tls_inspector/ja4_fingerprint.cc | 2 + .../redis_proxy/cluster_response_handler.cc | 11 ++++-- .../redis_proxy/command_splitter_impl.cc | 4 +- .../redis_proxy/info_command_handler.cc | 1 + .../network/redis_proxy/router_impl.cc | 4 +- .../override_host/load_balancer.cc | 2 +- .../tracers/dynamic_modules/tracer_config.cc | 3 ++ .../tracers/zipkin/span_context_extractor.cc | 2 +- .../cert_validator/spiffe/spiffe_validator.cc | 2 +- source/server/cgroup_cpu_util.cc | 6 ++- 43 files changed, 121 insertions(+), 77 deletions(-) diff --git a/envoy/matcher/matcher.h b/envoy/matcher/matcher.h index 9fb829c1807da..f2aad92c4d118 100644 --- a/envoy/matcher/matcher.h +++ b/envoy/matcher/matcher.h @@ -338,7 +338,7 @@ class DataInputGetResult : public NonCopyable { static DataInputGetResult NoData(DataAvailability data_availability = DataAvailability::AllDataAvailable) { - return DataInputGetResult(absl::monostate(), data_availability); + return {absl::monostate(), data_availability}; } /** @@ -348,19 +348,19 @@ class DataInputGetResult : public NonCopyable { static DataInputGetResult CreateStringView(absl::string_view data, DataAvailability data_availability = DataAvailability::AllDataAvailable) { - return DataInputGetResult(data, data_availability); + return {data, data_availability}; } static DataInputGetResult CreateString(std::string&& data, DataAvailability data_availability = DataAvailability::AllDataAvailable) { - return DataInputGetResult(std::move(data), data_availability); + return {std::move(data), data_availability}; } static DataInputGetResult CreateCustom(std::shared_ptr&& data, DataAvailability data_availability = DataAvailability::AllDataAvailable) { - return DataInputGetResult(std::move(data), data_availability); + return {std::move(data), data_availability}; } private: diff --git a/source/common/buffer/buffer_impl.cc b/source/common/buffer/buffer_impl.cc index 8cda4e1777a45..67c00ee694850 100644 --- a/source/common/buffer/buffer_impl.cc +++ b/source/common/buffer/buffer_impl.cc @@ -590,7 +590,7 @@ bool OwnedImpl::startsWith(absl::string_view data) const { return false; } - if (data.length() == 0) { + if (data.empty()) { return true; } diff --git a/source/common/common/scope_tracked_object_stack.h b/source/common/common/scope_tracked_object_stack.h index 880ddd8eeb3a0..0fcf1e4144b53 100644 --- a/source/common/common/scope_tracked_object_stack.h +++ b/source/common/common/scope_tracked_object_stack.h @@ -24,6 +24,7 @@ class ScopeTrackedObjectStack : public ScopeTrackedObject { void add(const ScopeTrackedObject& object) { tracked_objects_.push_back(object); } OptRef trackedStream() const override { + // NOLINTNEXTLINE(modernize-loop-convert) for (auto iter = tracked_objects_.rbegin(); iter != tracked_objects_.rend(); ++iter) { OptRef stream = iter->get().trackedStream(); if (stream.has_value()) { @@ -34,6 +35,7 @@ class ScopeTrackedObjectStack : public ScopeTrackedObject { } void dumpState(std::ostream& os, int indent_level) const override { + // NOLINTNEXTLINE(modernize-loop-convert) for (auto iter = tracked_objects_.rbegin(); iter != tracked_objects_.rend(); ++iter) { iter->get().dumpState(os, indent_level); } diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index 6cc1e52312528..6455f0e69d788 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -386,6 +386,7 @@ void DispatcherImpl::onFatalError(std::ostream& os) const { // Dump the state of the tracked objects in the dispatcher if thread safe. This generally // results in dumping the active state only for the thread which caused the fatal error. if (isThreadSafe()) { + // NOLINTNEXTLINE(modernize-loop-convert) for (auto iter = tracked_object_stack_.rbegin(); iter != tracked_object_stack_.rend(); ++iter) { (*iter)->dumpState(os); } diff --git a/source/common/formatter/substitution_format_utility.cc b/source/common/formatter/substitution_format_utility.cc index 5c7f44a8dbc5d..9ae72b09ff82f 100644 --- a/source/common/formatter/substitution_format_utility.cc +++ b/source/common/formatter/substitution_format_utility.cc @@ -18,12 +18,12 @@ absl::Status CommandSyntaxChecker::verifySyntax(CommandSyntaxChecker::CommandSyn absl::string_view command, absl::string_view subcommand, absl::optional length) { - if ((flags == COMMAND_ONLY) && ((subcommand.length() != 0) || length.has_value())) { + if ((flags == COMMAND_ONLY) && ((!subcommand.empty()) || length.has_value())) { return absl::InvalidArgumentError( fmt::format("{} does not take any parameters or length", command)); } - if ((flags & PARAMS_REQUIRED).any() && (subcommand.length() == 0)) { + if ((flags & PARAMS_REQUIRED).any() && (subcommand.empty())) { return absl::InvalidArgumentError(fmt::format("{} requires parameters", command)); } diff --git a/source/common/http/header_map_impl.cc b/source/common/http/header_map_impl.cc index 1a5b4e6886a1a..3cec5b6cbc15c 100644 --- a/source/common/http/header_map_impl.cc +++ b/source/common/http/header_map_impl.cc @@ -394,6 +394,7 @@ void HeaderMapImpl::iterate(HeaderMap::ConstIterateCb cb) const { } void HeaderMapImpl::iterateReverse(HeaderMap::ConstIterateCb cb) const { + // NOLINTNEXTLINE(modernize-loop-convert) for (auto it = headers_.rbegin(); it != headers_.rend(); it++) { if (cb(*it) == HeaderMap::Iterate::Break) { break; diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index e02141e75fa60..64c8c5d85fccf 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -1818,6 +1818,7 @@ void ConnectionImpl::onProtocolConstraintViolation() { void ConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { // Notify the streams based on least recently encoding to the connection. + // NOLINTNEXTLINE(modernize-loop-convert) for (auto it = active_streams_.rbegin(); it != active_streams_.rend(); ++it) { (*it)->runLowWatermarkCallbacks(); } diff --git a/source/common/http/http_server_properties_cache_impl.cc b/source/common/http/http_server_properties_cache_impl.cc index 7a23c11fe74bc..f04227a2d59cb 100644 --- a/source/common/http/http_server_properties_cache_impl.cc +++ b/source/common/http/http_server_properties_cache_impl.cc @@ -34,7 +34,7 @@ HttpServerPropertiesCacheImpl::stringToOrigin(const std::string& str) { std::string scheme; std::string hostname; int port = 0; - if (re2::RE2::FullMatch(str.c_str(), origin_regex, &scheme, &hostname, &port)) { + if (re2::RE2::FullMatch(str, origin_regex, &scheme, &hostname, &port)) { return HttpServerPropertiesCache::Origin(scheme, hostname, port); } return {}; diff --git a/source/common/jwt/simple_lru_cache_inl.h b/source/common/jwt/simple_lru_cache_inl.h index 2487274c45312..c76b5d6aedb91 100644 --- a/source/common/jwt/simple_lru_cache_inl.h +++ b/source/common/jwt/simple_lru_cache_inl.h @@ -37,8 +37,6 @@ #pragma once -#include - #include #include #include @@ -167,8 +165,9 @@ template struct SimpleLRUCacheElem { } void unlink() { - if (!isLinked()) + if (!isLinked()) { return; + } prev->next = next; next->prev = prev; prev = nullptr; @@ -248,8 +247,9 @@ template class SimpleLRUCacheB value_(cache_->lookupWithOptions(key_, options_)) {} ~ScopedLookup() { - if (value_ != nullptr) + if (value_ != nullptr) { cache_->releaseWithOptions(key_, value_, options_); + } } const Key& key() const { return key_; } Value* value() const { return value_; } @@ -391,8 +391,9 @@ template class SimpleLRUCacheB // Remove all entries which have exceeded their max idle time or age // set using setMaxIdleSeconds() or setAgeBasedEviction() respectively. void removeExpiredEntries() { - if (max_idle_ >= 0) + if (max_idle_ >= 0) { discardIdle(max_idle_); + } } // Return current size of cache @@ -608,8 +609,9 @@ template void SimpleLRUCacheBase::removeUnpinned() { for (Elem* e = head_.next; e != &head_;) { Elem* next = e->next; - if (e->pin == 0) + if (e->pin == 0) { remove(e->key); + } e = next; } } @@ -650,8 +652,9 @@ Value* SimpleLRUCacheBase::lookupWithOptions( pinned_units_ += e->units; // We are pinning this entry, take it off the LRU list if we are in LRU // mode. In strict age-based mode entries stay on the list while pinned. - if (lru_ && options.update_eviction_order()) + if (lru_ && options.update_eviction_order()) { e->unlink(); + } } e->pin++; return e->value; @@ -707,8 +710,9 @@ void SimpleLRUCacheBase::releaseWithOptions( e->pin--; if (e->pin == 0) { - if (lru_ && options.update_eviction_order()) + if (lru_ && options.update_eviction_order()) { e->link(&head_); + } pinned_units_ -= e->units; if (isOverfullInternal()) { // This element is no longer needed, and we are full. So kick it out. @@ -735,8 +739,9 @@ void SimpleLRUCacheBase::insertPinned(const Key& k, Val // If we are in the strict age-based eviction mode, the entry goes on the LRU // list now and is never removed. In the LRU mode, the list will only contain // unpinned entries. - if (!lru_) + if (!lru_) { e->link(&head_); + } garbageCollect(); } @@ -793,8 +798,9 @@ bool SimpleLRUCacheBase::inDeferredTable(const Key& k, const Elem* const head = iter->second; const Elem* e = head; do { - if (e->value == value || value == nullptr) + if (e->value == value || value == nullptr) { return true; + } e = e->prev; } while (e != head); } @@ -858,8 +864,9 @@ static const int kAcceptableClockSynchronizationDriftCycles = 1; template void SimpleLRUCacheBase::discardIdle(int64_t max_idle) { - if (max_idle < 0) + if (max_idle < 0) { return; + } Elem* e = head_.prev; const int64_t threshold = SimpleCycleTimer::now() - max_idle; @@ -928,8 +935,9 @@ int64_t SimpleLRUCacheBase::deferredEntries() const { template int64_t SimpleLRUCacheBase::ageOfLRUItemInMicroseconds() const { - if (head_.prev == &head_) + if (head_.prev == &head_) { return 0; + } return kSecToUsec * (SimpleCycleTimer::now() - head_.prev->last_use_) / SimpleCycleTimer::frequency(); } @@ -939,8 +947,9 @@ int64_t SimpleLRUCacheBase::getLastUseTime(const Key& k // getLastUseTime works only in LRU mode assert(lru_); TableConstIterator iter = table_.find(k); - if (iter == table_.end()) + if (iter == table_.end()) { return -1; + } const Elem* e = iter->second; return e->last_use_; } @@ -950,8 +959,9 @@ int64_t SimpleLRUCacheBase::getInsertionTime(const Key& // getInsertionTime works only in age-based mode assert(!lru_); TableConstIterator iter = table_.find(k); - if (iter == table_.end()) + if (iter == table_.end()) { return -1; + } const Elem* e = iter->second; return e->last_use_; } diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index d5a8fc5cf0e42..6cd9f27b6d767 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -1369,12 +1369,16 @@ RouteEntryImplBase::perFilterConfigs(absl::string_view filter_name) const { } const envoy::config::core::v3::Metadata& RouteEntryImplBase::metadata() const { - return metadata_ != nullptr ? metadata_->proto_metadata_ - : DefaultRouteMetadataPack::get().proto_metadata_; + if (metadata_ != nullptr) { + return metadata_->proto_metadata_; + } + return DefaultRouteMetadataPack::get().proto_metadata_; } const Envoy::Config::TypedMetadata& RouteEntryImplBase::typedMetadata() const { - return metadata_ != nullptr ? metadata_->typed_metadata_ - : DefaultRouteMetadataPack::get().typed_metadata_; + if (metadata_ != nullptr) { + return metadata_->typed_metadata_; + } + return DefaultRouteMetadataPack::get().typed_metadata_; } UriTemplateMatcherRouteEntryImpl::UriTemplateMatcherRouteEntryImpl( @@ -1728,12 +1732,16 @@ CommonVirtualHostImpl::perFilterConfigs(absl::string_view filter_name) const { } const envoy::config::core::v3::Metadata& CommonVirtualHostImpl::metadata() const { - return metadata_ != nullptr ? metadata_->proto_metadata_ - : DefaultRouteMetadataPack::get().proto_metadata_; + if (metadata_ != nullptr) { + return metadata_->proto_metadata_; + } + return DefaultRouteMetadataPack::get().proto_metadata_; } const Envoy::Config::TypedMetadata& CommonVirtualHostImpl::typedMetadata() const { - return metadata_ != nullptr ? metadata_->typed_metadata_ - : DefaultRouteMetadataPack::get().typed_metadata_; + if (metadata_ != nullptr) { + return metadata_->typed_metadata_; + } + return DefaultRouteMetadataPack::get().typed_metadata_; } absl::StatusOr> @@ -2136,12 +2144,16 @@ CommonConfigImpl::clusterSpecifierPlugin(absl::string_view provider) const { } const envoy::config::core::v3::Metadata& CommonConfigImpl::metadata() const { - return metadata_ != nullptr ? metadata_->proto_metadata_ - : DefaultRouteMetadataPack::get().proto_metadata_; + if (metadata_ != nullptr) { + return metadata_->proto_metadata_; + } + return DefaultRouteMetadataPack::get().proto_metadata_; } const Envoy::Config::TypedMetadata& CommonConfigImpl::typedMetadata() const { - return metadata_ != nullptr ? metadata_->typed_metadata_ - : DefaultRouteMetadataPack::get().typed_metadata_; + if (metadata_ != nullptr) { + return metadata_->typed_metadata_; + } + return DefaultRouteMetadataPack::get().typed_metadata_; } absl::StatusOr> diff --git a/source/common/router/router.cc b/source/common/router/router.cc index d37f14f90b742..a0ef5106328bd 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -1982,7 +1982,7 @@ void Filter::onUpstreamData(Buffer::Instance& data, UpstreamRequest& upstream_re // When route retry policy is configured and an upstream filter is returning StopIteration // in it's encodeHeaders() method, upstream_requests_.size() is equal to 0 in this case, // and we should just return. - if (upstream_requests_.size() == 0) { + if (upstream_requests_.empty()) { return; } @@ -2006,7 +2006,7 @@ void Filter::onUpstreamTrailers(Http::ResponseTrailerMapPtr&& trailers, // When route retry policy is configured and an upstream filter is returning StopIteration // in it's encodeHeaders() method, upstream_requests_.size() is equal to 0 in this case, // and we should just return. - if (upstream_requests_.size() == 0) { + if (upstream_requests_.empty()) { return; } diff --git a/source/common/router/scoped_config_impl.cc b/source/common/router/scoped_config_impl.cc index 5b39ff196901d..0bd72398b7169 100644 --- a/source/common/router/scoped_config_impl.cc +++ b/source/common/router/scoped_config_impl.cc @@ -51,7 +51,7 @@ HeaderValueExtractorImpl::computeFragment(const Http::HeaderMap& headers) const // This is an implicitly untrusted header, so per the API documentation only the first // value is used. std::vector elements{header_entry[0]->value().getStringView()}; - if (header_value_extractor_config_.element_separator().length() > 0) { + if (!header_value_extractor_config_.element_separator().empty()) { elements = absl::StrSplit(header_entry[0]->value().getStringView(), header_value_extractor_config_.element_separator()); } diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index e07abe5d9808f..ca54cf5245c63 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -387,7 +387,7 @@ Config::SharedConfig::parseTLVs(absl::Spanvalue().size() < 1) { + if (tlv->value().empty()) { throw EnvoyException("Invalid TLV configuration: 'value' must be at least one byte long."); } tlv_vector.push_back( diff --git a/source/common/thread_local/thread_local_impl.cc b/source/common/thread_local/thread_local_impl.cc index 7d1401991a0d8..1231c044f7ca7 100644 --- a/source/common/thread_local/thread_local_impl.cc +++ b/source/common/thread_local/thread_local_impl.cc @@ -248,6 +248,7 @@ void InstanceImpl::shutdownThread() { // number than the first. This is an edge case that does not exist anywhere // in the code today, but we can keep this in mind if things become more // complicated in the future. + // NOLINTNEXTLINE(modernize-loop-convert) for (auto it = thread_local_data_.data_.rbegin(); it != thread_local_data_.data_.rend(); ++it) { it->reset(); } diff --git a/source/common/tls/cert_validator/default_validator.cc b/source/common/tls/cert_validator/default_validator.cc index ba223518e5813..ffc8771062e30 100644 --- a/source/common/tls/cert_validator/default_validator.cc +++ b/source/common/tls/cert_validator/default_validator.cc @@ -396,7 +396,7 @@ bool DefaultCertValidator::verifySubjectAltName(X509* cert, for (const GENERAL_NAME* general_name : san_names.get()) { const std::string san = Utility::generalNameAsString(general_name); for (auto& config_san : subject_alt_names) { - if (general_name->type == GEN_DNS ? Utility::dnsNameMatch(config_san, san.c_str()) + if (general_name->type == GEN_DNS ? Utility::dnsNameMatch(config_san, san) : config_san == san) { return true; } diff --git a/source/common/tls/connection_info_impl_base.cc b/source/common/tls/connection_info_impl_base.cc index effa8f73c3038..31e050d71a12e 100644 --- a/source/common/tls/connection_info_impl_base.cc +++ b/source/common/tls/connection_info_impl_base.cc @@ -33,6 +33,7 @@ std::string certToPem(X509& cert) { const uint8_t* output; size_t length; RELEASE_ASSERT(BIO_mem_contents(buf.get(), &output, &length) == 1, ""); + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(reinterpret_cast(output), length); } diff --git a/source/common/tls/ocsp/asn1_utility.cc b/source/common/tls/ocsp/asn1_utility.cc index 6db1ddb0e3943..394edff6f5de5 100644 --- a/source/common/tls/ocsp/asn1_utility.cc +++ b/source/common/tls/ocsp/asn1_utility.cc @@ -63,7 +63,7 @@ absl::StatusOr Asn1Utility::parseGeneralizedTime(CBS& cbs) { // Local time or time differential, though a part of the `ASN.1` // `GENERALIZEDTIME` spec, are not supported. // Reference: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2 - if (time_str.length() > 0 && absl::ascii_toupper(time_str.at(time_str.length() - 1)) != 'Z') { + if (!time_str.empty() && absl::ascii_toupper(time_str.at(time_str.length() - 1)) != 'Z') { return absl::InvalidArgumentError("GENERALIZEDTIME must be in UTC"); } diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 3c532c8b5e249..8162ecbf7b545 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -1553,9 +1553,9 @@ ClusterInfoImpl::processHttpForOutlierDetection(Http::ResponseHeaderMap& headers // Run matchers. http_protocol_options_->outlier_detection_http_error_matcher_[0]->onHttpResponseHeaders(headers, statuses); - return absl::optional(http_protocol_options_->outlier_detection_http_error_matcher_[0] - ->matchStatus(statuses) - .matches_); + return http_protocol_options_->outlier_detection_http_error_matcher_[0] + ->matchStatus(statuses) + .matches_; } absl::StatusOr validateTransportSocketSupportsQuic( diff --git a/source/extensions/access_loggers/open_telemetry/otlp_log_utils.cc b/source/extensions/access_loggers/open_telemetry/otlp_log_utils.cc index 29fd6c7d04454..81b74890cba1a 100644 --- a/source/extensions/access_loggers/open_telemetry/otlp_log_utils.cc +++ b/source/extensions/access_loggers/open_telemetry/otlp_log_utils.cc @@ -106,6 +106,7 @@ uint64_t getBufferSizeBytes( std::vector getFilterStateObjectsToLog( const envoy::extensions::access_loggers::open_telemetry::v3::OpenTelemetryAccessLogConfig& config) { + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::vector(config.filter_state_objects_to_log().begin(), config.filter_state_objects_to_log().end()); } diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.cc b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.cc index 7ab08bb4f11d7..f34efbaa73c5c 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.cc +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.cc @@ -342,7 +342,7 @@ void UpstreamSocketManager::markSocketDead(const int fd) { void UpstreamSocketManager::cleanStaleNodeEntry(const std::string& node_id) { // Clean the given node ID if there are no active sockets. if (accepted_reverse_connections_.find(node_id) != accepted_reverse_connections_.end() && - accepted_reverse_connections_[node_id].size() > 0) { + !accepted_reverse_connections_[node_id].empty()) { ENVOY_LOG(trace, "reverse_tunnel: found {} active sockets for node {}.", accepted_reverse_connections_[node_id].size(), node_id); return; diff --git a/source/extensions/clusters/dynamic_modules/cluster.cc b/source/extensions/clusters/dynamic_modules/cluster.cc index 2ff67bdc2cbc6..ed40ae8df9fc3 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.cc +++ b/source/extensions/clusters/dynamic_modules/cluster.cc @@ -474,7 +474,7 @@ size_t DynamicModuleCluster::removeHosts(const std::vector= 1); + ASSERT(!priority_set_.hostSetsPerPriority().empty()); const auto& first_host_set = priority_set_.getOrCreateHostSet(0); // Build a set of removed host pointers for O(1) lookup. diff --git a/source/extensions/clusters/reverse_connection/reverse_connection.cc b/source/extensions/clusters/reverse_connection/reverse_connection.cc index 4e0ceae7660b7..e3bd4a9c59976 100644 --- a/source/extensions/clusters/reverse_connection/reverse_connection.cc +++ b/source/extensions/clusters/reverse_connection/reverse_connection.cc @@ -284,8 +284,8 @@ RevConClusterFactory::createClusterWithConfig( } absl::Status creation_status = absl::OkStatus(); - auto new_cluster = std::shared_ptr( - new RevConCluster(cluster, context, creation_status, proto_config)); + auto new_cluster = + std::make_shared(cluster, context, creation_status, proto_config); RETURN_IF_NOT_OK(creation_status); auto lb = std::make_unique(new_cluster); return std::make_pair(new_cluster, std::move(lb)); diff --git a/source/extensions/config_subscription/grpc/grpc_mux_impl.h b/source/extensions/config_subscription/grpc/grpc_mux_impl.h index 604a91b787e2e..c2d3d11653eaf 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_impl.h +++ b/source/extensions/config_subscription/grpc/grpc_mux_impl.h @@ -104,7 +104,7 @@ class GrpcMuxImpl : public GrpcMux, grpc_stream_.get()) ->currentStreamForTest(); } - return *grpc_stream_.get(); + return *grpc_stream_; } private: diff --git a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h index e94d7ef317de0..6adb1dda36515 100644 --- a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h +++ b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h @@ -99,7 +99,7 @@ class NewGrpcMuxImpl grpc_stream_.get()) ->currentStreamForTest(); } - return *grpc_stream_.get(); + return *grpc_stream_; } struct SubscriptionStuff { diff --git a/source/extensions/content_parsers/json/json_content_parser_impl.cc b/source/extensions/content_parsers/json/json_content_parser_impl.cc index 5e2f57f1679e7..fcf89085f8d02 100644 --- a/source/extensions/content_parsers/json/json_content_parser_impl.cc +++ b/source/extensions/content_parsers/json/json_content_parser_impl.cc @@ -47,8 +47,7 @@ ContentParser::ParseResult JsonContentParserImpl::parse(absl::string_view data) bool all_rules_have_limits = true; bool all_limited_rules_satisfied = true; - for (size_t i = 0; i < rules_.size(); ++i) { - auto& rule = rules_[i]; + for (auto& rule : rules_) { // Track if any rule has no limit if (rule.stop_processing_after_matches_ == 0) { diff --git a/source/extensions/filters/http/a2a/a2a_filter.cc b/source/extensions/filters/http/a2a/a2a_filter.cc index d6c15e96bd1b6..e483b74dc0e42 100644 --- a/source/extensions/filters/http/a2a/a2a_filter.cc +++ b/source/extensions/filters/http/a2a/a2a_filter.cc @@ -144,8 +144,9 @@ Http::FilterDataStatus A2aFilter::decodeData(Buffer::Instance& data, bool end_st } } - if (max_size > 0 && bytes_parsed_ == max_size) + if (max_size > 0 && bytes_parsed_ == max_size) { break; + } } // If we are here, we haven't collected all fields yet. diff --git a/source/extensions/filters/http/cache_v2/cache_sessions_impl.cc b/source/extensions/filters/http/cache_v2/cache_sessions_impl.cc index 314865f265a8a..e6c689557836f 100644 --- a/source/extensions/filters/http/cache_v2/cache_sessions_impl.cc +++ b/source/extensions/filters/http/cache_v2/cache_sessions_impl.cc @@ -15,8 +15,6 @@ namespace Extensions { namespace HttpFilters { namespace CacheV2 { -using CancelWrapper::cancelWrapped; - class UpstreamRequestWithCacheabilityReset : public HttpSource { public: UpstreamRequestWithCacheabilityReset( @@ -248,7 +246,7 @@ void CacheSession::sendLookupResponsesAndMaybeValidationRequest(CacheEntryStatus lookup_subscribers_.erase(it, lookup_subscribers_.end()); if (!lookup_subscribers_.empty()) { // At least one subscriber required validation. - return performValidation(); + performValidation(); } } @@ -586,7 +584,8 @@ void CacheSession::getLookupResult(ActiveLookupRequestPtr lookup, ActiveLookupRe } else { sub.context_->lookup().stats().incCacheSessionsSubscribers(); lookup_subscribers_.push_back(std::move(sub)); - return performValidation(); + performValidation(); + return; } } auto result = std::make_unique(); @@ -771,7 +770,6 @@ void CacheSession::onUncacheable(Http::ResponseHeaderMapPtr headers, EndStream e cache_sessions->stats().subCacheSessionsSubscribers(lookup_subscribers_.size()); } lookup_subscribers_.clear(); - return; } void CacheSession::onUpstreamHeaders(Http::ResponseHeaderMapPtr headers, EndStream end_stream, diff --git a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.cc b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.cc index cea35b9e87347..799b8b9d9d328 100644 --- a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.cc +++ b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.cc @@ -105,8 +105,9 @@ bool ReadToBuffer(Protobuf::io::ZeroCopyInputStream& stream, Buffer::Instance& b const void* out; int size; while (stream.Next(&out, &size)) { - if (size == 0) + if (size == 0) { return true; + } buffer.add(out, size); } return false; @@ -192,8 +193,9 @@ bool GrpcJsonReverseTranscoderFilter::EncoderBufferLimitReached(uint64_t buffer_ bool GrpcJsonReverseTranscoderFilter::CheckAndRejectIfRequestTranscoderFailed() const { const auto& status = transcoder_->RequestStatus(); - if (status.ok()) + if (status.ok()) { return false; + } decoder_callbacks_->sendLocalReply( Code::BadRequest, status.message(), nullptr, Status::WellKnownGrpcStatus::InvalidArgument, absl::StrCat(RcDetails::get().grpc_transcode_failed, "{", @@ -204,8 +206,9 @@ bool GrpcJsonReverseTranscoderFilter::CheckAndRejectIfRequestTranscoderFailed() bool GrpcJsonReverseTranscoderFilter::CheckAndRejectIfResponseTranscoderFailed() const { const auto& status = transcoder_->ResponseStatus(); - if (status.ok()) + if (status.ok()) { return false; + } ENVOY_STREAM_LOG(error, "Response transcoding failed: {}", *encoder_callbacks_, status.message()); encoder_callbacks_->sendLocalReply( Code::InternalServerError, status.message(), nullptr, Status::WellKnownGrpcStatus::Internal, diff --git a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.cc b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.cc index 8022b06bff861..1a758633b0f4e 100644 --- a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.cc +++ b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.cc @@ -40,7 +40,6 @@ using ResponseTranslator = ::google::grpc::transcoding::JsonRequestTranslator; using ResponseInfo = ::google::grpc::transcoding::RequestInfo; using ::envoy::extensions::filters::http::grpc_json_reverse_transcoder::v3:: GrpcJsonReverseTranscoder; -using ::google::api::HttpRule; using ::google::grpc::transcoding::Transcoder; using ::google::grpc::transcoding::TranscoderInputStream; using ::google::grpc::transcoding::TypeHelper; diff --git a/source/extensions/filters/http/grpc_json_transcoder/http_body_utils.cc b/source/extensions/filters/http/grpc_json_transcoder/http_body_utils.cc index 16f4f6999507f..7b48e26151e41 100644 --- a/source/extensions/filters/http/grpc_json_transcoder/http_body_utils.cc +++ b/source/extensions/filters/http/grpc_json_transcoder/http_body_utils.cc @@ -87,6 +87,7 @@ void HttpBodyUtils::appendHttpBodyEnvelope( CodedOutputStream::VarintSize64(content_length); std::vector message_sizes; message_sizes.reserve(request_body_field_path.size()); + // NOLINTNEXTLINE(modernize-loop-convert) for (auto it = request_body_field_path.rbegin(); it != request_body_field_path.rend(); ++it) { const Protobuf::Field* field = *it; const uint64_t message_size = envelope_size + content_length; diff --git a/source/extensions/filters/http/mcp/mcp_filter.cc b/source/extensions/filters/http/mcp/mcp_filter.cc index bfe39332f0b07..379a649ffcd17 100644 --- a/source/extensions/filters/http/mcp/mcp_filter.cc +++ b/source/extensions/filters/http/mcp/mcp_filter.cc @@ -310,8 +310,9 @@ Http::FilterDataStatus McpFilter::decodeData(Buffer::Instance& data, bool end_st } } - if (max_size > 0 && bytes_parsed_ == max_size) + if (max_size > 0 && bytes_parsed_ == max_size) { break; + } } // If we are here, we haven't collected all fields yet. diff --git a/source/extensions/filters/http/mcp_router/mcp_router.cc b/source/extensions/filters/http/mcp_router/mcp_router.cc index 91c7667e61bd1..b6a6512908abf 100644 --- a/source/extensions/filters/http/mcp_router/mcp_router.cc +++ b/source/extensions/filters/http/mcp_router/mcp_router.cc @@ -677,8 +677,9 @@ void McpRouterFilter::initializeFanout(AggregationCallback callback) { // stores a pointer to the headers. auto stream_it = multistream_->begin(); for (const auto& backend : config_->backends()) { - if (stream_it == multistream_->end()) + if (stream_it == multistream_->end()) { break; + } auto headers = createUpstreamHeaders(backend, backend_sessions_[backend.name]); upstream_headers_.push_back(std::move(headers)); diff --git a/source/extensions/filters/http/transform/transform.cc b/source/extensions/filters/http/transform/transform.cc index 9a97b607de2c2..6f42688c586cb 100644 --- a/source/extensions/filters/http/transform/transform.cc +++ b/source/extensions/filters/http/transform/transform.cc @@ -101,7 +101,7 @@ Transformation::Transformation(const ProtoTransformation& config, } } - if (config.headers_mutations().size() > 0) { + if (!config.headers_mutations().empty()) { auto mutations_or = Http::HeaderMutations::create(config.headers_mutations(), context, bodyCommandParsers()); SET_AND_RETURN_IF_NOT_OK(mutations_or.status(), creation_status); diff --git a/source/extensions/filters/listener/tls_inspector/ja4_fingerprint.cc b/source/extensions/filters/listener/tls_inspector/ja4_fingerprint.cc index 3d8f92d319c85..c180bb9bcc9c5 100644 --- a/source/extensions/filters/listener/tls_inspector/ja4_fingerprint.cc +++ b/source/extensions/filters/listener/tls_inspector/ja4_fingerprint.cc @@ -176,6 +176,7 @@ std::string JA4Fingerprinter::getJA4CipherHash(const SSL_CLIENT_HELLO* ssl_clien } if (ciphers.empty()) { + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(JA4_HASH_LENGTH, '0'); } @@ -239,6 +240,7 @@ std::string JA4Fingerprinter::getJA4ExtensionHash(const SSL_CLIENT_HELLO* ssl_cl } if (extensions.empty()) { + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(JA4_HASH_LENGTH, '0'); } diff --git a/source/extensions/filters/network/redis_proxy/cluster_response_handler.cc b/source/extensions/filters/network/redis_proxy/cluster_response_handler.cc index 8ba3911a18a31..58f6d3c4e6ccf 100644 --- a/source/extensions/filters/network/redis_proxy/cluster_response_handler.cc +++ b/source/extensions/filters/network/redis_proxy/cluster_response_handler.cc @@ -243,7 +243,7 @@ bool AllshardSameResponseHandler::areAllResponsesSame() const { const Common::Redis::RespValue* first_response = pending_responses_.front().get(); for (const auto& response : pending_responses_) { - if (!response || !first_response || *(response.get()) != *first_response) { + if (!response || !first_response || *response != *first_response) { return false; } } @@ -354,12 +354,15 @@ void ArrayAppendAggregateResponseHandler::processAggregatedResponses( void HelloResponseHandler::processAggregatedResponses(ClusterScopeCmdRequest& request) { // Helper to check if two RespValues are equal auto valuesEqual = [](const Common::Redis::RespValue& v1, const Common::Redis::RespValue& v2) { - if (v1.type() != v2.type()) + if (v1.type() != v2.type()) { return false; - if (v1.type() == Common::Redis::RespType::BulkString) + } + if (v1.type() == Common::Redis::RespType::BulkString) { return v1.asString() == v2.asString(); - if (v1.type() == Common::Redis::RespType::Integer) + } + if (v1.type() == Common::Redis::RespType::Integer) { return v1.asInteger() == v2.asInteger(); + } return true; }; diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index fcef0f21d617b..07a932241e3b0 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -1163,7 +1163,7 @@ SplitRequestPtr InstanceImpl::makeRequest(Common::Redis::RespValuePtr&& request, } // Get the handler for the downstream request - auto handler = handler_lookup_table_.find(command_name.c_str()); + auto handler = handler_lookup_table_.find(command_name); ASSERT(handler != nullptr); // If we are within a transaction, forward all requests to the transaction handler (i.e. handler @@ -1227,7 +1227,7 @@ void InstanceImpl::addHandler(Stats::Scope& scope, const std::string& stat_prefi Stats::StatNameManagedStorage storage{command_stat_prefix + std::string("latency"), scope.symbolTable()}; handler_lookup_table_.add( - to_lower_name.c_str(), + to_lower_name, std::make_shared(HandlerData{ CommandStats{ALL_COMMAND_STATS(POOL_COUNTER_PREFIX(scope, command_stat_prefix)) scope.histogramFromStatName(storage.statName(), diff --git a/source/extensions/filters/network/redis_proxy/info_command_handler.cc b/source/extensions/filters/network/redis_proxy/info_command_handler.cc index 68568bc932fd7..d3df3c97b8066 100644 --- a/source/extensions/filters/network/redis_proxy/info_command_handler.cc +++ b/source/extensions/filters/network/redis_proxy/info_command_handler.cc @@ -444,6 +444,7 @@ std::string InfoCmdAggregateResponseHandler::bytesToHuman(uint64_t bytes) { snprintf(buffer, sizeof(buffer), "%" PRIu64 "B", bytes); } + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(buffer); } diff --git a/source/extensions/filters/network/redis_proxy/router_impl.cc b/source/extensions/filters/network/redis_proxy/router_impl.cc index 2298f7f7a564d..d519ae5524b67 100644 --- a/source/extensions/filters/network/redis_proxy/router_impl.cc +++ b/source/extensions/filters/network/redis_proxy/router_impl.cc @@ -85,8 +85,8 @@ PrefixRoutes::PrefixRoutes( absl::AsciiStrToLower(©); } - auto success = prefix_lookup_table_.add( - copy.c_str(), std::make_shared(route, upstreams_, runtime), false); + auto success = + prefix_lookup_table_.add(copy, std::make_shared(route, upstreams_, runtime), false); if (!success) { throw EnvoyException(fmt::format("prefix `{}` already exists.", route.prefix())); } diff --git a/source/extensions/load_balancing_policies/override_host/load_balancer.cc b/source/extensions/load_balancing_policies/override_host/load_balancer.cc index a7559994312f8..05929336163ea 100644 --- a/source/extensions/load_balancing_policies/override_host/load_balancer.cc +++ b/source/extensions/load_balancing_policies/override_host/load_balancer.cc @@ -208,7 +208,7 @@ void OverrideHostLoadBalancer::LoadBalancerImpl::addSelectedHostKey( const std::string selected_endpoint = response.host->address()->asString(); const Config::MetadataKey& metadata_key = config_.selectedHostKey().value(); - if (metadata_key.path_.size() < 1) { + if (metadata_key.path_.empty()) { // Should not be possible based on proto validation, catching anyways. ENVOY_LOG(trace, "Path was not provided in selected_host_key."); return; diff --git a/source/extensions/tracers/dynamic_modules/tracer_config.cc b/source/extensions/tracers/dynamic_modules/tracer_config.cc index a8027c7c99e64..ec46a08a79d9c 100644 --- a/source/extensions/tracers/dynamic_modules/tracer_config.cc +++ b/source/extensions/tracers/dynamic_modules/tracer_config.cc @@ -186,6 +186,7 @@ std::string DynamicModuleSpan::getBaggage(absl::string_view key) { envoy_dynamic_module_type_module_buffer value_out = {.ptr = nullptr, .length = 0}; if (config_->on_span_get_baggage_(in_module_span_, key_buf, &value_out) && value_out.ptr != nullptr) { + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(value_out.ptr, value_out.length); } return {}; @@ -202,6 +203,7 @@ void DynamicModuleSpan::setBaggage(absl::string_view key, absl::string_view valu std::string DynamicModuleSpan::getTraceId() const { envoy_dynamic_module_type_module_buffer value_out = {.ptr = nullptr, .length = 0}; if (config_->on_span_get_trace_id_(in_module_span_, &value_out) && value_out.ptr != nullptr) { + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(value_out.ptr, value_out.length); } return {}; @@ -210,6 +212,7 @@ std::string DynamicModuleSpan::getTraceId() const { std::string DynamicModuleSpan::getSpanId() const { envoy_dynamic_module_type_module_buffer value_out = {.ptr = nullptr, .length = 0}; if (config_->on_span_get_span_id_(in_module_span_, &value_out) && value_out.ptr != nullptr) { + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(value_out.ptr, value_out.length); } return {}; diff --git a/source/extensions/tracers/zipkin/span_context_extractor.cc b/source/extensions/tracers/zipkin/span_context_extractor.cc index c02ec29cc11e2..463e83231258b 100644 --- a/source/extensions/tracers/zipkin/span_context_extractor.cc +++ b/source/extensions/tracers/zipkin/span_context_extractor.cc @@ -171,7 +171,7 @@ SpanContextExtractor::extractSpanContextFromB3SingleFormat(bool is_sampled) { ASSERT(b3_head_entry.has_value()); // This is an implicitly untrusted header, so only the first value is used. const std::string b3(b3_head_entry.value()); - if (!b3.length()) { + if (b3.empty()) { throw ExtractorException("Invalid input: empty"); } diff --git a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc index 4284adbcf933d..e81194f5fedb2 100644 --- a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc @@ -92,7 +92,7 @@ parseTrustBundles(absl::string_view trust_bundle_mapping_str) { return false; } const auto& certs = key->getStringArray("x5c"); - if (!certs.ok() || (*certs).size() == 0) { + if (!certs.ok() || (*certs).empty()) { parsing_status = absl::InvalidArgumentError(fmt::format( "missing or empty 'x5c' field found in keys for domain: '{}'", domain_name)); return false; diff --git a/source/server/cgroup_cpu_util.cc b/source/server/cgroup_cpu_util.cc index 7623f4b0a4b2c..4892fd7138e92 100644 --- a/source/server/cgroup_cpu_util.cc +++ b/source/server/cgroup_cpu_util.cc @@ -405,8 +405,9 @@ absl::optional CgroupCpuUtil::discoverCgroupMount(Filesystem::Insta } line = line.substr(space_pos + 1); } - if (!line_valid) + if (!line_valid) { continue; + } // (5) mount point: extract mount point size_t mount_end = line.find(' '); @@ -441,8 +442,9 @@ absl::optional CgroupCpuUtil::discoverCgroupMount(Filesystem::Insta } line = line.substr(space_pos + 1); } - if (!line_valid || !separator_found) + if (!line_valid || !separator_found) { continue; + } // (9) filesystem type: extract filesystem type size_t fs_type_end = line.find(' '); From 1e9d667f49f8b4593de89a9d0b878e18fd00314c Mon Sep 17 00:00:00 2001 From: code Date: Sun, 10 May 2026 23:03:11 +0800 Subject: [PATCH 278/750] clang tidy: fix naming/enum cast and so on of source (#44977) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Apply clang-tidy fixes for naming conventions and enum-cast issues across `source/`. **This PR only fix naming of inner function/local variable. For any method/function that exposed in the header file, I didn’t actually resolve the problem but only suppress the issue to make clang tidy CI happy because naming change may break our forks.** In the future, we can use the `NOLINTNEXTLINE(readability-identifier-naming)` as tag to figure out the unexpected naming easily from the code base and resolve them one by one. ## Risk Level Low — mechanical clang-tidy refactor; no behavior changes intended. ## Testing CI. ## Docs Changes None. ## Release Notes None. Signed-off-by: wbpcode/wangbaiping --------- Signed-off-by: wbpcode/wangbaiping --- envoy/matcher/matcher.h | 5 +++ envoy/server/listener_manager.h | 2 ++ source/common/http/http2/codec_impl.cc | 6 ++-- source/common/http/http2/codec_impl.h | 2 ++ .../common/http/http2/protocol_constraints.h | 2 ++ source/common/http/muxdemux.h | 9 ++--- source/common/http/session_idle_list.h | 15 +++++++++ .../common/http/session_idle_list_interface.h | 4 +++ source/common/json/json_rpc_field_extractor.h | 4 ++- source/common/jwt/check_audience.h | 2 +- source/common/jwt/jwks.cc | 5 ++- source/common/jwt/jwks.h | 4 +-- source/common/jwt/simple_lru_cache_inl.h | 33 +++++++++++-------- source/common/jwt/struct_utils.h | 8 +++++ source/common/matcher/map_matcher.h | 3 ++ source/common/memory/aligned_allocator.h | 7 ++-- .../common/quic/envoy_quic_client_session.h | 3 ++ source/common/quic/envoy_quic_dispatcher.h | 1 + .../quic/envoy_quic_server_connection.cc | 1 + .../quic/envoy_quic_server_connection.h | 1 + .../common/quic/envoy_quic_server_session.cc | 4 +++ .../common/quic/envoy_quic_server_session.h | 4 +++ source/common/quic/envoy_quic_stream.h | 1 + source/common/quic/quic_stats_gatherer.h | 1 + source/common/router/retry_state_impl.cc | 1 + .../upstream/load_stats_reporter_impl.cc | 1 + source/common/version/BUILD | 1 + .../extensions/access_loggers/stats/stats.h | 4 +-- source/extensions/common/dubbo/codec.cc | 1 + .../filters/common/expr/evaluator.h | 2 ++ .../filters/http/cache_v2/cache_sessions.h | 4 +-- .../http/cache_v2/upstream_request_impl.cc | 6 ++-- .../ext_proc/processing_request_modifier.h | 2 +- .../file_server/absl_status_to_http_status.cc | 1 + .../message_converter/message_converter.cc | 2 ++ .../message_converter_utility.cc | 1 + .../grpc_json_reverse_transcoder/filter.cc | 12 +++---- .../grpc_json_reverse_transcoder/filter.h | 12 +++++++ .../filter_config.h | 4 +++ .../grpc_json_reverse_transcoder/utils.cc | 5 +-- .../http/grpc_json_reverse_transcoder/utils.h | 4 +++ .../extraction_util/extraction_util.cc | 5 +++ .../extraction_util/extraction_util.h | 17 ++++++++++ .../extraction_util/proto_extractor.h | 2 ++ .../proto_extractor_interface.h | 5 +-- .../http/proto_message_extraction/extractor.h | 2 ++ .../http/proto_message_extraction/filter.cc | 3 ++ .../http/rate_limit_quota/client_impl.h | 4 +-- .../http/set_metadata/set_metadata_filter.cc | 6 ++-- .../network/common/redis/supported_commands.h | 1 + .../dubbo_proxy/dubbo_protocol_impl.cc | 2 ++ .../redis_proxy/cluster_response_handler.h | 6 ++-- .../thrift_proxy/binary_protocol_impl.cc | 2 ++ .../matching/http/cel_input/cel_input.h | 2 ++ .../open_telemetry/open_telemetry_impl.h | 5 +-- .../tracers/datadog/agent_http_client.h | 1 + .../tracers/datadog/event_scheduler.h | 1 + .../transport_sockets/tap/tap_config_impl.cc | 18 +++++----- .../transport_sockets/tap/tap_config_impl.h | 2 +- .../transport_sockets/tcp_stats/tcp_stats.h | 1 + tools/spelling/spelling_dictionary.txt | 1 + 61 files changed, 208 insertions(+), 68 deletions(-) diff --git a/envoy/matcher/matcher.h b/envoy/matcher/matcher.h index f2aad92c4d118..1c9b7263e5544 100644 --- a/envoy/matcher/matcher.h +++ b/envoy/matcher/matcher.h @@ -83,6 +83,7 @@ enum class MatchResult { }; // Prints a human-readable string representing the MatchResult. +// NOLINTNEXTLINE(readability-identifier-naming) inline static std::string MatchResultToString(MatchResult match_result) { switch (match_result) { case MatchResult::Matched: @@ -337,6 +338,7 @@ class DataInputGetResult : public NonCopyable { } static DataInputGetResult + // NOLINTNEXTLINE(readability-identifier-naming) NoData(DataAvailability data_availability = DataAvailability::AllDataAvailable) { return {absl::monostate(), data_availability}; } @@ -346,18 +348,21 @@ class DataInputGetResult : public NonCopyable { *duration of matching. Use CreateString when a string must be constructed. **/ static DataInputGetResult + // NOLINTNEXTLINE(readability-identifier-naming) CreateStringView(absl::string_view data, DataAvailability data_availability = DataAvailability::AllDataAvailable) { return {data, data_availability}; } static DataInputGetResult + // NOLINTNEXTLINE(readability-identifier-naming) CreateString(std::string&& data, DataAvailability data_availability = DataAvailability::AllDataAvailable) { return {std::move(data), data_availability}; } static DataInputGetResult + // NOLINTNEXTLINE(readability-identifier-naming) CreateCustom(std::shared_ptr&& data, DataAvailability data_availability = DataAvailability::AllDataAvailable) { return {std::move(data), data_availability}; diff --git a/envoy/server/listener_manager.h b/envoy/server/listener_manager.h index 161d2f5196d08..da8bd71df2422 100644 --- a/envoy/server/listener_manager.h +++ b/envoy/server/listener_manager.h @@ -323,6 +323,8 @@ class ListenerManager { // combination of flags, such as listeners(ListenerState::WARMING|ListenerState::ACTIVE) constexpr ListenerManager::ListenerState operator|(const ListenerManager::ListenerState lhs, const ListenerManager::ListenerState rhs) { + // Bitmask combinations intentionally produce intermediate values that are not named enumerators. + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) | static_cast(rhs)); } diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 64c8c5d85fccf..88c8aa446cf45 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -213,7 +213,7 @@ using OnHeaderResult = http2::adapter::Http2VisitorInterface::OnHeaderResult; enum Settings { // SETTINGS_HEADER_TABLE_SIZE = 0x01, // SETTINGS_ENABLE_PUSH = 0x02, - SETTINGS_MAX_CONCURRENT_STREAMS = 0x03, + SETTINGS_MAX_CONCURRENT_STREAMS = 0x03, // NOLINT(readability-identifier-naming) // SETTINGS_INITIAL_WINDOW_SIZE = 0x04, // SETTINGS_MAX_FRAME_SIZE = 0x05, // SETTINGS_MAX_HEADER_LIST_SIZE = 0x06, @@ -223,9 +223,9 @@ enum Settings { enum Flags { // FLAG_NONE = 0, - FLAG_END_STREAM = 0x01, + FLAG_END_STREAM = 0x01, // NOLINT(readability-identifier-naming) // FLAG_END_HEADERS = 0x04, - FLAG_ACK = 0x01, + FLAG_ACK = 0x01, // NOLINT(readability-identifier-naming) // FLAG_PADDED = 0x08, // FLAG_PRIORITY = 0x20 }; diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 20a620efdc2d9..4a0cf2c23094e 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -44,6 +44,7 @@ namespace Http { namespace Http2 { // Types inherited from nghttp2 and preserved in oghttp2 +// NOLINTBEGIN(readability-identifier-naming) enum ErrorType { OGHTTP2_NO_ERROR, OGHTTP2_PROTOCOL_ERROR, @@ -60,6 +61,7 @@ enum ErrorType { OGHTTP2_INADEQUATE_SECURITY, OGHTTP2_HTTP_1_1_REQUIRED, }; +// NOLINTEND(readability-identifier-naming) class Http2CodecImplTestFixture; diff --git a/source/common/http/http2/protocol_constraints.h b/source/common/http/http2/protocol_constraints.h index 86367d6d956cd..ad821c19e15b3 100644 --- a/source/common/http/http2/protocol_constraints.h +++ b/source/common/http/http2/protocol_constraints.h @@ -18,6 +18,7 @@ namespace Http { namespace Http2 { // Frame types as inherited from nghttp2 and preserved for oghttp2 +// NOLINTBEGIN(readability-identifier-naming) enum FrameType { OGHTTP2_DATA_FRAME_TYPE, OGHTTP2_HEADERS_FRAME_TYPE, @@ -30,6 +31,7 @@ enum FrameType { OGHTTP2_WINDOW_UPDATE_FRAME_TYPE, OGHTTP2_CONTINUATION_FRAME_TYPE, }; +// NOLINTEND(readability-identifier-naming) // Class for detecting abusive peers and validating additional constraints imposed by Envoy. // This class does not check protocol compliance with the H/2 standard, as this is checked by diff --git a/source/common/http/muxdemux.h b/source/common/http/muxdemux.h index 1dd8c3b6892b7..29af2e00045ac 100644 --- a/source/common/http/muxdemux.h +++ b/source/common/http/muxdemux.h @@ -50,10 +50,11 @@ class MultiStream { // Iterator over streams. Allows sending different headers, body or trailers to different streams. struct StreamIterator { - using difference_type = std::ptrdiff_t; - using element_type = AsyncClient::Stream*; - using pointer = element_type*; - using reference = element_type&; + // Standard iterator aliases intentionally use STL-prescribed snake_case names. + using difference_type = std::ptrdiff_t; // NOLINT(readability-identifier-naming) + using element_type = AsyncClient::Stream*; // NOLINT(readability-identifier-naming) + using pointer = element_type*; // NOLINT(readability-identifier-naming) + using reference = element_type&; // NOLINT(readability-identifier-naming) explicit StreamIterator(std::vector::iterator it) : it(it) {} StreamIterator() = default; diff --git a/source/common/http/session_idle_list.h b/source/common/http/session_idle_list.h index 8c00f3f7b0cb6..16dd68887df83 100644 --- a/source/common/http/session_idle_list.h +++ b/source/common/http/session_idle_list.h @@ -30,24 +30,30 @@ class SessionIdleList : public SessionIdleListInterface, public Logger::Loggable ~SessionIdleList() override = default; // Adds a session to the idle list. + // NOLINTNEXTLINE(readability-identifier-naming) void AddSession(IdleSessionInterface& session) override; // Removes a session from the idle list. + // NOLINTNEXTLINE(readability-identifier-naming) void RemoveSession(IdleSessionInterface& session) override; // Terminates idle sessions if they are eligible for termination. This is // called by the worker thread when the system is overloaded. + // NOLINTNEXTLINE(readability-identifier-naming) void MaybeTerminateIdleSessions(bool is_saturated) override; // Sets the minimum time before a session can be terminated. + // NOLINTNEXTLINE(readability-identifier-naming) void set_min_time_before_termination_allowed(absl::Duration min_time_before_termination_allowed) { min_time_before_termination_allowed_ = min_time_before_termination_allowed; }; + // NOLINTNEXTLINE(readability-identifier-naming) void set_max_sessions_to_terminate_in_one_round(int max_sessions_to_terminate_in_one_round) { max_sessions_to_terminate_in_one_round_ = max_sessions_to_terminate_in_one_round; } + // NOLINTNEXTLINE(readability-identifier-naming) void set_max_sessions_to_terminate_in_one_round_when_saturated( int max_sessions_to_terminate_in_one_round_when_saturated) { max_sessions_to_terminate_in_one_round_when_saturated_ = @@ -55,6 +61,7 @@ class SessionIdleList : public SessionIdleListInterface, public Logger::Loggable } // Sets whether to ignore the minimum time before a session can be terminated. + // NOLINTNEXTLINE(readability-identifier-naming) void set_ignore_min_time_before_termination_allowed(bool ignore) { ignore_min_time_before_termination_allowed_ = ignore; }; @@ -88,16 +95,21 @@ class SessionIdleList : public SessionIdleListInterface, public Logger::Loggable IdleSessions(const IdleSessions&) = delete; IdleSessions& operator=(const IdleSessions&) = delete; + // NOLINTNEXTLINE(readability-identifier-naming) IdleSessionInterface& next_session_to_terminate() { return *set_.begin()->session; } + // NOLINTNEXTLINE(readability-identifier-naming) void AddSessionToList(MonotonicTime enqueue_time, IdleSessionInterface& session); + // NOLINTNEXTLINE(readability-identifier-naming) void RemoveSessionFromList(IdleSessionInterface& session); // Get the time at which the session was added to the idle list. + // NOLINTNEXTLINE(readability-identifier-naming) MonotonicTime GetEnqueueTime(IdleSessionInterface& session) const; // Returns true if the session is in the map. For testing only. + // NOLINTNEXTLINE(readability-identifier-naming) bool ContainsForTest(IdleSessionInterface& session) const { return map_.contains(&session); } size_t size() const { return set_.size(); } @@ -111,14 +123,17 @@ class SessionIdleList : public SessionIdleListInterface, public Logger::Loggable IdleSessionMap map_; }; + // NOLINTNEXTLINE(readability-identifier-naming) const IdleSessions* idle_sessions() const { return &idle_sessions_; } // If this is > 0 then we do not terminate more than that many // sessions in a single attempt. This prevents us from doing too // much work in a single round. We want a small constant for this. + // NOLINTNEXTLINE(readability-identifier-naming) size_t MaxSessionsToTerminateInOneRound(bool is_saturated) const; // Returns the minimum time before a session can be terminated. + // NOLINTNEXTLINE(readability-identifier-naming) absl::Duration MinTimeBeforeTerminationAllowed() const; Event::Dispatcher& dispatcher_; diff --git a/source/common/http/session_idle_list_interface.h b/source/common/http/session_idle_list_interface.h index d0e13451f0aab..938f7fe83f83c 100644 --- a/source/common/http/session_idle_list_interface.h +++ b/source/common/http/session_idle_list_interface.h @@ -10,6 +10,7 @@ class IdleSessionInterface { // Terminates the idle session. This is called by the SessionIdleList when // the system is overloaded and the session is eligible for termination. + // NOLINTNEXTLINE(readability-identifier-naming) virtual void TerminateIdleSession() = 0; }; @@ -19,13 +20,16 @@ class SessionIdleListInterface { virtual ~SessionIdleListInterface() = default; // Adds a session to the idle list. + // NOLINTNEXTLINE(readability-identifier-naming) virtual void AddSession(IdleSessionInterface& session) = 0; // Removes a session from the idle list. + // NOLINTNEXTLINE(readability-identifier-naming) virtual void RemoveSession(IdleSessionInterface& session) = 0; // Terminates idle sessions if they are eligible for termination. This is // called by the worker thread when the system is overloaded. + // NOLINTNEXTLINE(readability-identifier-naming) virtual void MaybeTerminateIdleSessions(bool is_saturated) = 0; }; diff --git a/source/common/json/json_rpc_field_extractor.h b/source/common/json/json_rpc_field_extractor.h index 399a9fcd1b65a..0838e82fb36b6 100644 --- a/source/common/json/json_rpc_field_extractor.h +++ b/source/common/json/json_rpc_field_extractor.h @@ -82,6 +82,7 @@ class JsonRpcFieldExtractor : public ProtobufUtil::converter::ObjectWriter, virtual absl::string_view jsonRpcVersion() const = 0; virtual absl::string_view jsonRpcField() const = 0; virtual absl::string_view methodField() const = 0; + // NOLINTNEXTLINE(readability-identifier-naming) virtual bool lists_supported() const = 0; Protobuf::Struct temp_storage_; // Store all fields temporarily @@ -92,7 +93,8 @@ class JsonRpcFieldExtractor : public ProtobufUtil::converter::ObjectWriter, struct NestedContext { Protobuf::Struct* struct_ptr{nullptr}; Protobuf::ListValue* list_ptr{nullptr}; - std::string field_name{}; + std::string field_name; + // NOLINTNEXTLINE(readability-identifier-naming) bool is_list() const { return list_ptr != nullptr; } }; std::stack context_stack_; diff --git a/source/common/jwt/check_audience.h b/source/common/jwt/check_audience.h index 2b075425a4870..bd9e25c73a98d 100644 --- a/source/common/jwt/check_audience.h +++ b/source/common/jwt/check_audience.h @@ -39,7 +39,7 @@ class CheckAudience { std::set config_audiences_; }; -typedef std::unique_ptr CheckAudiencePtr; +using CheckAudiencePtr = std::unique_ptr; } // namespace JwtVerify } // namespace Envoy diff --git a/source/common/jwt/jwks.cc b/source/common/jwt/jwks.cc index 6bc3642a0400a..dcb0b6d429d8b 100644 --- a/source/common/jwt/jwks.cc +++ b/source/common/jwt/jwks.cc @@ -4,8 +4,7 @@ #include "source/common/jwt/jwks.h" -#include - +#include #include #include "source/common/jwt/struct_utils.h" @@ -133,7 +132,7 @@ class KeyGetter : public WithStatus { if (!absl::WebSafeBase64Unescape(s, &s_decoded)) { return nullptr; } - return bssl::UniquePtr(BN_bin2bn(castToUChar(s_decoded), s_decoded.length(), NULL)); + return bssl::UniquePtr(BN_bin2bn(castToUChar(s_decoded), s_decoded.length(), nullptr)); }; }; diff --git a/source/common/jwt/jwks.h b/source/common/jwt/jwks.h index 1769405dca1e4..d568253c1dc73 100644 --- a/source/common/jwt/jwks.h +++ b/source/common/jwt/jwks.h @@ -53,7 +53,7 @@ class Jwks : public WithStatus { bssl::UniquePtr bio_; bssl::UniquePtr x509_; }; - typedef std::unique_ptr PubkeyPtr; + using PubkeyPtr = std::unique_ptr; // Access to list of Jwks const std::vector& keys() const { return keys_; } @@ -68,7 +68,7 @@ class Jwks : public WithStatus { std::vector keys_; }; -typedef std::unique_ptr JwksPtr; +using JwksPtr = std::unique_ptr; } // namespace JwtVerify } // namespace Envoy diff --git a/source/common/jwt/simple_lru_cache_inl.h b/source/common/jwt/simple_lru_cache_inl.h index c76b5d6aedb91..c4a1f84bd11f6 100644 --- a/source/common/jwt/simple_lru_cache_inl.h +++ b/source/common/jwt/simple_lru_cache_inl.h @@ -104,10 +104,12 @@ template class SimpleLRUCacheConstIterator : public std::iterator> { public: - typedef typename MapType::const_iterator HashMapConstIterator; + using HashMapConstIterator = typename MapType::const_iterator; // Allow parent template's types to be referenced without qualification. - typedef typename SimpleLRUCacheConstIterator::reference reference; - typedef typename SimpleLRUCacheConstIterator::pointer pointer; + // NOLINTNEXTLINE(readability-identifier-naming) + using reference = typename SimpleLRUCacheConstIterator::reference; + // NOLINTNEXTLINE(readability-identifier-naming) + using pointer = typename SimpleLRUCacheConstIterator::pointer; // This default constructed Iterator can only be assigned to or destroyed. // All other operations give undefined behaviour. @@ -120,10 +122,12 @@ class SimpleLRUCacheConstIterator // For LRU mode, last_use_time() returns elements last use time. // See getLastUseTime() description for more information. + // NOLINTNEXTLINE(readability-identifier-naming) int64_t last_use_time() const { return last_use_; } // For age-based mode, insertion_time() returns elements insertion time. // See getInsertionTime() description for more information. + // NOLINTNEXTLINE(readability-identifier-naming) int64_t insertion_time() const { return last_use_; } friend bool operator==(const SimpleLRUCacheConstIterator& a, @@ -195,7 +199,9 @@ class SimpleLRUCacheOptions { // the element ordering (for LRU eviction) will be updated. // This value must be the same for both lookup and release. // The default is true. + // NOLINTNEXTLINE(readability-identifier-naming) bool update_eviction_order() const { return update_eviction_order_; } + // NOLINTNEXTLINE(readability-identifier-naming) void set_update_eviction_order(bool v) { update_eviction_order_ = v; } private: @@ -450,7 +456,8 @@ template class SimpleLRUCacheB } // STL style const_iterator support - typedef SimpleLRUCacheConstIterator const_iterator; + // NOLINTNEXTLINE(readability-identifier-naming) + using const_iterator = SimpleLRUCacheConstIterator; friend class SimpleLRUCacheConstIterator; const_iterator begin() const { return const_iterator(table_.begin(), table_.end()); } const_iterator end() const { return const_iterator(table_.end(), table_.end()); } @@ -491,13 +498,13 @@ template class SimpleLRUCacheB virtual bool isOverfull() const { return units_ > max_units_; } private: - typedef SimpleLRUCacheElem Elem; - typedef MapType Table; - typedef typename Table::iterator TableIterator; - typedef typename Table::const_iterator TableConstIterator; - typedef MapType DeferredTable; - typedef typename DeferredTable::iterator DeferredTableIterator; - typedef typename DeferredTable::const_iterator DeferredTableConstIterator; + using Elem = SimpleLRUCacheElem; + using Table = MapType; + using TableIterator = typename Table::iterator; + using TableConstIterator = typename Table::const_iterator; + using DeferredTable = MapType; + using DeferredTableIterator = typename DeferredTable::iterator; + using DeferredTableConstIterator = typename DeferredTable::const_iterator; Table table_; // Main table // Pinned entries awaiting to be released before they can be discarded. @@ -1038,8 +1045,8 @@ template class SimpleLRUCacheWithDeleter : public SimpleLRUCacheBase< Key, Value, absl::flat_hash_map*, H, EQ>, EQ> { - typedef absl::flat_hash_map*, H, EQ> HashMap; - typedef SimpleLRUCacheBase Base; + using HashMap = absl::flat_hash_map*, H, EQ>; + using Base = SimpleLRUCacheBase; public: explicit SimpleLRUCacheWithDeleter(int64_t total_units) : Base(total_units), deleter_() {} diff --git a/source/common/jwt/struct_utils.h b/source/common/jwt/struct_utils.h index 116d9bf3985f2..2d92e8beaee3c 100644 --- a/source/common/jwt/struct_utils.h +++ b/source/common/jwt/struct_utils.h @@ -15,29 +15,37 @@ class StructUtils { public: StructUtils(const Protobuf::Struct& struct_pb); + // NOLINTBEGIN(readability-identifier-naming) enum FindResult { OK = 0, MISSING, WRONG_TYPE, OUT_OF_RANGE, }; + // NOLINTEND(readability-identifier-naming) + // NOLINTNEXTLINE(readability-identifier-naming) FindResult GetString(const std::string& name, std::string* str_value); // Return error if the JSON value is not within a positive 64 bit integer // range. The decimals in the JSON value are dropped. + // NOLINTNEXTLINE(readability-identifier-naming) FindResult GetUInt64(const std::string& name, uint64_t* int_value); + // NOLINTNEXTLINE(readability-identifier-naming) FindResult GetDouble(const std::string& name, double* double_value); + // NOLINTNEXTLINE(readability-identifier-naming) FindResult GetBoolean(const std::string& name, bool* bool_value); // Get string or list of string, designed to get "aud" field // "aud" can be either string array or string. // Try as string array, read it as empty array if doesn't exist. + // NOLINTNEXTLINE(readability-identifier-naming) FindResult GetStringList(const std::string& name, std::vector* list); // Find the value with nested names. + // NOLINTNEXTLINE(readability-identifier-naming) FindResult GetValue(const std::string& nested_names, const Protobuf::Value*& found); private: diff --git a/source/common/matcher/map_matcher.h b/source/common/matcher/map_matcher.h index edc23fb372f34..3a0dda8d852ec 100644 --- a/source/common/matcher/map_matcher.h +++ b/source/common/matcher/map_matcher.h @@ -17,6 +17,9 @@ class MapMatcher : public MatchTree, Logger::Loggable&& on_match) PURE; ActionMatchResult doNoMatch(const DataType& data, SkippedMatchCb skipped_match_cb) { + // This is not true redundant-smartptr-get but we still add a NOLINT to make the + // clang-tidy check happy. + // NOLINTNEXTLINE(readability-redundant-smartptr-get) if (data_input_->get(data).availability() == DataAvailability::MoreDataMightBeAvailable) { return ActionMatchResult::insufficientData(); } diff --git a/source/common/memory/aligned_allocator.h b/source/common/memory/aligned_allocator.h index f45e7f74b2263..94cb5b14d8385 100644 --- a/source/common/memory/aligned_allocator.h +++ b/source/common/memory/aligned_allocator.h @@ -20,13 +20,14 @@ template class AlignedAllocator { static_assert((Alignment % sizeof(void*)) == 0, "Alignment must be a multiple of sizeof(void*) when using posix_memalign"); #endif - using value_type = T; + using value_type = T; // NOLINT(readability-identifier-naming) AlignedAllocator() noexcept = default; // Copy constructor for rebind compatibility. template explicit AlignedAllocator(const AlignedAllocator&) noexcept {} + // NOLINTNEXTLINE(readability-identifier-naming) static std::size_t round_up_to_alignment(std::size_t bytes) { return (bytes + Alignment - 1) & ~(Alignment - 1); } @@ -72,8 +73,8 @@ template class AlignedAllocator { } // Satisfy libc++ requirement. - template struct rebind { - using other = AlignedAllocator; + template struct rebind { // NOLINT(readability-identifier-naming) + using other = AlignedAllocator; // NOLINT(readability-identifier-naming) }; }; diff --git a/source/common/quic/envoy_quic_client_session.h b/source/common/quic/envoy_quic_client_session.h index b69459a6aa4ff..9a5831eeafa70 100644 --- a/source/common/quic/envoy_quic_client_session.h +++ b/source/common/quic/envoy_quic_client_session.h @@ -105,12 +105,15 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, // Register this session to the given registry for receiving network change events. void registerNetworkObserver(EnvoyQuicNetworkObserverRegistry& registry); + // NOLINTNEXTLINE(readability-identifier-naming) const quic::TransportParameters::ParameterMap& received_custom_transport_parameters() { return received_custom_transport_parameters_; } + // NOLINTNEXTLINE(readability-identifier-naming) const absl::optional& received_ipv6_alternate_server_address() { return received_ipv6_alternate_server_address_; } + // NOLINTNEXTLINE(readability-identifier-naming) const absl::optional& received_ipv4_alternate_server_address() { return received_ipv4_alternate_server_address_; } diff --git a/source/common/quic/envoy_quic_dispatcher.h b/source/common/quic/envoy_quic_dispatcher.h index 5eacabd3e3d3f..282e45c728016 100644 --- a/source/common/quic/envoy_quic_dispatcher.h +++ b/source/common/quic/envoy_quic_dispatcher.h @@ -103,6 +103,7 @@ class EnvoyQuicDispatcher : public quic::QuicDispatcher { private: friend class EnvoyQuicDispatcherTest; + // NOLINTNEXTLINE(readability-identifier-naming) Http::SessionIdleListInterface* idle_session_list() { return session_idle_list_.get(); } Network::ConnectionHandler& connection_handler_; diff --git a/source/common/quic/envoy_quic_server_connection.cc b/source/common/quic/envoy_quic_server_connection.cc index 773be68edad3d..ee26ac3508786 100644 --- a/source/common/quic/envoy_quic_server_connection.cc +++ b/source/common/quic/envoy_quic_server_connection.cc @@ -66,6 +66,7 @@ bool EnvoyQuicServerConnection::OnPacketHeader(const quic::QuicPacketHeader& hea return true; } +// NOLINTNEXTLINE(readability-identifier-naming) void EnvoyQuicServerConnection::OnWritePacketDone(size_t packet_size, const quic::WriteResult& /*result*/) { if (hasConnectionStats()) { diff --git a/source/common/quic/envoy_quic_server_connection.h b/source/common/quic/envoy_quic_server_connection.h index 759d2869cd186..246918ae4c15e 100644 --- a/source/common/quic/envoy_quic_server_connection.h +++ b/source/common/quic/envoy_quic_server_connection.h @@ -160,6 +160,7 @@ class EnvoyQuicServerConnection : public quic::QuicConnection, public QuicNetwor private: // Called when a packet is written to the packet writer. + // NOLINTNEXTLINE(readability-identifier-naming) void OnWritePacketDone(size_t packet_size, const quic::WriteResult& result); std::unique_ptr listener_filter_manager_; bool first_packet_received_ = false; diff --git a/source/common/quic/envoy_quic_server_session.cc b/source/common/quic/envoy_quic_server_session.cc index f0fabfea8df25..5925dfb4c0c6a 100644 --- a/source/common/quic/envoy_quic_server_session.cc +++ b/source/common/quic/envoy_quic_server_session.cc @@ -312,6 +312,7 @@ void EnvoyQuicServerSession::OnStreamClosed(quic::QuicStreamId id) { } } +// NOLINTNEXTLINE(readability-identifier-naming) void EnvoyQuicServerSession::TerminateIdleSession() { ENVOY_BUG(!on_connection_closed_called_, "TerminateIdleSession called after session on close called."); @@ -319,8 +320,10 @@ void EnvoyQuicServerSession::TerminateIdleSession() { quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); } +// NOLINTNEXTLINE(readability-identifier-naming) void EnvoyQuicServerSession::OnLastActiveStreamClosed() { MaybeAddSessionToIdleList(); } +// NOLINTNEXTLINE(readability-identifier-naming) void EnvoyQuicServerSession::MaybeAddSessionToIdleList() { if (session_idle_list_ == nullptr || is_in_idle_list_) { return; @@ -329,6 +332,7 @@ void EnvoyQuicServerSession::MaybeAddSessionToIdleList() { session_idle_list_->AddSession(*this); } +// NOLINTNEXTLINE(readability-identifier-naming) void EnvoyQuicServerSession::MaybeRemoveSessionFromIdleList() { if (session_idle_list_ == nullptr || !is_in_idle_list_) { return; diff --git a/source/common/quic/envoy_quic_server_session.h b/source/common/quic/envoy_quic_server_session.h index ef233194c8688..9fb0f674cbb8b 100644 --- a/source/common/quic/envoy_quic_server_session.h +++ b/source/common/quic/envoy_quic_server_session.h @@ -126,6 +126,7 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, void OnStreamClosed(quic::QuicStreamId id) override; // IdleSessionInterface + // NOLINTNEXTLINE(readability-identifier-naming) void TerminateIdleSession() override; using quic::QuicSession::PerformActionOnActiveStreams; @@ -149,12 +150,15 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, // Used by base class to access quic connection after initialization. const quic::QuicConnection* quicConnection() const override; quic::QuicConnection* quicConnection() override; + // NOLINTNEXTLINE(readability-identifier-naming) void MaybeAddSessionToIdleList(); + // NOLINTNEXTLINE(readability-identifier-naming) void MaybeRemoveSessionFromIdleList(); private: void setUpRequestDecoder(EnvoyQuicServerStream& stream); void ActivateStream(std::unique_ptr stream) override; + // NOLINTNEXTLINE(readability-identifier-naming) void OnLastActiveStreamClosed(); std::unique_ptr quic_connection_; diff --git a/source/common/quic/envoy_quic_stream.h b/source/common/quic/envoy_quic_stream.h index 81491dcd0ec38..11d8d720b68f4 100644 --- a/source/common/quic/envoy_quic_stream.h +++ b/source/common/quic/envoy_quic_stream.h @@ -245,6 +245,7 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, std::string quicStreamState(); + // NOLINTNEXTLINE(readability-identifier-naming) http2::adapter::HeaderValidator& header_validator() { return header_validator_; } #ifdef ENVOY_ENABLE_HTTP_DATAGRAMS diff --git a/source/common/quic/quic_stats_gatherer.h b/source/common/quic/quic_stats_gatherer.h index 578a760570252..b086d60f20974 100644 --- a/source/common/quic/quic_stats_gatherer.h +++ b/source/common/quic/quic_stats_gatherer.h @@ -59,6 +59,7 @@ class QuicStatsGatherer : public quic::QuicAckListenerInterface { } bool loggingDone() { return logging_done_; } uint64_t bytesOutstanding() { return bytes_outstanding_; } + // NOLINTNEXTLINE(readability-identifier-naming) bool notify_ack_listener_before_soon_to_be_destroyed() const { return notify_ack_listener_before_soon_to_be_destroyed_; } diff --git a/source/common/router/retry_state_impl.cc b/source/common/router/retry_state_impl.cc index 180d35b52bb88..71c7377dec4ed 100644 --- a/source/common/router/retry_state_impl.cc +++ b/source/common/router/retry_state_impl.cc @@ -368,6 +368,7 @@ RetryStateImpl::wouldRetryFromHeaders(const Http::ResponseHeaderMap& response_he } if ((retry_on_ & RetryPolicy::RETRY_ON_RETRIABLE_4XX)) { + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) Http::Code code = static_cast(response_status); if (code == Http::Code::Conflict) { return RetryDecision::RetryWithBackoff; diff --git a/source/common/upstream/load_stats_reporter_impl.cc b/source/common/upstream/load_stats_reporter_impl.cc index 51e0ad4dd0d28..a4f9338f70dbc 100644 --- a/source/common/upstream/load_stats_reporter_impl.cc +++ b/source/common/upstream/load_stats_reporter_impl.cc @@ -12,6 +12,7 @@ namespace Upstream { namespace { envoy::service::load_stats::v3::LoadStatsRequest +// NOLINTNEXTLINE(readability-identifier-naming) MakeRequestTemplate(const LocalInfo::LocalInfo& local_info) { envoy::service::load_stats::v3::LoadStatsRequest request; request.mutable_node()->MergeFrom(local_info.node()); diff --git a/source/common/version/BUILD b/source/common/version/BUILD index f324e41a6cd0a..9fa7def615687 100644 --- a/source/common/version/BUILD +++ b/source/common/version/BUILD @@ -67,6 +67,7 @@ envoy_cc_library( "//bazel:using_openssl": ["-DENVOY_SSL_VERSION=\\\"OpenSSL\\\""], "//conditions:default": ["-DENVOY_SSL_VERSION=\\\"BoringSSL\\\""], }), + tags = ["notidy"], deps = [ ":version_string_interface_lib", ":version_suffix_default", diff --git a/source/extensions/access_loggers/stats/stats.h b/source/extensions/access_loggers/stats/stats.h index d96b8e8f7d988..4762bc35a2efe 100644 --- a/source/extensions/access_loggers/stats/stats.h +++ b/source/extensions/access_loggers/stats/stats.h @@ -145,8 +145,8 @@ class StatsAccessLog : public AccessLoggers::Common::ImplBase, struct Gauge { enum class OperationType { SET, - PAIRED_ADD, - PAIRED_SUBTRACT, + PAIRED_ADD, // NOLINT(readability-identifier-naming) + PAIRED_SUBTRACT, // NOLINT(readability-identifier-naming) }; NameAndTags stat_; diff --git a/source/extensions/common/dubbo/codec.cc b/source/extensions/common/dubbo/codec.cc index d02012b88506e..89c9333d0adf5 100644 --- a/source/extensions/common/dubbo/codec.cc +++ b/source/extensions/common/dubbo/codec.cc @@ -121,6 +121,7 @@ void parseRequestInfoFromBuffer(Buffer::Instance& data, Context& context) { void parseResponseInfoFromBuffer(Buffer::Instance& buffer, Context& context) { ASSERT(buffer.length() >= DubboCodec::HeadersSize); + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) ResponseStatus status = static_cast(buffer.peekInt(StatusOffset)); if (!isValidResponseStatus(status)) { throw EnvoyException( diff --git a/source/extensions/filters/common/expr/evaluator.h b/source/extensions/filters/common/expr/evaluator.h index f6c8467bc927b..c1bb8fd898922 100644 --- a/source/extensions/filters/common/expr/evaluator.h +++ b/source/extensions/filters/common/expr/evaluator.h @@ -59,7 +59,9 @@ class StreamActivation : public google::api::expr::runtime::BaseActivation { FindFunctionOverloads(absl::string_view) const override { return {}; } + // NOLINTNEXTLINE(readability-identifier-naming) bool needs_response_path_data() const { return needs_response_path_data_; } + // NOLINTNEXTLINE(readability-identifier-naming) bool has_response_data() const { return activation_response_headers_ != nullptr || activation_response_trailers_ != nullptr; } diff --git a/source/extensions/filters/http/cache_v2/cache_sessions.h b/source/extensions/filters/http/cache_v2/cache_sessions.h index 818c1dcf08ef6..ae9e48b6b272f 100644 --- a/source/extensions/filters/http/cache_v2/cache_sessions.h +++ b/source/extensions/filters/http/cache_v2/cache_sessions.h @@ -21,8 +21,8 @@ class ActiveLookupRequest { const Http::RequestHeaderMap& request_headers, UpstreamRequestFactoryPtr upstream_request_factory, absl::string_view cluster_name, Event::Dispatcher& dispatcher, SystemTime timestamp, - const std::shared_ptr cacheable_response_checker_, - const std::shared_ptr stats_provider_, + const std::shared_ptr cacheable_response_checker, + const std::shared_ptr stats_provider, bool ignore_request_cache_control_header); // Caches may modify the key according to local needs, though care must be diff --git a/source/extensions/filters/http/cache_v2/upstream_request_impl.cc b/source/extensions/filters/http/cache_v2/upstream_request_impl.cc index c4e7d077ca878..db21c60a12efa 100644 --- a/source/extensions/filters/http/cache_v2/upstream_request_impl.cc +++ b/source/extensions/filters/http/cache_v2/upstream_request_impl.cc @@ -184,15 +184,15 @@ void UpstreamRequestImpl::sendHeaders(Http::RequestHeaderMapPtr request_headers) } } -template struct overloaded : Ts... { +template struct Overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...) -> overloaded; +template Overloaded(Ts...) -> Overloaded; void UpstreamRequestImpl::onReset() { ASSERT(dispatcher_.isThreadSafe()); stream_ = nullptr; - absl::visit(overloaded{ + absl::visit(Overloaded{ [](absl::monostate&&) {}, [](GetHeadersCallback&& cb) { cb(nullptr, EndStream::Reset); }, [](GetBodyCallback&& cb) { cb(nullptr, EndStream::Reset); }, diff --git a/source/extensions/filters/http/ext_proc/processing_request_modifier.h b/source/extensions/filters/http/ext_proc/processing_request_modifier.h index 8a4421c07f3fd..e504f82c943e7 100644 --- a/source/extensions/filters/http/ext_proc/processing_request_modifier.h +++ b/source/extensions/filters/http/ext_proc/processing_request_modifier.h @@ -35,7 +35,7 @@ class ProcessingRequestModifier { // Implementations may modify the request and must return true if any modifications were made. virtual bool modifyRequest(const Params& params, - envoy::service::ext_proc::v3::ProcessingRequest& processingRequest) PURE; + envoy::service::ext_proc::v3::ProcessingRequest& processing_request) PURE; }; class ProcessingRequestModifierFactory : public Config::TypedFactory { diff --git a/source/extensions/filters/http/file_server/absl_status_to_http_status.cc b/source/extensions/filters/http/file_server/absl_status_to_http_status.cc index c2486a5e81731..5cc6abefb132f 100644 --- a/source/extensions/filters/http/file_server/absl_status_to_http_status.cc +++ b/source/extensions/filters/http/file_server/absl_status_to_http_status.cc @@ -10,6 +10,7 @@ Http::Code abslStatusToHttpStatus(absl::StatusCode code) { case absl::StatusCode::kOk: return Http::Code::OK; case absl::StatusCode::kCancelled: + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(499); case absl::StatusCode::kUnknown: return Http::Code::InternalServerError; diff --git a/source/extensions/filters/http/grpc_field_extraction/message_converter/message_converter.cc b/source/extensions/filters/http/grpc_field_extraction/message_converter/message_converter.cc index 18059a3df65d3..5be5ed4191b5b 100644 --- a/source/extensions/filters/http/grpc_field_extraction/message_converter/message_converter.cc +++ b/source/extensions/filters/http/grpc_field_extraction/message_converter/message_converter.cc @@ -61,6 +61,7 @@ absl::StatusOr MessageConverter::accumulateMessage(Envoy::Buff ENVOY_LOG_MISC(info, "len(parsing_buffer_)={}", parsing_buffer_.length()); if (parsed_output->owned_bytes != nullptr) { + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) ABSL_DCHECK(!parsed_output->needs_more_data); ENVOY_LOG_MISC(info, "len(parsed owned_bytes)={}", parsed_output->owned_bytes->length()); } @@ -87,6 +88,7 @@ MessageConverter::accumulateMessages(Envoy::Buffer::Instance& data, bool end_str absl::StatusOr MessageConverter::convertBackToBuffer(StreamMessagePtr message) { + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) ABSL_DCHECK(message != nullptr); conversions_to_envoy_buffer_++; if (conversions_to_envoy_buffer_ > conversions_to_message_data_) { diff --git a/source/extensions/filters/http/grpc_field_extraction/message_converter/message_converter_utility.cc b/source/extensions/filters/http/grpc_field_extraction/message_converter/message_converter_utility.cc index e58e50b198602..bfef7ac98e5c1 100644 --- a/source/extensions/filters/http/grpc_field_extraction/message_converter/message_converter_utility.cc +++ b/source/extensions/filters/http/grpc_field_extraction/message_converter/message_converter_utility.cc @@ -78,6 +78,7 @@ absl::StatusOr parseGrpcMessage(CreateMessageDataFunc& factor bytes_needed -= slice_length; } + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) ABSL_DCHECK(bytes_needed == 0) << "Tried reading past the array of slices during " "parsing. This should never happen as " "caller already did size checks."; diff --git a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.cc b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.cc index 799b8b9d9d328..3332a79d4a7fc 100644 --- a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.cc +++ b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.cc @@ -64,9 +64,9 @@ namespace GrpcJsonReverseTranscoder { namespace { -// AppendHttpBodyEnvelope wraps the response returned from the upstream server +// appendHttpBodyEnvelope wraps the response returned from the upstream server // in a google.api.HttpBody message. -void AppendHttpBodyEnvelope(Buffer::Instance& output, std::string content_type, +void appendHttpBodyEnvelope(Buffer::Instance& output, std::string content_type, uint64_t content_length) { // Manually encode the protobuf envelope for the body. // See https://developers.google.com/protocol-buffers/docs/encoding#embedded @@ -101,7 +101,7 @@ void AppendHttpBodyEnvelope(Buffer::Instance& output, std::string content_type, output.add(proto_envelope); } -bool ReadToBuffer(Protobuf::io::ZeroCopyInputStream& stream, Buffer::Instance& buffer) { +bool readToBuffer(Protobuf::io::ZeroCopyInputStream& stream, Buffer::Instance& buffer) { const void* out; int size; while (stream.Next(&out, &size)) { @@ -261,7 +261,7 @@ void GrpcJsonReverseTranscoderFilter::SendHttpBodyResponse(Buffer::Instance* dat return; } Buffer::OwnedImpl message_payload; - AppendHttpBodyEnvelope(message_payload, response_content_type_, response_data_.length()); + appendHttpBodyEnvelope(message_payload, response_content_type_, response_data_.length()); response_content_type_.clear(); message_payload.move(response_data_); Grpc::Encoder().prependFrameHeader(Grpc::GRPC_FH_DEFAULT, message_payload); @@ -496,7 +496,7 @@ FilterDataStatus GrpcJsonReverseTranscoderFilter::decodeData(Buffer::Instance& d return FilterDataStatus::StopIterationNoBuffer; } - ReadToBuffer(*transcoder_->RequestOutput(), request_buffer_); + readToBuffer(*transcoder_->RequestOutput(), request_buffer_); if (!end_stream) { return FilterDataStatus::StopIterationNoBuffer; @@ -606,7 +606,7 @@ FilterDataStatus GrpcJsonReverseTranscoderFilter::encodeData(Buffer::Instance& d return FilterDataStatus::StopIterationNoBuffer; } - ReadToBuffer(*transcoder_->ResponseOutput(), response_buffer_); + readToBuffer(*transcoder_->ResponseOutput(), response_buffer_); if (CheckAndRejectIfResponseTranscoderFailed()) { return FilterDataStatus::StopIterationNoBuffer; diff --git a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.h b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.h index 23acdc64f6f03..c74d2fd2bada4 100644 --- a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.h +++ b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter.h @@ -82,21 +82,33 @@ class GrpcJsonReverseTranscoderFilter : public Http::StreamFilter, // MaybeExpandBufferLimits expands the buffer limits for the request and // response if the limits are set in the reverse transcoder config and are // greater than the default limits. + // NOLINTNEXTLINE(readability-identifier-naming) void MaybeExpandBufferLimits() const; + // NOLINTNEXTLINE(readability-identifier-naming) bool DecoderBufferLimitReached(uint64_t buffer_length) const; + // NOLINTNEXTLINE(readability-identifier-naming) bool EncoderBufferLimitReached(uint64_t buffer_length) const; + // NOLINTNEXTLINE(readability-identifier-naming) bool CheckAndRejectIfRequestTranscoderFailed() const; + // NOLINTNEXTLINE(readability-identifier-naming) bool CheckAndRejectIfResponseTranscoderFailed() const; + // NOLINTNEXTLINE(readability-identifier-naming) Grpc::Status::GrpcStatus GrpcStatusFromHeaders(Http::ResponseHeaderMap& headers); + // NOLINTNEXTLINE(readability-identifier-naming) void InitPerRouteConfig(); // BuildRequestFromHttpBody reads the contents of the data field of the // google.api.HttpBody message and builds the request body out of it. + // NOLINTNEXTLINE(readability-identifier-naming) bool BuildRequestFromHttpBody(Buffer::Instance& data); // SendHttpBodyResponse sends the response returned from the upstream server // as a google.api.HttpBody message. + // NOLINTNEXTLINE(readability-identifier-naming) void SendHttpBodyResponse(Buffer::Instance* data); + // NOLINTNEXTLINE(readability-identifier-naming) void ReplaceAPIVersionInPath(); + // NOLINTNEXTLINE(readability-identifier-naming) bool CreateDataBuffer(const nlohmann::json& payload, Buffer::OwnedImpl& buffer) const; + // NOLINTNEXTLINE(readability-identifier-naming) absl::Status ExtractHttpAnnotationValues(const Protobuf::MethodDescriptor* method_descriptor); Http::StreamDecoderFilterCallbacks* decoder_callbacks_; diff --git a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.h b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.h index 8e73ebb04410c..d437beb9145ad 100644 --- a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.h +++ b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.h @@ -73,19 +73,23 @@ class GrpcJsonReverseTranscoderConfig : public Router::RouteSpecificFilterConfig Api::Api& api); // Takes the value of the path header of a gRPC request and returns its path descriptor. + // NOLINTNEXTLINE(readability-identifier-naming) const Protobuf::MethodDescriptor* GetMethodDescriptor(absl::string_view path) const; // Checks if the request body field is of type `google.api.HttpBody`. + // NOLINTNEXTLINE(readability-identifier-naming) bool IsRequestNestedHttpBody(const Protobuf::MethodDescriptor* method_descriptor, const std::string& request_body_field) const; absl::StatusOr> + // NOLINTNEXTLINE(readability-identifier-naming) CreateTranscoder(const Protobuf::MethodDescriptor* method_descriptor, TranscoderInputStream& request_input, TranscoderInputStream& response_input) const; // Changes the body field to its `json_name` value or to camelCase if the filter // is not configured to preserve the proto field names. + // NOLINTNEXTLINE(readability-identifier-naming) absl::StatusOr ChangeBodyFieldName(absl::string_view path, absl::string_view body_field) const; diff --git a/source/extensions/filters/http/grpc_json_reverse_transcoder/utils.cc b/source/extensions/filters/http/grpc_json_reverse_transcoder/utils.cc index aa75068696244..2074f6febb1ff 100644 --- a/source/extensions/filters/http/grpc_json_reverse_transcoder/utils.cc +++ b/source/extensions/filters/http/grpc_json_reverse_transcoder/utils.cc @@ -25,7 +25,7 @@ namespace GrpcJsonReverseTranscoder { namespace { -absl::Status BuildReplacementVector(nlohmann::json& request, std::string http_rule_path, +absl::Status buildReplacementVector(nlohmann::json& request, std::string http_rule_path, std::vector>& replacements, absl::flat_hash_set& param_set) { size_t end = 0; @@ -139,7 +139,8 @@ absl::StatusOr BuildPath(nlohmann::json& request, std::string http_ std::string http_body_field) { std::vector> replacements; absl::flat_hash_set param_set; - absl::Status status = BuildReplacementVector(request, http_rule_path, replacements, param_set); + const absl::Status status = + buildReplacementVector(request, http_rule_path, replacements, param_set); if (!status.ok()) { return status; } diff --git a/source/extensions/filters/http/grpc_json_reverse_transcoder/utils.h b/source/extensions/filters/http/grpc_json_reverse_transcoder/utils.h index f3dd273cf9e6e..971fa6996e456 100644 --- a/source/extensions/filters/http/grpc_json_reverse_transcoder/utils.h +++ b/source/extensions/filters/http/grpc_json_reverse_transcoder/utils.h @@ -17,6 +17,7 @@ namespace GrpcJsonReverseTranscoder { inline constexpr char reserved_chars[] = " %:?#[]@!$&'()*+,;="; // This builds grpc-message header value from body data. +// NOLINTNEXTLINE(readability-identifier-naming) std::string BuildGrpcMessage(Envoy::Buffer::Instance& body_data); // Takes the json object and a path and returns the value of the json object at @@ -28,6 +29,7 @@ std::string BuildGrpcMessage(Envoy::Buffer::Instance& body_data); // [-_./0-9a-zA-Z]. // TODO(numanelahi): Add `~` to the list of characters that are not percent // encoded. +// NOLINTNEXTLINE(readability-identifier-naming) absl::optional GetNestedJsonValueAsString(const nlohmann::json& object, const std::string& key, bool has_one_path_segment); @@ -62,6 +64,7 @@ absl::optional GetNestedJsonValueAsString(const nlohmann::json& obj // param_set = {"a", "e"}, and // key_prefix = "prefix" // then query_string = "prefix.f=hello%20world&prefix.g=1.234" +// NOLINTNEXTLINE(readability-identifier-naming) void BuildQueryParamString(const nlohmann::json& object, const absl::flat_hash_set& ignore_list, std::string* query_string, std::string key_prefix = ""); @@ -86,6 +89,7 @@ void BuildQueryParamString(const nlohmann::json& object, // `/v1/projects/123456789/shelves/fiction?description=This%20is%20a%20test%20description&theme=Kids`, // when the http_body_field is "shelf", adding `theme` and `description` as // query parameters to the http path. +// NOLINTNEXTLINE(readability-identifier-naming) absl::StatusOr BuildPath(nlohmann::json& request, std::string http_rule_path, std::string http_body_field); diff --git a/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.cc b/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.cc index 6cffcc4067968..f60574917f77d 100644 --- a/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.cc +++ b/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.cc @@ -126,6 +126,7 @@ void GetMonitoredResourceLabels(absl::string_view label_extractor, WireFormatLite::WireType getWireType(const Field& field_desc) { static WireFormatLite::WireType field_kind_to_wire_type[] = { + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) static_cast(-1), // TYPE_UNKNOWN WireFormatLite::WIRETYPE_FIXED64, // TYPE_DOUBLE WireFormatLite::WIRETYPE_FIXED32, // TYPE_FLOAT @@ -172,6 +173,7 @@ ExtractRepeatedFieldSizeHelper(const FieldExtractor& field_extractor, const std: if (field->number() != WireFormatLite::GetTagFieldNumber(tag)) { WireFormatLite::SkipField(input_stream, tag); } else { + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) DCHECK_EQ(WireFormatLite::WIRETYPE_LENGTH_DELIMITED, WireFormatLite::GetTagWireType(tag)); uint32_t length; @@ -224,6 +226,7 @@ int64_t ExtractRepeatedFieldSize(const Type& type, // SCRUB directive should only be applied to one field. Tools // framework validation should check this case. + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) DCHECK_EQ(1, field_mask->paths_size()); FieldExtractor field_extractor(&type, std::move(type_finder)); @@ -269,6 +272,7 @@ void RedactPath(std::vector::const_iterator path_begin, auto* repeated_values = field_value.mutable_list_value()->mutable_values(); for (int i = 0; i < repeated_values->size(); ++i) { Value* value = repeated_values->Mutable(i); + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) CHECK(value->has_struct_value()) << "Cannot redact non-message-type field " << field; RedactPath(path_begin, path_end, value->mutable_struct_value()); } @@ -281,6 +285,7 @@ void RedactPath(std::vector::const_iterator path_begin, void RedactPaths(absl::Span paths_to_redact, Struct* proto_struct) { for (const std::string& path : paths_to_redact) { std::vector path_pieces = absl::StrSplit(path, '.', absl::SkipEmpty()); + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) CHECK(path_pieces.size() < kMaxRedactedPathDepth) << "Attempting to redact path with depth >= " << kMaxRedactedPathDepth << ": " << path; RedactPath(path_pieces.begin(), path_pieces.end(), proto_struct); diff --git a/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.h b/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.h index f2885b42fa6c8..95a1b7d96d17a 100644 --- a/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.h +++ b/source/extensions/filters/http/proto_message_extraction/extraction_util/extraction_util.h @@ -44,22 +44,28 @@ constexpr int kProtoTranslationMaxRecursionDepth = 64; ABSL_CONST_INIT const char* const kStructTypeUrl = "type.googleapis.com/google.protobuf.Struct"; +// NOLINTNEXTLINE(readability-identifier-naming) bool IsEmptyStruct(const Protobuf::Struct& message_struct); +// NOLINTNEXTLINE(readability-identifier-naming) bool IsLabelName(absl::string_view value); // Monitored resource label names are captured within curly brackets ("{", "}"). // The format is verified by the service config validator, so to extract label // name, we just remove the brackets. +// NOLINTNEXTLINE(readability-identifier-naming) std::string GetLabelName(absl::string_view value); // Singleton mapping of string to ExtractedMessageDirective. +// NOLINTNEXTLINE(readability-identifier-naming) const absl::flat_hash_map& StringToDirectiveMap(); absl::optional +// NOLINTNEXTLINE(readability-identifier-naming) ExtractedMessageDirectiveFromString(absl::string_view directive); // Returns a mapping of monitored resource label keys to their values. +// NOLINTNEXTLINE(readability-identifier-naming) void GetMonitoredResourceLabels(absl::string_view label_extractor, absl::string_view resource_string, Protobuf::Map* labels); @@ -70,6 +76,7 @@ void GetMonitoredResourceLabels(absl::string_view label_extractor, // In case of error or nullptr/empty field_mask, it returns a negative value and // logs the error. int64_t +// NOLINTNEXTLINE(readability-identifier-naming) ExtractRepeatedFieldSize(const Protobuf::Type& type, std::function type_finder, const Protobuf::FieldMask* field_mask, @@ -78,22 +85,27 @@ ExtractRepeatedFieldSize(const Protobuf::Type& type, // Extract the size of the repeated field represented by given field mask // path from given proto message. `path` must represent a repeated field. absl::StatusOr +// NOLINTNEXTLINE(readability-identifier-naming) ExtractRepeatedFieldSizeHelper(const Protobuf::field_extraction::FieldExtractor& field_extractor, const std::string& path, const Protobuf::field_extraction::MessageData& message); +// NOLINTNEXTLINE(readability-identifier-naming) absl::string_view ExtractLocationIdFromResourceName(absl::string_view resource_name); // Recursively redacts the path_pieces in the enclosing proto_struct. +// NOLINTNEXTLINE(readability-identifier-naming) void RedactPath(std::vector::const_iterator path_begin, std::vector::const_iterator path_end, Protobuf::Struct* proto_struct); +// NOLINTNEXTLINE(readability-identifier-naming) void RedactPaths(absl::Span paths_to_redact, Protobuf::Struct* proto_struct); // Finds the last value of the non-repeated string field after the first // value. Returns an empty string if there is only one string field. Returns // an error if the resource is malformed in case that the search goes forever. absl::StatusOr +// NOLINTNEXTLINE(readability-identifier-naming) FindSingularLastValue(const Protobuf::Field* field, Envoy::Protobuf::io::CodedInputStream* input_stream); @@ -104,15 +116,18 @@ FindSingularLastValue(const Protobuf::Field* field, // non-repeated field. However, parsers are expected to handle the case in // which they do." absl::StatusOr +// NOLINTNEXTLINE(readability-identifier-naming) SingularFieldUseLastValue(const std::string first_value, const Protobuf::Field* field, Envoy::Protobuf::io::CodedInputStream* input_stream); absl::StatusOr +// NOLINTNEXTLINE(readability-identifier-naming) ExtractStringFieldValue(const Protobuf::Type& type, std::function type_finder, const std::string& path, const Protobuf::field_extraction::MessageData& message); +// NOLINTNEXTLINE(readability-identifier-naming) absl::Status RedactStructRecursively(std::vector::const_iterator path_pieces_begin, std::vector::const_iterator path_pieces_end, Protobuf::Struct* message_struct); @@ -120,6 +135,7 @@ absl::Status RedactStructRecursively(std::vector::const_iterator pa // Converts given proto message to Struct. It also adds // a "@type" property with proto type url to the generated Struct. Expects the // TypeResolver to handle types prefixed with "type.googleapis.com/". +// NOLINTNEXTLINE(readability-identifier-naming) absl::Status ConvertToStruct(const Protobuf::field_extraction::MessageData& message, const Envoy::Protobuf::Type& type, ::Envoy::Protobuf::util::TypeResolver* type_resolver, @@ -131,6 +147,7 @@ absl::Status ConvertToStruct(const Protobuf::field_extraction::MessageData& mess // (1) `scrubber` is nullptr; // (2) error during scrubbing/converting; // (3) the message is empty after scrubbing; +// NOLINTNEXTLINE(readability-identifier-naming) bool ScrubToStruct(const proto_processing_lib::proto_scrubber::ProtoScrubber* scrubber, const Envoy::Protobuf::Type& type, const ::google::grpc::transcoding::TypeHelper& type_helper, diff --git a/source/extensions/filters/http/proto_message_extraction/extraction_util/proto_extractor.h b/source/extensions/filters/http/proto_message_extraction/extraction_util/proto_extractor.h index 9f6f7f911e03a..288c2f830bef4 100644 --- a/source/extensions/filters/http/proto_message_extraction/extraction_util/proto_extractor.h +++ b/source/extensions/filters/http/proto_message_extraction/extraction_util/proto_extractor.h @@ -42,11 +42,13 @@ class ProtoExtractor : public ProtoExtractorInterface { // Populate the target resource or the target resource callback in the extracted message // metadata. + // NOLINTNEXTLINE(readability-identifier-naming) void GetTargetResourceOrTargetResourceCallback( const Protobuf::FieldMask& field_mask, const Protobuf::field_extraction::MessageData& message, bool callback, ExtractedMessageMetadata* extracted_message_metadata) const; // Function to get the value associated with a key + // NOLINTNEXTLINE(readability-identifier-naming) const Protobuf::FieldMask& FindWithDefault(ExtractedMessageDirective directive); const google::grpc::transcoding::TypeHelper* type_helper_; diff --git a/source/extensions/filters/http/proto_message_extraction/extraction_util/proto_extractor_interface.h b/source/extensions/filters/http/proto_message_extraction/extraction_util/proto_extractor_interface.h index 19bbe9480529d..ac5e4bb451233 100644 --- a/source/extensions/filters/http/proto_message_extraction/extraction_util/proto_extractor_interface.h +++ b/source/extensions/filters/http/proto_message_extraction/extraction_util/proto_extractor_interface.h @@ -16,9 +16,9 @@ namespace ProtoMessageExtraction { // All valid field extraction directives. enum class ExtractedMessageDirective { - EXTRACT_REDACT, + EXTRACT_REDACT, // NOLINT(readability-identifier-naming) EXTRACT, - EXTRACT_REPEATED_CARDINALITY, + EXTRACT_REPEATED_CARDINALITY, // NOLINT(readability-identifier-naming) }; using FieldPathToExtractType = @@ -41,6 +41,7 @@ class ProtoExtractorInterface { // that contains the extracted message and other extracted message metadata obtained during // extraction. virtual ExtractedMessageMetadata + // NOLINTNEXTLINE(readability-identifier-naming) ExtractMessage(const Protobuf::field_extraction::MessageData& message) const = 0; virtual ~ProtoExtractorInterface() = default; diff --git a/source/extensions/filters/http/proto_message_extraction/extractor.h b/source/extensions/filters/http/proto_message_extraction/extractor.h index dce6b2ac13751..3c5dadf34d483 100644 --- a/source/extensions/filters/http/proto_message_extraction/extractor.h +++ b/source/extensions/filters/http/proto_message_extraction/extractor.h @@ -57,8 +57,10 @@ class Extractor { // is the last message. It only keeps the result from the first one the last. virtual void processResponse(Protobuf::field_extraction::MessageData& message) = 0; + // NOLINTNEXTLINE(readability-identifier-naming) virtual const ExtractedMessageResult& GetResult() const = 0; + // NOLINTNEXTLINE(readability-identifier-naming) virtual void ClearResult() = 0; }; diff --git a/source/extensions/filters/http/proto_message_extraction/filter.cc b/source/extensions/filters/http/proto_message_extraction/filter.cc index 9c74b5a6e2220..eb1126abdde25 100644 --- a/source/extensions/filters/http/proto_message_extraction/filter.cc +++ b/source/extensions/filters/http/proto_message_extraction/filter.cc @@ -321,9 +321,12 @@ Filter::HandleDataStatus Filter::handleEncodeData(Envoy::Buffer::Instance& data, // The converter returns an empty stream_message for the last empty // buffer. if (stream_message->message() == nullptr) { + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) DCHECK(end_stream); + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) DCHECK(stream_message->isFinalMessage()); // This is the last one in the vector. + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) DCHECK(msg_idx == buffering->size() - 1); continue; } diff --git a/source/extensions/filters/http/rate_limit_quota/client_impl.h b/source/extensions/filters/http/rate_limit_quota/client_impl.h index 70fc2df48849b..11f8d57853102 100644 --- a/source/extensions/filters/http/rate_limit_quota/client_impl.h +++ b/source/extensions/filters/http/rate_limit_quota/client_impl.h @@ -56,8 +56,8 @@ class LocalRateLimitClientImpl : public RateLimitClient, inline std::unique_ptr createLocalRateLimitClient(GlobalRateLimitClientImpl* global_client, - ThreadLocal::TypedSlot& buckets_cache_tls_) { - return std::make_unique(global_client, buckets_cache_tls_); + ThreadLocal::TypedSlot& buckets_cache_tls) { + return std::make_unique(global_client, buckets_cache_tls); } } // namespace RateLimitQuota diff --git a/source/extensions/filters/http/set_metadata/set_metadata_filter.cc b/source/extensions/filters/http/set_metadata/set_metadata_filter.cc index 778470fab405c..65778dedd0646 100644 --- a/source/extensions/filters/http/set_metadata/set_metadata_filter.cc +++ b/source/extensions/filters/http/set_metadata/set_metadata_filter.cc @@ -15,7 +15,7 @@ namespace SetMetadataFilter { namespace { -void ApplyConfigToMetadata(const Config& config, +void applyConfigToMetadata(const Config& config, envoy::config::core::v3::Metadata& dynamic_metadata) { // Add configured untyped metadata. @@ -97,13 +97,13 @@ SetMetadataFilter::~SetMetadataFilter() = default; Http::FilterHeadersStatus SetMetadataFilter::decodeHeaders(Http::RequestHeaderMap&, bool) { // Apply global config first. - ApplyConfigToMetadata(*config_, decoder_callbacks_->streamInfo().dynamicMetadata()); + applyConfigToMetadata(*config_, decoder_callbacks_->streamInfo().dynamicMetadata()); // Apply route-specific config if present. const Config* route_specific_cfg = dynamic_cast(decoder_callbacks_->mostSpecificPerFilterConfig()); if (route_specific_cfg) { - ApplyConfigToMetadata(*route_specific_cfg, decoder_callbacks_->streamInfo().dynamicMetadata()); + applyConfigToMetadata(*route_specific_cfg, decoder_callbacks_->streamInfo().dynamicMetadata()); } return Http::FilterHeadersStatus::Continue; diff --git a/source/extensions/filters/network/common/redis/supported_commands.h b/source/extensions/filters/network/common/redis/supported_commands.h index 9c0c756c25393..92f01ed8ebb4c 100644 --- a/source/extensions/filters/network/common/redis/supported_commands.h +++ b/source/extensions/filters/network/common/redis/supported_commands.h @@ -81,6 +81,7 @@ struct SupportedCommands { * @return commands without keys which are sent to all redis shards and the responses are handled * using special response handler according to its response type */ + // NOLINTNEXTLINE(readability-identifier-naming) static const absl::flat_hash_set& ClusterScopeCommands() { CONSTRUCT_ON_FIRST_USE(absl::flat_hash_set, "script", "flushall", "flushdb", "slowlog", "config", "info", "keys", "select", "role", "hello"); diff --git a/source/extensions/filters/network/dubbo_proxy/dubbo_protocol_impl.cc b/source/extensions/filters/network/dubbo_proxy/dubbo_protocol_impl.cc index 1e5318a792cb6..61e7f6aac2ab7 100644 --- a/source/extensions/filters/network/dubbo_proxy/dubbo_protocol_impl.cc +++ b/source/extensions/filters/network/dubbo_proxy/dubbo_protocol_impl.cc @@ -56,6 +56,7 @@ void parseRequestInfoFromBuffer(Buffer::Instance& data, MessageMetadataSharedPtr ASSERT(data.length() >= DubboProtocolImpl::MessageSize); uint8_t flag = data.peekInt(FlagOffset); bool is_two_way = (flag & TwoWayMask) == TwoWayMask ? true : false; + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) SerializationType type = static_cast(flag & SerializationTypeMask); if (!isValidSerializationType(type)) { throw EnvoyException( @@ -72,6 +73,7 @@ void parseRequestInfoFromBuffer(Buffer::Instance& data, MessageMetadataSharedPtr void parseResponseInfoFromBuffer(Buffer::Instance& buffer, MessageMetadataSharedPtr metadata) { ASSERT(buffer.length() >= DubboProtocolImpl::MessageSize); + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) ResponseStatus status = static_cast(buffer.peekInt(StatusOffset)); if (!isValidResponseStatus(status)) { throw EnvoyException( diff --git a/source/extensions/filters/network/redis_proxy/cluster_response_handler.h b/source/extensions/filters/network/redis_proxy/cluster_response_handler.h index ee0b2bdb1350d..6dcd70c3b330f 100644 --- a/source/extensions/filters/network/redis_proxy/cluster_response_handler.h +++ b/source/extensions/filters/network/redis_proxy/cluster_response_handler.h @@ -20,9 +20,9 @@ class ClusterScopeCmdRequest; * Enum defining the different response handler types for cluster scope commands */ enum class ClusterScopeResponseHandlerType { - allresponses_mustbe_same, - aggregate_all_responses, - response_handler_none + allresponses_mustbe_same, // NOLINT(readability-identifier-naming) + aggregate_all_responses, // NOLINT(readability-identifier-naming) + response_handler_none // NOLINT(readability-identifier-naming) }; /** diff --git a/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.cc b/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.cc index 6d4060feb46e6..5efcf8605956e 100644 --- a/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.cc @@ -30,6 +30,7 @@ bool BinaryProtocolImpl::readMessageBegin(Buffer::Instance& buffer, MessageMetad // The byte at offset 2 is unused and ignored. + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) MessageType type = static_cast(buffer.peekInt(3)); if (type < MessageType::Call || type > MessageType::LastMessageType) { throw EnvoyException( @@ -392,6 +393,7 @@ bool LaxBinaryProtocolImpl::readMessageBegin(Buffer::Instance& buffer, MessageMe return false; } + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) MessageType type = static_cast(buffer.peekInt(name_len + 4)); if (type < MessageType::Call || type > MessageType::LastMessageType) { throw EnvoyException( diff --git a/source/extensions/matching/http/cel_input/cel_input.h b/source/extensions/matching/http/cel_input/cel_input.h index 44cdc57152233..f055f8eb90ed8 100644 --- a/source/extensions/matching/http/cel_input/cel_input.h +++ b/source/extensions/matching/http/cel_input/cel_input.h @@ -28,7 +28,9 @@ using StreamActivationPtr = std::unique_ptrneeds_response_path_data(); } + // NOLINTNEXTLINE(readability-identifier-naming) bool has_response_data() const { return activation_->has_response_data(); } StreamActivationPtr activation_; }; diff --git a/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.h b/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.h index f1b2a46570045..cdb9d721c5d19 100644 --- a/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.h +++ b/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.h @@ -77,8 +77,8 @@ class MetricAggregator : public Logger::Loggable { class MetricKey { public: - MetricKey(std::string&& name, SortedAttributesVector&& sortedAttributes) - : name_(std::move(name)), sorted_attributes_(std::move(sortedAttributes)) {} + MetricKey(std::string&& name, SortedAttributesVector&& sorted_attributes) + : name_(std::move(name)), sorted_attributes_(std::move(sorted_attributes)) {} bool operator==(const MetricKey& other) const { return name_ == other.name_ && sorted_attributes_ == other.sorted_attributes_; @@ -227,6 +227,7 @@ class OtlpOptions { bool useTagExtractedName() { return use_tag_extracted_name_; } absl::string_view statPrefix() { return stat_prefix_; } const Protobuf::RepeatedPtrField& + // NOLINTNEXTLINE(readability-identifier-naming) resource_attributes() const { return resource_attributes_; } diff --git a/source/extensions/tracers/datadog/agent_http_client.h b/source/extensions/tracers/datadog/agent_http_client.h index 298f921cb2ef4..0ab1e39bda0c6 100644 --- a/source/extensions/tracers/datadog/agent_http_client.h +++ b/source/extensions/tracers/datadog/agent_http_client.h @@ -92,6 +92,7 @@ class AgentHTTPClient : public datadog::tracing::HTTPClient, * Return a JSON representation of this object's configuration. This function * is used in the startup banner logged by \c dd-trace-cpp. */ + // NOLINTNEXTLINE(readability-identifier-naming) const nlohmann::json& config_json() const; // Http::AsyncClient::Callbacks diff --git a/source/extensions/tracers/datadog/event_scheduler.h b/source/extensions/tracers/datadog/event_scheduler.h index 396e766d88759..905aad6eb7c50 100644 --- a/source/extensions/tracers/datadog/event_scheduler.h +++ b/source/extensions/tracers/datadog/event_scheduler.h @@ -40,6 +40,7 @@ class EventScheduler : public datadog::tracing::EventScheduler { std::string config() const override; // Provides JSON configuration for debug logging. + // NOLINTNEXTLINE(readability-identifier-naming) const nlohmann::json& config_json() const; private: diff --git a/source/extensions/transport_sockets/tap/tap_config_impl.cc b/source/extensions/transport_sockets/tap/tap_config_impl.cc index ea56c49c8a0fe..6e189c5858e0b 100644 --- a/source/extensions/transport_sockets/tap/tap_config_impl.cc +++ b/source/extensions/transport_sockets/tap/tap_config_impl.cc @@ -30,7 +30,7 @@ PerSocketTapperImpl::PerSocketTapperImpl( sink_handle_->submitTrace(std::move(trace)); pegSubmitCounter(true); } - seq_num++; + seq_num_++; } void PerSocketTapperImpl::fillConnectionInfo(envoy::data::tap::v3::Connection& connection) { @@ -49,19 +49,19 @@ void PerSocketTapperImpl::closeSocket(Network::ConnectionEvent) { } if (config_->streaming()) { - seq_num++; + seq_num_++; if (shouldSendStreamedMsgByConfiguredSize()) { makeStreamedTraceIfNeeded(); auto& event = *streamed_trace_->mutable_socket_streamed_trace_segment()->mutable_events()->add_events(); - initStreamingEvent(event, seq_num); + initStreamingEvent(event, seq_num_); event.mutable_closed(); // submit directly and don't check current_streamed_rx_tx_bytes_ any more submitStreamedDataPerConfiguredSize(); } else { TapCommon::TraceWrapperPtr trace = makeTraceSegment(); auto& event = *trace->mutable_socket_streamed_trace_segment()->mutable_event(); - initStreamingEvent(event, seq_num); + initStreamingEvent(event, seq_num_); event.mutable_closed(); sink_handle_->submitTrace(std::move(trace)); } @@ -140,7 +140,7 @@ void PerSocketTapperImpl::handleSendingStreamTappedMsgPerConfigSize(const Buffer makeStreamedTraceIfNeeded(); auto& event = *streamed_trace_->mutable_socket_streamed_trace_segment()->mutable_events()->add_events(); - initStreamingEvent(event, seq_num); + initStreamingEvent(event, seq_num_); uint32_t buffer_start_offset = 0; if (is_read) { buffer_start_offset = data.length() - total_bytes; @@ -171,14 +171,14 @@ void PerSocketTapperImpl::onRead(const Buffer::Instance& data, uint32_t bytes_re } else { TapCommon::TraceWrapperPtr trace = makeTraceSegment(); auto& event = *trace->mutable_socket_streamed_trace_segment()->mutable_event(); - initStreamingEvent(event, seq_num); + initStreamingEvent(event, seq_num_); TapCommon::Utility::addBufferToProtoBytes(*event.mutable_read()->mutable_data(), config_->maxBufferedRxBytes(), data, data.length() - bytes_read, bytes_read); sink_handle_->submitTrace(std::move(trace)); pegSubmitCounter(true); } - seq_num = seq_num + bytes_read; + seq_num_ = seq_num_ + bytes_read; } else { if (buffered_trace_ != nullptr && buffered_trace_->socket_buffered_trace().read_truncated()) { return; @@ -209,7 +209,7 @@ void PerSocketTapperImpl::onWrite(const Buffer::Instance& data, uint32_t bytes_w } else { TapCommon::TraceWrapperPtr trace = makeTraceSegment(); auto& event = *trace->mutable_socket_streamed_trace_segment()->mutable_event(); - initStreamingEvent(event, seq_num); + initStreamingEvent(event, seq_num_); TapCommon::Utility::addBufferToProtoBytes(*event.mutable_write()->mutable_data(), config_->maxBufferedTxBytes(), data, 0, bytes_written); @@ -217,7 +217,7 @@ void PerSocketTapperImpl::onWrite(const Buffer::Instance& data, uint32_t bytes_w sink_handle_->submitTrace(std::move(trace)); pegSubmitCounter(true); } - seq_num = seq_num + bytes_written; + seq_num_ = seq_num_ + bytes_written; } else { if (buffered_trace_ != nullptr && buffered_trace_->socket_buffered_trace().write_truncated()) { return; diff --git a/source/extensions/transport_sockets/tap/tap_config_impl.h b/source/extensions/transport_sockets/tap/tap_config_impl.h index 3be053732ce63..883969d9bcda3 100644 --- a/source/extensions/transport_sockets/tap/tap_config_impl.h +++ b/source/extensions/transport_sockets/tap/tap_config_impl.h @@ -65,7 +65,7 @@ class PerSocketTapperImpl : public PerSocketTapper { // for some protocols (e.g., HTTP/2 frames may span multiple reads/writes). // Add sequence number to allow the receiver to reconstruct byte order and // determine completeness, similar to TCP sequence numbers. - uint64_t seq_num{}; + uint64_t seq_num_ = 0; SocketTapConfigSharedPtr config_; Extensions::Common::Tap::PerTapSinkHandleManagerPtr sink_handle_; const Network::Connection& connection_; diff --git a/source/extensions/transport_sockets/tcp_stats/tcp_stats.h b/source/extensions/transport_sockets/tcp_stats/tcp_stats.h index 3117539993cf9..688b676d55ed6 100644 --- a/source/extensions/transport_sockets/tcp_stats/tcp_stats.h +++ b/source/extensions/transport_sockets/tcp_stats/tcp_stats.h @@ -13,6 +13,7 @@ #include "source/extensions/transport_sockets/common/passthrough.h" // Defined in /usr/include/linux/tcp.h. +// NOLINTNEXTLINE(readability-identifier-naming) struct tcp_info; namespace Envoy { diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 9b676da8be13a..2b775b942f7fb 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1711,3 +1711,4 @@ bitrate kbps dataset datasets +smartptr From 3883598807a5c972549b30612b14e59d14336710 Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Mon, 11 May 2026 09:45:01 -0400 Subject: [PATCH 279/750] perf: convert StrSplit result from container of std::string to container of absl::string_view (#44863) Commit Message: perf: convert StrSplit result from container of std::string to container of absl::string_view Additional Description: Minor perf change. Modify the return type of some StrSplit operations, so it uses `absl::string_view` instead of `std::string`. Risk Level: low - internal refactor Testing: N/A Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Adi Suissa-Peleg --- .../filters/http/mcp/mcp_json_parser.cc | 4 +-- .../http_request_builder.cc | 8 ++--- .../filters/http/mcp_router/session_codec.cc | 8 ++--- source/server/cgroup_cpu_util.cc | 30 +++++++++---------- source/server/cgroup_cpu_util.h | 2 +- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/source/extensions/filters/http/mcp/mcp_json_parser.cc b/source/extensions/filters/http/mcp/mcp_json_parser.cc index 0073c6df04669..efe942c5be772 100644 --- a/source/extensions/filters/http/mcp/mcp_json_parser.cc +++ b/source/extensions/filters/http/mcp/mcp_json_parser.cc @@ -650,7 +650,7 @@ void McpFieldExtractor::copySelectedFields() { } void McpFieldExtractor::copyFieldByPath(const std::string& path) { - std::vector segments = absl::StrSplit(path, '.'); + std::vector segments = absl::StrSplit(path, '.'); // Navigate source to find value const Protobuf::Struct* current_source = &temp_storage_; @@ -753,7 +753,7 @@ const Protobuf::Value* McpJsonParser::getNestedValue(const std::string& dotted_p return nullptr; } - std::vector path = absl::StrSplit(dotted_path, '.'); + std::vector path = absl::StrSplit(dotted_path, '.'); const Protobuf::Struct* current = &metadata_; for (size_t i = 0; i < path.size(); ++i) { diff --git a/source/extensions/filters/http/mcp_json_rest_bridge/http_request_builder.cc b/source/extensions/filters/http/mcp_json_rest_bridge/http_request_builder.cc index 2801b84f43076..427f4fbe25d30 100644 --- a/source/extensions/filters/http/mcp_json_rest_bridge/http_request_builder.cc +++ b/source/extensions/filters/http/mcp_json_rest_bridge/http_request_builder.cc @@ -17,7 +17,7 @@ namespace { using ::nlohmann::json; absl::StatusOr getJsonValue(const json& data, absl::string_view path) { - std::vector parts = absl::StrSplit(path, '.'); + std::vector parts = absl::StrSplit(path, '.'); json current = data; for (const auto& part : parts) { if (!current.contains(part)) { @@ -97,11 +97,11 @@ void appendQueryParamsToBaseUrl(std::string& url, absl::Span q // Recursively removes a path from a JSON object. // Returns true if `data` becomes empty after removal, false otherwise. -bool recursiveRemoveJsonPath(json& data, absl::Span parts) { +bool recursiveRemoveJsonPath(json& data, absl::Span parts) { if (parts.empty()) { return false; } - const std::string& key = parts[0]; + absl::string_view key = parts[0]; if (!data.is_object() || !data.contains(key)) { return false; } @@ -120,7 +120,7 @@ void removeJsonPath(json& data, absl::string_view path) { if (path.empty()) { return; } - std::vector parts = absl::StrSplit(path, '.'); + std::vector parts = absl::StrSplit(path, '.'); recursiveRemoveJsonPath(data, parts); } diff --git a/source/extensions/filters/http/mcp_router/session_codec.cc b/source/extensions/filters/http/mcp_router/session_codec.cc index 6fceaaf0b404d..1bef2f0de3dae 100644 --- a/source/extensions/filters/http/mcp_router/session_codec.cc +++ b/source/extensions/filters/http/mcp_router/session_codec.cc @@ -36,7 +36,7 @@ std::string SessionCodec::buildCompositeSessionId( absl::StatusOr SessionCodec::parseCompositeSessionId(const std::string& composite) { - std::vector parts = absl::StrSplit(composite, '@'); + std::vector parts = absl::StrSplit(composite, '@'); if (parts.size() != 3) { return absl::InvalidArgumentError("Invalid session format"); } @@ -50,14 +50,14 @@ SessionCodec::parseCompositeSessionId(const std::string& composite) { return result; } - std::vector backend_parts = absl::StrSplit(parts[2], ','); + std::vector backend_parts = absl::StrSplit(parts[2], ','); for (const auto& bp : backend_parts) { size_t colon = bp.find(':'); if (colon == std::string::npos || colon == 0) { return absl::InvalidArgumentError("Invalid backend session format"); } - std::string backend = bp.substr(0, colon); - std::string encoded_session = bp.substr(colon + 1); + absl::string_view backend = bp.substr(0, colon); + absl::string_view encoded_session = bp.substr(colon + 1); result.backend_sessions[backend] = Base64::decode(encoded_session); } diff --git a/source/server/cgroup_cpu_util.cc b/source/server/cgroup_cpu_util.cc index 4892fd7138e92..055644d95afb0 100644 --- a/source/server/cgroup_cpu_util.cc +++ b/source/server/cgroup_cpu_util.cc @@ -75,7 +75,7 @@ absl::optional CgroupCpuUtil::getCpuLimit(Filesystem::Instance& fs) { // Returns string_view without trailing newline on success, absl::nullopt on validation failure. absl::optional CgroupCpuUtil::validateCgroupFileContent(const std::string& content, const std::string& file_path) { - // ✅ Newline Validation: Require trailing newline + // Newline Validation: Require trailing newline if (content.empty() || content.back() != '\n') { ENVOY_LOG_MISC(warn, "Malformed `cgroup` file {}: missing trailing newline", file_path); return absl::nullopt; @@ -108,13 +108,13 @@ absl::optional CgroupCpuUtil::getCurrentCgroupPath(Filesystem::I } const std::string content = result.value(); - const std::vector lines = absl::StrSplit(content, '\n'); + const std::vector lines = absl::StrSplit(content, '\n'); std::string v2_path; // Save v2 path in case no v1 found bool found_v2 = false; // Track if we found any v2 hierarchy // Parse /proc/self/cgroup line by line - for (const std::string& line : lines) { + for (absl::string_view line : lines) { if (line.empty()) { continue; } @@ -302,7 +302,7 @@ absl::optional CgroupCpuUtil::readActualLimitsV2(const CpuFiles& cpu_fil const std::string content = std::string(absl::StripAsciiWhitespace(cpu_files.quota_content)); // Parse "quota period" format - const std::vector parts = absl::StrSplit(content, ' '); + const std::vector parts = absl::StrSplit(content, ' '); if (parts.size() != 2) { ENVOY_LOG_MISC(warn, "Malformed cgroup v2 cpu.max: expected 'quota period', got '{}'", content); @@ -382,17 +382,15 @@ absl::optional CgroupCpuUtil::discoverCgroupMount(Filesystem::Insta } const std::string content = result.value(); - const std::vector lines = absl::StrSplit(content, '\n'); + const std::vector lines = absl::StrSplit(content, '\n'); std::string v2_mount_point; // Save v2 mount in case no v1 found - for (const std::string& line_str : lines) { - if (line_str.empty()) { + for (absl::string_view line : lines) { + if (line.empty()) { continue; } - // Work with string_view for efficient parsing - absl::string_view line = line_str; bool line_valid = true; // Skip first four fields @@ -509,7 +507,7 @@ absl::optional CgroupCpuUtil::discoverCgroupMount(Filesystem::Insta // // This matches the Go runtime implementation: // https://github.com/golang/go/blob/master/src/internal/runtime/cgroup/cgroup_linux.go -std::string CgroupCpuUtil::unescapePath(const std::string& path) { +std::string CgroupCpuUtil::unescapePath(absl::string_view path) { std::string result; result.reserve(path.length()); // Pre-allocate to avoid `reallocations` @@ -540,7 +538,7 @@ std::string CgroupCpuUtil::unescapePath(const std::string& path) { continue; } - std::string octal_str = path.substr(i + 1, 3); + absl::string_view octal_str = path.substr(i + 1, 3); // Validate all characters are valid octal digits (0-7) bool valid = std::all_of(octal_str.begin(), octal_str.end(), @@ -555,10 +553,10 @@ std::string CgroupCpuUtil::unescapePath(const std::string& path) { // Convert octal string to integer char* end; - long decoded = std::strtol(octal_str.c_str(), &end, 8); + long decoded = std::strtol(octal_str.data(), &end, 8); // Verify conversion was successful and complete - if (end != octal_str.c_str() + 3 || decoded > 255) { + if (end != octal_str.data() + 3 || decoded > 255) { ENVOY_LOG_MISC(warn, "Invalid octal escape sequence in path '{}' at position {}", path, i); result += c; // Keep the backslash as-is continue; @@ -584,7 +582,7 @@ std::string CgroupCpuUtil::unescapePath(const std::string& path) { // NOTE: Mount points may contain escaped characters (\040 for space, \134 for backslash, etc.) // and must be unescaped before use. absl::optional CgroupCpuUtil::parseMountInfoLine(const std::string& line) { - const std::vector fields = absl::StrSplit(line, ' '); + const std::vector fields = absl::StrSplit(line, ' '); // Find the separator "-" to locate filesystem type field size_t separator_pos = 0; @@ -611,8 +609,8 @@ absl::optional CgroupCpuUtil::parseMountInfoLine(const std::string& return absl::nullopt; } - const std::string& mount_point_escaped = fields[4]; - const std::string& fs_type = fields[separator_pos + 1]; + absl::string_view mount_point_escaped = fields[4]; + absl::string_view fs_type = fields[separator_pos + 1]; // Check if this is a cgroup filesystem if (fs_type != "cgroup" && fs_type != "cgroup2") { diff --git a/source/server/cgroup_cpu_util.h b/source/server/cgroup_cpu_util.h index 633514a1179e9..439fdc72cf57d 100644 --- a/source/server/cgroup_cpu_util.h +++ b/source/server/cgroup_cpu_util.h @@ -141,7 +141,7 @@ class CgroupCpuUtil { * @param path The escaped path string from `mountinfo`. * @return The unescaped path string. */ - static std::string unescapePath(const std::string& path); + static std::string unescapePath(absl::string_view path); /** * Constructs complete `cgroup` path by combining mount point and process assignment. From d04e46c2cabe3e9627632111885f9eabd8e78209 Mon Sep 17 00:00:00 2001 From: aakugan <164153358+aakugan@users.noreply.github.com> Date: Mon, 11 May 2026 21:25:06 +0530 Subject: [PATCH 280/750] Add integration tests for the reverse tunnel reporter (#44345) ## Commit Message Add integration tests and examples for reverse tunnel reporter ## Additional Description Adds end-to-end integration tests and usage examples for the reverse tunnel reporting system introduced in the prior PRs (#44327, #44342, #44344). Integration tests: - `GrpcClientIntegrationTest`: spins up a full Envoy instance with the reverse tunnel bootstrap extensions and a real gRPC test server. Validates the complete flow: reverse tunnel establishment triggers connection events through the EventReporter new connections made using lds. - Test cases cover happy path, multi-listener fan-out, disconnect handling, reconnect with full push, and a release-only load test. Also fixes the call from the reverse_tunnel_filter to the reporting when done with tenant_isolation. Risk Level: Low ## Testing Integration tests. Signed-off-by: aakugan --- .../test/clients/BUILD | 43 ++ .../clients/grpc_client_integration_test.cc | 481 ++++++++++++++++++ .../test/clients/integration_test_utils.h | 231 +++++++++ .../reverse_tunnel/reverse_tunnel_filter.cc | 3 +- 4 files changed, 756 insertions(+), 2 deletions(-) create mode 100644 contrib/reverse_tunnel_reporter/test/clients/grpc_client_integration_test.cc create mode 100644 contrib/reverse_tunnel_reporter/test/clients/integration_test_utils.h diff --git a/contrib/reverse_tunnel_reporter/test/clients/BUILD b/contrib/reverse_tunnel_reporter/test/clients/BUILD index feeb167efd4d7..74fff54daa5ee 100644 --- a/contrib/reverse_tunnel_reporter/test/clients/BUILD +++ b/contrib/reverse_tunnel_reporter/test/clients/BUILD @@ -1,6 +1,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_test", + "envoy_cc_test_library", "envoy_contrib_package", ) @@ -26,3 +27,45 @@ envoy_cc_test( "//test/test_common:utility_lib", ], ) + +envoy_cc_test_library( + name = "integration_test_utils_lib", + hdrs = ["integration_test_utils.h"], + deps = [ + "//source/common/common:fmt_lib", + "//test/config:utility_lib", + "@envoy_api//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters:pkg_cc_proto", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/http/router/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/reverse_tunnel/v3:pkg_cc_proto", + ], +) + +envoy_cc_test( + name = "grpc_client_integration_test", + srcs = ["grpc_client_integration_test.cc"], + deps = [ + ":integration_test_utils_lib", + "//contrib/reverse_tunnel_reporter/source:config", + "//source/common/thread_local:thread_local_lib", + "//source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface:reverse_connection_resolver_lib", + "//source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface:reverse_tunnel_initiator_lib", + "//source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface:reverse_tunnel_acceptor_lib", + "//source/extensions/filters/http/router:config", + "//source/extensions/filters/network/http_connection_manager:config", + "//source/extensions/filters/network/reverse_tunnel:config", + "//test/integration:integration_lib", + "@com_github_grpc_grpc//:grpc++", + "@envoy_api//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client:pkg_cc_grpc", + "@envoy_api//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client:pkg_cc_proto", + "@envoy_api//contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters:pkg_cc_proto", + "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/upstreams/http/v3:pkg_cc_proto", + ], +) diff --git a/contrib/reverse_tunnel_reporter/test/clients/grpc_client_integration_test.cc b/contrib/reverse_tunnel_reporter/test/clients/grpc_client_integration_test.cc new file mode 100644 index 0000000000000..497ee9574db5c --- /dev/null +++ b/contrib/reverse_tunnel_reporter/test/clients/grpc_client_integration_test.cc @@ -0,0 +1,481 @@ +#include +#include +#include +#include + +#include "envoy/config/listener/v3/listener.pb.h" +#include "envoy/extensions/upstreams/http/v3/http_protocol_options.pb.h" + +#include "test/integration/integration.h" +#include "test/integration/utility.h" + +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/grpc_client.pb.h" +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/clients/grpc_client/stream_reverse_tunnels.grpc.pb.h" +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.pb.h" +#include "contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h" +#include "contrib/reverse_tunnel_reporter/test/clients/integration_test_utils.h" +#include "grpc++/grpc++.h" +#include "grpc++/server.h" +#include "grpc++/server_builder.h" +#include "grpc++/server_context.h" +#include "gtest/gtest.h" +#include "integration_test_utils.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +constexpr absl::string_view reporterName = + "envoy.extensions.reverse_tunnel.reverse_tunnel_reporting_service.reporters.event_reporter"; +constexpr absl::string_view grpcClient = + "envoy.extensions.reverse_tunnel.reverse_tunnel_reporting_service.clients.grpc_client"; +constexpr std::chrono::milliseconds serverWait{5}; +constexpr std::size_t sendInterval{500}; +constexpr std::size_t maxRetries{3}; + +using envoy::extensions::reverse_tunnel_reporters::v3alpha::clients::grpc_client::GrpcClientConfig; +using envoy::extensions::reverse_tunnel_reporters::v3alpha::clients::grpc_client:: + ReverseTunnelReportingService; +using envoy::extensions::reverse_tunnel_reporters::v3alpha::clients::grpc_client:: + StreamReverseTunnelsRequest; +using envoy::extensions::reverse_tunnel_reporters::v3alpha::clients::grpc_client:: + StreamReverseTunnelsResponse; +using envoy::extensions::reverse_tunnel_reporters::v3alpha::reporters::EventReporterConfig; +using envoy::extensions::upstreams::http::v3::HttpProtocolOptions; + +class TestingService final : public ReverseTunnelReportingService::Service { + absl::flat_hash_map state_; + std::atomic num_events_; + absl::optional newInterval; + +public: + grpc::Status StreamReverseTunnels( + grpc::ServerContext* /*context*/, + grpc::ServerReaderWriter* stream) + override { + StreamReverseTunnelsRequest request; + int cnt{0}; + + ENVOY_LOG_MISC(error, "GrpcClientIntegrationTest: Status=Connected"); + + while (stream->Read(&request)) { + cnt++; + StreamReverseTunnelsResponse response; + response.set_request_nonce(request.nonce()); + + if (newInterval.has_value()) { + *response.mutable_report_interval() = + Protobuf::util::TimeUtil::MillisecondsToDuration(newInterval.value().count()); + } + + if (!stream->Write(response)) { + ENVOY_LOG_MISC(error, "GrpcClientIntegrationTest: Unable to send the response: {}", + response.DebugString()); + break; + } + + process(request); + } + + ENVOY_LOG_MISC(error, "GrpcClientIntegrationTest: Stream ended, total messages: {}", cnt); + + return grpc::Status::OK; + } + + void process(StreamReverseTunnelsRequest& req) { + for (auto& conn : req.added_tunnels()) { + state_[conn.name()] = + ReverseTunnelEvent::Connected{.node_id = conn.identity().node_id(), + .cluster_id = conn.identity().cluster_id(), + .tenant_id = conn.identity().tenant_id(), + .created_at = Envoy::SystemTime{}}; + } + + for (auto& name : req.removed_tunnel_names()) { + state_.erase(name); + } + + num_events_ += 1; + } + + absl::flat_hash_map getState() { return state_; } + + std::size_t numEvents() { return num_events_.load(); } + + void setInterval(std::chrono::milliseconds ms) { newInterval = ms; } +}; + +struct GrpcServer { + std::unique_ptr server_; + std::thread server_thread_; + TestingService service_; + + explicit GrpcServer(absl::string_view localhost) { + std::string server_address = fmt::format("{}:{}", localhost, reportingPort); + grpc::ServerBuilder builder; + + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + builder.RegisterService(&service_); + + server_ = std::unique_ptr(builder.BuildAndStart()); + server_thread_ = std::thread([this] { server_->Wait(); }); + } + + ~GrpcServer() { + auto deadline = std::chrono::system_clock::now() + serverWait; // NO_CHECK_FORMAT(real_time) + server_->Shutdown(deadline); + server_thread_.join(); + } +}; + +class GrpcClientIntegrationTest : public testing::TestWithParam, + public BaseIntegrationTest { +public: + GrpcClientIntegrationTest() + : BaseIntegrationTest(GetParam(), ConfigHelper::baseConfigNoListeners()) {} + + void initialize() override { + use_lds_ = true; + + std::string localhost = Network::Test::getLoopbackAddressString(GetParam()); + std::string anyhost = Network::Test::getAnyAddressString(GetParam()); + + addBootstrapExtension(getUpstreamExtension(getReporterConfig(), enable_tenant_isolation_), + config_helper_); + addBootstrapExtension(getDownstreamExtension(), config_helper_); + addCluster(getDownstreamCluster(localhost), config_helper_); + addListener(getUpstreamListener(anyhost), config_helper_, current_listeners_); + + auto cluster = getUpstreamCluster(localhost); + addCluster(getHttp2Cluster(cluster), config_helper_); + + BaseIntegrationTest::initialize(); + + current_config_ = ConfigHelper{version_, config_helper_.bootstrap()}; + } + +protected: + EventReporterConfig getReporterConfig() { + EventReporterConfig cfg; + cfg.set_stat_prefix(reporterName); + + auto* client = cfg.add_clients(); + client->set_name(grpcClient); + + GrpcClientConfig grpc_cfg; + grpc_cfg.set_stat_prefix("reverse_connection_grpc_client"); + grpc_cfg.set_cluster(upstreamCluster); + *(grpc_cfg.mutable_default_send_interval()) = + Protobuf::util::TimeUtil::MillisecondsToDuration(sendInterval); + *(grpc_cfg.mutable_connect_retry_interval()) = + Protobuf::util::TimeUtil::MillisecondsToDuration(sendInterval); + grpc_cfg.set_max_retries(maxRetries); + grpc_cfg.set_max_buffer_count(1'000'000); + client->mutable_typed_config()->PackFrom(grpc_cfg); + + return cfg; + } + + void updateLds(ConfigHelper& new_config) { + new_config.setLds(std::to_string(++cur_version_)); + // Wait for up to a minute for the values to propagate. + test_server_->waitForGauge("listener_manager.total_listeners_active", + testing::Eq(current_listeners_), std::chrono::seconds(60)); + current_config_ = std::move(new_config); + } + + void addListenerLds(Listener&& listener) { + ConfigHelper new_config{version_, current_config_.bootstrap()}; + addListener(std::move(listener), new_config, current_listeners_); + updateLds(new_config); + } + + void addListenerLds(std::vector&& listeners) { + ConfigHelper new_config{version_, current_config_.bootstrap()}; + + for (auto& listener : listeners) { + addListener(std::move(listener), new_config, current_listeners_); + } + + updateLds(new_config); + } + + void removeListenerLds(const std::string& name) { + ConfigHelper new_config{version_, current_config_.bootstrap()}; + removeListener(name, new_config, current_listeners_); + updateLds(new_config); + } + + void removeListenerLds(std::vector& names) { + ConfigHelper new_config{version_, current_config_.bootstrap()}; + + for (auto& name : names) { + removeListener(name, new_config, current_listeners_); + } + + updateLds(new_config); + } + + void + validateEqual(std::chrono::milliseconds ms, + const absl::flat_hash_map& expected) { + timeSystem().advanceTimeWait(ms); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + // This is fine no concern about thread safety -> we have slept for some time and no extra + // events should be coming now. + auto state = grpc_server_->service_.getState(); + // EXPECT_THAT(state_, testing::ContainerEq(expected)); + EXPECT_EQ(state.size(), expected.size()); + } + + absl::flat_hash_map + getConns(std::vector node_ids) { + absl::flat_hash_map connections; + + for (auto& node_id : node_ids) { + connections[ReverseTunnelEvent::getName(node_id)] = + ReverseTunnelEvent::Connected{.node_id = node_id, + .cluster_id = std::string(downstreamCluster), + .tenant_id = std::string(downstreamTenant), + .created_at = Envoy::SystemTime{}}; + } + + return connections; + } + + void makeNewServer() { + grpc_server_ = + std::make_unique(Network::Test::getLoopbackAddressUrlString(GetParam())); + } + + std::string getTenantIsolatedName(std::string name) { + return fmt::format("{}:{}", downstreamTenant, name); + } + + std::vector getTenantIsolatedNames(std::vector names) { + for (auto& name : names) { + name = getTenantIsolatedName(name); + } + + return names; + } + + void setNewSendInterval(std::chrono::milliseconds ms) { grpc_server_->service_.setInterval(ms); } + + std::size_t numEvents() { return grpc_server_->service_.numEvents(); } + + std::function callback_; + std::unique_ptr grpc_server_; + + int current_listeners_{0}; + int cur_version_{0}; + + ConfigHelper current_config_{version_, config_helper_.bootstrap()}; + bool enable_tenant_isolation_{false}; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, GrpcClientIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +/** + * Test: HappyPath + * Tests the standard lifecycle: gRPC stream establishment, reporting new + * tunnel additions via LDS, and reporting removals when listeners are deleted. + */ +TEST_P(GrpcClientIntegrationTest, HappyPath) { + makeNewServer(); + initialize(); + + addListenerLds(getDownstreamListener("node-1", 1)); + addListenerLds(getDownstreamListener("node-2", 1)); + + // Give envoy time to make the rc, connect to the test server, timer to fire + // and send the updates. + validateEqual(std::chrono::milliseconds(sendInterval * 3), getConns({"node-1", "node-2"})); + + removeListenerLds("node-1"); + removeListenerLds("node-2"); + + validateEqual(std::chrono::milliseconds(sendInterval * 3), getConns({})); +} + +/** + * Test: TenantIsolation. + * Tests the working of the code when enabled with tenant isolation. + */ +TEST_P(GrpcClientIntegrationTest, TenantIsolation) { + enable_tenant_isolation_ = true; + + initialize(); + makeNewServer(); + + addListenerLds(getDownstreamListener("node-1", 1)); + addListenerLds(getDownstreamListener("node-2", 1)); + validateEqual(std::chrono::milliseconds(sendInterval * 3), + getConns(getTenantIsolatedNames({"node-1", "node-2"}))); + + removeListenerLds("node-1"); + removeListenerLds("node-2"); + validateEqual(std::chrono::milliseconds(sendInterval * 3), getConns({})); +} + +/** + * Test: ServerReconnect + * Tests that Envoy performs a "Full State Push" upon reconnection. + * Even if tunnels didn't change, they must be re-reported on a new gRPC stream. + */ +TEST_P(GrpcClientIntegrationTest, ServerReconnect) { + makeNewServer(); + initialize(); + + addListenerLds(getDownstreamListener("node-1", 1)); + addListenerLds(getDownstreamListener("node-2", 1)); + validateEqual(std::chrono::milliseconds(sendInterval * 3), getConns({"node-1", "node-2"})); + + makeNewServer(); + // Time to disconnect, connect and then full push. + validateEqual(std::chrono::milliseconds(sendInterval * 5), getConns({"node-1", "node-2"})); + + removeListenerLds("node-1"); + removeListenerLds("node-2"); + + validateEqual(std::chrono::milliseconds(sendInterval * 3), getConns({})); +} + +/** + * Test: EventsWhenServerIsDead + * Tests "Event Convergence." If state changes (Add/Remove) happen while the + * server is down, the client must report the final ground truth once back online. + */ +TEST_P(GrpcClientIntegrationTest, EventsWhenServerIsDead) { + makeNewServer(); + initialize(); + + addListenerLds(getDownstreamListener("node-1", 1)); + addListenerLds(getDownstreamListener("node-2", 1)); + validateEqual(std::chrono::milliseconds(sendInterval * 3), getConns({"node-1", "node-2"})); + + grpc_server_ = nullptr; + addListenerLds(getDownstreamListener("node-3", 1)); + test_server_->waitForGauge("listener.upstreamListener.downstream_cx_active", testing::Eq(3), + std::chrono::milliseconds(sendInterval * 3)); + removeListenerLds("node-1"); + // Wait for the connections to establish and drain + test_server_->waitForGauge("listener.upstreamListener.downstream_cx_active", testing::Eq(2), + std::chrono::milliseconds(sendInterval * 3)); + + makeNewServer(); + validateEqual(std::chrono::milliseconds(sendInterval * 3), getConns({"node-3", "node-2"})); + + removeListenerLds("node-2"); + removeListenerLds("node-3"); + validateEqual(std::chrono::milliseconds(sendInterval * 3), getConns({})); +} + +/** + * Test: ServerDead + * Tests the gRPC client's retry engine. It verifies that the client + * continues to attempt connections based on 'connect_retry_interval' forever. + * We have limited to max_retries + 2 as we had to stop somewhere. + */ +TEST_P(GrpcClientIntegrationTest, ServerDead) { + initialize(); + // First one on server init. + test_server_->waitForCounter( + "reverse_connection_grpc_client.connection_attempts.cluster.upstreamCluster", testing::Eq(1)); + + for (std::size_t i = 0; i <= maxRetries; i++) { + // Wait for the timer to fire first. + timeSystem().advanceTimeWait(std::chrono::milliseconds(sendInterval)); + test_server_->waitForCounter( + "reverse_connection_grpc_client.connection_attempts.cluster.upstreamCluster", + testing::Eq(i + 2)); + } +} + +/** + * Test: ServerLate + * Tests the "Catch-up" scenario. Verifies that if tunnels exist before the + * reporting service is reachable, the client pushes the state immediately on connection. + */ +TEST_P(GrpcClientIntegrationTest, ServerLate) { + initialize(); + + addListenerLds(getDownstreamListener("node-1", 1)); + addListenerLds(getDownstreamListener("node-2", 1)); + // Wait for the connections to establish. + test_server_->waitForGauge("listener.upstreamListener.downstream_cx_active", testing::Eq(2), + std::chrono::milliseconds(sendInterval * 3)); + + makeNewServer(); + validateEqual(std::chrono::milliseconds(sendInterval * 3), getConns({"node-1", "node-2"})); + + removeListenerLds("node-1"); + removeListenerLds("node-2"); + validateEqual(std::chrono::milliseconds(sendInterval * 3), getConns({})); +} + +/** + * Test: ServerSentTime. + * Tests that the client uses the time sent by the server for the updates. + * Overriding the default value. + */ +TEST_P(GrpcClientIntegrationTest, ServerSentTime) { + initialize(); + makeNewServer(); + + std::size_t num_events_1 = numEvents(); + timeSystem().advanceTimeWait(std::chrono::milliseconds(2 * sendInterval)); + std::size_t num_events_2 = numEvents(); + + EXPECT_GE(num_events_2 - num_events_1, 1); + + // Now change the send Interval to sendInterval / 5. + setNewSendInterval(std::chrono::milliseconds(sendInterval / 5)); + timeSystem().advanceTimeWait(std::chrono::milliseconds(2 * sendInterval)); + std::size_t num_events_3 = numEvents(); + + // This would have been scheduled and so we dont expect a change in the scheduling. + EXPECT_GE(num_events_3 - num_events_2, 1); + + // Now we track the numEvents and check that the sendInterval is smaller -> more events. + timeSystem().advanceTimeWait(std::chrono::milliseconds(2 * sendInterval)); + std::size_t num_events_4 = numEvents(); + EXPECT_GE(num_events_4 - num_events_3, 5); +} + +// Only run the load test in optimized builds. +#if defined(NDEBUG) +TEST_P(GrpcClientIntegrationTest, LoadTest) { + makeNewServer(); + initialize(); + + // Limited to 1000 to reduce test flakiness. + int sz = 1000; + std::vector nodes(sz); + for (int i = 0; i < sz; i++) { + nodes[i] = fmt::format("node-{}", i); + } + + std::vector listeners(sz); + for (int i = 0; i < sz; i++) { + listeners[i] = getDownstreamListener(nodes[i], 1); + } + + addListenerLds(std::move(listeners)); + // Pure overhead of running the client should be minimal. + validateEqual(std::chrono::milliseconds(sendInterval * 5), getConns(nodes)); + + removeListenerLds(nodes); + // Allow more time for the discovery of listener removal and then propogation. + validateEqual(std::chrono::milliseconds(sendInterval * 10), getConns({})); +} +#endif // defined(NDEBUG) + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/reverse_tunnel_reporter/test/clients/integration_test_utils.h b/contrib/reverse_tunnel_reporter/test/clients/integration_test_utils.h new file mode 100644 index 0000000000000..7bd0e585d9a54 --- /dev/null +++ b/contrib/reverse_tunnel_reporter/test/clients/integration_test_utils.h @@ -0,0 +1,231 @@ +#pragma once + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/config/core/v3/extension.pb.h" +#include "envoy/config/listener/v3/listener.pb.h" +#include "envoy/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/v3/downstream_reverse_connection_socket_interface.pb.h" +#include "envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/upstream_reverse_connection_socket_interface.pb.h" +#include "envoy/extensions/filters/http/router/v3/router.pb.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" +#include "envoy/extensions/filters/network/reverse_tunnel/v3/reverse_tunnel.pb.h" + +#include "source/common/common/fmt.h" + +#include "test/config/utility.h" + +#include "contrib/envoy/extensions/reverse_tunnel_reporters/v3alpha/reporters/event_reporter.pb.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +constexpr absl::string_view downstreamExtension = + "envoy.bootstrap.reverse_tunnel.downstream_socket_interface"; +constexpr absl::string_view upstreamExtension = + "envoy.bootstrap.reverse_tunnel.upstream_socket_interface"; +constexpr absl::string_view downstreamCluster = "downstreamCluster"; +constexpr absl::string_view upstreamCluster = "upstreamCluster"; +constexpr absl::string_view upstreamListener = "upstreamListener"; +constexpr absl::string_view upstreamFilter = "envoy.filters.network.reverse_tunnel"; +constexpr absl::string_view downstreamTenant = "downstreamTenant"; +constexpr absl::string_view downstreamResolver = "envoy.resolvers.reverse_connection"; + +constexpr int upstreamPort = 9000; +constexpr int reportingPort = 8082; + +using envoy::config::bootstrap::v3::Bootstrap; +using envoy::config::cluster::v3::Cluster; +using envoy::config::core::v3::TypedExtensionConfig; +using envoy::config::listener::v3::Listener; +using envoy::extensions::bootstrap::reverse_tunnel::downstream_socket_interface::v3:: + DownstreamReverseConnectionSocketInterface; +using envoy::extensions::bootstrap::reverse_tunnel::upstream_socket_interface::v3:: + UpstreamReverseConnectionSocketInterface; +using envoy::extensions::filters::network::reverse_tunnel::v3::ReverseTunnel; +using envoy::extensions::reverse_tunnel_reporters::v3alpha::reporters::EventReporterConfig; + +inline TypedExtensionConfig getDownstreamExtension() { + DownstreamReverseConnectionSocketInterface cfg; + cfg.set_stat_prefix(downstreamExtension); + cfg.set_enable_detailed_stats(true); + + TypedExtensionConfig ext; + ext.set_name(downstreamExtension); + ext.mutable_typed_config()->PackFrom(cfg); + + return ext; +} + +inline TypedExtensionConfig getUpstreamExtension(const EventReporterConfig& config, + bool enable_tenant_isolation) { + UpstreamReverseConnectionSocketInterface cfg; + cfg.set_stat_prefix(upstreamExtension); + cfg.set_enable_detailed_stats(true); + cfg.mutable_enable_tenant_isolation()->set_value(enable_tenant_isolation); + const std::string reporterName = + "envoy.extensions.reverse_tunnel.reverse_tunnel_reporting_service.reporters.event_reporter"; + auto* reporter = cfg.mutable_reporter_config(); + reporter->set_name(reporterName); + reporter->mutable_typed_config()->PackFrom(config); + + TypedExtensionConfig ext; + ext.set_name(upstreamExtension); + ext.mutable_typed_config()->PackFrom(cfg); + + return ext; +} + +inline Cluster getDownstreamCluster(absl::string_view localhost) { + Cluster cluster; + cluster.set_name(downstreamCluster); + cluster.set_type(Cluster::STATIC); + cluster.mutable_connect_timeout()->set_seconds(30); + cluster.mutable_load_assignment()->set_cluster_name(downstreamCluster); + + auto* sa = cluster.mutable_load_assignment() + ->mutable_endpoints() + ->Add() + ->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + + sa->set_address(localhost); + sa->set_port_value(upstreamPort); + + return cluster; +} + +inline Cluster getUpstreamCluster(absl::string_view localhost) { + Cluster cluster; + cluster.set_name(upstreamCluster); + cluster.set_type(Cluster::STATIC); + cluster.mutable_connect_timeout()->set_seconds(30); + cluster.mutable_load_assignment()->set_cluster_name(upstreamCluster); + + auto* sa = cluster.mutable_load_assignment() + ->mutable_endpoints() + ->Add() + ->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + + sa->set_address(localhost); + sa->set_port_value(reportingPort); + + return cluster; +} + +inline Listener getUpstreamListener(absl::string_view anyhost) { + Listener listener; + listener.set_name(upstreamListener); + listener.set_stat_prefix(upstreamListener); + + auto* sa = listener.mutable_address()->mutable_socket_address(); + sa->set_address(anyhost); + sa->set_port_value(upstreamPort); + + auto* filter = listener.add_filter_chains()->add_filters(); + filter->set_name(upstreamFilter); + + ReverseTunnel rtFilter; + rtFilter.mutable_ping_interval()->set_seconds(300); // No ping timeouts. + + filter->mutable_typed_config()->PackFrom(rtFilter); + + return listener; +} + +inline Listener getDownstreamListener(const std::string& name, int num_listeners) { + Listener listener; + listener.set_name(name); + + auto* sa = listener.mutable_address()->mutable_socket_address(); + sa->set_address(fmt::format("rc://{}:{}:{}@{}:{}", name, downstreamCluster, downstreamTenant, + downstreamCluster, num_listeners)); + sa->set_port_value(0); + sa->set_resolver_name(downstreamResolver); + + listener.mutable_listener_filters_timeout()->set_seconds(0); + + auto* filter_chain = listener.add_filter_chains(); + auto* filter = filter_chain->add_filters(); + filter->set_name("envoy.filters.network.http_connection_manager"); + + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager hcm; + hcm.set_stat_prefix(name); + + auto* route_config = hcm.mutable_route_config(); + auto* vh = route_config->add_virtual_hosts(); + vh->set_name(name); + vh->add_domains("*"); + + auto* route = vh->add_routes(); + route->mutable_match()->set_prefix("/"); + auto* direct_response = route->mutable_direct_response(); + direct_response->set_status(200); + direct_response->mutable_body()->set_inline_string("reverse connection listener OK"); + + auto* http_filter = hcm.add_http_filters(); + http_filter->set_name("envoy.filters.http.router"); + envoy::extensions::filters::http::router::v3::Router router_cfg; + http_filter->mutable_typed_config()->PackFrom(router_cfg); + + filter->mutable_typed_config()->PackFrom(hcm); + + return listener; +} + +void addCluster(Cluster&& cluster, ConfigHelper& config_helper) { + config_helper.addConfigModifier([cluster = std::move(cluster)](Bootstrap& bootstrap) { + *(bootstrap.mutable_static_resources()->mutable_clusters()->Add()) = cluster; + }); +} + +void addListener(Listener&& listener, ConfigHelper& config_helper, int& current) { + config_helper.addConfigModifier([listener = std::move(listener)](Bootstrap& bootstrap) { + *(bootstrap.mutable_static_resources()->mutable_listeners()->Add()) = listener; + }); + + ++current; +} + +void addBootstrapExtension(TypedExtensionConfig&& extension, ConfigHelper& config_helper) { + config_helper.addConfigModifier([extension = std::move(extension)](Bootstrap& bootstrap) { + *(bootstrap.mutable_bootstrap_extensions()->Add()) = extension; + }); +} + +void removeListener(const std::string& name, ConfigHelper& config_helper, int& current) { + config_helper.addConfigModifier([&name](Bootstrap& bootstrap) { + auto* listeners = bootstrap.mutable_static_resources()->mutable_listeners(); + + for (int i = 0; i < listeners->size(); ++i) { + if (listeners->Get(i).name() == name) { + listeners->DeleteSubrange(i, 1); + break; + } + } + }); + + --current; +} + +Cluster getHttp2Cluster(Cluster& cluster) { + ConfigHelper::HttpProtocolOptions http2_options; + http2_options.mutable_explicit_http_config()->mutable_http2_protocol_options(); + + (*cluster.mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] + .PackFrom(http2_options); + + return cluster; +} + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc b/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc index 959501f3e3d90..99b6e95b9a23a 100644 --- a/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc +++ b/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc @@ -581,8 +581,7 @@ void ReverseTunnelFilter::processAcceptedConnection(absl::string_view node_id, // Report the connection to the extension -> reporter. if (auto extension = socket_manager->getUpstreamExtension()) { - extension->reportConnection(std::string(node_id), std::string(cluster_id), - std::string(tenant_id)); + extension->reportConnection(socket_node_id, socket_cluster_id, tenant_id); } } From d485ce5563f76740472da3a8744b206b39389af2 Mon Sep 17 00:00:00 2001 From: code Date: Tue, 12 May 2026 00:03:28 +0800 Subject: [PATCH 281/750] clang tidy: fix contrib only clang tidy issues (#44985) Commit Message: clang tidy: fix contrib only clang tidy issues Additional Description: Contrib only clang tidy issues. Risk Level: low. Testing: n/a. Docs Changes: n/a. Release Notes: n/a. Platform Specific Features: n/a. --------- Signed-off-by: wbpcode/wangbaiping --- .../source/cryptomb_private_key_provider.h | 20 +-- .../filters/http/source/dynamo_filter.h | 4 +- contrib/golang/common/go/api/api.h | 3 + contrib/golang/filters/http/source/cgo.cc | 2 +- contrib/golang/filters/http/source/config.h | 2 +- .../filters/http/source/golang_filter.h | 3 + .../filters/http/test/golang_filter_test.cc | 1 - .../http/test/golang_integration_test.cc | 4 +- .../test_data/destroyconfig/destroyconfig.h | 2 + .../golang/filters/network/source/golang.h | 8 +- .../golang/filters/network/source/upstream.h | 10 +- .../filters/common/source/metadata_object.cc | 2 +- .../peer_metadata/source/peer_metadata.cc | 2 +- .../http/peer_metadata/test/filter_test.cc | 2 +- .../upstream_kafka_client_impl_unit_test.cc | 2 +- .../network/test/request_codec_unit_test.cc | 2 +- .../network/test/response_codec_unit_test.cc | 2 +- .../filters/network/source/mysql_decoder.h | 2 +- .../network/source/mysql_decoder_impl.cc | 3 +- .../test/mysql_ssl_integration_test.cc | 18 +-- .../http/test/peak_ewma_filter_test.cc | 2 - .../source/peak_ewma_lb.cc | 6 +- .../test/peak_ewma_lb_host_lifecycle_test.cc | 2 - .../listener/test/postgres_inspector_test.cc | 5 - .../qatzstd/compressor/source/config.cc | 3 +- .../qatzstd/compressor/source/config.h | 5 +- .../source/qatzstd_compressor_impl.cc | 8 +- .../source/qatzstd_compressor_impl.h | 6 +- .../qat/private_key_providers/source/qat.h | 8 +- .../source/qat_private_key_provider.h | 6 +- .../source/clients/grpc_client/client.cc | 3 +- .../source/clients/grpc_client/client.h | 2 +- .../reporters/event_reporter/reporter.cc | 2 +- .../source/reverse_tunnel_event_types.h | 2 +- .../test/clients/grpc_client_test.cc | 126 +++++++++--------- .../test/reporters/event_reporter_test.cc | 1 - .../filters/network/source/metadata.h | 2 +- .../network/source/app_exception_impl.cc | 2 +- .../filters/network/source/encoder.cc | 2 +- .../filters/network/source/metadata.h | 16 +-- .../network/source/router/router_impl.h | 10 +- .../filters/network/source/tra/tra_impl.h | 2 +- .../network/test/app_exception_impl_test.cc | 1 + .../source/wasm_filter_stat_sink_impl.cc | 4 +- .../sxg/filters/http/source/filter_config.cc | 4 +- .../udp_sink/source/udp_sink_impl.cc | 4 +- .../udp_sink/test/udp_sink_config_test.cc | 3 - .../udp_sink/test/udp_sink_interation_test.cc | 24 ++-- .../tap_sinks/udp_sink/test/udp_sink_test.cc | 24 ++-- 49 files changed, 189 insertions(+), 190 deletions(-) diff --git a/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.h b/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.h index 5ca044ac1c8fc..351409b459517 100644 --- a/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.h +++ b/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.h @@ -50,7 +50,7 @@ class CryptoMbContext { Event::Dispatcher& dispatcher_; Ssl::PrivateKeyConnectionCallbacks& cb_; // For scheduling the callback to the next dispatcher cycle. - Event::SchedulableCallbackPtr schedulable_{}; + Event::SchedulableCallbackPtr schedulable_; }; // CryptoMbEcdsaContext is a CryptoMbContext which holds the extra ECDSA parameters and has @@ -63,9 +63,9 @@ class CryptoMbEcdsaContext : public CryptoMbContext { bool ecdsaInit(const uint8_t* in, size_t in_len); // ECDSA key. - bssl::UniquePtr ec_key_{}; + bssl::UniquePtr ec_key_; // ECDSA context to create the ephemeral key k_. - bssl::UniquePtr ctx_{}; + bssl::UniquePtr ctx_; BIGNUM* k_{}; // ECDSA parameters, which will contain values whose memory is managed within // BoringSSL ECDSA key structure, so not wrapped in smart pointers. @@ -88,7 +88,7 @@ class CryptoMbRsaContext : public CryptoMbContext { bool rsaInit(const uint8_t* in, size_t in_len); // RSA key. - bssl::UniquePtr rsa_{}; + bssl::UniquePtr rsa_; // RSA parameters. Const pointers, which will contain values whose memory is // managed within BoringSSL RSA key structure, so not wrapped in smart // pointers. @@ -142,13 +142,13 @@ class CryptoMbQueue : public Logger::Loggable { int key_size_{}; // Thread local data slot. - ThreadLocal::SlotPtr slot_{}; + ThreadLocal::SlotPtr slot_; // Crypto operations library interface. - IppCryptoSharedPtr ipp_{}; + IppCryptoSharedPtr ipp_; // Timer to trigger queue processing if eight requests are not received in time. - Event::TimerPtr timer_{}; + Event::TimerPtr timer_; CryptoMbStats& stats_; }; @@ -170,10 +170,10 @@ class CryptoMbPrivateKeyConnection : public Logger::Loggable pkey_; }; @@ -211,7 +211,7 @@ class CryptoMbPrivateKeyMethodProvider : public virtual Ssl::PrivateKeyMethodPro CryptoMbQueue queue_; }; - Ssl::BoringSslPrivateKeyMethodSharedPtr method_{}; + Ssl::BoringSslPrivateKeyMethodSharedPtr method_; Api::Api& api_; bssl::UniquePtr pkey_; enum KeyType key_type_; diff --git a/contrib/dynamo/filters/http/source/dynamo_filter.h b/contrib/dynamo/filters/http/source/dynamo_filter.h index dca3fddebb62d..d47a7eb2072df 100644 --- a/contrib/dynamo/filters/http/source/dynamo_filter.h +++ b/contrib/dynamo/filters/http/source/dynamo_filter.h @@ -71,9 +71,9 @@ class DynamoFilter : public Http::StreamFilter { const DynamoStatsSharedPtr stats_; bool enabled_{}; - std::string operation_{}; + std::string operation_; RequestParser::TableDescriptor table_descriptor_{"", true}; - std::string error_type_{}; + std::string error_type_; MonotonicTime start_decode_; Http::ResponseHeaderMap* response_headers_; Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; diff --git a/contrib/golang/common/go/api/api.h b/contrib/golang/common/go/api/api.h index b2f47e313fbd2..5546118b8b0ae 100644 --- a/contrib/golang/common/go/api/api.h +++ b/contrib/golang/common/go/api/api.h @@ -20,6 +20,7 @@ typedef struct { // NOLINT(modernize-use-using) uint64_t len; } Cstring; +// NOLINTNEXTLINE(readability-identifier-naming) struct httpRequest; typedef struct { // NOLINT(modernize-use-using) @@ -28,6 +29,7 @@ typedef struct { // NOLINT(modernize-use-using) int state; } processState; +// NOLINTNEXTLINE(readability-identifier-naming) typedef struct httpRequest { // NOLINT(modernize-use-using) Cstring plugin_name; uint64_t configId; @@ -36,6 +38,7 @@ typedef struct httpRequest { // NOLINT(modernize-use-using) uint32_t worker_id; // This flag will be read & written by different threads, so it need to be atomic _Atomic(int) is_golang_processing_log; + // NOLINTNEXTLINE(readability-identifier-naming) } httpRequest; typedef struct { // NOLINT(modernize-use-using) diff --git a/contrib/golang/filters/http/source/cgo.cc b/contrib/golang/filters/http/source/cgo.cc index 6fb12f07fc728..0186cc6cd1dd5 100644 --- a/contrib/golang/filters/http/source/cgo.cc +++ b/contrib/golang/filters/http/source/cgo.cc @@ -113,7 +113,7 @@ CAPIStatus envoyGoFilterHttpSendLocalReply(void* s, int response_code, void* bod for (size_t i = 0; i < header_values.size(); i += 2) { const auto& key = header_values[i]; const auto& value = header_values[i + 1]; - if (value.length() > 0) { + if (!value.empty()) { headers.addCopy(Http::LowerCaseString(key), value); } } diff --git a/contrib/golang/filters/http/source/config.h b/contrib/golang/filters/http/source/config.h index 1e3a4a3cac899..95985092a196e 100644 --- a/contrib/golang/filters/http/source/config.h +++ b/contrib/golang/filters/http/source/config.h @@ -25,7 +25,7 @@ class GolangFilterConfig : public Common::FactoryBase< GolangFilterConfig() : FactoryBase(CanonicalName) {} private: - Server::ServerLifecycleNotifier::HandlePtr handler_{}; + Server::ServerLifecycleNotifier::HandlePtr handler_; Http::FilterFactoryCb createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::golang::v3alpha::Config& proto_config, diff --git a/contrib/golang/filters/http/source/golang_filter.h b/contrib/golang/filters/http/source/golang_filter.h index 950ac1e8ecfaa..4ef6d181ee4ca 100644 --- a/contrib/golang/filters/http/source/golang_filter.h +++ b/contrib/golang/filters/http/source/golang_filter.h @@ -57,6 +57,7 @@ class MetricStore { using MetricStoreSharedPtr = std::shared_ptr; +// NOLINTNEXTLINE(readability-identifier-naming) struct httpConfigInternal; class SecretReader { @@ -448,8 +449,10 @@ class Filter : public Http::StreamFilter, std::atomic has_destroyed_{false}; }; +// NOLINTNEXTLINE(readability-identifier-naming) struct httpConfigInternal : httpConfig { std::weak_ptr config_; + // NOLINTNEXTLINE(readability-identifier-naming) httpConfigInternal(std::weak_ptr c) { config_ = c; } std::weak_ptr weakFilterConfig() { return config_; } }; diff --git a/contrib/golang/filters/http/test/golang_filter_test.cc b/contrib/golang/filters/http/test/golang_filter_test.cc index 5bc625be4312b..955a09cc76191 100644 --- a/contrib/golang/filters/http/test/golang_filter_test.cc +++ b/contrib/golang/filters/http/test/golang_filter_test.cc @@ -31,7 +31,6 @@ using testing::AtLeast; using testing::InSequence; using testing::Invoke; using testing::Return; -using testing::SaveArg; namespace Envoy { namespace Extensions { diff --git a/contrib/golang/filters/http/test/golang_integration_test.cc b/contrib/golang/filters/http/test/golang_integration_test.cc index 91ebc9102a631..c83969f578f00 100644 --- a/contrib/golang/filters/http/test/golang_integration_test.cc +++ b/contrib/golang/filters/http/test/golang_integration_test.cc @@ -886,7 +886,7 @@ name: golang }); } - if (add_endpoint != "") { + if (!add_endpoint.empty()) { config_helper_.addConfigModifier( [add_endpoint](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { auto* cluster_0 = bootstrap.mutable_static_resources()->mutable_clusters()->Mutable(0); @@ -924,7 +924,7 @@ name: golang ASSERT_TRUE(response->waitForEndStream(std::chrono::milliseconds(100000))); EXPECT_EQ(expected_status_code, response->headers().getStatusValue()); - if (expected_upstream_host != "") { + if (!expected_upstream_host.empty()) { EXPECT_TRUE(absl::StrContains(getHeader(response->headers(), "rsp-upstream-host"), expected_upstream_host)); } diff --git a/contrib/golang/filters/http/test/test_data/destroyconfig/destroyconfig.h b/contrib/golang/filters/http/test/test_data/destroyconfig/destroyconfig.h index b0473e7e0a46e..c2027898f3107 100644 --- a/contrib/golang/filters/http/test/test_data/destroyconfig/destroyconfig.h +++ b/contrib/golang/filters/http/test/test_data/destroyconfig/destroyconfig.h @@ -4,6 +4,7 @@ #include "api.h" #ifdef __cplusplus +// NOLINTNEXTLINE(readability-identifier-naming) struct httpDestroyableConfig : httpConfig { int destroyed; }; @@ -12,6 +13,7 @@ extern "C" { typedef struct { httpConfig c; int destroyed; + // NOLINTNEXTLINE(readability-identifier-naming) } httpDestroyableConfig; #endif diff --git a/contrib/golang/filters/network/source/golang.h b/contrib/golang/filters/network/source/golang.h index e35913c2d4920..bd293b9a4ba14 100644 --- a/contrib/golang/filters/network/source/golang.h +++ b/contrib/golang/filters/network/source/golang.h @@ -91,15 +91,15 @@ class Filter : public Network::Filter, Server::Configuration::FactoryContext& context_; const FilterConfigSharedPtr config_; const uint64_t config_id_; - std::string plugin_name_{}; + std::string plugin_name_; Dso::NetworkFilterDsoPtr dynamic_lib_{nullptr}; FilterWrapper* wrapper_{nullptr}; Event::Dispatcher* dispatcher_{nullptr}; bool closed_{false}; Network::ReadFilterCallbacks* read_callbacks_{}; - std::string local_addr_{}; - std::string addr_{}; + std::string local_addr_; + std::string addr_; Thread::MutexBasicLockable mutex_{}; }; @@ -112,7 +112,7 @@ struct FilterWrapper { FilterWrapper(FilterWeakPtr ptr) : filter_ptr_(ptr) {} ~FilterWrapper() = default; - FilterWeakPtr filter_ptr_{}; + FilterWeakPtr filter_ptr_; // anchor a string temporarily, make sure it won't be freed before copied to Go. std::string str_value_; }; diff --git a/contrib/golang/filters/network/source/upstream.h b/contrib/golang/filters/network/source/upstream.h index 797baad746ec8..3dd35dc2a72f4 100644 --- a/contrib/golang/filters/network/source/upstream.h +++ b/contrib/golang/filters/network/source/upstream.h @@ -76,10 +76,10 @@ class UpstreamConn : public Tcp::ConnectionPool::Callbacks, private: struct DispatcherStore { - std::vector> dispatchers_ ABSL_GUARDED_BY(lock_){}; + std::vector> dispatchers_ ABSL_GUARDED_BY(lock_); int dispatcher_idx_ ABSL_GUARDED_BY(lock_){0}; Thread::MutexBasicLockable lock_{}; - std::once_flag init_once_{}; + std::once_flag init_once_; }; static DispatcherStore& dispatcherStore() { MUTABLE_CONSTRUCT_ON_FIRST_USE(DispatcherStore); } @@ -104,8 +104,8 @@ class UpstreamConn : public Tcp::ConnectionPool::Callbacks, Upstream::HostDescriptionConstSharedPtr host_{nullptr}; Tcp::ConnectionPool::Cancellable* handler_{nullptr}; bool closed_{false}; - std::string addr_{}; - std::string remote_addr_{}; + std::string addr_; + std::string remote_addr_; }; using UpstreamConnPtr = std::shared_ptr; @@ -117,7 +117,7 @@ struct UpstreamConnWrapper { // Must be strong shared_ptr, otherwise the UpstreamConn will be released immediately since we do // not have any other place to keep strong reference of the UpstreamConn. - UpstreamConnPtr conn_ptr_{}; + UpstreamConnPtr conn_ptr_; // anchor a string temporarily, make sure it won't be freed before copied to Go. std::string str_value_; }; diff --git a/contrib/istio/filters/common/source/metadata_object.cc b/contrib/istio/filters/common/source/metadata_object.cc index 6ab5038dc152b..bbcaf2e00ad45 100644 --- a/contrib/istio/filters/common/source/metadata_object.cc +++ b/contrib/istio/filters/common/source/metadata_object.cc @@ -505,7 +505,7 @@ convertBaggageToWorkloadMetadata(absl::string_view data, absl::string_view ident break; case BaggageToken::WorkloadName: { workload = parts.second; - std::vector splitWorkloadKey = absl::StrSplit(parts.first, "."); + std::vector splitWorkloadKey = absl::StrSplit(parts.first, '.'); if (splitWorkloadKey.size() >= 2 && splitWorkloadKey[0] == "k8s") { workload_type = fromSuffix(splitWorkloadKey[1]); } diff --git a/contrib/istio/filters/http/peer_metadata/source/peer_metadata.cc b/contrib/istio/filters/http/peer_metadata/source/peer_metadata.cc index 76950ef436e8b..b80ad7a1dfa50 100644 --- a/contrib/istio/filters/http/peer_metadata/source/peer_metadata.cc +++ b/contrib/istio/filters/http/peer_metadata/source/peer_metadata.cc @@ -241,7 +241,7 @@ void BaggagePropagationMethod::inject(const StreamInfo::StreamInfo&, Http::Heade headers.setReference(Headers::get().Baggage, value_); } -BaggageDiscoveryMethod::BaggageDiscoveryMethod() {} +BaggageDiscoveryMethod::BaggageDiscoveryMethod() = default; absl::optional BaggageDiscoveryMethod::derivePeerInfo(const StreamInfo::StreamInfo&, Http::HeaderMap& headers, diff --git a/contrib/istio/filters/http/peer_metadata/test/filter_test.cc b/contrib/istio/filters/http/peer_metadata/test/filter_test.cc index 331bc1a3933c9..8fc39365250e8 100644 --- a/contrib/istio/filters/http/peer_metadata/test/filter_test.cc +++ b/contrib/istio/filters/http/peer_metadata/test/filter_test.cc @@ -83,7 +83,7 @@ class PeerMetadataTest : public testing::Test { void checkShared(bool expected) { EXPECT_EQ(expected, - stream_info_.filterState()->objectsSharedWithUpstreamConnection()->size() > 0); + !stream_info_.filterState()->objectsSharedWithUpstreamConnection()->empty()); } NiceMock context_; NiceMock singleton_manager_; diff --git a/contrib/kafka/filters/network/test/mesh/upstream_kafka_client_impl_unit_test.cc b/contrib/kafka/filters/network/test/mesh/upstream_kafka_client_impl_unit_test.cc index 10478d4892ebd..923d2a36c8d06 100644 --- a/contrib/kafka/filters/network/test/mesh/upstream_kafka_client_impl_unit_test.cc +++ b/contrib/kafka/filters/network/test/mesh/upstream_kafka_client_impl_unit_test.cc @@ -30,7 +30,7 @@ class UpstreamKafkaClientTest : public testing::Test { protected: Event::MockDispatcher dispatcher_; Thread::ThreadFactory& thread_factory_ = Thread::threadFactoryForTest(); - NiceMock kafka_utils_{}; + NiceMock kafka_utils_; RawKafkaConfig config_ = {{"key1", "value1"}, {"key2", "value2"}}; std::unique_ptr producer_ptr_ = std::make_unique(); diff --git a/contrib/kafka/filters/network/test/request_codec_unit_test.cc b/contrib/kafka/filters/network/test/request_codec_unit_test.cc index 9a49b45d64316..a976a68253d4e 100644 --- a/contrib/kafka/filters/network/test/request_codec_unit_test.cc +++ b/contrib/kafka/filters/network/test/request_codec_unit_test.cc @@ -44,7 +44,7 @@ using MockRequestCallbackSharedPtr = std::shared_ptr; class RequestCodecUnitTest : public testing::Test, public BufferBasedTest { protected: MockParserFactory initial_parser_factory_{}; - MockRequestParserResolver parser_resolver_{}; + MockRequestParserResolver parser_resolver_; MockRequestCallbackSharedPtr callback_{std::make_shared()}; }; diff --git a/contrib/kafka/filters/network/test/response_codec_unit_test.cc b/contrib/kafka/filters/network/test/response_codec_unit_test.cc index ce208b1985005..6cb268bcfcf42 100644 --- a/contrib/kafka/filters/network/test/response_codec_unit_test.cc +++ b/contrib/kafka/filters/network/test/response_codec_unit_test.cc @@ -44,7 +44,7 @@ using MockResponseCallbackSharedPtr = std::shared_ptr; class ResponseCodecUnitTest : public testing::Test, public BufferBasedTest { protected: MockResponseInitialParserFactory factory_{}; - MockResponseParserResolver parser_resolver_{}; + MockResponseParserResolver parser_resolver_; MockResponseCallbackSharedPtr callback_{std::make_shared()}; }; diff --git a/contrib/mysql_proxy/filters/network/source/mysql_decoder.h b/contrib/mysql_proxy/filters/network/source/mysql_decoder.h index 60ad85c33c54d..63f406d42b8e5 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_decoder.h +++ b/contrib/mysql_proxy/filters/network/source/mysql_decoder.h @@ -66,7 +66,7 @@ class Decoder { protected: // Decoder attributes. Extensions::Common::SQLUtils::SQLUtils::DecoderAttributes attributes_; - std::vector payload_metadata_list_{}; + std::vector payload_metadata_list_; }; using DecoderPtr = std::unique_ptr; diff --git a/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.cc b/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.cc index 7175985c33ffa..47f233a6cd524 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.cc +++ b/contrib/mysql_proxy/filters/network/source/mysql_decoder_impl.cc @@ -182,7 +182,7 @@ bool DecoderImpl::decode(Buffer::Instance& data, bool is_upstream) { bool return_without_parse = false; bool result = true; - auto current_state = session_.getState(); + const auto current_state = session_.getState(); // ignore ssl message if (current_state == MySQLSession::State::SslPt) { @@ -208,7 +208,6 @@ bool DecoderImpl::decode(Buffer::Instance& data, bool is_upstream) { if (session_.getState() == MySQLSession::State::ReqResp && seq == MYSQL_REQUEST_PKT_NUM) { session_.resetSeq(); session_.setState(MySQLSession::State::Req); - current_state = session_.getState(); } else { ENVOY_LOG(info, "mysql_proxy: ignoring out-of-sync packet"); callbacks_.onProtocolError(); diff --git a/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc b/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc index 0f3e571293f70..a2f8ff86fbfb8 100644 --- a/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc +++ b/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc @@ -181,6 +181,7 @@ class MySQLSSLIntegrationTest : public testing::TestWithParamwaitForData([](const std::string& data) { return data.length() > 0; }, + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return !data.empty(); }, &upstream_data)); // 5. Server responds with fast auth success (seq=2, rewritten to seq=3 for client). @@ -277,7 +278,7 @@ TEST_P(MySQLSSLIntegrationTest, CachingSha2FullAuthRsaMediation) { clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); std::string upstream_data; - ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return !data.empty(); }, &upstream_data)); // 4. Server: full auth required (seq=2). @@ -356,7 +357,7 @@ TEST_P(MySQLSSLIntegrationTest, CachingSha2FullAuthRsaErr) { clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); std::string upstream_data; - ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return !data.empty(); }, &upstream_data)); // Full auth required. @@ -424,7 +425,7 @@ TEST_P(MySQLSSLIntegrationTest, SslTerminateLoginThenQuery) { clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); std::string upstream_data; - ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return !data.empty(); }, &upstream_data)); // Server OK (seq=2, rewritten to seq=3 for client). @@ -476,7 +477,7 @@ TEST_P(MySQLSSLIntegrationTest, CachingSha2FullAuthRsaThenQuery) { clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); std::string upstream_data; - ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return !data.empty(); }, &upstream_data)); // Full auth required → client password → RSA. @@ -749,6 +750,7 @@ class MySQLAllowIntegrationTest : public testing::TestWithParamwaitForData([](const std::string& data) { return data.length() > 0; }, + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return !data.empty(); }, &upstream_data)); ASSERT_TRUE(fake_upstream->write(encodeClientLoginResp(MYSQL_RESP_OK))); @@ -818,7 +820,7 @@ TEST_P(MySQLAllowIntegrationTest, AllowNonSslClientLogin) { clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM)); std::string upstream_data; - ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return !data.empty(); }, &upstream_data)); ASSERT_TRUE(fake_upstream->write(encodeClientLoginResp(MYSQL_RESP_OK))); @@ -854,7 +856,7 @@ TEST_P(MySQLAllowIntegrationTest, AllowSslFullAuthRsaMediation) { clientWrite(encodeClientLogin(CLIENT_PROTOCOL_41, "testuser", CHALLENGE_SEQ_NUM + 1)); std::string upstream_data; - ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return data.length() > 0; }, + ASSERT_TRUE(fake_upstream->waitForData([](const std::string& data) { return !data.empty(); }, &upstream_data)); // Full auth required. diff --git a/contrib/peak_ewma/filters/http/test/peak_ewma_filter_test.cc b/contrib/peak_ewma/filters/http/test/peak_ewma_filter_test.cc index c947915d48e90..b0945b99d55e8 100644 --- a/contrib/peak_ewma/filters/http/test/peak_ewma_filter_test.cc +++ b/contrib/peak_ewma/filters/http/test/peak_ewma_filter_test.cc @@ -8,10 +8,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::NiceMock; using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/contrib/peak_ewma/load_balancing_policies/source/peak_ewma_lb.cc b/contrib/peak_ewma/load_balancing_policies/source/peak_ewma_lb.cc index 3945561499338..c00516c72952e 100644 --- a/contrib/peak_ewma/load_balancing_policies/source/peak_ewma_lb.cc +++ b/contrib/peak_ewma/load_balancing_policies/source/peak_ewma_lb.cc @@ -270,13 +270,15 @@ double PeakEwmaLoadBalancer::updateEwmaWithSample(double current_ewma, double ne void PeakEwmaLoadBalancer::processHostSamples(Upstream::HostConstSharedPtr /* host */, PeakEwmaHostLbPolicyData* data) { - if (!data) + if (!data) { return; + } // Get the range of new samples to process (atomic ring buffer). auto [last_processed, current_write] = data->getNewSampleRange(); - if (last_processed == current_write) + if (last_processed == current_write) { return; + } // If ring buffer was fully overwritten, skip to oldest valid slot. // Uses unsigned arithmetic (always correct since write_index_ only increments). diff --git a/contrib/peak_ewma/load_balancing_policies/test/peak_ewma_lb_host_lifecycle_test.cc b/contrib/peak_ewma/load_balancing_policies/test/peak_ewma_lb_host_lifecycle_test.cc index 30f5010b18476..0c78d75cb5e35 100644 --- a/contrib/peak_ewma/load_balancing_policies/test/peak_ewma_lb_host_lifecycle_test.cc +++ b/contrib/peak_ewma/load_balancing_policies/test/peak_ewma_lb_host_lifecycle_test.cc @@ -12,8 +12,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::Invoke; using testing::NiceMock; using testing::Return; using testing::ReturnRef; diff --git a/contrib/postgres_inspector/filters/listener/test/postgres_inspector_test.cc b/contrib/postgres_inspector/filters/listener/test/postgres_inspector_test.cc index 467c9cbf6cb95..71be47e16b8c2 100644 --- a/contrib/postgres_inspector/filters/listener/test/postgres_inspector_test.cc +++ b/contrib/postgres_inspector/filters/listener/test/postgres_inspector_test.cc @@ -16,14 +16,9 @@ #include "gtest/gtest.h" using testing::_; -using testing::DoAll; -using testing::InSequence; using testing::Invoke; -using testing::InvokeWithoutArgs; using testing::NiceMock; -using testing::Return; using testing::ReturnRef; -using testing::SaveArg; namespace Envoy { namespace Extensions { diff --git a/contrib/qat/compression/qatzstd/compressor/source/config.cc b/contrib/qat/compression/qatzstd/compressor/source/config.cc index 1813258a07ae9..8c0ce55a3404b 100644 --- a/contrib/qat/compression/qatzstd/compressor/source/config.cc +++ b/contrib/qat/compression/qatzstd/compressor/source/config.cc @@ -23,8 +23,7 @@ QatzstdCompressorFactory::QatzstdCompressorFactory( } } -QatzstdCompressorFactory::QatzstdThreadLocal::QatzstdThreadLocal() - : initialized_(false), sequenceProducerState_(nullptr) {} +QatzstdCompressorFactory::QatzstdThreadLocal::QatzstdThreadLocal() = default; QatzstdCompressorFactory::QatzstdThreadLocal::~QatzstdThreadLocal() { if (initialized_) { diff --git a/contrib/qat/compression/qatzstd/compressor/source/config.h b/contrib/qat/compression/qatzstd/compressor/source/config.h index 0142816075a57..b385fac6bf0f2 100644 --- a/contrib/qat/compression/qatzstd/compressor/source/config.h +++ b/contrib/qat/compression/qatzstd/compressor/source/config.h @@ -49,9 +49,10 @@ class QatzstdCompressorFactory : public Envoy::Compression::Compressor::Compress public Logger::Loggable { QatzstdThreadLocal(); ~QatzstdThreadLocal() override; + // NOLINTNEXTLINE(readability-identifier-naming) void* GetQATSession(); - bool initialized_; - void* sequenceProducerState_; + bool initialized_{false}; + void* sequenceProducerState_{nullptr}; }; const uint32_t compression_level_; const bool enable_checksum_; diff --git a/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.cc b/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.cc index b623d57320058..90b3950bc3364 100644 --- a/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.cc +++ b/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.cc @@ -10,11 +10,11 @@ QatzstdCompressorImpl::QatzstdCompressorImpl(uint32_t compression_level, bool en uint32_t strategy, uint32_t chunk_size, bool enable_qat_zstd, uint32_t qat_zstd_fallback_threshold, - void* sequenceProducerState) + void* sequence_producer_state) : ZstdCompressorImplBase(compression_level, enable_checksum, strategy, chunk_size), enable_qat_zstd_(enable_qat_zstd), qat_zstd_fallback_threshold_(qat_zstd_fallback_threshold), - sequenceProducerState_(sequenceProducerState), - input_ptr_{std::make_unique(chunk_size)}, input_len_(0), chunk_size_(chunk_size) { + sequence_producer_state_(sequence_producer_state), + input_ptr_{std::make_unique(chunk_size)}, chunk_size_(chunk_size) { size_t result; result = ZSTD_CCtx_setParameter(cctx_.get(), ZSTD_c_compressionLevel, compression_level_); RELEASE_ASSERT(!ZSTD_isError(result), ""); @@ -25,7 +25,7 @@ QatzstdCompressorImpl::QatzstdCompressorImpl(uint32_t compression_level, bool en compression_level, strategy, chunk_size, enable_qat_zstd, qat_zstd_fallback_threshold); if (enable_qat_zstd_) { /* register qatSequenceProducer */ - ZSTD_registerSequenceProducer(cctx_.get(), sequenceProducerState_, qatSequenceProducer); + ZSTD_registerSequenceProducer(cctx_.get(), sequence_producer_state_, qatSequenceProducer); result = ZSTD_CCtx_setParameter(cctx_.get(), ZSTD_c_enableSeqProducerFallback, 1); RELEASE_ASSERT(!ZSTD_isError(result), ""); } diff --git a/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.h b/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.h index 1960b13b7671d..a9cc157ab5f40 100644 --- a/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.h +++ b/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.h @@ -23,7 +23,7 @@ class QatzstdCompressorImpl : public Envoy::Compression::Zstd::Compressor::ZstdC public: QatzstdCompressorImpl(uint32_t compression_level, bool enable_checksum, uint32_t strategy, uint32_t chunk_size, bool enable_qat_zstd, - uint32_t qat_zstd_fallback_threshold, void* sequenceProducerState); + uint32_t qat_zstd_fallback_threshold, void* sequence_producer_state); private: void compressPreprocess(Buffer::Instance& buffer, @@ -38,9 +38,9 @@ class QatzstdCompressorImpl : public Envoy::Compression::Zstd::Compressor::ZstdC bool enable_qat_zstd_; const uint32_t qat_zstd_fallback_threshold_; - void* sequenceProducerState_; + void* sequence_producer_state_; std::unique_ptr input_ptr_; - uint64_t input_len_; + uint64_t input_len_{0}; uint64_t chunk_size_; }; diff --git a/contrib/qat/private_key_providers/source/qat.h b/contrib/qat/private_key_providers/source/qat.h index 4e85a9da57a1e..69e96db9d56e7 100644 --- a/contrib/qat/private_key_providers/source/qat.h +++ b/contrib/qat/private_key_providers/source/qat.h @@ -47,14 +47,14 @@ class QatHandle : public Logger::Loggable { int getNodeAffinity(); int isDone(); - Thread::ThreadPtr polling_thread_{}; + Thread::ThreadPtr polling_thread_; Thread::MutexBasicLockable poll_lock_{}; Thread::CondVar qat_thread_cond_{}; private: CpaInstanceHandle handle_; CpaInstanceInfo2 info_; - LibQatCryptoSharedPtr libqat_{}; + LibQatCryptoSharedPtr libqat_; int users_{}; bool done_{}; }; @@ -75,7 +75,7 @@ class QatSection : public Logger::Loggable { Cpa16U num_instances_{}; std::vector qat_handles_; int next_handle_{}; - LibQatCryptoSharedPtr libqat_{}; + LibQatCryptoSharedPtr libqat_; }; /** @@ -96,7 +96,7 @@ class QatManager : public std::enable_shared_from_this, bool checkQatDevice(); private: - LibQatCryptoSharedPtr libqat_{}; + LibQatCryptoSharedPtr libqat_; bool qat_is_supported_{true}; }; diff --git a/contrib/qat/private_key_providers/source/qat_private_key_provider.h b/contrib/qat/private_key_providers/source/qat_private_key_provider.h index 7636430233c92..c24df1b4ad3f6 100644 --- a/contrib/qat/private_key_providers/source/qat_private_key_provider.h +++ b/contrib/qat/private_key_providers/source/qat_private_key_provider.h @@ -29,7 +29,7 @@ class QatPrivateKeyConnection { private: Ssl::PrivateKeyConnectionCallbacks& cb_; Event::Dispatcher& dispatcher_; - Event::FileEventPtr ssl_async_event_{}; + Event::FileEventPtr ssl_async_event_; QatHandle& handle_; bssl::UniquePtr pkey_; }; @@ -51,12 +51,12 @@ class QatPrivateKeyMethodProvider : public virtual Ssl::PrivateKeyMethodProvider Ssl::BoringSslPrivateKeyMethodSharedPtr getBoringSslPrivateKeyMethod() override; private: - Ssl::BoringSslPrivateKeyMethodSharedPtr method_{}; + Ssl::BoringSslPrivateKeyMethodSharedPtr method_; std::shared_ptr manager_; std::shared_ptr section_; Api::Api& api_; bssl::UniquePtr pkey_; - LibQatCryptoSharedPtr libqat_{}; + LibQatCryptoSharedPtr libqat_; bool initialized_{}; }; diff --git a/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.cc b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.cc index cd8223ca2bc60..71b2c6b5ca726 100644 --- a/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.cc +++ b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.cc @@ -270,8 +270,9 @@ StreamTunnelsReq GrpcClient::constructMessage(bool full_push) { } void GrpcClient::setTimer(Event::TimerPtr& timer, const std::chrono::milliseconds& ms) { - if (timer->enabled()) + if (timer->enabled()) { timer->disableTimer(); + } timer->enableTimer(ms); } diff --git a/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h index 15676b0dba490..65283539be166 100644 --- a/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h +++ b/contrib/reverse_tunnel_reporter/source/clients/grpc_client/client.h @@ -130,7 +130,7 @@ class GrpcClient : public ReverseTunnelReporterClient, // grpc client and stream requirements Grpc::AsyncClient async_client_; - Grpc::AsyncStream stream_{}; + Grpc::AsyncStream stream_; const Protobuf::MethodDescriptor& service_method_; // timers diff --git a/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.cc b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.cc index 33b5530677fc5..0410286660f88 100644 --- a/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.cc +++ b/contrib/reverse_tunnel_reporter/source/reporters/event_reporter/reporter.cc @@ -62,7 +62,7 @@ EventReporterStats EventReporter::generateStats(const std::string& prefix, Stats } void EventReporter::notifyClients(ReverseTunnelEvent::TunnelUpdates&& updates) { - ASSERT(clients_.size() > 0, "Need atleast one client. Enforced via the protos."); + ASSERT(!clients_.empty(), "Need at least one client. Enforced via the protos."); for (size_t i = 0; i < clients_.size() - 1; i++) { clients_[i]->receiveEvents(updates); diff --git a/contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h b/contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h index ae4df047082da..5831bdd636492 100644 --- a/contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h +++ b/contrib/reverse_tunnel_reporter/source/reverse_tunnel_event_types.h @@ -75,7 +75,7 @@ struct TunnelUpdates { // on sending the data alone. class ReverseTunnelReporterWithState : public ReverseTunnelReporter { public: - virtual ~ReverseTunnelReporterWithState() = default; + ~ReverseTunnelReporterWithState() override = default; virtual ReverseTunnelEvent::ConnectionsList getAllConnections() PURE; }; diff --git a/contrib/reverse_tunnel_reporter/test/clients/grpc_client_test.cc b/contrib/reverse_tunnel_reporter/test/clients/grpc_client_test.cc index 38c75f13b659c..d9aaaf49e3f65 100644 --- a/contrib/reverse_tunnel_reporter/test/clients/grpc_client_test.cc +++ b/contrib/reverse_tunnel_reporter/test/clients/grpc_client_test.cc @@ -38,7 +38,7 @@ class MockReverseTunnelReporter : public ReverseTunnelReporterWithState { MOCK_METHOD(ReverseTunnelEvent::ConnectionsList, getAllConnections, (), (override)); }; -GrpcConfigProto mock_config() { +GrpcConfigProto mockConfig() { GrpcConfigProto config_; config_.set_cluster("test_cluster"); @@ -51,7 +51,7 @@ GrpcConfigProto mock_config() { return config_; } -ReverseTunnelEvent::ConnectionsList make_connections(std::vector node_ids) { +ReverseTunnelEvent::ConnectionsList makeConnections(std::vector node_ids) { ReverseTunnelEvent::ConnectionsList connections; std::string cluster = "test_cluster"; std::string tenant = "test_tenant"; @@ -65,7 +65,7 @@ ReverseTunnelEvent::ConnectionsList make_connections(std::vector no return connections; } -ReverseTunnelEvent::DisconnectionsList make_disconnections(std::vector node_ids) { +ReverseTunnelEvent::DisconnectionsList makeDisconnections(std::vector node_ids) { std::string cluster = "test_cluster"; ReverseTunnelEvent::DisconnectionsList disconnections; @@ -78,8 +78,8 @@ ReverseTunnelEvent::DisconnectionsList make_disconnections(std::vectornode_id, req.added_tunnels(i).identity().node_id()); + } - for (std::size_t i = 0; i < actual.disconnections.size(); i++) + for (std::size_t i = 0; i < actual.disconnections.size(); i++) { EXPECT_EQ(actual.disconnections[i]->name, req.removed_tunnel_names(i)); + } EXPECT_EQ(full_push, req.full_push()); @@ -108,7 +110,7 @@ Protobuf::Duration getHalfDuration(const Protobuf::Duration& dur) { class GrpcClientTest : public testing::Test { public: - GrpcClientTest() {} + GrpcClientTest() = default; void SetUp() override { api_ = Api::createApiForTest(time_system_); @@ -132,13 +134,13 @@ class GrpcClientTest : public testing::Test { } protected: - void inc_time(const Protobuf::Duration& dur) { + void incTime(const Protobuf::Duration& dur) { time_system_.advanceTimeAsyncImpl( std::chrono::milliseconds(DurationUtil::durationToMilliseconds(dur))); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); } - void get_stream(int times) { + void getStream(int times) { EXPECT_CALL(*async_client_, startRaw(_, _, _, _)) .Times(times) .WillRepeatedly(Invoke([this](absl::string_view, absl::string_view, @@ -175,7 +177,7 @@ class GrpcClientTest : public testing::Test { std::string node_id{"tunnel-v2"}; std::string cluster_id{"tunnel-v2"}; - GrpcConfigProto config_{mock_config()}; + GrpcConfigProto config_{mockConfig()}; NiceMock mock_reporter_; }; @@ -193,7 +195,7 @@ TEST_F(GrpcClientTest, RetryAttemptsOnStreamCreationFailure) { client.onServerInitialized(&mock_reporter_); for (std::size_t i = 0; i < config_.max_retries() + 1; i++) { - inc_time(config_.connect_retry_interval()); + incTime(config_.connect_retry_interval()); } // Not incremented because no connection attempt was successful. @@ -216,15 +218,15 @@ TEST_F(GrpcClientTest, ClientSendsFullPushOnConnect) { GrpcClient client{context_, config_}; auto stats_{getStats()}; - ReverseTunnelEvent::TunnelUpdates events{make_connections({"node_1"}), {}}; + ReverseTunnelEvent::TunnelUpdates events{makeConnections({"node_1"}), {}}; EXPECT_CALL(mock_reporter_, getAllConnections()).WillOnce(Return(events.connections)); - get_stream(1); + getStream(1); EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) .WillOnce(Invoke([&events](Buffer::InstancePtr& request, bool) { - auto resp = validate_req(request, events, true); + auto resp = validateReq(request, events, true); EXPECT_EQ(resp.request_nonce(), 1); })); @@ -251,14 +253,14 @@ TEST_F(GrpcClientTest, ClientSendsDiffAfterFullPush) { int cur = 0, total = 4; ReverseTunnelEvent::TunnelUpdates updates[] = { - ReverseTunnelEvent::TunnelUpdates{make_connections({"node_1"}), {}}, - ReverseTunnelEvent::TunnelUpdates{make_connections({"node_2"}), - make_disconnections({"node_1"})}, - ReverseTunnelEvent::TunnelUpdates{make_connections({"node_3", "node_4"}), {}}, - ReverseTunnelEvent::TunnelUpdates{make_connections({"node_5"}), - make_disconnections({"node_3"})}}; + ReverseTunnelEvent::TunnelUpdates{makeConnections({"node_1"}), {}}, + ReverseTunnelEvent::TunnelUpdates{makeConnections({"node_2"}), + makeDisconnections({"node_1"})}, + ReverseTunnelEvent::TunnelUpdates{makeConnections({"node_3", "node_4"}), {}}, + ReverseTunnelEvent::TunnelUpdates{makeConnections({"node_5"}), + makeDisconnections({"node_3"})}}; - get_stream(1); + getStream(1); EXPECT_CALL(mock_reporter_, getAllConnections()).WillOnce(Invoke([&updates]() { return updates[0].connections; @@ -267,7 +269,7 @@ TEST_F(GrpcClientTest, ClientSendsDiffAfterFullPush) { EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) .Times(total) .WillRepeatedly(Invoke([this, &updates, &cur](Buffer::InstancePtr& request, bool) { - auto resp = validate_req(request, updates[cur], cur == 0); + auto resp = validateReq(request, updates[cur], cur == 0); EXPECT_EQ(resp.request_nonce(), cur + 1); callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); })); @@ -277,7 +279,7 @@ TEST_F(GrpcClientTest, ClientSendsDiffAfterFullPush) { for (; cur < total; cur++) { client.receiveEvents(updates[cur]); - inc_time(config_.default_send_interval()); + incTime(config_.default_send_interval()); } int total_events = 0; @@ -307,7 +309,7 @@ TEST_F(GrpcClientTest, ReportIntervalChangesReflectInClient) { ReverseTunnelEvent::TunnelUpdates events; - get_stream(1); + getStream(1); EXPECT_CALL(mock_reporter_, getAllConnections()).WillOnce(Invoke([&events]() { return events.connections; @@ -318,7 +320,7 @@ TEST_F(GrpcClientTest, ReportIntervalChangesReflectInClient) { EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) .Times(3) .WillRepeatedly(Invoke([this, &events, &cur](Buffer::InstancePtr& request, bool) { - auto resp = validate_req(request, events, cur == 0); + auto resp = validateReq(request, events, cur == 0); EXPECT_EQ(resp.request_nonce(), ++cur); if (cur == 1) { @@ -335,8 +337,8 @@ TEST_F(GrpcClientTest, ReportIntervalChangesReflectInClient) { DurationUtil::durationToMilliseconds(getHalfDuration(config_.default_send_interval()))); // This is already scheduled from the next time we will use the half interval for sending. - inc_time(config_.default_send_interval()); - inc_time(getHalfDuration(config_.default_send_interval())); + incTime(config_.default_send_interval()); + incTime(getHalfDuration(config_.default_send_interval())); EXPECT_EQ(stats_.send_attempts_counter_.value(), 3); } @@ -348,15 +350,15 @@ TEST_F(GrpcClientTest, FullPushAndDiffOnReconnect) { int cur = 0, total = 4; ReverseTunnelEvent::TunnelUpdates updates[] = { - ReverseTunnelEvent::TunnelUpdates{make_connections({"node_1"}), {}}, - ReverseTunnelEvent::TunnelUpdates{make_connections({"node_2"}), - make_disconnections({"node_1"})}, - ReverseTunnelEvent::TunnelUpdates{make_connections({"node_3", "node_4"}), {}}, - ReverseTunnelEvent::TunnelUpdates{make_connections({"node_5"}), - make_disconnections({"node_3"})}}; + ReverseTunnelEvent::TunnelUpdates{makeConnections({"node_1"}), {}}, + ReverseTunnelEvent::TunnelUpdates{makeConnections({"node_2"}), + makeDisconnections({"node_1"})}, + ReverseTunnelEvent::TunnelUpdates{makeConnections({"node_3", "node_4"}), {}}, + ReverseTunnelEvent::TunnelUpdates{makeConnections({"node_5"}), + makeDisconnections({"node_3"})}}; // 2 stream creations: initial connect + reconnect after remote close. - get_stream(2); + getStream(2); // getAllConnections is called once per full push (initial + reconnect). EXPECT_CALL(mock_reporter_, getAllConnections()).Times(2).WillRepeatedly(Invoke([&updates]() { @@ -369,7 +371,7 @@ TEST_F(GrpcClientTest, FullPushAndDiffOnReconnect) { .WillRepeatedly(Invoke([this, &updates, &cur](Buffer::InstancePtr& request, bool) { // cur is still 0 during both the initial connect and the reconnect full push, // so both correctly validate as full_push=true against updates[0]. - auto resp = validate_req(request, updates[cur], cur == 0); + auto resp = validateReq(request, updates[cur], cur == 0); EXPECT_EQ(resp.request_nonce(), cur + 1); callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); })); @@ -378,12 +380,12 @@ TEST_F(GrpcClientTest, FullPushAndDiffOnReconnect) { // disconnect and reconnect -> sends the full push automatically callbacks_->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Unknown, "Testing"); - inc_time(config_.connect_retry_interval()); + incTime(config_.connect_retry_interval()); cur++; // cur becomes 1 only after the reconnect full push has already fired. for (; cur < total; cur++) { client.receiveEvents(updates[cur]); - inc_time(config_.default_send_interval()); + incTime(config_.default_send_interval()); } int total_sz = 0; @@ -418,7 +420,7 @@ TEST_F(GrpcClientTest, DisconnectOnTooManyUnAckedRequests) { std::size_t cur = 0; ReverseTunnelEvent::TunnelUpdates events; - get_stream(1); + getStream(1); // max_retries + 1: the initial connect sends once, then max_retries timer ticks each send once. // On the next timer tick send() sees (nonce_current_ - nonce_acked_) > max_retries and @@ -426,7 +428,7 @@ TEST_F(GrpcClientTest, DisconnectOnTooManyUnAckedRequests) { EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) .Times(config_.max_retries() + 1) .WillRepeatedly(Invoke([&events, &cur](Buffer::InstancePtr& request, bool) { - auto resp = validate_req(request, events, cur == 0); + auto resp = validateReq(request, events, cur == 0); EXPECT_EQ(resp.request_nonce(), cur + 1); // No ACK is sent. It should eventually disconnect. @@ -440,7 +442,7 @@ TEST_F(GrpcClientTest, DisconnectOnTooManyUnAckedRequests) { // max_retries + 2: we need max_retries timer ticks for the sends, plus one more tick // to trigger the disconnect check. The first send happens on connect (cur=0). for (; cur < config_.max_retries() + 2; cur++) { - inc_time(config_.default_send_interval()); + incTime(config_.default_send_interval()); } EXPECT_EQ(stats_.connection_attempts_counter_.value(), 1); @@ -463,11 +465,11 @@ TEST_F(GrpcClientTest, DisconnectOnNack) { auto stats_{getStats()}; ReverseTunnelEvent::TunnelUpdates events; - get_stream(1); + getStream(1); EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) .WillOnce(Invoke([this, &events](Buffer::InstancePtr& request, bool) { - auto resp = validate_req(request, events, true); + auto resp = validateReq(request, events, true); EXPECT_EQ(resp.request_nonce(), 1); resp.mutable_error_detail()->set_code(Grpc::Status::WellKnownGrpcStatus::Unavailable); callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); @@ -498,8 +500,8 @@ TEST_F(GrpcClientTest, DisconnectOnBufferFull) { nodes.push_back("node_" + std::to_string(i)); } - ReverseTunnelEvent::TunnelUpdates connect_events{make_connections(nodes), {}}; - get_stream(1); + ReverseTunnelEvent::TunnelUpdates connect_events{makeConnections(nodes), {}}; + getStream(1); EXPECT_CALL(mock_reporter_, getAllConnections()).WillOnce([&connect_events]() { return connect_events.connections; @@ -507,7 +509,7 @@ TEST_F(GrpcClientTest, DisconnectOnBufferFull) { EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) .WillOnce(Invoke([this, &connect_events](Buffer::InstancePtr& request, bool) { - auto resp = validate_req(request, connect_events, true); + auto resp = validateReq(request, connect_events, true); EXPECT_EQ(resp.request_nonce(), 1); callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); })); @@ -539,7 +541,7 @@ TEST_F(GrpcClientTest, OutOfOrderNonce) { std::size_t cur = 0; ReverseTunnelEvent::TunnelUpdates events; - get_stream(1); + getStream(1); // Send nonce=0 (already acked) for all responses to trigger out-of-order. // nonce=0 is always <= nonce_acked_ (which starts at 0), so every response lands @@ -549,7 +551,7 @@ TEST_F(GrpcClientTest, OutOfOrderNonce) { EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) .Times(config_.max_retries() + 1) .WillRepeatedly(Invoke([this, &events, &cur](Buffer::InstancePtr& request, bool) { - auto resp = validate_req(request, events, cur == 0); + auto resp = validateReq(request, events, cur == 0); EXPECT_EQ(resp.request_nonce(), cur + 1); resp.set_request_nonce(0); @@ -562,7 +564,7 @@ TEST_F(GrpcClientTest, OutOfOrderNonce) { cur++; for (; cur < config_.max_retries() + 2; cur++) { - inc_time(config_.default_send_interval()); + incTime(config_.default_send_interval()); } EXPECT_EQ(stats_.connection_attempts_counter_.value(), 1); @@ -586,14 +588,14 @@ TEST_F(GrpcClientTest, SkipNonce) { std::size_t cur = 0; ReverseTunnelEvent::TunnelUpdates events; - get_stream(1); + getStream(1); // max_retries + 2: the normal max_retries + 1 sends that would trigger disconnect, // but a late ACK at iteration max_retries advances nonce_acked_ and buys one more send. EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) .Times(config_.max_retries() + 2) .WillRepeatedly(Invoke([this, &events, &cur](Buffer::InstancePtr& request, bool) { - auto resp = validate_req(request, events, cur == 0); + auto resp = validateReq(request, events, cur == 0); EXPECT_EQ(resp.request_nonce(), cur + 1); // Only ACK the nonce at iteration max_retries, proving a single late ACK @@ -607,7 +609,7 @@ TEST_F(GrpcClientTest, SkipNonce) { cur++; for (; cur < config_.max_retries() + 2; cur++) { - inc_time(config_.default_send_interval()); + incTime(config_.default_send_interval()); } // After the stream is up, retroactively send ACKs for earlier nonces. @@ -632,11 +634,11 @@ TEST_F(GrpcClientTest, OkRemoteClose) { auto stats_{getStats()}; ReverseTunnelEvent::TunnelUpdates events; - get_stream(1); + getStream(1); EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) .WillOnce(Invoke([&events](Buffer::InstancePtr& request, bool) { - auto response = validate_req(request, events, true); + auto response = validateReq(request, events, true); EXPECT_EQ(response.request_nonce(), 1); })); @@ -661,7 +663,7 @@ TEST_F(GrpcClientTest, ReceiveEventsBeforeInitialized) { GrpcClient client{context_, config_}; auto stats_{getStats()}; - ReverseTunnelEvent::TunnelUpdates events{make_connections({"node_1"}), {}}; + ReverseTunnelEvent::TunnelUpdates events{makeConnections({"node_1"}), {}}; client.receiveEvents(std::move(events)); EXPECT_EQ(stats_.queued_updates_counter_.value(), 0); @@ -675,7 +677,7 @@ TEST_F(GrpcClientTest, ClusterNotFoundLogsAndReturns) { client.onServerInitialized(&mock_reporter_); - ReverseTunnelEvent::TunnelUpdates events{make_connections({"node_1"}), {}}; + ReverseTunnelEvent::TunnelUpdates events{makeConnections({"node_1"}), {}}; client.receiveEvents(std::move(events)); auto stats_{getStats()}; @@ -691,7 +693,7 @@ TEST_F(GrpcClientTest, ClientCreationFailureLogsAndReturns) { client.onServerInitialized(&mock_reporter_); - ReverseTunnelEvent::TunnelUpdates events{make_connections({"node_1"}), {}}; + ReverseTunnelEvent::TunnelUpdates events{makeConnections({"node_1"}), {}}; client.receiveEvents(std::move(events)); auto stats_{getStats()}; @@ -708,14 +710,14 @@ TEST_F(GrpcClientTest, BufferOverflowWhileDisconnectedDoesNotRearmRetry) { nodes.push_back("node_" + std::to_string(i)); } - ReverseTunnelEvent::TunnelUpdates big_update{make_connections(nodes), {}}; - get_stream(1); + ReverseTunnelEvent::TunnelUpdates big_update{makeConnections(nodes), {}}; + getStream(1); EXPECT_CALL(mock_reporter_, getAllConnections()).WillOnce(Return(big_update.connections)); EXPECT_CALL(*async_stream_, sendMessageRaw_(_, false)) .WillOnce(Invoke([this, &big_update](Buffer::InstancePtr& request, bool) { - auto resp = validate_req(request, big_update, true); + auto resp = validateReq(request, big_update, true); callbacks_->onReceiveMessageRaw(Grpc::Common::serializeMessage(resp)); })); @@ -724,7 +726,7 @@ TEST_F(GrpcClientTest, BufferOverflowWhileDisconnectedDoesNotRearmRetry) { client.onServerInitialized(&mock_reporter_); // First overflow: stream is alive, should disconnect. - client.receiveEvents(ReverseTunnelEvent::TunnelUpdates{make_connections(nodes), {}}); + client.receiveEvents(ReverseTunnelEvent::TunnelUpdates{makeConnections(nodes), {}}); EXPECT_EQ( stats_ .getCounter(stats_.disconnects_, @@ -734,7 +736,7 @@ TEST_F(GrpcClientTest, BufferOverflowWhileDisconnectedDoesNotRearmRetry) { 1); // Second overflow: stream is null, should NOT increment disconnect counter. - client.receiveEvents(ReverseTunnelEvent::TunnelUpdates{make_connections(nodes), {}}); + client.receiveEvents(ReverseTunnelEvent::TunnelUpdates{makeConnections(nodes), {}}); EXPECT_EQ( stats_ .getCounter(stats_.disconnects_, diff --git a/contrib/reverse_tunnel_reporter/test/reporters/event_reporter_test.cc b/contrib/reverse_tunnel_reporter/test/reporters/event_reporter_test.cc index 8365198eff70b..47ed487ed174a 100644 --- a/contrib/reverse_tunnel_reporter/test/reporters/event_reporter_test.cc +++ b/contrib/reverse_tunnel_reporter/test/reporters/event_reporter_test.cc @@ -15,7 +15,6 @@ using testing::_; using testing::Invoke; using testing::NiceMock; -using testing::Return; using testing::ReturnRef; namespace Envoy { diff --git a/contrib/rocketmq_proxy/filters/network/source/metadata.h b/contrib/rocketmq_proxy/filters/network/source/metadata.h index a2efeb031bbc5..dd49da1a24cb2 100644 --- a/contrib/rocketmq_proxy/filters/network/source/metadata.h +++ b/contrib/rocketmq_proxy/filters/network/source/metadata.h @@ -30,7 +30,7 @@ class MessageMetadata { private: bool is_oneway_{false}; - absl::optional topic_name_{}; + absl::optional topic_name_; Http::HeaderMapPtr headers_{Http::RequestHeaderMapImpl::create()}; }; diff --git a/contrib/sip_proxy/filters/network/source/app_exception_impl.cc b/contrib/sip_proxy/filters/network/source/app_exception_impl.cc index 3df9a6b52d6b2..2b05e0da6ba68 100644 --- a/contrib/sip_proxy/filters/network/source/app_exception_impl.cc +++ b/contrib/sip_proxy/filters/network/source/app_exception_impl.cc @@ -25,7 +25,7 @@ DirectResponse::ResponseType AppException::encode(MessageMetadata& metadata, // unique number. The salt I am using here is the summation of each // character of the proxy's IP address output << ";tag="; - if (metadata.ep().has_value() && metadata.ep().value().length() > 0) { + if (metadata.ep().has_value() && !metadata.ep().value().empty()) { output << fmt::format("{}-", metadata.ep().value()); } std::time_t t; diff --git a/contrib/sip_proxy/filters/network/source/encoder.cc b/contrib/sip_proxy/filters/network/source/encoder.cc index 1bc451d6e4b51..e62f4188dc3e2 100644 --- a/contrib/sip_proxy/filters/network/source/encoder.cc +++ b/contrib/sip_proxy/filters/network/source/encoder.cc @@ -16,7 +16,7 @@ void EncoderImpl::encode(const MessageMetadataSharedPtr& metadata, Buffer::Insta case OperationType::Insert: { std::string value = absl::get(operation.value_).value_; if (value == ";ep=" || value == ",opaque=") { - if (metadata->ep().has_value() && metadata->ep().value().length() > 0) { + if (metadata->ep().has_value() && !metadata->ep().value().empty()) { output += raw_msg.substr(previous_position, operation.position_ - previous_position); previous_position = operation.position_; diff --git a/contrib/sip_proxy/filters/network/source/metadata.h b/contrib/sip_proxy/filters/network/source/metadata.h index 1701200429ffc..cee312f8229a1 100644 --- a/contrib/sip_proxy/filters/network/source/metadata.h +++ b/contrib/sip_proxy/filters/network/source/metadata.h @@ -192,23 +192,23 @@ class MessageMetadata : public Logger::Loggable { std::vector> headers_{HeaderType::HeaderMaxNum}; std::vector operation_list_; - absl::optional ep_{}; - absl::optional opaque_{}; + absl::optional ep_; + absl::optional opaque_; - absl::optional> p_cookie_ip_map_{}; + absl::optional> p_cookie_ip_map_; - absl::optional transaction_id_{}; + absl::optional transaction_id_; - std::string destination_{}; + std::string destination_; - std::vector affinity_{}; + std::vector affinity_; std::vector::iterator affinity_iteration_{affinity_.begin()}; - std::string raw_msg_{}; + std::string raw_msg_; State state_{State::TransportBegin}; bool stop_load_balance_{}; - TraContextMap tra_context_map_{}; + TraContextMap tra_context_map_; bool isDomainMatched( HeaderType type, diff --git a/contrib/sip_proxy/filters/network/source/router/router_impl.h b/contrib/sip_proxy/filters/network/source/router/router_impl.h index eed22812d89cf..40d4758c0b5d6 100644 --- a/contrib/sip_proxy/filters/network/source/router/router_impl.h +++ b/contrib/sip_proxy/filters/network/source/router/router_impl.h @@ -120,8 +120,8 @@ struct ThreadLocalTransactionInfo : public ThreadLocal::ThreadLocalObject, audit_timer_ = dispatcher.createTimer([this]() -> void { auditTimerAction(); }); audit_timer_->enableTimer(std::chrono::seconds(2)); } - absl::flat_hash_map> transaction_info_map_{}; - absl::flat_hash_map> upstream_request_map_{}; + absl::flat_hash_map> transaction_info_map_; + absl::flat_hash_map> upstream_request_map_; std::shared_ptr parent_; Event::Dispatcher& dispatcher_; @@ -284,15 +284,15 @@ class Router : public Upstream::LoadBalancerContextBase, Upstream::ClusterManager& cluster_manager_; RouterStats& stats_; - RouteConstSharedPtr route_{}; + RouteConstSharedPtr route_; const RouteEntry* route_entry_{}; - MessageMetadataSharedPtr metadata_{}; + MessageMetadataSharedPtr metadata_; std::shared_ptr upstream_request_; SipFilters::DecoderFilterCallbacks* callbacks_{}; Upstream::ClusterInfoConstSharedPtr cluster_; Upstream::ThreadLocalCluster* thread_local_cluster_; - std::shared_ptr transaction_infos_{}; + std::shared_ptr transaction_infos_; std::shared_ptr settings_; Server::Configuration::FactoryContext& context_; }; diff --git a/contrib/sip_proxy/filters/network/source/tra/tra_impl.h b/contrib/sip_proxy/filters/network/source/tra/tra_impl.h index 14a426bf4e7bd..263a0d8eae58c 100644 --- a/contrib/sip_proxy/filters/network/source/tra/tra_impl.h +++ b/contrib/sip_proxy/filters/network/source/tra/tra_impl.h @@ -173,7 +173,7 @@ class GrpcClientImpl : public Client, Grpc::AsyncStream< envoy::extensions::filters::network::sip_proxy::tra::v3alpha::TraServiceRequest> - stream_{}; + stream_; GrpcClientImpl& parent_; }; diff --git a/contrib/sip_proxy/filters/network/test/app_exception_impl_test.cc b/contrib/sip_proxy/filters/network/test/app_exception_impl_test.cc index f99ac5240e30b..7eea3c6defaea 100644 --- a/contrib/sip_proxy/filters/network/test/app_exception_impl_test.cc +++ b/contrib/sip_proxy/filters/network/test/app_exception_impl_test.cc @@ -11,6 +11,7 @@ namespace SipProxy { TEST(AppExceptionImplTest, CopyConstructor) { AppException app_ex(AppExceptionType::InternalError, "msg"); + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) AppException copy(app_ex); EXPECT_EQ(app_ex.type_, copy.type_); diff --git a/contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.cc b/contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.cc index 9ecbc0086121a..7d40ffe84bfc4 100644 --- a/contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.cc +++ b/contrib/stat_sinks/wasm_filter/source/wasm_filter_stat_sink_impl.cc @@ -382,11 +382,11 @@ RegisterForeignFunction registerStatsFilterGetAllMetricTags( const auto& histograms = ctx->snapshot->histograms(); auto collectBufferedTags = [](const auto& metrics, const auto& buffer_to_snapshot, - auto extractTags) { + auto extract_tags) { std::vector all; all.reserve(buffer_to_snapshot.size()); for (uint32_t snapshot_index : buffer_to_snapshot) { - all.push_back(extractTags(metrics[snapshot_index])); + all.push_back(extract_tags(metrics[snapshot_index])); } return all; }; diff --git a/contrib/sxg/filters/http/source/filter_config.cc b/contrib/sxg/filters/http/source/filter_config.cc index 8d0fb33600175..6456c15d4f1df 100644 --- a/contrib/sxg/filters/http/source/filter_config.cc +++ b/contrib/sxg/filters/http/source/filter_config.cc @@ -34,10 +34,10 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::sxg::v3alpha: duration_(proto_config.has_duration() ? proto_config.duration().seconds() : 604800UL), cbor_url_(proto_config.cbor_url()), validity_url_(proto_config.validity_url()), mi_record_size_(proto_config.mi_record_size() ? proto_config.mi_record_size() : 4096L), - client_can_accept_sxg_header_(proto_config.client_can_accept_sxg_header().length() > 0 + client_can_accept_sxg_header_(!proto_config.client_can_accept_sxg_header().empty() ? proto_config.client_can_accept_sxg_header() : "x-client-can-accept-sxg"), - should_encode_sxg_header_(proto_config.should_encode_sxg_header().length() > 0 + should_encode_sxg_header_(!proto_config.should_encode_sxg_header().empty() ? proto_config.should_encode_sxg_header() : "x-should-encode-sxg"), header_prefix_filters_(initializeHeaderPrefixFilters(proto_config.header_prefix_filters())), diff --git a/contrib/tap_sinks/udp_sink/source/udp_sink_impl.cc b/contrib/tap_sinks/udp_sink/source/udp_sink_impl.cc index 649a8e5aae6d6..8e292a7fc048d 100644 --- a/contrib/tap_sinks/udp_sink/source/udp_sink_impl.cc +++ b/contrib/tap_sinks/udp_sink/source/udp_sink_impl.cc @@ -234,7 +234,7 @@ void UdpTapSink::UdpTapSinkHandle::handleSocketStreamedTraceForMultiEvents( src_repeated_events->DeleteSubrange(0, 1); // Check if the remaining original trace now fits within the size limit. - if (src_repeated_events->size() > 0 && + if (!src_repeated_events->empty() && static_cast(trace->ByteSizeLong()) < max_size_of_each_sub_data) { doSubmitTrace(std::move(trace), format); return; @@ -251,7 +251,7 @@ void UdpTapSink::UdpTapSinkHandle::handleSocketStreamedTraceForMultiEvents( submitTraceAndResetVariables(new_trace, format); // Send the original trace and return if its remaining size is within the allowed limit. - if (src_repeated_events->size() > 0 && + if (!src_repeated_events->empty() && static_cast(trace->ByteSizeLong()) < max_size_of_each_sub_data) { doSubmitTrace(std::move(trace), format); return; diff --git a/contrib/tap_sinks/udp_sink/test/udp_sink_config_test.cc b/contrib/tap_sinks/udp_sink/test/udp_sink_config_test.cc index 9b3f95925b020..f62fb37290ea0 100644 --- a/contrib/tap_sinks/udp_sink/test/udp_sink_config_test.cc +++ b/contrib/tap_sinks/udp_sink/test/udp_sink_config_test.cc @@ -21,9 +21,6 @@ namespace TapSinks { namespace UDP { // Test Udp sink -using ::testing::_; -using ::testing::AtLeast; -using ::testing::Return; namespace TapCommon = Extensions::Common::Tap; diff --git a/contrib/tap_sinks/udp_sink/test/udp_sink_interation_test.cc b/contrib/tap_sinks/udp_sink/test/udp_sink_interation_test.cc index dc583ef3aa872..98efbfc63d86f 100644 --- a/contrib/tap_sinks/udp_sink/test/udp_sink_interation_test.cc +++ b/contrib/tap_sinks/udp_sink/test/udp_sink_interation_test.cc @@ -160,11 +160,11 @@ name: tap sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; - if (inet_pton(AF_INET, UDP_SERVER_IP_, &server_addr.sin_addr) <= 0) { + if (inet_pton(AF_INET, udp_server_ip_, &server_addr.sin_addr) <= 0) { close(server_socket_); return false; } - server_addr.sin_port = htons(UDP_PORT_); + server_addr.sin_port = htons(udp_port_); if (bind(server_socket_, reinterpret_cast(&server_addr), sizeof(server_addr)) < 0) { close(server_socket_); @@ -186,13 +186,13 @@ name: tap // Return false because there is no any message. // Consider other logical if EWOULDBLOCK is occurred, and // shouldn't occur in UT env - isRcvMatchedUDPMsg_ = false; + is_rcv_matched_udp_msg_ = false; } else { // Go the message. std::string rcv_msg{buffer}; - if (rcv_msg.find(MATCHED_TAP_REQ_STR_) != std::string::npos && - rcv_msg.find(MATCHED_TAP_RESP_STR_) != std::string::npos) { - isRcvMatchedUDPMsg_ = true; + if (rcv_msg.find(matched_tap_req_str_) != std::string::npos && + rcv_msg.find(matched_tap_resp_str_) != std::string::npos) { + is_rcv_matched_udp_msg_ = true; } } } @@ -202,15 +202,15 @@ name: tap server_socket_ = -1; } - bool isUDPServerRcvMatchedUDPMsg(void) { return isRcvMatchedUDPMsg_; } + bool isUDPServerRcvMatchedUDPMsg(void) { return is_rcv_matched_udp_msg_; } private: - const int UDP_PORT_ = 8089; - const char* UDP_SERVER_IP_ = "127.0.0.1"; - const char* MATCHED_TAP_REQ_STR_ = "tapudp"; - const char* MATCHED_TAP_RESP_STR_ = "200"; + const int udp_port_ = 8089; + const char* udp_server_ip_ = "127.0.0.1"; + const char* matched_tap_req_str_ = "tapudp"; + const char* matched_tap_resp_str_ = "200"; int server_socket_ = -1; - bool isRcvMatchedUDPMsg_ = false; + bool is_rcv_matched_udp_msg_ = false; }; // Start UDP server firstly. diff --git a/contrib/tap_sinks/udp_sink/test/udp_sink_test.cc b/contrib/tap_sinks/udp_sink/test/udp_sink_test.cc index ab52636f04f65..6153b19f8d364 100644 --- a/contrib/tap_sinks/udp_sink/test/udp_sink_test.cc +++ b/contrib/tap_sinks/udp_sink/test/udp_sink_test.cc @@ -19,13 +19,11 @@ namespace TapSinks { namespace UDP { // Test Udp sink -using ::testing::_; -using ::testing::Return; class UdpTapSinkTest : public testing::Test { protected: - UdpTapSinkTest() {} - ~UdpTapSinkTest() {} + UdpTapSinkTest() = default; + ~UdpTapSinkTest() override = default; public: envoy::config::core::v3::SocketAddress socket_address_; @@ -103,8 +101,8 @@ TEST_F(UdpTapSinkTest, TestSubmitTraceForNotSUpportedFormat) { // re Mock class UdpPacketWriter and not use existing Network::MockUdpPacketWriter class MockUdpPacketWriterNew : public Network::UdpPacketWriter { public: - MockUdpPacketWriterNew(bool isReturnOk) : isReturnOkForwritePacket_(isReturnOk) {}; - ~MockUdpPacketWriterNew() override {}; + MockUdpPacketWriterNew(bool is_return_ok) : is_return_ok_forwrite_packet_(is_return_ok) {} + ~MockUdpPacketWriterNew() override = default; Api::IoCallUint64Result writePacket(const Buffer::Instance& buffer, const Network::Address::Ip* local_ip, @@ -112,10 +110,10 @@ class MockUdpPacketWriterNew : public Network::UdpPacketWriter { (void)buffer; (void)local_ip; (void)peer_address; - if (isReturnOkForwritePacket_) { - return Api::IoCallUint64Result(99, Api::IoError::none()); + if (is_return_ok_forwrite_packet_) { + return {99, Api::IoError::none()}; } else { - return Api::IoCallUint64Result(0, Network::IoSocketError::getIoSocketEagainError()); + return {0, Network::IoSocketError::getIoSocketEagainError()}; } } MOCK_METHOD(bool, isWriteBlocked, (), (const)); @@ -129,16 +127,16 @@ class MockUdpPacketWriterNew : public Network::UdpPacketWriter { MOCK_METHOD(Api::IoCallUint64Result, flush, ()); private: - const bool isReturnOkForwritePacket_; + const bool is_return_ok_forwrite_packet_; }; class UtSpecialUdpTapSink : public UdpTapSink { public: UtSpecialUdpTapSink(const envoy::extensions::tap_sinks::udp_sink::v3alpha::UdpSink& config) : UdpTapSink(config) {} - ~UtSpecialUdpTapSink() {} - void replaceOrigUdpPacketWriter(Network::UdpPacketWriterPtr&& utUdpPacketWriter) { - udp_packet_writer_ = std::move(utUdpPacketWriter); + ~UtSpecialUdpTapSink() override = default; + void replaceOrigUdpPacketWriter(Network::UdpPacketWriterPtr&& ut_udp_packet_writer) { + udp_packet_writer_ = std::move(ut_udp_packet_writer); } }; From f358b154f7890dc13fcfa9b1af8b44646cacdcb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 18:25:45 +0100 Subject: [PATCH 282/750] build(deps): bump urllib3 from 2.6.3 to 2.7.0 in /tools/base in the pip group across 1 directory (#45006) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 450b0062c2e56..400fa0d6f8f4e 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -1449,9 +1449,9 @@ uritemplate==4.1.1 \ --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e # via gidgethub -urllib3==2.6.3 \ - --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \ - --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 +urllib3==2.7.0 \ + --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \ + --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 # via # pygithub # requests From 751909c0e7f54c5e7bdc8dc6f1efb795e018308f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 18:32:15 +0100 Subject: [PATCH 283/750] build(deps): bump urllib3 from 2.6.3 to 2.7.0 in /docs/tools/python in the pip group across 1 directory (#45005) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/tools/python/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tools/python/requirements.txt b/docs/tools/python/requirements.txt index b9d78b91cd260..5a29366eaab31 100644 --- a/docs/tools/python/requirements.txt +++ b/docs/tools/python/requirements.txt @@ -1451,9 +1451,9 @@ uritemplate==4.2.0 \ --hash=sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e \ --hash=sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686 # via gidgethub -urllib3==2.6.3 \ - --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \ - --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 +urllib3==2.7.0 \ + --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \ + --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 # via requests uvloop==0.20.0 \ --hash=sha256:265a99a2ff41a0fd56c19c3838b29bf54d1d177964c300dad388b27e84fd7847 \ From 232401d25a289840690eb3c0be85bf818a8b0bfb Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 11 May 2026 18:57:29 +0100 Subject: [PATCH 284/750] deps/python: Update toolshed deps and cleanup requirements (#45004) Signed-off-by: Ryan Northey --- docs/tools/protodoc/protodoc.py | 2 +- mobile/ci/start_ios_mock_server.py | 2 +- tools/base/requirements.in | 43 +- tools/base/requirements.txt | 703 ++++++++++++++--------------- tools/dependency/cve_fetch.py | 8 +- 5 files changed, 368 insertions(+), 390 deletions(-) diff --git a/docs/tools/protodoc/protodoc.py b/docs/tools/protodoc/protodoc.py index 8d9f0cd46ad5b..3a405ad0438e7 100755 --- a/docs/tools/protodoc/protodoc.py +++ b/docs/tools/protodoc/protodoc.py @@ -11,7 +11,7 @@ from functools import cached_property, lru_cache from typing import Dict, Iterable, Iterator, Set, Tuple -from google.protobuf.descriptor_pb2 import FieldDescriptorProto as field_proto +from google.protobuf.descriptor_pb2 import FieldDescriptorProto as field_proto # noqa: N813 import yaml from udpa.annotations import security_pb2 diff --git a/mobile/ci/start_ios_mock_server.py b/mobile/ci/start_ios_mock_server.py index 7e837ecdb688d..e906ffb9ba8b6 100755 --- a/mobile/ci/start_ios_mock_server.py +++ b/mobile/ci/start_ios_mock_server.py @@ -12,7 +12,7 @@ class MockHandler(http.server.SimpleHTTPRequestHandler): - def do_GET(self): + def do_GET(self): # noqa: N802 if self.path == '/ping': self.send_response(200) self.send_header('Content-Type', 'application/json') diff --git a/tools/base/requirements.in b/tools/base/requirements.in index c65a504ae1242..372a17636b384 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -1,44 +1,33 @@ -abstracts>=0.0.12 -aio.api.bazel -aio.api.github>=0.2.5 -aio.core>=0.10.2 -aiodocker>=0.22.2 +aio.api.bazel>=0.1.0 +aio.api.github>=0.3.0 +aio.core>=0.11.0 aiohttp>=3.13.3 aioquic>=0.9.21 -cffi>=1.15.0 -colorama -coloredlogs -cryptography -dependatool>=0.2.2 -envoy.base.utils>=0.5.10 -envoy.ci.report>=0.0.3 -envoy.code.check>=0.5.12 -envoy.dependency.check>=0.1.10 -envoy.distribution.distrotest>=0.0.11 +dependatool>=0.3.0 +envoy.base.utils>=0.6.0 +envoy.ci.report>=0.1.0 +envoy.code.check>=0.6.0 +envoy.dependency.check>=0.2.0 +# 0.0.13 pins envoy-base-utils==0.5.11 (old-style strict pin) conflicting with >=0.6.0 era +envoy.distribution.distrotest>=0.0.12 +# 0.1.0 has a contradictory envoy-github-release==0.1.0 / envoy.github.release==0.1.1.dev dep envoy.distribution.release>=0.0.9 +# 0.0.10 pins envoy-base-utils==0.5.11 (old-style strict pin) conflicting with >=0.6.0 era envoy.distribution.repo>=0.0.8 -envoy.distribution.verify>=0.0.11 -envoy.gpg.identity>=0.1.1 -envoy.gpg.sign>=0.2.0 +envoy.distribution.verify>=0.1.0 +envoy.gpg.identity>=0.1.2 +envoy.gpg.sign>=0.3.0 flake8>=6 frozendict>=2.3.7 gitpython icalendar>=6.1.1 -jinja2 - -multidict>=6.0.2 orjson>=3.10.15 pep8-naming -ply # Upgrading beyond 4.21.x doesnt currently work. 4.23+ might work when the following is resolved # `MessageFactory class is deprecated. Please use GetMessageClass() instead of MessageFactory.GetPrototype` protobuf<7.35.0 +ply pygithub -pyopenssl>=26.1.0 -pyreadline pyyaml setuptools slack_sdk -thrift -verboselogs -yarl>=1.17.2 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 400fa0d6f8f4e..b444096d54122 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -4,14 +4,14 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -abstracts==0.0.12 \ - --hash=sha256:acc01ff56c8a05fb88150dff62e295f9071fc33388c42f1dfc2787a8d1c755ff +abstracts==0.2.0 \ + --hash=sha256:1dd6df509a67cae1722c0dd9f58cdd6dee6e42f45bf0d7197e159b2e4c5e98e9 \ + --hash=sha256:995f326ab4408a6652a346439cb33169f48e66f25515c6baf9fc47f1a3e4652a # via - # -r requirements.in # aio-api-bazel # aio-api-github - # aio-api-nist # aio-core + # aio-run-checker # aio-run-runner # dependatool # envoy-base-utils @@ -22,29 +22,25 @@ abstracts==0.0.12 \ # envoy-distribution-repo # envoy-github-abstract # envoy-github-release -aio-api-bazel==0.0.4 \ - --hash=sha256:33ba61ac41c6ee8a13ed3e831ab808590fe074cc9703de6d2401d2f200f3c10a - # via -r requirements.in -aio-api-github==0.2.10 \ - --hash=sha256:2563ad2cd219352a7933b78190c17ab460b503b437bec348a738c0f804adf45f \ - --hash=sha256:75e17073e9e03386d719d1af355f599c16d68345992eaa97aebfa6159b07ad02 +aio-api-bazel==0.1.0 \ + --hash=sha256:4fafbae68311190b78fb0a7a921beb37f2a6f80b299678204c8041c4921c6553 \ + --hash=sha256:995fda8552e66f3124d4b11c04c07dd068f64b248a5aa8afd1c1c406c008c836 + # via -r tools/base/requirements.in +aio-api-github==0.3.0 \ + --hash=sha256:502dde7cbcdd20ce24dfad44a3b29430387a29815aaf071db64ab8c338e755a9 \ + --hash=sha256:5844bf9015fcfe23c6d1000cad70e60a8e874440cdd107974d13441395e47bab # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-base-utils # envoy-ci-report # envoy-dependency-check -aio-api-nist==0.0.4 \ - --hash=sha256:1f2909d60ed4fdb3a3ffc37ad6012666f34078b71648394be91f5e67bbf8b6ca \ - --hash=sha256:c948ee597b9e7cda7982e17bc4aca509b8aa68510899b42e2d382c10fb0d6f89 - # via envoy-dependency-check -aio-core==0.10.5 \ - --hash=sha256:ba512132df47514a15930b89835ba1ef13522b7e31ec83a3b0622b7431f3f42e \ - --hash=sha256:d8486a0115ade8e0362783b8e7cd988368766d0d911c62cb73b716bfe1f50a92 +aio-core==0.11.0 \ + --hash=sha256:2b7e49ca221052f84eddf32424c76093a32569fc0802cc41da7e683457241fd0 \ + --hash=sha256:88c0646f6360472963d80143cfb23026fb910cfa5bcd724593ffb88b9be454ad # via - # -r requirements.in + # -r tools/base/requirements.in # aio-api-bazel # aio-api-github - # aio-api-nist # aio-run-runner # dependatool # envoy-base-utils @@ -56,18 +52,19 @@ aio-core==0.10.5 \ # envoy-github-abstract # envoy-github-release # envoy-gpg-identity -aio-run-checker==0.5.8 \ - --hash=sha256:9bf5e20773f83113b2eee39277f0ec3e014aeecfd33deb44103b0ed2482d3ff5 \ - --hash=sha256:b5fcb91651da986b13eefd340a45b402100a2967e1eb1a1b941ccfbc36fe0eda + # envoy-gpg-sign +aio-run-checker==0.6.0 \ + --hash=sha256:6e3be38072506c9b7d30ce79d762e5accf6ff6f3dcc0084370cb490945b7826f \ + --hash=sha256:ff63df32360b17c6c6e3edd250d33f4a2cb328b3fb8967d7a40bbfc6497da162 # via # dependatool # envoy-code-check # envoy-dependency-check # envoy-distribution-distrotest # envoy-distribution-verify -aio-run-runner==0.3.4 \ - --hash=sha256:3f7bdb91c9a7a60d547d18c620ce2444b4e81d6e6a64a2e1ef465e54db8708bf \ - --hash=sha256:ffa5693d6452b9fc07674cbea665825959d3ea5adca4e492998d242dadf3a773 +aio-run-runner==0.4.0 \ + --hash=sha256:6ca926a9a5eb435707a88d03031c27c71fd1402434cfafbbf1bed170de6bd760 \ + --hash=sha256:d89efce2f80627cf00f99e1dcc878ace9a272f56abfa579a32c57515a36c8803 # via # aio-api-bazel # aio-run-checker @@ -78,11 +75,10 @@ aio-run-runner==0.3.4 \ # envoy-github-abstract # envoy-github-release # envoy-gpg-sign -aiodocker==0.25.0 \ - --hash=sha256:0e11f39360cc69dd0a27621f15ffbf42010fb146628920e9aa8de449a9fcb0c5 \ - --hash=sha256:e5145ba622a8eceee9263c277c6d4e7ff6be4632d11765c554c80756b2d58be8 +aiodocker==0.26.0 \ + --hash=sha256:7e4ee40c6f98e6d1cf95d712f15aef3853087c0475bc7ea5ec499c2d485ba7ec \ + --hash=sha256:f450acf0b402a03c6a6f1517d06bdff739c404e62578b332182ca272fde6e986 # via - # -r requirements.in # envoy-distribution-verify # envoy-docker-utils aiofiles==24.1.0 \ @@ -215,11 +211,11 @@ aiohttp==3.13.5 \ --hash=sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540 \ --hash=sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254 # via - # -r requirements.in + # -r tools/base/requirements.in # aio-api-github - # aio-api-nist # aiodocker # envoy-base-utils + # envoy-ci-report # envoy-dependency-check # envoy-github-abstract # envoy-github-release @@ -236,7 +232,7 @@ aioquic==1.3.0 \ --hash=sha256:9d15a89213d38cbc4679990fa5151af8ea02655a1d6ce5ec972b0a6af74d5f1c \ --hash=sha256:a8881239801279188e33ced6f9849cedf033325a48a6f44d7e55e583abc555a3 \ --hash=sha256:ba30016244e45d9222fdd1fbd4e8b0e5f6811e81a5d0643475ad7024a537274a - # via -r requirements.in + # via -r tools/base/requirements.in aiosignal==1.4.0 \ --hash=sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e \ --hash=sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7 @@ -339,7 +335,6 @@ cffi==2.0.0 \ --hash=sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453 \ --hash=sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf # via - # -r requirements.in # cryptography # pynacl charset-normalizer==3.3.2 \ @@ -434,16 +429,10 @@ charset-normalizer==3.3.2 \ --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 # via requests -colorama==0.4.6 \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 - # via -r requirements.in coloredlogs==15.0.1 \ --hash=sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934 \ --hash=sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0 - # via - # -r requirements.in - # aio-run-runner + # via aio-run-runner cryptography==47.0.0 \ --hash=sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7 \ --hash=sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27 \ @@ -495,20 +484,19 @@ cryptography==47.0.0 \ --hash=sha256:f7db373287273d8af1414cf95dc4118b13ffdc62be521997b0f2b270771fef50 \ --hash=sha256:f9a034b642b960767fb343766ae5ba6ad653f2e890ddd82955aef288ffea8736 # via - # -r requirements.in # aioquic # pyjwt # pyopenssl # service-identity -dependatool==0.2.3 \ - --hash=sha256:04bf88d01302eec697a69e8301d14668a89d676dbd2a3914e91c610a531e9db7 \ - --hash=sha256:113a6641889d3dae7c81cb0a0483c31a2657f179474e11f4731b285963475ade - # via -r requirements.in -envoy-base-utils==0.5.11 \ - --hash=sha256:008ebe43ecbde736e8033a34242659ff784caf50cc3301b1d3214899e31fa83d \ - --hash=sha256:be942364a42d92439281700486cf5040bdbcf3786a4e1dbcc9ea0af033a7e47e +dependatool==0.3.0 \ + --hash=sha256:12baf8c611f20d2165a0966d3340fda92e0683bca72e47d2883e13e3afc169bc \ + --hash=sha256:2add2aac95e711979bc5bfef8b3d60b0069c410aea8aefd87255e05f4777cc61 + # via -r tools/base/requirements.in +envoy-base-utils==0.6.0 \ + --hash=sha256:1354ad0198b0282fa1e2072324aa9e479eb45d368cc9d84e5f196a277ee631d0 \ + --hash=sha256:e56c9810cbc8b0db988ba4223f6c90281e9f1a16aa5a7bdb781d1062d8b3f8a4 # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-code-check # envoy-dependency-check # envoy-distribution-distrotest @@ -517,39 +505,40 @@ envoy-base-utils==0.5.11 \ # envoy-distribution-verify # envoy-github-release # envoy-gpg-sign -envoy-ci-report==0.0.3 \ - --hash=sha256:1c4bf3cb9f92117eb482186ea4fa58ff15e26641cdaeeae45bb826ae8454859e \ - --hash=sha256:cc59d0980330a4b73cfcaba2a1b5050348c1d6530baffeb354dccea3e46672d2 - # via -r requirements.in -envoy-code-check==0.5.14 \ - --hash=sha256:56ed152ba633b8d6846509424a4dc94996ed0eb3d656495ec2b32a94144ed904 \ - --hash=sha256:83cce34e3c2ee3d1b9a375922ee0d08fedbd8d3621f72d3ee55dd0a7d26a0e0e - # via -r requirements.in -envoy-dependency-check==0.1.13 \ - --hash=sha256:4337b9c4129ae723dc92f70733b167a8dde187368d873687c6c54732d6fb5e48 \ - --hash=sha256:795e885eccd072d7878dc8ce11fe9f84761f0e449603e583fdab5e9e17111af2 - # via -r requirements.in +envoy-ci-report==0.1.0 \ + --hash=sha256:126f4e39d4b8488fef9c923b18face50f9a31450c8fa4f750804c973e9eee889 \ + --hash=sha256:40a8e1c241bf6d6fb8a97717a19ae36dda12987db97b028d1e55a310f7842358 + # via -r tools/base/requirements.in +envoy-code-check==0.6.0 \ + --hash=sha256:8fddd82e6f66811c1e49e7c46ae27d87d45aa8688a1619272d8b98bdca187956 \ + --hash=sha256:d4a91b6425bc60bebca01e6a2fac32f41323d07f1817092ab3b6f5d4f5d18639 + # via -r tools/base/requirements.in +envoy-dependency-check==0.2.0 \ + --hash=sha256:28b5a22b2da0de21de8c0d15e64073c37e9d362a7848b79d49fbb66c2a9d9350 \ + --hash=sha256:2ce4790e18a88d53b18af7c3b9aea169c73873f13a24d2fb3d25ab1807c17609 + # via -r tools/base/requirements.in envoy-distribution-distrotest==0.0.12 \ --hash=sha256:04f2f5302c039aca82eb4e7d1e8a6fee5fc274a1cdae27130d15754eb3abb4b4 \ --hash=sha256:2762ff531715c1e8d057f10f0fc6c79807f0f67930fc5032fa5a2f239498a7d7 - # via - # -r requirements.in - # envoy-distribution-verify + # via -r tools/base/requirements.in envoy-distribution-release==0.0.9 \ --hash=sha256:592bdc8bc6847daa7e677011d72163b507e3fee821f5ea13a944e27c2fda334f \ --hash=sha256:974308468be49d034e5b174745bd6a5671364d090a9a810f0f6f36e81afbcb5d - # via -r requirements.in + # via -r tools/base/requirements.in envoy-distribution-repo==0.0.8 \ --hash=sha256:84151ae1c77e63a6967404b5e4fd1130138010b540d3081a0c016c28a657a170 \ --hash=sha256:c264232b666964696dbbc0ced1a82a4aefcf8f0af89ffd88c05ca8428f2557b5 - # via -r requirements.in -envoy-distribution-verify==0.0.11 \ - --hash=sha256:7a560cd283321ec00e206c3d6938751836e16ba686fe9585af2383fb11499b38 \ - --hash=sha256:f62a64d158aa656b25629714bef2a1d20d0cbfcab040c6351fd6e960567885e9 - # via -r requirements.in -envoy-docker-utils==0.0.2 \ - --hash=sha256:a12cb57f0b6e204d646cbf94f927b3a8f5a27ed15f60d0576176584ec16a4b76 - # via envoy-distribution-distrotest + # via -r tools/base/requirements.in +envoy-distribution-verify==0.1.0 \ + --hash=sha256:927dd59a9e146d86ebcb581c588b76103b02f116f0062d7a7eba374178d0561b \ + --hash=sha256:af94e7611a6320e8a672d459f747e883a5de2ce245f811cd9bd014800208892a + # via -r tools/base/requirements.in +envoy-docker-utils==0.1.0 \ + --hash=sha256:5ab22736f371c762bc7462e0fcd12fcf2e6cacfc070fd9b76a12bd1f3e3b5314 \ + --hash=sha256:c4d9b366059afc146301bed0e463ba5ce40def4201a2872cd4cca309e1276fe0 + # via + # envoy-distribution-distrotest + # envoy-distribution-verify envoy-github-abstract==0.0.22 \ --hash=sha256:2dd65e2f247a4947d0198b295c82716c13162e30c433b7625c27d59eee7bcf78 \ --hash=sha256:86de8bbe2ecf9db896ecc4ff30ab48fc44a516d868ab1748cd4ae538facacb10 @@ -560,21 +549,19 @@ envoy-github-release==0.0.15 \ --hash=sha256:955671aa0c664ade3d8dbcd60bc9c72284139b81aeaa3fe72157c4e8383fd35b \ --hash=sha256:d571422a79e535ec36130dae5a3c56b429040fb20ca60cd3b8cf8588c52b546c # via envoy-distribution-release -envoy-gpg-identity==0.1.1 \ - --hash=sha256:03f615278b2ca0de652be9d9e3a45faffae74f85f483347c1e0d690edd4019f3 \ - --hash=sha256:c41505491f906bd5ab22504b0ae2f9e76430ae492c9f59278a306225ed19c785 - # via - # -r requirements.in - # envoy-gpg-sign -envoy-gpg-sign==0.2.0 \ - --hash=sha256:53ef217a05555d725d467ceb70fbf7bc623eeb973a41996e8bbe1f295d8c9aab \ - --hash=sha256:8bca326766a2b82864ec6274c51d99c9924f2ec773316c2f13034925ddf50772 - # via -r requirements.in +envoy-gpg-identity==0.1.2 \ + --hash=sha256:3751c09f5deafdbd96e8ccbec276d13ea431381cd20dea0efa24932ba9e15b8f \ + --hash=sha256:fcb916aa1ca0e2dd6d8d6d09f6b54fbb2a276e6011f985f1bd397d60cdbf130e + # via -r tools/base/requirements.in +envoy-gpg-sign==0.3.0 \ + --hash=sha256:149c00b471c13884df43b1d3eb30b4092ead44a8b4d6eb07e6d12c65d5f18a2e \ + --hash=sha256:22745ac13a087ed4425f2f1c897ca59b67a7b3ba67a3af405273713fabbb24e4 + # via -r tools/base/requirements.in flake8==7.3.0 \ --hash=sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e \ --hash=sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872 # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-code-check # pep8-naming frozendict==2.4.7 \ @@ -670,7 +657,7 @@ frozendict==2.4.7 \ --hash=sha256:ffd1a9f9babec9119712e76a39397d8aa0d72ef8c4ccad917c6175d7e7f81b74 \ --hash=sha256:fff8584e3bbdc5c1713cd016fbf4b88babfffd4e5e89b39020f2a208dd24c900 # via - # -r requirements.in + # -r tools/base/requirements.in # aio-run-runner # envoy-base-utils frozenlist==1.4.1 \ @@ -754,11 +741,12 @@ frozenlist==1.4.1 \ # via # aiohttp # aiosignal -gidgethub==5.3.0 \ - --hash=sha256:4dd92f2252d12756b13f9dd15cde322bfb0d625b6fb5d680da1567ec74b462c0 \ - --hash=sha256:9ece7d37fbceb819b80560e7ed58f936e48a65d37ec5f56db79145156b426a25 +gidgethub==5.4.0 \ + --hash=sha256:522bd3a846a315c3163925a840930c4c9a0d685d5853afe852fd589cb472daf5 \ + --hash=sha256:7470d7723d7c1743471a2d62e79c8752fba12b1c0972e4bad57252338a501dbd # via # aio-api-github + # envoy-ci-report # envoy-dependency-check # envoy-distribution-release # envoy-github-abstract @@ -770,7 +758,7 @@ gitdb==4.0.11 \ gitpython==3.1.50 \ --hash=sha256:80da2d12504d52e1f998772dc5baf6e553f8d2fcfe1fcc226c9d9a2ee3372dcc \ --hash=sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9 - # via -r requirements.in + # via -r tools/base/requirements.in humanfriendly==10.0 \ --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 \ --hash=sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc @@ -778,7 +766,7 @@ humanfriendly==10.0 \ icalendar==7.0.3 \ --hash=sha256:8c9fea6d3a89671bba8b6938d8565b4d0ec465c6a2796ef0f92790dcb9e627cd \ --hash=sha256:95027ece087ab87184d765f03761f25875821f74cdd18d3b57e9c868216d8fde - # via -r requirements.in + # via -r tools/base/requirements.in idna==3.10 \ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 @@ -789,7 +777,6 @@ jinja2==3.1.6 \ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 # via - # -r requirements.in # envoy-base-utils # envoy-dependency-check markupsafe==2.1.5 \ @@ -1006,106 +993,108 @@ multidict==6.7.1 \ --hash=sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba \ --hash=sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19 # via - # -r requirements.in + # aio-api-github # aiohttp + # envoy-dependency-check # yarl -orjson==3.11.8 \ - --hash=sha256:0022bb50f90da04b009ce32c512dc1885910daa7cb10b7b0cba4505b16db82a8 \ - --hash=sha256:003646067cc48b7fcab2ae0c562491c9b5d2cbd43f1e5f16d98fd118c5522d34 \ - --hash=sha256:01928d0476b216ad2201823b0a74000440360cef4fed1912d297b8d84718f277 \ - --hash=sha256:01c4e5a6695dc09098f2e6468a251bc4671c50922d4d745aff1a0a33a0cf5b8d \ - --hash=sha256:093d489fa039ddade2db541097dbb484999fcc65fc2b0ff9819141e2ab364f25 \ - --hash=sha256:0b57f67710a8cd459e4e54eb96d5f77f3624eba0c661ba19a525807e42eccade \ - --hash=sha256:0e32f7154299f42ae66f13488963269e5eccb8d588a65bc839ed986919fc9fac \ - --hash=sha256:14439063aebcb92401c11afc68ee4e407258d2752e62d748b6942dad20d2a70d \ - --hash=sha256:14778ffd0f6896aa613951a7fbf4690229aa7a543cb2bfbe9f358e08aafa9546 \ - --hash=sha256:14f7b8fcb35ef403b42fa5ecfa4ed032332a91f3dc7368fbce4184d59e1eae0d \ - --hash=sha256:1ab359aff0436d80bfe8a23b46b5fea69f1e18aaf1760a709b4787f1318b317f \ - --hash=sha256:1cd0b77e77c95758f8e1100139844e99f3ccc87e71e6fc8e1c027e55807c549f \ - --hash=sha256:25e0c672a2e32348d2eb33057b41e754091f2835f87222e4675b796b92264f06 \ - --hash=sha256:29c009e7a2ca9ad0ed1376ce20dd692146a5d9fe4310848904b6b4fee5c5c137 \ - --hash=sha256:3222adff1e1ff0dce93c16146b93063a7793de6c43d52309ae321234cdaf0f4d \ - --hash=sha256:3223665349bbfb68da234acd9846955b1a0808cbe5520ff634bf253a4407009b \ - --hash=sha256:3cf17c141617b88ced4536b2135c552490f07799f6ad565948ea07bef0dcb9a6 \ - --hash=sha256:3f23426851d98478c8970da5991f84784a76682213cd50eb73a1da56b95239dc \ - --hash=sha256:3f262401086a3960586af06c054609365e98407151f5ea24a62893a40d80dbbb \ - --hash=sha256:436c4922968a619fb7fef1ccd4b8b3a76c13b67d607073914d675026e911a65c \ - --hash=sha256:469ac2125611b7c5741a0b3798cd9e5786cbad6345f9f400c77212be89563bec \ - --hash=sha256:4861bde57f4d253ab041e374f44023460e60e71efaa121f3c5f0ed457c3a701e \ - --hash=sha256:48854463b0572cc87dac7d981aa72ed8bf6deedc0511853dc76b8bbd5482d36d \ - --hash=sha256:53a0f57e59a530d18a142f4d4ba6dfc708dc5fdedce45e98ff06b44930a2a48f \ - --hash=sha256:54153d21520a71a4c82a0dbb4523e468941d549d221dc173de0f019678cf3813 \ - --hash=sha256:55120759e61309af7fcf9e961c6f6af3dde5921cdb3ee863ef63fd9db126cae6 \ - --hash=sha256:5774c1fdcc98b2259800b683b19599c133baeb11d60033e2095fd9d4667b82db \ - --hash=sha256:58a4a208a6fbfdb7a7327b8f201c6014f189f721fd55d047cafc4157af1bc62a \ - --hash=sha256:58fb9b17b4472c7b1dcf1a54583629e62e23779b2331052f09a9249edf81675b \ - --hash=sha256:5d8b5231de76c528a46b57010bbd83fb51e056aa0220a372fd5065e978406f1c \ - --hash=sha256:5f8952d6d2505c003e8f0224ff7858d341fa4e33fef82b91c4ff0ef070f2393c \ - --hash=sha256:61c9d357a59465736022d5d9ba06687afb7611dfb581a9d2129b77a6fcf78e59 \ - --hash=sha256:6a3d159d5ffa0e3961f353c4b036540996bf8b9697ccc38261c0eac1fd3347a6 \ - --hash=sha256:6a4a639049c44d36a6d1ae0f4a94b271605c745aee5647fa8ffaabcdc01b69a6 \ - --hash=sha256:6ccdea2c213cf9f3d9490cbd5d427693c870753df41e6cb375bd79bcbafc8817 \ - --hash=sha256:6dbe9a97bdb4d8d9d5367b52a7c32549bba70b2739c58ef74a6964a6d05ae054 \ - --hash=sha256:6eda5b8b6be91d3f26efb7dc6e5e68ee805bc5617f65a328587b35255f138bf4 \ - --hash=sha256:705b895b781b3e395c067129d8551655642dfe9437273211d5404e87ac752b53 \ - --hash=sha256:708c95f925a43ab9f34625e45dcdadf09ec8a6e7b664a938f2f8d5650f6c090b \ - --hash=sha256:735e2262363dcbe05c35e3a8869898022af78f89dde9e256924dc02e99fe69ca \ - --hash=sha256:76070a76e9c5ae661e2d9848f216980d8d533e0f8143e6ed462807b242e3c5e8 \ - --hash=sha256:7679bc2f01bb0d219758f1a5f87bb7c8a81c0a186824a393b366876b4948e14f \ - --hash=sha256:88006eda83858a9fdf73985ce3804e885c2befb2f506c9a3723cdeb5a2880e3e \ - --hash=sha256:883206d55b1bd5f5679ad5e6ddd3d1a5e3cac5190482927fdb8c78fb699193b5 \ - --hash=sha256:8ac7381c83dd3d4a6347e6635950aa448f54e7b8406a27c7ecb4a37e9f1ae08b \ - --hash=sha256:8e8c6218b614badf8e229b697865df4301afa74b791b6c9ade01d19a9953a942 \ - --hash=sha256:9185589c1f2a944c17e26c9925dcdbc2df061cc4a145395c57f0c51f9b5dbfcd \ - --hash=sha256:93de06bc920854552493c81f1f729fab7213b7db4b8195355db5fda02c7d1363 \ - --hash=sha256:96163d9cdc5a202703e9ad1b9ae757d5f0ca62f4fa0cc93d1f27b0e180cc404e \ - --hash=sha256:97c8f5d3b62380b70c36ffacb2a356b7c6becec86099b177f73851ba095ef623 \ - --hash=sha256:97d823831105c01f6c8029faf297633dbeb30271892bd430e9c24ceae3734744 \ - --hash=sha256:98bdc6cb889d19bed01de46e67574a2eab61f5cc6b768ed50e8ac68e9d6ffab6 \ - --hash=sha256:9b48e274f8824567d74e2158199e269597edf00823a1b12b63d48462bbf5123e \ - --hash=sha256:a5c370674ebabe16c6ccac33ff80c62bf8a6e59439f5e9d40c1f5ab8fd2215b7 \ - --hash=sha256:b43dc2a391981d36c42fa57747a49dae793ef1d2e43898b197925b5534abd10a \ - --hash=sha256:c154a35dd1330707450bb4d4e7dd1f17fa6f42267a40c1e8a1daa5e13719b4b8 \ - --hash=sha256:c2bdf7b2facc80b5e34f48a2d557727d5c5c57a8a450de122ae81fa26a81c1bc \ - --hash=sha256:c492a0e011c0f9066e9ceaa896fbc5b068c54d365fea5f3444b697ee01bc8625 \ - --hash=sha256:c60c0423f15abb6cf78f56dff00168a1b582f7a1c23f114036e2bfc697814d5f \ - --hash=sha256:c98121237fea2f679480765abd566f7713185897f35c9e6c2add7e3a9900eb61 \ - --hash=sha256:ccd7ba1b0605813a0715171d39ec4c314cb97a9c85893c2c5c0c3a3729df38bf \ - --hash=sha256:cdbc8c9c02463fef4d3c53a9ba3336d05496ec8e1f1c53326a1e4acc11f5c600 \ - --hash=sha256:e0950ed1bcb9893f4293fd5c5a7ee10934fbf82c4101c70be360db23ce24b7d2 \ - --hash=sha256:e6693ff90018600c72fd18d3d22fa438be26076cd3c823da5f63f7bab28c11cb \ - --hash=sha256:ea56a955056a6d6c550cf18b3348656a9d9a4f02e2d0c02cabf3c73f1055d506 \ - --hash=sha256:ebaed4cef74a045b83e23537b52ef19a367c7e3f536751e355a2a394f8648559 \ - --hash=sha256:ec795530a73c269a55130498842aaa762e4a939f6ce481a7e986eeaa790e9da4 \ - --hash=sha256:ed193ce51d77a3830cad399a529cd4ef029968761f43ddc549e1bc62b40d88f8 \ - --hash=sha256:ee8db7bfb6fe03581bbab54d7c4124a6dd6a7f4273a38f7267197890f094675f \ - --hash=sha256:f30491bc4f862aa15744b9738517454f1e46e56c972a2be87d70d727d5b2a8f8 \ - --hash=sha256:f89b6d0b3a8d81e1929d3ab3d92bbc225688bd80a770c49432543928fe09ac55 \ - --hash=sha256:fa72e71977bff96567b0f500fc5bfd2fdf915f34052c782a4c6ebbdaa97aa858 \ - --hash=sha256:fe0b8c83e0f36247fc9431ce5425a5d95f9b3a689133d494831bdbd6f0bceb13 \ - --hash=sha256:ff51f9d657d1afb6f410cb435792ce4e1fe427aab23d2fcd727a2876e21d4cb6 +orjson==3.11.9 \ + --hash=sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4 \ + --hash=sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62 \ + --hash=sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979 \ + --hash=sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0 \ + --hash=sha256:0b34789fa0da61cf7bef0546b09c738fb195331e017e477096d129e9105ab03d \ + --hash=sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a \ + --hash=sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd \ + --hash=sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce \ + --hash=sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1 \ + --hash=sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180 \ + --hash=sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980 \ + --hash=sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697 \ + --hash=sha256:231742b4a11dad8d5380a435962c57e91b7c37b79be858f4ef1c0df1a259897e \ + --hash=sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1 \ + --hash=sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09 \ + --hash=sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd \ + --hash=sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470 \ + --hash=sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b \ + --hash=sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877 \ + --hash=sha256:34fd2317602587321faab75ab76c623a0117e80841a6413654f04e47f339a8fb \ + --hash=sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd \ + --hash=sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe \ + --hash=sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97 \ + --hash=sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c \ + --hash=sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5 \ + --hash=sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021 \ + --hash=sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362 \ + --hash=sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206 \ + --hash=sha256:4da3c38a2083ca4aaf9c2a36776cce3e9328e6647b10d118948f3cfb4913ffe4 \ + --hash=sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218 \ + --hash=sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9 \ + --hash=sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f \ + --hash=sha256:53b50b0e14084b8f7e29c5ce84c5af0f1160169b30d8a6914231d97d2fe297d4 \ + --hash=sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02 \ + --hash=sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be \ + --hash=sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972 \ + --hash=sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586 \ + --hash=sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2 \ + --hash=sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124 \ + --hash=sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa \ + --hash=sha256:71f3db16e69b667b132e0f305a833d5497da302d801508cbb051ed9a9819da47 \ + --hash=sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c \ + --hash=sha256:8697ab6a080a5c46edaad50e2bc5bd8c7ca5c66442d24104fa44ec74910a8244 \ + --hash=sha256:87e4d4ab280b0c87424d47695bec2182caf8cfc17879ea78dab76680194abc13 \ + --hash=sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10 \ + --hash=sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677 \ + --hash=sha256:97d0d932803c1b164fde11cb542a9efcb1e0f63b184537cca65887147906ff48 \ + --hash=sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4 \ + --hash=sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624 \ + --hash=sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49 \ + --hash=sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0 \ + --hash=sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b \ + --hash=sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c \ + --hash=sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2 \ + --hash=sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db \ + --hash=sha256:ace6c58523302d3b97b6ac5c38a5298a54b473762b6be82726b4265c41029f92 \ + --hash=sha256:b3afcf569c15577a9fe64627292daa3e6b3a70f4fb77a5df246a87ec21681b94 \ + --hash=sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e \ + --hash=sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61 \ + --hash=sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882 \ + --hash=sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff \ + --hash=sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254 \ + --hash=sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f \ + --hash=sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32 \ + --hash=sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff \ + --hash=sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673 \ + --hash=sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291 \ + --hash=sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0 \ + --hash=sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c \ + --hash=sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9 \ + --hash=sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f \ + --hash=sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e \ + --hash=sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7 \ + --hash=sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81 # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-base-utils -packaging==24.1 \ - --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ - --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 +packaging==26.2 \ + --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \ + --hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661 # via # aio-api-github - # aio-api-nist # envoy-base-utils # envoy-code-check # envoy-dependency-check # envoy-github-abstract # envoy-github-release -pathspec==0.12.1 \ - --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ - --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 +pathspec==1.1.1 \ + --hash=sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a \ + --hash=sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189 # via yamllint pep8-naming==0.15.1 \ --hash=sha256:eb63925e7fd9e028c7f7ee7b1e413ec03d1ee5de0e627012102ee0222c273c86 \ --hash=sha256:f6f4a499aba2deeda93c1f26ccc02f3da32b035c8b2db9696b730ef2c9639d29 - # via -r requirements.in + # via + # -r tools/base/requirements.in + # envoy-code-check platformdirs==4.3.6 \ --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb @@ -1113,7 +1102,7 @@ platformdirs==4.3.6 \ ply==3.11 \ --hash=sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3 \ --hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce - # via -r requirements.in + # via -r tools/base/requirements.in propcache==0.3.1 \ --hash=sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e \ --hash=sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b \ @@ -1226,7 +1215,7 @@ protobuf==7.34.1 \ --hash=sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7 \ --hash=sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c # via - # -r requirements.in + # -r tools/base/requirements.in # envoy-base-utils pyasn1==0.6.3 \ --hash=sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf \ @@ -1253,8 +1242,8 @@ pyflakes==3.4.0 \ pygithub==2.9.1 \ --hash=sha256:2ec78fca30092d51a42d76f4ddb02131b6f0c666a35dfdf364cf302cdda115b9 \ --hash=sha256:59771d7ff63d54d427be2e7d0dad2208dfffc2b0a045fec959263787739b611c - # via -r requirements.in -pyjwt[crypto]==2.12.1 \ + # via -r tools/base/requirements.in +pyjwt==2.12.1 \ --hash=sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c \ --hash=sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b # via @@ -1290,28 +1279,18 @@ pynacl==1.6.2 \ pyopenssl==26.1.0 \ --hash=sha256:115563879b2c8ccb207975705d3e491434d8c9d7c79667c902ecbf5f3bbd2ece \ --hash=sha256:737f0a2275c5bc54f3b02137687e1a765931fb3949b9a92a825e4d33b9eec08b - # via - # -r requirements.in - # aioquic -pyreadline==2.1 \ - --hash=sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1 - # via -r requirements.in + # via aioquic python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via icalendar -python-gnupg==0.5.2 \ - --hash=sha256:01d8013931c9fa3f45824bbea7054c03d6e11f258a72e7e086e168dbcb91854c \ - --hash=sha256:72ce142af6da7f07e433fef148b445fb3e07854acd2f88739008838745c0e9f5 +python-gnupg==0.5.6 \ + --hash=sha256:5743e96212d38923fc19083812dc127907e44dbd3bcf0db4d657e291d3c21eac \ + --hash=sha256:b5050a55663d8ab9fcc8d97556d229af337a87a3ebebd7054cbd8b7e2043394a # via # envoy-base-utils # envoy-gpg-identity -pytz==2024.2 \ - --hash=sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a \ - --hash=sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725 - # via - # aio-core - # envoy-base-utils + # envoy-gpg-sign pyyaml==6.0.3 \ --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ @@ -1380,7 +1359,7 @@ pyyaml==6.0.3 \ --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 # via - # -r requirements.in + # -r tools/base/requirements.in # aio-core # envoy-base-utils # yamllint @@ -1392,6 +1371,10 @@ service-identity==24.1.0 \ --hash=sha256:6829c9d62fb832c2e1c435629b0a8c476e1929881f28bee4d20bc24161009221 \ --hash=sha256:a28caf8130c8a5c1c7a6f5293faaf239bbfb7751e4862436920ee6f2616f568a # via aioquic +setuptools==82.0.1 \ + --hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \ + --hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb + # via -r tools/base/requirements.in six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 @@ -1399,17 +1382,14 @@ six==1.16.0 \ slack-sdk==3.41.0 \ --hash=sha256:bb18dcdfff1413ec448e759cf807ec3324090993d8ab9111c74081623b692a89 \ --hash=sha256:eb61eb12a65bebeca9cb5d36b3f799e836ed2be21b456d15df2627cfe34076ca - # via -r requirements.in + # via -r tools/base/requirements.in smmap==5.0.1 \ --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ --hash=sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da # via gitdb -thrift==0.22.0 \ - --hash=sha256:42e8276afbd5f54fe1d364858b6877bc5e5a4a5ed69f6a005b94ca4918fe1466 - # via -r requirements.in -trycast==1.2.0 \ - --hash=sha256:5cece718b5a34378df8577f3b3aa515479611c9a37b4685d4d5aa4e6c1b4b20f \ - --hash=sha256:827a84f702d8a6e7b43bb7ba5e4cccd81b3fb7e59c9b2417e718407781e317a7 +trycast==1.3.0 \ + --hash=sha256:09ba60415234b033bb80756db154056e95519edc35f4973a267c63e9e0d848c9 \ + --hash=sha256:e02f15c4e375b3b958017ba111a51dc0587756536f0228f4385e9548b69fae84 # via # aio-core # envoy-base-utils @@ -1417,19 +1397,13 @@ types-orjson==3.6.2 \ --hash=sha256:22ee9a79236b6b0bfb35a0684eded62ad930a88a56797fa3c449b026cf7dbfe4 \ --hash=sha256:cf9afcc79a86325c7aff251790338109ed6f6b1bab09d2d4262dd18c85a3c638 # via aio-core -types-protobuf==5.28.0.20240924 \ - --hash=sha256:5cecf612ccdefb7dc95f7a51fb502902f20fc2e6681cd500184aaa1b3931d6a7 \ - --hash=sha256:d181af8a256e5a91ce8d5adb53496e880efd9144c7d54483e3653332b60296f0 +types-protobuf==7.34.1.20260508 \ + --hash=sha256:1c93e8c294281b76a5255fc21c747db0004694463ac6ea9866ee06da969fa555 \ + --hash=sha256:a5d647381f8651bd505304ed1148b8a7b342781796e0f80e0284c774c2262a09 # via envoy-base-utils -types-pytz==2024.2.0.20241003 \ - --hash=sha256:3e22df1336c0c6ad1d29163c8fda82736909eb977281cb823c57f8bae07118b7 \ - --hash=sha256:575dc38f385a922a212bac00a7d6d2e16e141132a3c955078f4a4fd13ed6cb44 - # via - # aio-core - # envoy-base-utils -types-pyyaml==6.0.12.20240917 \ - --hash=sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570 \ - --hash=sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587 +types-pyyaml==6.0.12.20260508 \ + --hash=sha256:5ae42149c3ebf7aaaf6c65ee49af590c80f0ba52e9e3f75a75c5564b33556fa6 \ + --hash=sha256:edc094ed3a918b0c6232f71a5b67fdf38e76e17517b7d87bfbb9fc27d442fb51 # via # aio-core # envoy-code-check @@ -1455,51 +1429,69 @@ urllib3==2.7.0 \ # via # pygithub # requests -uvloop==0.20.0 \ - --hash=sha256:265a99a2ff41a0fd56c19c3838b29bf54d1d177964c300dad388b27e84fd7847 \ - --hash=sha256:2beee18efd33fa6fdb0976e18475a4042cd31c7433c866e8a09ab604c7c22ff2 \ - --hash=sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b \ - --hash=sha256:36c530d8fa03bfa7085af54a48f2ca16ab74df3ec7108a46ba82fd8b411a2315 \ - --hash=sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5 \ - --hash=sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469 \ - --hash=sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d \ - --hash=sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf \ - --hash=sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9 \ - --hash=sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab \ - --hash=sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e \ - --hash=sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e \ - --hash=sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0 \ - --hash=sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756 \ - --hash=sha256:89e8d33bb88d7263f74dc57d69f0063e06b5a5ce50bb9a6b32f5fcbe655f9e73 \ - --hash=sha256:94707205efbe809dfa3a0d09c08bef1352f5d3d6612a506f10a319933757c006 \ - --hash=sha256:95720bae002ac357202e0d866128eb1ac82545bcf0b549b9abe91b5178d9b541 \ - --hash=sha256:9b04d96188d365151d1af41fa2d23257b674e7ead68cfd61c725a422764062ae \ - --hash=sha256:9d0fba61846f294bce41eb44d60d58136090ea2b5b99efd21cbdf4e21927c56a \ - --hash=sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996 \ - --hash=sha256:a0fac7be202596c7126146660725157d4813aa29a4cc990fe51346f75ff8fde7 \ - --hash=sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00 \ - --hash=sha256:b10c2956efcecb981bf9cfb8184d27d5d64b9033f917115a960b83f11bfa0d6b \ - --hash=sha256:b16696f10e59d7580979b420eedf6650010a4a9c3bd8113f24a103dfdb770b10 \ - --hash=sha256:d8c36fdf3e02cec92aed2d44f63565ad1522a499c654f07935c8f9d04db69e95 \ - --hash=sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9 \ - --hash=sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037 \ - --hash=sha256:e7d61fe8e8d9335fac1bf8d5d82820b4808dd7a43020c149b63a1ada953d48a6 \ - --hash=sha256:e97152983442b499d7a71e44f29baa75b3b02e65d9c44ba53b10338e98dedb66 \ - --hash=sha256:f0e94b221295b5e69de57a1bd4aeb0b3a29f61be6e1b478bb8a69a73377db7ba \ - --hash=sha256:fee6044b64c965c425b65a4e17719953b96e065c5b7e09b599ff332bb2744bdf +uvloop==0.22.1 \ + --hash=sha256:017bd46f9e7b78e81606329d07141d3da446f8798c6baeec124260e22c262772 \ + --hash=sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e \ + --hash=sha256:05e4b5f86e621cf3927631789999e697e58f0d2d32675b67d9ca9eb0bca55743 \ + --hash=sha256:0ae676de143db2b2f60a9696d7eca5bb9d0dd6cc3ac3dad59a8ae7e95f9e1b54 \ + --hash=sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec \ + --hash=sha256:17d4e97258b0172dfa107b89aa1eeba3016f4b1974ce85ca3ef6a66b35cbf659 \ + --hash=sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8 \ + --hash=sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad \ + --hash=sha256:286322a90bea1f9422a470d5d2ad82d38080be0a29c4dd9b3e6384320a4d11e7 \ + --hash=sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35 \ + --hash=sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289 \ + --hash=sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142 \ + --hash=sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77 \ + --hash=sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733 \ + --hash=sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd \ + --hash=sha256:4a968a72422a097b09042d5fa2c5c590251ad484acf910a651b4b620acd7f193 \ + --hash=sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74 \ + --hash=sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0 \ + --hash=sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6 \ + --hash=sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473 \ + --hash=sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21 \ + --hash=sha256:55502bc2c653ed2e9692e8c55cb95b397d33f9f2911e929dc97c4d6b26d04242 \ + --hash=sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705 \ + --hash=sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702 \ + --hash=sha256:57df59d8b48feb0e613d9b1f5e57b7532e97cbaf0d61f7aa9aa32221e84bc4b6 \ + --hash=sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f \ + --hash=sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e \ + --hash=sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d \ + --hash=sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370 \ + --hash=sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4 \ + --hash=sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792 \ + --hash=sha256:80eee091fe128e425177fbd82f8635769e2f32ec9daf6468286ec57ec0313efa \ + --hash=sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079 \ + --hash=sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2 \ + --hash=sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86 \ + --hash=sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6 \ + --hash=sha256:b45649628d816c030dba3c80f8e2689bab1c89518ed10d426036cdc47874dfc4 \ + --hash=sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3 \ + --hash=sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21 \ + --hash=sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c \ + --hash=sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e \ + --hash=sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25 \ + --hash=sha256:c3e5c6727a57cb6558592a95019e504f605d1c54eb86463ee9f7a2dbd411c820 \ + --hash=sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9 \ + --hash=sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88 \ + --hash=sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2 \ + --hash=sha256:ea721dd3203b809039fcc2983f14608dae82b212288b346e0bfe46ec2fab0b7c \ + --hash=sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c \ + --hash=sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42 # via aio-run-runner verboselogs==1.7 \ --hash=sha256:d63f23bf568295b95d3530c6864a0b580cec70e7ff974177dead1e4ffbc6ff49 \ --hash=sha256:e33ddedcdfdafcb3a174701150430b11b46ceb64c2a9a26198c76a156568e427 # via - # -r requirements.in # aio-run-runner # envoy-distribution-repo # envoy-github-abstract # envoy-github-release -yamllint==1.35.1 \ - --hash=sha256:2e16e504bb129ff515b37823b472750b36b6de07963bd74b307341ef5ad8bdc3 \ - --hash=sha256:7a003809f88324fd2c877734f2d575ee7881dd9043360657cc8049c809eba6cd + # envoy-gpg-sign +yamllint==1.38.0 \ + --hash=sha256:09e5f29531daab93366bb061e76019d5e91691ef0a40328f04c927387d1d364d \ + --hash=sha256:fc394a5b3be980a4062607b8fdddc0843f4fa394152b6da21722f5d59013c220 # via envoy-code-check yapf==0.43.0 \ --hash=sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e \ @@ -1635,110 +1627,107 @@ yarl==1.23.0 \ --hash=sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6 \ --hash=sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d # via - # -r requirements.in + # aio-api-github # aiohttp -zstandard==0.23.0 \ - --hash=sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473 \ - --hash=sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916 \ - --hash=sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15 \ - --hash=sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072 \ - --hash=sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4 \ - --hash=sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e \ - --hash=sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26 \ - --hash=sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8 \ - --hash=sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5 \ - --hash=sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd \ - --hash=sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c \ - --hash=sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db \ - --hash=sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5 \ - --hash=sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc \ - --hash=sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152 \ - --hash=sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269 \ - --hash=sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045 \ - --hash=sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e \ - --hash=sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d \ - --hash=sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a \ - --hash=sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb \ - --hash=sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740 \ - --hash=sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105 \ - --hash=sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274 \ - --hash=sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2 \ - --hash=sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58 \ - --hash=sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b \ - --hash=sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4 \ - --hash=sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db \ - --hash=sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e \ - --hash=sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9 \ - --hash=sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0 \ - --hash=sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813 \ - --hash=sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e \ - --hash=sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512 \ - --hash=sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0 \ - --hash=sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b \ - --hash=sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48 \ - --hash=sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a \ - --hash=sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772 \ - --hash=sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed \ - --hash=sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373 \ - --hash=sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea \ - --hash=sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd \ - --hash=sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f \ - --hash=sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc \ - --hash=sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23 \ - --hash=sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2 \ - --hash=sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db \ - --hash=sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70 \ - --hash=sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259 \ - --hash=sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9 \ - --hash=sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700 \ - --hash=sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003 \ - --hash=sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba \ - --hash=sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a \ - --hash=sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c \ - --hash=sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90 \ - --hash=sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690 \ - --hash=sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f \ - --hash=sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840 \ - --hash=sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d \ - --hash=sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9 \ - --hash=sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35 \ - --hash=sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd \ - --hash=sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a \ - --hash=sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea \ - --hash=sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1 \ - --hash=sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573 \ - --hash=sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09 \ - --hash=sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094 \ - --hash=sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78 \ - --hash=sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9 \ - --hash=sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5 \ - --hash=sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9 \ - --hash=sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391 \ - --hash=sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847 \ - --hash=sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2 \ - --hash=sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c \ - --hash=sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2 \ - --hash=sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057 \ - --hash=sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20 \ - --hash=sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d \ - --hash=sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4 \ - --hash=sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54 \ - --hash=sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171 \ - --hash=sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e \ - --hash=sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160 \ - --hash=sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b \ - --hash=sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58 \ - --hash=sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8 \ - --hash=sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33 \ - --hash=sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a \ - --hash=sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880 \ - --hash=sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca \ - --hash=sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b \ - --hash=sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69 + # envoy-dependency-check +zstandard==0.25.0 \ + --hash=sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64 \ + --hash=sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a \ + --hash=sha256:05353cef599a7b0b98baca9b068dd36810c3ef0f42bf282583f438caf6ddcee3 \ + --hash=sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f \ + --hash=sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6 \ + --hash=sha256:07b527a69c1e1c8b5ab1ab14e2afe0675614a09182213f21a0717b62027b5936 \ + --hash=sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431 \ + --hash=sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250 \ + --hash=sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa \ + --hash=sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f \ + --hash=sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851 \ + --hash=sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3 \ + --hash=sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9 \ + --hash=sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6 \ + --hash=sha256:19796b39075201d51d5f5f790bf849221e58b48a39a5fc74837675d8bafc7362 \ + --hash=sha256:1cd5da4d8e8ee0e88be976c294db744773459d51bb32f707a0f166e5ad5c8649 \ + --hash=sha256:1f3689581a72eaba9131b1d9bdbfe520ccd169999219b41000ede2fca5c1bfdb \ + --hash=sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5 \ + --hash=sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439 \ + --hash=sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137 \ + --hash=sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa \ + --hash=sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd \ + --hash=sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701 \ + --hash=sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0 \ + --hash=sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043 \ + --hash=sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1 \ + --hash=sha256:37daddd452c0ffb65da00620afb8e17abd4adaae6ce6310702841760c2c26860 \ + --hash=sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611 \ + --hash=sha256:3b870ce5a02d4b22286cf4944c628e0f0881b11b3f14667c1d62185a99e04f53 \ + --hash=sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b \ + --hash=sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088 \ + --hash=sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e \ + --hash=sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa \ + --hash=sha256:4b14abacf83dfb5c25eb4e4a79520de9e7e205f72c9ee7702f91233ae57d33a2 \ + --hash=sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0 \ + --hash=sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7 \ + --hash=sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf \ + --hash=sha256:51526324f1b23229001eb3735bc8c94f9c578b1bd9e867a0a646a3b17109f388 \ + --hash=sha256:53e08b2445a6bc241261fea89d065536f00a581f02535f8122eba42db9375530 \ + --hash=sha256:53f94448fe5b10ee75d246497168e5825135d54325458c4bfffbaafabcc0a577 \ + --hash=sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902 \ + --hash=sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc \ + --hash=sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98 \ + --hash=sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a \ + --hash=sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097 \ + --hash=sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea \ + --hash=sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09 \ + --hash=sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb \ + --hash=sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7 \ + --hash=sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74 \ + --hash=sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b \ + --hash=sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b \ + --hash=sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b \ + --hash=sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91 \ + --hash=sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150 \ + --hash=sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049 \ + --hash=sha256:89c4b48479a43f820b749df49cd7ba2dbc2b1b78560ecb5ab52985574fd40b27 \ + --hash=sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a \ + --hash=sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00 \ + --hash=sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd \ + --hash=sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072 \ + --hash=sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c \ + --hash=sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c \ + --hash=sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065 \ + --hash=sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512 \ + --hash=sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1 \ + --hash=sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f \ + --hash=sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2 \ + --hash=sha256:a51ff14f8017338e2f2e5dab738ce1ec3b5a851f23b18c1ae1359b1eecbee6df \ + --hash=sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab \ + --hash=sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7 \ + --hash=sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b \ + --hash=sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550 \ + --hash=sha256:b9af1fe743828123e12b41dd8091eca1074d0c1569cc42e6e1eee98027f2bbd0 \ + --hash=sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea \ + --hash=sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277 \ + --hash=sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2 \ + --hash=sha256:c2ba942c94e0691467ab901fc51b6f2085ff48f2eea77b1a48240f011e8247c7 \ + --hash=sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778 \ + --hash=sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859 \ + --hash=sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d \ + --hash=sha256:d8c56bb4e6c795fc77d74d8e8b80846e1fb8292fc0b5060cd8131d522974b751 \ + --hash=sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12 \ + --hash=sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2 \ + --hash=sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d \ + --hash=sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0 \ + --hash=sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3 \ + --hash=sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd \ + --hash=sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e \ + --hash=sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f \ + --hash=sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e \ + --hash=sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94 \ + --hash=sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708 \ + --hash=sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313 \ + --hash=sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4 \ + --hash=sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c \ + --hash=sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344 \ + --hash=sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551 \ + --hash=sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01 # via envoy-base-utils - -# The following packages are considered to be unsafe in a requirements file: -setuptools==82.0.1 \ - --hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \ - --hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb - # via -r requirements.in diff --git a/tools/dependency/cve_fetch.py b/tools/dependency/cve_fetch.py index 6e1fd2a41fcb5..681a4ded402cb 100644 --- a/tools/dependency/cve_fetch.py +++ b/tools/dependency/cve_fetch.py @@ -19,7 +19,7 @@ BACKOFF_BASE = 10 -class NvdDownloaderException(Exception): +class NvdDownloaderError(Exception): pass @@ -156,7 +156,7 @@ async def fetch_window(self, start_date, end_date): current = datetime(current.year, current.month + 1, 1) @runner.cleansup - @runner.catches(NvdDownloaderException) + @runner.catches(NvdDownloaderError) async def run(self): tempdir = pathlib.Path(self.tempdir.name) months = {} @@ -182,8 +182,8 @@ async def run(self): output_file = self.output_path / f"{month}.json" if output_file.exists(): if not self.overwrite: - raise NvdDownloaderException( - f"File {output_file} exists and overwrite was not specfied") + raise NvdDownloaderError( + f"File {output_file} exists and overwrite was not specified") output_file.unlink() with output_file.open("a", encoding="utf-8") as outfile: From 4096403711facfd7cac8e4b1575cb85928373ada Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 19:16:27 +0100 Subject: [PATCH 285/750] build(deps): bump urllib3 from 2.6.3 to 2.7.0 in /mobile/tools/python in the pip group across 1 directory (#45007) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mobile/tools/python/requirements.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/mobile/tools/python/requirements.txt b/mobile/tools/python/requirements.txt index fc491fcfc3266..05ba36d5b6036 100644 --- a/mobile/tools/python/requirements.txt +++ b/mobile/tools/python/requirements.txt @@ -36,7 +36,7 @@ black==26.3.1 \ --hash=sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb \ --hash=sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56 \ --hash=sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568 - # via -r tools/python/requirements.in + # via -r requirements.in certifi==2026.4.22 \ --hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \ --hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580 @@ -215,7 +215,7 @@ gevent==26.4.0 \ --hash=sha256:df208cd53c7382a4cc8470d39a92fc73b3cf2f0f3379d6c88bb556823a26ccb7 \ --hash=sha256:f0a03650ca60c4c5774cbe21333905b95f2f5abd98ea5a3dbf28d93f2a7a5a84 \ --hash=sha256:f7e12fdd28cc9f39a463d8df5172d698c64a8ed385a21d98e7092fd8308a139a - # via -r tools/python/requirements.in + # via -r requirements.in greenlet==3.5.0 \ --hash=sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846 \ --hash=sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4 \ @@ -288,7 +288,7 @@ httpcore==1.0.9 \ httpx==0.28.1 \ --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad - # via -r tools/python/requirements.in + # via -r requirements.in idna==3.13 \ --hash=sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242 \ --hash=sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3 @@ -437,7 +437,7 @@ mypy==1.20.2 \ --hash=sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665 \ --hash=sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026 \ --hash=sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd - # via -r tools/python/requirements.in + # via -r requirements.in mypy-extensions==1.1.0 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 @@ -476,17 +476,17 @@ pytest==9.0.3 \ --hash=sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 \ --hash=sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c # via - # -r tools/python/requirements.in + # -r requirements.in # pytest-asyncio # pytest-benchmark pytest-asyncio==1.3.0 \ --hash=sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5 \ --hash=sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5 - # via -r tools/python/requirements.in + # via -r requirements.in pytest-benchmark==5.2.3 \ --hash=sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803 \ --hash=sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779 - # via -r tools/python/requirements.in + # via -r requirements.in pytokens==0.4.1 \ --hash=sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1 \ --hash=sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009 \ @@ -534,7 +534,7 @@ pytokens==0.4.1 \ requests==2.33.1 \ --hash=sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517 \ --hash=sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a - # via -r tools/python/requirements.in + # via -r requirements.in typing-extensions==4.15.0 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 @@ -542,9 +542,9 @@ typing-extensions==4.15.0 \ # anyio # mypy # pytest-asyncio -urllib3==2.6.3 \ - --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \ - --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 +urllib3==2.7.0 \ + --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \ + --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 # via requests zope-event==6.2 \ --hash=sha256:5e755153ac4faf64c10a4b6dd3307680166a3edf65b38df22df592610f8fa874 \ From 92c6cb5831fc0faccbf6707bfc156458d5c5932f Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 11 May 2026 19:54:43 +0100 Subject: [PATCH 286/750] ci/actions: Bump toolshed -> 0.4.16 (#45008) Signed-off-by: Ryan Northey --- .github/workflows/_check_coverage.yml | 4 +-- .github/workflows/_cve_fetch.yml | 2 +- .github/workflows/_cve_scan.yml | 2 +- .github/workflows/_finish.yml | 8 ++--- .github/workflows/_load.yml | 10 +++--- .github/workflows/_load_env.yml | 8 ++--- .github/workflows/_precheck_publish.yml | 2 +- .github/workflows/_publish_release.yml | 4 +-- .../workflows/_publish_release_container.yml | 6 ++-- .github/workflows/_publish_verify.yml | 2 +- .github/workflows/_request.yml | 24 ++++++------- .github/workflows/_request_cache_bazel.yml | 10 +++--- .github/workflows/_request_cache_docker.yml | 10 +++--- .github/workflows/_request_checks.yml | 10 +++--- .github/workflows/_run.yml | 28 +++++++-------- .github/workflows/_upload_gcs.yml | 6 ++-- .github/workflows/codeql-daily.yml | 4 +-- .github/workflows/codeql-push.yml | 4 +-- .github/workflows/command.yml | 6 ++-- .github/workflows/envoy-dependency.yml | 18 +++++----- .github/workflows/envoy-release.yml | 34 +++++++++---------- .github/workflows/envoy-sync.yml | 4 +-- .github/workflows/mobile-android_build.yml | 12 +++---- .github/workflows/mobile-ios_build.yml | 4 +-- 24 files changed, 111 insertions(+), 111 deletions(-) diff --git a/.github/workflows/_check_coverage.yml b/.github/workflows/_check_coverage.yml index 3ee0ee1888fa6..b4132e70cf55d 100644 --- a/.github/workflows/_check_coverage.yml +++ b/.github/workflows/_check_coverage.yml @@ -61,7 +61,7 @@ jobs: upload-name: coverage upload-path: generated/coverage/html steps-post: | - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: output-path: generated/coverage/html/gcs-metadata.json input-format: yaml @@ -82,7 +82,7 @@ jobs: - target: fuzz_coverage name: Fuzz coverage steps-post: | - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: output-path: generated/fuzz_coverage/html/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_cve_fetch.yml b/.github/workflows/_cve_fetch.yml index 13c95bb755b35..7947f7340f89a 100644 --- a/.github/workflows/_cve_fetch.yml +++ b/.github/workflows/_cve_fetch.yml @@ -35,7 +35,7 @@ jobs: else echo "weekly_run=false" >> $GITHUB_OUTPUT fi - - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/gcp/setup@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_cve_scan.yml b/.github/workflows/_cve_scan.yml index f08900578d5ae..c1a7c9d6ff873 100644 --- a/.github/workflows/_cve_scan.yml +++ b/.github/workflows/_cve_scan.yml @@ -28,7 +28,7 @@ jobs: id: vars run: | echo "cve-data-path=${{ inputs.cve-data-path }}" > $GITHUB_OUTPUT - - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/gcp/setup@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_finish.yml b/.github/workflows/_finish.yml index 8b6b4d81928ee..55dc28fde80ac 100644 --- a/.github/workflows/_finish.yml +++ b/.github/workflows/_finish.yml @@ -36,7 +36,7 @@ jobs: actions: read contents: read steps: - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Incoming data id: needs with: @@ -87,7 +87,7 @@ jobs: summary: "Check has finished", text: $text}}}} - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Print summary with: input: ${{ toJSON(steps.needs.outputs.value).summary-title }} @@ -95,13 +95,13 @@ jobs: "## \(.)" options: -Rr output-path: GITHUB_STEP_SUMMARY - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checks@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Update check with: action: update diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index d3ce020ab86de..6e01033fae624 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -102,7 +102,7 @@ jobs: # Handle any failure in triggering job # Remove any `checks` we dont care about # Prepare a check request - - uses: envoyproxy/toolshed/actions/github/env/load@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/env/load@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Load env id: data with: @@ -113,13 +113,13 @@ jobs: GH_TOKEN: ${{ github.token }} # Update the check - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checks@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Update check if: ${{ fromJSON(steps.data.outputs.data).data.check.action == 'RUN' }} with: @@ -127,7 +127,7 @@ jobs: checks: ${{ toJSON(fromJSON(steps.data.outputs.data).checks) }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Print request summary with: input: | @@ -147,7 +147,7 @@ jobs: | $summary.summary as $summary | "${{ inputs.template-request-summary }}" - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: request-output name: Load request with: diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index a8036596341fa..2406b36ab1188 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -62,18 +62,18 @@ jobs: request: ${{ steps.env.outputs.data }} trusted: true steps: - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: checkout name: Checkout Envoy repository - name: Generate environment variables - uses: envoyproxy/toolshed/actions/envoy/ci/env@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/envoy/ci/env@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: env with: branch-name: ${{ inputs.branch-name }} @@ -85,7 +85,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/env/summary@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} diff --git a/.github/workflows/_precheck_publish.yml b/.github/workflows/_precheck_publish.yml index e891b56aa0d1d..e5cca6ee73a06 100644 --- a/.github/workflows/_precheck_publish.yml +++ b/.github/workflows/_precheck_publish.yml @@ -88,7 +88,7 @@ jobs: upload-name: docs upload-path: generated/docs steps-post: | - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: output-path: generated/docs/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_publish_release.yml b/.github/workflows/_publish_release.yml index b49edc72ca7c4..1e678f089ddf1 100644 --- a/.github/workflows/_publish_release.yml +++ b/.github/workflows/_publish_release.yml @@ -142,12 +142,12 @@ jobs: needs: - release steps: - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/actions/dispatch@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/dispatch@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: ref: main repository: ${{ fromJSON(inputs.request).request.version.dev && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} diff --git a/.github/workflows/_publish_release_container.yml b/.github/workflows/_publish_release_container.yml index 1374d55b42e97..6dfd1abc188dc 100644 --- a/.github/workflows/_publish_release_container.yml +++ b/.github/workflows/_publish_release_container.yml @@ -58,7 +58,7 @@ jobs: - name: Generate manifest configuration (dev) id: dev-config if: ${{ inputs.dev && inputs.target-branch == 'main' }} - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: input-format: yaml filter: >- @@ -137,7 +137,7 @@ jobs: - tools-dev-${{ github.sha }} - name: Generate manifest configuration (release) - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: release-config if: ${{ ! inputs.dev || ! inputs.target-branch != 'main' }} with: @@ -227,7 +227,7 @@ jobs: - tools-v${{ inputs.version-major }}.${{ inputs.version-minor }}-latest - name: Collect and push OCI artifacts - uses: envoyproxy/toolshed/actions/oci/collector@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/oci/collector@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: artifacts-pattern: oci.* manifest-config: ${{ steps.dev-config.outputs.value || steps.release-config.outputs.value }} diff --git a/.github/workflows/_publish_verify.yml b/.github/workflows/_publish_verify.yml index 83a50ecdb3f70..00906ed8ba42b 100644 --- a/.github/workflows/_publish_verify.yml +++ b/.github/workflows/_publish_verify.yml @@ -114,7 +114,7 @@ jobs: export NO_BUILD_SETUP=1 steps-pre: | - id: version-support - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: input: | version_major: ${{ fromJSON(inputs.request).request.version.major }} diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index 8d293dee26424..2bd7ef7bf1e00 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -55,14 +55,14 @@ jobs: caches: ${{ steps.caches.outputs.value }} config: ${{ steps.config.outputs.config }} steps: - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: checkout name: Checkout Envoy repository (requested) with: @@ -76,7 +76,7 @@ jobs: # *ALL* variables collected should be treated as untrusted and should be sanitized before # use - name: Generate environment variables from commit - uses: envoyproxy/toolshed/actions/envoy/ci/request@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/envoy/ci/request@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: env with: branch-name: ${{ steps.checkout.outputs.branch-name }} @@ -87,7 +87,7 @@ jobs: vars: ${{ toJSON(vars) }} working-directory: requested - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -95,7 +95,7 @@ jobs: config: | fetch-depth: 1 path: target - - uses: envoyproxy/toolshed/actions/hashfiles@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/hashfiles@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: bazel-cache-hash name: Bazel cache hash with: @@ -104,7 +104,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/env/summary@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} @@ -120,28 +120,28 @@ jobs: target-branch: ${{ fromJSON(steps.env.outputs.data).request.target-branch }} - id: cache-id-bazel-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-arm64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-arm64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-docs-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-docs-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-external-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-external-x64 wf-path: .github/workflows/request.yml - name: Environment data - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: data with: input: | @@ -189,7 +189,7 @@ jobs: path: /tmp/cache key: ${{ fromJSON(steps.data.outputs.value).request.build-image.default }}-arm64 - name: Caches - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: caches with: input-format: yaml diff --git a/.github/workflows/_request_cache_bazel.yml b/.github/workflows/_request_cache_bazel.yml index 6237eeb0091df..45ba16bb182b9 100644 --- a/.github/workflows/_request_cache_bazel.yml +++ b/.github/workflows/_request_cache_bazel.yml @@ -59,7 +59,7 @@ jobs: && ! fromJSON(inputs.caches).bazel[format('{0}-{1}', inputs.output-base, inputs.arch)]) }} steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bind-mounts@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: mounts: | - src: /mnt/workspace @@ -73,9 +73,9 @@ jobs: target: /build chown: "runner:docker" - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/diskspace@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 if: inputs.arch == 'x64' && github.event.repository.private - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -83,14 +83,14 @@ jobs: config: | fetch-depth: 1 - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/cache/prime@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/cache/prime@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: bazel-cache name: Prime Bazel cache with: diff --git a/.github/workflows/_request_cache_docker.yml b/.github/workflows/_request_cache_docker.yml index 3fd5228e9feb5..454eb09207fe3 100644 --- a/.github/workflows/_request_cache_docker.yml +++ b/.github/workflows/_request_cache_docker.yml @@ -39,7 +39,7 @@ on: # For a job that does, you can restore with something like: # # steps: -# - uses: envoyproxy/toolshed/actions/docker/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 +# - uses: envoyproxy/toolshed/actions/docker/cache/restore@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 # with: # key: "${{ needs.env.outputs.build-image }}" # @@ -51,13 +51,13 @@ jobs: name: "[${{ inputs.arch }}] Prime Docker cache" if: ${{ ! fromJSON(inputs.caches).docker[inputs.arch] }} steps: - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/docker/cache/prime@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/docker/cache/prime@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: docker name: Prime Docker cache (${{ inputs.image-tag }}${{ inputs.cache-suffix }}) with: @@ -65,7 +65,7 @@ jobs: key-suffix: ${{ inputs.cache-suffix }} lock-token: ${{ steps.appauth.outputs.token }} lock-repository: ${{ inputs.lock-repository }} - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: data name: Cache data with: @@ -73,7 +73,7 @@ jobs: input: | cached: ${{ steps.docker.outputs.cached }} key: ${{ inputs.image-tag }}${{ inputs.cache-suffix }} - - uses: envoyproxy/toolshed/actions/json/table@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/json/table@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Summary with: json: ${{ steps.data.outputs.value }} diff --git a/.github/workflows/_request_checks.yml b/.github/workflows/_request_checks.yml index 55e894fcd1764..9edbaa2c4b447 100644 --- a/.github/workflows/_request_checks.yml +++ b/.github/workflows/_request_checks.yml @@ -55,7 +55,7 @@ jobs: runs-on: ${{ fromJSON(inputs.env).config.ci.agent-ubuntu }} name: Start checks steps: - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: check-config name: Prepare check data with: @@ -78,13 +78,13 @@ jobs: | .skipped.output.summary = "${{ inputs.skipped-summary }}" | .skipped.output.text = "" - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checks@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Start checks id: checks with: @@ -95,7 +95,7 @@ jobs: ${{ fromJSON(inputs.env).summary.summary }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/json/table@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/json/table@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Summary with: collapse-open: true @@ -119,7 +119,7 @@ jobs: output-path: GITHUB_STEP_SUMMARY title: Checks started/skipped - - uses: envoyproxy/toolshed/actions/github/env/save@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/env/save@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Save env id: data with: diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 56ebeb49f6bc8..3bab87a3a75d5 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -146,7 +146,7 @@ on: summary-post: type: string default: | - - uses: envoyproxy/toolshed/actions/envoy/run/summary@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/run/summary@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: context: %{{ inputs.context }} steps-pre: @@ -215,7 +215,7 @@ jobs: name: ${{ inputs.target-suffix && format('[{0}] ', inputs.target-suffix) || '' }}${{ inputs.command }} ${{ inputs.target }} timeout-minutes: ${{ inputs.timeout-minutes }} steps: - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: started name: Create timestamp with: @@ -223,7 +223,7 @@ jobs: filter: | now # This controls which input vars are exposed to the run action (and related steps) - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Context id: context with: @@ -256,14 +256,14 @@ jobs: fi echo "mnt-available=$MNT_AVAILABLE" >> "$GITHUB_OUTPUT" id: disk - - uses: envoyproxy/toolshed/actions/github/remnt@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/remnt@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 if: steps.disk.outputs.should-remnt == 'true' - - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bind-mounts@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 if: inputs.bind-mount && steps.disk.outputs.mnt-available == 'true' with: mounts: ${{ inputs.bind-mounts }} - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/diskspace@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 if: inputs.diskspace-hack || steps.disk.outputs.mnt-available != 'true' with: to_remove: ${{ inputs.diskspace-hack-paths }} @@ -271,7 +271,7 @@ jobs: mount df -h - - uses: envoyproxy/toolshed/actions/bson@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bson@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Configure Docker if: runner.os == 'Linux' with: @@ -287,7 +287,7 @@ jobs: | "${{ inputs.template-docker-configure }}" # Caches - - uses: envoyproxy/toolshed/actions/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/cache/restore@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 if: >- fromJSON(inputs.bazel-cache) name: >- @@ -328,12 +328,12 @@ jobs: key: ${{ inputs.cache-build-image }}${{ inputs.cache-build-image-key-suffix }} - if: ${{ inputs.cache-build-image && steps.cache-lookup.outputs.cache-hit == 'true' }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/actions/docker/cache/restore@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/docker/cache/restore@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: image-tag: ${{ inputs.cache-build-image }} key-suffix: ${{ inputs.cache-build-image-key-suffix }} - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: appauth name: Appauth if: ${{ inputs.trusted }} @@ -344,7 +344,7 @@ jobs: # - the workaround is to allow the token to be passed through. token: ${{ github.token }} token-ok: true - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: checkout name: Checkout Envoy repository with: @@ -360,7 +360,7 @@ jobs: token: ${{ inputs.trusted && steps.appauth.outputs.token || github.token }} # This is currently only use by mobile-docs and can be removed once they are updated to the newer website - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: checkout-extra name: Checkout extra repository (for publishing) if: ${{ inputs.checkout-extra }} @@ -369,7 +369,7 @@ jobs: ssh-key: ${{ inputs.trusted && inputs.ssh-key-extra || '' }} - name: Import GPG key - uses: envoyproxy/toolshed/actions/gpg/import@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/gpg/import@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 if: ${{ inputs.import-gpg }} with: key: ${{ secrets.gpg-key }} @@ -406,7 +406,7 @@ jobs: # NOTE: This is where untrusted code can be run!!! # Only post-failure notification steps (which don't use any repo code) should follow this step. - - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/run@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: ci-run name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: diff --git a/.github/workflows/_upload_gcs.yml b/.github/workflows/_upload_gcs.yml index 556c3ec314567..4c100dcdc7f9d 100644 --- a/.github/workflows/_upload_gcs.yml +++ b/.github/workflows/_upload_gcs.yml @@ -29,17 +29,17 @@ jobs: with: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} - - uses: envoyproxy/toolshed/actions/jq@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: metadata with: input: ${{ matrix.artifact }}/gcs-metadata.json input-format: json-path - run: | rm -rf ${{ matrix.artifact }}/gcs-metadata.json - - uses: envoyproxy/toolshed/actions/gcp/setup@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/gcp/setup@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: key: ${{ secrets.gcp-key }} - - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: bucket: ${{ fromJSON(steps.metadata.outputs.value).bucket }} path: ${{ matrix.artifact }} diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 5baf6be36d540..4a552b229b436 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -28,7 +28,7 @@ jobs: steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bind-mounts@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 if: | ! github.event.repository.private with: @@ -43,7 +43,7 @@ jobs: if: | env.BUILD_TARGETS != '' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/diskspace@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 932cb66523f26..a67ea2735d991 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-22.04 if: github.repository == 'envoyproxy/envoy' steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bind-mounts@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 if: ! github.event.repository.private with: mounts: | @@ -64,7 +64,7 @@ jobs: if: | env.CPP_CHANGED == 'true' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/diskspace@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/command.yml b/.github/workflows/command.yml index bded5e0902aaa..62466c22993eb 100644 --- a/.github/workflows/command.yml +++ b/.github/workflows/command.yml @@ -28,7 +28,7 @@ jobs: && github.actor != 'dependabot[bot]' }} steps: - - uses: envoyproxy/toolshed/actions/github/command@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/command@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Parse command from comment id: command with: @@ -37,14 +37,14 @@ jobs: ^/(retest) # /retest - - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 if: ${{ steps.command.outputs.command == 'retest' }} id: appauth-retest name: Appauth (retest) with: key: ${{ secrets.ENVOY_CI_APP_KEY }} app_id: ${{ secrets.ENVOY_CI_APP_ID }} - - uses: envoyproxy/toolshed/actions/retest@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/retest@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 if: ${{ steps.command.outputs.command == 'retest' }} name: Retest with: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 41f5259d91acd..9291c983211b1 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -53,16 +53,16 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/bson@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/bson@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: update name: Update dependency (${{ inputs.dependency }}) with: @@ -97,13 +97,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/actions/upload/diff@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/upload/diff@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.update.outputs.output }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/pr@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: base: main body: | @@ -134,11 +134,11 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: checkout name: Checkout Envoy repository with: @@ -191,7 +191,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/actions/docker/shas@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/docker/shas@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: images: | sha-ci: docker.io/envoyproxy/envoy-build:ci-${{ steps.build-tools.outputs.tag }} @@ -229,7 +229,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/pr@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 319eae3a9ce01..06f429057f376 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -60,14 +60,14 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} @@ -88,10 +88,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/actions/email/validate@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/email/validate@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/run@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Create release with: source: | @@ -116,7 +116,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/pr@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: base: ${{ github.ref_name }} commit: false @@ -142,19 +142,19 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/run@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Re-open branch with: command: >- @@ -169,7 +169,7 @@ jobs: name: Dev version id: dev - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/pr@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: base: ${{ github.ref_name }} commit: false @@ -193,20 +193,20 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/github/run@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 name: Sync version histories with: command: >- @@ -216,7 +216,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/pr@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: append-commit-message: true base: ${{ github.ref_name }} @@ -243,13 +243,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: config: | fetch-depth: 0 @@ -279,13 +279,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - name: Checkout repository - uses: envoyproxy/toolshed/actions/github/checkout@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index eee736da174c9..ce6db6199f196 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -42,13 +42,13 @@ jobs: echo "skip=true" >> "$GITHUB_OUTPUT" fi - if: steps.tip.outputs.skip != 'true' - uses: envoyproxy/toolshed/actions/appauth@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - if: steps.tip.outputs.skip != 'true' - uses: envoyproxy/toolshed/actions/dispatch@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + uses: envoyproxy/toolshed/actions/dispatch@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index b86db3c471b7e..9ffa30dccea7e 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -107,9 +107,9 @@ jobs: target: kotlin-hello-world runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: apk: bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity @@ -152,7 +152,7 @@ jobs: target: ${{ matrix.target }} runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 steps-post: ${{ matrix.steps-post }} timeout-minutes: 50 trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} @@ -163,7 +163,7 @@ jobs: include: - name: java-hello-world steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: apk: bazel-bin/examples/java/hello_world/hello_envoy.apk app: io.envoyproxy.envoymobile.helloenvoy/.MainActivity @@ -186,7 +186,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/baseline:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: apk: bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity @@ -203,7 +203,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/experimental:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + - uses: envoyproxy/toolshed/actions/envoy/android/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 with: apk: bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 15a64d00a0215..7501b3cd03602 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -103,7 +103,7 @@ jobs: # source ./ci/mac_ci_setup.sh # bazel shutdown # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} @@ -148,7 +148,7 @@ jobs: # source: | # source ./ci/mac_ci_setup.sh # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@2ed4461f62fe3cf80196c05bea0d7dc39f15cf26 # actions-v0.4.12 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} From 15d9cdacbb100f9c3beee2585b5864b4ed2e3f89 Mon Sep 17 00:00:00 2001 From: code Date: Tue, 12 May 2026 08:29:38 +0800 Subject: [PATCH 287/750] clang tidy: fix member initializer of source (#44976) --- envoy/network/proxy_protocol.h | 2 +- envoy/server/admin.h | 4 +- source/common/access_log/access_log_impl.h | 2 +- source/common/buffer/buffer_impl.h | 2 +- source/common/buffer/watermark_buffer.h | 2 +- source/common/common/basic_resource_impl.h | 2 +- source/common/common/thread.cc | 2 +- source/common/event/timer_impl.h | 2 +- source/common/grpc/typed_async_client.h | 2 +- source/common/http/http1/codec_impl.cc | 8 +-- source/common/http/http1/codec_impl.h | 22 +++--- source/common/http/http2/codec_impl.cc | 10 +-- source/common/http/http2/codec_impl.h | 24 +++---- source/common/http/muxdemux.cc | 2 +- source/common/io/io_uring_worker_impl.h | 2 +- source/common/jwt/jwt.h | 2 +- source/common/jwt/simple_lru_cache_inl.h | 6 +- source/common/jwt/status.h | 4 +- .../listener_manager/active_tcp_listener.h | 2 +- .../connection_handler_impl.h | 2 +- source/common/matcher/regex_replace.h | 4 +- source/common/network/connection_impl.cc | 3 - source/common/network/connection_impl.h | 16 ++--- source/common/quic/active_quic_listener.h | 2 +- source/common/quic/quic_stats_gatherer.h | 2 +- source/common/router/upstream_request.cc | 8 +-- source/common/router/upstream_request.h | 26 +++---- .../common/ssl/tls_certificate_config_impl.h | 2 +- source/common/stats/histogram_impl.h | 2 +- source/common/stats/symbol_table.h | 9 ++- source/common/stream_info/stream_info_impl.h | 2 +- source/common/tcp_proxy/tcp_proxy.h | 2 +- source/common/tcp_proxy/upstream.h | 4 +- .../common/thread_local/thread_local_impl.h | 2 +- source/common/tls/context_impl.h | 2 +- source/common/upstream/cluster_manager_impl.h | 5 +- .../upstream/health_discovery_service.h | 3 +- .../upstream/load_stats_reporter_impl.h | 2 +- source/common/upstream/upstream_impl.h | 15 ++-- .../process_ratelimit/provider_singleton.cc | 3 +- .../process_ratelimit/provider_singleton.h | 2 +- .../reverse_connection_io_handle.h | 8 +-- .../reverse_tunnel_acceptor.cc | 2 +- .../reverse_tunnel_acceptor.h | 4 +- .../clusters/dynamic_modules/cluster.cc | 3 +- .../clusters/dynamic_modules/cluster.h | 4 +- source/extensions/common/dubbo/codec.h | 2 +- source/extensions/common/dubbo/message.h | 2 +- source/extensions/common/dubbo/metadata.h | 2 +- source/extensions/common/matcher/matcher.h | 2 +- .../redis/cluster_refresh_manager_impl.h | 8 +-- source/extensions/common/wasm/foreign.cc | 2 +- source/extensions/common/wasm/plugin.h | 2 +- source/extensions/common/wasm/wasm.h | 6 +- .../grpc/grpc_mux_failover.h | 3 +- .../config_subscription/grpc/grpc_mux_impl.h | 2 +- .../grpc/new_grpc_mux_impl.h | 2 +- .../grpc/xds_mux/subscription_state.h | 2 +- .../filters/common/expr/cel_state.h | 2 +- .../filters/common/ext_authz/ext_authz.h | 28 ++++---- .../common/ext_authz/ext_authz_http_impl.cc | 9 ++- .../filters/http/api_key_auth/api_key_auth.h | 2 +- .../bandwidth_share/fair_token_bucket_impl.h | 2 +- .../http/decompressor/decompressor_filter.h | 4 +- .../filters/http/ext_authz/ext_authz.cc | 19 ++++-- .../filters/http/ext_authz/ext_authz.h | 10 +-- .../filters/http/ext_proc/ext_proc.cc | 40 ++++++----- .../filters/http/ext_proc/ext_proc.h | 68 ++++++++++--------- .../filters/http/fault/fault_filter.h | 14 ++-- .../http/grpc_http1_reverse_bridge/filter.h | 4 +- .../json_transcoder_filter.h | 2 +- .../filters/http/health_check/health_check.h | 6 +- .../scrubbing_util_lib/field_checker.cc | 7 +- .../scrubbing_util_lib/field_checker.h | 4 +- .../http/proto_message_extraction/filter.h | 5 +- .../rate_limit_quota/global_client_impl.h | 4 +- .../filters/http/thrift_to_metadata/filter.h | 4 +- .../listener/proxy_protocol/proxy_protocol.h | 2 +- .../network/dubbo_proxy/active_message.cc | 5 +- .../network/dubbo_proxy/active_message.h | 6 +- .../network/dubbo_proxy/message_impl.h | 4 +- .../network/dubbo_proxy/router/router_impl.cc | 6 +- .../network/dubbo_proxy/router/router_impl.h | 10 +-- .../filters/network/ext_authz/ext_authz.h | 2 +- .../filters/network/ext_proc/ext_proc.h | 2 +- .../filters/network/generic_proxy/stats.h | 4 +- .../redis_proxy/cluster_response_handler.h | 2 +- .../redis_proxy/info_command_handler.h | 6 +- .../network/thrift_proxy/auto_protocol_impl.h | 2 +- .../thrift_proxy/auto_transport_impl.h | 2 +- .../thrift_proxy/compact_protocol_impl.h | 6 +- .../network/thrift_proxy/conn_manager.h | 21 +++--- .../payload_to_metadata_filter.h | 2 +- .../filters/network/thrift_proxy/metadata.h | 14 ++-- .../network/thrift_proxy/router/router_impl.h | 12 ++-- .../thrift_proxy/router/upstream_request.cc | 1 - .../thrift_proxy/router/upstream_request.h | 6 +- .../filters/udp/udp_proxy/udp_proxy_filter.h | 2 +- .../http/health_checker_impl.cc | 3 +- .../http/health_checker_impl.h | 6 +- .../dynamic_modules/lb_config.cc | 3 +- .../dynamic_modules/lb_config.h | 2 +- .../dynamic_modules/load_balancer.cc | 3 +- .../dynamic_modules/load_balancer.h | 2 +- .../actions/transform_stat/transform_stat.h | 2 +- .../resource_detectors/resource_detector.h | 2 +- .../samplers/dynatrace/sampling_controller.h | 6 +- .../transport_sockets/alts/tsi_socket.h | 2 +- .../transport_sockets/http_11_proxy/connect.h | 2 +- .../proxy_protocol/proxy_protocol.h | 6 +- .../cert_validator/spiffe/spiffe_validator.h | 2 +- source/extensions/upstreams/http/config.h | 2 +- .../upstreams/http/http/upstream_request.h | 2 +- source/server/hot_restarting_child.h | 2 +- source/server/server.cc | 3 +- source/server/server.h | 4 +- 116 files changed, 332 insertions(+), 349 deletions(-) diff --git a/envoy/network/proxy_protocol.h b/envoy/network/proxy_protocol.h index ca866e91a131f..70c65783c0d8f 100644 --- a/envoy/network/proxy_protocol.h +++ b/envoy/network/proxy_protocol.h @@ -21,7 +21,7 @@ using ProxyProtocolTLVVector = std::vector; struct ProxyProtocolData { const Network::Address::InstanceConstSharedPtr src_addr_; const Network::Address::InstanceConstSharedPtr dst_addr_; - const ProxyProtocolTLVVector tlv_vector_{}; + const ProxyProtocolTLVVector tlv_vector_; std::string asStringForHash() const { return std::string(src_addr_ ? src_addr_->asString() : "null") + (dst_addr_ ? dst_addr_->asString() : "null"); diff --git a/envoy/server/admin.h b/envoy/server/admin.h index a0b4fa4934584..c1964536da9f8 100644 --- a/envoy/server/admin.h +++ b/envoy/server/admin.h @@ -111,7 +111,7 @@ class Admin { Type type_; std::string id_; // HTML form ID and query-param name (JS var name rules). std::string help_; // Rendered into home-page HTML and /help text. - std::vector enum_choices_{}; + std::vector enum_choices_; }; using ParamDescriptorVec = std::vector; @@ -163,7 +163,7 @@ class Admin { const GenRequestFn handler_; const bool removable_; const bool mutates_server_state_; - const ParamDescriptorVec params_{}; + const ParamDescriptorVec params_; }; /** diff --git a/source/common/access_log/access_log_impl.h b/source/common/access_log/access_log_impl.h index a312e9aaa20b1..5fb07922d0e20 100644 --- a/source/common/access_log/access_log_impl.h +++ b/source/common/access_log/access_log_impl.h @@ -188,7 +188,7 @@ class ResponseFlagFilter : public Filter { const StreamInfo::StreamInfo& info) const override; private: - std::vector configured_flags_{}; + std::vector configured_flags_; }; /** diff --git a/source/common/buffer/buffer_impl.h b/source/common/buffer/buffer_impl.h index 72cca4ba9b1a2..6aff485f4a72c 100644 --- a/source/common/buffer/buffer_impl.h +++ b/source/common/buffer/buffer_impl.h @@ -40,7 +40,7 @@ class Slice { using StoragePtr = std::unique_ptr; struct SizedStorage { - StoragePtr mem_{}; + StoragePtr mem_; size_t len_{}; }; diff --git a/source/common/buffer/watermark_buffer.h b/source/common/buffer/watermark_buffer.h index 18c5df1cb0007..6471b6405cd3b 100644 --- a/source/common/buffer/watermark_buffer.h +++ b/source/common/buffer/watermark_buffer.h @@ -144,7 +144,7 @@ class BufferMemoryAccountImpl : public BufferMemoryAccount { uint64_t buffer_memory_allocated_ = 0; // Current bucket index where the account is being tracked in. - absl::optional current_bucket_idx_{}; + absl::optional current_bucket_idx_; WatermarkBufferFactory* factory_ = nullptr; diff --git a/source/common/common/basic_resource_impl.h b/source/common/common/basic_resource_impl.h index 8fedbb4ad8013..4470715664e04 100644 --- a/source/common/common/basic_resource_impl.h +++ b/source/common/common/basic_resource_impl.h @@ -49,7 +49,7 @@ class BasicResourceLimitImpl : public ResourceLimit { void resetMax() { max_ = std::numeric_limits::max(); } protected: - std::atomic current_{}; + std::atomic current_{0}; private: uint64_t max_; diff --git a/source/common/common/thread.cc b/source/common/common/thread.cc index a22c1754439f8..9c90438e98cd1 100644 --- a/source/common/common/thread.cc +++ b/source/common/common/thread.cc @@ -67,7 +67,7 @@ struct ThreadIds { private: mutable absl::Mutex mutex_; - std::atomic skip_asserts_{}; + std::atomic skip_asserts_{0}; absl::flat_hash_map main_threads_to_usage_count_ ABSL_GUARDED_BY(mutex_); }; diff --git a/source/common/event/timer_impl.h b/source/common/event/timer_impl.h index d5612f37e20f6..314d536bdf470 100644 --- a/source/common/event/timer_impl.h +++ b/source/common/event/timer_impl.h @@ -71,7 +71,7 @@ class TimerImpl : public Timer, ImplBase { // This has to be atomic for alarms which are handled out of thread, for // example if the DispatcherImpl::post is called by two threads, they race to // both set this to null. - std::atomic object_{}; + std::atomic object_{nullptr}; }; } // namespace Event diff --git a/source/common/grpc/typed_async_client.h b/source/common/grpc/typed_async_client.h index e664106c04688..03b92f27c4a86 100644 --- a/source/common/grpc/typed_async_client.h +++ b/source/common/grpc/typed_async_client.h @@ -139,7 +139,7 @@ template class AsyncClient /* : public Raw void reset() { client_.reset(); } private: - RawAsyncClientSharedPtr client_{}; + RawAsyncClientSharedPtr client_; }; } // namespace Grpc diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index fef23db545003..f541dce5767f3 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -104,9 +104,7 @@ static constexpr absl::string_view COLON_SPACE = ": "; StreamEncoderImpl::StreamEncoderImpl(ConnectionImpl& connection, StreamInfo::BytesMeterSharedPtr&& bytes_meter) - : connection_(connection), disable_chunk_encoding_(false), chunk_encoding_(true), - connect_request_(false), is_tcp_tunneling_(false), is_response_to_head_request_(false), - is_response_to_connect_request_(false), bytes_meter_(std::move(bytes_meter)) { + : connection_(connection), bytes_meter_(std::move(bytes_meter)) { if (!bytes_meter_) { bytes_meter_ = std::make_shared(); } @@ -535,9 +533,7 @@ ConnectionImpl::ConnectionImpl(Network::Connection& connection, CodecStats& stat uint32_t max_headers_kb, const uint32_t max_headers_count) : connection_(connection), stats_(stats), codec_settings_(settings), encode_only_header_key_formatter_(encodeOnlyFormatterFromSettings(settings)), - processing_trailers_(false), handling_upgrade_(false), reset_stream_called_(false), - deferred_end_stream_headers_(false), dispatching_(false), max_headers_kb_(max_headers_kb), - max_headers_count_(max_headers_count) { + max_headers_kb_(max_headers_kb), max_headers_count_(max_headers_count) { parser_ = std::make_unique(type, this, max_headers_kb_ * 1024, enableTrailers(), codec_settings_.allow_custom_methods_); } diff --git a/source/common/http/http1/codec_impl.h b/source/common/http/http1/codec_impl.h index 8e0f5d83ca8d9..3bbb475b0ec9d 100644 --- a/source/common/http/http1/codec_impl.h +++ b/source/common/http/http1/codec_impl.h @@ -102,12 +102,12 @@ class StreamEncoderImpl : public virtual StreamEncoder, Buffer::BufferMemoryAccountSharedPtr buffer_memory_account_; ConnectionImpl& connection_; uint32_t read_disable_calls_{}; - bool disable_chunk_encoding_ : 1; - bool chunk_encoding_ : 1; - bool connect_request_ : 1; - bool is_tcp_tunneling_ : 1; - bool is_response_to_head_request_ : 1; - bool is_response_to_connect_request_ : 1; + bool disable_chunk_encoding_ : 1 = false; + bool chunk_encoding_ : 1 = true; + bool connect_request_ : 1 = false; + bool is_tcp_tunneling_ : 1 = false; + bool is_response_to_head_request_ : 1 = false; + bool is_response_to_connect_request_ : 1 = false; private: /** @@ -311,14 +311,14 @@ class ConnectionImpl : public virtual Connection, const HeaderKeyFormatterConstPtr encode_only_header_key_formatter_; HeaderString current_header_field_; HeaderString current_header_value_; - bool processing_trailers_ : 1; - bool handling_upgrade_ : 1; - bool reset_stream_called_ : 1; + bool processing_trailers_ : 1 = false; + bool handling_upgrade_ : 1 = false; + bool reset_stream_called_ : 1 = false; // Deferred end stream headers indicate that we are not going to raise headers until the full // HTTP/1 message has been flushed from the parser. This allows raising an HTTP/2 style headers // block with end stream set to true with no further protocol data remaining. - bool deferred_end_stream_headers_ : 1; - bool dispatching_ : 1; + bool deferred_end_stream_headers_ : 1 = false; + bool dispatching_ : 1 = false; bool dispatching_slice_already_drained_ : 1; StreamInfo::BytesMeterSharedPtr bytes_meter_before_stream_; const uint32_t max_headers_kb_; diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 88c8aa446cf45..57155a0d2a178 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -301,12 +301,7 @@ ConnectionImpl::StreamImpl::StreamImpl(ConnectionImpl& parent, uint32_t buffer_l pending_send_data_(parent_.connection_.dispatcher().getWatermarkFactory().createBuffer( [this]() -> void { this->pendingSendBufferLowWatermark(); }, [this]() -> void { this->pendingSendBufferHighWatermark(); }, - []() -> void { /* TODO(adisuissa): Handle overflow watermark */ })), - local_end_stream_sent_(false), remote_end_stream_(false), remote_rst_(false), - data_deferred_(false), received_noninformational_headers_(false), - pending_receive_buffer_high_watermark_called_(false), - pending_send_buffer_high_watermark_called_(false), reset_due_to_messaging_error_(false), - extend_stream_lifetime_flag_(false) { + []() -> void { /* TODO(adisuissa): Handle overflow watermark */ })) { parent_.stats_.streams_active_.inc(); if (buffer_limit > 0) { setWriteBufferWatermarks(buffer_limit); @@ -983,8 +978,7 @@ ConnectionImpl::ConnectionImpl(Network::Connection& connection, CodecStats& stat per_stream_buffer_limit_(http2_options.initial_stream_window_size().value()), stream_error_on_invalid_http_messaging_( http2_options.override_stream_error_on_invalid_http_message().value()), - protocol_constraints_(stats, http2_options), dispatching_(false), raised_goaway_(false), - random_(random_generator), + protocol_constraints_(stats, http2_options), random_(random_generator), last_received_data_time_(connection_.dispatcher().timeSource().monotonicTime()) { if (http2_options.has_use_oghttp2_codec()) { use_oghttp2_library_ = http2_options.use_oghttp2_codec().value(); diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 4a0cf2c23094e..d0cebbc36f384 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -80,7 +80,7 @@ class ReceivedSettingsImpl : public ReceivedSettings { } private: - absl::optional concurrent_stream_limit_{}; + absl::optional concurrent_stream_limit_; }; class Utility { @@ -441,16 +441,16 @@ class ConnectionImpl : public virtual Connection, // to determine whether we should continue processing that data. absl::optional reset_reason_; HeaderString cookies_; - bool local_end_stream_sent_ : 1; - bool remote_end_stream_ : 1; - bool remote_rst_ : 1; - bool data_deferred_ : 1; - bool received_noninformational_headers_ : 1; - bool pending_receive_buffer_high_watermark_called_ : 1; - bool pending_send_buffer_high_watermark_called_ : 1; - bool reset_due_to_messaging_error_ : 1; + bool local_end_stream_sent_ : 1 = false; + bool remote_end_stream_ : 1 = false; + bool remote_rst_ : 1 = false; + bool data_deferred_ : 1 = false; + bool received_noninformational_headers_ : 1 = false; + bool pending_receive_buffer_high_watermark_called_ : 1 = false; + bool pending_send_buffer_high_watermark_called_ : 1 = false; + bool reset_due_to_messaging_error_ : 1 = false; // Latch whether this stream is operating with this flag. - bool extend_stream_lifetime_flag_ : 1; + bool extend_stream_lifetime_flag_ : 1 = false; absl::string_view details_; /** @@ -810,8 +810,8 @@ class ConnectionImpl : public virtual Connection, // remove streams from the map when they are closed in order to avoid calls to resetStreamWorker // after the stream has been removed from the active list. std::map pending_deferred_reset_streams_; - bool dispatching_ : 1; - bool raised_goaway_ : 1; + bool dispatching_ : 1 = false; + bool raised_goaway_ : 1 = false; Event::SchedulableCallbackPtr protocol_constraint_violation_callback_; Random::RandomGenerator& random_; MonotonicTime last_received_data_time_; diff --git a/source/common/http/muxdemux.cc b/source/common/http/muxdemux.cc index 8d62962a7a503..1d092e20f7352 100644 --- a/source/common/http/muxdemux.cc +++ b/source/common/http/muxdemux.cc @@ -148,7 +148,7 @@ void MultiStream::maybeSwitchToIdle() { MuxDemux::MuxDemux(Server::Configuration::FactoryContext& context) : factory_context_(context) {} -MuxDemux::~MuxDemux() {} +MuxDemux::~MuxDemux() = default; absl::StatusOr> MuxDemux::multicast(const AsyncClient::StreamOptions& options, diff --git a/source/common/io/io_uring_worker_impl.h b/source/common/io/io_uring_worker_impl.h index b2ebd1d380497..26a50fc8402fe 100644 --- a/source/common/io/io_uring_worker_impl.h +++ b/source/common/io/io_uring_worker_impl.h @@ -227,7 +227,7 @@ class IoUringServerSocket : public IoUringSocketEntry { // shutdown_ has 3 states. A absl::nullopt indicates the socket has not been shutdown, a false // value represents the socket wants to be shutdown but the shutdown has not been performed or // completed, and a true value means the socket has been shutdown. - absl::optional shutdown_{}; + absl::optional shutdown_; // If there is in progress write_or_shutdown_req_ during closing, a write timeout timer may be // setup to cancel the write_or_shutdown_req_, either a write request or a shutdown request. So // we can make sure all SQEs bounding to the iouring socket is completed and the socket can be diff --git a/source/common/jwt/jwt.h b/source/common/jwt/jwt.h index 1609f8352921a..fd8f01bc1f48f 100644 --- a/source/common/jwt/jwt.h +++ b/source/common/jwt/jwt.h @@ -62,7 +62,7 @@ struct Jwt { /** * Standard constructor. */ - Jwt() {} + Jwt() = default; /** * Copy constructor. The copy constructor is marked as explicit as the caller * should understand the copy operation is non-trivial as a complete diff --git a/source/common/jwt/simple_lru_cache_inl.h b/source/common/jwt/simple_lru_cache_inl.h index c4a1f84bd11f6..42facd57760fc 100644 --- a/source/common/jwt/simple_lru_cache_inl.h +++ b/source/common/jwt/simple_lru_cache_inl.h @@ -113,7 +113,7 @@ class SimpleLRUCacheConstIterator // This default constructed Iterator can only be assigned to or destroyed. // All other operations give undefined behaviour. - SimpleLRUCacheConstIterator() {} + SimpleLRUCacheConstIterator() = default; SimpleLRUCacheConstIterator(HashMapConstIterator it, HashMapConstIterator end); SimpleLRUCacheConstIterator& operator++(); @@ -193,7 +193,7 @@ template const int64_t SimpleLRUCacheElem diff --git a/source/common/jwt/status.h b/source/common/jwt/status.h index ba5c7ff48009b..5c5d5fc9217d3 100644 --- a/source/common/jwt/status.h +++ b/source/common/jwt/status.h @@ -225,7 +225,7 @@ std::string getStatusString(Status status); */ class WithStatus { public: - WithStatus() : status_(Status::Ok) {} + WithStatus() = default; /** * Get the current status. @@ -245,7 +245,7 @@ class WithStatus { private: // The internal status. - Status status_; + Status status_{Status::Ok}; }; } // namespace JwtVerify diff --git a/source/common/listener_manager/active_tcp_listener.h b/source/common/listener_manager/active_tcp_listener.h index 299fc34d51267..93be22e527a84 100644 --- a/source/common/listener_manager/active_tcp_listener.h +++ b/source/common/listener_manager/active_tcp_listener.h @@ -94,7 +94,7 @@ class ActiveTcpListener final : public Network::TcpListenerCallbacks, Network::TcpConnectionHandler& tcp_conn_handler_; // The number of connections currently active on this listener. This is typically used for // connection balancing across per-handler listeners. - std::atomic num_listener_connections_{}; + std::atomic num_listener_connections_{0}; Network::ConnectionBalancer& connection_balancer_; // This is the address this listener is listening on. It's used to get the correct listener diff --git a/source/common/listener_manager/connection_handler_impl.h b/source/common/listener_manager/connection_handler_impl.h index 917b090876638..3b3538f2731a8 100644 --- a/source/common/listener_manager/connection_handler_impl.h +++ b/source/common/listener_manager/connection_handler_impl.h @@ -158,7 +158,7 @@ class ConnectionHandlerImpl : public ConnectionHandler, OptRef null_overload_manager_; const std::string per_handler_stat_prefix_; // Declare before its users ActiveListenerDetails. - std::atomic num_handler_connections_{}; + std::atomic num_handler_connections_{0}; absl::flat_hash_map> listener_map_by_tag_; absl::flat_hash_map> tcp_listener_map_by_address_; diff --git a/source/common/matcher/regex_replace.h b/source/common/matcher/regex_replace.h index 17bb68b5491aa..b7ca27cf87fc7 100644 --- a/source/common/matcher/regex_replace.h +++ b/source/common/matcher/regex_replace.h @@ -24,8 +24,8 @@ class RegexReplace { std::string apply(absl::string_view in) const; private: - Regex::CompiledMatcherPtr regex_{}; - const std::string substitution_{}; + Regex::CompiledMatcherPtr regex_; + const std::string substitution_; }; } // namespace Matcher diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index f125a5ce69248..b507d2b5fb109 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -80,9 +80,6 @@ ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPt [this]() -> void { this->onReadBufferLowWatermark(); }, [this]() -> void { this->onReadBufferHighWatermark(); }, []() -> void { /* TODO(adisuissa): Handle overflow watermark */ })), - detect_early_close_(true), enable_half_close_(false), read_end_stream_raised_(false), - read_end_stream_(false), write_end_stream_(false), current_write_end_stream_(false), - dispatch_buffered_data_(false), transport_wants_read_(false), enable_close_through_filter_manager_(Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.connection_close_through_filter_manager")) { diff --git a/source/common/network/connection_impl.h b/source/common/network/connection_impl.h index b27bf7945f7d2..f773409e93930 100644 --- a/source/common/network/connection_impl.h +++ b/source/common/network/connection_impl.h @@ -264,19 +264,19 @@ class ConnectionImpl : public ConnectionImplBase, public TransportSocketCallback StreamInfo::DetectedCloseType detected_close_type_{StreamInfo::DetectedCloseType::Normal}; std::chrono::milliseconds buffer_high_watermark_timeout_{}; Event::TimerPtr buffer_high_watermark_timer_{nullptr}; - bool detect_early_close_ : 1; - bool enable_half_close_ : 1; - bool read_end_stream_raised_ : 1; - bool read_end_stream_ : 1; - bool write_end_stream_ : 1; - bool current_write_end_stream_ : 1; - bool dispatch_buffered_data_ : 1; + bool detect_early_close_ : 1 = true; + bool enable_half_close_ : 1 = false; + bool read_end_stream_raised_ : 1 = false; + bool read_end_stream_ : 1 = false; + bool write_end_stream_ : 1 = false; + bool current_write_end_stream_ : 1 = false; + bool dispatch_buffered_data_ : 1 = false; // True if the most recent call to the transport socket's doRead method invoked // setTransportSocketIsReadable to schedule read resumption after yielding due to // shouldDrainReadBuffer(). When true, readDisable must schedule read resumption when // read_disable_count_ == 0 to ensure that read resumption happens when remaining bytes are held // in transport socket internal buffers. - bool transport_wants_read_ : 1; + bool transport_wants_read_ : 1 = false; bool enable_close_through_filter_manager_ : 1; }; diff --git a/source/common/quic/active_quic_listener.h b/source/common/quic/active_quic_listener.h index 377d041e128cc..b0b3c5a125b52 100644 --- a/source/common/quic/active_quic_listener.h +++ b/source/common/quic/active_quic_listener.h @@ -89,7 +89,7 @@ class ActiveQuicListener : public Envoy::Server::ActiveUdpListenerBase, quic::QuicVersionManager version_manager_; std::unique_ptr quic_dispatcher_; const bool kernel_worker_routing_; - absl::optional enabled_{}; + absl::optional enabled_; Network::UdpPacketWriter* udp_packet_writer_; // The number of runs of the event loop in which at least one CHLO was buffered. diff --git a/source/common/quic/quic_stats_gatherer.h b/source/common/quic/quic_stats_gatherer.h index b086d60f20974..d1da93de6d32e 100644 --- a/source/common/quic/quic_stats_gatherer.h +++ b/source/common/quic/quic_stats_gatherer.h @@ -67,7 +67,7 @@ class QuicStatsGatherer : public quic::QuicAckListenerInterface { private: uint64_t bytes_outstanding_ = 0; bool fin_sent_ = false; - AccessLog::InstanceSharedPtrVector access_log_handlers_{}; + AccessLog::InstanceSharedPtrVector access_log_handlers_; Http::RequestHeaderMapConstSharedPtr request_header_map_; Http::ResponseHeaderMapConstSharedPtr response_header_map_; Http::ResponseTrailerMapConstSharedPtr response_trailer_map_; diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index a409cbba034e1..0b3fdb03b4faf 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -88,14 +88,8 @@ UpstreamRequest::UpstreamRequest(RouterFilterInterface& parent, : nullptr, StreamInfo::FilterState::LifeSpan::FilterChain), start_time_(parent_.callbacks()->dispatcher().timeSource().monotonicTime()), - upstream_canary_(false), router_sent_end_stream_(false), encode_trailers_(false), - retried_(false), awaiting_headers_(true), outlier_detection_timeout_recorded_(false), - create_per_try_timeout_on_request_complete_(false), paused_for_connect_(false), - paused_for_websocket_(false), reset_stream_(false), record_timeout_budget_(parent_.cluster()->timeoutBudgetStats().has_value()), - cleaned_up_(false), had_upstream_(false), - stream_options_({can_send_early_data, can_use_http3}), grpc_rq_success_deferred_(false), - enable_half_close_(enable_half_close) { + stream_options_({can_send_early_data, can_use_http3}), enable_half_close_(enable_half_close) { // Get tracing config once and reuse it. auto tracing_config = parent_.callbacks()->tracingConfig(); diff --git a/source/common/router/upstream_request.h b/source/common/router/upstream_request.h index 8b095cd72a0e7..e7a9dc5ab0a97 100644 --- a/source/common/router/upstream_request.h +++ b/source/common/router/upstream_request.h @@ -238,29 +238,29 @@ class UpstreamRequest : public Logger::Loggable, // Keep small members (bools and enums) at the end of class, to reduce alignment overhead. // Tracks the number of times the flow of data from downstream has been disabled. uint32_t downstream_data_disabled_{}; - bool upstream_canary_ : 1; - bool router_sent_end_stream_ : 1; - bool encode_trailers_ : 1; - bool retried_ : 1; - bool awaiting_headers_ : 1; - bool outlier_detection_timeout_recorded_ : 1; + bool upstream_canary_ : 1 = false; + bool router_sent_end_stream_ : 1 = false; + bool encode_trailers_ : 1 = false; + bool retried_ : 1 = false; + bool awaiting_headers_ : 1 = true; + bool outlier_detection_timeout_recorded_ : 1 = false; // Tracks whether we deferred a per try timeout because the downstream request // had not been completed yet. - bool create_per_try_timeout_on_request_complete_ : 1; + bool create_per_try_timeout_on_request_complete_ : 1 = false; // True if the CONNECT headers have been sent but proxying payload is paused // waiting for response headers. - bool paused_for_connect_ : 1; - bool paused_for_websocket_ : 1; - bool reset_stream_ : 1; + bool paused_for_connect_ : 1 = false; + bool paused_for_websocket_ : 1 = false; + bool reset_stream_ : 1 = false; // Sentinel to indicate if timeout budget tracking is configured for the cluster, // and if so, if the per-try histogram should record a value. bool record_timeout_budget_ : 1; // Track if one time clean up has been performed. - bool cleaned_up_ : 1; - bool had_upstream_ : 1; + bool cleaned_up_ : 1 = false; + bool had_upstream_ : 1 = false; Http::ConnectionPool::Instance::StreamOptions stream_options_; - bool grpc_rq_success_deferred_ : 1; + bool grpc_rq_success_deferred_ : 1 = false; bool enable_half_close_ : 1; }; diff --git a/source/common/ssl/tls_certificate_config_impl.h b/source/common/ssl/tls_certificate_config_impl.h index 7c8fbe74bd4a6..9557b2e86d724 100644 --- a/source/common/ssl/tls_certificate_config_impl.h +++ b/source/common/ssl/tls_certificate_config_impl.h @@ -51,7 +51,7 @@ class TlsCertificateConfigImpl : public TlsCertificateConfig { const std::string password_path_; const std::vector ocsp_staple_; const std::string ocsp_staple_path_; - Envoy::Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_{}; + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_; }; } // namespace Ssl diff --git a/source/common/stats/histogram_impl.h b/source/common/stats/histogram_impl.h index c8f121cf93ba2..2cc2bac78299b 100644 --- a/source/common/stats/histogram_impl.h +++ b/source/common/stats/histogram_impl.h @@ -35,7 +35,7 @@ class HistogramSettingsImpl : public HistogramSettings { absl::optional buckets_; absl::optional bins_; }; - const std::vector configs_{}; + const std::vector configs_; }; /** diff --git a/source/common/stats/symbol_table.h b/source/common/stats/symbol_table.h index d008881ba263e..8f2fc96631dd8 100644 --- a/source/common/stats/symbol_table.h +++ b/source/common/stats/symbol_table.h @@ -519,6 +519,7 @@ class StatNameStorageBase { const uint8_t* bytes() const { return bytes_.get(); } protected: + SymbolTable::StoragePtr releaseBytes() { return std::move(bytes_); } void setBytes(SymbolTable::StoragePtr&& bytes) { bytes_ = std::move(bytes); } void clear() { bytes_.reset(); } @@ -567,6 +568,9 @@ class StatNameStorage : public StatNameStorageBase { * @param table the symbol table. */ void free(SymbolTable& table); + +protected: + StatNameStorage() = default; }; /** @@ -733,8 +737,9 @@ class StatNameManagedStorage : public StatNameStorage { // generate symbols for it. StatNameManagedStorage(absl::string_view name, SymbolTable& table) : StatNameStorage(name, table), symbol_table_(table) {} - StatNameManagedStorage(StatNameManagedStorage&& src) noexcept - : StatNameStorage(std::move(src)), symbol_table_(src.symbol_table_) {} + StatNameManagedStorage(StatNameManagedStorage&& src) noexcept : symbol_table_(src.symbol_table_) { + setBytes(src.releaseBytes()); + } StatNameManagedStorage(StatName src, SymbolTable& table) noexcept : StatNameStorage(src, table), symbol_table_(table) {} diff --git a/source/common/stream_info/stream_info_impl.h b/source/common/stream_info/stream_info_impl.h index 0fda47ac6b5a6..ad93e894ecac6 100644 --- a/source/common/stream_info/stream_info_impl.h +++ b/source/common/stream_info/stream_info_impl.h @@ -586,7 +586,7 @@ struct StreamInfoImpl : public StreamInfo { bool should_scheme_match_upstream_{false}; bool should_drain_connection_{false}; bool is_shadow_{false}; - absl::optional codec_stream_id_{}; + absl::optional codec_stream_id_; }; } // namespace StreamInfo diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index 546f0c2ec5aba..f9e6b08b120a9 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -757,7 +757,7 @@ class Filter : public Network::ReadFilter, // the upstream connection is established. bool receive_before_connect_{false}; bool early_data_end_stream_{false}; - Buffer::OwnedImpl early_data_buffer_{}; + Buffer::OwnedImpl early_data_buffer_; HttpStreamDecoderFilterCallbacks upstream_decoder_filter_callbacks_; // Connection establishment mode configuration. diff --git a/source/common/tcp_proxy/upstream.h b/source/common/tcp_proxy/upstream.h index a9b05aaf5781e..59c4e777b6719 100644 --- a/source/common/tcp_proxy/upstream.h +++ b/source/common/tcp_proxy/upstream.h @@ -50,7 +50,7 @@ class TcpConnPool : public GenericConnPool, public Tcp::ConnectionPool::Callback Upstream::HostDescriptionConstSharedPtr host) override; private: - absl::optional conn_pool_data_{}; + absl::optional conn_pool_data_; Tcp::ConnectionPool::Cancellable* upstream_handle_{}; GenericConnectionPoolCallbacks* callbacks_{}; Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks_; @@ -155,7 +155,7 @@ class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callba Ssl::ConnectionInfoConstSharedPtr ssl_info); const TunnelingConfigHelper& config_; Http::CodecType type_; - absl::optional conn_pool_data_{}; + absl::optional conn_pool_data_; Http::ConnectionPool::Cancellable* upstream_handle_{}; GenericConnectionPoolCallbacks* callbacks_{}; Http::StreamDecoderFilterCallbacks* decoder_filter_callbacks_; diff --git a/source/common/thread_local/thread_local_impl.h b/source/common/thread_local/thread_local_impl.h index cb0558ff57f02..a2aa9351f0d1d 100644 --- a/source/common/thread_local/thread_local_impl.h +++ b/source/common/thread_local/thread_local_impl.h @@ -87,7 +87,7 @@ class InstanceImpl : Logger::Loggable, public NonCopyable, pub std::vector free_slot_indexes_; std::list> registered_threads_; Event::Dispatcher* main_thread_dispatcher_{}; - std::atomic shutdown_{}; + std::atomic shutdown_{false}; // Test only. friend class ThreadLocalInstanceImplTest; diff --git a/source/common/tls/context_impl.h b/source/common/tls/context_impl.h index 2db772e212d9d..71b9c689aff7b 100644 --- a/source/common/tls/context_impl.h +++ b/source/common/tls/context_impl.h @@ -53,7 +53,7 @@ struct TlsContext { // for "not an ECDSA context". CurveNID ec_group_curve_name_ = EC_CURVE_INVALID_NID; bool is_must_staple_{}; - Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_{}; + Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_; #ifdef ENVOY_ENABLE_QUIC quiche::QuicheReferenceCountedPointer quic_cert_; diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 3e7d5f9f78f41..c2662a041c261 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -781,8 +781,7 @@ class ClusterManagerImpl : public ClusterManager, : cluster_config_(cluster_config), config_hash_(cluster_config_hash), version_info_(version_info), cluster_(std::move(cluster)), last_updated_(time_source.systemTime()), added_via_api_(added_via_api), - avoid_cds_removal_(avoid_cds_removal), added_or_updated_{}, - required_for_ads_(required_for_ads) {} + avoid_cds_removal_(avoid_cds_removal), required_for_ads_(required_for_ads) {} bool blockUpdate(uint64_t hash) { return !added_via_api_ || config_hash_ == hash; } @@ -816,7 +815,7 @@ class ClusterManagerImpl : public ClusterManager, // Keep smaller fields near the end to reduce padding const bool added_via_api_ : 1; const bool avoid_cds_removal_ : 1; - bool added_or_updated_ : 1; + bool added_or_updated_ : 1 = false; const bool required_for_ads_ : 1; }; diff --git a/source/common/upstream/health_discovery_service.h b/source/common/upstream/health_discovery_service.h index 8d3cd54fe19fb..5b8e032efb6cd 100644 --- a/source/common/upstream/health_discovery_service.h +++ b/source/common/upstream/health_discovery_service.h @@ -174,8 +174,7 @@ class HdsDelegate : Grpc::AsyncStreamCallbacks async_client_; - Grpc::AsyncStream - stream_{}; + Grpc::AsyncStream stream_; Event::Dispatcher& dispatcher_; Server::Configuration::ServerFactoryContext& server_context_; Envoy::Stats::Store& store_stats_; diff --git a/source/common/upstream/load_stats_reporter_impl.h b/source/common/upstream/load_stats_reporter_impl.h index 35d90fe5dd097..b151ac7df9ac2 100644 --- a/source/common/upstream/load_stats_reporter_impl.h +++ b/source/common/upstream/load_stats_reporter_impl.h @@ -75,7 +75,7 @@ class LoadStatsReporterImpl Grpc::AsyncClient async_client_; - Grpc::AsyncStream stream_{}; + Grpc::AsyncStream stream_; const Protobuf::MethodDescriptor& service_method_; Event::TimerPtr retry_timer_; Event::TimerPtr response_timer_; diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 8a4b1fffe8394..3572b1cf32033 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -138,7 +138,7 @@ class DetectorHostMonitorNullImpl : public Outlier::DetectorHostMonitor { double successRate(SuccessRateMonitorType) const override { return -1; } private: - const absl::optional time_{}; + const absl::optional time_; }; /** @@ -479,13 +479,14 @@ class HostImplBase : public Host, void setEdsHealthFlag(envoy::config::core::v3::HealthStatus health_status); - std::atomic health_flags_{}; + std::atomic health_flags_{0}; std::atomic weight_; bool disable_active_health_check_; // TODO(wbpcode): should we store the EDS health status to health_flags_ to get unified status or // flag access? May be we could refactor HealthFlag to contain all these statuses and flags in the // future. - std::atomic eds_health_status_{}; + std::atomic eds_health_status_{ + envoy::config::core::v3::HealthStatus::UNKNOWN}; // 0 indicates no status has been set. std::atomic last_hc_http_status_{0}; @@ -501,7 +502,7 @@ class HostImplBase : public Host, } const std::weak_ptr parent_; }; - mutable std::atomic handle_count_{}; + mutable std::atomic handle_count_{0}; }; class HostImpl : public HostImplBase, public HostDescriptionImpl { @@ -685,7 +686,7 @@ using HostSetImplPtr = std::unique_ptr; */ class PrioritySetImpl : public PrioritySet { public: - PrioritySetImpl() : batch_update_(false) {} + PrioritySetImpl() = default; // From PrioritySet ABSL_MUST_USE_RESULT Common::CallbackHandlePtr addMemberUpdateCb(MemberUpdateCb callback) const override { @@ -750,7 +751,7 @@ class PrioritySetImpl : public PrioritySet { member_update_cb_helper_; mutable Common::CallbackManager priority_update_cb_helper_; - bool batch_update_ : 1; + bool batch_update_ : 1 = false; // Helper class to maintain state as we perform multiple host updates. Keeps track of all hosts // that have been added/removed throughout the batch update, and ensures that we properly manage @@ -1251,7 +1252,7 @@ class ClusterImplBase : public Cluster, protected Logger::Loggable config_; std::weak_ptr limiter_; const Config::ResourceTypeHelper resource_type_helper_; - size_t token_bucket_config_hash_; + size_t token_bucket_config_hash_{0}; }; Server::Configuration::ServerFactoryContext& factory_context_; diff --git a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h index ec097a8041b91..9a841e08f08ab 100644 --- a/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h +++ b/source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle.h @@ -94,10 +94,10 @@ struct ReverseConnectionSocketConfig { // multiple remote clusters in the same ReverseConnectionAddress and therefore should be able // to use a single ReverseConnectionIOHandle for multiple remote clusters. std::vector - remote_clusters; // List of remote cluster configurations. - bool enable_circuit_breaker; // Whether to place a cluster in backoff when reverse connection - // attempts fail. - ReverseConnectionSocketConfig() : enable_circuit_breaker(true) {} + remote_clusters; // List of remote cluster configurations. + bool enable_circuit_breaker{true}; // Whether to place a cluster in backoff when reverse + // connection attempts fail. + ReverseConnectionSocketConfig() = default; }; /** diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.cc b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.cc index ffa9c781e38f5..8bb380bd57183 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.cc +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.cc @@ -17,7 +17,7 @@ namespace ReverseConnection { // ReverseTunnelAcceptor implementation ReverseTunnelAcceptor::ReverseTunnelAcceptor(Server::Configuration::ServerFactoryContext& context) - : extension_(nullptr), context_(&context) { + : context_(&context) { ENVOY_LOG(debug, "reverse_tunnel: created acceptor"); } diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h index b5437525439c2..64711ca549617 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h @@ -43,7 +43,7 @@ class ReverseTunnelAcceptor : public Envoy::Network::SocketInterfaceBase, */ ReverseTunnelAcceptor(Server::Configuration::ServerFactoryContext& context); - ReverseTunnelAcceptor() : extension_(nullptr), context_(nullptr) {} + ReverseTunnelAcceptor() = default; // SocketInterface overrides /** @@ -113,7 +113,7 @@ class ReverseTunnelAcceptor : public Envoy::Network::SocketInterfaceBase, ReverseTunnelAcceptorExtension* extension_{nullptr}; private: - Server::Configuration::ServerFactoryContext* context_; + Server::Configuration::ServerFactoryContext* context_{nullptr}; }; DECLARE_FACTORY(ReverseTunnelAcceptor); diff --git a/source/extensions/clusters/dynamic_modules/cluster.cc b/source/extensions/clusters/dynamic_modules/cluster.cc index ed40ae8df9fc3..dd90e7867b8ea 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.cc +++ b/source/extensions/clusters/dynamic_modules/cluster.cc @@ -190,7 +190,6 @@ DynamicModuleCluster::DynamicModuleCluster(const envoy::config::cluster::v3::Clu Upstream::ClusterFactoryContext& context, absl::Status& creation_status) : ClusterImplBase(cluster, context, creation_status), config_(std::move(config)), - in_module_cluster_(nullptr), dispatcher_(context.serverFactoryContext().mainThreadDispatcher()), server_context_(context.serverFactoryContext()) { @@ -642,7 +641,7 @@ bool DynamicModuleLoadBalancer::withActiveInstance( DynamicModuleLoadBalancer::DynamicModuleLoadBalancer( const DynamicModuleClusterHandleSharedPtr& handle, const Upstream::PrioritySet& priority_set) - : handle_(handle), priority_set_(priority_set), in_module_lb_(nullptr) { + : handle_(handle), priority_set_(priority_set) { // Register before invoking any module hook so a concurrent async host selection completion can // validate its raw pointer against a live instance. registerActiveDynamicModuleLoadBalancer(this); diff --git a/source/extensions/clusters/dynamic_modules/cluster.h b/source/extensions/clusters/dynamic_modules/cluster.h index 6a4a30a5096bb..2f8443d5d199d 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.h +++ b/source/extensions/clusters/dynamic_modules/cluster.h @@ -406,7 +406,7 @@ class DynamicModuleCluster : public Upstream::ClusterImplBase, uint64_t getNextCalloutId() { return next_callout_id_++; } DynamicModuleClusterConfigSharedPtr config_; - envoy_dynamic_module_type_cluster_module_ptr in_module_cluster_; + envoy_dynamic_module_type_cluster_module_ptr in_module_cluster_{nullptr}; Event::Dispatcher& dispatcher_; Server::Configuration::ServerFactoryContext& server_context_; @@ -484,7 +484,7 @@ class DynamicModuleAsyncHostSelectionHandle : public Upstream::AsyncHostSelectio private: envoy_dynamic_module_type_cluster_lb_async_handle_module_ptr async_handle_; - envoy_dynamic_module_type_cluster_lb_module_ptr in_module_lb_; + envoy_dynamic_module_type_cluster_lb_module_ptr in_module_lb_{nullptr}; OnClusterLbCancelHostSelectionType cancel_fn_; std::shared_ptr> cancelled_; }; diff --git a/source/extensions/common/dubbo/codec.h b/source/extensions/common/dubbo/codec.h index e177c642b18e4..4bff08048a2fb 100644 --- a/source/extensions/common/dubbo/codec.h +++ b/source/extensions/common/dubbo/codec.h @@ -40,7 +40,7 @@ class DubboCodec { static constexpr int32_t MaxBodySize = 16 * 1024 * 1024; private: - SerializerPtr serializer_{}; + SerializerPtr serializer_; }; class DirectResponseUtil { diff --git a/source/extensions/common/dubbo/message.h b/source/extensions/common/dubbo/message.h index 5808e496a5cdb..626fbae107370 100644 --- a/source/extensions/common/dubbo/message.h +++ b/source/extensions/common/dubbo/message.h @@ -252,7 +252,7 @@ class RpcResponse { ResponseContent& content() const { return content_; } private: - absl::optional response_type_{}; + absl::optional response_type_; mutable ResponseContent content_; }; diff --git a/source/extensions/common/dubbo/metadata.h b/source/extensions/common/dubbo/metadata.h index 6c2790c0ce8ff..129d251970d04 100644 --- a/source/extensions/common/dubbo/metadata.h +++ b/source/extensions/common/dubbo/metadata.h @@ -46,7 +46,7 @@ class Context { private: MessageType message_type_{MessageType::Request}; - absl::optional response_status_{}; + absl::optional response_status_; int64_t request_id_{}; size_t body_size_{}; diff --git a/source/extensions/common/matcher/matcher.h b/source/extensions/common/matcher/matcher.h index d3dce44dcb3aa..b05f8164ca724 100644 --- a/source/extensions/common/matcher/matcher.h +++ b/source/extensions/common/matcher/matcher.h @@ -53,7 +53,7 @@ class Matcher { bool matches_{false}; // Does the matcher currently match? bool might_change_status_{true}; // Is it possible for matches_ to change in subsequent updates? - std::unique_ptr ctx_{}; // Context used by matchers to save interim context. + std::unique_ptr ctx_; // Context used by matchers to save interim context. }; using MatchStatusVector = std::vector; diff --git a/source/extensions/common/redis/cluster_refresh_manager_impl.h b/source/extensions/common/redis/cluster_refresh_manager_impl.h index 94bbfc315db65..c3d53a5cef59b 100644 --- a/source/extensions/common/redis/cluster_refresh_manager_impl.h +++ b/source/extensions/common/redis/cluster_refresh_manager_impl.h @@ -35,10 +35,10 @@ class ClusterRefreshManagerImpl : public ClusterRefreshManager, redirects_threshold_(redirects_threshold), failure_threshold_(failure_threshold), host_degraded_threshold_(host_degraded_threshold), cb_(std::move(cb)) {} std::string cluster_name_; - std::atomic last_callback_time_ms_{}; - std::atomic redirects_count_{}; - std::atomic failures_count_{}; - std::atomic host_degraded_count_{}; + std::atomic last_callback_time_ms_{0}; + std::atomic redirects_count_{0}; + std::atomic failures_count_{0}; + std::atomic host_degraded_count_{0}; std::chrono::milliseconds min_time_between_triggering_; const uint32_t redirects_threshold_; const uint32_t failure_threshold_; diff --git a/source/extensions/common/wasm/foreign.cc b/source/extensions/common/wasm/foreign.cc index 1d612f4cace14..58931b5d7c08b 100644 --- a/source/extensions/common/wasm/foreign.cc +++ b/source/extensions/common/wasm/foreign.cc @@ -262,7 +262,7 @@ class ExpressionFactory : public Logger::Loggable { const Filters::Common::Expr::Builder* builder() const { return builder_.get(); } private: - const Filters::Common::Expr::BuilderConstPtr builder_{}; + const Filters::Common::Expr::BuilderConstPtr builder_; uint32_t next_expr_token_ = 0; absl::flat_hash_map expr_; }; diff --git a/source/extensions/common/wasm/plugin.h b/source/extensions/common/wasm/plugin.h index 74bf96290af88..0ec0201a41413 100644 --- a/source/extensions/common/wasm/plugin.h +++ b/source/extensions/common/wasm/plugin.h @@ -29,7 +29,7 @@ class WasmConfig { private: const envoy::extensions::wasm::v3::PluginConfig config_; - proxy_wasm::AllowedCapabilitiesMap allowed_capabilities_{}; + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities_; EnvironmentVariableMap envs_; }; diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index 25d648e3810bc..056f0d21a0572 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -154,8 +154,8 @@ using PluginHandleSharedPtr = std::shared_ptr; class PluginHandleSharedPtrThreadLocal : public ThreadLocal::ThreadLocalObject { public: - PluginHandleSharedPtr handle{}; - MonotonicTime last_load{}; + PluginHandleSharedPtr handle; + MonotonicTime last_load; PluginHandleSharedPtrThreadLocal(PluginHandleSharedPtr h, MonotonicTime t = {}) : handle(std::move(h)), last_load(t) {} @@ -224,7 +224,7 @@ class PluginConfig : Logger::Loggable { PluginSharedPtr plugin_; RemoteAsyncDataProviderPtr remote_data_provider_; const bool is_singleton_handle_{}; - WasmHandleSharedPtr base_wasm_{}; + WasmHandleSharedPtr base_wasm_; absl::variant plugin_handle_; }; diff --git a/source/extensions/config_subscription/grpc/grpc_mux_failover.h b/source/extensions/config_subscription/grpc/grpc_mux_failover.h index fb971ac460c45..42756c48eee1f 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_failover.h +++ b/source/extensions/config_subscription/grpc/grpc_mux_failover.h @@ -70,8 +70,7 @@ class GrpcMuxFailover : public GrpcStreamInterface, Event::Dispatcher& dispatcher) : grpc_mux_callbacks_(grpc_mux_callbacks), primary_callbacks_(*this), primary_grpc_stream_(std::move(primary_stream_creator(&primary_callbacks_))), - connection_state_(ConnectionState::None), ever_connected_to_primary_(false), - previously_connected_to_(ConnectedTo::None) { + connection_state_(ConnectionState::None), previously_connected_to_(ConnectedTo::None) { ASSERT(primary_grpc_stream_ != nullptr); if (failover_stream_creator.has_value()) { ENVOY_LOG(warn, "Using xDS-Failover. Note that the implementation is currently considered " diff --git a/source/extensions/config_subscription/grpc/grpc_mux_impl.h b/source/extensions/config_subscription/grpc/grpc_mux_impl.h index c2d3d11653eaf..b9da5b3c93657 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_impl.h +++ b/source/extensions/config_subscription/grpc/grpc_mux_impl.h @@ -261,7 +261,7 @@ class GrpcMuxImpl : public GrpcMux, TtlManager ttl_; // The identifier for the server that sent the most recent response, or // empty if there is none. - std::string control_plane_identifier_{}; + std::string control_plane_identifier_; // If true, xDS resources were previously fetched from an xDS source or an xDS delegate. bool previously_fetched_data_{false}; }; diff --git a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h index 6adb1dda36515..439a4bce09163 100644 --- a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h +++ b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.h @@ -117,7 +117,7 @@ class NewGrpcMuxImpl WatchMap watch_map_; DeltaSubscriptionState sub_state_; - std::string control_plane_identifier_{}; + std::string control_plane_identifier_; SubscriptionStuff(const SubscriptionStuff&) = delete; SubscriptionStuff& operator=(const SubscriptionStuff&) = delete; diff --git a/source/extensions/config_subscription/grpc/xds_mux/subscription_state.h b/source/extensions/config_subscription/grpc/xds_mux/subscription_state.h index 2265a53b5341f..dedadf4f5f22a 100644 --- a/source/extensions/config_subscription/grpc/xds_mux/subscription_state.h +++ b/source/extensions/config_subscription/grpc/xds_mux/subscription_state.h @@ -130,7 +130,7 @@ class BaseSubscriptionState : public SubscriptionState, UntypedConfigUpdateCallbacks& callbacks_; Event::Dispatcher& dispatcher_; bool dynamic_context_changed_{}; - std::string control_plane_identifier_{}; + std::string control_plane_identifier_; XdsConfigTrackerOptRef xds_config_tracker_; XdsResourcesDelegateOptRef xds_resources_delegate_; const std::string target_xds_authority_; diff --git a/source/extensions/filters/common/expr/cel_state.h b/source/extensions/filters/common/expr/cel_state.h index 9509ef19f3dea..0a322d9462c69 100644 --- a/source/extensions/filters/common/expr/cel_state.h +++ b/source/extensions/filters/common/expr/cel_state.h @@ -82,7 +82,7 @@ class CelState : public StreamInfo::FilterState::Object { const bool readonly_; const CelStateType type_; absl::string_view schema_; - std::string value_{}; + std::string value_; bool initialized_{false}; }; diff --git a/source/extensions/filters/common/ext_authz/ext_authz.h b/source/extensions/filters/common/ext_authz/ext_authz.h index 208cbe12b6973..8b26d401e62d1 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz.h +++ b/source/extensions/filters/common/ext_authz/ext_authz.h @@ -86,49 +86,49 @@ using UnsafeHeaderVector = std::vector; */ struct Response { // Call status. - CheckStatus status; + CheckStatus status = CheckStatus::OK; // A set of HTTP headers returned by the authorization server, that will be optionally appended // to the request to the upstream server. - UnsafeHeaderVector headers_to_append{}; + UnsafeHeaderVector headers_to_append; // A set of HTTP headers returned by the authorization server, will be optionally set // (using "setCopy") to the request to the upstream server. - UnsafeHeaderVector headers_to_set{}; + UnsafeHeaderVector headers_to_set; // A set of HTTP headers returned by the authorization server, will be optionally added // (using "addCopy") to the request to the upstream server. - UnsafeHeaderVector headers_to_add{}; + UnsafeHeaderVector headers_to_add; // A set of HTTP headers returned by the authorization server, will be optionally added // (using "addCopy") to the response sent back to the downstream client on OK auth // responses. - UnsafeHeaderVector response_headers_to_add{}; + UnsafeHeaderVector response_headers_to_add; // A set of HTTP headers returned by the authorization server, will be optionally set (using // "setCopy") to the response sent back to the downstream client on OK auth responses. - UnsafeHeaderVector response_headers_to_set{}; + UnsafeHeaderVector response_headers_to_set; // A set of HTTP headers returned by the authorization server, will be optionally added // (using "addCopy") to the response sent back to the downstream client on OK auth // responses only if the headers were not returned from the authz server. - UnsafeHeaderVector response_headers_to_add_if_absent{}; + UnsafeHeaderVector response_headers_to_add_if_absent; // A set of HTTP headers returned by the authorization server, will be optionally set (using // "setCopy") to the response sent back to the downstream client on OK auth responses // only if the headers were returned from the authz server. - UnsafeHeaderVector response_headers_to_overwrite_if_exists{}; + UnsafeHeaderVector response_headers_to_overwrite_if_exists; // Whether the authorization server returned any headers with an invalid append action type. - bool saw_invalid_append_actions{false}; + bool saw_invalid_append_actions = false; // A set of HTTP headers consumed by the authorization server, will be removed // from the request to the upstream server. - std::vector headers_to_remove{}; + std::vector headers_to_remove; // A set of query string parameters to be set (possibly overwritten) on the // request to the upstream server. - Http::Utility::QueryParamsVector query_parameters_to_set{}; + Http::Utility::QueryParamsVector query_parameters_to_set; // A set of query string parameters to remove from the request to the upstream server. - std::vector query_parameters_to_remove{}; + std::vector query_parameters_to_remove; // Optional http body used only on denied response. - std::string body{}; + std::string body; // Optional http status used only on denied response. Http::Code status_code{}; // A set of metadata returned by the authorization server, that will be emitted as filter's // dynamic metadata that other filters can leverage. - Protobuf::Struct dynamic_metadata{}; + Protobuf::Struct dynamic_metadata; // The gRPC status returned by the authorization server when it is making a // gRPC call. diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc index e8e9d07795a9c..0284a0c7d97ab 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc @@ -33,6 +33,11 @@ const Http::HeaderMap& lengthZeroHeader() { return *headers; } +Http::Code zeroHttpCode() { + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + return static_cast(0); +} + // Static response used for creating authorization ERROR responses. // Note: status_code is left unset so the filter can use the configured status_on_error // configuration. @@ -50,7 +55,7 @@ const Response& errorResponse() { Http::Utility::QueryParamsVector{}, {}, EMPTY_STRING, - static_cast(0), + zeroHttpCode(), Protobuf::Struct{}}); } @@ -70,7 +75,7 @@ struct SuccessResponse { : headers_(headers), matchers_(matchers), append_matchers_(append_matchers), response_matchers_(response_matchers), to_dynamic_metadata_matchers_(dynamic_metadata_matchers), - response_(std::make_unique(response)) { + response_(std::make_unique(std::move(response))) { headers_.iterate([this](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { // UpstreamHeaderMatcher if (matchers_->matches(header.key().getStringView())) { diff --git a/source/extensions/filters/http/api_key_auth/api_key_auth.h b/source/extensions/filters/http/api_key_auth/api_key_auth.h index de8b974727473..ec5a25acda74f 100644 --- a/source/extensions/filters/http/api_key_auth/api_key_auth.h +++ b/source/extensions/filters/http/api_key_auth/api_key_auth.h @@ -189,7 +189,7 @@ class RouteConfig : public Router::RouteSpecificFilterConfig { struct AuthResult { bool authenticated{}; bool authorized{}; - absl::string_view response_code_details{}; + absl::string_view response_code_details; }; class FilterConfig { diff --git a/source/extensions/filters/http/bandwidth_share/fair_token_bucket_impl.h b/source/extensions/filters/http/bandwidth_share/fair_token_bucket_impl.h index 993eb11a839f1..b0eebd9006300 100644 --- a/source/extensions/filters/http/bandwidth_share/fair_token_bucket_impl.h +++ b/source/extensions/filters/http/bandwidth_share/fair_token_bucket_impl.h @@ -158,7 +158,7 @@ class Bucket { // The empty_at_ value is used to store how many tokens are in the bucket, // for example if empty_at_ is 100ms in the past, then there is 1/10 of // max_tokens_, or if 1000ms or more, then there is max_tokens_. - MonotonicTime empty_at_ ABSL_GUARDED_BY(mutex_){}; + MonotonicTime empty_at_ ABSL_GUARDED_BY(mutex_); absl::flat_hash_set active_tenants_ ABSL_GUARDED_BY(mutex_); std::vector> draining_tenants_ ABSL_GUARDED_BY(mutex_); uint64_t active_tenants_total_weight_ = 0; diff --git a/source/extensions/filters/http/decompressor/decompressor_filter.h b/source/extensions/filters/http/decompressor/decompressor_filter.h index d67a5ea7bf8e2..6fd697cd0689f 100644 --- a/source/extensions/filters/http/decompressor/decompressor_filter.h +++ b/source/extensions/filters/http/decompressor/decompressor_filter.h @@ -238,8 +238,8 @@ class DecompressorFilter : public Http::PassThroughFilter, } DecompressorFilterConfigSharedPtr config_; - Compression::Decompressor::DecompressorPtr request_decompressor_{}; - Compression::Decompressor::DecompressorPtr response_decompressor_{}; + Compression::Decompressor::DecompressorPtr request_decompressor_; + Compression::Decompressor::DecompressorPtr response_decompressor_; ByteTracker request_byte_tracker_; ByteTracker response_byte_tracker_; }; diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index 404093d2f704f..f6b5ce114cb04 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -75,6 +75,11 @@ bool headersWithinLimits(const Http::HeaderMap& headers) { headers.byteSize() <= headers.maxHeadersKb() * 1024; } +Http::Code zeroHttpCode() { + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + return static_cast(0); +} + } // namespace FilterConfig::FilterConfig(const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& config, @@ -1111,7 +1116,7 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { } else { // Use custom status code from error_response if provided, otherwise use status_on_error. // Status code 0 means not set. - const Http::Code status_code = response->status_code != static_cast(0) + const Http::Code status_code = response->status_code != zeroHttpCode() ? response->status_code : config_->statusOnError(); ENVOY_STREAM_LOG( @@ -1222,7 +1227,7 @@ bool Filter::validateAndClearInvalidErrorResponseAttributes( response->headers_to_set.clear(); response->headers_to_append.clear(); response->body.clear(); - response->status_code = static_cast(0); // Clear custom status. + response->status_code = zeroHttpCode(); // Clear custom status. return false; } } @@ -1241,7 +1246,7 @@ bool Filter::validateAndClearInvalidErrorResponseAttributes( response->headers_to_set.clear(); response->headers_to_append.clear(); response->body.clear(); - response->status_code = static_cast(0); // Clear custom status. + response->status_code = zeroHttpCode(); // Clear custom status. return false; } } @@ -1291,7 +1296,7 @@ void Filter::addErrorResponseHeaders( void ShadowDecisionObject::populateProto(ShadowDecisionProto& msg) const { msg.set_check_result(check_result_); - if (status_code_ != static_cast(0)) { + if (status_code_ != zeroHttpCode()) { msg.set_status_code(static_cast(status_code_)); } for (const auto& [key, value] : response_headers_) { @@ -1318,7 +1323,7 @@ void Filter::setShadowFilterState(Filters::Common::ExtAuthz::Response& response) using ShadowDecisionProto = envoy::extensions::filters::http::ext_authz::v3::ShadowDecision; ShadowDecisionProto::CheckResult check_result = ShadowDecisionProto::UNSPECIFIED; - Http::Code status_code = static_cast(0); + Http::Code status_code{}; Filters::Common::ExtAuthz::UnsafeHeaderVector response_headers; switch (response.status) { @@ -1334,8 +1339,8 @@ void Filter::setShadowFilterState(Filters::Common::ExtAuthz::Response& response) break; case CheckStatus::Error: check_result = ShadowDecisionProto::ERROR; - status_code = response.status_code != static_cast(0) ? response.status_code - : config_->statusOnError(); + status_code = + response.status_code != zeroHttpCode() ? response.status_code : config_->statusOnError(); stats_.shadow_error_.inc(); break; default: diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index 0d937d1033a1b..b545d0d8a8cae 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -561,10 +561,10 @@ class Filter : public Logger::Loggable, Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; Http::StreamEncoderFilterCallbacks* encoder_callbacks_{}; Http::RequestHeaderMap* request_headers_; - Http::HeaderVector response_headers_to_add_{}; - Http::HeaderVector response_headers_to_set_{}; - Http::HeaderVector response_headers_to_add_if_absent_{}; - Http::HeaderVector response_headers_to_overwrite_if_exists_{}; + Http::HeaderVector response_headers_to_add_; + Http::HeaderVector response_headers_to_set_; + Http::HeaderVector response_headers_to_add_if_absent_; + Http::HeaderVector response_headers_to_overwrite_if_exists_; State state_{State::NotStarted}; FilterReturn filter_return_{FilterReturn::ContinueDecoding}; Upstream::ClusterInfoConstSharedPtr cluster_; @@ -580,7 +580,7 @@ class Filter : public Logger::Loggable, bool initiating_call_{}; bool buffer_data_{}; bool skip_check_{false}; - envoy::service::auth::v3::CheckRequest check_request_{}; + envoy::service::auth::v3::CheckRequest check_request_; }; } // namespace ExtAuthz diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 87f9b35eb4e23..8c6e42bd41e55 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -27,7 +27,6 @@ namespace HttpFilters { namespace ExternalProcessing { namespace { -using envoy::config::common::mutation_rules::v3::HeaderMutationRules; using envoy::config::core::v3::TrafficDirection; using envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor; using envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute; @@ -38,7 +37,6 @@ using envoy::service::ext_proc::v3::ImmediateResponse; using envoy::service::ext_proc::v3::ProcessingRequest; using envoy::service::ext_proc::v3::ProcessingResponse; -using Filters::Common::MutationRules::Checker; using Filters::Common::ProcessingEffect::Effect; using Http::FilterDataStatus; using Http::FilterHeadersStatus; @@ -261,25 +259,7 @@ FilterConfig::FilterConfig(const ExternalProcessor& config, const std::string& stats_prefix, bool is_upstream, Extensions::Filters::Common::Expr::BuilderInstanceSharedConstPtr builder, Server::Configuration::CommonFactoryContext& context) - : failure_mode_allow_(config.failure_mode_allow()), - observability_mode_(config.observability_mode()), - route_cache_action_(config.route_cache_action()), - deferred_close_timeout_(PROTOBUF_GET_MS_OR_DEFAULT(config, deferred_close_timeout, - DEFAULT_DEFERRED_CLOSE_TIMEOUT_MS)), - message_timeout_(message_timeout), max_message_timeout_ms_(max_message_timeout_ms), - grpc_service_(getFilterGrpcService(config)), - send_body_without_waiting_for_header_response_( - config.send_body_without_waiting_for_header_response()), - stats_(generateStats(stats_prefix, config.stat_prefix(), scope)), - processing_mode_(config.processing_mode()), - mutation_checker_(config.mutation_rules(), context.regexEngine()), - filter_metadata_(config.filter_metadata()), - allow_mode_override_(config.allow_mode_override()), - disable_immediate_response_(config.disable_immediate_response()), - allowed_headers_(initHeaderMatchers(config.forward_rules().allowed_headers(), context)), - disallowed_headers_(initHeaderMatchers(config.forward_rules().disallowed_headers(), context)), - is_upstream_(is_upstream), graceful_grpc_close_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.ext_proc_graceful_grpc_close")), + : stats_(generateStats(stats_prefix, config.stat_prefix(), scope)), untyped_forwarding_namespaces_( config.metadata_options().forwarding_namespaces().untyped().begin(), config.metadata_options().forwarding_namespaces().untyped().end()), @@ -295,7 +275,12 @@ FilterConfig::FilterConfig(const ExternalProcessor& config, typed_cluster_metadata_forwarding_namespaces_( config.metadata_options().cluster_metadata_forwarding_namespaces().typed().begin(), config.metadata_options().cluster_metadata_forwarding_namespaces().typed().end()), + allowed_headers_(initHeaderMatchers(config.forward_rules().allowed_headers(), context)), + disallowed_headers_(initHeaderMatchers(config.forward_rules().disallowed_headers(), context)), allowed_override_modes_(config.allowed_override_modes()), + grpc_service_(getFilterGrpcService(config)), + mutation_checker_(config.mutation_rules(), context.regexEngine()), + filter_metadata_(config.filter_metadata()), expression_manager_(builder, context.localInfo(), config.request_attributes(), config.response_attributes()), processing_request_modifier_factory_cb_( @@ -303,9 +288,22 @@ FilterConfig::FilterConfig(const ExternalProcessor& config, on_processing_response_factory_cb_( createOnProcessingResponseCb(config, context, stats_prefix)), thread_local_stream_manager_slot_(context.threadLocal().allocateSlot()), + route_cache_action_(config.route_cache_action()), processing_mode_(config.processing_mode()), + deferred_close_timeout_(PROTOBUF_GET_MS_OR_DEFAULT(config, deferred_close_timeout, + DEFAULT_DEFERRED_CLOSE_TIMEOUT_MS)), + message_timeout_(message_timeout), remote_close_timeout_(context.runtime().snapshot().getInteger( RemoteCloseTimeout, DefaultRemoteCloseTimeoutMilliseconds)), + max_message_timeout_ms_(max_message_timeout_ms), status_on_error_(toErrorCode(config.status_on_error().code())), + failure_mode_allow_(config.failure_mode_allow()), + observability_mode_(config.observability_mode()), + send_body_without_waiting_for_header_response_( + config.send_body_without_waiting_for_header_response()), + allow_mode_override_(config.allow_mode_override()), + disable_immediate_response_(config.disable_immediate_response()), is_upstream_(is_upstream), + graceful_grpc_close_( + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.ext_proc_graceful_grpc_close")), allow_content_length_header_(config.allow_content_length_header()) { if (config.disable_clear_route_cache()) { route_cache_action_ = ExternalProcessor::RETAIN; diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index 342fab098465f..af665ca33ea19 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -365,38 +365,24 @@ class FilterConfig { const envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor& config, Extensions::Filters::Common::Expr::BuilderInstanceSharedConstPtr builder, Server::Configuration::CommonFactoryContext& context); - const bool failure_mode_allow_; - const bool observability_mode_; - envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor::RouteCacheAction - route_cache_action_; - const std::chrono::milliseconds deferred_close_timeout_; - const std::chrono::milliseconds message_timeout_; - const uint32_t max_message_timeout_ms_; - const absl::optional grpc_service_; - const bool send_body_without_waiting_for_header_response_; ExtProcFilterStats stats_; - const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode processing_mode_; - const Filters::Common::MutationRules::Checker mutation_checker_; - const Protobuf::Struct filter_metadata_; - // If set to true, allow the processing mode to be modified by the ext_proc response. - const bool allow_mode_override_; - // If set to true, disable the immediate response from the ext_proc server, which means - // closing the stream to the ext_proc server, and no more external processing. - const bool disable_immediate_response_; - // Empty allowed_header_ means allow all. - const std::vector allowed_headers_; - // Empty disallowed_header_ means disallow nothing, i.e, allow all. - const std::vector disallowed_headers_; - // is_upstream_ is true if ext_proc filter is in the upstream filter chain. - const bool is_upstream_; - const bool graceful_grpc_close_; + const std::vector untyped_forwarding_namespaces_; const std::vector typed_forwarding_namespaces_; const std::vector untyped_receiving_namespaces_; const std::vector untyped_cluster_metadata_forwarding_namespaces_; const std::vector typed_cluster_metadata_forwarding_namespaces_; + // Empty allowed_header_ means allow all. + const std::vector allowed_headers_; + // Empty disallowed_header_ means disallow nothing, i.e, allow all. + const std::vector disallowed_headers_; + const AllowedOverrideModesSet allowed_override_modes_; + + const absl::optional grpc_service_; + const Filters::Common::MutationRules::Checker mutation_checker_; + const Protobuf::Struct filter_metadata_; const ExpressionManager expression_manager_; const std::function()> @@ -404,9 +390,29 @@ class FilterConfig { const std::function()> on_processing_response_factory_cb_; ThreadLocal::SlotPtr thread_local_stream_manager_slot_; + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor::RouteCacheAction + route_cache_action_; + const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode processing_mode_; + const std::chrono::milliseconds deferred_close_timeout_; + const std::chrono::milliseconds message_timeout_; const std::chrono::milliseconds remote_close_timeout_; - const Http::Code status_on_error_; - const bool allow_content_length_header_; + const uint32_t max_message_timeout_ms_ = 0; + + const Http::Code status_on_error_{}; + + const bool failure_mode_allow_ = false; + const bool observability_mode_ = false; + const bool send_body_without_waiting_for_header_response_ = false; + // If set to true, allow the processing mode to be modified by the ext_proc response. + const bool allow_mode_override_ = false; + // If set to true, disable the immediate response from the ext_proc server, which means + // closing the stream to the ext_proc server, and no more external processing. + const bool disable_immediate_response_ = false; + // is_upstream_ is true if ext_proc filter is in the upstream filter chain. + const bool is_upstream_ = false; + const bool graceful_grpc_close_ = false; + + const bool allow_content_length_header_ = false; }; using FilterConfigSharedPtr = std::shared_ptr; @@ -681,11 +687,11 @@ class Filter : public Logger::Loggable, DecodingProcessorState decoding_state_; EncodingProcessorState encoding_state_; - std::vector untyped_forwarding_namespaces_{}; - std::vector typed_forwarding_namespaces_{}; - std::vector untyped_receiving_namespaces_{}; - std::vector untyped_cluster_metadata_forwarding_namespaces_{}; - std::vector typed_cluster_metadata_forwarding_namespaces_{}; + std::vector untyped_forwarding_namespaces_; + std::vector typed_forwarding_namespaces_; + std::vector untyped_receiving_namespaces_; + std::vector untyped_cluster_metadata_forwarding_namespaces_; + std::vector typed_cluster_metadata_forwarding_namespaces_; Http::StreamFilterCallbacks* filter_callbacks_; Http::StreamFilterSidestreamWatermarkCallbacks watermark_callbacks_; diff --git a/source/extensions/filters/http/fault/fault_filter.h b/source/extensions/filters/http/fault/fault_filter.h index 1189682daa5d9..9699b44f622b4 100644 --- a/source/extensions/filters/http/fault/fault_filter.h +++ b/source/extensions/filters/http/fault/fault_filter.h @@ -98,7 +98,7 @@ class FaultSettings : public Router::RouteSpecificFilterConfig { Filters::Common::Fault::FaultAbortConfigPtr request_abort_config_; std::string upstream_cluster_; // restrict faults to specific upstream cluster const std::vector fault_filter_headers_; - absl::flat_hash_set downstream_nodes_{}; // Inject failures for specific downstream + absl::flat_hash_set downstream_nodes_; // Inject failures for specific downstream absl::optional max_active_faults_; Filters::Common::Fault::FaultRateLimitConfigPtr response_rate_limit_; @@ -223,16 +223,16 @@ class FaultFilter : public Http::StreamFilter, Logger::Loggable downstream_cluster_storage_; const FaultSettings* fault_settings_; bool fault_active_{}; std::unique_ptr response_limiter_; - std::string downstream_cluster_delay_percent_key_{}; - std::string downstream_cluster_abort_percent_key_{}; - std::string downstream_cluster_delay_duration_key_{}; - std::string downstream_cluster_abort_http_status_key_{}; - std::string downstream_cluster_abort_grpc_status_key_{}; + std::string downstream_cluster_delay_percent_key_; + std::string downstream_cluster_abort_percent_key_; + std::string downstream_cluster_delay_duration_key_; + std::string downstream_cluster_abort_http_status_key_; + std::string downstream_cluster_abort_grpc_status_key_; }; } // namespace Fault diff --git a/source/extensions/filters/http/grpc_http1_reverse_bridge/filter.h b/source/extensions/filters/http/grpc_http1_reverse_bridge/filter.h index 723dba1f90962..430ca917aca84 100644 --- a/source/extensions/filters/http/grpc_http1_reverse_bridge/filter.h +++ b/source/extensions/filters/http/grpc_http1_reverse_bridge/filter.h @@ -57,11 +57,11 @@ class Filter : public Envoy::Http::PassThroughFilter { // The actual size of the response returned by the upstream so far. uint32_t upstream_response_bytes_{}; - std::string content_type_{}; + std::string content_type_; Grpc::Status::GrpcStatus grpc_status_{}; // Normally we'd use the encoding buffer, but since we need to mutate the // buffer we instead maintain our own. - Buffer::OwnedImpl buffer_{}; + Buffer::OwnedImpl buffer_; }; using FilterPtr = std::unique_ptr; diff --git a/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.h b/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.h index 1eebd85a21ff4..29e810ec8439b 100644 --- a/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.h +++ b/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.h @@ -101,7 +101,7 @@ class JsonTranscoderConfig : public Logger::Loggable, } envoy::extensions::filters::http::grpc_json_transcoder::v3::GrpcJsonTranscoder:: - RequestValidationOptions request_validation_options_{}; + RequestValidationOptions request_validation_options_; absl::optional max_request_body_size_; absl::optional max_response_body_size_; diff --git a/source/extensions/filters/http/health_check/health_check.h b/source/extensions/filters/http/health_check/health_check.h index 05abfe6d076b4..0caca322e475f 100644 --- a/source/extensions/filters/http/health_check/health_check.h +++ b/source/extensions/filters/http/health_check/health_check.h @@ -72,9 +72,9 @@ class HealthCheckCacheManager { Event::TimerPtr clear_cache_timer_; const std::chrono::milliseconds timeout_; - std::atomic use_cached_response_{}; - std::atomic last_response_code_{}; - std::atomic last_response_degraded_{}; + std::atomic use_cached_response_{false}; + std::atomic last_response_code_{static_cast(0)}; + std::atomic last_response_degraded_{false}; }; using HealthCheckCacheManagerSharedPtr = std::shared_ptr; diff --git a/source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.cc b/source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.cc index bad8055442c7b..74c44f2e944bf 100644 --- a/source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.cc +++ b/source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.cc @@ -24,10 +24,9 @@ namespace { struct TraversalState { const Protobuf::Descriptor* current_desc; std::vector normalized_path; - bool is_map_entry; + bool is_map_entry{false}; - TraversalState(const Protobuf::Descriptor* root, size_t capacity) - : current_desc(root), is_map_entry(false) { + TraversalState(const Protobuf::Descriptor* root, size_t capacity) : current_desc(root) { normalized_path.reserve(capacity); } }; @@ -107,7 +106,7 @@ FieldChecker::FieldChecker(const ScrubberContext scrubber_context, const std::string& method_name, const ProtoApiScrubberFilterConfig* filter_config) : scrubber_context_(scrubber_context), matching_data_(*stream_info), method_name_(method_name), - filter_config_ptr_(filter_config), root_descriptor_(nullptr) { + filter_config_ptr_(filter_config) { if (request_headers.has_value()) { matching_data_.onRequestHeaders(request_headers.ref()); diff --git a/source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.h b/source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.h index f4eac3cc41321..c60990e710827 100644 --- a/source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.h +++ b/source/extensions/filters/http/proto_api_scrubber/scrubbing_util_lib/field_checker.h @@ -43,7 +43,7 @@ class FieldChecker : public FieldCheckerInterface, public Logger::Loggable, NormalizationResult> path_cache_; diff --git a/source/extensions/filters/http/proto_message_extraction/filter.h b/source/extensions/filters/http/proto_message_extraction/filter.h index 93d6930faa928..9411c61767e30 100644 --- a/source/extensions/filters/http/proto_message_extraction/filter.h +++ b/source/extensions/filters/http/proto_message_extraction/filter.h @@ -40,11 +40,10 @@ class Filter : public Envoy::Http::PassThroughFilter, private: struct HandleDataStatus { - explicit HandleDataStatus(Envoy::Http::FilterDataStatus status) - : got_messages(false), filter_status(status) {} + explicit HandleDataStatus(Envoy::Http::FilterDataStatus status) : filter_status(status) {} // If true, the function has processed at least one message. - bool got_messages; + bool got_messages{false}; // If "got_message" is false, return this filter_status. Envoy::Http::FilterDataStatus filter_status; diff --git a/source/extensions/filters/http/rate_limit_quota/global_client_impl.h b/source/extensions/filters/http/rate_limit_quota/global_client_impl.h index 7259973a529da..20d5016e26a03 100644 --- a/source/extensions/filters/http/rate_limit_quota/global_client_impl.h +++ b/source/extensions/filters/http/rate_limit_quota/global_client_impl.h @@ -68,7 +68,7 @@ class GlobalRateLimitClientImpl : public Grpc::AsyncStreamCallbacks< std::chrono::milliseconds send_reports_interval, ThreadLocal::TypedSlot& buckets_tls, Envoy::Event::Dispatcher& main_dispatcher); - ~GlobalRateLimitClientImpl() = default; + ~GlobalRateLimitClientImpl() override = default; void onReceiveMessage(RateLimitQuotaResponsePtr&& response) override; @@ -164,7 +164,7 @@ class GlobalRateLimitClientImpl : public Grpc::AsyncStreamCallbacks< // Client is stored as the bare object since GrpcAsyncClient already takes ownership of the given // raw AsyncClientPtr. GrpcAsyncClient async_client_; - Grpc::AsyncStream stream_{}; + Grpc::AsyncStream stream_; // Reference to TLS slot for the global quota bucket cache. It outlives // the filter. diff --git a/source/extensions/filters/http/thrift_to_metadata/filter.h b/source/extensions/filters/http/thrift_to_metadata/filter.h index 6a7ce9c27a2c1..4c8c8234623c2 100644 --- a/source/extensions/filters/http/thrift_to_metadata/filter.h +++ b/source/extensions/filters/http/thrift_to_metadata/filter.h @@ -74,8 +74,8 @@ class Rule { private: const ProtoRule rule_; const uint16_t rule_id_; - std::string method_name_{}; - ThriftMetadataToProtobufValue protobuf_value_extracter_{}; + std::string method_name_; + ThriftMetadataToProtobufValue protobuf_value_extracter_; }; using Rules = std::vector; diff --git a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.h b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.h index 7319026e5ba0a..9877279a8c7bb 100644 --- a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.h +++ b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.h @@ -151,7 +151,7 @@ class Config : public Logger::Loggable { absl::flat_hash_map tlv_types_; const bool allow_requests_without_proxy_protocol_; const bool pass_all_tlvs_; - absl::flat_hash_set pass_through_tlvs_{}; + absl::flat_hash_set pass_through_tlvs_; bool allow_v1_{true}; bool allow_v2_{true}; const envoy::extensions::filters::listener::proxy_protocol::v3::ProxyProtocol::TlvLocation diff --git a/source/extensions/filters/network/dubbo_proxy/active_message.cc b/source/extensions/filters/network/dubbo_proxy/active_message.cc index 36b894a30846c..20dfc46221ff1 100644 --- a/source/extensions/filters/network/dubbo_proxy/active_message.cc +++ b/source/extensions/filters/network/dubbo_proxy/active_message.cc @@ -15,7 +15,7 @@ ActiveResponseDecoder::ActiveResponseDecoder(ActiveMessage& parent, DubboFilterS ProtocolPtr&& protocol) : parent_(parent), stats_(stats), response_connection_(connection), protocol_(std::move(protocol)), - decoder_(std::make_unique(*protocol_, *this)), complete_(false) {} + decoder_(std::make_unique(*protocol_, *this)) {} DubboFilters::UpstreamResponseStatus ActiveResponseDecoder::onData(Buffer::Instance& data) { ENVOY_LOG(debug, "dubbo response: the received reply data length is {}", data.length()); @@ -180,8 +180,7 @@ ActiveMessage::ActiveMessage(ConnectionManager& parent) parent_.stats().request_time_ms_, parent.timeSystem())), stream_id_(parent.randomGenerator().random()), stream_info_(parent.timeSystem(), parent_.connection().connectionInfoProviderSharedPtr(), - StreamInfo::FilterState::LifeSpan::FilterChain), - pending_stream_decoded_(false), local_response_sent_(false) { + StreamInfo::FilterState::LifeSpan::FilterChain) { parent_.stats().request_active_.inc(); } diff --git a/source/extensions/filters/network/dubbo_proxy/active_message.h b/source/extensions/filters/network/dubbo_proxy/active_message.h index 54ec81f17efb0..1adb3015ab523 100644 --- a/source/extensions/filters/network/dubbo_proxy/active_message.h +++ b/source/extensions/filters/network/dubbo_proxy/active_message.h @@ -54,7 +54,7 @@ class ActiveResponseDecoder : public ResponseDecoderCallbacks, ProtocolPtr protocol_; ResponseDecoderPtr decoder_; MessageMetadataSharedPtr metadata_; - bool complete_ : 1; + bool complete_ : 1 = false; DubboFilters::UpstreamResponseStatus response_status_{ DubboFilters::UpstreamResponseStatus::MoreData}; }; @@ -209,8 +209,8 @@ class ActiveMessage : public LinkedObject, Buffer::OwnedImpl response_buffer_; - bool pending_stream_decoded_ : 1; - bool local_response_sent_ : 1; + bool pending_stream_decoded_ : 1 = false; + bool local_response_sent_ : 1 = false; friend class ActiveResponseDecoder; }; diff --git a/source/extensions/filters/network/dubbo_proxy/message_impl.h b/source/extensions/filters/network/dubbo_proxy/message_impl.h index 2b9092d41c579..8dd75eaf2f166 100644 --- a/source/extensions/filters/network/dubbo_proxy/message_impl.h +++ b/source/extensions/filters/network/dubbo_proxy/message_impl.h @@ -123,8 +123,8 @@ class RpcInvocationImpl : public RpcInvocationBase { AttachmentLazyCallback attachment_lazy_callback_; ParametersLazyCallback parameters_lazy_callback_; - mutable ParametersPtr parameters_{}; - mutable AttachmentPtr attachment_{}; + mutable ParametersPtr parameters_; + mutable AttachmentPtr attachment_; }; class RpcResultImpl : public RpcResult { diff --git a/source/extensions/filters/network/dubbo_proxy/router/router_impl.cc b/source/extensions/filters/network/dubbo_proxy/router/router_impl.cc index 15bb35a9c35ab..22bbfdf4fed54 100644 --- a/source/extensions/filters/network/dubbo_proxy/router/router_impl.cc +++ b/source/extensions/filters/network/dubbo_proxy/router/router_impl.cc @@ -267,10 +267,8 @@ Router::UpstreamRequest::UpstreamRequest(Router& parent, Upstream::TcpPoolData& SerializationType serialization_type, ProtocolType protocol_type) : parent_(parent), conn_pool_data_(pool_data), metadata_(metadata), - protocol_( - NamedProtocolConfigFactory::getFactory(protocol_type).createProtocol(serialization_type)), - request_complete_(false), response_started_(false), response_complete_(false), - stream_reset_(false) {} + protocol_(NamedProtocolConfigFactory::getFactory(protocol_type) + .createProtocol(serialization_type)) {} Router::UpstreamRequest::~UpstreamRequest() = default; diff --git a/source/extensions/filters/network/dubbo_proxy/router/router_impl.h b/source/extensions/filters/network/dubbo_proxy/router/router_impl.h index ac2b3980370cf..532ff6019d016 100644 --- a/source/extensions/filters/network/dubbo_proxy/router/router_impl.h +++ b/source/extensions/filters/network/dubbo_proxy/router/router_impl.h @@ -84,10 +84,10 @@ class Router : public Tcp::ConnectionPool::UpstreamCallbacks, SerializerPtr serializer_; ProtocolPtr protocol_; - bool request_complete_ : 1; - bool response_started_ : 1; - bool response_complete_ : 1; - bool stream_reset_ : 1; + bool request_complete_ : 1 = false; + bool response_started_ : 1 = false; + bool response_complete_ : 1 = false; + bool stream_reset_ : 1 = false; }; void cleanup(); @@ -97,7 +97,7 @@ class Router : public Tcp::ConnectionPool::UpstreamCallbacks, DubboFilters::DecoderFilterCallbacks* callbacks_{}; DubboFilters::EncoderFilterCallbacks* encoder_callbacks_{}; - RouteConstSharedPtr route_{}; + RouteConstSharedPtr route_; const RouteEntry* route_entry_{}; Upstream::ClusterInfoConstSharedPtr cluster_; diff --git a/source/extensions/filters/network/ext_authz/ext_authz.h b/source/extensions/filters/network/ext_authz/ext_authz.h index b43da941f3428..dced7f7c489fd 100644 --- a/source/extensions/filters/network/ext_authz/ext_authz.h +++ b/source/extensions/filters/network/ext_authz/ext_authz.h @@ -162,7 +162,7 @@ class Filter : public Network::ReadFilter, FilterReturn filter_return_{FilterReturn::Stop}; // Used to identify if the callback to onComplete() is synchronous (on the stack) or asynchronous. bool calling_check_{}; - envoy::service::auth::v3::CheckRequest check_request_{}; + envoy::service::auth::v3::CheckRequest check_request_; }; } // namespace ExtAuthz } // namespace NetworkFilters diff --git a/source/extensions/filters/network/ext_proc/ext_proc.h b/source/extensions/filters/network/ext_proc/ext_proc.h index 6d5ef48106d07..804f3f2903923 100644 --- a/source/extensions/filters/network/ext_proc/ext_proc.h +++ b/source/extensions/filters/network/ext_proc/ext_proc.h @@ -48,7 +48,7 @@ struct NetworkExtProcStats { */ class NetworkExtProcLoggingInfo : public Envoy::StreamInfo::FilterState::Object { public: - explicit NetworkExtProcLoggingInfo() {} + explicit NetworkExtProcLoggingInfo() = default; // Direction-specific aggregated statistics. struct DirectionalStats { diff --git a/source/extensions/filters/network/generic_proxy/stats.h b/source/extensions/filters/network/generic_proxy/stats.h index 59c2b88b167d7..1e12c53028221 100644 --- a/source/extensions/filters/network/generic_proxy/stats.h +++ b/source/extensions/filters/network/generic_proxy/stats.h @@ -102,8 +102,8 @@ class GenericFilterStatsHelper { // Even in the worst case where the status code and response flag are different for every // request, we will only do some additional integer comparisons and local variable // assignments which should be very cheap. - std::pair> last_code_counter_{}; - std::pair> last_flag_counter_{}; + std::pair> last_code_counter_; + std::pair> last_flag_counter_; }; } // namespace GenericProxy diff --git a/source/extensions/filters/network/redis_proxy/cluster_response_handler.h b/source/extensions/filters/network/redis_proxy/cluster_response_handler.h index 6dcd70c3b330f..6656346e23aa6 100644 --- a/source/extensions/filters/network/redis_proxy/cluster_response_handler.h +++ b/source/extensions/filters/network/redis_proxy/cluster_response_handler.h @@ -50,7 +50,7 @@ class BaseClusterScopeResponseHandler : public Logger::Loggable pending_responses_; explicit BaseClusterScopeResponseHandler(uint32_t shard_count) - : num_pending_responses_(shard_count), error_count_(0) { + : num_pending_responses_(shard_count) { pending_responses_.reserve(shard_count); } diff --git a/source/extensions/filters/network/redis_proxy/info_command_handler.h b/source/extensions/filters/network/redis_proxy/info_command_handler.h index a09633809457e..d6d020b99b36e 100644 --- a/source/extensions/filters/network/redis_proxy/info_command_handler.h +++ b/source/extensions/filters/network/redis_proxy/info_command_handler.h @@ -46,15 +46,15 @@ class InfoCmdAggregateResponseHandler : public BaseAggregateResponseHandler { std::string key; // Metric key name AggregationType agg_type; // How to aggregate std::string str_value; // String value storage - int64_t int_value; // Integer value storage + int64_t int_value{0}; // Integer value storage CustomAggregatorFunc custom_handler; // Custom aggregation function std::string source_metric_for_human; // If set, convert this metric to human-readable format MetricConfig(const std::string& sec, const std::string& k, AggregationType type, const std::string& default_val = "", CustomAggregatorFunc handler = nullptr, const std::string& human_source = "") - : section(sec), key(k), agg_type(type), str_value(default_val), int_value(0), - custom_handler(handler), source_metric_for_human(human_source) {} + : section(sec), key(k), agg_type(type), str_value(default_val), custom_handler(handler), + source_metric_for_human(human_source) {} }; // Initialize the metric template diff --git a/source/extensions/filters/network/thrift_proxy/auto_protocol_impl.h b/source/extensions/filters/network/thrift_proxy/auto_protocol_impl.h index 7f931199d31ba..13bde0e1e29a6 100644 --- a/source/extensions/filters/network/thrift_proxy/auto_protocol_impl.h +++ b/source/extensions/filters/network/thrift_proxy/auto_protocol_impl.h @@ -157,7 +157,7 @@ class AutoProtocolImpl : public Protocol { } private: - ProtocolPtr protocol_{}; + ProtocolPtr protocol_; std::string name_; }; diff --git a/source/extensions/filters/network/thrift_proxy/auto_transport_impl.h b/source/extensions/filters/network/thrift_proxy/auto_transport_impl.h index cffacf7e8931a..7b7694b3bf626 100644 --- a/source/extensions/filters/network/thrift_proxy/auto_transport_impl.h +++ b/source/extensions/filters/network/thrift_proxy/auto_transport_impl.h @@ -44,7 +44,7 @@ class AutoTransportImpl : public Transport { } private: - TransportPtr transport_{}; + TransportPtr transport_; std::string name_; }; diff --git a/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h b/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h index a2c978bcf2f86..2b5fba021255a 100644 --- a/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h +++ b/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h @@ -98,15 +98,15 @@ class CompactProtocolImpl : public Protocol { void writeFieldBeginInternal(Buffer::Instance& buffer, FieldType field_type, int16_t field_id, absl::optional field_type_override); - std::stack last_field_id_stack_{}; + std::stack last_field_id_stack_; int16_t last_field_id_{0}; // Compact protocol encodes boolean struct fields as true/false *types* with no data. // This tracks the last boolean struct field's value for readBool. - absl::optional bool_value_{}; + absl::optional bool_value_; // Similarly, track the field id for writeBool. - absl::optional bool_field_id_{}; + absl::optional bool_field_id_; const static uint16_t Magic; const static uint16_t MagicMask; diff --git a/source/extensions/filters/network/thrift_proxy/conn_manager.h b/source/extensions/filters/network/thrift_proxy/conn_manager.h index 4c29b9c2a1239..2ec67474449bb 100644 --- a/source/extensions/filters/network/thrift_proxy/conn_manager.h +++ b/source/extensions/filters/network/thrift_proxy/conn_manager.h @@ -88,8 +88,7 @@ class ConnectionManager : public Network::ReadFilter, struct ResponseDecoder : public DecoderCallbacks, public DecoderEventHandler { ResponseDecoder(ActiveRpc& parent, Transport& transport, Protocol& protocol) : parent_(parent), decoder_(std::make_unique(transport, protocol, *this)), - protocol_converter_(std::make_shared()), complete_{false}, - passthrough_{false}, pending_transport_end_{false} { + protocol_converter_(std::make_shared()) { protocol_converter_->initProtocolConverter(*parent_.parent_.protocol_, parent_.response_buffer_); } @@ -135,9 +134,9 @@ class ConnectionManager : public Network::ReadFilter, MessageMetadataSharedPtr metadata_; ProtocolConverterSharedPtr protocol_converter_; absl::optional success_; - bool complete_ : 1; - bool passthrough_ : 1; - bool pending_transport_end_ : 1; + bool complete_ : 1 = false; + bool passthrough_ : 1 = false; + bool pending_transport_end_ : 1 = false; }; using ResponseDecoderPtr = std::unique_ptr; @@ -218,9 +217,7 @@ class ConnectionManager : public Network::ReadFilter, stream_id_(parent_.random_generator_.random()), stream_info_(parent_.time_source_, parent_.read_callbacks_->connection().connectionInfoProviderSharedPtr(), - StreamInfo::FilterState::LifeSpan::FilterChain), - local_response_sent_{false}, pending_transport_end_{false}, passthrough_{false}, - under_on_local_reply_{false} { + StreamInfo::FilterState::LifeSpan::FilterChain) { parent_.stats_.request_active_.inc(); } ~ActiveRpc() override { @@ -370,10 +367,10 @@ class ConnectionManager : public Network::ReadFilter, int32_t original_sequence_id_{0}; MessageType original_msg_type_{MessageType::Call}; std::function filter_action_; - bool local_response_sent_ : 1; - bool pending_transport_end_ : 1; - bool passthrough_ : 1; - bool under_on_local_reply_ : 1; + bool local_response_sent_ : 1 = false; + bool pending_transport_end_ : 1 = false; + bool passthrough_ : 1 = false; + bool under_on_local_reply_ : 1 = false; }; using ActiveRpcPtr = std::unique_ptr; diff --git a/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.h b/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.h index 1ea75e721e70d..f400ed85a5d17 100644 --- a/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.h +++ b/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.h @@ -45,7 +45,7 @@ class Rule { }; const ProtoRule rule_; absl::optional regex_replace_; - std::string method_or_service_name_{}; + std::string method_or_service_name_; MatchType match_type_; uint16_t rule_id_; }; diff --git a/source/extensions/filters/network/thrift_proxy/metadata.h b/source/extensions/filters/network/thrift_proxy/metadata.h index 224afbdcc709b..dd07bbbc70b76 100644 --- a/source/extensions/filters/network/thrift_proxy/metadata.h +++ b/source/extensions/filters/network/thrift_proxy/metadata.h @@ -256,13 +256,13 @@ class MessageMetadata { copy->setSampled(sampled_opt.value()); } } - absl::optional frame_size_{}; - absl::optional proto_{}; - absl::optional method_name_{}; - absl::optional header_flags_{}; - absl::optional seq_id_{}; - absl::optional msg_type_{}; - absl::optional reply_type_{}; + absl::optional frame_size_; + absl::optional proto_; + absl::optional method_name_; + absl::optional header_flags_; + absl::optional seq_id_; + absl::optional msg_type_; + absl::optional reply_type_; Http::RequestHeaderMapPtr request_headers_{nullptr}; Http::ResponseHeaderMapPtr response_headers_{nullptr}; absl::optional app_ex_type_; diff --git a/source/extensions/filters/network/thrift_proxy/router/router_impl.h b/source/extensions/filters/network/thrift_proxy/router/router_impl.h index e2aeecce17e8d..5c7751d860a50 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_impl.h +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.h @@ -231,8 +231,8 @@ class Router : public Tcp::ConnectionPool::UpstreamCallbacks, public: Router(Upstream::ClusterManager& cluster_manager, const RouterStats& stats, Runtime::Loader& runtime, ShadowWriter& shadow_writer, bool close_downstream_on_error) - : RequestOwner(cluster_manager, stats), passthrough_supported_(false), runtime_(runtime), - shadow_writer_(shadow_writer), close_downstream_on_error_(close_downstream_on_error) {} + : RequestOwner(cluster_manager, stats), runtime_(runtime), shadow_writer_(shadow_writer), + close_downstream_on_error_(close_downstream_on_error) {} ~Router() override = default; @@ -321,19 +321,19 @@ class Router : public Tcp::ConnectionPool::UpstreamCallbacks, void cleanup(); ThriftFilters::DecoderFilterCallbacks* callbacks_{}; - std::unique_ptr upstream_response_callbacks_{}; - RouteConstSharedPtr route_{}; + std::unique_ptr upstream_response_callbacks_; + RouteConstSharedPtr route_; const RouteEntry* route_entry_{}; Envoy::Router::MetadataMatchCriteriaConstPtr metadata_match_criteria_; std::unique_ptr upstream_request_; Buffer::OwnedImpl upstream_request_buffer_; - bool passthrough_supported_ : 1; + bool passthrough_supported_ : 1 = false; uint64_t request_size_{}; Runtime::Loader& runtime_; ShadowWriter& shadow_writer_; - std::vector> shadow_routers_{}; + std::vector> shadow_routers_; bool close_downstream_on_error_; }; diff --git a/source/extensions/filters/network/thrift_proxy/router/upstream_request.cc b/source/extensions/filters/network/thrift_proxy/router/upstream_request.cc index 124fb88be682b..1ffea1db55938 100644 --- a/source/extensions/filters/network/thrift_proxy/router/upstream_request.cc +++ b/source/extensions/filters/network/thrift_proxy/router/upstream_request.cc @@ -14,7 +14,6 @@ UpstreamRequest::UpstreamRequest(RequestOwner& parent, Upstream::TcpPoolData& po : parent_(parent), stats_(parent.stats()), conn_pool_data_(pool_data), metadata_(metadata), transport_(NamedTransportConfigFactory::getFactory(transport_type).createTransport()), protocol_(NamedProtocolConfigFactory::getFactory(protocol_type).createProtocol()), - request_complete_(false), response_underflow_(false), charged_response_timing_(false), close_downstream_on_error_(close_downstream_on_error) {} UpstreamRequest::~UpstreamRequest() { diff --git a/source/extensions/filters/network/thrift_proxy/router/upstream_request.h b/source/extensions/filters/network/thrift_proxy/router/upstream_request.h index fe3f70043cf15..aa1947fbdc21a 100644 --- a/source/extensions/filters/network/thrift_proxy/router/upstream_request.h +++ b/source/extensions/filters/network/thrift_proxy/router/upstream_request.h @@ -79,9 +79,9 @@ struct UpstreamRequest : public Tcp::ConnectionPool::Callbacks, }; ResponseState response_state_{ResponseState::None}; - bool request_complete_ : 1; - bool response_underflow_ : 1; - bool charged_response_timing_ : 1; + bool request_complete_ : 1 = false; + bool response_underflow_ : 1 = false; + bool charged_response_timing_ : 1 = false; bool close_downstream_on_error_ : 1; absl::optional downstream_request_complete_time_; diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h index c1c921375f269..275f7aed8ed12 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h @@ -444,7 +444,7 @@ class TunnelingConnectionPoolImpl : public TunnelingConnectionPool, StreamInfo::StreamInfo& upstream_info, absl::optional) override; private: - absl::optional conn_pool_data_{}; + absl::optional conn_pool_data_; HttpStreamCallbacks* callbacks_{}; UpstreamTunnelCallbacks& upstream_callbacks_; std::unique_ptr upstream_; diff --git a/source/extensions/health_checkers/http/health_checker_impl.cc b/source/extensions/health_checkers/http/health_checker_impl.cc index 8d6c8a281aecd..2de983a0ed39f 100644 --- a/source/extensions/health_checkers/http/health_checker_impl.cc +++ b/source/extensions/health_checkers/http/health_checker_impl.cc @@ -213,8 +213,7 @@ HttpHealthCheckerImpl::HttpActiveHealthCheckSession::HttpActiveHealthCheckSessio local_connection_info_provider_(std::make_shared( Network::Utility::getCanonicalIpv4LoopbackAddress(), Network::Utility::getCanonicalIpv4LoopbackAddress())), - protocol_(codecClientTypeToProtocol(parent_.codec_client_type_)), expect_reset_(false), - reuse_connection_(false), request_in_flight_(false) {} + protocol_(codecClientTypeToProtocol(parent_.codec_client_type_)) {} HttpHealthCheckerImpl::HttpActiveHealthCheckSession::~HttpActiveHealthCheckSession() { ASSERT(client_ == nullptr); diff --git a/source/extensions/health_checkers/http/health_checker_impl.h b/source/extensions/health_checkers/http/health_checker_impl.h index 68fd6214f9166..3ee058513cffc 100644 --- a/source/extensions/health_checkers/http/health_checker_impl.h +++ b/source/extensions/health_checkers/http/health_checker_impl.h @@ -146,9 +146,9 @@ class HttpHealthCheckerImpl : public HealthCheckerImplBase { Network::ConnectionInfoProviderSharedPtr local_connection_info_provider_; // Keep small members (bools and enums) at the end of class, to reduce alignment overhead. const Http::Protocol protocol_; - bool expect_reset_ : 1; - bool reuse_connection_ : 1; - bool request_in_flight_ : 1; + bool expect_reset_ : 1 = false; + bool reuse_connection_ : 1 = false; + bool request_in_flight_ : 1 = false; }; using HttpActiveHealthCheckSessionPtr = std::unique_ptr; diff --git a/source/extensions/load_balancing_policies/dynamic_modules/lb_config.cc b/source/extensions/load_balancing_policies/dynamic_modules/lb_config.cc index d95e35395c7b7..63859a70cedc6 100644 --- a/source/extensions/load_balancing_policies/dynamic_modules/lb_config.cc +++ b/source/extensions/load_balancing_policies/dynamic_modules/lb_config.cc @@ -57,8 +57,7 @@ DynamicModuleLbConfig::DynamicModuleLbConfig( const std::string& lb_policy_name, const std::string& lb_config, const std::string& metrics_namespace, Envoy::Extensions::DynamicModules::DynamicModulePtr dynamic_module, Stats::Scope& stats_scope) - : in_module_config_(nullptr), - stats_scope_(stats_scope.createScope(absl::StrCat(metrics_namespace, "."))), + : stats_scope_(stats_scope.createScope(absl::StrCat(metrics_namespace, "."))), stat_name_pool_(stats_scope_->symbolTable()), lb_policy_name_(lb_policy_name), lb_config_(lb_config), dynamic_module_(std::move(dynamic_module)) {} diff --git a/source/extensions/load_balancing_policies/dynamic_modules/lb_config.h b/source/extensions/load_balancing_policies/dynamic_modules/lb_config.h index 9c2bc41ce63a7..e6a0d3c5cd838 100644 --- a/source/extensions/load_balancing_policies/dynamic_modules/lb_config.h +++ b/source/extensions/load_balancing_policies/dynamic_modules/lb_config.h @@ -69,7 +69,7 @@ class DynamicModuleLbConfig : public Logger::Loggableon_lb_new_(config_->in_module_config_, this); if (in_module_lb_ == nullptr) { diff --git a/source/extensions/load_balancing_policies/dynamic_modules/load_balancer.h b/source/extensions/load_balancing_policies/dynamic_modules/load_balancer.h index cdcb190c4db41..3b3a6959531ad 100644 --- a/source/extensions/load_balancing_policies/dynamic_modules/load_balancer.h +++ b/source/extensions/load_balancing_policies/dynamic_modules/load_balancer.h @@ -46,7 +46,7 @@ class DynamicModuleLoadBalancer : public Upstream::LoadBalancer, DynamicModuleLbConfigSharedPtr config_; const Upstream::PrioritySet& priority_set_; std::string cluster_name_; - envoy_dynamic_module_type_lb_module_ptr in_module_lb_; + envoy_dynamic_module_type_lb_module_ptr in_module_lb_{nullptr}; // Handle for the member update callback registration. Automatically unregisters on destruction. Envoy::Common::CallbackHandlePtr member_update_cb_; diff --git a/source/extensions/matching/actions/transform_stat/transform_stat.h b/source/extensions/matching/actions/transform_stat/transform_stat.h index 0edea1c0d2688..11f15f84b3863 100644 --- a/source/extensions/matching/actions/transform_stat/transform_stat.h +++ b/source/extensions/matching/actions/transform_stat/transform_stat.h @@ -70,7 +70,7 @@ class DropTag : public Matcher::ActionBase, public Transform class NoOpAction : public Matcher::ActionBase, public TransformStatAction { public: - explicit NoOpAction() {} + explicit NoOpAction() = default; Result apply(std::string&) const override; }; diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h b/source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h index 5a3c3d48a53eb..42b7359f87bf0 100644 --- a/source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h +++ b/source/extensions/tracers/opentelemetry/resource_detectors/resource_detector.h @@ -26,7 +26,7 @@ using ResourceAttributes = absl::flat_hash_map; */ struct Resource { std::string schema_url_{""}; - ResourceAttributes attributes_{}; + ResourceAttributes attributes_; virtual ~Resource() = default; }; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index 5a6b31bdb3906..6b322eb65ff10 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -109,11 +109,11 @@ class SamplingController : public Logger::Loggable { private: using SamplingExponentsT = absl::flat_hash_map; SamplingExponentsT sampling_exponents_; - mutable absl::Mutex sampling_exponents_mutex_{}; - std::string rest_bucket_key_{}; + mutable absl::Mutex sampling_exponents_mutex_; + std::string rest_bucket_key_; std::unique_ptr stream_summary_; uint64_t last_effective_count_{}; - mutable absl::Mutex stream_summary_mutex_{}; + mutable absl::Mutex stream_summary_mutex_; SamplerConfigProviderPtr sampler_config_provider_; void logSamplingInfo(const TopKListT& top_k, const SamplingExponentsT& new_sampling_exponents, diff --git a/source/extensions/transport_sockets/alts/tsi_socket.h b/source/extensions/transport_sockets/alts/tsi_socket.h index f8937f09286af..f03c1ef108bfa 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.h +++ b/source/extensions/transport_sockets/alts/tsi_socket.h @@ -99,7 +99,7 @@ class TsiSocket : public Network::TransportSocket, HandshakerFactory handshaker_factory_; HandshakeValidator handshake_validator_; - TsiHandshakerPtr handshaker_{}; + TsiHandshakerPtr handshaker_; bool handshaker_next_calling_{}; TsiFrameProtectorPtr frame_protector_; diff --git a/source/extensions/transport_sockets/http_11_proxy/connect.h b/source/extensions/transport_sockets/http_11_proxy/connect.h index 061331849cacd..6c5b4152dd940 100644 --- a/source/extensions/transport_sockets/http_11_proxy/connect.h +++ b/source/extensions/transport_sockets/http_11_proxy/connect.h @@ -52,7 +52,7 @@ class UpstreamHttp11ConnectSocket : public TransportSockets::PassthroughSocket, Network::TransportSocketOptionsConstSharedPtr options_; Network::TransportSocketCallbacks* callbacks_{}; - Buffer::OwnedImpl header_buffer_{}; + Buffer::OwnedImpl header_buffer_; bool need_to_strip_connect_response_{}; }; diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h index 647ee8d63dc91..4df34584acc3b 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h @@ -51,12 +51,12 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket, Network::TransportSocketOptionsConstSharedPtr options_; Network::TransportSocketCallbacks* callbacks_{}; - Buffer::OwnedImpl header_buffer_{}; + Buffer::OwnedImpl header_buffer_; ProxyProtocolConfig_Version version_{ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V1}; const UpstreamProxyProtocolStats& stats_; const bool pass_all_tlvs_; - absl::flat_hash_set pass_through_tlvs_{}; - std::vector added_tlvs_{}; + absl::flat_hash_set pass_through_tlvs_; + std::vector added_tlvs_; }; class UpstreamProxyProtocolSocketFactory : public PassthroughFactory { diff --git a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h index 19f92a7eef3bf..851f365923fcf 100644 --- a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.h @@ -97,7 +97,7 @@ class SPIFFEValidator : public CertValidator, Logger::Loggable spiffe_data_; - std::vector subject_alt_name_matchers_{}; + std::vector subject_alt_name_matchers_; SslStats& stats_; TimeSource& time_source_; using SpiffeTrustBundles = Config::DataSource::ProviderSingleton; diff --git a/source/extensions/upstreams/http/config.h b/source/extensions/upstreams/http/config.h index 5f93c3595ae9e..04e6216033e84 100644 --- a/source/extensions/upstreams/http/config.h +++ b/source/extensions/upstreams/http/config.h @@ -73,7 +73,7 @@ class ProtocolOptionsConfigImpl : public Upstream::HttpProtocolOptionsConfig { const Envoy::Http::Http1Settings http1_settings_; const envoy::config::core::v3::Http2ProtocolOptions http2_options_; - const envoy::config::core::v3::Http3ProtocolOptions http3_options_{}; + const envoy::config::core::v3::Http3ProtocolOptions http3_options_; const envoy::config::core::v3::HttpProtocolOptions common_http_protocol_options_; const absl::optional upstream_http_protocol_options_; diff --git a/source/extensions/upstreams/http/http/upstream_request.h b/source/extensions/upstreams/http/http/upstream_request.h index 31bc7cf8e56ac..bfd1c2ef1ac9b 100644 --- a/source/extensions/upstreams/http/http/upstream_request.h +++ b/source/extensions/upstreams/http/http/upstream_request.h @@ -48,7 +48,7 @@ class HttpConnPool : public Router::GenericConnPool, public Envoy::Http::Connect protected: // Points to the actual connection pool to create streams from. - absl::optional pool_data_{}; + absl::optional pool_data_; Envoy::Http::ConnectionPool::Cancellable* conn_pool_stream_handle_{}; Router::GenericConnectionPoolCallbacks* callbacks_{}; }; diff --git a/source/server/hot_restarting_child.h b/source/server/hot_restarting_child.h index 88f19192eefdb..b0bbbd507e273 100644 --- a/source/server/hot_restarting_child.h +++ b/source/server/hot_restarting_child.h @@ -81,7 +81,7 @@ class HotRestartingChild : public HotRestartingBase, const bool skip_parent_stats_; sockaddr_un parent_address_; sockaddr_un parent_address_udp_forwarding_; - std::unique_ptr stat_merger_{}; + std::unique_ptr stat_merger_; Stats::StatName hot_restart_generation_stat_name_; // There are multiple listener instances per address that must all be reactivated // when the parent is drained, so a multimap is used to contain them. diff --git a/source/server/server.cc b/source/server/server.cc index 58deefa97883c..9176aefab3c67 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -103,8 +103,7 @@ InstanceBase::InstanceBase(Init::Manager& init_manager, const Options& options, : nullptr), grpc_context_(store.symbolTable()), http_context_(store.symbolTable()), router_context_(store.symbolTable()), process_context_(std::move(process_context)), - hooks_(hooks), quic_stat_names_(store.symbolTable()), server_contexts_(*this), - enable_reuse_port_default_(true), stats_flush_in_progress_(false) { + hooks_(hooks), quic_stat_names_(store.symbolTable()), server_contexts_(*this) { // Register the server factory context on the main thread. Configuration::ServerFactoryContextInstance::initialize(&server_contexts_); } diff --git a/source/server/server.h b/source/server/server.h index f18f3d0c028d7..f28c0907ac85d 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -439,9 +439,9 @@ class InstanceBase : Logger::Loggable, ListenerHooks& hooks_; Quic::QuicStatNames quic_stat_names_; ServerFactoryContextImpl server_contexts_; - bool enable_reuse_port_default_{false}; + bool enable_reuse_port_default_ = true; Regex::EnginePtr regex_engine_; - bool stats_flush_in_progress_ : 1; + bool stats_flush_in_progress_ = false; std::unique_ptr memory_allocator_manager_; template From 27df5fc0d8f068693b5d80c382765a783cfe69ff Mon Sep 17 00:00:00 2001 From: code Date: Tue, 12 May 2026 11:28:13 +0800 Subject: [PATCH 288/750] clang tidy: fix misc issues of source (#44980) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Apply miscellaneous clang-tidy fixes across `source/`. Part of a series of small, focused clang-tidy cleanup PRs split out from a larger branch. ## Risk Level Low — mechanical clang-tidy refactor; no behavior changes intended. ## Testing CI. ## Docs Changes None. ## Release Notes None. Signed-off-by: wbpcode/wangbaiping --------- Signed-off-by: wbpcode/wangbaiping Signed-off-by: code Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- source/common/matcher/matcher.h | 2 +- source/common/router/router.cc | 4 +++ source/common/tls/context_config_impl.cc | 2 +- source/common/tls/context_impl.cc | 5 ++- .../upstream/transport_socket_match_impl.cc | 2 +- source/common/upstream/upstream_impl.cc | 2 ++ .../grpc/http_grpc_access_log_impl.cc | 2 +- .../grpc/tcp_grpc_access_log_impl.cc | 2 +- .../reverse_tunnel_acceptor_extension.cc | 21 +++++++----- .../async_file_manager_thread_pool.cc | 1 - .../dynamic_forward_proxy/dns_cache_impl.cc | 7 +++- .../dynamic_modules/sdk/cpp/sdk_internal.cc | 9 ++--- .../filters/http/cache/upstream_request.cc | 34 +++++++++---------- .../filters/http/dynamic_modules/factory.cc | 6 +--- .../filter_config.cc | 2 +- .../http/proto_api_scrubber/filter_config.cc | 2 +- .../simple_http_cache/simple_http_cache.cc | 2 +- .../connection_id_generator/quic_lb/config.cc | 2 +- .../tls/cert_selectors/on_demand/config.cc | 4 +-- source/server/cgroup_cpu_util.cc | 4 +-- source/server/server.cc | 21 ++++++------ source/server/server.h | 1 + 22 files changed, 74 insertions(+), 63 deletions(-) diff --git a/source/common/matcher/matcher.h b/source/common/matcher/matcher.h index 416527fff6a10..e84a146e4df1c 100644 --- a/source/common/matcher/matcher.h +++ b/source/common/matcher/matcher.h @@ -294,7 +294,7 @@ class MatchTreeFactory : public OnMatchFactory { return [match_children, data_input, on_no_match, creation_function]() { auto matcher_or_error = creation_function( data_input(), on_no_match ? absl::make_optional((*on_no_match)()) : absl::nullopt); - THROW_IF_NOT_OK(matcher_or_error.status()); + THROW_IF_NOT_OK_REF(matcher_or_error.status()); auto multimap_matcher = std::move(*matcher_or_error); for (const auto& children : match_children) { multimap_matcher->addChild(children.first, children.second()); diff --git a/source/common/router/router.cc b/source/common/router/router.cc index a0ef5106328bd..7410a86dd5486 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -904,6 +904,8 @@ bool Filter::continueDecodeHeaders(Upstream::ThreadLocalCluster* cluster, // copy whole headers map is not cheap. shadow_headers = std::move(original_shadow_headers); } else { + ASSERT(original_shadow_headers != nullptr); + // NOLINTNEXTLINE(bugprone-use-after-move) shadow_headers = Http::createHeaderMap(*original_shadow_headers); } applyShadowPolicyHeaders(shadow_policy, *shadow_headers); @@ -1181,6 +1183,8 @@ Http::FilterTrailersStatus Filter::decodeTrailers(Http::RequestTrailerMap& trail // copy whole trailers map is not cheap. shadow_trailer = std::move(original_shadow_trailer); } else { + ASSERT(original_shadow_trailer != nullptr); + // NOLINTNEXTLINE(bugprone-use-after-move) shadow_trailer = Http::createHeaderMap(*original_shadow_trailer); } ASSERT(shadow_trailer != nullptr); diff --git a/source/common/tls/context_config_impl.cc b/source/common/tls/context_config_impl.cc index be4993752aca7..d5f493e8735e8 100644 --- a/source/common/tls/context_config_impl.cc +++ b/source/common/tls/context_config_impl.cc @@ -136,7 +136,7 @@ getCertificateValidationContextConfigProvider( const std::string hash_id = generateCertificateHash(validation_context.trusted_ca().inline_bytes()); if (!hash_id.empty()) { - ca_cert_id = absl::StrCat(ca_cert_id, "_", hash_id); + absl::StrAppend(&ca_cert_id, "_", hash_id); } } return CertificateValidationContextConfigProviderSharedPtrWithName{ diff --git a/source/common/tls/context_impl.cc b/source/common/tls/context_impl.cc index 39888edb8e8a9..d4230532aeceb 100644 --- a/source/common/tls/context_impl.cc +++ b/source/common/tls/context_impl.cc @@ -325,14 +325,13 @@ ContextImpl::ContextImpl( } if (additional_init != nullptr) { - absl::Status init_status = additional_init(ctx, tls_certificate); - SET_AND_RETURN_IF_NOT_OK(creation_status, init_status); + SET_AND_RETURN_IF_NOT_OK(additional_init(ctx, tls_certificate), creation_status); } } } parsed_alpn_protocols_ = parseAlpnProtocols(config.alpnProtocols(), creation_status); - SET_AND_RETURN_IF_NOT_OK(creation_status, creation_status); + RETURN_ONLY_IF_NOT_OK_REF(creation_status); // Register stat names based on lists reported by BoringSSL. std::vector list(SSL_get_all_cipher_names(nullptr, 0)); diff --git a/source/common/upstream/transport_socket_match_impl.cc b/source/common/upstream/transport_socket_match_impl.cc index 205feeb1ab58a..ee12c6ab1bab8 100644 --- a/source/common/upstream/transport_socket_match_impl.cc +++ b/source/common/upstream/transport_socket_match_impl.cc @@ -154,7 +154,7 @@ TransportSocketMatcher::MatchData TransportSocketMatcherImpl::resolveUsingMatche filter_state.get()); auto on_match = Matcher::evaluateMatch(*matcher_, data); if (on_match.isMatch()) { - const auto action = on_match.action(); + const auto& action = on_match.action(); if (action) { const auto& name_action = action->getTyped(); const std::string& transport_socket_name = name_action.name(); diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 8162ecbf7b545..bc8bf43f55927 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -514,6 +514,8 @@ HostDescriptionImplBase::HostDescriptionImplBase( locality_metadata_(locality_metadata), locality_(std::move(locality)), locality_zone_stat_name_(locality_->zone(), cluster->statsScope().symbolTable()), priority_(priority), + // TODO(wbpcode): This should be resolved. + // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall) socket_factory_(resolveTransportSocketFactory(dest_address, endpoint_metadata_.get())) { if (health_check_config.port_value() != 0 && dest_address->type() != Network::Address::Type::Ip) { // Setting the health check port to non-0 only works for IP-type addresses. Setting the port diff --git a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc index 6953ff1fec8b5..7bee5bcf44bd4 100644 --- a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc @@ -31,7 +31,7 @@ HttpGrpcAccessLog::HttpGrpcAccessLog(AccessLog::FilterPtr&& filter, GrpcCommon::GrpcAccessLoggerCacheSharedPtr access_logger_cache, const Formatter::CommandParserPtrVector& command_parsers) : Common::ImplBase(std::move(filter)), - config_(std::make_shared(std::move(config))), + config_(std::make_shared(config)), tls_slot_(tls.allocateSlot()), access_logger_cache_(std::move(access_logger_cache)), common_properties_config_(config.common_config(), command_parsers) { for (const auto& header : config_->additional_request_headers_to_log()) { diff --git a/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc index 6bbf1a40fecca..653a6dd9aca46 100644 --- a/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc @@ -24,7 +24,7 @@ TcpGrpcAccessLog::TcpGrpcAccessLog(AccessLog::FilterPtr&& filter, GrpcCommon::GrpcAccessLoggerCacheSharedPtr access_logger_cache, const Formatter::CommandParserPtrVector& command_parsers) : Common::ImplBase(std::move(filter)), - config_(std::make_shared(std::move(config))), + config_(std::make_shared(config)), tls_slot_(tls.allocateSlot()), access_logger_cache_(std::move(access_logger_cache)), common_properties_config_(config.common_config(), command_parsers) { THROW_IF_NOT_OK(Config::Utility::checkTransportVersion(config_->common_config())); diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc index 4a23bd037ee3c..f5f9b93542089 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc @@ -56,16 +56,19 @@ void ReverseTunnelAcceptorExtension::onServerInitialized(Server::Instance&) { // Create thread-local slot for the dispatcher and socket manager. tls_slot_ = ThreadLocal::TypedSlot::makeUnique(context_.threadLocal()); + const uint32_t ping_failure_threshold = ping_failure_threshold_; + const bool enable_tenant_isolation = enable_tenant_isolation_; // Set up the thread-local dispatcher and socket manager. - tls_slot_->set([this](Event::Dispatcher& dispatcher) { - auto tls = std::make_shared(dispatcher, this); - // Propagate configured miss threshold and tenant isolation into the socket manager. - if (tls->socketManager()) { - tls->socketManager()->setMissThreshold(ping_failure_threshold_); - tls->socketManager()->setTenantIsolationEnabled(enable_tenant_isolation_); - } - return tls; - }); + tls_slot_->set( + [this, ping_failure_threshold, enable_tenant_isolation](Event::Dispatcher& dispatcher) { + auto tls = std::make_shared(dispatcher, this); + // Propagate configured miss threshold and tenant isolation into the socket manager. + if (auto* socket_manager = tls->socketManager(); socket_manager != nullptr) { + socket_manager->setMissThreshold(ping_failure_threshold); + socket_manager->setTenantIsolationEnabled(enable_tenant_isolation); + } + return tls; + }); } // Get thread-local registry for the current thread. diff --git a/source/extensions/common/async_files/async_file_manager_thread_pool.cc b/source/extensions/common/async_files/async_file_manager_thread_pool.cc index fa0500ba57f06..5be8b8598cf93 100644 --- a/source/extensions/common/async_files/async_file_manager_thread_pool.cc +++ b/source/extensions/common/async_files/async_file_manager_thread_pool.cc @@ -161,7 +161,6 @@ void AsyncFileManagerThreadPool::worker() { } if (action.action_ != nullptr) { executeAction(std::move(action)); - action.action_ = nullptr; } if (cleanup_action != nullptr) { std::move(cleanup_action)->onCancelledBeforeCallback(); diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc index 4188e5abba034..03ff8da322466 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc @@ -443,13 +443,18 @@ void DnsCacheImpl::finishResolve(const std::string& host, // Functions like this one that modify primary_hosts_ are only called in the main thread so we // know it is safe to use the PrimaryHostInfo pointers outside of the lock. - auto* primary_host_info = [&]() { + auto* primary_host_info = [this, &host]() { absl::ReaderMutexLock reader_lock{primary_hosts_lock_}; const auto primary_host_it = primary_hosts_.find(host); ASSERT(primary_host_it != primary_hosts_.end()); return primary_host_it->second.get(); }(); + if (primary_host_info == nullptr) { + ENVOY_LOG(warn, "host '{}' was removed during resolution, skipping update", host); + return; + } + std::string details_with_maybe_trace = std::string(details); if (primary_host_info != nullptr && primary_host_info->active_query_ != nullptr) { if (enable_dfp_dns_trace_) { diff --git a/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc b/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc index 70c9015876612..3d1f2e1649f5b 100644 --- a/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc +++ b/source/extensions/dynamic_modules/sdk/cpp/sdk_internal.cc @@ -234,7 +234,7 @@ class ChildSpanImpl : public ChildSpan { : host_plugin_ptr_(host_plugin_ptr), child_span_ptr_(child_span_ptr), span_ptr_(reinterpret_cast(child_span_ptr)) {} - ~ChildSpanImpl() override { finish(); } + ~ChildSpanImpl() override { finishImpl(); } void setTag(std::string_view key, std::string_view value) override { envoy_dynamic_module_callback_http_span_set_tag( @@ -298,7 +298,10 @@ class ChildSpanImpl : public ChildSpan { return std::make_unique(host_plugin_ptr_, child); } - void finish() override { + void finish() override { finishImpl(); } + +private: + void finishImpl() { if (child_span_ptr_ == nullptr) { return; } @@ -306,8 +309,6 @@ class ChildSpanImpl : public ChildSpan { child_span_ptr_ = nullptr; span_ptr_ = nullptr; } - -private: const envoy_dynamic_module_type_http_filter_envoy_ptr host_plugin_ptr_; envoy_dynamic_module_type_child_span_module_ptr child_span_ptr_; envoy_dynamic_module_type_span_envoy_ptr span_ptr_; diff --git a/source/extensions/filters/http/cache/upstream_request.cc b/source/extensions/filters/http/cache/upstream_request.cc index 550b81c59a7f6..3f8279ff3544b 100644 --- a/source/extensions/filters/http/cache/upstream_request.cc +++ b/source/extensions/filters/http/cache/upstream_request.cc @@ -205,23 +205,23 @@ void UpstreamRequest::onHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_s if (filter_) { ENVOY_STREAM_LOG(debug, "UpstreamRequest::onHeaders inserting headers", *filter_->decoder_callbacks_); - } - auto insert_context = - cache_->makeInsertContext(std::move(lookup_), *filter_->encoder_callbacks_); - lookup_ = nullptr; - if (insert_context != nullptr) { - // The callbacks passed to CacheInsertQueue are all called through the dispatcher, - // so they're thread-safe. During CacheFilter::onDestroy the queue is given ownership - // of itself and all the callbacks are cancelled, so they are also filter-destruction-safe. - insert_queue_ = std::make_unique(cache_, *filter_->encoder_callbacks_, - std::move(insert_context), *this); - // Add metadata associated with the cached response. Right now this is only response_time; - const ResponseMetadata metadata = {config_->timeSource().systemTime()}; - insert_queue_->insertHeaders(*headers, metadata, end_stream); - // insert_status_ remains absl::nullopt if end_stream == false, as we have not completed the - // insertion yet. - if (end_stream) { - setInsertStatus(InsertStatus::InsertSucceeded); + auto insert_context = + cache_->makeInsertContext(std::move(lookup_), *filter_->encoder_callbacks_); + lookup_ = nullptr; + if (insert_context != nullptr) { + // The callbacks passed to CacheInsertQueue are all called through the dispatcher, + // so they're thread-safe. During CacheFilter::onDestroy the queue is given ownership + // of itself and all the callbacks are cancelled, so they are also filter-destruction-safe. + insert_queue_ = std::make_unique(cache_, *filter_->encoder_callbacks_, + std::move(insert_context), *this); + // Add metadata associated with the cached response. Right now this is only response_time; + const ResponseMetadata metadata = {config_->timeSource().systemTime()}; + insert_queue_->insertHeaders(*headers, metadata, end_stream); + // insert_status_ remains absl::nullopt if end_stream == false, as we have not completed the + // insertion yet. + if (end_stream) { + setInsertStatus(InsertStatus::InsertSucceeded); + } } } } else { diff --git a/source/extensions/filters/http/dynamic_modules/factory.cc b/source/extensions/filters/http/dynamic_modules/factory.cc index fe26af71d021f..b55e2e4d663dd 100644 --- a/source/extensions/filters/http/dynamic_modules/factory.cc +++ b/source/extensions/filters/http/dynamic_modules/factory.cc @@ -188,10 +188,6 @@ DynamicModuleConfigFactory::createFilterFactoryFromRemoteSource( }; auto async_state = std::make_shared(); - // Copies for use in the callback — the originals may not outlive the async fetch. - const FilterConfig proto_config_copy = proto_config; - const auto module_config_copy = module_config; - // Use a weak_ptr in the callback to break the reference cycle: // async_state -> remote_provider -> callback -> async_state. std::weak_ptr weak_state = async_state; @@ -200,7 +196,7 @@ DynamicModuleConfigFactory::createFilterFactoryFromRemoteSource( context.clusterManager(), init_manager, module_config.module().remote(), context.mainThreadDispatcher(), context.api().randomGenerator(), /*allow_empty=*/true, - [weak_state, proto_config_copy, module_config_copy, &context, + [weak_state, proto_config_copy = proto_config, module_config_copy = module_config, &context, &scope](const std::string& data) { auto state = weak_state.lock(); if (!state) { diff --git a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.cc b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.cc index 1a758633b0f4e..e37a2772d6250 100644 --- a/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.cc +++ b/source/extensions/filters/http/grpc_json_reverse_transcoder/filter_config.cc @@ -49,7 +49,7 @@ GrpcJsonReverseTranscoderConfig::GrpcJsonReverseTranscoderConfig( Protobuf::FileDescriptorSet descriptor_set; if (!transcoder_config.descriptor_path().empty()) { auto file_or_error = api.fileSystem().fileReadToEnd(transcoder_config.descriptor_path()); - THROW_IF_NOT_OK(file_or_error.status()); + THROW_IF_NOT_OK_REF(file_or_error.status()); if (!descriptor_set.ParseFromString(file_or_error.value())) { throw EnvoyException("Unable to parse proto descriptor"); } diff --git a/source/extensions/filters/http/proto_api_scrubber/filter_config.cc b/source/extensions/filters/http/proto_api_scrubber/filter_config.cc index efee7219b20f3..5361ac12332ab 100644 --- a/source/extensions/filters/http/proto_api_scrubber/filter_config.cc +++ b/source/extensions/filters/http/proto_api_scrubber/filter_config.cc @@ -416,7 +416,7 @@ ProtoApiScrubberFilterConfig::getParentType(const Envoy::Protobuf::Field* field) void ProtoApiScrubberFilterConfig::buildFieldParentMap( const Envoy::Protobuf::FileDescriptorSet& descriptor_set) { for (const auto& file : descriptor_set.file()) { - std::string package_prefix = file.package(); + const auto& package_prefix = file.package(); for (const auto& msg : file.message_type()) { populateMapForMessage(msg, package_prefix); } diff --git a/source/extensions/http/cache_v2/simple_http_cache/simple_http_cache.cc b/source/extensions/http/cache_v2/simple_http_cache/simple_http_cache.cc index cb79a93ac38cc..36e1a3baab20f 100644 --- a/source/extensions/http/cache_v2/simple_http_cache/simple_http_cache.cc +++ b/source/extensions/http/cache_v2/simple_http_cache/simple_http_cache.cc @@ -85,7 +85,7 @@ void InsertContext::onBody(AdjustedByteRange range, Buffer::InstancePtr buffer, } else { range = AdjustedByteRange(0, entry_->bodySize()); } - progress_receiver_->onBodyInserted(std::move(range), end_stream == EndStream::End); + progress_receiver_->onBodyInserted(range, end_stream == EndStream::End); if (end_stream != EndStream::End) { AdjustedByteRange next_range(range.end(), range.end() + InsertReadChunkSize); return source_->getBody(next_range, diff --git a/source/extensions/quic/connection_id_generator/quic_lb/config.cc b/source/extensions/quic/connection_id_generator/quic_lb/config.cc index 5e07b5dfbda8e..2c1f754540312 100644 --- a/source/extensions/quic/connection_id_generator/quic_lb/config.cc +++ b/source/extensions/quic/connection_id_generator/quic_lb/config.cc @@ -23,7 +23,7 @@ EnvoyQuicConnectionIdGeneratorFactoryPtr ConfigFactory::createQuicConnectionIdGe const envoy::extensions::quic::connection_id_generator::quic_lb::v3::Config&>( config, validation_visitor), context); - THROW_IF_NOT_OK(factory_or_status.status()); + THROW_IF_NOT_OK_REF(factory_or_status.status()); return std::move(factory_or_status.value()); } diff --git a/source/extensions/transport_sockets/tls/cert_selectors/on_demand/config.cc b/source/extensions/transport_sockets/tls/cert_selectors/on_demand/config.cc index d8af541a7fa05..fa996a75e1260 100644 --- a/source/extensions/transport_sockets/tls/cert_selectors/on_demand/config.cc +++ b/source/extensions/transport_sockets/tls/cert_selectors/on_demand/config.cc @@ -120,7 +120,7 @@ absl::Status SecretManager::updateCertificate(absl::string_view secret_name, CacheEntry& entry = cache_[secret_name]; entry.cert_context_ = cert_context; size_t notify_count = 0; - for (auto fetch_handle : entry.callbacks_) { + for (const auto& fetch_handle : entry.callbacks_) { if (auto handle = fetch_handle.lock(); handle) { handle->notify(cert_context); notify_count++; @@ -168,7 +168,7 @@ void SecretManager::doRemoveCertificateConfig(absl::string_view secret_name) { return; } size_t notify_count = 0; - for (auto fetch_handle : it->second.callbacks_) { + for (const auto& fetch_handle : it->second.callbacks_) { if (auto handle = fetch_handle.lock(); handle) { handle->notify(nullptr); notify_count++; diff --git a/source/server/cgroup_cpu_util.cc b/source/server/cgroup_cpu_util.cc index 055644d95afb0..4ed64e5d56bff 100644 --- a/source/server/cgroup_cpu_util.cc +++ b/source/server/cgroup_cpu_util.cc @@ -107,7 +107,7 @@ absl::optional CgroupCpuUtil::getCurrentCgroupPath(Filesystem::I return absl::nullopt; } - const std::string content = result.value(); + const std::string& content = result.value(); const std::vector lines = absl::StrSplit(content, '\n'); std::string v2_path; // Save v2 path in case no v1 found @@ -381,7 +381,7 @@ absl::optional CgroupCpuUtil::discoverCgroupMount(Filesystem::Insta return absl::nullopt; } - const std::string content = result.value(); + const std::string& content = result.value(); const std::vector lines = absl::StrSplit(content, '\n'); std::string v2_mount_point; // Save v2 mount in case no v1 found diff --git a/source/server/server.cc b/source/server/server.cc index 9176aefab3c67..43d34d5651fce 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -234,7 +234,8 @@ void InstanceUtil::flushMetricsToSinks(const std::list& sinks, S } } -void InstanceBase::flushStats() { +void InstanceBase::flushStats() { flushStatsImpl(); } +void InstanceBase::flushStatsImpl() { if (stats_flush_in_progress_) { ENVOY_LOG(debug, "skipping stats flush as flush is already in progress"); server_stats_->dropped_stat_flushes_.inc(); @@ -247,7 +248,7 @@ void InstanceBase::flushStats() { // completion callback is not called immediately. As a result of this server stats will // not be updated and flushed to stat sinks. So skip mergeHistograms call if workers are // not started yet. - if (initManager().state() == Init::Manager::State::Initialized) { + if (init_manager_.state() == Init::Manager::State::Initialized) { // A shutdown initiated before this callback may prevent this from being called as per // the semantics documented in ThreadLocal's runOnAllThreads method. stats_store_.mergeHistograms([this]() -> void { flushStatsInternal(); }); @@ -266,22 +267,21 @@ void InstanceBase::updateServerStats() { parent_stats.parent_memory_allocated_); server_stats_->memory_heap_size_.set(Memory::Stats::totalCurrentlyReserved()); server_stats_->memory_physical_size_.set(Memory::Stats::totalPhysicalBytes()); - if (!options().hotRestartDisabled()) { + if (!options_.hotRestartDisabled()) { server_stats_->parent_connections_.set(parent_stats.parent_connections_); } server_stats_->total_connections_.set(listener_manager_->numConnections() + parent_stats.parent_connections_); server_stats_->days_until_first_cert_expiring_.set( - sslContextManager().daysUntilFirstCertExpires().value_or(0)); + ssl_context_manager_->daysUntilFirstCertExpires().value_or(0)); auto secs_until_ocsp_response_expires = - sslContextManager().secondsUntilFirstOcspResponseExpires(); + ssl_context_manager_->secondsUntilFirstOcspResponseExpires(); if (secs_until_ocsp_response_expires) { server_stats_->seconds_until_first_ocsp_response_expiring_.set( secs_until_ocsp_response_expires.value()); } - server_stats_->state_.set( - enumToInt(Utility::serverState(initManager().state(), healthCheckFailed()))); + server_stats_->state_.set(enumToInt(Utility::serverState(init_manager_.state(), !live_.load()))); server_stats_->stats_recent_lookups_.set( stats_store_.symbolTable().getRecentLookups([](absl::string_view, uint64_t) {})); } @@ -289,8 +289,9 @@ void InstanceBase::updateServerStats() { void InstanceBase::flushStatsInternal() { updateServerStats(); auto& stats_config = config_.statsConfig(); - InstanceUtil::flushMetricsToSinks(stats_config.sinks(), stats_store_, clusterManager(), - timeSource()); + ASSERT(config_.clusterManager() != nullptr); + InstanceUtil::flushMetricsToSinks(stats_config.sinks(), stats_store_, *config_.clusterManager(), + time_source_); if (const auto evict_on_flush = stats_config.evictOnFlush(); evict_on_flush > 0) { stats_eviction_counter_ = (stats_eviction_counter_ + 1) % evict_on_flush; if (stats_eviction_counter_ == 0) { @@ -1127,7 +1128,7 @@ void InstanceBase::terminate() { // Only flush if we have not been hot restarted. if (stat_flush_timer_) { - flushStats(); + flushStatsImpl(); } if (config_.clusterManager() != nullptr) { diff --git a/source/server/server.h b/source/server/server.h index f28c0907ac85d..a240b36c93030 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -348,6 +348,7 @@ class InstanceBase : Logger::Loggable, Network::DnsResolverSharedPtr getOrCreateDnsResolver(); ProtobufTypes::MessagePtr dumpBootstrapConfig(); + void flushStatsImpl(); void flushStatsInternal(); void updateServerStats(); // This does most of the work of initialization, but can throw or return errors caught From 8995d7165b803bd80d44967ee5aee81affd1543e Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 13:27:33 +0100 Subject: [PATCH 289/750] deps: Bump build images -> `v0.1.3` (#44951) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- .github/config.yml | 18 +++++++++--------- ci/envoy_build_sha.sh | 2 +- test/config/integration/certs/certs.sh | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/config.yml b/.github/config.yml index 8c702d4acb57f..92da6b09f73fd 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -4,16 +4,16 @@ build-image: repo: docker.io/envoyproxy/envoy-build repo-gcr: gcr.io/envoy-ci/envoy-build # default ci caching (ci) - sha: afe3addcdb7a5177bcca77d041f079acf4a6c5fb32701aadb2ab96fd0933e0b2 - sha-ci: afe3addcdb7a5177bcca77d041f079acf4a6c5fb32701aadb2ab96fd0933e0b2 - sha-devtools: 420d53d577ba85245db3791255c53423ebc8869d7ca9940c0f29d1decba0c00a - sha-docker: 5fbafb6f366bc7815e9941f813854301ad4341d5dae4efc783bd1e3d602d58ba - sha-gcc: b344f62c6160304d002290f73339ac648b735c3700e28a1c6ad05d9fc2815cec - sha-mobile: 25ef9a9e8977150fe1278e7358307cd039bc90b0ebc5c16fea1a5cd566af4e38 - sha-worker: 7189239fbbc3983b15840609b122a35ce228a1af83aa440b022b1c949adcc32a + sha: 1f67d403defd45e9dd312645122fdee70a16911fdd614378d274c31f27c518b3 + sha-ci: 1f67d403defd45e9dd312645122fdee70a16911fdd614378d274c31f27c518b3 + sha-devtools: 8e21fd13f52323f9e16fa4cf3030d5b6a7f28b1d82343735a425be961651da05 + sha-docker: c290f5f548906d3a426be5c0200e19942a794c3bbb11c6d6257f5529702d7f78 + sha-gcc: 2a12bbcd5e95bc037bae5dfb2eb6361e55faf737098cbf90b4378e2345d9eecf + sha-mobile: cf77d069ffcff08e01e9472c6b0bad608e861ede2bc1c7cb7eb68f7c8e419bce + sha-worker: 482b9906428e68a65101a1b27abf81f69dfd1b4377f66283866110bfd8127ea3 # TODO: remove this dupe (currently used by ci request handler) - mobile-sha: 25ef9a9e8977150fe1278e7358307cd039bc90b0ebc5c16fea1a5cd566af4e38 - tag: 99bbf423a0d1ff5a7332fb1e23bd62b90754fcd2 + mobile-sha: cf77d069ffcff08e01e9472c6b0bad608e861ede2bc1c7cb7eb68f7c8e419bce + tag: v0.1.3 config: envoy: diff --git a/ci/envoy_build_sha.sh b/ci/envoy_build_sha.sh index d507c88e1e030..7d3baa54a8b23 100644 --- a/ci/envoy_build_sha.sh +++ b/ci/envoy_build_sha.sh @@ -45,7 +45,7 @@ if [[ -n "$BUILD_SHA" && ! "$BUILD_SHA" =~ ^[a-fA-F0-9]+$ ]]; then echo "Error: build-image.sha contains invalid characters" exit 1 fi -if [[ -n "$BUILD_TAG" && ! "$BUILD_TAG" =~ ^[a-fA-F0-9]+$ ]]; then +if [[ -n "$BUILD_TAG" && ! "$BUILD_TAG" =~ ^[a-zA-Z0-9._-]+$ ]]; then echo "Error: build-image.tag contains invalid characters" exit 1 fi diff --git a/test/config/integration/certs/certs.sh b/test/config/integration/certs/certs.sh index 5e3fc3ae156ef..108ed30488877 100755 --- a/test/config/integration/certs/certs.sh +++ b/test/config/integration/certs/certs.sh @@ -40,7 +40,7 @@ generate_x509_cert() { generate_expired_x509_cert() { local days="${3:-730}" openssl req -new -key "${1}key.pem" -out "${1}cert.csr" -config "${1}cert.cfg" -batch -sha256 - docker run --rm -u root -v "$(pwd):/work" -w /work envoyproxy/envoy-build:gcc-99bbf423a0d1ff5a7332fb1e23bd62b90754fcd2 sh -c "apt-get update -y && apt-get install -y faketime && faketime '370 days ago' openssl x509 -req -days \"${days}\" -in \"${1}cert.csr\" -sha256 -CA \"${2}cert.pem\" -CAkey \"${2}key.pem\" -CAcreateserial -out \"${1}cert.pem\" -extensions v3_ca -extfile \"${1}cert.cfg\"" + docker run --rm -u root -v "$(pwd):/work" -w /work envoyproxy/envoy-build:gcc-v0.1.3 sh -c "apt-get update -y && apt-get install -y faketime && faketime '370 days ago' openssl x509 -req -days \"${days}\" -in \"${1}cert.csr\" -sha256 -CA \"${2}cert.pem\" -CAkey \"${2}key.pem\" -CAcreateserial -out \"${1}cert.pem\" -extensions v3_ca -extfile \"${1}cert.cfg\"" echo -e "// NOLINT(namespace-envoy)\nconstexpr char TEST_$(echo "$1" | tr "[:lower:]" "[:upper:]")_CERT_HASH[] = \"$(openssl x509 -in "${1}cert.pem" -noout -fingerprint -sha256 | cut -d"=" -f2)\";" > "${1}cert_hash.h" } From b718515e5b0b1535674bd640ddfbc39c0b0a5040 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 12 May 2026 15:34:36 +0100 Subject: [PATCH 290/750] test: Narrow config_test server mock deps and includes (#45028) these tests tend to timeout on build (oom suspected) reducing their includes should help Signed-off-by: Ryan Northey --- contrib/rocketmq_proxy/filters/network/test/BUILD | 1 - contrib/rocketmq_proxy/filters/network/test/config_test.cc | 1 - test/extensions/filters/common/ratelimit_config/BUILD | 2 +- .../filters/common/ratelimit_config/ratelimit_config_test.cc | 2 +- test/extensions/filters/http/aws_lambda/BUILD | 1 - test/extensions/filters/http/aws_lambda/config_test.cc | 1 - test/extensions/filters/http/buffer/BUILD | 1 - test/extensions/filters/http/buffer/config_test.cc | 1 - test/extensions/filters/http/dynamic_forward_proxy/BUILD | 3 +-- .../filters/http/dynamic_forward_proxy/config_test.cc | 3 +-- test/extensions/filters/http/grpc_http1_reverse_bridge/BUILD | 1 - .../filters/http/grpc_http1_reverse_bridge/config_test.cc | 1 - test/extensions/filters/http/header_to_metadata/BUILD | 1 - test/extensions/filters/http/header_to_metadata/config_test.cc | 1 - test/extensions/filters/http/jwt_authn/BUILD | 1 - test/extensions/filters/http/jwt_authn/filter_config_test.cc | 1 - test/extensions/filters/http/rbac/BUILD | 1 - test/extensions/filters/http/rbac/config_test.cc | 1 - test/extensions/filters/http/set_metadata/BUILD | 1 - test/extensions/filters/http/set_metadata/config_test.cc | 1 - test/extensions/filters/http/stateful_session/BUILD | 1 - test/extensions/filters/http/stateful_session/config_test.cc | 1 - .../network/thrift_proxy/filters/header_to_metadata/BUILD | 2 +- .../thrift_proxy/filters/header_to_metadata/config_test.cc | 1 - .../network/thrift_proxy/filters/payload_to_metadata/BUILD | 2 +- .../thrift_proxy/filters/payload_to_metadata/config_test.cc | 1 - test/extensions/http/header_validators/envoy_default/BUILD | 2 +- .../http/header_validators/envoy_default/config_test.cc | 2 +- test/extensions/matching/actions/format_string/BUILD | 2 +- test/extensions/matching/actions/format_string/config_test.cc | 2 +- test/extensions/stats_sinks/dog_statsd/BUILD | 2 +- test/extensions/stats_sinks/dog_statsd/config_test.cc | 2 +- test/extensions/stats_sinks/graphite_statsd/BUILD | 2 +- test/extensions/stats_sinks/graphite_statsd/config_test.cc | 2 +- test/extensions/stats_sinks/hystrix/BUILD | 2 +- test/extensions/stats_sinks/hystrix/config_test.cc | 2 +- test/extensions/stats_sinks/open_telemetry/BUILD | 2 +- test/extensions/stats_sinks/open_telemetry/config_test.cc | 2 +- test/extensions/stats_sinks/statsd/BUILD | 2 +- test/extensions/stats_sinks/statsd/config_test.cc | 2 +- test/extensions/upstreams/http/BUILD | 2 +- test/extensions/upstreams/http/config_test.cc | 2 +- 42 files changed, 22 insertions(+), 44 deletions(-) diff --git a/contrib/rocketmq_proxy/filters/network/test/BUILD b/contrib/rocketmq_proxy/filters/network/test/BUILD index 52533527f0db2..5adbf7bd341df 100644 --- a/contrib/rocketmq_proxy/filters/network/test/BUILD +++ b/contrib/rocketmq_proxy/filters/network/test/BUILD @@ -102,7 +102,6 @@ envoy_cc_test( "//contrib/rocketmq_proxy/filters/network/source:config", "//test/mocks/local_info:local_info_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:registry_lib", "@envoy_api//contrib/envoy/extensions/filters/network/rocketmq_proxy/v3:pkg_cc_proto", ], diff --git a/contrib/rocketmq_proxy/filters/network/test/config_test.cc b/contrib/rocketmq_proxy/filters/network/test/config_test.cc index c0900b0bdeecd..7b6e577aa6411 100644 --- a/contrib/rocketmq_proxy/filters/network/test/config_test.cc +++ b/contrib/rocketmq_proxy/filters/network/test/config_test.cc @@ -1,6 +1,5 @@ #include "test/mocks/local_info/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/registry.h" #include "contrib/envoy/extensions/filters/network/rocketmq_proxy/v3/rocketmq_proxy.pb.h" diff --git a/test/extensions/filters/common/ratelimit_config/BUILD b/test/extensions/filters/common/ratelimit_config/BUILD index a98ea5dcd28f7..4cd33d128b47a 100644 --- a/test/extensions/filters/common/ratelimit_config/BUILD +++ b/test/extensions/filters/common/ratelimit_config/BUILD @@ -31,7 +31,7 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/ratelimit:ratelimit_mocks", "//test/mocks/router:router_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/filters/common/ratelimit_config/ratelimit_config_test.cc b/test/extensions/filters/common/ratelimit_config/ratelimit_config_test.cc index 3ac092cabd700..dbd5ab46e9338 100644 --- a/test/extensions/filters/common/ratelimit_config/ratelimit_config_test.cc +++ b/test/extensions/filters/common/ratelimit_config/ratelimit_config_test.cc @@ -16,7 +16,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/ratelimit/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/printers.h" #include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" diff --git a/test/extensions/filters/http/aws_lambda/BUILD b/test/extensions/filters/http/aws_lambda/BUILD index ff95981d53dff..87eae4e34874b 100644 --- a/test/extensions/filters/http/aws_lambda/BUILD +++ b/test/extensions/filters/http/aws_lambda/BUILD @@ -59,7 +59,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/aws_lambda:config", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "@envoy_api//envoy/extensions/filters/http/aws_lambda/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/http/aws_lambda/config_test.cc b/test/extensions/filters/http/aws_lambda/config_test.cc index df74d57baf1f9..78f02388084ec 100644 --- a/test/extensions/filters/http/aws_lambda/config_test.cc +++ b/test/extensions/filters/http/aws_lambda/config_test.cc @@ -5,7 +5,6 @@ #include "source/extensions/filters/http/aws_lambda/config.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/buffer/BUILD b/test/extensions/filters/http/buffer/BUILD index a777195e98d6c..4dff575819df4 100644 --- a/test/extensions/filters/http/buffer/BUILD +++ b/test/extensions/filters/http/buffer/BUILD @@ -53,7 +53,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/buffer:config", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/buffer/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/buffer/config_test.cc b/test/extensions/filters/http/buffer/config_test.cc index 78023120d8bf5..4d8201a666875 100644 --- a/test/extensions/filters/http/buffer/config_test.cc +++ b/test/extensions/filters/http/buffer/config_test.cc @@ -5,7 +5,6 @@ #include "source/extensions/filters/http/buffer/config.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/http/dynamic_forward_proxy/BUILD b/test/extensions/filters/http/dynamic_forward_proxy/BUILD index 99eaea69c275f..169171067487e 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/BUILD +++ b/test/extensions/filters/http/dynamic_forward_proxy/BUILD @@ -20,8 +20,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/filters/http/dynamic_forward_proxy:config", - "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "@envoy_api//envoy/extensions/filters/http/dynamic_forward_proxy/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/http/dynamic_forward_proxy/config_test.cc b/test/extensions/filters/http/dynamic_forward_proxy/config_test.cc index ed2402b000c84..145c2a2eab7ba 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/config_test.cc +++ b/test/extensions/filters/http/dynamic_forward_proxy/config_test.cc @@ -4,8 +4,7 @@ #include "source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h" #include "source/extensions/filters/http/dynamic_forward_proxy/config.h" -#include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/http/grpc_http1_reverse_bridge/BUILD b/test/extensions/filters/http/grpc_http1_reverse_bridge/BUILD index 50010f98adce8..b619d3051a564 100644 --- a/test/extensions/filters/http/grpc_http1_reverse_bridge/BUILD +++ b/test/extensions/filters/http/grpc_http1_reverse_bridge/BUILD @@ -51,7 +51,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/grpc_http1_reverse_bridge:config", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/grpc_http1_reverse_bridge/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/grpc_http1_reverse_bridge/config_test.cc b/test/extensions/filters/http/grpc_http1_reverse_bridge/config_test.cc index fe08f536c4d0d..50a18adc1008b 100644 --- a/test/extensions/filters/http/grpc_http1_reverse_bridge/config_test.cc +++ b/test/extensions/filters/http/grpc_http1_reverse_bridge/config_test.cc @@ -4,7 +4,6 @@ #include "source/extensions/filters/http/grpc_http1_reverse_bridge/filter.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/http/header_to_metadata/BUILD b/test/extensions/filters/http/header_to_metadata/BUILD index 79f377c6dac6b..be3375e15cb25 100644 --- a/test/extensions/filters/http/header_to_metadata/BUILD +++ b/test/extensions/filters/http/header_to_metadata/BUILD @@ -32,7 +32,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/header_to_metadata:config", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/header_to_metadata/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/header_to_metadata/config_test.cc b/test/extensions/filters/http/header_to_metadata/config_test.cc index e26fa4d130c5a..638457b8e2d0d 100644 --- a/test/extensions/filters/http/header_to_metadata/config_test.cc +++ b/test/extensions/filters/http/header_to_metadata/config_test.cc @@ -7,7 +7,6 @@ #include "source/extensions/filters/http/header_to_metadata/header_to_metadata_filter.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index f7896e43afe15..afd14e7703126 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -69,7 +69,6 @@ envoy_extension_cc_test( "//source/extensions/filters/http/jwt_authn:config", "//test/extensions/filters/http/jwt_authn:test_common_lib", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/extensions/filters/http/jwt_authn/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/jwt_authn/filter_config_test.cc b/test/extensions/filters/http/jwt_authn/filter_config_test.cc index e4c52c39690cf..bf81d4fa35607 100644 --- a/test/extensions/filters/http/jwt_authn/filter_config_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_config_test.cc @@ -6,7 +6,6 @@ #include "test/extensions/filters/http/jwt_authn/test_common.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/test_runtime.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/rbac/BUILD b/test/extensions/filters/http/rbac/BUILD index 86c2a2eba3f1d..549ff23508a4b 100644 --- a/test/extensions/filters/http/rbac/BUILD +++ b/test/extensions/filters/http/rbac/BUILD @@ -23,7 +23,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/rbac:config", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "@envoy_api//envoy/config/rbac/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/rbac/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/rbac/config_test.cc b/test/extensions/filters/http/rbac/config_test.cc index 4966acfcc5c42..717fab401154d 100644 --- a/test/extensions/filters/http/rbac/config_test.cc +++ b/test/extensions/filters/http/rbac/config_test.cc @@ -7,7 +7,6 @@ #include "source/extensions/filters/http/rbac/rbac_filter.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/http/set_metadata/BUILD b/test/extensions/filters/http/set_metadata/BUILD index d5743f4291b03..7cf4e1e1b07ce 100644 --- a/test/extensions/filters/http/set_metadata/BUILD +++ b/test/extensions/filters/http/set_metadata/BUILD @@ -33,7 +33,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/set_metadata:config", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/set_metadata/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/set_metadata/config_test.cc b/test/extensions/filters/http/set_metadata/config_test.cc index 974a89fe0a3e8..0b12896953954 100644 --- a/test/extensions/filters/http/set_metadata/config_test.cc +++ b/test/extensions/filters/http/set_metadata/config_test.cc @@ -7,7 +7,6 @@ #include "source/extensions/filters/http/set_metadata/set_metadata_filter.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/stateful_session/BUILD b/test/extensions/filters/http/stateful_session/BUILD index 10be4144b8b89..b11038ad789a6 100644 --- a/test/extensions/filters/http/stateful_session/BUILD +++ b/test/extensions/filters/http/stateful_session/BUILD @@ -58,7 +58,6 @@ envoy_extension_cc_test( "//source/extensions/filters/http/stateful_session:config", "//test/mocks/http:stateful_session_mock", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:registry_lib", "//test/test_common:utility_lib", ], diff --git a/test/extensions/filters/http/stateful_session/config_test.cc b/test/extensions/filters/http/stateful_session/config_test.cc index 1d89b0a4ee118..1c682650fe9a2 100644 --- a/test/extensions/filters/http/stateful_session/config_test.cc +++ b/test/extensions/filters/http/stateful_session/config_test.cc @@ -2,7 +2,6 @@ #include "test/mocks/http/stateful_session.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/registry.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/network/thrift_proxy/filters/header_to_metadata/BUILD b/test/extensions/filters/network/thrift_proxy/filters/header_to_metadata/BUILD index 0814b7053dd5a..13887e4330c54 100644 --- a/test/extensions/filters/network/thrift_proxy/filters/header_to_metadata/BUILD +++ b/test/extensions/filters/network/thrift_proxy/filters/header_to_metadata/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//source/extensions/filters/network/thrift_proxy/filters/header_to_metadata:config", "//source/extensions/filters/network/thrift_proxy/filters/header_to_metadata:header_to_metadata_filter_lib", "//test/extensions/filters/network/thrift_proxy:mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:factory_context_mocks", "@envoy_api//envoy/extensions/filters/network/thrift_proxy/filters/header_to_metadata/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/network/thrift_proxy/filters/header_to_metadata/config_test.cc b/test/extensions/filters/network/thrift_proxy/filters/header_to_metadata/config_test.cc index bee231135f993..8747113412d00 100644 --- a/test/extensions/filters/network/thrift_proxy/filters/header_to_metadata/config_test.cc +++ b/test/extensions/filters/network/thrift_proxy/filters/header_to_metadata/config_test.cc @@ -8,7 +8,6 @@ #include "test/extensions/filters/network/thrift_proxy/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/BUILD b/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/BUILD index 3159ac46e9b4f..0a743a632e3fb 100644 --- a/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/BUILD +++ b/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata:config", "//source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata:payload_to_metadata_filter_lib", "//test/extensions/filters/network/thrift_proxy:mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:factory_context_mocks", "@envoy_api//envoy/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/config_test.cc b/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/config_test.cc index 8dacdf50e294f..4f44ed6a52a7d 100644 --- a/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/config_test.cc +++ b/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/config_test.cc @@ -8,7 +8,6 @@ #include "test/extensions/filters/network/thrift_proxy/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/http/header_validators/envoy_default/BUILD b/test/extensions/http/header_validators/envoy_default/BUILD index 7d5e9d001ccf6..f97d0c48c6ed8 100644 --- a/test/extensions/http/header_validators/envoy_default/BUILD +++ b/test/extensions/http/header_validators/envoy_default/BUILD @@ -141,7 +141,7 @@ envoy_extension_cc_test( deps = [ "//envoy/registry", "//source/extensions/http/header_validators/envoy_default:config", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/http/header_validators/envoy_default/v3:pkg_cc_proto", ], diff --git a/test/extensions/http/header_validators/envoy_default/config_test.cc b/test/extensions/http/header_validators/envoy_default/config_test.cc index 9191b5d3e05ea..2b345d4f671a3 100644 --- a/test/extensions/http/header_validators/envoy_default/config_test.cc +++ b/test/extensions/http/header_validators/envoy_default/config_test.cc @@ -4,7 +4,7 @@ #include "source/extensions/http/header_validators/envoy_default/config.h" #include "source/extensions/http/header_validators/envoy_default/header_validator_factory.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/matching/actions/format_string/BUILD b/test/extensions/matching/actions/format_string/BUILD index 7b5d4bb7e5fa2..abcf44eef357f 100644 --- a/test/extensions/matching/actions/format_string/BUILD +++ b/test/extensions/matching/actions/format_string/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//source/common/formatter:formatter_extension_lib", "//source/extensions/matching/actions/format_string:config", "//test/mocks/network:network_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", ], ) diff --git a/test/extensions/matching/actions/format_string/config_test.cc b/test/extensions/matching/actions/format_string/config_test.cc index 7028674e75f2d..af37dbc0def1a 100644 --- a/test/extensions/matching/actions/format_string/config_test.cc +++ b/test/extensions/matching/actions/format_string/config_test.cc @@ -1,7 +1,7 @@ #include "source/extensions/matching/actions/format_string/config.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "gtest/gtest.h" diff --git a/test/extensions/stats_sinks/dog_statsd/BUILD b/test/extensions/stats_sinks/dog_statsd/BUILD index 34eedda5d4dad..834b9f8ce5304 100644 --- a/test/extensions/stats_sinks/dog_statsd/BUILD +++ b/test/extensions/stats_sinks/dog_statsd/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//envoy/registry", "//source/common/protobuf:utility_lib", "//source/extensions/stat_sinks/dog_statsd:config", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/stats_sinks/dog_statsd/config_test.cc b/test/extensions/stats_sinks/dog_statsd/config_test.cc index 5b7938ed7a6a3..67b48abfcff39 100644 --- a/test/extensions/stats_sinks/dog_statsd/config_test.cc +++ b/test/extensions/stats_sinks/dog_statsd/config_test.cc @@ -7,7 +7,7 @@ #include "source/extensions/stat_sinks/common/statsd/statsd.h" #include "source/extensions/stat_sinks/dog_statsd/config.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/utility.h" diff --git a/test/extensions/stats_sinks/graphite_statsd/BUILD b/test/extensions/stats_sinks/graphite_statsd/BUILD index a03dd71feb395..4c69ae7212177 100644 --- a/test/extensions/stats_sinks/graphite_statsd/BUILD +++ b/test/extensions/stats_sinks/graphite_statsd/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//envoy/registry", "//source/common/protobuf:utility_lib", "//source/extensions/stat_sinks/graphite_statsd:config", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/stats_sinks/graphite_statsd/config_test.cc b/test/extensions/stats_sinks/graphite_statsd/config_test.cc index 526cef125f5ed..d059debbfd965 100644 --- a/test/extensions/stats_sinks/graphite_statsd/config_test.cc +++ b/test/extensions/stats_sinks/graphite_statsd/config_test.cc @@ -8,7 +8,7 @@ #include "source/extensions/stat_sinks/common/statsd/statsd.h" #include "source/extensions/stat_sinks/graphite_statsd/config.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/utility.h" diff --git a/test/extensions/stats_sinks/hystrix/BUILD b/test/extensions/stats_sinks/hystrix/BUILD index 6c6e6d746a2dc..2a8de832918c7 100644 --- a/test/extensions/stats_sinks/hystrix/BUILD +++ b/test/extensions/stats_sinks/hystrix/BUILD @@ -21,7 +21,7 @@ envoy_extension_cc_test( "//envoy/registry", "//source/common/protobuf:utility_lib", "//source/extensions/stat_sinks/hystrix:config", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/stats_sinks/hystrix/config_test.cc b/test/extensions/stats_sinks/hystrix/config_test.cc index b4fb9cbc4b9ff..e119d1df07ef2 100644 --- a/test/extensions/stats_sinks/hystrix/config_test.cc +++ b/test/extensions/stats_sinks/hystrix/config_test.cc @@ -5,7 +5,7 @@ #include "source/extensions/stat_sinks/hystrix/config.h" #include "source/extensions/stat_sinks/hystrix/hystrix.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/utility.h" diff --git a/test/extensions/stats_sinks/open_telemetry/BUILD b/test/extensions/stats_sinks/open_telemetry/BUILD index 4fd003624a366..1d1ac7f0da865 100644 --- a/test/extensions/stats_sinks/open_telemetry/BUILD +++ b/test/extensions/stats_sinks/open_telemetry/BUILD @@ -19,7 +19,7 @@ envoy_extension_cc_test( extension_names = ["envoy.stat_sinks.open_telemetry"], deps = [ "//source/extensions/stat_sinks/open_telemetry:config", - "//test/mocks/server:server_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/stat_sinks/open_telemetry/v3:pkg_cc_proto", ], diff --git a/test/extensions/stats_sinks/open_telemetry/config_test.cc b/test/extensions/stats_sinks/open_telemetry/config_test.cc index 2517a8a836d0f..186fb882015b6 100644 --- a/test/extensions/stats_sinks/open_telemetry/config_test.cc +++ b/test/extensions/stats_sinks/open_telemetry/config_test.cc @@ -3,7 +3,7 @@ #include "source/extensions/stat_sinks/open_telemetry/config.h" #include "source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/stats_sinks/statsd/BUILD b/test/extensions/stats_sinks/statsd/BUILD index 66f996ffcb860..0804ed3d88657 100644 --- a/test/extensions/stats_sinks/statsd/BUILD +++ b/test/extensions/stats_sinks/statsd/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//envoy/registry", "//source/common/protobuf:utility_lib", "//source/extensions/stat_sinks/statsd:config", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/stats_sinks/statsd/config_test.cc b/test/extensions/stats_sinks/statsd/config_test.cc index 470b1b24edd31..33254846493bc 100644 --- a/test/extensions/stats_sinks/statsd/config_test.cc +++ b/test/extensions/stats_sinks/statsd/config_test.cc @@ -8,7 +8,7 @@ #include "source/extensions/stat_sinks/common/statsd/statsd.h" #include "source/extensions/stat_sinks/statsd/config.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/utility.h" diff --git a/test/extensions/upstreams/http/BUILD b/test/extensions/upstreams/http/BUILD index e0c729de7c14f..66928e9f21791 100644 --- a/test/extensions/upstreams/http/BUILD +++ b/test/extensions/upstreams/http/BUILD @@ -26,7 +26,7 @@ envoy_cc_test( "//source/common/upstream:upstream_lib", "//source/extensions/upstreams/http:config", "//test/mocks/http:header_validator_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/upstreams/http/config_test.cc b/test/extensions/upstreams/http/config_test.cc index ec48e616a5211..73be85f40229d 100644 --- a/test/extensions/upstreams/http/config_test.cc +++ b/test/extensions/upstreams/http/config_test.cc @@ -8,7 +8,7 @@ #include "test/extensions/upstreams/http/config.pb.h" #include "test/extensions/upstreams/http/config.pb.validate.h" #include "test/mocks/http/header_validator.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" From 44a99ba95879555185d93d268332047d7ecb9576 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 12 May 2026 17:22:22 +0100 Subject: [PATCH 291/750] test/config_test: Give more memory to avoid oom (#45035) Signed-off-by: Ryan Northey --- test/config_test/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/config_test/BUILD b/test/config_test/BUILD index ff9c0e0fa87fc..5e210aa8ca89e 100644 --- a/test/config_test/BUILD +++ b/test/config_test/BUILD @@ -54,7 +54,7 @@ envoy_cc_test( "CONFIGS_TAR_PATH": "$(location :configs)", "GODEBUG": "cgocheck=0", }, - rbe_pool = "6gig", + rbe_pool = "linux_x64_large", deps = [":test_runner_lib"], ) From e287e0632242a4a0cabea5159fd184994998d326 Mon Sep 17 00:00:00 2001 From: Stephan Zuercher Date: Tue, 12 May 2026 10:17:12 -0700 Subject: [PATCH 292/750] docs: fix rate limit stat prefix for network local ratelimit filter (#44930) Risk Level: low Testing: n/a Docs Changes: yes Release Notes: n/a Platform Specific Features: n/a Fixes #44895 Signed-off-by: Stephan Zuercher --- .../listeners/network_filters/local_rate_limit_filter.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/configuration/listeners/network_filters/local_rate_limit_filter.rst b/docs/root/configuration/listeners/network_filters/local_rate_limit_filter.rst index 5848d667488ee..f5893eb079585 100644 --- a/docs/root/configuration/listeners/network_filters/local_rate_limit_filter.rst +++ b/docs/root/configuration/listeners/network_filters/local_rate_limit_filter.rst @@ -35,7 +35,7 @@ be immediately closed without further filter iteration. Statistics ---------- -Every configured local rate limit filter has statistics rooted at *local_ratelimit..* +Every configured local rate limit filter has statistics rooted at *local_rate_limit..* with the following statistics: .. csv-table:: From 461764f017a478dfd8e1ddd18e14be8dfe0929fd Mon Sep 17 00:00:00 2001 From: Biren Roy Date: Tue, 12 May 2026 13:22:01 -0400 Subject: [PATCH 293/750] Avoids copying std::function objects upon every buffer instance construction (#44842) This is accomplished by using `absl::AnyInvocable` rather than `std::function`. The Abseil type is similar, but move-only. Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: --------- Signed-off-by: Biren Roy --- .../test/mysql_ssl_integration_test.cc | 62 ++++++++++--------- envoy/buffer/buffer.h | 7 ++- .../common/integration/xds_test_server.cc | 11 ++-- source/common/buffer/watermark_buffer.h | 30 +++++---- test/common/network/connection_impl_test.cc | 56 ++++++++++------- test/common/tls/ssl_socket_test.cc | 18 +++--- .../starttls/starttls_integration_test.cc | 31 +++++----- test/integration/base_integration_test.cc | 11 ++-- test/integration/integration_tcp_client.cc | 24 ++++--- test/integration/tcp_proxy_integration.cc | 22 ++++--- .../integration/tcp_proxy_integration_test.cc | 22 ++++--- test/integration/tracked_watermark_buffer.cc | 9 +-- test/integration/tracked_watermark_buffer.h | 15 +++-- test/mocks/buffer/mocks.cc | 14 +++-- test/mocks/buffer/mocks.h | 36 ++++++----- test/mocks/event/mocks.cc | 11 ++-- 16 files changed, 215 insertions(+), 164 deletions(-) diff --git a/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc b/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc index a2f8ff86fbfb8..f017d4eb2381d 100644 --- a/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc +++ b/contrib/mysql_proxy/filters/network/test/mysql_ssl_integration_test.cc @@ -70,20 +70,23 @@ class MySQLSSLIntegrationTest : public testing::TestWithParam below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - client_write_buffer_ = - new NiceMock(below_low, above_high, above_overflow); - ON_CALL(*client_write_buffer_, move(_)) - .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); - ON_CALL(*client_write_buffer_, drain(_)) - .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); - return client_write_buffer_; - })) - .WillOnce(Invoke([&](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillOnce( + Invoke([&](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + client_write_buffer_ = new NiceMock( + std::move(below_low), std::move(above_high), std::move(above_overflow)); + ON_CALL(*client_write_buffer_, move(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); + ON_CALL(*client_write_buffer_, drain(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); + return client_write_buffer_; + })) + .WillOnce( + Invoke([&](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); // Create raw buffer and TLS transport socket factories. auto raw_config = @@ -647,20 +650,23 @@ class MySQLAllowIntegrationTest : public testing::TestWithParam below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - client_write_buffer_ = - new NiceMock(below_low, above_high, above_overflow); - ON_CALL(*client_write_buffer_, move(_)) - .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); - ON_CALL(*client_write_buffer_, drain(_)) - .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); - return client_write_buffer_; - })) - .WillRepeatedly(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillOnce( + Invoke([&](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + client_write_buffer_ = new NiceMock( + std::move(below_low), std::move(above_high), std::move(above_overflow)); + ON_CALL(*client_write_buffer_, move(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); + ON_CALL(*client_write_buffer_, drain(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); + return client_write_buffer_; + })) + .WillRepeatedly( + Invoke([](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); auto raw_config = std::make_unique(); diff --git a/envoy/buffer/buffer.h b/envoy/buffer/buffer.h index 0e0a7f7b2ab8f..f723fc43773c6 100644 --- a/envoy/buffer/buffer.h +++ b/envoy/buffer/buffer.h @@ -16,6 +16,7 @@ #include "source/common/common/utility.h" #include "absl/container/inlined_vector.h" +#include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "absl/types/span.h" @@ -552,9 +553,9 @@ class WatermarkFactory { * high watermark. * @return a newly created InstancePtr. */ - virtual InstancePtr createBuffer(std::function below_low_watermark, - std::function above_high_watermark, - std::function above_overflow_watermark) PURE; + virtual InstancePtr createBuffer(absl::AnyInvocable below_low_watermark, + absl::AnyInvocable above_high_watermark, + absl::AnyInvocable above_overflow_watermark) PURE; /** * Create and returns a buffer memory account. diff --git a/mobile/test/common/integration/xds_test_server.cc b/mobile/test/common/integration/xds_test_server.cc index 5ab34b0a3765d..a6025416d9a16 100644 --- a/mobile/test/common/integration/xds_test_server.cc +++ b/mobile/test/common/integration/xds_test_server.cc @@ -42,11 +42,12 @@ XdsTestServer::XdsTestServer() api_->allocateDispatcher("test_thread", Buffer::WatermarkFactoryPtr{mock_buffer_factory_}); ON_CALL(*mock_buffer_factory_, createBuffer_(_, _, _)) - .WillByDefault(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), - std::move(above_overflow)); - })); + .WillByDefault( + Invoke([](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); ON_CALL(factory_context_.server_context_, api()).WillByDefault(testing::ReturnRef(*api_)); ON_CALL(factory_context_, statsScope()) .WillByDefault(testing::ReturnRef(*stats_store_.rootScope())); diff --git a/source/common/buffer/watermark_buffer.h b/source/common/buffer/watermark_buffer.h index 6471b6405cd3b..8d2d448634712 100644 --- a/source/common/buffer/watermark_buffer.h +++ b/source/common/buffer/watermark_buffer.h @@ -9,6 +9,8 @@ #include "source/common/buffer/buffer_impl.h" +#include "absl/functional/any_invocable.h" + namespace Envoy { namespace Buffer { @@ -21,11 +23,12 @@ namespace Buffer { // It is only called on the first time the buffer overflows. class WatermarkBuffer : public OwnedImpl { public: - WatermarkBuffer(std::function below_low_watermark, - std::function above_high_watermark, - std::function above_overflow_watermark) - : below_low_watermark_(below_low_watermark), above_high_watermark_(above_high_watermark), - above_overflow_watermark_(above_overflow_watermark) {} + WatermarkBuffer(absl::AnyInvocable below_low_watermark, + absl::AnyInvocable above_high_watermark, + absl::AnyInvocable above_overflow_watermark) + : below_low_watermark_(std::move(below_low_watermark)), + above_high_watermark_(std::move(above_high_watermark)), + above_overflow_watermark_(std::move(above_overflow_watermark)) {} // Override all functions from Instance which can result in changing the size // of the underlying buffer. @@ -61,9 +64,9 @@ class WatermarkBuffer : public OwnedImpl { void commit(uint64_t length, absl::Span slices, ReservationSlicesOwnerPtr slices_owner) override; - std::function below_low_watermark_; - std::function above_high_watermark_; - std::function above_overflow_watermark_; + absl::AnyInvocable below_low_watermark_; + absl::AnyInvocable above_high_watermark_; + absl::AnyInvocable above_overflow_watermark_; // Used for enforcing buffer limits (off by default). If these are set to non-zero by a call to // setWatermarks() the watermark callbacks will be called as described above. @@ -194,11 +197,12 @@ class WatermarkBufferFactory : public WatermarkFactory { // Buffer::WatermarkFactory ~WatermarkBufferFactory() override; - InstancePtr createBuffer(std::function below_low_watermark, - std::function above_high_watermark, - std::function above_overflow_watermark) override { - return std::make_unique(below_low_watermark, above_high_watermark, - above_overflow_watermark); + InstancePtr createBuffer(absl::AnyInvocable below_low_watermark, + absl::AnyInvocable above_high_watermark, + absl::AnyInvocable above_overflow_watermark) override { + return std::make_unique(std::move(below_low_watermark), + std::move(above_high_watermark), + std::move(above_overflow_watermark)); } BufferMemoryAccountSharedPtr createAccount(Http::StreamResetHandler& reset_handler) override; diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index 9b42bb6342be2..34bbda1f983c7 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -239,15 +239,19 @@ class ConnectionImplTestBase { // Other calls for server sessions will by default get a normal OwnedImpl. EXPECT_CALL(*factory, createBuffer_(_, _, _)) .Times(AnyNumber()) - .WillOnce(Invoke([&](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - client_write_buffer_ = new MockWatermarkBuffer(below_low, above_high, above_overflow); - return client_write_buffer_; - })) - .WillRepeatedly(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillOnce( + Invoke([&](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + client_write_buffer_ = new MockWatermarkBuffer( + std::move(below_low), std::move(above_high), std::move(above_overflow)); + return client_write_buffer_; + })) + .WillRepeatedly( + Invoke([](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); } protected: @@ -262,12 +266,14 @@ class ConnectionImplTestBase { ConnectionMocks createConnectionMocks(bool create_timer = true) { auto dispatcher = std::make_unique>(); EXPECT_CALL(dispatcher->buffer_factory_, createBuffer_(_, _, _)) - .WillRepeatedly(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - // ConnectionImpl calls Envoy::MockBufferFactory::create(), which calls createBuffer_() - // and wraps the returned raw pointer below with a unique_ptr. - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillRepeatedly( + Invoke([](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + // ConnectionImpl calls Envoy::MockBufferFactory::create(), which calls + // createBuffer_() and wraps the returned raw pointer below with a unique_ptr. + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); if (create_timer) { // This timer will be returned (transferring ownership) to the ConnectionImpl when @@ -2211,10 +2217,12 @@ TEST_P(ConnectionImplTest, FlushWriteAndDelayConfigDisabledTest) { NiceMock callbacks; NiceMock dispatcher; EXPECT_CALL(dispatcher.buffer_factory_, createBuffer_(_, _, _)) - .WillRepeatedly(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillRepeatedly( + Invoke([](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); IoHandlePtr io_handle = std::make_unique(0); std::unique_ptr server_connection(new Network::ConnectionImpl( dispatcher, std::make_unique(std::move(io_handle), nullptr, nullptr), @@ -3227,10 +3235,12 @@ class MockTransportConnectionImplTest : public testing::Test { void initializeConnection() { EXPECT_CALL(dispatcher_, isThreadSafe()).WillRepeatedly(Return(true)); EXPECT_CALL(dispatcher_.buffer_factory_, createBuffer_(_, _, _)) - .WillRepeatedly(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillRepeatedly( + Invoke([](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); file_event_ = new NiceMock; EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) diff --git a/test/common/tls/ssl_socket_test.cc b/test/common/tls/ssl_socket_test.cc index 6f730cabd5b4b..b76019a36b5b6 100644 --- a/test/common/tls/ssl_socket_test.cc +++ b/test/common/tls/ssl_socket_test.cc @@ -6913,15 +6913,19 @@ class SslReadBufferLimitTest : public SslSocketTest { // By default, expect 4 buffers to be created - the client and server read and write buffers. EXPECT_CALL(*factory, createBuffer_(_, _, _)) .Times(4) - .WillOnce(Invoke([&](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - client_write_buffer = new MockWatermarkBuffer(below_low, above_high, above_overflow); + .WillOnce(Invoke([&](absl::AnyInvocable below_low, + absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + client_write_buffer = new MockWatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); return client_write_buffer; })) - .WillRepeatedly(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillRepeatedly( + Invoke([](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); initialize(); diff --git a/test/extensions/transport_sockets/starttls/starttls_integration_test.cc b/test/extensions/transport_sockets/starttls/starttls_integration_test.cc index cebdd24377916..d5ce42e8e2a41 100644 --- a/test/extensions/transport_sockets/starttls/starttls_integration_test.cc +++ b/test/extensions/transport_sockets/starttls/starttls_integration_test.cc @@ -180,22 +180,25 @@ void StartTlsIntegrationTest::initialize() { EXPECT_CALL(*mock_buffer_factory_, createBuffer_(_, _, _)) // Connection constructor will first create write buffer. // Test tracks how many bytes are sent. - .WillOnce(Invoke([&](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - client_write_buffer_ = - new NiceMock(below_low, above_high, above_overflow); - ON_CALL(*client_write_buffer_, move(_)) - .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); - ON_CALL(*client_write_buffer_, drain(_)) - .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); - return client_write_buffer_; - })) + .WillOnce( + Invoke([&](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + client_write_buffer_ = new NiceMock( + std::move(below_low), std::move(above_high), std::move(above_overflow)); + ON_CALL(*client_write_buffer_, move(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); + ON_CALL(*client_write_buffer_, drain(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); + return client_write_buffer_; + })) // Connection constructor will also create read buffer, but the test does // not track received bytes. - .WillOnce(Invoke([&](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillOnce( + Invoke([&](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); config_helper_.renameListener("tcp_proxy"); addStartTlsSwitchFilter(config_helper_); diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index 2ea388bb6c5b0..44f2ae7e50522 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -26,6 +26,7 @@ #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" +#include "absl/functional/any_invocable.h" #include "gtest/gtest.h" namespace Envoy { @@ -57,10 +58,12 @@ BaseIntegrationTest::BaseIntegrationTest(const InstanceConstSharedPtrFn& upstrea // necessary right now. timeSystem().realSleepDoNotUseWithoutScrutiny(std::chrono::milliseconds(10)); ON_CALL(*mock_buffer_factory_, createBuffer_(_, _, _)) - .WillByDefault(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillByDefault( + Invoke([](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); ON_CALL(factory_context_.server_context_, api()).WillByDefault(ReturnRef(*api_)); ON_CALL(factory_context_, statsScope()).WillByDefault(ReturnRef(*stats_store_.rootScope())); ON_CALL(factory_context_.server_context_, sslContextManager()) diff --git a/test/integration/integration_tcp_client.cc b/test/integration/integration_tcp_client.cc index e1655957dd6f7..dd3718a480393 100644 --- a/test/integration/integration_tcp_client.cc +++ b/test/integration/integration_tcp_client.cc @@ -21,6 +21,7 @@ #include "test/test_common/network_utility.h" #include "test/test_common/test_time_system.h" +#include "absl/functional/any_invocable.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -43,16 +44,19 @@ IntegrationTcpClient::IntegrationTcpClient( callbacks_(new ConnectionCallbacks(*this)) { EXPECT_CALL(factory, createBuffer_(_, _, _)) .Times(AtLeast(1)) - .WillOnce(Invoke([&](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - client_write_buffer_ = - new NiceMock(below_low, above_high, above_overflow); - return client_write_buffer_; - })) - .WillRepeatedly(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillOnce( + Invoke([&](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + client_write_buffer_ = new NiceMock( + std::move(below_low), std::move(above_high), std::move(above_overflow)); + return client_write_buffer_; + })) + .WillRepeatedly( + Invoke([](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); connection_ = dispatcher.createClientConnection( *Network::Utility::resolveUrl(fmt::format( diff --git a/test/integration/tcp_proxy_integration.cc b/test/integration/tcp_proxy_integration.cc index 7e67d9041f79f..95d3c37f1830c 100644 --- a/test/integration/tcp_proxy_integration.cc +++ b/test/integration/tcp_proxy_integration.cc @@ -1,5 +1,6 @@ #include "test/integration/tcp_proxy_integration.h" +#include "absl/functional/any_invocable.h" #include "gtest/gtest.h" namespace Envoy { @@ -32,16 +33,17 @@ BaseTcpProxySslIntegrationTest::ClientSslConnection::ClientSslConnection( // buffer. This allows us to track the bytes actually written to the socket. EXPECT_CALL(*parent.mock_buffer_factory_, createBuffer_(_, _, _)) .Times(::testing::AtLeast(1)) - .WillOnce(Invoke([&](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - client_write_buffer_ = - new NiceMock(below_low, above_high, above_overflow); - ON_CALL(*client_write_buffer_, move(_)) - .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); - ON_CALL(*client_write_buffer_, drain(_)) - .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); - return client_write_buffer_; - })); + .WillOnce( + Invoke([&](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + client_write_buffer_ = new NiceMock( + std::move(below_low), std::move(above_high), std::move(above_overflow)); + ON_CALL(*client_write_buffer_, move(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); + ON_CALL(*client_write_buffer_, drain(_)) + .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); + return client_write_buffer_; + })); // Set up the SSL client. Network::Address::InstanceConstSharedPtr address = Ssl::getSslAddress(parent.version_, parent.lookupPort("tcp_proxy")); diff --git a/test/integration/tcp_proxy_integration_test.cc b/test/integration/tcp_proxy_integration_test.cc index 9fc9fe8b475f9..a18d060ad8d89 100644 --- a/test/integration/tcp_proxy_integration_test.cc +++ b/test/integration/tcp_proxy_integration_test.cc @@ -25,6 +25,7 @@ #include "test/integration/utility.h" #include "test/test_common/registry.h" +#include "absl/functional/any_invocable.h" #include "gtest/gtest.h" using testing::_; @@ -2529,16 +2530,17 @@ TEST_P(TcpProxySslIntegrationTest, OnDownstreamTlsHandshakeModeWithEarlyData) { // buffer. This allows us to track the bytes actually written to the socket. EXPECT_CALL(*mock_buffer_factory_, createBuffer_(_, _, _)) .Times(AtLeast(1)) - .WillOnce(Invoke([&](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - client_write_buffer = - new NiceMock(below_low, above_high, above_overflow); - ON_CALL(*client_write_buffer, move(_)) - .WillByDefault(Invoke(client_write_buffer, &MockWatermarkBuffer::baseMove)); - ON_CALL(*client_write_buffer, drain(_)) - .WillByDefault(Invoke(client_write_buffer, &MockWatermarkBuffer::trackDrains)); - return client_write_buffer; - })); + .WillOnce( + Invoke([&](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + client_write_buffer = new NiceMock( + std::move(below_low), std::move(above_high), std::move(above_overflow)); + ON_CALL(*client_write_buffer, move(_)) + .WillByDefault(Invoke(client_write_buffer, &MockWatermarkBuffer::baseMove)); + ON_CALL(*client_write_buffer, drain(_)) + .WillByDefault(Invoke(client_write_buffer, &MockWatermarkBuffer::trackDrains)); + return client_write_buffer; + })); // Set up the SSL client. Network::Address::InstanceConstSharedPtr address = diff --git a/test/integration/tracked_watermark_buffer.cc b/test/integration/tracked_watermark_buffer.cc index 61b1fe645ba91..cc50b5b606def 100644 --- a/test/integration/tracked_watermark_buffer.cc +++ b/test/integration/tracked_watermark_buffer.cc @@ -27,9 +27,9 @@ TrackedWatermarkBufferFactory::~TrackedWatermarkBufferFactory() { } Buffer::InstancePtr -TrackedWatermarkBufferFactory::createBuffer(std::function below_low_watermark, - std::function above_high_watermark, - std::function above_overflow_watermark) { +TrackedWatermarkBufferFactory::createBuffer(absl::AnyInvocable below_low_watermark, + absl::AnyInvocable above_high_watermark, + absl::AnyInvocable above_overflow_watermark) { absl::MutexLock lock(mutex_); uint64_t idx = next_idx_++; ++active_buffer_count_; @@ -88,7 +88,8 @@ TrackedWatermarkBufferFactory::createBuffer(std::function below_low_wate actively_bound_buffers_.emplace(buffer); } }, - below_low_watermark, above_high_watermark, above_overflow_watermark); + std::move(below_low_watermark), std::move(above_high_watermark), + std::move(above_overflow_watermark)); } BufferMemoryAccountSharedPtr diff --git a/test/integration/tracked_watermark_buffer.h b/test/integration/tracked_watermark_buffer.h index 19144abac6dab..65a81de22d754 100644 --- a/test/integration/tracked_watermark_buffer.h +++ b/test/integration/tracked_watermark_buffer.h @@ -9,6 +9,7 @@ #include "test/test_common/utility.h" #include "absl/container/node_hash_map.h" +#include "absl/functional/any_invocable.h" #include "absl/synchronization/mutex.h" #include "absl/synchronization/notification.h" @@ -23,9 +24,11 @@ class TrackedWatermarkBuffer : public Buffer::WatermarkBuffer { std::function update_high_watermark, std::function on_delete, std::function on_bind, - std::function below_low_watermark, std::function above_high_watermark, - std::function above_overflow_watermark) - : WatermarkBuffer(below_low_watermark, above_high_watermark, above_overflow_watermark), + absl::AnyInvocable below_low_watermark, + absl::AnyInvocable above_high_watermark, + absl::AnyInvocable above_overflow_watermark) + : WatermarkBuffer(std::move(below_low_watermark), std::move(above_high_watermark), + std::move(above_overflow_watermark)), update_size_(update_size), update_high_watermark_(update_high_watermark), on_delete_(on_delete), on_bind_(on_bind) {} ~TrackedWatermarkBuffer() override { on_delete_(this); } @@ -66,9 +69,9 @@ class TrackedWatermarkBufferFactory : public WatermarkBufferFactory { TrackedWatermarkBufferFactory(uint32_t min_tracking_bytes); ~TrackedWatermarkBufferFactory() override; // Buffer::WatermarkFactory - Buffer::InstancePtr createBuffer(std::function below_low_watermark, - std::function above_high_watermark, - std::function above_overflow_watermark) override; + Buffer::InstancePtr createBuffer(absl::AnyInvocable below_low_watermark, + absl::AnyInvocable above_high_watermark, + absl::AnyInvocable above_overflow_watermark) override; BufferMemoryAccountSharedPtr createAccount(Http::StreamResetHandler& reset_handler) override; void unregisterAccount(const BufferMemoryAccountSharedPtr& account, absl::optional current_class) override; diff --git a/test/mocks/buffer/mocks.cc b/test/mocks/buffer/mocks.cc index 56fb48e0d0fe2..75de3f715d58b 100644 --- a/test/mocks/buffer/mocks.cc +++ b/test/mocks/buffer/mocks.cc @@ -5,10 +5,11 @@ namespace Envoy { template <> -MockBufferBase::MockBufferBase(std::function below_low, - std::function above_high, - std::function above_overflow) - : Buffer::WatermarkBuffer(below_low, above_high, above_overflow) {} +MockBufferBase::MockBufferBase(absl::AnyInvocable below_low, + absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) + : Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)) {} template <> MockBufferBase::MockBufferBase() @@ -16,8 +17,9 @@ MockBufferBase::MockBufferBase() ASSERT(0); // This constructor is not supported for WatermarkBuffer. } template <> -MockBufferBase::MockBufferBase(std::function, std::function, - std::function) { +MockBufferBase::MockBufferBase(absl::AnyInvocable, + absl::AnyInvocable, + absl::AnyInvocable) { ASSERT(0); // This constructor is not supported for OwnedImpl. } diff --git a/test/mocks/buffer/mocks.h b/test/mocks/buffer/mocks.h index 100c77eb8d4ea..c8feb00c9fc8f 100644 --- a/test/mocks/buffer/mocks.h +++ b/test/mocks/buffer/mocks.h @@ -11,6 +11,7 @@ #include "test/test_common/printers.h" #include "test/test_common/utility.h" +#include "absl/functional/any_invocable.h" #include "gmock/gmock.h" namespace Envoy { @@ -19,8 +20,8 @@ namespace Envoy { template class MockBufferBase : public BaseClass { public: MockBufferBase(); - MockBufferBase(std::function below_low, std::function above_high, - std::function above_overflow); + MockBufferBase(absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow); MOCK_METHOD(void, move, (Buffer::Instance & rhs)); MOCK_METHOD(void, move, (Buffer::Instance & rhs, uint64_t length)); @@ -43,15 +44,15 @@ template class MockBufferBase : public BaseClass { }; template <> -MockBufferBase::MockBufferBase(std::function below_low, - std::function above_high, - std::function above_overflow); +MockBufferBase::MockBufferBase(absl::AnyInvocable below_low, + absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow); template <> MockBufferBase::MockBufferBase(); template <> -MockBufferBase::MockBufferBase(std::function below_low, - std::function above_high, - std::function above_overflow); +MockBufferBase::MockBufferBase(absl::AnyInvocable below_low, + absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow); template <> MockBufferBase::MockBufferBase(); class MockBuffer : public MockBufferBase { @@ -65,9 +66,9 @@ class MockWatermarkBuffer : public MockBufferBase { public: using BaseClass = MockBufferBase; - MockWatermarkBuffer(std::function below_low, std::function above_high, - std::function above_overflow) - : BaseClass(below_low, above_high, above_overflow) { + MockWatermarkBuffer(absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) + : BaseClass(std::move(below_low), std::move(above_high), std::move(above_overflow)) { ON_CALL(*this, move(testing::_)) .WillByDefault(testing::Invoke(this, &MockWatermarkBuffer::baseMove)); } @@ -78,17 +79,18 @@ class MockBufferFactory : public Buffer::WatermarkFactory { MockBufferFactory(); ~MockBufferFactory() override; - Buffer::InstancePtr createBuffer(std::function below_low, - std::function above_high, - std::function above_overflow) override { - auto buffer = Buffer::InstancePtr{createBuffer_(below_low, above_high, above_overflow)}; + Buffer::InstancePtr createBuffer(absl::AnyInvocable below_low, + absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) override { + auto buffer = Buffer::InstancePtr{ + createBuffer_(std::move(below_low), std::move(above_high), std::move(above_overflow))}; ASSERT(buffer != nullptr); return buffer; } MOCK_METHOD(Buffer::Instance*, createBuffer_, - (std::function below_low, std::function above_high, - std::function above_overflow)); + (absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow)); MOCK_METHOD(Buffer::BufferMemoryAccountSharedPtr, createAccount, (Http::StreamResetHandler&)); MOCK_METHOD(uint64_t, resetAccountsGivenPressure, (float)); diff --git a/test/mocks/event/mocks.cc b/test/mocks/event/mocks.cc index 5a557332e31b7..36e7fce7246ad 100644 --- a/test/mocks/event/mocks.cc +++ b/test/mocks/event/mocks.cc @@ -1,5 +1,6 @@ #include "mocks.h" +#include "absl/functional/any_invocable.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -38,10 +39,12 @@ MockDispatcher::MockDispatcher(const std::string& name) : name_(name) { }); ON_CALL(buffer_factory_, createBuffer_(_, _, _)) - .WillByDefault(Invoke([](std::function below_low, std::function above_high, - std::function above_overflow) -> Buffer::Instance* { - return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); - })); + .WillByDefault( + Invoke([](absl::AnyInvocable below_low, absl::AnyInvocable above_high, + absl::AnyInvocable above_overflow) -> Buffer::Instance* { + return new Buffer::WatermarkBuffer(std::move(below_low), std::move(above_high), + std::move(above_overflow)); + })); ON_CALL(*this, isThreadSafe()).WillByDefault([thread_factory, thread_id]() { return thread_factory->currentThreadId() == thread_id; }); From adf73a298dccbd829460d016e9e9580036217ea8 Mon Sep 17 00:00:00 2001 From: code Date: Wed, 13 May 2026 01:36:40 +0800 Subject: [PATCH 294/750] route: add refresh cluster support to weighted clusters (#44823) Commit Message: route: add refresh cluster support to weighted clusters Additional Description: This PR add the refreshRouteCluster() support to weighted cluster specifier. If a filter (e.g. retry of router filter) call the refreshRouteCluster(), now the weighted cluster will reselect another un-chosen cluster. This is a behavior change in some way because the refreshRouteCluster() of weighted cluster specifier was no-op before but now will refresh the cluster. But note, this only triggers when the filter calling the `refreshRouteCluster()` (for now, only transform filter provided a API flag to call this method). So the change should be expected. Combine with https://github.com/envoyproxy/envoy/pull/44719 to support cross cluster retry for weighted clusters. Risk Level: mid, touch core code. Testing: unit. Docs Changes: n/a. Release Notes: added. Platform Specific Features: n/a. --------- Signed-off-by: wbpcode/wangbaiping Signed-off-by: code --- changelogs/current.yaml | 8 + source/common/router/delegating_route_impl.h | 6 +- .../router/weighted_cluster_specifier.cc | 287 +++++++++++------- .../router/weighted_cluster_specifier.h | 32 +- test/common/router/config_impl_test.cc | 213 +++++++++++++ 5 files changed, 427 insertions(+), 119 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 0fc3636aa24fb..778f4aa2c644a 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -81,6 +81,14 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: router + change: | + Added support for ``refreshRouteCluster`` on weighted cluster routes. When a filter calls + ``refreshRouteCluster()``, the weighted cluster entry will select a different cluster from the + configured pool, avoiding previously-tried clusters within the same request. Once all clusters + have been tried, the selection pool resets so that any cluster may be chosen again. This enables + filters to implement per-attempt cluster failover across weighted clusters without replacing the + entire route. - area: tcp_proxy change: | Added :ref:`check_drain_close diff --git a/source/common/router/delegating_route_impl.h b/source/common/router/delegating_route_impl.h index 8ef5622c33514..98e36a52a4642 100644 --- a/source/common/router/delegating_route_impl.h +++ b/source/common/router/delegating_route_impl.h @@ -148,8 +148,10 @@ class DynamicRouteEntry : public DelegatingRouteEntry { const std::string& clusterName() const override { return cluster_name_; } -private: - const std::string cluster_name_; +protected: + // Keep this as non-const member since some derived class may want to update the cluster name + // dynamically based on the request. + std::string cluster_name_; }; } // namespace Router diff --git a/source/common/router/weighted_cluster_specifier.cc b/source/common/router/weighted_cluster_specifier.cc index de40278a80e53..7b2d5f0f33ec5 100644 --- a/source/common/router/weighted_cluster_specifier.cc +++ b/source/common/router/weighted_cluster_specifier.cc @@ -1,5 +1,8 @@ #include "source/common/router/weighted_cluster_specifier.h" +#include +#include + #include "source/common/config/well_known_names.h" #include "source/common/router/config_utility.h" @@ -18,20 +21,75 @@ absl::Status validateWeightedClusterSpecifier(const ClusterWeightProto& cluster) return absl::InvalidArgumentError(error); } -absl::StatusOr> WeightedClustersConfigEntry::create( - const ClusterWeightProto& cluster, const MetadataMatchCriteria* parent_metadata_match, - std::string&& runtime_key, Server::Configuration::ServerFactoryContext& context) { +template +absl::optional pickClusterIndex(absl::Span weighed_clusters, uint64_t random_value, + uint64_t total_cluster_weight, Runtime::Loader& loader) { + // The total_cluster_weight can be cached and be used directly only in the case that all + // following conditions are met: + // * the runtime key prefix is not configured which means the cluster weight is static and will + // not be changed through runtime. + // * all clusters are candidates for the selection which means this selection is not for refresh. + const bool need_recompute_total_weight = total_cluster_weight == 0; + + absl::InlinedVector cluster_weights; + if (need_recompute_total_weight) { + cluster_weights.reserve(weighed_clusters.size()); + for (const auto& cluster : weighed_clusters) { + auto cluster_weight = cluster->clusterWeight(loader); + cluster_weights.push_back(cluster_weight); + if (cluster_weight > std::numeric_limits::max() - total_cluster_weight) { + IS_ENVOY_BUG("Sum of weight cannot overflow 2^32"); + return absl::nullopt; + } + total_cluster_weight += cluster_weight; + } + if (total_cluster_weight == 0) { + IS_ENVOY_BUG("Sum of weight cannot be zero"); + return absl::nullopt; + } + } + + const uint64_t selected_value = random_value % total_cluster_weight; + uint64_t begin = 0; + uint64_t end = 0; + + // Find the right cluster to route to based on the interval in which + // the selected value falls. The intervals are determined as + // [0, cluster1_weight), [cluster1_weight, cluster1_weight+cluster2_weight),.. + for (size_t i = 0; i < weighed_clusters.size(); ++i) { + if (need_recompute_total_weight) { + end = begin + cluster_weights[i]; + } else { + end = begin + weighed_clusters[i]->clusterWeight(); + } + if (selected_value >= begin && selected_value < end) { + return weighed_clusters[i]->clusterIndex(); + } + begin = end; + } + + IS_ENVOY_BUG("unexpected"); + return absl::nullopt; +} + +absl::StatusOr> +WeightedClustersConfigEntry::create(const ClusterWeightProto& cluster, uint64_t index, + const MetadataMatchCriteria* parent_metadata_match, + absl::string_view runtime_key_prefix, + Server::Configuration::ServerFactoryContext& context) { RETURN_IF_NOT_OK(validateWeightedClusterSpecifier(cluster)); return std::unique_ptr(new WeightedClustersConfigEntry( - cluster, parent_metadata_match, std::move(runtime_key), context)); + cluster, index, parent_metadata_match, runtime_key_prefix, context)); } WeightedClustersConfigEntry::WeightedClustersConfigEntry( - const envoy::config::route::v3::WeightedCluster::ClusterWeight& cluster, - const MetadataMatchCriteria* parent_metadata_match, std::string&& runtime_key, + const envoy::config::route::v3::WeightedCluster::ClusterWeight& cluster, uint64_t index, + const MetadataMatchCriteria* parent_metadata_match, absl::string_view runtime_key_prefix, Server::Configuration::ServerFactoryContext& context) - : runtime_key_(std::move(runtime_key)), - cluster_weight_(PROTOBUF_GET_WRAPPED_REQUIRED(cluster, weight)), + : runtime_key_(runtime_key_prefix.empty() + ? "" + : fmt::format("{}.{}", runtime_key_prefix, cluster.name())), + cluster_weight_(PROTOBUF_GET_WRAPPED_REQUIRED(cluster, weight)), cluster_index_(index), per_filter_configs_( THROW_OR_RETURN_VALUE(PerFilterConfigs::create(cluster.typed_per_filter_config(), context, context.messageValidationVisitor()), @@ -71,25 +129,26 @@ WeightedClusterSpecifierPlugin::WeightedClusterSpecifierPlugin( const MetadataMatchCriteria* parent_metadata_match, absl::string_view route_name, Server::Configuration::ServerFactoryContext& context, absl::Status& creation_status) : loader_(context.runtime()), random_value_header_(weighted_clusters.header_name()), - runtime_key_prefix_(weighted_clusters.runtime_key_prefix()), use_hash_policy_(weighted_clusters.random_value_specifier_case() == WeightedClusterProto::kUseHashPolicy ? weighted_clusters.use_hash_policy().value() : false) { + // The runtime key prefix is used to construct the runtime key for each cluster's weight. If the + // prefix is not provided, the runtime key will be empty, and the cluster weight will always be + // determined by the static weight in the config. absl::string_view runtime_key_prefix = weighted_clusters.runtime_key_prefix(); weighted_clusters_.reserve(weighted_clusters.clusters().size()); - + uint64_t total_cluster_weight = 0; for (const ClusterWeightProto& cluster : weighted_clusters.clusters()) { - auto cluster_entry = - THROW_OR_RETURN_VALUE(WeightedClustersConfigEntry::create( - cluster, parent_metadata_match, - absl::StrCat(runtime_key_prefix, ".", cluster.name()), context), - std::shared_ptr); + auto cluster_entry = THROW_OR_RETURN_VALUE( + WeightedClustersConfigEntry::create(cluster, weighted_clusters_.size(), + parent_metadata_match, runtime_key_prefix, context), + std::shared_ptr); weighted_clusters_.emplace_back(std::move(cluster_entry)); - total_cluster_weight_ += weighted_clusters_.back()->clusterWeight(loader_); - if (total_cluster_weight_ > std::numeric_limits::max()) { + total_cluster_weight += weighted_clusters_.back()->clusterWeight(loader_); + if (total_cluster_weight > std::numeric_limits::max()) { creation_status = absl::InvalidArgumentError( fmt::format("The sum of weights of all weighted clusters of route {} exceeds {}", route_name, std::numeric_limits::max())); @@ -98,11 +157,18 @@ WeightedClusterSpecifierPlugin::WeightedClusterSpecifierPlugin( } // Reject the config if the total_weight of all clusters is 0. - if (total_cluster_weight_ == 0) { + if (total_cluster_weight == 0) { creation_status = absl::InvalidArgumentError( "Sum of weights in the weighted_cluster must be greater than 0."); return; } + + // If runtime key prefix is not configured, the total cluster weight will be static and can be + // cached. Otherwise, the total cluster weight needs to be computed for every request since the + // cluster weight can be dynamically changed through runtime. + if (runtime_key_prefix.empty()) { + total_cluster_weight_ = total_cluster_weight; + } } /** @@ -111,12 +177,14 @@ WeightedClusterSpecifierPlugin::WeightedClusterSpecifierPlugin( * RouteEntryImplBase object. Almost all functions in this class forward calls back to the * parent, with the exception of clusterName, routeEntry, and metadataMatchCriteria. */ -class WeightedClusterEntry : public DynamicRouteEntry { +class WeightedClusterEntry : public DynamicRouteEntry, public Logger::Loggable { public: WeightedClusterEntry(RouteConstSharedPtr route, std::string&& cluster_name, - WeightedClustersConfigEntryConstSharedPtr config) - : DynamicRouteEntry(route, std::move(cluster_name)), config_(std::move(config)) { - ASSERT(config_ != nullptr); + std::shared_ptr plugin, + const WeightedClustersConfigEntry* config, uint64_t random_value) + : DynamicRouteEntry(route, std::move(cluster_name)), plugin_(std::move(plugin)), + random_value_(random_value), config_(config) { + ASSERT(plugin_ != nullptr); } const std::string& clusterName() const override { @@ -183,8 +251,70 @@ class WeightedClusterEntry : public DynamicRouteEntry { } return result; } + void refreshRouteCluster(const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo&) const override { + // Note this function will refresh the target cluster but the cluster specific + // metadata match criteria, header manipulation and so on may have been applied to the request + // based on the initially selected cluster configuration and won't be applied again. + // This is known limitation of the current implementation. + // + // Dynamic route entry is created for each request and only used for single request in single + // thread. So, it is safe to update the cluster config or state in the route entry without + // worrying about thread safety or affecting other requests. + const_cast(this)->refreshRouteClusterInternal(headers); + } private: + void refreshRouteClusterInternal(const Http::RequestHeaderMap& headers) { + // Put the current cluster index in used_cluster_indices_ to avoid selecting the same cluster + // again in the next round of selection. + used_cluster_indices_.insert(config_->clusterIndex()); + if (used_cluster_indices_.size() == plugin_->weighted_clusters_.size()) { + // All clusters have been used. Clear the used_cluster_indices_ to make all clusters eligible + // for selection again. + used_cluster_indices_.clear(); + } + + absl::optional cluster_index; + + if (used_cluster_indices_.empty()) { + // If all clusters are eligible for selection, we can directly pick cluster from the full list + // of clusters. + cluster_index = + pickClusterIndex(absl::MakeConstSpan(plugin_->weighted_clusters_), random_value_, + plugin_->total_cluster_weight_, plugin_->loader_); + } else { + // If only part of the clusters are eligible for selection, we need to construct a temporary + // vector that only contains the eligible clusters and pick cluster from it. + absl::InlinedVector candidate_clusters; + for (const auto& cluster : plugin_->weighted_clusters_) { + if (used_cluster_indices_.find(cluster->clusterIndex()) == used_cluster_indices_.end()) { + candidate_clusters.push_back(cluster.get()); + } + } + ASSERT(!candidate_clusters.empty()); + // 0 is passed in as total_cluster_weight here because the weight of each cluster will be + // re-computed in pickClusterIndex. + cluster_index = pickClusterIndex(absl::MakeConstSpan(candidate_clusters), random_value_, 0, + plugin_->loader_); + } + + if (!cluster_index.has_value()) { + ENVOY_LOG(warn, "Failed to pick a new cluster for route {}. No eligible cluster found.", + this->routeName()); + return; + } + + const auto* config = plugin_->weighted_clusters_[cluster_index.value()].get(); + if (config->cluster_name_.empty()) { + ASSERT(!config->cluster_header_name_.get().empty()); + const auto entries = headers.get(config->cluster_header_name_); + auto cluster = entries.empty() ? absl::string_view{} : entries[0]->value().getStringView(); + cluster_name_.assign(cluster.data(), cluster.size()); + } + config_ = config; + } + const HeaderParser& requestHeaderParser() const { if (config_->request_headers_parser_ != nullptr) { return *config_->request_headers_parser_; @@ -198,30 +328,28 @@ class WeightedClusterEntry : public DynamicRouteEntry { return HeaderParser::defaultParser(); } - WeightedClustersConfigEntryConstSharedPtr config_; -}; + std::shared_ptr plugin_; + const uint64_t random_value_{0}; -// Selects a cluster depending on weight parameters from configuration or from headers. -// This function takes into account the weights set through configuration or through -// runtime parameters. -// Returns selected cluster, or nullptr if weighted configuration is invalid. -RouteConstSharedPtr WeightedClusterSpecifierPlugin::pickWeightedCluster( - RouteEntryAndRouteConstSharedPtr parent, const Http::RequestHeaderMap& headers, - const StreamInfo::StreamInfo& stream_info, const uint64_t random_value) const { - absl::optional hash_value; + mutable std::set used_cluster_indices_; + mutable const WeightedClustersConfigEntry* config_; +}; - // Only use hash policy if explicitly enabled via use_hash_policy field +RouteConstSharedPtr WeightedClusterSpecifierPlugin::route(RouteEntryAndRouteConstSharedPtr parent, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& stream_info, + uint64_t random) const { + absl::optional random_value_from_hash; + // Only use hash policy if explicitly enabled via use_hash_policy field. if (use_hash_policy_) { const auto* route_hash_policy = parent->hashPolicy(); if (route_hash_policy != nullptr) { - hash_value = route_hash_policy->generateHash( + random_value_from_hash = route_hash_policy->generateHash( OptRef(headers), OptRef(stream_info), nullptr); } } - const uint64_t selection_value = hash_value.has_value() ? hash_value.value() : random_value; - absl::optional random_value_from_header; // Retrieve the random value from the header if corresponding header name is specified. // weighted_clusters_config_ is known not to be nullptr here. If it were, pickWeightedCluster @@ -231,8 +359,8 @@ RouteConstSharedPtr WeightedClusterSpecifierPlugin::pickWeightedCluster( if (header_value.size() == 1) { // We expect single-valued header here, otherwise it will potentially cause inconsistent // weighted cluster picking throughout the process because different values are used to - // compute the selected value. So, we treat multi-valued header as invalid input and fall back - // to use internally generated random number. + // compute the selected value. So, we treat multi-valued header as invalid input and fall + // back to use internally generated random number. uint64_t random_value = 0; if (absl::SimpleAtoi(header_value[0]->value().getStringView(), &random_value)) { random_value_from_header = random_value; @@ -247,78 +375,29 @@ RouteConstSharedPtr WeightedClusterSpecifierPlugin::pickWeightedCluster( } } - const bool runtime_key_prefix_configured = !runtime_key_prefix_.empty(); - uint32_t total_cluster_weight = total_cluster_weight_; - absl::InlinedVector cluster_weights; + const uint64_t random_value = + random_value_from_header.has_value() ? random_value_from_header.value() + : random_value_from_hash.has_value() ? random_value_from_hash.value() + : random; - // if runtime config is used, we need to recompute total_weight. - if (runtime_key_prefix_configured) { - // Temporary storage to hold consistent cluster weights. Since cluster weight - // can be changed with runtime keys, we need a way to gather all the weight - // and aggregate the total without a change in between. - // The InlinedVector will be able to handle at least 4 cluster weights - // without allocation. For cases when more clusters are needed, it is - // reserved to ensure at most a single allocation. - cluster_weights.reserve(weighted_clusters_.size()); - - total_cluster_weight = 0; - for (const auto& cluster : weighted_clusters_) { - auto cluster_weight = cluster->clusterWeight(loader_); - cluster_weights.push_back(cluster_weight); - if (cluster_weight > std::numeric_limits::max() - total_cluster_weight) { - IS_ENVOY_BUG("Sum of weight cannot overflow 2^32"); - return nullptr; - } - total_cluster_weight += cluster_weight; - } - } - - if (total_cluster_weight == 0) { - IS_ENVOY_BUG("Sum of weight cannot be zero"); + absl::optional cluster_index = pickClusterIndex( + absl::MakeConstSpan(weighted_clusters_), random_value, total_cluster_weight_, loader_); + if (!cluster_index.has_value()) { return nullptr; } - const uint64_t selected_value = - (random_value_from_header.has_value() ? random_value_from_header.value() : selection_value) % - total_cluster_weight; - uint64_t begin = 0; - uint64_t end = 0; - auto cluster_weight = cluster_weights.begin(); - - // Find the right cluster to route to based on the interval in which - // the selected value falls. The intervals are determined as - // [0, cluster1_weight), [cluster1_weight, cluster1_weight+cluster2_weight),.. - for (const auto& cluster : weighted_clusters_) { - if (runtime_key_prefix_configured) { - end = begin + *cluster_weight++; - } else { - end = begin + cluster->clusterWeight(loader_); - } - - if (selected_value >= begin && selected_value < end) { - if (!cluster->cluster_name_.empty()) { - return std::make_shared(std::move(parent), "", cluster); - } - ASSERT(!cluster->cluster_header_name_.get().empty()); - - const auto entries = headers.get(cluster->cluster_header_name_); - absl::string_view cluster_name = - entries.empty() ? absl::string_view{} : entries[0]->value().getStringView(); - return std::make_shared(std::move(parent), std::string(cluster_name), - cluster); - } - begin = end; + const auto* config = weighted_clusters_[cluster_index.value()].get(); + if (!config->cluster_name_.empty()) { + return std::make_shared(std::move(parent), "", this->shared_from_this(), + config, random_value); } - IS_ENVOY_BUG("unexpected"); - return nullptr; -} - -RouteConstSharedPtr WeightedClusterSpecifierPlugin::route(RouteEntryAndRouteConstSharedPtr parent, - const Http::RequestHeaderMap& headers, - const StreamInfo::StreamInfo& stream_info, - uint64_t random) const { - return pickWeightedCluster(std::move(parent), headers, stream_info, random); + ASSERT(!config->cluster_header_name_.get().empty()); + const auto entries = headers.get(config->cluster_header_name_); + absl::string_view cluster_name = + entries.empty() ? absl::string_view{} : entries[0]->value().getStringView(); + return std::make_shared(std::move(parent), std::string(cluster_name), + this->shared_from_this(), config, random_value); } absl::Status diff --git a/source/common/router/weighted_cluster_specifier.h b/source/common/router/weighted_cluster_specifier.h index c8f3fb1de37af..a0ed788c7d037 100644 --- a/source/common/router/weighted_cluster_specifier.h +++ b/source/common/router/weighted_cluster_specifier.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "envoy/router/cluster_specifier_plugin.h" #include "source/common/router/delegating_route_impl.h" @@ -19,24 +22,29 @@ class WeightedClusterSpecifierPlugin; struct WeightedClustersConfigEntry { public: static absl::StatusOr> - create(const ClusterWeightProto& cluster, const MetadataMatchCriteria* parent_metadata_match, - std::string&& runtime_key, Server::Configuration::ServerFactoryContext& context); + create(const ClusterWeightProto& cluster, uint64_t index, + const MetadataMatchCriteria* parent_metadata_match, absl::string_view runtime_key_prefix, + Server::Configuration::ServerFactoryContext& context); uint64_t clusterWeight(Runtime::Loader& loader) const { - return loader.snapshot().getInteger(runtime_key_, cluster_weight_); + return runtime_key_.empty() ? cluster_weight_ + : loader.snapshot().getInteger(runtime_key_, cluster_weight_); } + uint64_t clusterWeight() const { return cluster_weight_; } + uint64_t clusterIndex() const { return cluster_index_; } private: friend class WeightedClusterEntry; friend class WeightedClusterSpecifierPlugin; - WeightedClustersConfigEntry(const ClusterWeightProto& cluster, + WeightedClustersConfigEntry(const ClusterWeightProto& cluster, uint64_t index, const MetadataMatchCriteria* parent_metadata_match, - std::string&& runtime_key, + absl::string_view runtime_key_prefix, Server::Configuration::ServerFactoryContext& context); const std::string runtime_key_; - const uint64_t cluster_weight_; + const uint64_t cluster_weight_{}; + const uint64_t cluster_index_{}; MetadataMatchCriteriaConstPtr cluster_metadata_match_criteria_; HeaderParserPtr request_headers_parser_; HeaderParserPtr response_headers_parser_; @@ -49,8 +57,10 @@ struct WeightedClustersConfigEntry { using WeightedClustersConfigEntryConstSharedPtr = std::shared_ptr; -class WeightedClusterSpecifierPlugin : public ClusterSpecifierPlugin, - public Logger::Loggable { +class WeightedClusterSpecifierPlugin + : public ClusterSpecifierPlugin, + public Logger::Loggable, + public std::enable_shared_from_this { public: WeightedClusterSpecifierPlugin(const WeightedClusterProto& weighted_clusters, const MetadataMatchCriteria* parent_metadata_match, @@ -65,14 +75,10 @@ class WeightedClusterSpecifierPlugin : public ClusterSpecifierPlugin, absl::Status validateClusters(const Upstream::ClusterManager& cm) const override; private: - RouteConstSharedPtr pickWeightedCluster(RouteEntryAndRouteConstSharedPtr parent, - const Http::RequestHeaderMap& headers, - const StreamInfo::StreamInfo& stream_info, - uint64_t random_value) const; + friend class WeightedClusterEntry; Runtime::Loader& loader_; const Http::LowerCaseString random_value_header_; - const std::string runtime_key_prefix_; const bool use_hash_policy_{}; std::vector weighted_clusters_; uint64_t total_cluster_weight_{0}; diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 43361927d483a..2beb08e00bc21 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -7221,6 +7221,219 @@ TEST_F(RouteMatcherTest, WeightedClusterInvalidConfigWithInvalidHttpHeader) { EnvoyException, "Proto constraint validation failed.*"); } +// Tests for WeightedClusterEntry::refreshRouteCluster which allows re-selecting a different +// weighted cluster (e.g. for retries). +TEST_F(RouteMatcherTest, WeightedClusterRefreshRouteClusters) { + const std::string yaml = R"EOF( + virtual_hosts: + - name: www1 + domains: ["www1.lyft.com"] + routes: + - match: { prefix: "/" } + route: + weighted_clusters: + clusters: + - name: cluster1 + weight: 30 + - name: cluster2 + weight: 30 + - name: cluster3 + weight: 40 + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"cluster1", "cluster2", "cluster3"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true, + creation_status_); + NiceMock stream_info; + + // With random_value=0: cluster1 selected (range [0,30) of total 100). + Http::TestRequestHeaderMapImpl headers = genHeaders("www1.lyft.com", "/foo", "GET"); + RouteConstSharedPtr route = config.route(headers, 0); + ASSERT_NE(nullptr, route); + const RouteEntry* route_entry = route->routeEntry(); + EXPECT_EQ("cluster1", route_entry->clusterName()); + + // First refresh: cluster1 is marked used. Eligible: cluster2 (wt=30), cluster3 (wt=40). + // random_value=0, 0 % 70 = 0 → [0,30) → cluster2. + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster2", route_entry->clusterName()); + + // Second refresh: cluster2 is now marked used. Eligible: cluster3 (wt=40) only. + // random_value=0, 0 % 40 = 0 → cluster3. + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster3", route_entry->clusterName()); + + // Third refresh: all 3 clusters used → reset pool. + // Now full list is eligible, random_value=0 → cluster1 again. + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster1", route_entry->clusterName()); + + // Fourth refresh after reset: cluster1 marked used again → cluster2. + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster2", route_entry->clusterName()); +} + +// Two equal-weight clusters: refresh always picks the other cluster and cycles. +TEST_F(RouteMatcherTest, WeightedClusterRefreshRouteClustersTwoCluster) { + const std::string yaml = R"EOF( + virtual_hosts: + - name: www1 + domains: ["www1.lyft.com"] + routes: + - match: { prefix: "/" } + route: + weighted_clusters: + clusters: + - name: cluster1 + weight: 50 + - name: cluster2 + weight: 50 + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"cluster1", "cluster2"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true, + creation_status_); + NiceMock stream_info; + + // random_value=0 → cluster1. + Http::TestRequestHeaderMapImpl headers = genHeaders("www1.lyft.com", "/foo", "GET"); + RouteConstSharedPtr route = config.route(headers, 0); + const RouteEntry* route_entry = route->routeEntry(); + EXPECT_EQ("cluster1", route_entry->clusterName()); + + // After one refresh, the only unused cluster is cluster2. + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster2", route_entry->clusterName()); + + // After second refresh, all clusters exhausted → reset → cluster1. + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster1", route_entry->clusterName()); +} + +// A single cluster: refresh exhausts the single-cluster pool immediately, resets, +// and re-selects the same cluster. +TEST_F(RouteMatcherTest, WeightedClusterRefreshRouteClustersOneCluster) { + const std::string yaml = R"EOF( + virtual_hosts: + - name: www1 + domains: ["www1.lyft.com"] + routes: + - match: { prefix: "/" } + route: + weighted_clusters: + clusters: + - name: cluster1 + weight: 100 + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"cluster1"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true, + creation_status_); + NiceMock stream_info; + + Http::TestRequestHeaderMapImpl headers = genHeaders("www1.lyft.com", "/foo", "GET"); + RouteConstSharedPtr route = config.route(headers, 0); + const RouteEntry* route_entry = route->routeEntry(); + EXPECT_EQ("cluster1", route_entry->clusterName()); + + // Refresh: pool immediately exhausted and reset → only option is cluster1. + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster1", route_entry->clusterName()); + + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster1", route_entry->clusterName()); +} + +// Cluster-header mode: refreshing reads the cluster name from the request header again. +TEST_F(RouteMatcherTest, WeightedClusterRefreshRouteClustersClusterHeader) { + const std::string yaml = R"EOF( + virtual_hosts: + - name: www1 + domains: ["www1.lyft.com"] + routes: + - match: { prefix: "/" } + route: + weighted_clusters: + clusters: + - cluster_header: x-cluster + weight: 30 + - name: cluster1 + weight: 30 + - name: cluster2 + weight: 40 + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"dynamic", "cluster1", "cluster2"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true, + creation_status_); + NiceMock stream_info; + + // random_value=0 with x-cluster="dynamic" header → cluster at index 0 (header-based), name + // "dynamic". + Http::TestRequestHeaderMapImpl headers = genHeaders("www1.lyft.com", "/foo", "GET"); + headers.addCopy("x-cluster", "dynamic"); + RouteConstSharedPtr route = config.route(headers, 0); + const RouteEntry* route_entry = route->routeEntry(); + EXPECT_EQ("dynamic", route_entry->clusterName()); + + // First refresh: header cluster (idx=0) marked used. Eligible: cluster1 (wt=30), cluster2 + // (wt=40). 0%70=0 → [0,30) → clusterIndex=1 → cluster1 (static name). + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster1", route_entry->clusterName()); + + // Second refresh: cluster1 (idx=1) marked used. Eligible: cluster2 (wt=40) only. + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster2", route_entry->clusterName()); + + // Third refresh: all clusters exhausted → reset → back to header cluster "dynamic". + route_entry->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("dynamic", route_entry->clusterName()); +} + +// Refreshes on different RouteConstSharedPtrs from the same config are independent: +// each entry tracks its own used-cluster state. +TEST_F(RouteMatcherTest, WeightedClusterRefreshRouteClustersIndependentPerEntry) { + const std::string yaml = R"EOF( + virtual_hosts: + - name: www1 + domains: ["www1.lyft.com"] + routes: + - match: { prefix: "/" } + route: + weighted_clusters: + clusters: + - name: cluster1 + weight: 50 + - name: cluster2 + weight: 50 + )EOF"; + + factory_context_.cluster_manager_.initializeClusters({"cluster1", "cluster2"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true, + creation_status_); + NiceMock stream_info; + + Http::TestRequestHeaderMapImpl headers = genHeaders("www1.lyft.com", "/foo", "GET"); + RouteConstSharedPtr route_a = config.route(headers, 0); + RouteConstSharedPtr route_b = config.route(headers, 0); + + const RouteEntry* entry_a = route_a->routeEntry(); + const RouteEntry* entry_b = route_b->routeEntry(); + + EXPECT_EQ("cluster1", entry_a->clusterName()); + EXPECT_EQ("cluster1", entry_b->clusterName()); + + // Refresh entry_a: it picks cluster2, entry_b is unaffected. + entry_a->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster2", entry_a->clusterName()); + EXPECT_EQ("cluster1", entry_b->clusterName()); + + // Refresh entry_b independently: it also picks cluster2. + entry_b->refreshRouteCluster(headers, stream_info); + EXPECT_EQ("cluster2", entry_a->clusterName()); + EXPECT_EQ("cluster2", entry_b->clusterName()); +} + TEST(NullConfigImplTest, All) { NullConfigImpl config; NiceMock stream_info; From 2c709c31db01f2d16ab98abd0786a90948fe096a Mon Sep 17 00:00:00 2001 From: Ethan Truong Date: Tue, 12 May 2026 14:04:29 -0400 Subject: [PATCH 295/750] Add test that demonstrates keep_empty_value is ignored for ext_authz (#44925) Add this test to ensure that the default behavior does not change. Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Ethan Truong --- .../ext_authz/ext_authz_grpc_impl_test.cc | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc b/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc index 74be98da059a0..4b576e69daad6 100644 --- a/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc +++ b/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc @@ -635,6 +635,49 @@ TEST_F(ExtAuthzGrpcClientTest, AuthorizationOkUpstreamHeaderMutations) { span_); } +// TODO(https://github.com/envoyproxy/envoy/issues/45003) +// This test ensures the default (buggy) behavior remains unchanged since +// existing Envoy deployments may be relying on the default (buggy) behavior +TEST_F(ExtAuthzGrpcClientTest, AuthorizationOkWithKeepEmptyValueIgnored) { + initialize(); + + envoy::service::auth::v3::CheckResponse check_response; + TestUtility::loadFromYaml(R"EOF( +status: + code: 0 +ok_response: + headers: + - header: + key: x-keep-empty + value: "" + keep_empty_value: false +)EOF", + check_response); + + // If keep_empty_value were respected, the header "x-keep-empty" should be DROPPED + // because its value is empty. However, the current implementation ignores this + // flag and blindly adds the empty header to the headers_to_set vector. + auto expected_authz_response = Response{ + .status = CheckStatus::OK, + .headers_to_set = UnsafeHeaderVector{{"x-keep-empty", ""}}, + .status_code = Http::Code::OK, + .grpc_status = Grpc::Status::WellKnownGrpcStatus::Ok, + }; + + envoy::service::auth::v3::CheckRequest request; + expectCallSend(request); + client_->check(request_callbacks_, request, Tracing::NullSpan::instance(), stream_info_); + + Http::TestRequestHeaderMapImpl headers; + client_->onCreateInitialMetadata(headers); + + EXPECT_CALL(span_, setTag(Eq("ext_authz_status"), Eq("ext_authz_ok"))); + EXPECT_CALL(request_callbacks_, onComplete_(WhenDynamicCastTo( + AuthzOkResponse(expected_authz_response)))); + client_->onSuccess(std::make_unique(check_response), + span_); +} + } // namespace ExtAuthz } // namespace Common } // namespace Filters From d3a964c6341098f6ad8d59411691869db5a6e34b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 12 May 2026 16:27:48 -0400 Subject: [PATCH 296/750] otel: add option to disable service.name resource attribute (#44814) Commit Message: otel: add option to disable service.name resource attribute Additional Description: Currently, the OpenTelemetry tracer always populates the `service.name` resource attribute. If no name is configured in the YAML, it defaults to `unknown_service:envoy` which ends up being forcefully emitted into the resource spans. Similar to #44689, we have use cases where we do not want to set or leak the `service.name` resource attribute. This PR introduces a new configuration option `set_service_name_resource_attribute` (default true) in `OpenTelemetryConfig` to allow opting-out of populating this attribute. When set to false, the `service.name` key will be omitted from the emitted resource object entirely. As part of this change, the internal parameter propagation in `ResourceProvider` was refactored to use an options pattern (`ResourceProviderOptions`) to aggregate boolean configuration flags rather than extending method signatures with consecutive bool primitives. The new behavior is opt-in (default behavior is unchanged) to preserve backward compatibility. Risk Level: Low Testing: - Unit tests in `test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc` have been updated and a new test added to validate that the `service.name` attribute is omitted when disabled. - Unit tests in `test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc` have been updated to verify the configuration flags are propagated correctly via the new options struct. Docs Changes: None. Release Notes: Added a configuration option to disable populating the `service.name` resource attribute in the OpenTelemetry tracer. Platform Specific Features: None. Runtime guard: None. [Disclosed usage of generative AI: Yes, used to assist in code modifications and refactoring.] --------- Signed-off-by: Frank Fort --- api/envoy/config/trace/v3/opentelemetry.proto | 6 +- .../stat_sinks/open_telemetry/config.cc | 2 +- .../opentelemetry_tracer_impl.cc | 12 +-- .../resource_detectors/resource_provider.cc | 10 +-- .../resource_detectors/resource_provider.h | 22 +++++- .../opentelemetry_tracer_impl_test.cc | 77 ++++++++++++++++++- .../resource_provider_test.cc | 70 ++++++++++++----- 7 files changed, 160 insertions(+), 39 deletions(-) diff --git a/api/envoy/config/trace/v3/opentelemetry.proto b/api/envoy/config/trace/v3/opentelemetry.proto index c20cf15196f22..928d5a5d90adf 100644 --- a/api/envoy/config/trace/v3/opentelemetry.proto +++ b/api/envoy/config/trace/v3/opentelemetry.proto @@ -21,7 +21,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Configuration for the OpenTelemetry tracer. // [#extension: envoy.tracers.opentelemetry] -// [#next-free-field: 8] +// [#next-free-field: 9] message OpenTelemetryConfig { // The upstream gRPC cluster that will receive OTLP traces. // Note that the tracer drops traces if the server does not read data fast enough. @@ -72,4 +72,8 @@ message OpenTelemetryConfig { // // If not specified, the default is to set these attributes. google.protobuf.BoolValue set_telemetry_sdk_resource_attributes = 7; + + // Specifies whether to set the ``service.name`` resource attribute. + // If not specified, the default is to set this attribute. + google.protobuf.BoolValue set_service_name_resource_attribute = 8; } diff --git a/source/extensions/stat_sinks/open_telemetry/config.cc b/source/extensions/stat_sinks/open_telemetry/config.cc index 528fae6c76408..07a242940d4b7 100644 --- a/source/extensions/stat_sinks/open_telemetry/config.cc +++ b/source/extensions/stat_sinks/open_telemetry/config.cc @@ -26,7 +26,7 @@ OpenTelemetrySinkFactory::createStatsSink(const Protobuf::Message& config, sink_config, resource_provider->getResource(sink_config.resource_detectors(), server, /*service_name=*/"", - /*set_telemetry_sdk_resource_attributes=*/true), + Tracers::OpenTelemetry::ResourceProviderOptions{}), server); std::shared_ptr otlp_metrics_flusher = std::make_shared(otlp_options); diff --git a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc index ed005bf32275a..3d4d2a04afb90 100644 --- a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc +++ b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc @@ -76,17 +76,17 @@ Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetr POOL_COUNTER_PREFIX(context.serverFactoryContext().scope(), "tracing.opentelemetry"))} { auto& factory_context = context.serverFactoryContext(); - bool set_telemetry_sdk_resource_attributes = true; - if (opentelemetry_config.has_set_telemetry_sdk_resource_attributes()) { - set_telemetry_sdk_resource_attributes = - opentelemetry_config.set_telemetry_sdk_resource_attributes().value(); - } + ResourceProviderOptions options; + options.set_telemetry_sdk_resource_attributes = PROTOBUF_GET_WRAPPED_OR_DEFAULT( + opentelemetry_config, set_telemetry_sdk_resource_attributes, true); + options.set_service_name_resource_attribute = PROTOBUF_GET_WRAPPED_OR_DEFAULT( + opentelemetry_config, set_service_name_resource_attribute, true); Resource resource = resource_provider.getResource( opentelemetry_config.resource_detectors(), context.serverFactoryContext(), opentelemetry_config.service_name().empty() ? kDefaultServiceName : opentelemetry_config.service_name(), - set_telemetry_sdk_resource_attributes); + options); ResourceConstSharedPtr resource_ptr = std::make_shared(std::move(resource)); if (opentelemetry_config.has_grpc_service() && opentelemetry_config.has_http_service()) { diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc index 52496fa04a132..3fbc0f7784bc9 100644 --- a/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc +++ b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.cc @@ -15,15 +15,15 @@ namespace { bool isEmptyResource(const Resource& resource) { return resource.attributes_.empty(); } Resource createInitialResource(absl::string_view service_name, - bool set_telemetry_sdk_resource_attributes) { + const ResourceProviderOptions& options) { Resource resource{}; // Creates initial resource with the static service.name and telemetry.sdk.* attributes. - if (!service_name.empty()) { + if (options.set_service_name_resource_attribute && !service_name.empty()) { resource.attributes_[kServiceNameKey] = service_name; } - if (set_telemetry_sdk_resource_attributes) { + if (options.set_telemetry_sdk_resource_attributes) { resource.attributes_[kTelemetrySdkLanguageKey] = kDefaultTelemetrySdkLanguage; resource.attributes_[kTelemetrySdkNameKey] = kDefaultTelemetrySdkName; resource.attributes_[kTelemetrySdkVersionKey] = Envoy::VersionInfo::version(); @@ -88,9 +88,9 @@ Resource ResourceProviderImpl::getResource( const Protobuf::RepeatedPtrField& resource_detectors, Envoy::Server::Configuration::ServerFactoryContext& context, absl::string_view service_name, - bool set_telemetry_sdk_resource_attributes) const { + const ResourceProviderOptions& options) const { - Resource resource = createInitialResource(service_name, set_telemetry_sdk_resource_attributes); + Resource resource = createInitialResource(service_name, options); for (const auto& detector_config : resource_detectors) { ResourceDetectorPtr detector; diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h index b25712c0ecf43..c8fed92f35226 100644 --- a/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h +++ b/source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h @@ -20,6 +20,24 @@ constexpr absl::string_view kDefaultTelemetrySdkName = "envoy"; constexpr absl::string_view kTelemetrySdkVersionKey = "telemetry.sdk.version"; +/** + * @brief Configuration options controlling the population of optional attributes in the emitted + * OpenTelemetry resource object. + */ +struct ResourceProviderOptions { + /** + * @brief Whether to automatically include telemetry SDK metadata attributes + * (`telemetry.sdk.language`, `telemetry.sdk.name`, `telemetry.sdk.version`). + */ + bool set_telemetry_sdk_resource_attributes{true}; + /** + * @brief Whether to automatically include the `service.name` resource attribute. + */ + bool set_service_name_resource_attribute{true}; + + bool operator==(const ResourceProviderOptions& other) const = default; +}; + class ResourceProvider : public Logger::Loggable { public: virtual ~ResourceProvider() = default; @@ -39,7 +57,7 @@ class ResourceProvider : public Logger::Loggable { getResource(const Protobuf::RepeatedPtrField& resource_detectors, Server::Configuration::ServerFactoryContext& context, absl::string_view service_name, - bool set_telemetry_sdk_resource_attributes) const PURE; + const ResourceProviderOptions& options) const PURE; }; using ResourceProviderPtr = std::shared_ptr; @@ -49,7 +67,7 @@ class ResourceProviderImpl : public ResourceProvider { getResource(const Protobuf::RepeatedPtrField& resource_detectors, Server::Configuration::ServerFactoryContext& context, absl::string_view service_name, - bool set_telemetry_sdk_resource_attributes) const override; + const ResourceProviderOptions& options) const override; }; } // namespace OpenTelemetry diff --git a/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc b/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc index 441f603d08584..d0124a2abf913 100644 --- a/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc +++ b/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc @@ -35,7 +35,7 @@ class MockResourceProvider : public ResourceProvider { (const Protobuf::RepeatedPtrField& resource_detectors, Server::Configuration::ServerFactoryContext& context, absl::string_view service_name, - bool set_telemetry_sdk_resource_attributes), + const ResourceProviderOptions& options), (const)); }; @@ -148,7 +148,10 @@ TEST_F(OpenTelemetryDriverTest, PassSetTelemetrySdkResourceAttributesFalse) { Resource resource; auto mock_resource_provider = NiceMock(); - EXPECT_CALL(mock_resource_provider, getResource(_, _, _, false)).WillOnce(Return(resource)); + ResourceProviderOptions expected_options; + expected_options.set_telemetry_sdk_resource_attributes = false; + EXPECT_CALL(mock_resource_provider, getResource(_, _, _, expected_options)) + .WillOnce(Return(resource)); driver_ = std::make_unique(opentelemetry_config, context_, mock_resource_provider); } @@ -178,7 +181,75 @@ TEST_F(OpenTelemetryDriverTest, PassSetTelemetrySdkResourceAttributesDefaultTrue Resource resource; auto mock_resource_provider = NiceMock(); - EXPECT_CALL(mock_resource_provider, getResource(_, _, _, true)).WillOnce(Return(resource)); + ResourceProviderOptions expected_options; + EXPECT_CALL(mock_resource_provider, getResource(_, _, _, expected_options)) + .WillOnce(Return(resource)); + + driver_ = std::make_unique(opentelemetry_config, context_, mock_resource_provider); +} + +// Verifies that set_service_name_resource_attribute=false is passed to ResourceProvider +TEST_F(OpenTelemetryDriverTest, PassSetServiceNameResourceAttributeFalse) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + set_service_name_resource_attribute: false + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + auto mock_client_factory = std::make_unique>(); + auto mock_client = std::make_unique>(); + mock_client_ = mock_client.get(); + ON_CALL(*mock_client_factory, createUncachedRawAsyncClient()) + .WillByDefault(Return(ByMove(std::move(mock_client)))); + auto& factory_context = context_.server_factory_context_; + ON_CALL(factory_context, runtime()).WillByDefault(ReturnRef(runtime_)); + ON_CALL(factory_context.cluster_manager_.async_client_manager_, factoryForGrpcService(_, _, _)) + .WillByDefault(Return(ByMove(std::move(mock_client_factory)))); + ON_CALL(factory_context, scope()).WillByDefault(ReturnRef(scope_)); + + Resource resource; + auto mock_resource_provider = NiceMock(); + + ResourceProviderOptions expected_options; + expected_options.set_service_name_resource_attribute = false; + EXPECT_CALL(mock_resource_provider, getResource(_, _, _, expected_options)) + .WillOnce(Return(resource)); + + driver_ = std::make_unique(opentelemetry_config, context_, mock_resource_provider); +} + +// Verifies that set_service_name_resource_attribute defaults to true +TEST_F(OpenTelemetryDriverTest, PassSetServiceNameResourceAttributeDefaultTrue) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + auto mock_client_factory = std::make_unique>(); + auto mock_client = std::make_unique>(); + mock_client_ = mock_client.get(); + ON_CALL(*mock_client_factory, createUncachedRawAsyncClient()) + .WillByDefault(Return(ByMove(std::move(mock_client)))); + auto& factory_context = context_.server_factory_context_; + ON_CALL(factory_context, runtime()).WillByDefault(ReturnRef(runtime_)); + ON_CALL(factory_context.cluster_manager_.async_client_manager_, factoryForGrpcService(_, _, _)) + .WillByDefault(Return(ByMove(std::move(mock_client_factory)))); + ON_CALL(factory_context, scope()).WillByDefault(ReturnRef(scope_)); + + Resource resource; + auto mock_resource_provider = NiceMock(); + + ResourceProviderOptions expected_options; + EXPECT_CALL(mock_resource_provider, getResource(_, _, _, expected_options)) + .WillOnce(Return(resource)); driver_ = std::make_unique(opentelemetry_config, context_, mock_resource_provider); } diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc index 306e485aebd89..dcbf3ce57da68 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc +++ b/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc @@ -78,9 +78,9 @@ TEST_F(ResourceProviderTest, NoResourceDetectorsConfigured) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, - opentelemetry_config.service_name(), true); + Resource resource = resource_provider.getResource( + opentelemetry_config.resource_detectors(), server_factory_context_, + opentelemetry_config.service_name(), ResourceProviderOptions{}); EXPECT_EQ(resource.schema_url_, ""); @@ -115,9 +115,11 @@ TEST_F(ResourceProviderTest, NoResourceDetectorsConfiguredAttributesDisabled) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; + ResourceProviderOptions options; + options.set_telemetry_sdk_resource_attributes = false; Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), server_factory_context_, - opentelemetry_config.service_name(), false); + opentelemetry_config.service_name(), options); // Verify that telemetry SDK attributes are NOT present EXPECT_TRUE(resource.attributes_.find("telemetry.sdk.language") == resource.attributes_.end()); @@ -125,6 +127,32 @@ TEST_F(ResourceProviderTest, NoResourceDetectorsConfiguredAttributesDisabled) { EXPECT_TRUE(resource.attributes_.find("telemetry.sdk.version") == resource.attributes_.end()); } +// Verifies a resource without service name is returned when service name attribute is disabled +TEST_F(ResourceProviderTest, NoResourceDetectorsConfiguredServiceNameDisabled) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake-cluster + timeout: 0.250s + service_name: my-service + set_service_name_resource_attribute: false + )EOF"; + envoy::config::trace::v3::OpenTelemetryConfig opentelemetry_config; + TestUtility::loadFromYaml(yaml_string, opentelemetry_config); + + ResourceProviderImpl resource_provider; + ResourceProviderOptions options; + options.set_service_name_resource_attribute = false; + Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), + server_factory_context_, + opentelemetry_config.service_name(), options); + + // Verify that service.name attribute is NOT present + EXPECT_TRUE(resource.attributes_.find("service.name") == resource.attributes_.end()); + // Verify other attributes are still present since only service.name was disabled + EXPECT_TRUE(resource.attributes_.find("telemetry.sdk.language") != resource.attributes_.end()); +} + // Verifies it is possible to configure multiple resource detectors TEST_F(ResourceProviderTest, MultipleResourceDetectorsConfigured) { auto detector_a = std::make_unique>(); @@ -171,9 +199,9 @@ TEST_F(ResourceProviderTest, MultipleResourceDetectorsConfigured) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, - opentelemetry_config.service_name(), true); + Resource resource = resource_provider.getResource( + opentelemetry_config.resource_detectors(), server_factory_context_, + opentelemetry_config.service_name(), ResourceProviderOptions{}); EXPECT_EQ(resource.schema_url_, ""); @@ -209,7 +237,7 @@ TEST_F(ResourceProviderTest, UnknownResourceDetectors) { EXPECT_THROW_WITH_MESSAGE( resource_provider.getResource(opentelemetry_config.resource_detectors(), server_factory_context_, opentelemetry_config.service_name(), - true), + ResourceProviderOptions{}), EnvoyException, "Resource detector factory not found: " "'envoy.tracers.opentelemetry.resource_detectors.UnkownResourceDetector'"); @@ -241,7 +269,7 @@ TEST_F(ResourceProviderTest, ProblemCreatingResourceDetector) { ResourceProviderImpl resource_provider; EXPECT_THROW_WITH_MESSAGE(resource_provider.getResource( opentelemetry_config.resource_detectors(), server_factory_context_, - opentelemetry_config.service_name(), true), + opentelemetry_config.service_name(), ResourceProviderOptions{}), EnvoyException, "Resource detector could not be created: " "'envoy.tracers.opentelemetry.resource_detectors.a'"); @@ -291,9 +319,9 @@ TEST_F(ResourceProviderTest, OldSchemaEmptyUpdatingSet) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, - opentelemetry_config.service_name(), true); + Resource resource = resource_provider.getResource( + opentelemetry_config.resource_detectors(), server_factory_context_, + opentelemetry_config.service_name(), ResourceProviderOptions{}); // OTel spec says the updating schema should be used EXPECT_EQ(expected_schema_url, resource.schema_url_); @@ -343,9 +371,9 @@ TEST_F(ResourceProviderTest, OldSchemaSetUpdatingEmpty) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, - opentelemetry_config.service_name(), true); + Resource resource = resource_provider.getResource( + opentelemetry_config.resource_detectors(), server_factory_context_, + opentelemetry_config.service_name(), ResourceProviderOptions{}); // OTel spec says the updating schema should be used EXPECT_EQ(expected_schema_url, resource.schema_url_); @@ -395,9 +423,9 @@ TEST_F(ResourceProviderTest, OldAndUpdatingSchemaAreEqual) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, - opentelemetry_config.service_name(), true); + Resource resource = resource_provider.getResource( + opentelemetry_config.resource_detectors(), server_factory_context_, + opentelemetry_config.service_name(), ResourceProviderOptions{}); EXPECT_EQ(expected_schema_url, resource.schema_url_); } @@ -446,9 +474,9 @@ TEST_F(ResourceProviderTest, OldAndUpdatingSchemaAreDifferent) { TestUtility::loadFromYaml(yaml_string, opentelemetry_config); ResourceProviderImpl resource_provider; - Resource resource = resource_provider.getResource(opentelemetry_config.resource_detectors(), - server_factory_context_, - opentelemetry_config.service_name(), true); + Resource resource = resource_provider.getResource( + opentelemetry_config.resource_detectors(), server_factory_context_, + opentelemetry_config.service_name(), ResourceProviderOptions{}); // OTel spec says Old schema should be used EXPECT_EQ(expected_schema_url, resource.schema_url_); From a0ee8c6824486f8b6377006d9c9973d7368f2007 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 12 May 2026 14:03:42 -0700 Subject: [PATCH 297/750] san_matcher: correctly propagate creation errors to callers (#45019) Thanks to @omkhar for reporting this issue. Signed-off-by: Greg Greenway --- changelogs/current.yaml | 10 +++++++ .../tls/cert_validator/default_validator.cc | 9 +++---- .../common/tls/cert_validator/san_matcher.cc | 8 +++--- .../common/tls/cert_validator/san_matcher.h | 4 ++- .../mtls_authenticated/mtls_authenticated.cc | 8 ++++-- .../cert_validator/spiffe/spiffe_validator.cc | 5 +++- test/common/tls/cert_validator/BUILD | 1 + .../cert_validator/default_validator_test.cc | 2 +- .../tls/cert_validator/san_matcher_test.cc | 22 ++++++++++----- .../filters/common/rbac/matchers_test.cc | 15 +++++++++++ .../spiffe/spiffe_validator_test.cc | 27 +++++++++++++++++++ 11 files changed, 91 insertions(+), 20 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 778f4aa2c644a..6c64fd9ed46c3 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -51,6 +51,16 @@ bug_fixes: Fixed a bug in load stats reporting where reports were dropped if only custom metrics or completed requests were present in a reporting interval. This behavioral change can be reverted by setting the runtime guard ``envoy.reloadable_features.report_load_for_non_zero_stats`` to ``false``. +- area: spiffe_validator + change: | + Fixed a bug where if an invalid custom SAN matcher was configured it would later lead to a crash + when validating a certificate. +- area: rbac + change: | + Fixed a bug in the :ref:`mTLS Authenticated ` + principal extension where if an invalid SAN matcher was configured, it would incorrectly match any + certificate in the allowed trust chain. Known bad configurations are an invalid ``OID`` when using + an ``OTHER_NAME``, or specifying a ``StringMatcher`` with an invalid configuration. - area: build change: | Fixed ``Illegal ambiguous match`` error when building contrib targets with ``--config=aws-lc-fips`` diff --git a/source/common/tls/cert_validator/default_validator.cc b/source/common/tls/cert_validator/default_validator.cc index ffc8771062e30..563ef4c230404 100644 --- a/source/common/tls/cert_validator/default_validator.cc +++ b/source/common/tls/cert_validator/default_validator.cc @@ -162,12 +162,9 @@ absl::StatusOr DefaultCertValidator::initializeSslContexts(std::vectorsubjectAltNameMatchers().empty()) { for (const envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher& matcher : cert_validation_config->subjectAltNameMatchers()) { - auto san_matcher = createStringSanMatcher(matcher, context_); - if (san_matcher == nullptr) { - return absl::InvalidArgumentError( - absl::StrCat("Failed to create string SAN matcher of type ", matcher.san_type())); - } - subject_alt_name_matchers_.emplace_back(std::move(san_matcher)); + auto status_or_san_matcher = createStringSanMatcher(matcher, context_); + RETURN_IF_NOT_OK_REF(status_or_san_matcher.status()); + subject_alt_name_matchers_.emplace_back(std::move(*status_or_san_matcher)); } verify_mode = verify_mode_validation_context; } diff --git a/source/common/tls/cert_validator/san_matcher.cc b/source/common/tls/cert_validator/san_matcher.cc index 30d35f5e85ff2..52b5c6ed560d4 100644 --- a/source/common/tls/cert_validator/san_matcher.cc +++ b/source/common/tls/cert_validator/san_matcher.cc @@ -52,7 +52,7 @@ bool DnsExactStringSanMatcher::match(GENERAL_NAME const* general_name) const { return Utility::dnsNameMatch(dns_exact_match_, Utility::generalNameAsString(general_name)); } -SanMatcherPtr createStringSanMatcher( +absl::StatusOr createStringSanMatcher( envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher const& matcher, Server::Configuration::CommonFactoryContext& context) { // Verify that a new san type has not been added. @@ -79,7 +79,8 @@ SanMatcherPtr createStringSanMatcher( // Invalid/Empty OID returns a nullptr from OBJ_txt2obj bssl::UniquePtr oid(OBJ_txt2obj(matcher.oid().c_str(), 0)); if (oid == nullptr) { - return nullptr; + return absl::InvalidArgumentError( + absl::StrCat("Failed to create SAN matcher for OTHER_NAME with OID: ", matcher.oid())); } return SanMatcherPtr{std::make_unique(GEN_OTHERNAME, matcher.matcher(), context, std::move(oid))}; @@ -87,7 +88,8 @@ SanMatcherPtr createStringSanMatcher( case envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SAN_TYPE_UNSPECIFIED: PANIC("unhandled value"); } - return nullptr; + return absl::InternalError( + "Unhandled case in createStringSanMatcher; this should be unreachable"); } } // namespace Tls diff --git a/source/common/tls/cert_validator/san_matcher.h b/source/common/tls/cert_validator/san_matcher.h index 99617a808b945..cdfa9c52802fb 100644 --- a/source/common/tls/cert_validator/san_matcher.h +++ b/source/common/tls/cert_validator/san_matcher.h @@ -84,7 +84,9 @@ class DnsExactStringSanMatcher : public SanMatcher { const std::string dns_exact_match_; }; -SanMatcherPtr createStringSanMatcher( +// Returns either a non-null SanMatcherPtr, or an error why it couldn't be +// created. +absl::StatusOr createStringSanMatcher( const envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher& matcher, Server::Configuration::CommonFactoryContext& context); diff --git a/source/extensions/filters/common/rbac/principals/mtls_authenticated/mtls_authenticated.cc b/source/extensions/filters/common/rbac/principals/mtls_authenticated/mtls_authenticated.cc index 3d6c5107113c1..18e41aa7d07de 100644 --- a/source/extensions/filters/common/rbac/principals/mtls_authenticated/mtls_authenticated.cc +++ b/source/extensions/filters/common/rbac/principals/mtls_authenticated/mtls_authenticated.cc @@ -12,8 +12,12 @@ namespace Principals { MtlsAuthenticatedMatcher::MtlsAuthenticatedMatcher( const envoy::extensions::rbac::principals::mtls_authenticated::v3::Config& auth, Server::Configuration::CommonFactoryContext& context) - : matcher_(auth.has_san_matcher() ? Extensions::TransportSockets::Tls::createStringSanMatcher( - auth.san_matcher(), context) + : matcher_(auth.has_san_matcher() ? [&]() { + auto status_or_matcher = + Extensions::TransportSockets::Tls::createStringSanMatcher(auth.san_matcher(), context); + THROW_IF_NOT_OK_REF(status_or_matcher.status()); + return std::move(*status_or_matcher); + }() : nullptr) { if (!auth.has_san_matcher() && !auth.any_validated_client_certificate()) { throw EnvoyException("envoy.rbac.principals.mtls_authenticated did not have any configured " diff --git a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc index e81194f5fedb2..d7333a84878af 100644 --- a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc @@ -162,7 +162,10 @@ SPIFFEValidator::SPIFFEValidator(const Envoy::Ssl::CertificateValidationContextC // SAN types. See the discussion: https://github.com/envoyproxy/envoy/issues/15392 // TODO(pradeepcrao): Throw an exception when a non-URI matcher is encountered after the // deprecated field match_subject_alt_names is removed - subject_alt_name_matchers_.emplace_back(createStringSanMatcher(matcher, context)); + auto status_or_matcher = createStringSanMatcher(matcher, context); + SET_AND_RETURN_IF_NOT_OK(status_or_matcher.status(), creation_status); + + subject_alt_name_matchers_.emplace_back(std::move(*status_or_matcher)); } } } diff --git a/test/common/tls/cert_validator/BUILD b/test/common/tls/cert_validator/BUILD index 6176cb47fd79e..6bee7acf14a3a 100644 --- a/test/common/tls/cert_validator/BUILD +++ b/test/common/tls/cert_validator/BUILD @@ -61,6 +61,7 @@ envoy_cc_test( "//source/common/protobuf:utility_lib", "//source/common/tls/cert_validator:cert_validator_lib", "//test/mocks/server:server_factory_context_mocks", + "//test/test_common:status_utility_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", ], diff --git a/test/common/tls/cert_validator/default_validator_test.cc b/test/common/tls/cert_validator/default_validator_test.cc index a7a9f47834e06..28c86cbd0e886 100644 --- a/test/common/tls/cert_validator/default_validator_test.cc +++ b/test/common/tls/cert_validator/default_validator_test.cc @@ -734,7 +734,7 @@ TEST(DefaultCertValidatorTest, TestUnexpectedSanMatcherType) { std::make_unique(mock_context_config.get(), ssl_stats, context); auto ctx = std::vector(); EXPECT_THAT(validator->initializeSslContexts(ctx, false, *store.rootScope()).status().message(), - testing::ContainsRegex("Failed to create string SAN matcher of type.*")); + testing::ContainsRegex("Unhandled case in createStringSanMatcher.*")); } TEST(DefaultCertValidatorTest, TestInitializeSslContextFailure) { diff --git a/test/common/tls/cert_validator/san_matcher_test.cc b/test/common/tls/cert_validator/san_matcher_test.cc index 42fe9cd416732..0ef273a7e480f 100644 --- a/test/common/tls/cert_validator/san_matcher_test.cc +++ b/test/common/tls/cert_validator/san_matcher_test.cc @@ -5,6 +5,7 @@ #include "source/common/tls/cert_validator/san_matcher.h" #include "test/mocks/server/server_factory_context.h" +#include "test/test_common/status_utility.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -15,6 +16,8 @@ namespace Extensions { namespace TransportSockets { namespace Tls { +using ::Envoy::StatusHelpers::HasStatusCode; + // Verify that we get a valid string san matcher for all valid san types. TEST(SanMatcherConfigTest, TestValidSanType) { NiceMock context; @@ -36,17 +39,22 @@ TEST(SanMatcherConfigTest, TestValidSanType) { } if (san_type == envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher:: SAN_TYPE_UNSPECIFIED) { - EXPECT_DEATH(createStringSanMatcher(san_matcher, context), "unhandled value"); + EXPECT_DEATH( + { + auto result = createStringSanMatcher(san_matcher, context); + UNREFERENCED_PARAMETER(result); + }, + "unhandled value"); } else { - const SanMatcherPtr matcher = createStringSanMatcher(san_matcher, context); - EXPECT_NE(matcher.get(), nullptr); + auto result = createStringSanMatcher(san_matcher, context); + ASSERT_OK(result); + EXPECT_NE(result.value().get(), nullptr); // Verify that the message is valid. TestUtility::validate(san_matcher); } } } -// Verify that setting Invalid OID for OtherName SAN results in a panic. TEST(SanMatcherConfigTest, TestInvalidOidOtherNameSanType) { NiceMock context; envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher san_matcher; @@ -54,7 +62,8 @@ TEST(SanMatcherConfigTest, TestInvalidOidOtherNameSanType) { san_matcher.set_oid("1.3.6.1.4.1.311.20.2.ffff"); san_matcher.set_san_type( envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::OTHER_NAME); - EXPECT_EQ(createStringSanMatcher(san_matcher, context), nullptr); + EXPECT_THAT(createStringSanMatcher(san_matcher, context), + HasStatusCode(absl::StatusCode::kInvalidArgument)); } TEST(SanMatcherConfigTest, UnspecifiedSanType) { @@ -73,7 +82,8 @@ TEST(SanMatcherConfigTest, UnspecifiedSanType) { static_cast( static_cast(123)); san_matcher.set_san_type(san_type); - EXPECT_EQ(createStringSanMatcher(san_matcher, context), nullptr); + EXPECT_THAT(createStringSanMatcher(san_matcher, context), + HasStatusCode(absl::StatusCode::kInternal)); } } // namespace Tls diff --git a/test/extensions/filters/common/rbac/matchers_test.cc b/test/extensions/filters/common/rbac/matchers_test.cc index 61c0b8df5271f..55017a62732d3 100644 --- a/test/extensions/filters/common/rbac/matchers_test.cc +++ b/test/extensions/filters/common/rbac/matchers_test.cc @@ -628,6 +628,21 @@ TEST(MtlsAuthenticatedMatcher, SanMatcher) { checkMatcher(Principals::MtlsAuthenticatedMatcher(auth, factory_context), false, conn); } +TEST(MtlsAuthenticatedMatcher, InvalidOid) { + NiceMock factory_context; + + envoy::extensions::rbac::principals::mtls_authenticated::v3::Config auth; + auto* matcher = auth.mutable_san_matcher(); + matcher->set_san_type( + envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::OTHER_NAME); + matcher->mutable_matcher()->set_exact("test_value"); + matcher->set_oid("not_a_valid_oid"); + + EXPECT_THROW_WITH_MESSAGE( + { Principals::MtlsAuthenticatedMatcher m(auth, factory_context); }, EnvoyException, + "Failed to create SAN matcher for OTHER_NAME with OID: not_a_valid_oid"); +} + TEST(MetadataMatcher, MetadataMatcher) { NiceMock context; Envoy::Network::MockConnection conn; diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc index dc8a7c6ac888d..4bdb2f1fa7f0b 100644 --- a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc @@ -163,6 +163,10 @@ class TestSPIFFEValidator : public testing::Test { } }; + void addSanMatcher(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher matcher) { + san_matchers_.push_back(std::move(matcher)); + } + Api::ApiPtr api_; NiceMock factory_context_; NiceMock dispatcher_; @@ -1244,6 +1248,29 @@ name: envoy.tls.cert_validator.spiffe EXPECT_EQ(gauge_opt->get().value(), 1787339642); } +// Verify that a URI SAN matcher with an unregistered custom string matcher extension +// fails during SPIFFE validator construction. +TEST_F(TestSPIFFEValidator, InvalidCustomMatcherInSanMatcher) { + envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher san_matcher; + san_matcher.set_san_type( + envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI); + auto* custom = san_matcher.mutable_matcher()->mutable_custom(); + custom->set_name("unregistered_custom_matcher"); + custom->mutable_typed_config()->set_type_url("type.googleapis.com/nonexistent.Type"); + addSanMatcher(san_matcher); + + EXPECT_THROW_WITH_REGEX((void)initialize(TestEnvironment::substitute(R"EOF( +name: envoy.tls.cert_validator.spiffe +typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.SPIFFECertValidatorConfig + trust_domains: + - name: hello.com + trust_bundle: + filename: "{{ test_rundir }}/test/common/tls/test_data/ca_cert.pem" + )EOF")), + EnvoyException, "Didn't find a registered implementation"); +} + } // namespace Tls } // namespace TransportSockets } // namespace Extensions From dd8cce9255e3eea2e2a564f7c18b4dd86627f1f9 Mon Sep 17 00:00:00 2001 From: code Date: Wed, 13 May 2026 21:47:49 +0800 Subject: [PATCH 298/750] lua test: ensure markDead be called before destory it by lua close (#44945) Commit Message: Additional Description: Try to address problem that referred at https://github.com/envoyproxy/envoy/pull/43204 Risk Level: low. test only change. Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: wbpcode/wangbaiping --- .../filters/http/lua/wrappers_test.cc | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/test/extensions/filters/http/lua/wrappers_test.cc b/test/extensions/filters/http/lua/wrappers_test.cc index 4ef0a53e0ba31..eef4e615b6ff9 100644 --- a/test/extensions/filters/http/lua/wrappers_test.cc +++ b/test/extensions/filters/http/lua/wrappers_test.cc @@ -31,6 +31,13 @@ class LuaHeaderMapWrapperTest : public Filters::Common::Lua::LuaWrappersTestBase Filters::Common::Lua::LuaWrappersTestBase::setup(script); state_->registerType(); } + +protected: + Filters::Common::Lua::LuaDeathRef + createWrapperRef(Http::HeaderMap& headers, HeaderMapWrapper::CheckModifiableCb cb) { + return Filters::Common::Lua::LuaDeathRef( + HeaderMapWrapper::create(coroutine_->luaState(), headers, cb), true); + } }; // Basic methods test for the header wrapper. @@ -62,7 +69,7 @@ TEST_F(LuaHeaderMapWrapperTest, Methods) { setup(SCRIPT); Http::TestRequestHeaderMapImpl headers; - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return true; }); + auto wrapper = createWrapperRef(headers, []() { return true; }); EXPECT_CALL(printer_, testPrint("WORLD")); EXPECT_CALL(printer_, testPrint("'hello' 'WORLD'")); EXPECT_CALL(printer_, testPrint("'header1' ''")); @@ -71,6 +78,7 @@ TEST_F(LuaHeaderMapWrapperTest, Methods) { EXPECT_CALL(printer_, testPrint("'header2' 'foo'")); EXPECT_CALL(printer_, testPrint("foo,bar")); start("callMe"); + wrapper.reset(); } // Get the total number of values for a certain header with multiple values. @@ -87,11 +95,12 @@ TEST_F(LuaHeaderMapWrapperTest, GetNumValues) { setup(SCRIPT); Http::TestRequestHeaderMapImpl headers{{":path", "/"}, {"x-test", "foo"}, {"x-test", "bar"}}; - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return true; }); + auto wrapper = createWrapperRef(headers, []() { return true; }); EXPECT_CALL(printer_, testPrint("2")); EXPECT_CALL(printer_, testPrint("1")); EXPECT_CALL(printer_, testPrint("0")); start("callMe"); + wrapper.reset(); } // Get the value on a certain index for a header with multiple values. @@ -115,13 +124,14 @@ TEST_F(LuaHeaderMapWrapperTest, GetAtIndex) { Http::TestRequestHeaderMapImpl headers{ {":path", "/"}, {"x-test", "foo"}, {"x-test", "bar"}, {"x-test", ""}}; - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return true; }); + auto wrapper = createWrapperRef(headers, []() { return true; }); EXPECT_CALL(printer_, testPrint("invalid_negative_index")); EXPECT_CALL(printer_, testPrint("foo")); EXPECT_CALL(printer_, testPrint("bar")); EXPECT_CALL(printer_, testPrint("")); EXPECT_CALL(printer_, testPrint("nil_value")); start("callMe"); + wrapper.reset(); } // Test modifiable methods. @@ -150,23 +160,27 @@ TEST_F(LuaHeaderMapWrapperTest, ModifiableMethods) { setup(SCRIPT); Http::TestRequestHeaderMapImpl headers; - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return false; }); + auto should_be_ok_wrapper = createWrapperRef(headers, []() { return false; }); start("shouldBeOk"); + should_be_ok_wrapper.reset(); setup(SCRIPT); - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return false; }); + auto should_fail_remove_wrapper = createWrapperRef(headers, []() { return false; }); EXPECT_THROW_WITH_MESSAGE(start("shouldFailRemove"), Filters::Common::Lua::LuaException, "[string \"...\"]:9: header map can no longer be modified"); + should_fail_remove_wrapper.reset(); setup(SCRIPT); - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return false; }); + auto should_fail_add_wrapper = createWrapperRef(headers, []() { return false; }); EXPECT_THROW_WITH_MESSAGE(start("shouldFailAdd"), Filters::Common::Lua::LuaException, "[string \"...\"]:13: header map can no longer be modified"); + should_fail_add_wrapper.reset(); setup(SCRIPT); - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return false; }); + auto should_fail_replace_wrapper = createWrapperRef(headers, []() { return false; }); EXPECT_THROW_WITH_MESSAGE(start("shouldFailReplace"), Filters::Common::Lua::LuaException, "[string \"...\"]:17: header map can no longer be modified"); + should_fail_replace_wrapper.reset(); } // Verify that replace works correctly with both inline and normal headers. @@ -183,8 +197,9 @@ TEST_F(LuaHeaderMapWrapperTest, Replace) { setup(SCRIPT); Http::TestRequestHeaderMapImpl headers{{":path", "/"}, {"other_header", "hello"}}; - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return true; }); + auto wrapper = createWrapperRef(headers, []() { return true; }); start("callMe"); + wrapper.reset(); EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":path", "/new_path"}, {"other_header", "other_header_value"}, @@ -206,9 +221,10 @@ TEST_F(LuaHeaderMapWrapperTest, ModifyDuringIteration) { setup(SCRIPT); Http::TestRequestHeaderMapImpl headers{{"foo", "bar"}}; - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return true; }); + auto wrapper = createWrapperRef(headers, []() { return true; }); EXPECT_THROW_WITH_MESSAGE(start("callMe"), Filters::Common::Lua::LuaException, "[string \"...\"]:4: header map cannot be modified while iterating"); + wrapper.reset(); } // Modify after iteration. @@ -231,11 +247,12 @@ TEST_F(LuaHeaderMapWrapperTest, ModifyAfterIteration) { setup(SCRIPT); Http::TestRequestHeaderMapImpl headers{{"foo", "bar"}}; - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return true; }); + auto wrapper = createWrapperRef(headers, []() { return true; }); EXPECT_CALL(printer_, testPrint("'foo' 'bar'")); EXPECT_CALL(printer_, testPrint("'foo' 'bar'")); EXPECT_CALL(printer_, testPrint("'hello' 'world'")); start("callMe"); + wrapper.reset(); } // Don't finish iteration. @@ -252,10 +269,11 @@ TEST_F(LuaHeaderMapWrapperTest, DontFinishIteration) { setup(SCRIPT); Http::TestRequestHeaderMapImpl headers{{"foo", "bar"}, {"hello", "world"}}; - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return true; }); + auto wrapper = createWrapperRef(headers, []() { return true; }); EXPECT_THROW_WITH_MESSAGE( start("callMe"), Filters::Common::Lua::LuaException, "[string \"...\"]:5: cannot create a second iterator before completing the first"); + wrapper.reset(); } // Use iterator across yield. @@ -272,8 +290,7 @@ TEST_F(LuaHeaderMapWrapperTest, IteratorAcrossYield) { setup(SCRIPT); Http::TestRequestHeaderMapImpl headers{{"foo", "bar"}, {"hello", "world"}}; - Filters::Common::Lua::LuaDeathRef wrapper( - HeaderMapWrapper::create(coroutine_->luaState(), headers, []() { return true; }), true); + auto wrapper = createWrapperRef(headers, []() { return true; }); yield_callback_ = [] {}; start("callMe"); wrapper.reset(); @@ -293,8 +310,9 @@ TEST_F(LuaHeaderMapWrapperTest, SetHttp1ReasonPhrase) { setup(SCRIPT); auto headers = Http::ResponseHeaderMapImpl::create(); - HeaderMapWrapper::create(coroutine_->luaState(), *headers, []() { return true; }); + auto wrapper = createWrapperRef(*headers, []() { return true; }); start("callMe"); + wrapper.reset(); Http::StatefulHeaderKeyFormatterOptRef formatter(headers->formatter()); EXPECT_EQ(true, formatter.has_value()); From 8569caf0164f17ec6a95e273d3ae10827017f47c Mon Sep 17 00:00:00 2001 From: Derek Argueta Date: Wed, 13 May 2026 08:49:48 -0500 Subject: [PATCH 299/750] wasm: include environment_variables in vm_key to trigger VM recreation on change (#44563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When only the environment variables in `vm_config` changed, Envoy was silently reusing the existing Wasm VM rather than creating a new one. `proxy_wasm::makeVmKey` takes `(vm_id, configuration, code)` — none of which captured `environment_variables` — so the key was identical before and after an env var update. The VM was only recreated on code changes. - Serialize `vm_config.environment_variables()` and append it to the `code` string before computing the vm_key. Since `makeVmKey` is in the upstream `proxy_wasm_cpp_host` library and its signature can't be changed, appending to `code` is the cleanest available lever. - Add a unit test (`VmCacheEnvVarChange`) verifying that two `createWasm` calls differing only in env vars produce distinct VM handles rather than a cache hit. Fixes #40881 Risk Level: low Testing: unit test added Docs Changes: N/A Release Notes: done Platform Specific Features: no --------- Signed-off-by: Derek Argueta --- changelogs/current.yaml | 6 ++++ source/extensions/common/wasm/wasm.cc | 7 ++++- test/extensions/common/wasm/wasm_test.cc | 38 ++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 6c64fd9ed46c3..4498f1170b362 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -46,6 +46,12 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: wasm + change: | + Fixed a bug where Envoy did not recreate the Wasm VM when only + :ref:`environment_variables ` + changed in ``vm_config``. The VM was previously reused from the cache because environment variables + were not included in the vm_key computation. - area: load_report change: | Fixed a bug in load stats reporting where reports were dropped if only custom metrics or completed diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index abfe36be6a791..62052c71b26a7 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -373,8 +373,13 @@ bool createWasm(const PluginSharedPtr& plugin, const Stats::ScopeSharedPtr& scop .value_or(code.empty() ? EMPTY_STRING : INLINE_STRING); } + // Include environment_variables in the vm_key so that a change to env vars triggers VM + // recreation, the same way a code change does. The env vars hash is appended to vm_id (which is + // small) rather than to code (which can be O(MB)), with a separator to avoid key collisions. + const std::size_t env_vars_hash = MessageUtil::hash(vm_config.environment_variables()); + const std::string vm_id_with_env = absl::StrCat(vm_config.vm_id(), "|", env_vars_hash); auto vm_key = proxy_wasm::makeVmKey( - vm_config.vm_id(), + vm_id_with_env, THROW_OR_RETURN_VALUE(MessageUtil::anyToBytes(vm_config.configuration()), std::string), code); auto complete_cb = [cb, vm_key, plugin, scope, &api, &cluster_manager, &dispatcher, &lifecycle_notifier, create_root_context_for_testing, diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index 0851f8f146574..6c3701599b299 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -649,6 +649,44 @@ TEST_P(WasmCommonTest, VmCache) { proxy_wasm::clearWasmCachesForTesting(); } +// Changing environment variables must produce a distinct VM, not a cache hit. +TEST_P(WasmCommonTest, VmCacheEnvVarChange) { + // NullVm doesn't support key_values environment variables. + if (std::get<0>(GetParam()) == "null") { + return; + } + NiceMock init_manager; + + envoy::extensions::wasm::v3::PluginConfig plugin_config; + auto* vm_config = plugin_config.mutable_vm_config(); + vm_config->set_runtime(absl::StrCat("envoy.wasm.runtime.", std::get<0>(GetParam()))); + const std::string code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/common/wasm/test_data/test_cpp.wasm")); + EXPECT_FALSE(code.empty()); + vm_config->mutable_code()->mutable_local()->set_inline_bytes(code); + + auto plugin1 = std::make_shared( + plugin_config, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info_, nullptr); + WasmHandleSharedPtr wasm_handle1; + createWasm(plugin1, scope_, cluster_manager_, init_manager, *dispatcher_, *api_, + lifecycle_notifier_, remote_data_provider_, + [&wasm_handle1](const WasmHandleSharedPtr& w) { wasm_handle1 = w; }); + EXPECT_NE(wasm_handle1, nullptr); + + // Same config but with an env var added — must produce a new VM, not a cache hit. + (*vm_config->mutable_environment_variables()->mutable_key_values())["K"] = "V"; + auto plugin2 = std::make_shared( + plugin_config, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info_, nullptr); + WasmHandleSharedPtr wasm_handle2; + createWasm(plugin2, scope_, cluster_manager_, init_manager, *dispatcher_, *api_, + lifecycle_notifier_, remote_data_provider_, + [&wasm_handle2](const WasmHandleSharedPtr& w) { wasm_handle2 = w; }); + EXPECT_NE(wasm_handle2, nullptr); + EXPECT_NE(wasm_handle1, wasm_handle2); + + proxy_wasm::clearWasmCachesForTesting(); +} + TEST_P(WasmCommonTest, RemoteCode) { if (std::get<0>(GetParam()) == "null") { return; From a6462d30631477584689e366bfe52ccb0f2d3bf5 Mon Sep 17 00:00:00 2001 From: Reuben Tanner Date: Wed, 13 May 2026 12:11:18 -0400 Subject: [PATCH 300/750] Apply weak pointer semantics to streams in HTTP1 codec (#44676) Commit Message: Apply weak pointer semantics to HTTP/1 streams in ServerConnectionImpl and ClientConnectionImpl Additional Description: This change applies [an existing pattern](https://github.com/envoyproxy/envoy/commit/b701b033689be044e31e714f60a0f9265d2800ce) used to fix use-after-free issues in h2/h3 to both `ServerConnectionImpl` and `ClientConnectionImpl` in the HTTP/1 codec to safely access decoders. We replaced raw pointers to `RequestDecoder` and `ResponseDecoder` with `RequestDecoderHandlePtr` and `ResponseDecoderHandlePtr` respectively. This allows safe verification of whether the decoder is still valid before attempting to access it. We have also added `ENVOY_BUG` assertions to catch instances where the decoder is unexpectedly null. After running all tests internally, expectedly, there are no existing instances of the ENVOY_BUGs. Tests have been added/updated for both paths to verify that the `ENVOY_BUG` is triggered as expected when decoders are destroyed before message completion. *Disclosure: Used Gen AI to help generate the code and tests* Risk Level: Low (safety improvement using established patterns). Testing: Updated `ResponseDecoderDestroyedBeforeMessageComplete` and added `RequestDecoderDestroyedBeforeMessageComplete` in `codec_impl_test.cc`. All tests passed. Docs Changes: None. Release Notes: None. Platform Specific Features: None. --------- Signed-off-by: Reuben Tanner --- source/common/http/http1/codec_impl.cc | 91 ++++-- source/common/http/http1/codec_impl.h | 9 +- .../reverse_tunnel/reverse_tunnel_filter.cc | 11 +- test/common/http/http1/codec_impl_test.cc | 267 +++++++++++++----- .../reverse_tunnel/filter_unit_test.cc | 3 +- test/mocks/http/stream_decoder.cc | 2 +- 6 files changed, 270 insertions(+), 113 deletions(-) diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index f541dce5767f3..5da4f6a0587cf 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -1057,7 +1057,7 @@ void ClientConnectionImpl::dumpAdditionalState(std::ostream& os, int indent_leve os << spaces << "Dumping corresponding downstream request:"; if (pending_response_.has_value()) { os << '\n'; - const ResponseDecoder* decoder = pending_response_.value().decoder_; + const ResponseDecoder* decoder = pending_response_.value().decoder_handle_->get().ptr(); DUMP_DETAILS(decoder); } else { os << " null\n"; @@ -1246,7 +1246,11 @@ Envoy::StatusOr ServerConnectionImpl::onHeadersCompleteBase() { if (parser_->isChunked() || (parser_->contentLength().has_value() && parser_->contentLength().value() > 0) || handling_upgrade_) { - active_request_->request_decoder_->decodeHeaders(std::move(headers), false); + RequestDecoder* decoder = active_request_->request_decoder_handle_->get().ptr(); + ENVOY_BUG(decoder != nullptr, "RequestDecoder is null in onHeadersCompleteBase"); + if (decoder) { + decoder->decodeHeaders(std::move(headers), false); + } // If the connection has been closed (or is closing) after decoding headers, pause the parser // so we return control to the caller. @@ -1265,7 +1269,8 @@ Status ServerConnectionImpl::onMessageBeginBase() { if (!resetStreamCalled()) { ASSERT(active_request_ == nullptr); active_request_ = std::make_unique(*this, std::move(bytes_meter_before_stream_)); - active_request_->request_decoder_ = &callbacks_.newStream(active_request_->response_encoder_); + active_request_->request_decoder_handle_ = + callbacks_.newStream(active_request_->response_encoder_).getRequestDecoderHandle(); // Check for pipelined request flood as we prepare to accept a new request. // Parse errors that happen prior to onMessageBegin result in stream termination, it is not @@ -1289,7 +1294,11 @@ void ServerConnectionImpl::onBody(Buffer::Instance& data) { ASSERT(!deferred_end_stream_headers_); if (active_request_) { ENVOY_CONN_LOG(trace, "body size={}", connection_, data.length()); - active_request_->request_decoder_->decodeData(data, false); + RequestDecoder* decoder = active_request_->request_decoder_handle_->get().ptr(); + ENVOY_BUG(decoder != nullptr, "RequestDecoder is null in onBody"); + if (decoder) { + decoder->decodeData(data, false); + } } } @@ -1325,19 +1334,25 @@ CallbackResult ServerConnectionImpl::onMessageCompleteBase() { if (active_request_) { // The request_decoder should be non-null after we've called the newStream on callbacks. - ASSERT(active_request_->request_decoder_); + RequestDecoder* decoder = active_request_->request_decoder_handle_->get().ptr(); + ENVOY_BUG(decoder != nullptr, "request_decoder_handle_ is null in onMessageCompleteBase"); active_request_->remote_complete_ = true; if (deferred_end_stream_headers_) { - active_request_->request_decoder_->decodeHeaders( - std::move(absl::get(headers_or_trailers_)), true); + if (decoder) { + decoder->decodeHeaders(std::move(absl::get(headers_or_trailers_)), + true); + } deferred_end_stream_headers_ = false; } else if (processing_trailers_) { - active_request_->request_decoder_->decodeTrailers( - std::move(absl::get(headers_or_trailers_))); + if (decoder) { + decoder->decodeTrailers(std::move(absl::get(headers_or_trailers_))); + } } else { Buffer::OwnedImpl buffer; - active_request_->request_decoder_->decodeData(buffer, true); + if (decoder) { + decoder->decodeData(buffer, true); + } } // Reset to ensure no information from one requests persists to the next. @@ -1382,8 +1397,12 @@ Status ServerConnectionImpl::sendProtocolError(absl::string_view details) { active_request_->response_encoder_.setDetails(details); if (!active_request_->response_encoder_.startedResponse()) { - active_request_->request_decoder_->sendLocalReply( - error_code_, CodeUtility::toString(error_code_), nullptr, absl::nullopt, details); + RequestDecoder* decoder = active_request_->request_decoder_handle_->get().ptr(); + ENVOY_BUG(decoder != nullptr, "RequestDecoder is null in sendProtocolError"); + if (decoder) { + decoder->sendLocalReply(error_code_, CodeUtility::toString(error_code_), nullptr, + absl::nullopt, details); + } } return okStatus(); } @@ -1483,7 +1502,8 @@ RequestEncoder& ClientConnectionImpl::newStream(ResponseDecoder& response_decode ASSERT(!pending_response_.has_value()); ASSERT(pending_response_done_); - pending_response_.emplace(*this, std::move(bytes_meter_before_stream_), &response_decoder); + pending_response_.emplace(*this, std::move(bytes_meter_before_stream_), + response_decoder.createResponseDecoderHandle()); pending_response_done_ = false; return pending_response_.value().encoder_; } @@ -1540,12 +1560,16 @@ Envoy::StatusOr ClientConnectionImpl::onHeadersCompleteBase() { } } - if (HeaderUtility::isSpecial1xx(*headers)) { - pending_response_.value().decoder_->decode1xxHeaders(std::move(headers)); - } else if (cannotHaveBody() && !handling_upgrade_) { - deferred_end_stream_headers_ = true; - } else { - pending_response_.value().decoder_->decodeHeaders(std::move(headers), false); + ResponseDecoder* decoder = pending_response_.value().decoder_handle_->get().ptr(); + ENVOY_BUG(decoder != nullptr, "ResponseDecoder is null in onHeadersCompleteBase"); + if (decoder) { + if (HeaderUtility::isSpecial1xx(*headers)) { + decoder->decode1xxHeaders(std::move(headers)); + } else if (cannotHaveBody() && !handling_upgrade_) { + deferred_end_stream_headers_ = true; + } else { + decoder->decodeHeaders(std::move(headers), false); + } } // http-parser treats 1xx headers as their own complete response. Swallow the spurious @@ -1576,7 +1600,11 @@ void ClientConnectionImpl::onBody(Buffer::Instance& data) { ASSERT(!deferred_end_stream_headers_); if (pending_response_.has_value()) { ASSERT(!pending_response_done_); - pending_response_.value().decoder_->decodeData(data, false); + ResponseDecoder* decoder = pending_response_.value().decoder_handle_->get().ptr(); + ENVOY_BUG(decoder != nullptr, "ResponseDecoder is null in onBody"); + if (decoder) { + decoder->decodeData(data, false); + } } } @@ -1594,16 +1622,19 @@ CallbackResult ClientConnectionImpl::onMessageCompleteBase() { // be reset just yet. Preserve the state in pending_response_done_ instead. pending_response_done_ = true; - if (deferred_end_stream_headers_) { - response.decoder_->decodeHeaders( - std::move(absl::get(headers_or_trailers_)), true); - deferred_end_stream_headers_ = false; - } else if (processing_trailers_) { - response.decoder_->decodeTrailers( - std::move(absl::get(headers_or_trailers_))); - } else { - Buffer::OwnedImpl buffer; - response.decoder_->decodeData(buffer, true); + ResponseDecoder* decoder = response.decoder_handle_->get().ptr(); + ENVOY_BUG(decoder != nullptr, "ResponseDecoder is null in onMessageCompleteBase"); + if (decoder) { + if (deferred_end_stream_headers_) { + decoder->decodeHeaders(std::move(absl::get(headers_or_trailers_)), + true); + deferred_end_stream_headers_ = false; + } else if (processing_trailers_) { + decoder->decodeTrailers(std::move(absl::get(headers_or_trailers_))); + } else { + Buffer::OwnedImpl buffer; + decoder->decodeData(buffer, true); + } } if (force_reset_on_premature_upstream_half_close_ && !encode_complete_) { diff --git a/source/common/http/http1/codec_impl.h b/source/common/http/http1/codec_impl.h index 3bbb475b0ec9d..3b7b0e3f1e2e8 100644 --- a/source/common/http/http1/codec_impl.h +++ b/source/common/http/http1/codec_impl.h @@ -475,7 +475,7 @@ class ServerConnectionImpl : public ServerConnection, public ConnectionImpl { void dumpState(std::ostream& os, int indent_level) const; HeaderString request_url_; - RequestDecoder* request_decoder_{}; + RequestDecoderHandlePtr request_decoder_handle_; ResponseEncoderImpl response_encoder_; bool remote_complete_{}; }; @@ -588,10 +588,11 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { private: struct PendingResponse { PendingResponse(ConnectionImpl& connection, StreamInfo::BytesMeterSharedPtr&& bytes_meter, - ResponseDecoder* decoder) - : encoder_(connection, std::move(bytes_meter)), decoder_(decoder) {} + ResponseDecoderHandlePtr&& decoder_handle) + : encoder_(connection, std::move(bytes_meter)), decoder_handle_(std::move(decoder_handle)) { + } RequestEncoderImpl encoder_; - ResponseDecoder* decoder_; + ResponseDecoderHandlePtr decoder_handle_; }; bool cannotHaveBody(); diff --git a/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc b/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc index 99b6e95b9a23a..10370d9ba204f 100644 --- a/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc +++ b/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc @@ -48,6 +48,15 @@ Extensions::Bootstrap::ReverseConnection::UpstreamSocketManager* getThreadLocalS return tls_registry->socketManager(); } +class RequestDecoderHandleImpl : public Http::RequestDecoderHandle { +public: + explicit RequestDecoderHandleImpl(Http::RequestDecoder& decoder) : decoder_(decoder) {} + OptRef get() override { return decoder_; } + +private: + Http::RequestDecoder& decoder_; +}; + } // namespace // Stats helper implementation. @@ -332,7 +341,7 @@ AccessLog::InstanceSharedPtrVector ReverseTunnelFilter::RequestDecoderImpl::acce } Http::RequestDecoderHandlePtr ReverseTunnelFilter::RequestDecoderImpl::getRequestDecoderHandle() { - return nullptr; + return std::make_unique(*this); } void ReverseTunnelFilter::RequestDecoderImpl::processIfComplete(bool end_stream) { diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 92833d1b109e5..0abd9daf6baf6 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -49,6 +49,16 @@ namespace Envoy { namespace Http { namespace { +void setupRequestDecoderMock(Http::MockRequestDecoder& request_decoder) { + EXPECT_CALL(request_decoder, getRequestDecoderHandle()) + .WillRepeatedly(Invoke([&request_decoder]() { + auto handle = std::make_unique>(); + ON_CALL(*handle, get()) + .WillByDefault(Return(OptRef(request_decoder))); + return handle; + })); +} + constexpr absl::string_view kNullCharacter("\0", 1); std::string createHeaderOrTrailerFragment(int num_headers) { @@ -98,7 +108,9 @@ class MockRequestDecoderShimWithUhv : public Http::MockRequestDecoder { public: MockRequestDecoderShimWithUhv(Http::ServerHeaderValidator* header_validator, Network::MockConnection& connection) - : header_validator_(header_validator), connection_(connection) {} + : header_validator_(header_validator), connection_(connection) { + setupRequestDecoderMock(*this); + } void setResponseEncoder(Http::ResponseEncoder* response_encoder) { response_encoder_ = response_encoder; @@ -208,6 +220,7 @@ class Http1ServerConnectionImplTest : public Http1CodecTestBase { sendAndValidateRequestAndSendResponse(Buffer::Instance& buffer, const TestRequestHeaderMapImpl& expected_request_headers) { NiceMock decoder; + setupRequestDecoderMock(decoder); Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -258,8 +271,6 @@ class Http1ServerConnectionImplTest : public Http1CodecTestBase { void Http1ServerConnectionImplTest::expect400(Buffer::OwnedImpl& buffer, absl::string_view expected_details, absl::string_view expected_message) { - InSequence sequence; - codec_settings_.allow_absolute_url_ = true; codec_ = std::make_unique( connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, @@ -267,6 +278,8 @@ void Http1ServerConnectionImplTest::expect400(Buffer::OwnedImpl& buffer, overload_manager_); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + InSequence sequence; Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -285,8 +298,6 @@ void Http1ServerConnectionImplTest::expect400(Buffer::OwnedImpl& buffer, void Http1ServerConnectionImplTest::expectHeadersTest(Protocol p, bool allow_absolute_url, Buffer::OwnedImpl& buffer, TestRequestHeaderMapImpl& expected_headers) { - InSequence sequence; - // Make a new 'codec' with the right settings if (allow_absolute_url) { codec_settings_.allow_absolute_url_ = allow_absolute_url; @@ -297,6 +308,9 @@ void Http1ServerConnectionImplTest::expectHeadersTest(Protocol p, bool allow_abs } MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, decodeHeaders_(HeaderMapEqual(&expected_headers), true)); @@ -318,8 +332,9 @@ void Http1ServerConnectionImplTest::expectTrailersTest(bool enable_trailers) { overload_manager_); } - InSequence sequence; StrictMock decoder; + setupRequestDecoderMock(decoder); + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, decodeHeaders_(_, false)); @@ -492,9 +507,10 @@ void Http1ServerConnectionImplTest::testServerAllowChunkedContentLength(uint32_t TEST_F(Http1ServerConnectionImplTest, EmptyHeader) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -516,9 +532,8 @@ TEST_F(Http1ServerConnectionImplTest, EmptyHeader) { TEST_F(Http1ServerConnectionImplTest, IdentityEncodingNoChunked) { initialize(); - InSequence sequence; - MockRequestDecoderShimWithUhv decoder(header_validator_.get(), connection_); + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\nHost: host\r\ntransfer-encoding: identity\r\n\r\n"); @@ -536,9 +551,8 @@ TEST_F(Http1ServerConnectionImplTest, IdentityEncodingNoChunked) { TEST_F(Http1ServerConnectionImplTest, UnsupportedEncoding) { initialize(); - InSequence sequence; - MockRequestDecoderShimWithUhv decoder(header_validator_.get(), connection_); + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\nHost: host\r\ntransfer-encoding: gzip\r\n\r\n"); @@ -560,9 +574,10 @@ TEST_F(Http1ServerConnectionImplTest, UnsupportedEncoding) { TEST_F(Http1ServerConnectionImplTest, LargeBodyOptimization) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); std::string post = "POST / HTTP/1.1\r\ncontent-length: 1000000\r\n\r\n"; @@ -590,9 +605,10 @@ TEST_F(Http1ServerConnectionImplTest, LargeBodyOptimization) { TEST_F(Http1ServerConnectionImplTest, ContentLengthAllBitsSet) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -613,9 +629,10 @@ TEST_F(Http1ServerConnectionImplTest, ContentLengthAllBitsSet) { TEST_F(Http1ServerConnectionImplTest, ChunkedBody) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -644,9 +661,10 @@ TEST_F(Http1ServerConnectionImplTest, ChunkedBody) { TEST_F(Http1ServerConnectionImplTest, ChunkedBodySplitOverTwoDispatches) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -682,9 +700,10 @@ TEST_F(Http1ServerConnectionImplTest, ChunkedBodySplitOverTwoDispatches) { TEST_F(Http1ServerConnectionImplTest, ChunkedBodyFragmentedBuffer) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -711,9 +730,10 @@ TEST_F(Http1ServerConnectionImplTest, ChunkedBodyFragmentedBuffer) { TEST_F(Http1ServerConnectionImplTest, ChunkedBodyCase) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -738,9 +758,10 @@ TEST_F(Http1ServerConnectionImplTest, ChunkedBodyCase) { TEST_F(Http1ServerConnectionImplTest, InvalidChunkHeader) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -770,9 +791,10 @@ TEST_F(Http1ServerConnectionImplTest, IdentityAndChunkedBody) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); Buffer::OwnedImpl buffer("POST / HTTP/1.1\r\nHost: host\r\ntransfer-encoding: " @@ -909,9 +931,10 @@ TEST_F(Http1ServerConnectionImplTest, Http10) { codec_settings_.accept_http_10_ = true; initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{{":path", "/"}, {":method", "GET"}}; @@ -929,9 +952,10 @@ TEST_F(Http1ServerConnectionImplTest, Http10HostAdded) { codec_settings_.default_host_for_http_10_ = "example.com"; initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -971,6 +995,7 @@ TEST_F(Http1ServerConnectionImplTest, Http10MultipleResponses) { initialize(); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); // Send a full HTTP/1.0 request and proxy a response. { Buffer::OwnedImpl buffer( @@ -1030,9 +1055,10 @@ TEST_F(Http1ServerConnectionImplTest, HttpVersion) { const absl::optional& expected_error = test_case.expected_error_; initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); if (expected_error.has_value()) { EXPECT_CALL(decoder, @@ -1121,6 +1147,7 @@ TEST_F(Http1ServerConnectionImplTest, Http11InvalidTrailerPost) { initialize(); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, decodeHeaders_(_, false)); @@ -1194,9 +1221,10 @@ TEST_F(Http1ServerConnectionImplTest, Http11Options) { TEST_F(Http1ServerConnectionImplTest, SimpleGet) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{{":path", "/"}, {":method", "GET"}}; @@ -1212,9 +1240,10 @@ TEST_F(Http1ServerConnectionImplTest, SimpleGet) { TEST_F(Http1ServerConnectionImplTest, ProtocolStreamId) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -1236,6 +1265,7 @@ TEST_F(Http1ServerConnectionImplTest, BadRequestNoStream) { initialize(); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); // Check that before any headers are parsed, requests do not look like HEAD or gRPC requests. EXPECT_CALL(decoder, sendLocalReply(_, _, _, _, _)); @@ -1249,6 +1279,7 @@ TEST_F(Http1ServerConnectionImplTest, RejectCustomMethod) { initialize(); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, sendLocalReply(_, _, _, _, _)); @@ -1264,6 +1295,7 @@ TEST_F(Http1ServerConnectionImplTest, RejectInvalidCharacterInMethod) { initialize(); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, sendLocalReply(_, _, _, _, _)); @@ -1280,6 +1312,7 @@ TEST_F(Http1ServerConnectionImplTest, AllowCustomMethod) { initialize(); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); Buffer::OwnedImpl buffer("BAD / HTTP/1.1\r\n"); @@ -1299,6 +1332,7 @@ TEST_F(Http1ServerConnectionImplTest, BadRequestStartedStream) { initialize(); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); Buffer::OwnedImpl buffer("G"); @@ -1360,9 +1394,10 @@ TEST_F(Http1ServerConnectionImplTest, FloodProtection) { TEST_F(Http1ServerConnectionImplTest, HostHeaderTranslation) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -1381,6 +1416,7 @@ TEST_F(Http1ServerConnectionImplTest, HeaderInvalidCharsRejection) { initialize(); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -1477,6 +1513,7 @@ TEST_F(Http1ServerConnectionImplTest, HeaderInvalidAuthority) { initialize(); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -1500,9 +1537,10 @@ TEST_F(Http1ServerConnectionImplTest, HeaderMutateEmbeddedNul) { for (size_t n = 0; n < example_input.size(); ++n) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); Buffer::OwnedImpl buffer( @@ -1529,9 +1567,10 @@ TEST_F(Http1ServerConnectionImplTest, TrailerMutateEmbeddedNul) { for (size_t n = 0; n < trailers.size(); ++n) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); Buffer::OwnedImpl buffer(absl::StrCat(headers_and_body, trailers.substr(0, n), kNullCharacter, @@ -1571,9 +1610,10 @@ TEST_F(Http1ServerConnectionImplTest, HeaderMutateEmbeddedCRLF) { TEST_F(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -1593,9 +1633,10 @@ TEST_F(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { TEST_F(Http1ServerConnectionImplTest, PostWithContentLength) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -1619,9 +1660,10 @@ TEST_F(Http1ServerConnectionImplTest, PostWithContentLength) { TEST_F(Http1ServerConnectionImplTest, PostWithContentLengthFragmentedBuffer) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -1903,6 +1945,9 @@ TEST_F(Http1ServerConnectionImplTest, ChunkedResponse) { TEST_F(Http1ServerConnectionImplTest, VerifyRequestHeaderTrailerMapMaxLimits) { initialize(); + MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + InSequence sequence; codec_settings_.allow_absolute_url_ = true; @@ -1912,7 +1957,6 @@ TEST_F(Http1ServerConnectionImplTest, VerifyRequestHeaderTrailerMapMaxLimits) { max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW, overload_manager_); - MockRequestDecoder decoder; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ {{":path", "/"}, {":method", "POST"}, {"transfer-encoding", "chunked"}}, @@ -2346,8 +2390,8 @@ TEST_F(Http1ServerConnectionImplTest, ConnectRequestWithEarlyData) { TEST_F(Http1ServerConnectionImplTest, ConnectRequestWithTEChunked) { initialize(); - InSequence sequence; MockRequestDecoderShimWithUhv decoder(header_validator_.get(), connection_); + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { decoder.setResponseEncoder(&encoder); @@ -2472,6 +2516,7 @@ TEST_F(Http1ServerConnectionImplTest, LoadShedPointCanCloseConnectionOnDispatchO EXPECT_CALL(mock_abort_dispatch, shouldShedLoad()).WillOnce(Return(true)); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, sendLocalReply(_, _, _, _, _)); @@ -2523,6 +2568,7 @@ TEST_F(Http1ServerConnectionImplTest, LoadShedPointCanCloseConnectionOnDispatchO EXPECT_CALL(mock_abort_dispatch, shouldShedLoad()).WillOnce(Return(false)); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); Buffer::OwnedImpl request_line_buffer("GET / HTTP/1.1\r\n"); @@ -3620,6 +3666,7 @@ TEST_F(Http1ServerConnectionImplTest, Utf8Path) { initialize(); MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); Buffer::OwnedImpl buffer("GET /δ¶/δt/pope?q=1#narf HXXP/1.1\r\n\r\n"); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); @@ -4020,6 +4067,8 @@ TEST_F(Http1ServerConnectionImplTest, ParseUrl) { initialize(); StrictMock decoder; + setupRequestDecoderMock(decoder); + EXPECT_CALL(decoder, decodeHeaders_(_, _)).Times(testing::AnyNumber()); Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -4039,9 +4088,9 @@ TEST_F(Http1ServerConnectionImplTest, ParseUrl) { for (const char* invalid_first_line : kInvalidFirstLines) { initialize(); - InSequence sequence; - StrictMock decoder; + setupRequestDecoderMock(decoder); + InSequence sequence; Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -4148,6 +4197,7 @@ TEST_F(Http1ServerConnectionImplTest, ValidMethodFirstCharacter) { initialize(); StrictMock decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); Buffer::OwnedImpl buffer1("G"); @@ -4180,8 +4230,6 @@ TEST_F(Http1ClientConnectionImplTest, InvalidResponseFirstCharacter) { // A first read of zero bytes when parsing a request is ignored. TEST_F(Http1ServerConnectionImplTest, FirstReadEOF) { initialize(); - InSequence s; - // A read of zero bytes does not trigger creation of a new stream. EXPECT_CALL(callbacks_, newStream(_, _)).Times(0); @@ -4190,6 +4238,8 @@ TEST_F(Http1ServerConnectionImplTest, FirstReadEOF) { ASSERT_TRUE(status.ok()); StrictMock decoder; + setupRequestDecoderMock(decoder); + InSequence s; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\n\r\n"); @@ -4224,9 +4274,9 @@ TEST_F(Http1ClientConnectionImplTest, FirstReadEOF) { // A read of zero bytes during the first line of a request is an error. TEST_F(Http1ServerConnectionImplTest, EOFDuringHeaders) { initialize(); - InSequence s; - StrictMock decoder; + setupRequestDecoderMock(decoder); + InSequence s; Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -4271,9 +4321,9 @@ TEST_F(Http1ClientConnectionImplTest, EOFDuringHeaders) { // A read of zero bytes during chunked request body is an error. TEST_F(Http1ServerConnectionImplTest, EOFDuringChunkedBody) { initialize(); - InSequence s; - StrictMock decoder; + setupRequestDecoderMock(decoder); + InSequence s; Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -4329,9 +4379,9 @@ TEST_F(Http1ClientConnectionImplTest, EOFDuringChunkedBody) { // A read of zero bytes before Content-Length bytes of request body are read is an error. TEST_F(Http1ServerConnectionImplTest, EOFDuringContentLengthBody) { initialize(); - InSequence s; - StrictMock decoder; + setupRequestDecoderMock(decoder); + InSequence s; Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -4386,9 +4436,9 @@ TEST_F(Http1ClientConnectionImplTest, EOFDuringContentLengthBody) { // body but without a Content-Length (or Transfer-Encoding: chunked) header. TEST_F(Http1ServerConnectionImplTest, NoContentLengthRequest) { initialize(); - InSequence s; - StrictMock decoder; + setupRequestDecoderMock(decoder); + InSequence s; Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -4453,12 +4503,68 @@ TEST_F(Http1ClientConnectionImplTest, NoContentLengthResponse) { } } +TEST_F(Http1ClientConnectionImplTest, DecoderDestroyedBeforeMessageComplete) { + initialize(); + + auto response_decoder = std::make_unique>(); + Http::RequestEncoder& request_encoder = codec_->newStream(*response_decoder); + TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); + + EXPECT_CALL(*response_decoder, decodeHeaders_(_, false)); + EXPECT_CALL(*response_decoder, decodeData(BufferStringEqual("hello"), false)); + + Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" + "5\r\nhello\r\n"); + auto status = codec_->dispatch(response); + EXPECT_TRUE(status.ok()); + + // Destroy the decoder! + response_decoder.reset(); + + // Send final chunk. This triggers onMessageCompleteBase. + Buffer::OwnedImpl final_chunk("0\r\n\r\n"); + EXPECT_ENVOY_BUG(status = codec_->dispatch(final_chunk), + "ResponseDecoder is null in onMessageCompleteBase"); + EXPECT_TRUE(status.ok()); +} + +TEST_F(Http1ServerConnectionImplTest, RequestDecoderDestroyedBeforeMessageComplete) { + initialize(); + + StrictMock decoder; + auto* handle = new NiceMock(); + auto handle_ptr = std::unique_ptr(handle); + + EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); + EXPECT_CALL(decoder, getRequestDecoderHandle()) + .WillOnce(Return(testing::ByMove(std::move(handle_ptr)))); + + EXPECT_CALL(*handle, get()).WillRepeatedly(Return(OptRef(decoder))); + + Buffer::OwnedImpl headers_buffer( + "GET / HTTP/1.1\r\nHost: host\r\nTransfer-Encoding: chunked\r\n\r\n"); + + EXPECT_CALL(decoder, decodeHeaders_(_, false)); + + auto status = codec_->dispatch(headers_buffer); + EXPECT_TRUE(status.ok()); + + EXPECT_CALL(*handle, get()).WillRepeatedly(Return(OptRef{})); + + Buffer::OwnedImpl final_chunk("0\r\n\r\n"); + + EXPECT_ENVOY_BUG(status = codec_->dispatch(final_chunk), + "request_decoder_handle_ is null in onMessageCompleteBase"); + EXPECT_TRUE(status.ok()); +} + // Regression test for https://github.com/envoyproxy/envoy/issues/25458. TEST_F(Http1ServerConnectionImplTest, EmptyFieldName) { initialize(); - InSequence s; - StrictMock decoder; + setupRequestDecoderMock(decoder); + InSequence s; Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -4483,9 +4589,8 @@ TEST_F(Http1ServerConnectionImplTest, EmptyFieldName) { // Multiple Transfer-Encoding request headers are not allowed, regardless of their value. TEST_F(Http1ServerConnectionImplTest, MultipleTransferEncoding) { initialize(); - InSequence s; - MockRequestDecoderShimWithUhv decoder(header_validator_.get(), connection_); + InSequence s; Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -4514,9 +4619,9 @@ TEST_F(Http1ServerConnectionImplTest, MultipleTransferEncoding) { TEST_F(Http1ServerConnectionImplTest, Http10Rejected) { initialize(); - InSequence s; - StrictMock decoder; + setupRequestDecoderMock(decoder); + InSequence s; Http::ResponseEncoder* response_encoder = nullptr; EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { @@ -4556,6 +4661,7 @@ TEST_F(Http1ServerConnectionImplTest, SeparatorInHeaderName) { initialize(); StrictMock decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, sendLocalReply(Http::Code::BadRequest, "Bad Request", _, _, "http1.codec_error")); @@ -4594,6 +4700,7 @@ TEST_F(Http1ServerConnectionImplTest, SpaceInHeaderName) { initialize(); StrictMock decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, @@ -4630,6 +4737,7 @@ TEST_F(Http1ServerConnectionImplTest, ExtendedAsciiInHeaderName) { initialize(); StrictMock decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, sendLocalReply(Http::Code::BadRequest, "Bad Request", _, _, "http1.codec_error")); @@ -4664,6 +4772,7 @@ TEST_F(Http1ServerConnectionImplTest, Char22InHeaderValue) { initialize(); StrictMock decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, sendLocalReply(Http::Code::BadRequest, "Bad Request", _, _, "http1.invalid_characters")); @@ -4698,6 +4807,7 @@ TEST_F(Http1ServerConnectionImplTest, MultipleContentLength) { initialize(); StrictMock decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, sendLocalReply(Http::Code::BadRequest, "Bad Request", _, _, "http1.codec_error")); @@ -4771,6 +4881,7 @@ TEST_F(Http1ServerConnectionImplTest, ObsFold) { initialize(); StrictMock decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -4819,6 +4930,7 @@ void Http1ServerConnectionImplTest::testRequestWithValueExpectSuccess( initialize(); StrictMock decoder; + setupRequestDecoderMock(decoder); TestRequestHeaderMapImpl expected_headers{ {":path", "/"}, {":method", "GET"}, @@ -4840,6 +4952,7 @@ void Http1ServerConnectionImplTest::testRequestWithValueExpectFailure( initialize(); StrictMock decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, sendLocalReply(Http::Code::BadRequest, "Bad Request", _, _, expected_error_details)); @@ -5015,9 +5128,9 @@ TEST_F(Http1ClientConnectionImplTest, ValueEndsWithLF) { TEST_F(Http1ServerConnectionImplTest, FirstLineInvalidCR) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -5066,9 +5179,9 @@ TEST_F(Http1ClientConnectionImplTest, FirstLineInvalidCR) { TEST_F(Http1ServerConnectionImplTest, HeaderNameInvalidCR) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, sendLocalReply(Http::Code::BadRequest, "Bad Request", _, _, "http1.codec_error")); @@ -5110,9 +5223,9 @@ TEST_F(Http1ClientConnectionImplTest, HeaderNameInvalidCR) { TEST_F(Http1ServerConnectionImplTest, ChunkExtensionInvalidCR) { initialize(); - InSequence sequence; - MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); + InSequence sequence; EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); TestRequestHeaderMapImpl expected_headers{ @@ -5170,6 +5283,7 @@ TEST_F(Http1ServerConnectionImplTest, RequestAfterConnectionClose) { { Http::ResponseEncoder* response_encoder = nullptr; MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)) .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { response_encoder = &encoder; @@ -5189,6 +5303,7 @@ TEST_F(Http1ServerConnectionImplTest, RequestAfterConnectionClose) { { MockRequestDecoder decoder; + setupRequestDecoderMock(decoder); EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); EXPECT_CALL(decoder, decodeHeaders_(_, true)); diff --git a/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc b/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc index 23e6025130165..5d331e5b51895 100644 --- a/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc +++ b/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc @@ -438,7 +438,8 @@ TEST_F(ReverseTunnelFilterUnitTest, RequestDecoderInterfaceCoverageViaNewStream) auto logs = decoder.accessLogHandlers(); EXPECT_TRUE(logs.empty()); auto handle = decoder.getRequestDecoderHandle(); - EXPECT_EQ(nullptr, handle.get()); + EXPECT_NE(nullptr, handle.get()); + EXPECT_EQ(&decoder, handle->get().ptr()); } // Test configuration with custom ping interval. diff --git a/test/mocks/http/stream_decoder.cc b/test/mocks/http/stream_decoder.cc index 30b98fe5aacb1..61efbdb2e5996 100644 --- a/test/mocks/http/stream_decoder.cc +++ b/test/mocks/http/stream_decoder.cc @@ -19,7 +19,7 @@ MockRequestDecoder::MockRequestDecoder() { ASSERT_NE(nullptr, headers->Method()); })); ON_CALL(*this, getRequestDecoderHandle()).WillByDefault(Invoke([this]() { - auto handle = std::make_unique(); + auto handle = std::make_unique>(); ON_CALL(*handle, get()).WillByDefault(Return(OptRef(*this))); return handle; })); From 1fa8281e0beda249c343f3b95939a08f38227c8a Mon Sep 17 00:00:00 2001 From: Krishna Sharma <113549196+roll-no-21@users.noreply.github.com> Date: Wed, 13 May 2026 21:56:42 +0530 Subject: [PATCH 301/750] reverse_tunnel: add lifecycle access logging (#44298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emit structured lifecycle records for tunnel setup, handoff, close, and post-handoff HTTP/2 keepalive timeouts so reverse-tunnel behavior can be debugged from access logs without relying on codec changes. Commit Message: reverse_tunnel: add lifecycle access logging Additional Description: Today, debugging reverse tunnel connection issues (silent drops, keepalive timeouts, unexpected closes) requires correlating codec-level debug logs across multiple threads and connections. This PR adds first-class access log support to the reverse tunnel upstream socket interface so that operators can observe the full tunnel lifecycle from structured, machine-parseable access logs. **Proto change (`UpstreamReverseConnectionSocketInterface`)** A new `repeated config.accesslog.v3.AccessLog access_log = 6` field is added to the bootstrap config. When configured, lifecycle events are emitted through the standard Envoy access log framework. No logs are emitted when the field is absent — fully backward compatible. **Lifecycle info struct (`ReverseTunnelLifecycleInfo`)** A new `reverse_tunnel_lifecycle_info.h` header defines: - `ReverseTunnelLifecycleInfo` — per-socket metadata carried through the tunnel lifetime: `node_id`, `cluster_id`, `tenant_id`, local/remote addresses, worker name, file descriptor, and state flags (`handed_off_to_upstream`, `upstream_lifecycle_filter_attached`, `socket_dead_notified`, `close_log_emitted`, `close_reason`). - Well-known string constants for all lifecycle events, socket states, handoff kinds, close reasons, filter-state keys, and the dynamic metadata namespace. **Lifecycle events emitted** Each access log record is written into the `envoy.reverse_tunnel.lifecycle` dynamic metadata namespace with these fields: | Field | Description | |----------------|---------------------------------------------------------------| | `event` | One of the event names below. | | `node_id` | Originating node identifier. | | `cluster_id` | Originating cluster identifier. | | `tenant_id` | Tenant identifier (empty when tenant isolation is off). | | `worker` | Envoy worker thread that owns the socket. | | `fd` | File descriptor number. | | `socket_state` | `idle`, `handed_off`, or `in_use`. | | `handoff_kind` | Present only on `socket_handoff`: `pool_to_upstream`. | | `close_reason` | Present only on `tunnel_closed` (see close reasons below). | The same node/cluster/tenant/worker/fd values are also written into connection filter state under `envoy.reverse_tunnel.*` keys so they can be referenced in `%FILTER_STATE(...)%` format strings. *Core lifecycle events:* - `tunnel_setup` — reverse tunnel accepted and cached. - `socket_handoff` — idle socket handed off to the upstream connection pool. - `tunnel_closed` — socket removed from tracking (final event for every tunnel). - `http2_keepalive_timeout` — upstream closed locally due to HTTP/2 PING timeout after handoff. *Idle-phase ping events (while socket is cached):* - `idle_ping_sent` / `idle_ping_ack` / `idle_ping_miss` / `idle_ping_timeout` *Post-handoff HTTP/2 keepalive events:* - `http2_keepalive_ping_sent` / `http2_keepalive_ping_ack` *Close reason values on `tunnel_closed`:* - `idle_peer_close` — peer closed while idle. - `idle_read_error` — read error on the idle socket. - `idle_ping_write_failure` — RPING write failed. - `idle_ping_timeout` — RPINGs went unanswered. - `remote_close` — peer closed after handoff. - `local_close` — Envoy closed after handoff. - `explicit_close` — explicit close (e.g. shutdown). **Upstream network filter (`envoy.filters.upstream_network.reverse_tunnel_lifecycle`)** A new `ReverseTunnelUpstreamLifecycleFilter` (upstream read filter) is introduced to observe post-handoff connection events. On `onNewConnection()` it copies the tunnel's `ReverseTunnelLifecycleInfo` from the socket manager into the upstream connection's filter state. On `LocalClose` with `http2_ping_timeout`, it emits the `http2_keepalive_timeout` event through the extension's access loggers. It also provides the final close reason to the socket manager so the deferred `tunnel_closed` record carries the real reason rather than a generic `explicit_close`. **`UpstreamSocketManager` changes** - Tracks `fd_to_lifecycle_info_` map alongside existing fd maps. - Emits `tunnel_setup` on `addConnectionSocket()`. - Emits `socket_handoff` on `getConnectionSocket()`. - Emits `tunnel_closed` from `markSocketDead()`, with deferred close log support: when the upstream lifecycle filter is attached and no close reason is known yet, the close log is deferred until the filter reports the reason via `maybeEmitDeferredCloseLog()`. - Sets close reason on idle-phase failures: `idle_peer_close`, `idle_read_error`, `idle_ping_write_failure`, `idle_ping_timeout`. - Passes `tenant_id` through `handoffSocketToWorker()` and `addConnectionSocket()` so lifecycle info retains the original tenant. **`ReverseTunnelAcceptorExtension` changes** - Constructor moved from header to `.cc` to instantiate access loggers from config via `AccessLog::AccessLogFactory::fromProto()`. - Two new emit methods: - `emitSyntheticLifecycleLog()` — builds a temporary `StreamInfoImpl` from lifecycle metadata (used for idle-phase events and close events where no real connection StreamInfo exists). - `emitConnectionLifecycleLog()` — populates an existing connection's StreamInfo with lifecycle metadata (used for post-handoff events). - Both write dynamic metadata and filter state, then call all configured access loggers. **`ReverseTunnelFilter` change** - `processAcceptedConnection()` now passes the original (unscoped) node_id, cluster_id, and tenant_id to `addConnectionSocket()`. The socket manager derives tenant-scoped internal keys itself, keeping lifecycle metadata clean. **Docs** - New "Lifecycle access logs" section in `reverse_tunnel.rst` documenting config, events, metadata fields, and the upstream network filter. Risk Level: Low — additive only; no logs emitted unless `access_log` is configured. Testing: - `reverse_tunnel_upstream_lifecycle_test.cc` — unit tests for the upstream lifecycle filter: filter state propagation, keepalive timeout event emission, remote/local close reason forwarding, deferred close log behavior. - `upstream_socket_manager_test.cc` — lifecycle log tests: tunnel_setup on add, socket_handoff on get, tunnel_closed on markSocketDead with correct close reasons for each idle-phase failure mode. - `config_validation_test.cc` — validates the new access_log proto field parses. Docs Changes: Added lifecycle access logs section and upstream network filter usage to `docs/root/configuration/other_features/reverse_tunnel.rst`. Release Notes: N/A (reverse tunnel is not a public-facing feature) Platform Specific Features: N/A Signed-off-by: Krishna Sharma Signed-off-by: Krishna Sharma --- .../upstream_socket_interface/v3/BUILD | 1 + ..._reverse_connection_socket_interface.proto | 7 +- .../reverse_connection/responder-envoy.yaml | 16 + .../other_features/reverse_tunnel.rst | 90 +++- .../upstream_socket_interface/BUILD | 15 + .../reverse_tunnel_acceptor_extension.cc | 181 ++++++++ .../reverse_tunnel_acceptor_extension.h | 84 ++-- .../reverse_tunnel_lifecycle_info.h | 68 +++ .../reverse_tunnel_upstream_lifecycle.cc | 173 ++++++++ .../reverse_tunnel_upstream_lifecycle.h | 56 +++ .../upstream_socket_manager.cc | 254 +++++++++-- .../upstream_socket_manager.h | 33 +- source/extensions/extensions_build_config.bzl | 1 + source/extensions/extensions_metadata.yaml | 7 + .../reverse_tunnel/reverse_tunnel_filter.cc | 9 +- .../upstream_socket_interface/BUILD | 25 ++ .../config_validation_test.cc | 21 + .../reverse_tunnel_upstream_lifecycle_test.cc | 396 ++++++++++++++++++ .../upstream_socket_manager_test.cc | 325 ++++++++++++++ tools/extensions/extensions_schema.yaml | 1 + tools/spelling/spelling_dictionary.txt | 2 + 21 files changed, 1693 insertions(+), 72 deletions(-) create mode 100644 source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_lifecycle_info.h create mode 100644 source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.cc create mode 100644 source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.h create mode 100644 test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle_test.cc diff --git a/api/envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/BUILD b/api/envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/BUILD index 504c6c70514ac..6245b874a86cd 100644 --- a/api/envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/BUILD +++ b/api/envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/BUILD @@ -6,6 +6,7 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/config/accesslog/v3:pkg", "//envoy/config/core/v3:pkg", "@xds//udpa/annotations:pkg", ], diff --git a/api/envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/upstream_reverse_connection_socket_interface.proto b/api/envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/upstream_reverse_connection_socket_interface.proto index 0338c30270916..1591290b681b8 100644 --- a/api/envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/upstream_reverse_connection_socket_interface.proto +++ b/api/envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/upstream_reverse_connection_socket_interface.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package envoy.extensions.bootstrap.reverse_tunnel.upstream_socket_interface.v3; +import "envoy/config/accesslog/v3/accesslog.proto"; import "envoy/config/core/v3/extension.proto"; import "google/protobuf/wrappers.proto"; @@ -19,7 +20,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.bootstrap.reverse_tunnel.upstream_socket_interface] // Configuration for the upstream reverse connection socket interface. -// [#next-free-field: 6] +// [#next-free-field: 7] message UpstreamReverseConnectionSocketInterface { // Stat prefix for upstream reverse connection socket interface stats. string stat_prefix = 1; @@ -44,4 +45,8 @@ message UpstreamReverseConnectionSocketInterface { // containing the ``:`` delimiter are rejected to avoid ambiguity. // Defaults to ``false`` for backwards compatibility. google.protobuf.BoolValue enable_tenant_isolation = 5; + + // Access logs emitted for reverse tunnel lifecycle events. Entries are generated for tunnel setup, + // socket handoff, tunnel close, and post-handoff HTTP/2 keepalive timeout observations. + repeated config.accesslog.v3.AccessLog access_log = 6; } diff --git a/docs/root/_configs/reverse_connection/responder-envoy.yaml b/docs/root/_configs/reverse_connection/responder-envoy.yaml index 544ae5689e2b5..c7d1f948c3a7c 100644 --- a/docs/root/_configs/reverse_connection/responder-envoy.yaml +++ b/docs/root/_configs/reverse_connection/responder-envoy.yaml @@ -10,6 +10,18 @@ bootstrap_extensions: "@type": >- type.googleapis.com/envoy.extensions.bootstrap.reverse_tunnel.upstream_socket_interface.v3.UpstreamReverseConnectionSocketInterface stat_prefix: "upstream_reverse_connection" + access_log: + - name: envoy.access_loggers.stdout + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog + log_format: + text_format_source: + inline_string: >- + event=%DYNAMIC_METADATA(envoy.reverse_tunnel.lifecycle:event)% + node=%FILTER_STATE(envoy.reverse_tunnel.node_id)% + cluster=%FILTER_STATE(envoy.reverse_tunnel.cluster_id)% + tenant=%FILTER_STATE(envoy.reverse_tunnel.tenant_id)% + reason=%CONNECTION_TERMINATION_DETAILS% static_resources: listeners: @@ -93,6 +105,10 @@ static_resources: - name: reverse_connection_cluster connect_timeout: 200s lb_policy: CLUSTER_PROVIDED + filters: + - name: envoy.filters.upstream_network.reverse_tunnel_lifecycle + typed_config: + "@type": type.googleapis.com/google.protobuf.Empty cluster_type: name: envoy.clusters.reverse_connection typed_config: diff --git a/docs/root/configuration/other_features/reverse_tunnel.rst b/docs/root/configuration/other_features/reverse_tunnel.rst index 75a01271d1d42..199072ce08f9c 100644 --- a/docs/root/configuration/other_features/reverse_tunnel.rst +++ b/docs/root/configuration/other_features/reverse_tunnel.rst @@ -215,6 +215,69 @@ concatenates the tenant identifier with the node and cluster identifiers using t in any of the reverse tunnel headers are rejected with ``400`` to prevent ambiguous lookups. The flag defaults to ``false`` to preserve existing behaviour. +Lifecycle access logs +~~~~~~~~~~~~~~~~~~~~~ + +The upstream socket interface can emit access logs for reverse-tunnel lifecycle events directly from +reverse-tunnel-owned code. Configure the ``access_log`` field on +``envoy.bootstrap.reverse_tunnel.upstream_socket_interface`` to log tunnel setup, socket handoff, +tunnel close, and post-handoff HTTP/2 keepalive timeout events: + +.. literalinclude:: /_configs/reverse_connection/responder-envoy.yaml + :language: yaml + :lines: 7-24 + :linenos: + :lineno-start: 7 + :caption: :download:`responder-envoy.yaml ` + +The lifecycle logger emits the following event names: + +**Core lifecycle events:** + +* ``tunnel_setup`` – emitted when a new reverse tunnel connection is accepted and cached. +* ``socket_handoff`` – emitted when a cached idle socket is handed off to an upstream connection pool. +* ``tunnel_closed`` – emitted when the reverse tunnel connection is closed. +* ``http2_keepalive_timeout`` – emitted when the upstream connection closes locally due to an HTTP/2 keepalive (``PING``) timeout after handoff. + +**Idle-phase ping events** (emitted while the socket is idle in the cache): + +* ``idle_ping_sent`` – an ``RPING`` probe was sent to verify the idle connection is alive. +* ``idle_ping_ack`` – the peer acknowledged the ``RPING``. +* ``idle_ping_miss`` – no ``RPING`` acknowledgement was received within the expected window. +* ``idle_ping_timeout`` – the idle connection is being closed because the peer failed to respond to ``RPING`` probes. + +The dynamic metadata namespace is ``envoy.reverse_tunnel.lifecycle``. The emitted fields are +``event``, ``node_id``, ``cluster_id``, ``tenant_id``, ``worker``, ``fd``, ``socket_state``, +and, when relevant, ``handoff_kind`` or ``close_reason``. + +**Socket state values** (the ``socket_state`` field): + +* ``idle`` – the socket is cached and waiting for a data request. +* ``handed_off`` – the socket has been handed off to an upstream connection pool. +* ``in_use`` – the socket is actively being used for upstream traffic. + +**Handoff kind values** (the ``handoff_kind`` field, present only in ``socket_handoff`` events): + +* ``pool_to_upstream`` – the socket was handed off from the idle cache to the upstream connection pool. + +**Close reason values** (the ``close_reason`` field, present only in ``tunnel_closed`` events): + +* ``idle_peer_close`` – the peer closed the connection while it was idle. +* ``idle_read_error`` – a read error occurred on the idle connection. +* ``idle_ping_write_failure`` – writing an ``RPING`` probe to the idle connection failed. +* ``idle_ping_timeout`` – the idle connection was closed because ``RPING`` probes went unanswered. +* ``remote_close`` – the peer closed the connection after handoff. +* ``local_close`` – Envoy closed the connection after handoff. +* ``explicit_close`` – the connection was explicitly closed (e.g., during shutdown). + +The same identifiers are also copied into connection filter state under these keys: + +* ``envoy.reverse_tunnel.node_id`` +* ``envoy.reverse_tunnel.cluster_id`` +* ``envoy.reverse_tunnel.tenant_id`` +* ``envoy.reverse_tunnel.worker`` +* ``envoy.reverse_tunnel.fd`` + .. _config_reverse_tunnel_network_filter: Reverse tunnel network filter @@ -226,9 +289,9 @@ parameters. .. literalinclude:: /_configs/reverse_connection/responder-envoy.yaml :language: yaml - :lines: 17-28 + :lines: 29-41 :linenos: - :lineno-start: 17 + :lineno-start: 29 :caption: :download:`responder-envoy.yaml ` .. _config_reverse_connection_cluster: @@ -258,11 +321,26 @@ automatically constructs tenant-scoped identifiers using the formatted tenant ID will not be routed. This ensures strict tenant isolation and prevents requests from being routed without proper tenant scoping. +To observe post-handoff upstream connection events such as HTTP/2 keepalive timeout, add the reverse +tunnel lifecycle upstream network filter to the reverse connection cluster: + +.. literalinclude:: /_configs/reverse_connection/responder-envoy.yaml + :language: yaml + :lines: 105-111 + :linenos: + :lineno-start: 105 + :caption: :download:`responder-envoy.yaml ` + +This filter copies the reverse-tunnel identifiers into the handed-off upstream connection's filter +state and emits exactly one ``http2_keepalive_timeout`` access-log event when the upstream +connection closes locally with ``http2_ping_timeout``. The subsequent ``tunnel_closed`` record +reuses the same close reason. + .. literalinclude:: /_configs/reverse_connection/responder-envoy.yaml :language: yaml - :lines: 92-112 + :lines: 104-129 :linenos: - :lineno-start: 92 + :lineno-start: 104 :caption: :download:`responder-envoy.yaml ` The reverse connection cluster configuration includes several key fields: @@ -311,9 +389,9 @@ that identifies the target downstream node for each request. .. literalinclude:: /_configs/reverse_connection/responder-envoy.yaml :language: yaml - :lines: 31-88 + :lines: 43-101 :linenos: - :lineno-start: 31 + :lineno-start: 43 :caption: :download:`responder-envoy.yaml ` The example above demonstrates using a :ref:`Lua filter ` to implement flexible diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD index a8f41785df285..b39eb22420014 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD @@ -14,9 +14,13 @@ envoy_cc_extension( "reverse_connection_io_handle.h", "reverse_tunnel_acceptor.h", "reverse_tunnel_acceptor_extension.h", + "reverse_tunnel_lifecycle_info.h", + "reverse_tunnel_upstream_lifecycle.h", ], visibility = ["//visibility:public"], deps = [ + "//envoy/access_log:access_log_interface", + "//envoy/common:time_interface", "//envoy/event:dispatcher_interface", "//envoy/event:timer_interface", "//envoy/extensions/bootstrap/reverse_tunnel:reverse_tunnel_reporter_lib", @@ -24,7 +28,9 @@ envoy_cc_extension( "//envoy/network:socket_interface", "//envoy/registry", "//envoy/server:bootstrap_extension_config_interface", + "//envoy/server:filter_config_interface", "//envoy/stats:stats_interface", + "//envoy/stream_info:stream_info_interface", "//envoy/thread_local:thread_local_interface", "//source/common/config:utility_lib", "//source/common/network:default_socket_interface_lib", @@ -40,14 +46,23 @@ envoy_cc_extension( "reverse_connection_io_handle.cc", "reverse_tunnel_acceptor.cc", "reverse_tunnel_acceptor_extension.cc", + "reverse_tunnel_upstream_lifecycle.cc", ], visibility = ["//visibility:public"], deps = [ ":reverse_tunnel_acceptor_includes", ":upstream_socket_manager_lib", + "//source/common/access_log:access_log_lib", + "//source/common/common:assert_lib", "//source/common/common:logger_lib", + "//source/common/formatter:stream_info_formatter_extension_lib", "//source/common/network:default_socket_interface_lib", + "//source/common/network:socket_lib", "//source/common/protobuf", + "//source/common/router:string_accessor_lib", + "//source/common/stream_info:stream_info_lib", + "//source/common/stream_info:uint64_accessor_lib", + "//source/server:generic_factory_context_lib", ], alwayslink = 1, ) diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc index f5f9b93542089..cf8c578764b8f 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc @@ -1,7 +1,15 @@ #include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h" +#include "source/common/access_log/access_log_impl.h" +#include "source/common/network/socket_impl.h" +#include "source/common/protobuf/protobuf.h" +#include "source/common/router/string_accessor_impl.h" +#include "source/common/stream_info/stream_info_impl.h" +#include "source/common/stream_info/uint64_accessor_impl.h" #include "source/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h" #include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h" +#include "source/server/generic_factory_context.h" namespace Envoy { namespace Extensions { @@ -11,6 +19,50 @@ namespace ReverseConnection { // Static warning flag for reverse tunnel detailed stats activation. static bool reverse_tunnel_detailed_stats_warning_logged = false; +namespace { + +void setStringMetadataField(Protobuf::Struct& metadata, absl::string_view key, + absl::string_view value) { + (*metadata.mutable_fields())[std::string(key)].set_string_value(std::string(value)); +} + +void setNumberMetadataField(Protobuf::Struct& metadata, absl::string_view key, double value) { + (*metadata.mutable_fields())[std::string(key)].set_number_value(value); +} + +void maybeSetStringFilterState(StreamInfo::FilterState& filter_state, absl::string_view key, + absl::string_view value) { + if (value.empty() || filter_state.hasDataWithName(key)) { + return; + } + + filter_state.setData(key, std::make_shared(value), + StreamInfo::FilterState::StateType::ReadOnly, + StreamInfo::FilterState::LifeSpan::Connection); +} + +void maybeSetUint64FilterState(StreamInfo::FilterState& filter_state, absl::string_view key, + uint64_t value) { + if (filter_state.hasDataWithName(key)) { + return; + } + + filter_state.setData(key, std::make_shared(value), + StreamInfo::FilterState::StateType::ReadOnly, + StreamInfo::FilterState::LifeSpan::Connection); +} + +absl::string_view socketStateForEvent(absl::string_view event, + const ReverseTunnelLifecycleInfo& lifecycle) { + if (event == kLifecycleEventSocketHandoff) { + return kLifecycleSocketStateHandedOff; + } + + return lifecycle.handed_off_to_upstream ? kLifecycleSocketStateInUse : kLifecycleSocketStateIdle; +} + +} // namespace + // UpstreamSocketThreadLocal implementation UpstreamSocketThreadLocal::UpstreamSocketThreadLocal(Event::Dispatcher& dispatcher, ReverseTunnelAcceptorExtension* extension) @@ -38,6 +90,135 @@ UpstreamSocketThreadLocal::UpstreamSocketThreadLocal(Event::Dispatcher& dispatch } } +ReverseTunnelAcceptorExtension::~ReverseTunnelAcceptorExtension() { + // Reset the TLS slot before other members (especially access_logs_) are destroyed. + // Without this, the implicit member destruction order destroys access_logs_ before tls_slot_, + // and the UpstreamSocketManager destructor (triggered by tls_slot_ teardown) calls + // emitSyntheticLifecycleLog() which accesses the already-destroyed access_logs_, causing a + // segfault under GCC. + tls_slot_.reset(); +} + +ReverseTunnelAcceptorExtension::ReverseTunnelAcceptorExtension( + ReverseTunnelAcceptor& sock_interface, Server::Configuration::ServerFactoryContext& context, + const envoy::extensions::bootstrap::reverse_tunnel::upstream_socket_interface::v3:: + UpstreamReverseConnectionSocketInterface& config) + : Envoy::Network::SocketInterfaceExtension(sock_interface), context_(context), + socket_interface_(&sock_interface) { + stat_prefix_ = PROTOBUF_GET_STRING_OR_DEFAULT(config, stat_prefix, "reverse_tunnel_acceptor"); + const uint32_t cfg_threshold = PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, ping_failure_threshold, 3); + ping_failure_threshold_ = std::max(1, cfg_threshold); + enable_detailed_stats_ = config.enable_detailed_stats(); + enable_tenant_isolation_ = + config.has_enable_tenant_isolation() ? config.enable_tenant_isolation().value() : false; + + Server::GenericFactoryContextImpl generic_context(context_, context_.messageValidationVisitor()); + for (const auto& access_log_config : config.access_log()) { + access_logs_.push_back( + AccessLog::AccessLogFactory::fromProto(access_log_config, generic_context)); + } + + ENVOY_LOG(debug, + "ReverseTunnelAcceptorExtension: creating upstream reverse connection socket " + "interface with stat_prefix: {}, tenant_isolation: {}, access_logs: {}", + stat_prefix_, enable_tenant_isolation_, access_logs_.size()); + + if (config.has_reporter_config()) { + auto& reporter_factory = + Config::Utility::getAndCheckFactoryByName( + config.reporter_config().name()); + auto reporter_config = Config::Utility::translateAnyToFactoryConfig( + config.reporter_config().typed_config(), context_.messageValidationVisitor(), + reporter_factory); + + reporter_ = reporter_factory.createReporter(context, std::move(reporter_config)); + } + + if (socket_interface_ != nullptr) { + socket_interface_->extension_ = this; + } +} + +void ReverseTunnelAcceptorExtension::emitSyntheticLifecycleLog( + absl::string_view event, const ReverseTunnelLifecycleInfo& lifecycle, TimeSource& time_source, + AccessLog::AccessLogType access_log_type, absl::string_view handoff_kind, + absl::string_view close_reason, const LifecycleLogMetadata& extra_metadata) const { + if (access_logs_.empty()) { + return; + } + + auto connection_info_provider = std::make_shared( + lifecycle.local_address, lifecycle.remote_address); + StreamInfo::StreamInfoImpl stream_info(time_source, connection_info_provider, + StreamInfo::FilterState::LifeSpan::Connection); + + populateLifecycleStreamInfo(stream_info, lifecycle, event, handoff_kind, close_reason, + extra_metadata); + stream_info.onRequestComplete(); + emitLifecycleLog(Formatter::Context(nullptr, nullptr, nullptr, {}, access_log_type), stream_info); +} + +void ReverseTunnelAcceptorExtension::emitConnectionLifecycleLog( + absl::string_view event, StreamInfo::StreamInfo& stream_info, + const ReverseTunnelLifecycleInfo& lifecycle, AccessLog::AccessLogType access_log_type, + absl::string_view handoff_kind, absl::string_view close_reason, + const LifecycleLogMetadata& extra_metadata) const { + if (access_logs_.empty()) { + return; + } + + populateLifecycleStreamInfo(stream_info, lifecycle, event, handoff_kind, close_reason, + extra_metadata); + emitLifecycleLog(Formatter::Context(nullptr, nullptr, nullptr, {}, access_log_type), stream_info); +} + +void ReverseTunnelAcceptorExtension::emitLifecycleLog( + const Formatter::Context& log_context, const StreamInfo::StreamInfo& stream_info) const { + for (const auto& access_log : access_logs_) { + access_log->log(log_context, stream_info); + } +} + +void ReverseTunnelAcceptorExtension::populateLifecycleStreamInfo( + StreamInfo::StreamInfo& stream_info, const ReverseTunnelLifecycleInfo& lifecycle, + absl::string_view event, absl::string_view handoff_kind, absl::string_view close_reason, + const LifecycleLogMetadata& extra_metadata) const { + if (const auto& filter_state = stream_info.filterState(); filter_state != nullptr) { + maybeSetStringFilterState(*filter_state, kFilterStateNodeId, lifecycle.node_id); + maybeSetStringFilterState(*filter_state, kFilterStateClusterId, lifecycle.cluster_id); + maybeSetStringFilterState(*filter_state, kFilterStateTenantId, lifecycle.tenant_id); + maybeSetStringFilterState(*filter_state, kFilterStateWorker, lifecycle.worker); + if (lifecycle.fd >= 0) { + maybeSetUint64FilterState(*filter_state, kFilterStateFd, lifecycle.fd); + } + } + + Protobuf::Struct metadata; + setStringMetadataField(metadata, "event", event); + setStringMetadataField(metadata, "node_id", lifecycle.node_id); + setStringMetadataField(metadata, "cluster_id", lifecycle.cluster_id); + setStringMetadataField(metadata, "tenant_id", lifecycle.tenant_id); + setStringMetadataField(metadata, "worker", lifecycle.worker); + setStringMetadataField(metadata, "socket_state", socketStateForEvent(event, lifecycle)); + if (!handoff_kind.empty()) { + setStringMetadataField(metadata, "handoff_kind", handoff_kind); + } + if (!close_reason.empty()) { + setStringMetadataField(metadata, "close_reason", close_reason); + stream_info.setConnectionTerminationDetails(close_reason); + } + if (lifecycle.fd >= 0) { + setNumberMetadataField(metadata, "fd", lifecycle.fd); + } + for (const auto& [key, value] : extra_metadata) { + if (!value.empty()) { + setStringMetadataField(metadata, key, value); + } + } + + stream_info.setDynamicMetadata(std::string(kAccessLogMetadataNamespace), metadata); +} + // ReverseTunnelAcceptorExtension implementation void ReverseTunnelAcceptorExtension::onServerInitialized(Server::Instance&) { // Initialize the reporter. diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h index 32717efb16030..becd8da190f46 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h @@ -5,7 +5,10 @@ #include #include #include +#include +#include "envoy/access_log/access_log.h" +#include "envoy/common/time.h" #include "envoy/event/dispatcher.h" #include "envoy/event/timer.h" #include "envoy/extensions/bootstrap/reverse_tunnel/reverse_tunnel_reporter.h" @@ -16,14 +19,16 @@ #include "envoy/registry/registry.h" #include "envoy/server/bootstrap_extension_config.h" #include "envoy/stats/scope.h" +#include "envoy/stream_info/stream_info.h" #include "envoy/thread_local/thread_local.h" #include "source/common/config/utility.h" #include "source/common/network/io_socket_handle_impl.h" #include "source/common/network/socket_interface.h" -#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_lifecycle_info.h" #include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" #include "fmt/format.h" namespace Envoy { @@ -34,6 +39,7 @@ namespace ReverseConnection { // Forward declarations class UpstreamSocketManager; class ReverseTunnelAcceptorExtension; +class ReverseTunnelAcceptor; /** * Thread local storage for ReverseTunnelAcceptor. @@ -92,40 +98,9 @@ class ReverseTunnelAcceptorExtension ReverseTunnelAcceptorExtension( ReverseTunnelAcceptor& sock_interface, Server::Configuration::ServerFactoryContext& context, const envoy::extensions::bootstrap::reverse_tunnel::upstream_socket_interface::v3:: - UpstreamReverseConnectionSocketInterface& config) - : Envoy::Network::SocketInterfaceExtension(sock_interface), context_(context), - socket_interface_(&sock_interface) { - stat_prefix_ = PROTOBUF_GET_STRING_OR_DEFAULT(config, stat_prefix, "reverse_tunnel_acceptor"); - // Configure ping miss threshold (minimum 1). - const uint32_t cfg_threshold = - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, ping_failure_threshold, 3); - ping_failure_threshold_ = std::max(1, cfg_threshold); - // Configure detailed stats flag (defaults to false). - enable_detailed_stats_ = config.enable_detailed_stats(); - // Configure tenant isolation flag (defaults to false). - enable_tenant_isolation_ = - config.has_enable_tenant_isolation() ? config.enable_tenant_isolation().value() : false; - ENVOY_LOG(debug, - "ReverseTunnelAcceptorExtension: creating upstream reverse connection " - "socket interface with stat_prefix: {}, tenant_isolation: {}", - stat_prefix_, enable_tenant_isolation_); - // Construct the reporter if enabled from the yaml. - if (config.has_reporter_config()) { - auto& reporter_factory = - Config::Utility::getAndCheckFactoryByName( - config.reporter_config().name()); - auto reporter_config = Config::Utility::translateAnyToFactoryConfig( - config.reporter_config().typed_config(), context_.messageValidationVisitor(), - reporter_factory); - - reporter_ = reporter_factory.createReporter(context, std::move(reporter_config)); - } - // Ensure the socket interface has a reference to this extension early, so stats can be - // recorded even before onServerInitialized(). - if (socket_interface_ != nullptr) { - socket_interface_->extension_ = this; - } - } + UpstreamReverseConnectionSocketInterface& config); + + ~ReverseTunnelAcceptorExtension() override; /** * Called when the server is initialized. @@ -193,6 +168,30 @@ class ReverseTunnelAcceptorExtension */ bool enableTenantIsolation() const { return enable_tenant_isolation_; } + /** + * @return whether lifecycle access logs are configured. + */ + bool hasAccessLogs() const { return !access_logs_.empty(); } + + /** + * Emit a lifecycle access log using a synthetic StreamInfo built from reverse-tunnel metadata. + */ + void emitSyntheticLifecycleLog( + absl::string_view event, const ReverseTunnelLifecycleInfo& lifecycle, TimeSource& time_source, + AccessLog::AccessLogType access_log_type = AccessLog::AccessLogType::NotSet, + absl::string_view handoff_kind = {}, absl::string_view close_reason = {}, + const LifecycleLogMetadata& extra_metadata = LifecycleLogMetadata{}) const; + + /** + * Emit a lifecycle access log using an existing connection-backed StreamInfo. + */ + void emitConnectionLifecycleLog( + absl::string_view event, StreamInfo::StreamInfo& stream_info, + const ReverseTunnelLifecycleInfo& lifecycle, + AccessLog::AccessLogType access_log_type = AccessLog::AccessLogType::NotSet, + absl::string_view handoff_kind = {}, absl::string_view close_reason = {}, + const LifecycleLogMetadata& extra_metadata = LifecycleLogMetadata{}) const; + /** * Forward a connection event to the configured reporter. * If no reporter is present, the call is ignored. @@ -228,7 +227,21 @@ class ReverseTunnelAcceptorExtension tls_slot_ = std::move(slot); } + /** + * Test-only method to replace lifecycle access loggers. + */ + void setTestOnlyAccessLogs(AccessLog::InstanceSharedPtrVector access_logs) { + access_logs_ = std::move(access_logs); + } + private: + void emitLifecycleLog(const Formatter::Context& log_context, + const StreamInfo::StreamInfo& stream_info) const; + void populateLifecycleStreamInfo(StreamInfo::StreamInfo& stream_info, + const ReverseTunnelLifecycleInfo& lifecycle, + absl::string_view event, absl::string_view handoff_kind, + absl::string_view close_reason, + const LifecycleLogMetadata& extra_metadata) const; Server::Configuration::ServerFactoryContext& context_; // Thread-local slot for storing the socket manager per worker thread. std::unique_ptr> tls_slot_; @@ -237,6 +250,7 @@ class ReverseTunnelAcceptorExtension uint32_t ping_failure_threshold_{3}; bool enable_detailed_stats_{false}; bool enable_tenant_isolation_{false}; + AccessLog::InstanceSharedPtrVector access_logs_; ReverseTunnelReporterPtr reporter_{nullptr}; /** diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_lifecycle_info.h b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_lifecycle_info.h new file mode 100644 index 0000000000000..b74514897a192 --- /dev/null +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_lifecycle_info.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include + +#include "envoy/network/socket.h" + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +using LifecycleLogMetadata = std::vector>; + +struct ReverseTunnelLifecycleInfo { + std::string node_id; + std::string cluster_id; + std::string tenant_id; + Network::Address::InstanceConstSharedPtr local_address; + Network::Address::InstanceConstSharedPtr remote_address; + std::string worker; + int fd{-1}; + bool handed_off_to_upstream{false}; + bool upstream_lifecycle_filter_attached{false}; + bool socket_dead_notified{false}; + bool close_log_emitted{false}; + std::string close_reason; +}; + +inline constexpr absl::string_view kAccessLogMetadataNamespace = "envoy.reverse_tunnel.lifecycle"; + +inline constexpr absl::string_view kFilterStateNodeId = "envoy.reverse_tunnel.node_id"; +inline constexpr absl::string_view kFilterStateClusterId = "envoy.reverse_tunnel.cluster_id"; +inline constexpr absl::string_view kFilterStateTenantId = "envoy.reverse_tunnel.tenant_id"; +inline constexpr absl::string_view kFilterStateWorker = "envoy.reverse_tunnel.worker"; +inline constexpr absl::string_view kFilterStateFd = "envoy.reverse_tunnel.fd"; + +inline constexpr absl::string_view kLifecycleEventTunnelSetup = "tunnel_setup"; +inline constexpr absl::string_view kLifecycleEventIdlePingSent = "idle_ping_sent"; +inline constexpr absl::string_view kLifecycleEventIdlePingAck = "idle_ping_ack"; +inline constexpr absl::string_view kLifecycleEventIdlePingMiss = "idle_ping_miss"; +inline constexpr absl::string_view kLifecycleEventIdlePingTimeout = "idle_ping_timeout"; +inline constexpr absl::string_view kLifecycleEventSocketHandoff = "socket_handoff"; +inline constexpr absl::string_view kLifecycleEventTunnelClosed = "tunnel_closed"; +inline constexpr absl::string_view kLifecycleEventHttp2KeepaliveTimeout = "http2_keepalive_timeout"; + +inline constexpr absl::string_view kLifecycleSocketStateIdle = "idle"; +inline constexpr absl::string_view kLifecycleSocketStateHandedOff = "handed_off"; +inline constexpr absl::string_view kLifecycleSocketStateInUse = "in_use"; + +inline constexpr absl::string_view kLifecycleHandoffKindPoolToUpstream = "pool_to_upstream"; + +inline constexpr absl::string_view kLifecycleCloseReasonIdlePeerClose = "idle_peer_close"; +inline constexpr absl::string_view kLifecycleCloseReasonIdleReadError = "idle_read_error"; +inline constexpr absl::string_view kLifecycleCloseReasonIdlePingWriteFailure = + "idle_ping_write_failure"; +inline constexpr absl::string_view kLifecycleCloseReasonIdlePingTimeout = "idle_ping_timeout"; +inline constexpr absl::string_view kLifecycleCloseReasonRemoteClose = "remote_close"; +inline constexpr absl::string_view kLifecycleCloseReasonLocalClose = "local_close"; +inline constexpr absl::string_view kLifecycleCloseReasonExplicitClose = "explicit_close"; + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.cc b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.cc new file mode 100644 index 0000000000000..671d4b7ca17e9 --- /dev/null +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.cc @@ -0,0 +1,173 @@ +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.h" + +#include +#include + +#include "envoy/registry/registry.h" +#include "envoy/stream_info/stream_info.h" + +#include "source/common/common/assert.h" +#include "source/common/network/socket_interface.h" +#include "source/common/protobuf/protobuf.h" +#include "source/common/router/string_accessor_impl.h" +#include "source/common/stream_info/uint64_accessor_impl.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +namespace { + +UpstreamSocketManager* getThreadLocalSocketManager() { + auto* upstream_interface = + Network::socketInterface("envoy.bootstrap.reverse_tunnel.upstream_socket_interface"); + if (upstream_interface == nullptr) { + return nullptr; + } + + auto* acceptor = const_cast( + dynamic_cast(upstream_interface)); + if (acceptor == nullptr) { + return nullptr; + } + + auto* tls_registry = acceptor->getLocalRegistry(); + return (tls_registry != nullptr) ? tls_registry->socketManager() : nullptr; +} + +void maybeSetStringFilterState(StreamInfo::FilterState& filter_state, absl::string_view key, + absl::string_view value) { + if (value.empty() || filter_state.hasDataWithName(key)) { + return; + } + + filter_state.setData(key, std::make_shared(value), + StreamInfo::FilterState::StateType::ReadOnly, + StreamInfo::FilterState::LifeSpan::Connection); +} + +void maybeSetUint64FilterState(StreamInfo::FilterState& filter_state, absl::string_view key, + uint64_t value) { + if (filter_state.hasDataWithName(key)) { + return; + } + + filter_state.setData(key, std::make_shared(value), + StreamInfo::FilterState::StateType::ReadOnly, + StreamInfo::FilterState::LifeSpan::Connection); +} + +} // namespace + +void ReverseTunnelUpstreamLifecycleFilter::initializeReadFilterCallbacks( + Network::ReadFilterCallbacks& callbacks) { + read_callbacks_ = &callbacks; +} + +Network::FilterStatus ReverseTunnelUpstreamLifecycleFilter::onNewConnection() { + ASSERT(read_callbacks_ != nullptr); + read_callbacks_->connection().addConnectionCallbacks(*this); + + const auto& socket = read_callbacks_->connection().getSocket(); + if (!socket) { + return Network::FilterStatus::Continue; + } + + // Safe to use fdDoNotUse() here: the lifecycle filter is a connection callback, + // guaranteeing the socket is alive during onNewConnection() and onEvent(). The + // UpstreamReverseConnectionIOHandle calls markSocketDead() before releasing the + // socket, preventing FD recycling before lifecycle tracking cleanup. All reverse + // tunnel FD-indexed maps follow this same ownership pattern. + fd_ = socket->ioHandle().fdDoNotUse(); + auto* socket_manager = getThreadLocalSocketManager(); + if (socket_manager == nullptr) { + return Network::FilterStatus::Continue; + } + + const auto* lifecycle = socket_manager->getLifecycleInfo(fd_); + if (lifecycle == nullptr) { + return Network::FilterStatus::Continue; + } + + lifecycle_ = *lifecycle; + socket_manager->markUpstreamLifecycleFilterAttached(fd_); + if (const auto& filter_state = read_callbacks_->connection().streamInfo().filterState(); + filter_state != nullptr) { + maybeSetStringFilterState(*filter_state, kFilterStateNodeId, lifecycle_->node_id); + maybeSetStringFilterState(*filter_state, kFilterStateClusterId, lifecycle_->cluster_id); + maybeSetStringFilterState(*filter_state, kFilterStateTenantId, lifecycle_->tenant_id); + maybeSetStringFilterState(*filter_state, kFilterStateWorker, lifecycle_->worker); + if (lifecycle_->fd >= 0) { + maybeSetUint64FilterState(*filter_state, kFilterStateFd, lifecycle_->fd); + } + } + + return Network::FilterStatus::Continue; +} + +void ReverseTunnelUpstreamLifecycleFilter::onEvent(Network::ConnectionEvent event) { + if (!lifecycle_.has_value()) { + return; + } + + auto* socket_manager = getThreadLocalSocketManager(); + if (socket_manager == nullptr) { + return; + } + + if (event == Network::ConnectionEvent::RemoteClose) { + lifecycle_->close_reason = std::string(kLifecycleCloseReasonRemoteClose); + socket_manager->setCloseReason(fd_, lifecycle_->close_reason); + socket_manager->maybeEmitDeferredCloseLog(fd_, lifecycle_->close_reason); + return; + } + + if (event != Network::ConnectionEvent::LocalClose) { + return; + } + + absl::string_view close_reason = read_callbacks_->connection().localCloseReason(); + if (close_reason.empty()) { + close_reason = kLifecycleCloseReasonLocalClose; + } + + lifecycle_->close_reason = std::string(close_reason); + socket_manager->setCloseReason(fd_, close_reason); + + if (close_reason != StreamInfo::LocalCloseReasons::get().Http2PingTimeout || + keepalive_timeout_logged_) { + socket_manager->maybeEmitDeferredCloseLog(fd_, close_reason); + return; + } + + keepalive_timeout_logged_ = true; + if (auto* extension = socket_manager->getUpstreamExtension()) { + extension->emitConnectionLifecycleLog(kLifecycleEventHttp2KeepaliveTimeout, + read_callbacks_->connection().streamInfo(), *lifecycle_, + AccessLog::AccessLogType::UpstreamEnd, {}, close_reason); + } + socket_manager->maybeEmitDeferredCloseLog(fd_, close_reason); +} + +Network::FilterFactoryCb ReverseTunnelUpstreamLifecycleConfigFactory::createFilterFactoryFromProto( + const Protobuf::Message&, Server::Configuration::UpstreamFactoryContext&) { + return [](Network::FilterManager& filter_manager) { + filter_manager.addReadFilter(std::make_shared()); + }; +} + +ProtobufTypes::MessagePtr ReverseTunnelUpstreamLifecycleConfigFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +REGISTER_FACTORY(ReverseTunnelUpstreamLifecycleConfigFactory, + Server::Configuration::NamedUpstreamNetworkFilterConfigFactory); + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.h b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.h new file mode 100644 index 0000000000000..2b487b60bf5d6 --- /dev/null +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.h @@ -0,0 +1,56 @@ +#pragma once + +#include "envoy/network/filter.h" +#include "envoy/server/filter_config.h" + +#include "source/common/common/logger.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_lifecycle_info.h" + +#include "absl/types/optional.h" + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +class ReverseTunnelUpstreamLifecycleFilter : public Network::ReadFilter, + public Network::ConnectionCallbacks, + public Logger::Loggable { +public: + Network::FilterStatus onData(Buffer::Instance&, bool) override { + return Network::FilterStatus::Continue; + } + Network::FilterStatus onNewConnection() override; + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override; + + void onEvent(Network::ConnectionEvent event) override; + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + +private: + Network::ReadFilterCallbacks* read_callbacks_{nullptr}; + absl::optional lifecycle_; + int fd_{-1}; + bool keepalive_timeout_logged_{false}; +}; + +class ReverseTunnelUpstreamLifecycleConfigFactory + : public Server::Configuration::NamedUpstreamNetworkFilterConfigFactory { +public: + Network::FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message&, + Server::Configuration::UpstreamFactoryContext&) override; + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + std::string name() const override { + return "envoy.filters.upstream_network.reverse_tunnel_lifecycle"; + } + bool isTerminalFilterByProto(const Protobuf::Message&, + Server::Configuration::ServerFactoryContext&) override { + return false; + } +}; + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.cc b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.cc index f34efbaa73c5c..32d09bba1fb5f 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.cc +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.cc @@ -18,6 +18,25 @@ namespace ReverseConnection { constexpr absl::string_view kMainThreadDispatcherName = "main_thread"; +namespace { + +std::string maybeBuildTenantScopedIdentifier(bool tenant_isolation_enabled, + absl::string_view tenant_id, + absl::string_view identifier) { + if (tenant_isolation_enabled && !tenant_id.empty()) { + return ReverseConnectionUtility::buildTenantScopedIdentifier(tenant_id, identifier); + } + + return std::string(identifier); +} + +AccessLog::AccessLogType closeAccessLogType(const ReverseTunnelLifecycleInfo& lifecycle) { + return lifecycle.handed_off_to_upstream ? AccessLog::AccessLogType::UpstreamEnd + : AccessLog::AccessLogType::TcpConnectionEnd; +} + +} // namespace + std::vector UpstreamSocketManager::socket_managers_{}; absl::Mutex UpstreamSocketManager::socket_manager_lock{}; @@ -84,28 +103,36 @@ UpstreamSocketManager::pickLeastLoadedSocketManager(const std::string& node_id, void UpstreamSocketManager::handoffSocketToWorker(const std::string& node_id, const std::string& cluster_id, Network::ConnectionSocketPtr socket, - const std::chrono::seconds& ping_interval) { - dispatcher_.post( - [this, node_id, cluster_id, ping_interval, socket = std::move(socket)]() mutable -> void { - this->addConnectionSocket(node_id, cluster_id, std::move(socket), ping_interval, - true /* rebalanced */); - }); + const std::chrono::seconds& ping_interval, + absl::string_view tenant_id) { + dispatcher_.post([this, node_id, cluster_id, ping_interval, tenant_id = std::string(tenant_id), + socket = std::move(socket)]() mutable -> void { + this->addConnectionSocket(node_id, cluster_id, std::move(socket), ping_interval, + true /* rebalanced */, tenant_id); + }); } void UpstreamSocketManager::addConnectionSocket(const std::string& node_id, const std::string& cluster_id, Network::ConnectionSocketPtr socket, const std::chrono::seconds& ping_interval, - bool rebalanced) { + bool rebalanced, absl::string_view tenant_id) { + const std::string scoped_node_id = + maybeBuildTenantScopedIdentifier(tenant_isolation_enabled_, tenant_id, node_id); + const std::string scoped_cluster_id = + maybeBuildTenantScopedIdentifier(tenant_isolation_enabled_, tenant_id, cluster_id); + // If not already rebalanced, check if we should move this socket to a different worker thread. if (!rebalanced) { - UpstreamSocketManager& target_manager = pickLeastLoadedSocketManager(node_id, cluster_id); + UpstreamSocketManager& target_manager = + pickLeastLoadedSocketManager(scoped_node_id, scoped_cluster_id); if (&target_manager != this) { ENVOY_LOG(debug, "reverse_tunnel: Rebalancing socket to a different worker thread for node: " "{} cluster: {}", node_id, cluster_id); - target_manager.handoffSocketToWorker(node_id, cluster_id, std::move(socket), ping_interval); + target_manager.handoffSocketToWorker(node_id, cluster_id, std::move(socket), ping_interval, + tenant_id); return; } } @@ -128,29 +155,46 @@ void UpstreamSocketManager::addConnectionSocket(const std::string& node_id, node_id, cluster_id); // Store node -> cluster mapping. - ENVOY_LOG(trace, "reverse_tunnel: adding mapping node {} -> cluster {}.", node_id, cluster_id); - if (node_to_cluster_map_.find(node_id) == node_to_cluster_map_.end()) { - node_to_cluster_map_[node_id] = cluster_id; - cluster_to_node_info_map_[cluster_id].nodes.push_back(node_id); - } - - fd_to_node_map_[fd] = node_id; - fd_to_cluster_map_[fd] = cluster_id; - node_to_active_fd_count_[node_id]++; + ENVOY_LOG(trace, "reverse_tunnel: adding mapping node {} -> cluster {}.", scoped_node_id, + scoped_cluster_id); + if (node_to_cluster_map_.find(scoped_node_id) == node_to_cluster_map_.end()) { + node_to_cluster_map_[scoped_node_id] = scoped_cluster_id; + cluster_to_node_info_map_[scoped_cluster_id].nodes.push_back(scoped_node_id); + } + + fd_to_node_map_[fd] = scoped_node_id; + fd_to_cluster_map_[fd] = scoped_cluster_id; + fd_to_lifecycle_info_[fd] = + ReverseTunnelLifecycleInfo{node_id, + cluster_id, + std::string(tenant_id), + socket->connectionInfoProvider().localAddress(), + socket->connectionInfoProvider().remoteAddress(), + dispatcher_.name(), + fd, + false, + false, + false, + false, + ""}; + node_to_active_fd_count_[scoped_node_id]++; // Create per-connection timeout timer for ping responses. fd_to_timer_map_[fd] = dispatcher_.createTimer([this, fd]() { onPingTimeout(fd); }); - accepted_reverse_connections_[node_id].push_back(std::move(socket)); - fd_to_socket_it_map_[fd] = std::prev(accepted_reverse_connections_[node_id].end()); - Network::ConnectionSocketPtr& socket_ref = accepted_reverse_connections_[node_id].back(); + accepted_reverse_connections_[scoped_node_id].push_back(std::move(socket)); + fd_to_socket_it_map_[fd] = std::prev(accepted_reverse_connections_[scoped_node_id].end()); + Network::ConnectionSocketPtr& socket_ref = accepted_reverse_connections_[scoped_node_id].back(); // Update stats registry. if (auto extension = getUpstreamExtension()) { - extension->updateConnectionStats(node_id, cluster_id, true /* increment */, + extension->updateConnectionStats(scoped_node_id, scoped_cluster_id, true /* increment */, tenant_isolation_enabled_); - ENVOY_LOG(debug, "reverse_tunnel: updated stats registry for node '{}' cluster '{}'.", node_id, - cluster_id); + extension->emitSyntheticLifecycleLog(kLifecycleEventTunnelSetup, fd_to_lifecycle_info_[fd], + dispatcher_.timeSource(), + AccessLog::AccessLogType::TcpUpstreamConnected); + ENVOY_LOG(debug, "reverse_tunnel: updated stats registry for node '{}' cluster '{}'.", + scoped_node_id, scoped_cluster_id); } // onPingResponse() expects a ping reply on the socket. @@ -175,7 +219,7 @@ void UpstreamSocketManager::addConnectionSocket(const std::string& node_id, std::chrono::milliseconds(pingIntervalWithJitterMs())); ENVOY_LOG(debug, "reverse_tunnel: added socket to maps. node: {} connection key: {} fd: {}.", - node_id, connectionKey, fd); + scoped_node_id, connectionKey, fd); } Network::ConnectionSocketPtr @@ -220,8 +264,19 @@ UpstreamSocketManager::getConnectionSocket(const std::string& node_id) { fd_to_event_map_.erase(fd); fd_to_timer_map_.erase(fd); fd_to_ping_send_timer_map_.erase(fd); + fd_to_miss_count_.erase(fd); fd_to_socket_it_map_.erase(fd); + auto lifecycle_it = fd_to_lifecycle_info_.find(fd); + if (lifecycle_it != fd_to_lifecycle_info_.end()) { + lifecycle_it->second.handed_off_to_upstream = true; + if (auto extension = getUpstreamExtension()) { + extension->emitSyntheticLifecycleLog( + kLifecycleEventSocketHandoff, lifecycle_it->second, dispatcher_.timeSource(), + AccessLog::AccessLogType::UpstreamPoolReady, kLifecycleHandoffKindPoolToUpstream); + } + } + return socket; } @@ -249,6 +304,57 @@ std::string UpstreamSocketManager::getNodeWithSocket(const std::string& key) { return key; } +const ReverseTunnelLifecycleInfo* UpstreamSocketManager::getLifecycleInfo(int fd) const { + auto it = fd_to_lifecycle_info_.find(fd); + return it != fd_to_lifecycle_info_.end() ? &it->second : nullptr; +} + +void UpstreamSocketManager::setCloseReason(int fd, absl::string_view close_reason) { + auto it = fd_to_lifecycle_info_.find(fd); + if (it != fd_to_lifecycle_info_.end()) { + it->second.close_reason = std::string(close_reason); + } +} + +void UpstreamSocketManager::markUpstreamLifecycleFilterAttached(int fd) { + auto it = fd_to_lifecycle_info_.find(fd); + if (it != fd_to_lifecycle_info_.end()) { + it->second.upstream_lifecycle_filter_attached = true; + } +} + +void UpstreamSocketManager::maybeEmitDeferredCloseLog(int fd, absl::string_view close_reason) { + auto lifecycle_it = fd_to_lifecycle_info_.find(fd); + if (lifecycle_it == fd_to_lifecycle_info_.end()) { + return; + } + + auto& lifecycle = lifecycle_it->second; + if (!close_reason.empty()) { + lifecycle.close_reason = std::string(close_reason); + } + + if (!lifecycle.socket_dead_notified) { + return; + } + + if (!lifecycle.close_log_emitted) { + if (lifecycle.close_reason.empty()) { + lifecycle.close_reason = std::string(kLifecycleCloseReasonExplicitClose); + } + if (auto* extension = getUpstreamExtension()) { + extension->emitSyntheticLifecycleLog(kLifecycleEventTunnelClosed, lifecycle, + dispatcher_.timeSource(), closeAccessLogType(lifecycle), + {}, lifecycle.close_reason); + } + lifecycle.close_log_emitted = true; + } + + if (lifecycle.socket_dead_notified) { + fd_to_lifecycle_info_.erase(lifecycle_it); + } +} + bool UpstreamSocketManager::hasAnySocketsForNode(const std::string& node_id) { auto it = node_to_active_fd_count_.find(node_id); return it != node_to_active_fd_count_.end() && it->second > 0; @@ -264,6 +370,17 @@ void UpstreamSocketManager::markSocketDead(const int fd) { } const std::string node_id = node_it->second; + ReverseTunnelLifecycleInfo lifecycle; + auto lifecycle_it = fd_to_lifecycle_info_.find(fd); + if (lifecycle_it != fd_to_lifecycle_info_.end()) { + lifecycle_it->second.socket_dead_notified = true; + lifecycle = lifecycle_it->second; + } else { + lifecycle.node_id = node_id; + lifecycle.worker = dispatcher_.name(); + lifecycle.fd = fd; + } + // Get cluster_id from fd_to_cluster_map_. We use the fd_to_cluster_map_ to get the cluster_id // and not the cluster_to_node_info_map_ because the node might have changed clusters before the // socket is marked dead, but the FD will always be tied to the same cluster in @@ -282,10 +399,14 @@ void UpstreamSocketManager::markSocketDead(const int fd) { } ENVOY_LOG(debug, "reverse_tunnel: found node '{}' cluster '{}' for fd: {}", node_id, cluster_id, fd); + if (lifecycle.cluster_id.empty()) { + lifecycle.cluster_id = cluster_id; + } // Remove FD from tracking maps before checking remaining sockets. fd_to_node_map_.erase(fd); fd_to_cluster_map_.erase(fd); + fd_to_miss_count_.erase(fd); // Decrement the active FD counter for the node. auto count_it = node_to_active_fd_count_.find(node_id); @@ -316,12 +437,28 @@ void UpstreamSocketManager::markSocketDead(const int fd) { node_id, cluster_id, fd); } + const bool defer_close_log = lifecycle.handed_off_to_upstream && + lifecycle.upstream_lifecycle_filter_attached && + !lifecycle.close_log_emitted && lifecycle.close_reason.empty(); + // Update Envoy's stats system. if (auto extension = getUpstreamExtension()) { extension->updateConnectionStats(node_id, cluster_id, false /* decrement */, tenant_isolation_enabled_); // Report the disconnection to the extension for further action. extension->reportDisconnection(node_id, cluster_id); + if (!defer_close_log && !lifecycle.close_log_emitted) { + if (lifecycle.close_reason.empty()) { + lifecycle.close_reason = std::string(kLifecycleCloseReasonExplicitClose); + } + extension->emitSyntheticLifecycleLog(kLifecycleEventTunnelClosed, lifecycle, + dispatcher_.timeSource(), closeAccessLogType(lifecycle), + {}, lifecycle.close_reason); + lifecycle.close_log_emitted = true; + if (lifecycle_it != fd_to_lifecycle_info_.end()) { + lifecycle_it->second.close_log_emitted = true; + } + } ENVOY_LOG(trace, "reverse_tunnel: decremented stats registry for node '{}' cluster '{}'.", node_id, cluster_id); @@ -337,6 +474,11 @@ void UpstreamSocketManager::markSocketDead(const int fd) { ENVOY_LOG(trace, "reverse_tunnel: node '{}' still has remaining sockets, keeping in maps.", node_id); } + + if (lifecycle_it != fd_to_lifecycle_info_.end() && + (!defer_close_log || lifecycle_it->second.close_log_emitted)) { + fd_to_lifecycle_info_.erase(lifecycle_it); + } } void UpstreamSocketManager::cleanStaleNodeEntry(const std::string& node_id) { @@ -388,6 +530,7 @@ void UpstreamSocketManager::onPingResponse(Network::IoHandle& io_handle) { if (!result.ok()) { ENVOY_LOG(debug, "reverse_tunnel: Read error on FD: {}: error - {}", fd, result.err_->getErrorDetails()); + setCloseReason(fd, kLifecycleCloseReasonIdleReadError); markSocketDead(fd); return; } @@ -396,6 +539,7 @@ void UpstreamSocketManager::onPingResponse(Network::IoHandle& io_handle) { // peer in a graceful manner, unlike a connection refused, or a reset. if (result.return_value_ == 0) { ENVOY_LOG(debug, "reverse_tunnel: FD: {}: reverse connection closed", fd); + setCloseReason(fd, kLifecycleCloseReasonIdlePeerClose); markSocketDead(fd); return; } @@ -419,6 +563,15 @@ void UpstreamSocketManager::onPingResponse(Network::IoHandle& io_handle) { // Reset miss counter on success. fd_to_miss_count_.erase(fd); + if (auto* ext = getUpstreamExtension()) { + auto lifecycle_it = fd_to_lifecycle_info_.find(fd); + if (lifecycle_it != fd_to_lifecycle_info_.end()) { + ext->emitSyntheticLifecycleLog(kLifecycleEventIdlePingAck, lifecycle_it->second, + dispatcher_.timeSource(), + AccessLog::AccessLogType::TcpUpstreamConnected); + } + } + // Re-arm the per-connection send timer with jitter. rearmPingSendTimer(fd); } @@ -454,11 +607,21 @@ void UpstreamSocketManager::sendPingForConnection(int fd) { result.return_value_, result.err_->getErrorDetails()); if (result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { ENVOY_LOG(error, "reverse_tunnel: node:{} FD:{}: failed to send ping", node_id, fd); + setCloseReason(fd, kLifecycleCloseReasonIdlePingWriteFailure); markSocketDead(fd); return; } } } + + if (auto* ext = getUpstreamExtension()) { + auto lifecycle_it = fd_to_lifecycle_info_.find(fd); + if (lifecycle_it != fd_to_lifecycle_info_.end()) { + ext->emitSyntheticLifecycleLog(kLifecycleEventIdlePingSent, lifecycle_it->second, + dispatcher_.timeSource(), + AccessLog::AccessLogType::TcpUpstreamConnected); + } + } } void UpstreamSocketManager::onPingTimeout(const int fd) { @@ -469,10 +632,29 @@ void UpstreamSocketManager::onPingTimeout(const int fd) { if (misses >= miss_threshold_) { ENVOY_LOG(debug, "reverse_tunnel: fd {} exceeded miss threshold {}; marking dead.", fd, miss_threshold_); + if (auto* ext = getUpstreamExtension()) { + auto lifecycle_it = fd_to_lifecycle_info_.find(fd); + if (lifecycle_it != fd_to_lifecycle_info_.end()) { + ext->emitSyntheticLifecycleLog(kLifecycleEventIdlePingTimeout, lifecycle_it->second, + dispatcher_.timeSource(), + AccessLog::AccessLogType::TcpUpstreamConnected, {}, {}, + {{"miss_count", std::to_string(misses)}}); + } + } fd_to_miss_count_.erase(fd); + setCloseReason(fd, kLifecycleCloseReasonIdlePingTimeout); markSocketDead(fd); } else { // Below threshold: re-arm send timer for the next ping cycle. + if (auto* ext = getUpstreamExtension()) { + auto lifecycle_it = fd_to_lifecycle_info_.find(fd); + if (lifecycle_it != fd_to_lifecycle_info_.end()) { + ext->emitSyntheticLifecycleLog(kLifecycleEventIdlePingMiss, lifecycle_it->second, + dispatcher_.timeSource(), + AccessLog::AccessLogType::TcpUpstreamConnected, {}, {}, + {{"miss_count", std::to_string(misses)}}); + } + } rearmPingSendTimer(fd); } } @@ -516,6 +698,27 @@ UpstreamSocketManager::~UpstreamSocketManager() { } fd_to_ping_send_timer_map_.clear(); + // Emit any deferred close logs before tearing down. If a lifecycle filter never reported + // a close reason (e.g., connection destroyed without firing callbacks), we emit the + // tunnel_closed log here with a fallback reason to avoid permanently losing the entry. + if (extension_ != nullptr) { + for (auto& [fd, lifecycle] : fd_to_lifecycle_info_) { + if (!lifecycle.close_log_emitted) { + if (lifecycle.close_reason.empty()) { + lifecycle.close_reason = std::string(kLifecycleCloseReasonExplicitClose); + } + extension_->emitSyntheticLifecycleLog( + kLifecycleEventTunnelClosed, lifecycle, dispatcher_.timeSource(), + closeAccessLogType(lifecycle), {}, lifecycle.close_reason); + lifecycle.close_log_emitted = true; + } + } + } + + // Null out extension before cleanup to avoid calling into a potentially-destroyed + // ReverseTunnelAcceptorExtension during TLS teardown. + extension_ = nullptr; + // Now mark all sockets as dead. std::vector fds_to_cleanup; for (const auto& [fd, node_id] : fd_to_node_map_) { @@ -530,6 +733,7 @@ UpstreamSocketManager::~UpstreamSocketManager() { // Clear any remaining fd mappings. fd_to_node_map_.clear(); fd_to_cluster_map_.clear(); + fd_to_lifecycle_info_.clear(); fd_to_socket_it_map_.clear(); // Remove this instance from the global socket managers list. diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h index f4923ddc28393..d9c675684fa6c 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h @@ -14,7 +14,9 @@ #include "source/common/common/logger.h" #include "source/common/common/random_generator.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_lifecycle_info.h" +#include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" namespace Envoy { @@ -50,7 +52,8 @@ class UpstreamSocketManager : public ThreadLocal::ThreadLocalObject, */ void addConnectionSocket(const std::string& node_id, const std::string& cluster_id, Network::ConnectionSocketPtr socket, - const std::chrono::seconds& ping_interval, bool rebalanced = true); + const std::chrono::seconds& ping_interval, bool rebalanced = true, + absl::string_view tenant_id = {}); /** * Hand off a socket to this socket manager's dispatcher. @@ -62,7 +65,8 @@ class UpstreamSocketManager : public ThreadLocal::ThreadLocalObject, */ void handoffSocketToWorker(const std::string& node_id, const std::string& cluster_id, Network::ConnectionSocketPtr socket, - const std::chrono::seconds& ping_interval); + const std::chrono::seconds& ping_interval, + absl::string_view tenant_id = {}); /** * Get an available reverse connection socket. @@ -77,6 +81,28 @@ class UpstreamSocketManager : public ThreadLocal::ThreadLocalObject, */ void markSocketDead(const int fd); + /** + * @return lifecycle metadata for a tracked reverse-tunnel socket, or nullptr if none exists. + */ + const ReverseTunnelLifecycleInfo* getLifecycleInfo(int fd) const; + + /** + * Update the close reason attached to a tracked reverse-tunnel socket. + */ + void setCloseReason(int fd, absl::string_view close_reason); + + /** + * Mark that a handed-off upstream connection has the lifecycle filter attached and can emit its + * own close log once the real close reason is known. + */ + void markUpstreamLifecycleFilterAttached(int fd); + + /** + * Emit the deferred close log for a handed-off socket once the connection filter has learned the + * final close reason. + */ + void maybeEmitDeferredCloseLog(int fd, absl::string_view close_reason); + /** * Send a ping keepalive for a single reverse connection. * @param fd the file descriptor of the connection to ping. @@ -175,6 +201,9 @@ class UpstreamSocketManager : public ThreadLocal::ThreadLocalObject, // from a node and is removed when the socket dies. absl::flat_hash_map fd_to_cluster_map_; + // Original reverse-tunnel identity and addressing metadata keyed by file descriptor. + absl::flat_hash_map fd_to_lifecycle_info_; + // Map of node ID to cluster, for all nodes that have a reverse tunnel socket. absl::flat_hash_map node_to_cluster_map_; diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index efdb8e357721c..8d92b810d835d 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -71,6 +71,7 @@ EXTENSIONS = { "envoy.bootstrap.reverse_tunnel.downstream_socket_interface": "//source/extensions/bootstrap/reverse_tunnel/downstream_socket_interface:reverse_tunnel_initiator_lib", "envoy.bootstrap.reverse_tunnel.upstream_socket_interface": "//source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface:reverse_tunnel_acceptor_lib", + "envoy.filters.upstream_network.reverse_tunnel_lifecycle": "//source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface:reverse_tunnel_acceptor_lib", # # Health checkers diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index d257439d04399..88f88a8aa308f 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1059,6 +1059,13 @@ envoy.filters.udp.session.dynamic_forward_proxy: status: alpha type_urls: - envoy.extensions.filters.udp.udp_proxy.session.dynamic_forward_proxy.v3.FilterConfig +envoy.filters.upstream_network.reverse_tunnel_lifecycle: + categories: + - envoy.filters.upstream_network + security_posture: unknown + status: wip + type_urls: + - google.protobuf.Empty envoy.formatter.cel: categories: - envoy.formatter diff --git a/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc b/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc index 10370d9ba204f..9bfb9bc811fa0 100644 --- a/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc +++ b/source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.cc @@ -567,7 +567,9 @@ void ReverseTunnelFilter::processAcceptedConnection(absl::string_view node_id, const std::chrono::seconds ping_seconds = std::chrono::duration_cast(config_->pingInterval()); - // Register the wrapped socket for reuse under the provided identifiers. + // Register the wrapped socket for reuse under the original identifiers. The socket manager + // derives any tenant-scoped internal keys itself so lifecycle logging can retain the original + // node, cluster, and tenant fields. // Note: The socket manager is expected to be thread-safe. // Get tenant isolation setting from socket manager (configured at bootstrap level). const bool tenant_isolation_enabled = socket_manager->tenantIsolationEnabled(); @@ -583,8 +585,9 @@ void ReverseTunnelFilter::processAcceptedConnection(absl::string_view node_id, : std::string(cluster_id); ENVOY_CONN_LOG(trace, "reverse_tunnel: registering wrapped socket for reuse", connection); - socket_manager->addConnectionSocket(socket_node_id, socket_cluster_id, std::move(wrapped_socket), - ping_seconds, false /* rebalanced */); + socket_manager->addConnectionSocket(std::string(node_id), std::string(cluster_id), + std::move(wrapped_socket), ping_seconds, + false /* rebalanced */, tenant_id); ENVOY_CONN_LOG(debug, "reverse_tunnel: successfully registered wrapped socket for reuse", connection); diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD index 4c752e4e3c1b9..9f5d51b630ca9 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD @@ -43,13 +43,35 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "reverse_tunnel_upstream_lifecycle_test", + size = "medium", + srcs = ["reverse_tunnel_upstream_lifecycle_test.cc"], + deps = [ + "//envoy/router:string_accessor_interface", + "//envoy/stream_info:uint64_accessor_interface", + "//source/common/buffer:buffer_lib", + "//source/common/network:utility_lib", + "//source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface:reverse_tunnel_acceptor_lib", + "//test/mocks/access_log:access_log_mocks", + "//test/mocks/event:event_mocks", + "//test/mocks/network:network_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/thread_local:thread_local_mocks", + "@envoy_api//envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3:pkg_cc_proto", + ], +) + envoy_cc_test( name = "upstream_socket_manager_test", size = "large", srcs = ["upstream_socket_manager_test.cc"], deps = [ + "//envoy/router:string_accessor_interface", + "//envoy/stream_info:uint64_accessor_interface", "//source/common/network:utility_lib", "//source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface:reverse_tunnel_acceptor_lib", + "//test/mocks/access_log:access_log_mocks", "//test/mocks/event:event_mocks", "//test/mocks/reverse_tunnel_reporting_service:reporter_mocks", "//test/mocks/server:factory_context_mocks", @@ -79,8 +101,11 @@ envoy_cc_test( size = "small", srcs = ["config_validation_test.cc"], deps = [ + "//source/extensions/access_loggers/stream:config", "//source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface:reverse_tunnel_acceptor_lib", "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/access_loggers/stream/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/config_validation_test.cc b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/config_validation_test.cc index 7c9d808dd3cd4..cfff6655e9d1f 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/config_validation_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/config_validation_test.cc @@ -1,8 +1,11 @@ +#include "envoy/extensions/access_loggers/stream/v3/stream.pb.h" #include "envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/upstream_reverse_connection_socket_interface.pb.h" #include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h" #include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -36,6 +39,24 @@ TEST_F(ConfigValidationTest, EmptyStatPrefix) { EXPECT_NO_THROW(acceptor.createBootstrapExtension(config_, context_)); } +TEST_F(ConfigValidationTest, AccessLogConfigurationIsAccepted) { + auto* access_log = config_.add_access_log(); + access_log->set_name("envoy.access_loggers.stdout"); + envoy::extensions::access_loggers::stream::v3::StdoutAccessLog stdout_access_log; + stdout_access_log.mutable_log_format()->mutable_text_format_source()->set_inline_string( + "%DYNAMIC_METADATA(envoy.reverse_tunnel.lifecycle:event)%\n"); + access_log->mutable_typed_config()->PackFrom(stdout_access_log); + + EXPECT_CALL(context_, messageValidationVisitor()) + .WillRepeatedly(testing::ReturnRef(ProtobufMessage::getStrictValidationVisitor())); + + ReverseTunnelAcceptor acceptor(context_); + auto extension = acceptor.createBootstrapExtension(config_, context_); + auto* reverse_tunnel_extension = dynamic_cast(extension.get()); + ASSERT_NE(reverse_tunnel_extension, nullptr); + EXPECT_TRUE(reverse_tunnel_extension->hasAccessLogs()); +} + } // namespace ReverseConnection } // namespace Bootstrap } // namespace Extensions diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle_test.cc b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle_test.cc new file mode 100644 index 0000000000000..3afe8d2d439ec --- /dev/null +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle_test.cc @@ -0,0 +1,396 @@ +#include +#include + +#include "envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/upstream_reverse_connection_socket_interface.pb.h" +#include "envoy/router/string_accessor.h" +#include "envoy/stream_info/stream_info.h" +#include "envoy/stream_info/uint64_accessor.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/network/utility.h" +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.h" +#include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h" + +#include "test/mocks/access_log/mocks.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/mocks/thread_local/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Bootstrap { +namespace ReverseConnection { + +class ReverseTunnelUpstreamLifecycleFilterTest : public testing::Test { +protected: + ReverseTunnelUpstreamLifecycleFilterTest() : dispatcher_("worker_0") { + stats_scope_ = Stats::ScopeSharedPtr(stats_store_.createScope("test_scope.")); + EXPECT_CALL(context_, threadLocal()).WillRepeatedly(ReturnRef(thread_local_)); + EXPECT_CALL(context_, scope()).WillRepeatedly(ReturnRef(*stats_scope_)); + + config_.set_stat_prefix("test_prefix"); + EXPECT_CALL(dispatcher_, createTimer_(_)) + .WillRepeatedly(testing::ReturnNew>()); + EXPECT_CALL(dispatcher_, createFileEvent_(_, _, _, _)) + .WillRepeatedly(testing::ReturnNew>()); + + socket_interface_ = std::make_unique(context_); + extension_ = + std::make_unique(*socket_interface_, context_, config_); + } + + void SetUp() override { + auto* registered_upstream_interface = + Network::socketInterface("envoy.bootstrap.reverse_tunnel.upstream_socket_interface"); + ASSERT_NE(registered_upstream_interface, nullptr); + auto* registered_acceptor = dynamic_cast( + const_cast(registered_upstream_interface)); + ASSERT_NE(registered_acceptor, nullptr); + registered_acceptor->extension_ = extension_.get(); + + thread_local_registry_ = + std::make_shared(dispatcher_, extension_.get()); + socket_manager_ = thread_local_registry_->socketManager(); + ASSERT_NE(socket_manager_, nullptr); + + tls_slot_ = ThreadLocal::TypedSlot::makeUnique(thread_local_); + thread_local_.setDispatcher(&dispatcher_); + tls_slot_->set([registry = thread_local_registry_](Event::Dispatcher&) + -> std::shared_ptr { return registry; }); + extension_->setTestOnlyTLSRegistry(std::move(tls_slot_)); + + EXPECT_CALL(callbacks_, connection()).WillRepeatedly(ReturnRef(callbacks_.connection_)); + EXPECT_CALL(callbacks_.connection_, streamInfo()) + .WillRepeatedly(ReturnRef(callbacks_.connection_.stream_info_)); + EXPECT_CALL(callbacks_.connection_, localCloseReason()) + .WillRepeatedly(Invoke( + [this]() -> absl::string_view { return callbacks_.connection_.local_close_reason_; })); + } + + Network::ConnectionSocketPtr createMockSocket(int fd = 123, + const std::string& local_addr = "127.0.0.1:8080", + const std::string& remote_addr = "127.0.0.1:9090") { + auto socket = std::make_unique>(); + + auto local_colon_pos = local_addr.find(':'); + auto local_address = Network::Utility::parseInternetAddressNoThrow( + local_addr.substr(0, local_colon_pos), std::stoi(local_addr.substr(local_colon_pos + 1))); + auto remote_colon_pos = remote_addr.find(':'); + auto remote_address = Network::Utility::parseInternetAddressNoThrow( + remote_addr.substr(0, remote_colon_pos), + std::stoi(remote_addr.substr(remote_colon_pos + 1))); + + auto mock_io_handle = std::make_unique>(); + auto* mock_io_handle_ptr = mock_io_handle.get(); + EXPECT_CALL(*mock_io_handle_ptr, fdDoNotUse()).WillRepeatedly(Return(fd)); + EXPECT_CALL(*socket, ioHandle()).WillRepeatedly(ReturnRef(*mock_io_handle_ptr)); + + socket->io_handle_ = std::move(mock_io_handle); + socket->connection_info_provider_->setLocalAddress(local_address); + socket->connection_info_provider_->setRemoteAddress(remote_address); + return socket; + } + + std::string filterStateString(absl::string_view key) const { + const auto* accessor = + callbacks_.connection_.stream_info_.filter_state_->getDataReadOnly( + key); + return accessor != nullptr ? std::string(accessor->asString()) : ""; + } + + uint64_t filterStateUint64(absl::string_view key) const { + const auto* accessor = callbacks_.connection_.stream_info_.filter_state_ + ->getDataReadOnly(key); + return accessor != nullptr ? accessor->value() : 0; + } + + NiceMock context_; + NiceMock thread_local_; + Stats::IsolatedStoreImpl stats_store_; + Stats::ScopeSharedPtr stats_scope_; + NiceMock dispatcher_; + NiceMock callbacks_; + + envoy::extensions::bootstrap::reverse_tunnel::upstream_socket_interface::v3:: + UpstreamReverseConnectionSocketInterface config_; + + std::unique_ptr socket_interface_; + std::unique_ptr extension_; + std::shared_ptr thread_local_registry_; + std::unique_ptr> tls_slot_; + UpstreamSocketManager* socket_manager_{nullptr}; + Network::ConnectionSocketPtr handed_off_socket_; +}; + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, + CopiesLifecycleIntoFilterStateAndLogsKeepaliveTimeoutOnce) { + const int fd = 321; + auto socket = createMockSocket(fd); + socket_manager_->addConnectionSocket("node-c", "cluster-c", std::move(socket), + std::chrono::seconds(30), false, "tenant-c"); + handed_off_socket_ = socket_manager_->getConnectionSocket("node-c"); + ASSERT_NE(handed_off_socket_, nullptr); + + EXPECT_CALL(callbacks_.connection_, getSocket()).WillRepeatedly(ReturnRef(handed_off_socket_)); + EXPECT_CALL(callbacks_.connection_, addConnectionCallbacks(_)); + + ReverseTunnelUpstreamLifecycleFilter filter; + filter.initializeReadFilterCallbacks(callbacks_); + EXPECT_EQ(filter.onNewConnection(), Network::FilterStatus::Continue); + + EXPECT_EQ(filterStateString(kFilterStateNodeId), "node-c"); + EXPECT_EQ(filterStateString(kFilterStateClusterId), "cluster-c"); + EXPECT_EQ(filterStateString(kFilterStateTenantId), "tenant-c"); + EXPECT_EQ(filterStateUint64(kFilterStateFd), fd); + + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + callbacks_.connection_.local_close_reason_ = + std::string(StreamInfo::LocalCloseReasons::get().Http2PingTimeout); + + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce(Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo&) { + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::UpstreamEnd); + })); + + filter.onEvent(Network::ConnectionEvent::LocalClose); + filter.onEvent(Network::ConnectionEvent::LocalClose); + + const auto* lifecycle = socket_manager_->getLifecycleInfo(fd); + ASSERT_NE(lifecycle, nullptr); + EXPECT_EQ(lifecycle->close_reason, StreamInfo::LocalCloseReasons::get().Http2PingTimeout); + + // Clear access logs before teardown so the destructor-emitted tunnel_closed + // close log does not trigger an unexpected extra mock call. + extension_->setTestOnlyAccessLogs({}); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, + LocalCloseAfterSocketDeathReusesHttp2PingTimeoutForTunnelClosed) { + const int fd = 654; + auto socket = createMockSocket(fd); + socket_manager_->addConnectionSocket("node-d", "cluster-d", std::move(socket), + std::chrono::seconds(30), false, "tenant-d"); + handed_off_socket_ = socket_manager_->getConnectionSocket("node-d"); + ASSERT_NE(handed_off_socket_, nullptr); + + EXPECT_CALL(callbacks_.connection_, getSocket()).WillRepeatedly(ReturnRef(handed_off_socket_)); + EXPECT_CALL(callbacks_.connection_, addConnectionCallbacks(_)); + + ReverseTunnelUpstreamLifecycleFilter filter; + filter.initializeReadFilterCallbacks(callbacks_); + EXPECT_EQ(filter.onNewConnection(), Network::FilterStatus::Continue); + + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + callbacks_.connection_.local_close_reason_ = + std::string(StreamInfo::LocalCloseReasons::get().Http2PingTimeout); + + testing::InSequence sequence; + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::UpstreamEnd); + ASSERT_TRUE(stream_info.connectionTerminationDetails().has_value()); + EXPECT_EQ(stream_info.connectionTerminationDetails().value(), + StreamInfo::LocalCloseReasons::get().Http2PingTimeout); + })); + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::UpstreamEnd); + ASSERT_TRUE(stream_info.connectionTerminationDetails().has_value()); + EXPECT_EQ(stream_info.connectionTerminationDetails().value(), + StreamInfo::LocalCloseReasons::get().Http2PingTimeout); + })); + + socket_manager_->markSocketDead(fd); + filter.onEvent(Network::ConnectionEvent::LocalClose); + + EXPECT_EQ(socket_manager_->getLifecycleInfo(fd), nullptr); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, + RemoteCloseAfterSocketDeathLogsTunnelClosedWithRemoteCloseReason) { + const int fd = 777; + auto socket = createMockSocket(fd); + socket_manager_->addConnectionSocket("node-e", "cluster-e", std::move(socket), + std::chrono::seconds(30), false, "tenant-e"); + handed_off_socket_ = socket_manager_->getConnectionSocket("node-e"); + ASSERT_NE(handed_off_socket_, nullptr); + + EXPECT_CALL(callbacks_.connection_, getSocket()).WillRepeatedly(ReturnRef(handed_off_socket_)); + EXPECT_CALL(callbacks_.connection_, addConnectionCallbacks(_)); + + ReverseTunnelUpstreamLifecycleFilter filter; + filter.initializeReadFilterCallbacks(callbacks_); + EXPECT_EQ(filter.onNewConnection(), Network::FilterStatus::Continue); + + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = stream_info.dynamicMetadata().filter_metadata().at( + std::string(kAccessLogMetadataNamespace)); + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::UpstreamEnd); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventTunnelClosed)); + EXPECT_EQ(metadata.fields().at("close_reason").string_value(), + std::string(kLifecycleCloseReasonRemoteClose)); + ASSERT_TRUE(stream_info.connectionTerminationDetails().has_value()); + EXPECT_EQ(stream_info.connectionTerminationDetails().value(), + std::string(kLifecycleCloseReasonRemoteClose)); + })); + + socket_manager_->markSocketDead(fd); + filter.onEvent(Network::ConnectionEvent::RemoteClose); + + EXPECT_EQ(socket_manager_->getLifecycleInfo(fd), nullptr); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, + GenericLocalCloseAfterSocketDeathLogsTunnelClosedWithLocalCloseReason) { + const int fd = 888; + auto socket = createMockSocket(fd); + socket_manager_->addConnectionSocket("node-f", "cluster-f", std::move(socket), + std::chrono::seconds(30), false, "tenant-f"); + handed_off_socket_ = socket_manager_->getConnectionSocket("node-f"); + ASSERT_NE(handed_off_socket_, nullptr); + + EXPECT_CALL(callbacks_.connection_, getSocket()).WillRepeatedly(ReturnRef(handed_off_socket_)); + EXPECT_CALL(callbacks_.connection_, addConnectionCallbacks(_)); + + ReverseTunnelUpstreamLifecycleFilter filter; + filter.initializeReadFilterCallbacks(callbacks_); + EXPECT_EQ(filter.onNewConnection(), Network::FilterStatus::Continue); + + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + callbacks_.connection_.local_close_reason_.clear(); + + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = stream_info.dynamicMetadata().filter_metadata().at( + std::string(kAccessLogMetadataNamespace)); + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::UpstreamEnd); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventTunnelClosed)); + EXPECT_EQ(metadata.fields().at("close_reason").string_value(), + std::string(kLifecycleCloseReasonLocalClose)); + ASSERT_TRUE(stream_info.connectionTerminationDetails().has_value()); + EXPECT_EQ(stream_info.connectionTerminationDetails().value(), + std::string(kLifecycleCloseReasonLocalClose)); + })); + + socket_manager_->markSocketDead(fd); + filter.onEvent(Network::ConnectionEvent::LocalClose); + + EXPECT_EQ(socket_manager_->getLifecycleInfo(fd), nullptr); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, OnNewConnectionReturnsWhenSocketIsNull) { + Network::ConnectionSocketPtr null_socket; + EXPECT_CALL(callbacks_.connection_, getSocket()).WillRepeatedly(ReturnRef(null_socket)); + EXPECT_CALL(callbacks_.connection_, addConnectionCallbacks(_)); + + ReverseTunnelUpstreamLifecycleFilter filter; + filter.initializeReadFilterCallbacks(callbacks_); + EXPECT_EQ(filter.onNewConnection(), Network::FilterStatus::Continue); + + EXPECT_EQ(filterStateString(kFilterStateNodeId), ""); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, OnNewConnectionReturnsWhenNoLifecycleInfo) { + const int fd = 999; + auto socket = createMockSocket(fd); + EXPECT_CALL(callbacks_.connection_, getSocket()).WillRepeatedly(ReturnRef(socket)); + EXPECT_CALL(callbacks_.connection_, addConnectionCallbacks(_)); + + ReverseTunnelUpstreamLifecycleFilter filter; + filter.initializeReadFilterCallbacks(callbacks_); + EXPECT_EQ(filter.onNewConnection(), Network::FilterStatus::Continue); + + EXPECT_EQ(filterStateString(kFilterStateNodeId), ""); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, OnEventIgnoredWhenLifecycleNotInitialized) { + Network::ConnectionSocketPtr null_socket; + EXPECT_CALL(callbacks_.connection_, getSocket()).WillRepeatedly(ReturnRef(null_socket)); + EXPECT_CALL(callbacks_.connection_, addConnectionCallbacks(_)); + + ReverseTunnelUpstreamLifecycleFilter filter; + filter.initializeReadFilterCallbacks(callbacks_); + filter.onNewConnection(); + + filter.onEvent(Network::ConnectionEvent::LocalClose); + filter.onEvent(Network::ConnectionEvent::RemoteClose); + filter.onEvent(Network::ConnectionEvent::Connected); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, ConnectedEventIsIgnored) { + const int fd = 555; + auto socket = createMockSocket(fd); + socket_manager_->addConnectionSocket("node-g", "cluster-g", std::move(socket), + std::chrono::seconds(30), false, "tenant-g"); + handed_off_socket_ = socket_manager_->getConnectionSocket("node-g"); + ASSERT_NE(handed_off_socket_, nullptr); + + EXPECT_CALL(callbacks_.connection_, getSocket()).WillRepeatedly(ReturnRef(handed_off_socket_)); + EXPECT_CALL(callbacks_.connection_, addConnectionCallbacks(_)); + + ReverseTunnelUpstreamLifecycleFilter filter; + filter.initializeReadFilterCallbacks(callbacks_); + EXPECT_EQ(filter.onNewConnection(), Network::FilterStatus::Continue); + + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + + EXPECT_CALL(*access_log, log(_, _)).Times(0); + filter.onEvent(Network::ConnectionEvent::Connected); + + extension_->setTestOnlyAccessLogs({}); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, OnDataReturnsContinue) { + ReverseTunnelUpstreamLifecycleFilter filter; + Buffer::OwnedImpl buffer("data"); + EXPECT_EQ(filter.onData(buffer, false), Network::FilterStatus::Continue); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, FactoryCreatesEmptyProto) { + ReverseTunnelUpstreamLifecycleConfigFactory factory; + auto proto = factory.createEmptyConfigProto(); + EXPECT_NE(proto, nullptr); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, FactoryReportsCorrectName) { + ReverseTunnelUpstreamLifecycleConfigFactory factory; + EXPECT_EQ(factory.name(), "envoy.filters.upstream_network.reverse_tunnel_lifecycle"); +} + +TEST_F(ReverseTunnelUpstreamLifecycleFilterTest, FactoryIsNotTerminalFilter) { + ReverseTunnelUpstreamLifecycleConfigFactory factory; + Protobuf::Empty empty; + EXPECT_FALSE(factory.isTerminalFilterByProto(empty, context_)); +} + +} // namespace ReverseConnection +} // namespace Bootstrap +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager_test.cc b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager_test.cc index b011667bfbd9e..28459e52c8bb0 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager_test.cc @@ -1,10 +1,15 @@ #include "envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3/upstream_reverse_connection_socket_interface.pb.h" +#include "envoy/router/string_accessor.h" +#include "envoy/stream_info/stream_info.h" +#include "envoy/stream_info/uint64_accessor.h" #include "source/common/network/utility.h" +#include "source/common/protobuf/protobuf.h" #include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h" #include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h" #include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h" +#include "test/mocks/access_log/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/reverse_tunnel_reporting_service/reporter.h" #include "test/mocks/server/factory_context.h" @@ -197,6 +202,24 @@ class TestUpstreamSocketManager : public testing::Test { return (it != manager->node_to_conn_count_map_.end()) ? it->second : 0; } + const Protobuf::Struct& lifecycleMetadata(const StreamInfo::StreamInfo& stream_info) const { + return stream_info.dynamicMetadata().filter_metadata().at( + std::string(kAccessLogMetadataNamespace)); + } + + std::string filterStateString(const StreamInfo::StreamInfo& stream_info, + absl::string_view key) const { + const auto* accessor = stream_info.filterState().getDataReadOnly(key); + return accessor != nullptr ? std::string(accessor->asString()) : ""; + } + + uint64_t filterStateUint64(const StreamInfo::StreamInfo& stream_info, + absl::string_view key) const { + const auto* accessor = + stream_info.filterState().getDataReadOnly(key); + return accessor != nullptr ? accessor->value() : 0; + } + NiceMock context_; NiceMock thread_local_; Stats::IsolatedStoreImpl stats_store_; @@ -258,6 +281,112 @@ TEST_F(TestUpstreamSocketManager, AddConnectionSocketEmptyNodeId) { EXPECT_EQ(retrieved_socket, nullptr); } +TEST_F(TestUpstreamSocketManager, SetupLogPreservesOriginalIdentifiersWithTenantIsolation) { + socket_manager_->setTenantIsolationEnabled(true); + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + + const int fd = 123; + auto socket = createMockSocket(fd, "10.0.0.1:8080", "10.0.0.2:9090"); + + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::TcpUpstreamConnected); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventTunnelSetup)); + EXPECT_EQ(metadata.fields().at("node_id").string_value(), "node-a"); + EXPECT_EQ(metadata.fields().at("cluster_id").string_value(), "cluster-a"); + EXPECT_EQ(metadata.fields().at("tenant_id").string_value(), "tenant-a"); + EXPECT_EQ(metadata.fields().at("socket_state").string_value(), + std::string(kLifecycleSocketStateIdle)); + EXPECT_EQ(metadata.fields().at("fd").number_value(), fd); + EXPECT_EQ(filterStateString(stream_info, kFilterStateNodeId), "node-a"); + EXPECT_EQ(filterStateString(stream_info, kFilterStateClusterId), "cluster-a"); + EXPECT_EQ(filterStateString(stream_info, kFilterStateTenantId), "tenant-a"); + EXPECT_EQ(filterStateUint64(stream_info, kFilterStateFd), fd); + })) + .RetiresOnSaturation(); + + socket_manager_->addConnectionSocket("node-a", "cluster-a", std::move(socket), + std::chrono::seconds(30), false, "tenant-a"); + + EXPECT_EQ(getNodeToClusterMapping("tenant-a:node-a"), "tenant-a:cluster-a"); + testing::Mock::VerifyAndClearExpectations(access_log.get()); + extension_->setTestOnlyAccessLogs({}); +} + +TEST_F(TestUpstreamSocketManager, SyntheticLifecycleLogDoesNotSetConnectionId) { + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce(Invoke([&](const Formatter::Context&, const StreamInfo::StreamInfo& stream_info) { + // Connection ID should not be set on synthetic lifecycle logs — the FD is + // available in dynamic metadata instead, and FDs are not valid connection IDs + // because they can be recycled by the OS. + auto id = stream_info.downstreamAddressProvider().connectionID(); + EXPECT_FALSE(id.has_value()); + })); + + auto socket = createMockSocket(42); + socket_manager_->addConnectionSocket("node-1", "cluster-1", std::move(socket), + std::chrono::seconds(30)); + + testing::Mock::VerifyAndClearExpectations(access_log.get()); + extension_->setTestOnlyAccessLogs({}); +} + +TEST_F(TestUpstreamSocketManager, HandoffAndCloseLogsUseLifecycleMetadata) { + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + + const int fd = 456; + auto socket = createMockSocket(fd, "10.0.0.1:8081", "10.0.0.2:9091"); + + testing::InSequence sequence; + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::TcpUpstreamConnected); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventTunnelSetup)); + })); + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::UpstreamPoolReady); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventSocketHandoff)); + EXPECT_EQ(metadata.fields().at("handoff_kind").string_value(), + std::string(kLifecycleHandoffKindPoolToUpstream)); + EXPECT_EQ(metadata.fields().at("socket_state").string_value(), + std::string(kLifecycleSocketStateHandedOff)); + })); + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::UpstreamEnd); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventTunnelClosed)); + EXPECT_EQ(metadata.fields().at("close_reason").string_value(), + StreamInfo::LocalCloseReasons::get().Http2PingTimeout); + EXPECT_EQ(metadata.fields().at("socket_state").string_value(), + std::string(kLifecycleSocketStateInUse)); + })); + + socket_manager_->addConnectionSocket("node-b", "cluster-b", std::move(socket), + std::chrono::seconds(30), false, "tenant-b"); + auto handed_off_socket = socket_manager_->getConnectionSocket("node-b"); + ASSERT_NE(handed_off_socket, nullptr); + socket_manager_->setCloseReason(fd, StreamInfo::LocalCloseReasons::get().Http2PingTimeout); + socket_manager_->markSocketDead(fd); +} + TEST_F(TestUpstreamSocketManager, AddAndGetMultipleSocketsSameNode) { auto socket1 = createMockSocket(123); auto socket2 = createMockSocket(456); @@ -1532,6 +1661,202 @@ TEST_F(TestUpstreamSocketManagerRebalancing, MainThreadExcludedFromRebalancing) EXPECT_EQ(getNodeConnCount(main_socket_manager.get(), node_id), 0); } +TEST_F(TestUpstreamSocketManager, SendPingEmitsIdlePingSentEvent) { + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + + auto socket = createMockSocket(123); + const std::string node_id = "test-node"; + const std::string cluster_id = "test-cluster"; + const std::chrono::seconds ping_interval(30); + + // Expect tunnel_setup on add. + EXPECT_CALL(*access_log, log(_, _)).Times(1).RetiresOnSaturation(); + socket_manager_->addConnectionSocket(node_id, cluster_id, std::move(socket), ping_interval); + testing::Mock::VerifyAndClearExpectations(access_log.get()); + + auto& sockets = getSocketsForNode(node_id); + auto* mock_io_handle = + dynamic_cast*>(&sockets.front()->ioHandle()); + + EXPECT_CALL(*mock_io_handle, write(_)) + .WillRepeatedly(Invoke([](Buffer::Instance& buffer) -> Api::IoCallUint64Result { + uint64_t len = buffer.length(); + buffer.drain(len); + return Api::IoCallUint64Result{len, Api::IoError::none()}; + })); + + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::TcpUpstreamConnected); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventIdlePingSent)); + EXPECT_EQ(metadata.fields().at("socket_state").string_value(), + std::string(kLifecycleSocketStateIdle)); + })); + + socket_manager_->sendPingForConnection(123); + extension_->setTestOnlyAccessLogs({}); +} + +TEST_F(TestUpstreamSocketManager, PingAckEmitsIdlePingAckEvent) { + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + + auto socket = createMockSocket(123); + const std::string node_id = "test-node"; + const std::string cluster_id = "test-cluster"; + const std::chrono::seconds ping_interval(30); + + // Expect tunnel_setup on add. + EXPECT_CALL(*access_log, log(_, _)).Times(1).RetiresOnSaturation(); + socket_manager_->addConnectionSocket(node_id, cluster_id, std::move(socket), ping_interval); + testing::Mock::VerifyAndClearExpectations(access_log.get()); + + auto mock_io_handle = std::make_unique>(); + EXPECT_CALL(*mock_io_handle, fdDoNotUse()).WillRepeatedly(Return(123)); + + const std::string ping_response = "RPING"; + EXPECT_CALL(*mock_io_handle, read(_, _)) + .WillOnce([&](Buffer::Instance& buffer, absl::optional) -> Api::IoCallUint64Result { + buffer.add(ping_response); + return Api::IoCallUint64Result{ping_response.size(), Api::IoError::none()}; + }); + + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::TcpUpstreamConnected); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventIdlePingAck)); + })); + + socket_manager_->onPingResponse(*mock_io_handle); + extension_->setTestOnlyAccessLogs({}); +} + +TEST_F(TestUpstreamSocketManager, PingMissEmitsIdlePingMissEvent) { + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + + auto socket = createMockSocket(123); + const std::string node_id = "test-node"; + const std::string cluster_id = "test-cluster"; + const std::chrono::seconds ping_interval(30); + + // Expect tunnel_setup on add. + EXPECT_CALL(*access_log, log(_, _)).Times(1).RetiresOnSaturation(); + socket_manager_->addConnectionSocket(node_id, cluster_id, std::move(socket), ping_interval); + testing::Mock::VerifyAndClearExpectations(access_log.get()); + + // Default miss threshold is 3. First timeout should emit idle_ping_miss. + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce( + Invoke([&](const Formatter::Context& context, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(context.accessLogType(), AccessLog::AccessLogType::TcpUpstreamConnected); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventIdlePingMiss)); + EXPECT_EQ(metadata.fields().at("miss_count").string_value(), "1"); + })); + + socket_manager_->onPingTimeout(123); + EXPECT_TRUE(verifyFDToNodeMap(123)); // Should still be alive. + extension_->setTestOnlyAccessLogs({}); +} + +TEST_F(TestUpstreamSocketManager, PingTimeoutEmitsIdlePingTimeoutEvent) { + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + + auto socket = createMockSocket(123); + const std::string node_id = "test-node"; + const std::string cluster_id = "test-cluster"; + const std::chrono::seconds ping_interval(30); + + // Expect tunnel_setup on add. + EXPECT_CALL(*access_log, log(_, _)).Times(1).RetiresOnSaturation(); + socket_manager_->addConnectionSocket(node_id, cluster_id, std::move(socket), ping_interval); + testing::Mock::VerifyAndClearExpectations(access_log.get()); + + // Miss twice (below threshold=3) -- expect idle_ping_miss events. + EXPECT_CALL(*access_log, log(_, _)) + .Times(2) + .WillRepeatedly( + Invoke([&](const Formatter::Context&, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventIdlePingMiss)); + })) + .RetiresOnSaturation(); + socket_manager_->onPingTimeout(123); + socket_manager_->onPingTimeout(123); + testing::Mock::VerifyAndClearExpectations(access_log.get()); + + // Third miss exceeds threshold -- expect idle_ping_timeout then tunnel_closed. + testing::InSequence seq; + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce(Invoke([&](const Formatter::Context&, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventIdlePingTimeout)); + EXPECT_EQ(metadata.fields().at("miss_count").string_value(), "3"); + })); + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce(Invoke([&](const Formatter::Context&, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventTunnelClosed)); + EXPECT_EQ(metadata.fields().at("close_reason").string_value(), + std::string(kLifecycleCloseReasonIdlePingTimeout)); + })); + + socket_manager_->onPingTimeout(123); + EXPECT_FALSE(verifyFDToNodeMap(123)); // Should be dead. + extension_->setTestOnlyAccessLogs({}); +} + +TEST_F(TestUpstreamSocketManager, DestructorEmitsDeferredCloseLogs) { + auto access_log = std::make_shared>(); + extension_->setTestOnlyAccessLogs({access_log}); + + const int fd = 555; + auto socket = createMockSocket(fd); + + // Expect tunnel_setup on add, then socket_handoff on get. + EXPECT_CALL(*access_log, log(_, _)).Times(2).RetiresOnSaturation(); + socket_manager_->addConnectionSocket("node-x", "cluster-x", std::move(socket), + std::chrono::seconds(30), false, "tenant-x"); + auto handed_off = socket_manager_->getConnectionSocket("node-x"); + ASSERT_NE(handed_off, nullptr); + testing::Mock::VerifyAndClearExpectations(access_log.get()); + + // Attach the lifecycle filter flag so markSocketDead defers the close log. + socket_manager_->markUpstreamLifecycleFilterAttached(fd); + + // markSocketDead with empty close reason + filter attached = deferred. + // This should NOT emit the close log yet. + EXPECT_CALL(*access_log, log(_, _)).Times(0); + socket_manager_->markSocketDead(fd); + testing::Mock::VerifyAndClearExpectations(access_log.get()); + + // Destructor should emit the deferred tunnel_closed log. + EXPECT_CALL(*access_log, log(_, _)) + .WillOnce(Invoke([&](const Formatter::Context&, const StreamInfo::StreamInfo& stream_info) { + const auto& metadata = lifecycleMetadata(stream_info); + EXPECT_EQ(metadata.fields().at("event").string_value(), + std::string(kLifecycleEventTunnelClosed)); + EXPECT_EQ(metadata.fields().at("close_reason").string_value(), + std::string(kLifecycleCloseReasonExplicitClose)); + })); + + socket_manager_.reset(); // Triggers destructor. + extension_->setTestOnlyAccessLogs({}); +} + } // namespace ReverseConnection. } // namespace Bootstrap. } // namespace Extensions. diff --git a/tools/extensions/extensions_schema.yaml b/tools/extensions/extensions_schema.yaml index e0d5cc2f66755..91f659d9a01a8 100644 --- a/tools/extensions/extensions_schema.yaml +++ b/tools/extensions/extensions_schema.yaml @@ -79,6 +79,7 @@ categories: - envoy.filters.network - envoy.filters.udp_listener - envoy.filters.udp.session +- envoy.filters.upstream_network - envoy.filters.quic_listener - envoy.formatter - envoy.geoip_providers diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 2b775b942f7fb..f9dcb8751fb9f 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -926,6 +926,7 @@ gso gzip hackery hacky +handoff handshaker hardcoded hardcodes @@ -1148,6 +1149,7 @@ num numkeys oauth observability +observations ocagent offsetof oneof From e1b6a315e2eacedf01ed090c104ed305a2db6d11 Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Wed, 13 May 2026 12:53:34 -0400 Subject: [PATCH 302/750] scoped-routes: remove unused data-member form StringKeyFragment (#44960) Commit Message: scoped-routes: remove unused data-member form StringKeyFragment Additional Description: Removing an unused data-member. Risk Level: low - internal modification. Testing: N/A Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A --------- Signed-off-by: Adi Suissa-Peleg --- envoy/router/scopes.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/envoy/router/scopes.h b/envoy/router/scopes.h index 47ba039756eb1..936b72b76fd48 100644 --- a/envoy/router/scopes.h +++ b/envoy/router/scopes.h @@ -69,13 +69,11 @@ using ScopeKeyPtr = std::unique_ptr; // String fragment. class StringKeyFragment : public ScopeKeyFragmentBase { public: - explicit StringKeyFragment(absl::string_view value) - : value_(value), hash_(HashUtil::xxHash64(value_)) {} + explicit StringKeyFragment(absl::string_view value) : hash_(HashUtil::xxHash64(value)) {} uint64_t hash() const override { return hash_; } private: - const std::string value_; const uint64_t hash_; }; From 02726a09ec30b65e669f7ef5b569af62fac6a3f6 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 13 May 2026 13:14:33 -0400 Subject: [PATCH 303/750] Update rules for Google Patch reward program. (#44931) Rename `Vulnerability Reward Program` to `Patch Reward Program` and update the link to the correct program. Risk Level: low Testing: n/a Docs Changes: yes Release Notes: n/a Platform Specific Features: n/a --------- Signed-off-by: yavlasov --- .../arch_overview/security/google_vrp.rst | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/root/intro/arch_overview/security/google_vrp.rst b/docs/root/intro/arch_overview/security/google_vrp.rst index 16ad3bcb02b75..c681b1fb993b5 100644 --- a/docs/root/intro/arch_overview/security/google_vrp.rst +++ b/docs/root/intro/arch_overview/security/google_vrp.rst @@ -1,10 +1,10 @@ .. _arch_overview_google_vrp: -Google Vulnerability Reward Program (VRP) +Google Patch Reward Program (PRP) ========================================= -Envoy is a participant in `Google's Vulnerability Reward Program (VRP) -`_. This is open to all security +Envoy is a participant in `Google's Patch Reward Program (PRP) +`_. This is open to all security researchers and will provide rewards for vulnerabilities discovered and reported according to the rules below. @@ -13,7 +13,7 @@ rules below. Rules ----- -The goal of the VRP is to provide a formal process to honor contributions from external +The goal of the PRP is to provide a formal process to honor contributions from external security researchers to Envoy's security. Vulnerabilities should meet the following conditions to be eligible for the program: @@ -22,22 +22,26 @@ to be eligible for the program: :ref:`execution environment ` and be consistent with the program's :ref:`threat model `. -2. Vulnerabilities must be reported to **both** the Envoy project and via https://bughunters.google.com/report. - To report to the Envoy project, preferably - `open a GitHub Security Advisory `_ +2. Vulnerabilities must be reported to the Envoy project, preferably by + `opening a GitHub Security Advisory `_ — alternatively, you may email envoy-security@googlegroups.com Vulnerabilities must be kept under embargo while triage and potential security releases occur. Please follow the :repo:`disclosure guidance ` when submitting reports. Disclosure SLOs are documented :repo:`here `. In general, security disclosures are subject to the `Linux Foundation's privacy policy - `_ with the added proviso that VRP reports (including - reporter e-mail address and name) may be freely shared with Google for VRP purposes. + `_ with the added proviso that PRP reports (including + reporter e-mail address and name) may be freely shared with Google for PRP purposes. -3. Vulnerabilities must not be previously known in a public forum, e.g. GitHub issues trackers, +3. After vulnerability has been confirmed by the Envoy project and the patch had been publicly released, + you may apply to `Google's Patch Reward program `_. + The program requires you to submit the PR as well as the evidence of improvement, such as resolved security advisory, + which are only available after public disclosure. + +4. Vulnerabilities must not be previously known in a public forum, e.g. GitHub issues trackers, CVE databases (when previously associated with Envoy), etc. Existing CVEs that have not been previously associated with an Envoy vulnerability are fair game. -4. Vulnerabilities must not be also submitted to a parallel reward program run by Google or +5. Vulnerabilities must not be also submitted to a parallel reward program run by Google or `Lyft `_. Rewards are at the discretion of the Envoy OSS security team and Google. They will be conditioned on @@ -45,7 +49,7 @@ the above criteria. If multiple instances of the same vulnerability are reported independent researchers or the vulnerability is already tracked under embargo by the OSS Envoy security team, we will aim to fairly divide the reward amongst reporters. -Rewards should be claimed from Google VRP following the corresponding Envoy security release. +Rewards should be claimed from Google PRP following the corresponding Envoy security release. .. _arch_overview_google_vrp_threat_model: From c14fe0c85acb1023982f5014792898bb289152f7 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Wed, 13 May 2026 14:31:23 -0400 Subject: [PATCH 304/750] test: fix build break from BufferStringEqual rename merge skew (#45053) BufferStringEqual was renamed to BufferString in #44817, but #44676 merged after with a new use of the old name, causing a build break. Signed-off-by: Jonh Wendell --- test/common/http/http1/codec_impl_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 0abd9daf6baf6..55d1242fdd96b 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -4512,7 +4512,7 @@ TEST_F(Http1ClientConnectionImplTest, DecoderDestroyedBeforeMessageComplete) { EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(*response_decoder, decodeHeaders_(_, false)); - EXPECT_CALL(*response_decoder, decodeData(BufferStringEqual("hello"), false)); + EXPECT_CALL(*response_decoder, decodeData(BufferString("hello"), false)); Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" "5\r\nhello\r\n"); From 4c4ba5c91e9d2d0932cddf939f1781b6fd36f966 Mon Sep 17 00:00:00 2001 From: Raven Black Date: Wed, 13 May 2026 15:07:10 -0400 Subject: [PATCH 305/750] Improve docs on timeout (#45054) Commit Message: Improve docs on timeout Additional Description: It was unclear what "incompatible" meant, and also not very clear that where `timeout` is *really* incompatible is streaming requests. This clarifies both those things. Risk Level: None, it's docs Testing: n/a Docs Changes: Yes it is. Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Raven Black --- docs/root/faq/configuration/timeouts.rst | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/root/faq/configuration/timeouts.rst b/docs/root/faq/configuration/timeouts.rst index c043021f215ef..34a515b53e3dd 100644 --- a/docs/root/faq/configuration/timeouts.rst +++ b/docs/root/faq/configuration/timeouts.rst @@ -126,9 +126,21 @@ stream timeouts already introduced above. .. attention:: - This timeout defaults to *15 seconds*, however, it is not compatible with streaming responses - (responses that never end), and will need to be disabled. Stream idle timeouts should be used - in the case of streaming APIs as described elsewhere on this page. + This timeout defaults to *15 seconds*. This is typically a problem for streaming responses + (or any responses that are not expected to end within 15 seconds), and will need to be + disabled by setting to 0, or set to a long enough value. Typically + :ref:`idle_timeout ` + should be used in the case of streaming APIs as described elsewhere on this page. + + .. attention:: + + This timeout will not be triggered by streaming *requests*, as it only begins when the + request is complete. If you wish to have a timeout that applies to streaming requests, + it is recommended to set ``timeout`` to 0 to disable it, and use + :ref:`max_stream_duration ` + and/or :ref:`idle_timeout `, + whose timers start when the request begins. + * The route :ref:`idle_timeout ` allows overriding of the HTTP connection manager :ref:`stream_idle_timeout ` From 5e46d571ad7314a3703549aa28d8236ecac8a046 Mon Sep 17 00:00:00 2001 From: Jonathan Wu Date: Thu, 14 May 2026 06:31:39 -0400 Subject: [PATCH 306/750] Improve mobile/python CI flakiness (#45033) - Removed unused gevent requirement. This import possibly might conflict with asyncio - Add timeouts to tests - Set a default timeout of 60m in _mobile_container_ci.yml to match _run.yml [Optional Fixes #44894] --------- Signed-off-by: Jonathan Wu --- .github/workflows/_mobile_container_ci.yml | 1 + .../async_client/async_client_fetch_test.py | 5 +- mobile/test/python/echo_test_server.py | 1 + mobile/tools/python/requirements.in | 1 - mobile/tools/python/requirements.txt | 78 ------------------- 5 files changed, 6 insertions(+), 80 deletions(-) diff --git a/.github/workflows/_mobile_container_ci.yml b/.github/workflows/_mobile_container_ci.yml index 49ac85083de0f..2977313af3859 100644 --- a/.github/workflows/_mobile_container_ci.yml +++ b/.github/workflows/_mobile_container_ci.yml @@ -116,6 +116,7 @@ on: type: string timeout-minutes: type: number + default: 60 trusted: type: boolean default: false diff --git a/mobile/test/python/async_client/async_client_fetch_test.py b/mobile/test/python/async_client/async_client_fetch_test.py index 84be7740acf35..2018481d7bd78 100644 --- a/mobile/test/python/async_client/async_client_fetch_test.py +++ b/mobile/test/python/async_client/async_client_fetch_test.py @@ -107,7 +107,10 @@ async def run(): ), client.get(f"{self._echo_server_url}/"), ] - responses = await asyncio.gather(*tasks) + responses = await asyncio.wait_for( + asyncio.gather(*tasks), + timeout=30, + ) for resp in responses: async with resp: diff --git a/mobile/test/python/echo_test_server.py b/mobile/test/python/echo_test_server.py index 6199b9a8df671..79118b518d354 100644 --- a/mobile/test/python/echo_test_server.py +++ b/mobile/test/python/echo_test_server.py @@ -72,6 +72,7 @@ class EchoTestServer: def __init__(self, ip, port): self._server = HTTPServer((ip, port), EchoServerHandler) self._server_thread = Thread(target=self._server.serve_forever, daemon=True) + self._server.socket.settimeout(10.0) def start(self): self._server_thread.start() diff --git a/mobile/tools/python/requirements.in b/mobile/tools/python/requirements.in index 4adcbe275396a..2f5c6ffbb0fec 100644 --- a/mobile/tools/python/requirements.in +++ b/mobile/tools/python/requirements.in @@ -1,5 +1,4 @@ black -gevent httpx mypy pytest diff --git a/mobile/tools/python/requirements.txt b/mobile/tools/python/requirements.txt index 05ba36d5b6036..d9f6e01c469ba 100644 --- a/mobile/tools/python/requirements.txt +++ b/mobile/tools/python/requirements.txt @@ -179,43 +179,6 @@ click==8.3.1 \ --hash=sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a \ --hash=sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6 # via black -gevent==26.4.0 \ - --hash=sha256:079dca3fe5e048714e93ab5ce7cbf2ec31709c860979feb117abbbf2b8ae6ad0 \ - --hash=sha256:1a18e543c830a1c07a2efeb33786a57ccac360af70cb42bbaf5a6f5f7ca49300 \ - --hash=sha256:1c737e6ac6ce1398df0e3f41c58d982e397c993cbe73ac05b7edbe39e128c9cb \ - --hash=sha256:1fe581d41c63cd1d8b12c69561ce53a48ad0d8763b254740d7bfea997335a38c \ - --hash=sha256:201323a5fb9a0646a0c7b384395ca55d60ee83200677919229df0648c4b78e6c \ - --hash=sha256:288d03addfccf0d1c67268358b6759b04392bf3bc35d26f3d9a45c82899c292d \ - --hash=sha256:297a361071dc6708115d4544859321e93b02a6cd5823ba02c0a909530a519d45 \ - --hash=sha256:29a225d2d4da37e20c7a246754a64442d0e43e4534b8cc764f89530bb22a4237 \ - --hash=sha256:318a0a73f664113e8d86d0cb0e328e7650e2d7d9c2e045418ab6fb1285831ad3 \ - --hash=sha256:35b037b415ed38369717800250fe5974249525953b46026bef9def20f946dfb0 \ - --hash=sha256:45d6010a6a981f5a2b3411c4e38fbe305a1b46e4b12db3b4914775927dea7ba4 \ - --hash=sha256:4a9a64c064457c1afaf93ee2815fe0f38be6ecbb92806a6a712f12afc3e26cf5 \ - --hash=sha256:4fbd3ff28a7babbfee750684c4f46ba6eedb3bce69365dd146726986b79fa6c1 \ - --hash=sha256:684256c29e3e5d4d0c4d06b772d00574d0dc859dfbb2fd13d318c512b16e1f89 \ - --hash=sha256:6d973735d2067607a32cd182893978755eee829a0dc268087592d3b715e63fad \ - --hash=sha256:73eafd06b158d511f1ec6e5902a45e0ae3b48e745f35e9df97d25f809f537d88 \ - --hash=sha256:74f1e3a460c43aefcb4ff9ef91aac15abc0b42e5233771e1956574d14ba9cac6 \ - --hash=sha256:7ab0f183a6fd2369eef619832eef14f1f2f69c605163c3f2dc41deb799af4a71 \ - --hash=sha256:7e5906860e632bf965e1966c57e6bfc19dcb79dc262f04fdb0a9d7c12147bf69 \ - --hash=sha256:7e74f59e5c9011afa2a9cb7106bb9a59f2a1f74c3d7b272c1b852eb0bc0b8f90 \ - --hash=sha256:7eef2ea508ce41795e20587a5fc868ae4919543097c81a40fbdfd65bc479f54f \ - --hash=sha256:82d68a60a4207826db295b4e80a204c9d392ce78ccc15679195faeb9e29d8388 \ - --hash=sha256:954258873ae0bcc97fb41e48db25284fb73454bfefe27db8ceb89225da5502fb \ - --hash=sha256:a1b897c952baefd72232efaeb3bdb1ca2fa7ae94cbfe68ac21201b03e843190a \ - --hash=sha256:c503b0c0a681e795255a13e5bb4e41615c3b020c1db93b8dfa04cfeb8f19d5a9 \ - --hash=sha256:c58c8e034f94329be4dc0979fba3301005a433dbab42cea0b2c33fd736946872 \ - --hash=sha256:ccaa0f002cfd69c03621ac05243c1a00ed77cee97b363d0108b0e36663e4ca33 \ - --hash=sha256:ce7aa033a3f68beb6732d1450a80c1af29e63e0c2d01abad7918cf2507f72fa6 \ - --hash=sha256:d00d8c4ca1afab90e478b79679dd53787082c6da8d2f4fdc7667a4440d1e1a7a \ - --hash=sha256:d1c08bc9bb6bd79732a26710a99588b5e9b67b668e165dd609704b876f41baab \ - --hash=sha256:d48e3ee13d7678c24c22f19d441ad6bc220a79f23662d03ff36fae0d62efdb59 \ - --hash=sha256:dc38137ba2f43794c488615aafa2eefd0cc142f484a8274d4c827ed7a031a1e2 \ - --hash=sha256:df208cd53c7382a4cc8470d39a92fc73b3cf2f0f3379d6c88bb556823a26ccb7 \ - --hash=sha256:f0a03650ca60c4c5774cbe21333905b95f2f5abd98ea5a3dbf28d93f2a7a5a84 \ - --hash=sha256:f7e12fdd28cc9f39a463d8df5172d698c64a8ed385a21d98e7092fd8308a139a - # via -r requirements.in greenlet==3.5.0 \ --hash=sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846 \ --hash=sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4 \ @@ -546,44 +509,3 @@ urllib3==2.7.0 \ --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \ --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 # via requests -zope-event==6.2 \ - --hash=sha256:5e755153ac4faf64c10a4b6dd3307680166a3edf65b38df22df592610f8fa874 \ - --hash=sha256:b97d5d6327067ee6b9dfcbdf606ade9ade70991e19c162e808ea39e5fcf0f8d3 - # via gevent -zope-interface==8.4 \ - --hash=sha256:049ba3c7b38cc400ae08e011617635706e0f442e1d075db1b015246fcbf6091e \ - --hash=sha256:04c2c9b58e9c177628715d85e94834efa807c1f9f0a2f57ae0f7b553e8266ac4 \ - --hash=sha256:0d88c1f106a4f06e074a3ada2d20f4a602e3f2871c4f55726ed5d91e94ec19b1 \ - --hash=sha256:265bad2df2ec070f23ff863249a89b408b11908fd4207662781fd18e3c6fc912 \ - --hash=sha256:29f09ec8bda65f7b30294328070070a2590b90f252f834ee0817cdb0e2c35f6a \ - --hash=sha256:2bc388cebcb753d21eaf2a0481fd6f0ce6840a47300a40dcec0b56bac27d0f97 \ - --hash=sha256:2e9e4aa33b76877af903d5532545e64d24ade0f6f80d9d1a31e6efcea76a60bc \ - --hash=sha256:36c575356732d59ffd3279ad67e302a6fe517e67db5b061b36b377ee0fa016c4 \ - --hash=sha256:376d0ef005a131b349e2088e302aa094fa23c826d2ec8a7db4b00fb33c71e0d9 \ - --hash=sha256:3e5866917ccb57d929e515a1136d729bd3fa4f367965fb16e38a4bc72cb05521 \ - --hash=sha256:415de524326ddd61a78f0816f65942fa1aa35dced19e72579ad30dd106ce523e \ - --hash=sha256:4713bf651ec36e7eea49d2ace4f0e89bec2b33a339674874b1121f2537edc62a \ - --hash=sha256:4ae6a1e111642dbf724f635424dcaf5a5c8abbde49eac3f452f5323ffaa10232 \ - --hash=sha256:5ec1a56b6cf9a757cbbce9da38284a01473b92b96c1517eabd99150f51f1bb69 \ - --hash=sha256:7cbb887fdbfaacb4c362dbb487033551646e28013ad5ffe72e96eb260003a1a1 \ - --hash=sha256:81ed23698bfb588c48b1756129814b890febac971ff6c8a414f82601773145bb \ - --hash=sha256:84064876ed96ddd0744e3ad5d37134c758d77885e54113567792671405a02bac \ - --hash=sha256:8544081e32b515bbaf1c6339eef06b23ed470cf4876ff2f575803f82a744cc43 \ - --hash=sha256:892b4b5350e58d6914858f58eb85d39fe9b992640ac6ece695f46c978046554d \ - --hash=sha256:8b302f955c36e924e1f4fe70dd9105ff06235857861c6ae72c3b10b016aeee99 \ - --hash=sha256:8b733af6e89a2b0b8edf5ff7a37988fe4e1788806e84e72127b88c47858f0da6 \ - --hash=sha256:8d683267a6243526869cb69677dcfc663416d5f87904c1576ddec6e420687d51 \ - --hash=sha256:9c4ac009c2c8e43283842f80387c4d4b41bcbc293391c3b9ab71532ae1ccc301 \ - --hash=sha256:9dbee7925a23aa6349738892c911019d4095a96cff487b743482073ecbc174a8 \ - --hash=sha256:a5638c6be715116d3453e6d099c299c6844d54810de7445ce116424e905ede06 \ - --hash=sha256:b8147b40bfcd53803870a9519e0879ff066aeecc2fcff8295663c1b17fc38dc2 \ - --hash=sha256:caffd033b27e311b45e15f01923cc9e73c6bfd8e843b4532e29b59ee432bf893 \ - --hash=sha256:cd55965d715413038774aead54851bc3dbdd74a69f3ce30252182a94407b9905 \ - --hash=sha256:d934497c4b72d5f528d2b5ebe9b8b5a7004b5877948ebd4ea00c2432fb27178f \ - --hash=sha256:e0b9d7e958657fad414f8272afcdf0b8a873fbbb2bb6a6287232d2f11a232bf8 \ - --hash=sha256:e195e76767847afb5379ffd67690c17d3c6efdab58dc0e477cf81ac94d5a5a15 \ - --hash=sha256:eef0a49e041f4dc4d2a6ab894b4fd0c5354e0e8037e731fb953531e59b0d3d33 \ - --hash=sha256:f00fd65343d2a241a2b17688a12f5e815aa704ed64f9ca375de5f9e0ae9c9bda \ - --hash=sha256:f1f854bef8bc137519e4413bcc1322d55faad28b20b3ca39f7bec49d2f1b26df \ - --hash=sha256:fa0a26d5767087170b3da9ff503221d535ea266bf61b522d0afa2590fd05db0a - # via gevent From 6266bfdf73d53fdac3b2e5ddbd9b69cf1013a331 Mon Sep 17 00:00:00 2001 From: aakugan <164153358+aakugan@users.noreply.github.com> Date: Thu, 14 May 2026 16:58:38 +0530 Subject: [PATCH 307/750] build: fix build for contrib arm boring ssl fips (#45043) ## Commit Message build: fix build for contrib arm boring ssl fips ## Additional Description Building envoy on arm with boring ssl fips fails due to the following: ``` envoybuild@ip-10-92-23-225:/workspaces/envoy$ bazel cquery //contrib/exe:envoy-static --config=boringssl-fips --platforms=//bazel/platforms:linux_arm64 ERROR: /workspaces/envoy/contrib/exe/BUILD:25:16: Illegal ambiguous match on configurable attribute "deps" in //contrib/exe:envoy-static: //bazel:linux_aarch64 //bazel:using_boringssl_fips Multiple matches are not allowed unless one is unambiguously more specialized or they resolve to the same value. See https://bazel.build/reference/be/functions#select. ERROR: Analysis of target '//contrib/exe:envoy-static' failed; build aborted INFO: Elapsed time: 0.883s INFO: 0 processes. ERROR: Build did NOT complete successfully ``` Fix is exactly the same as the aws lc one: #44661 After the change it seems to work ``` envoybuild@ip-10-92-23-225:/workspaces/envoy$ bazel cquery //contrib/exe:envoy-static --config=boringssl-fips --platforms=//bazel/platforms:linux_arm64 INFO: Analyzed target //contrib/exe:envoy-static (1463 packages loaded, 81211 targets configured). INFO: Found 1 target... //contrib/exe:envoy-static (39afe8a) INFO: Elapsed time: 2.313s, Critical Path: 0.00s INFO: 0 processes. INFO: Build completed successfully, 0 total actions ``` ## Risk level Low existing builds should not be affected due to the change. Signed-off-by: aakugan --- bazel/SSL.md | 2 +- changelogs/current.yaml | 5 +++++ contrib/BUILD | 14 ++++++++++++++ contrib/all_contrib_extensions.bzl | 2 +- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/bazel/SSL.md b/bazel/SSL.md index c0a680ba97328..07031e746bcb4 100644 --- a/bazel/SSL.md +++ b/bazel/SSL.md @@ -33,7 +33,7 @@ not be changed on the release branch unless a bug or security vulnerability whic bazel build --config=boringssl-fips //source/exe:envoy-static ``` -- **Supported architectures:** Linux x86_64 only +- **Supported architectures:** Linux x86_64, aarch64 - **Version string:** `BoringSSL-FIPS` (visible in `envoy --version`) ### AWS-LC FIPS diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 4498f1170b362..b7c4e52da8c64 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -92,6 +92,11 @@ bug_fixes: resulting binary "plaintext" was written back into the ``Cookie:`` request header and tripped a ``HeaderString`` validation assert. Such plaintexts are now rejected and the original cookie value is preserved, matching the behavior already documented for the explicit decryption-failure case. +- area: build + change: | + Fixed ``Illegal ambiguous match`` error when building contrib targets with ``--config=boringssl-fips`` + on aarch64 by restricting the ``using_boringssl_fips`` branch of ``SELECTED_CONTRIB_EXTENSIONS`` to + ``linux_x86_64``. Mirrors the approach taken by #44661 for ``aws-lc``. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/contrib/BUILD b/contrib/BUILD index 3b31dcffb35e5..41ad533be507a 100644 --- a/contrib/BUILD +++ b/contrib/BUILD @@ -23,6 +23,20 @@ selects.config_setting_group( visibility = ["//visibility:public"], ) +# Disambiguation for SELECTED_CONTRIB_EXTENSIONS' select, where both +# "//bazel:linux_aarch64" and "//bazel:using_boringssl_fips" otherwise match for an +# arm64 boringssl fips build. Restricting the boringssl fips branch to x86_64 lets the +# aarch64 branch win on arm64; arm64 + boringssl fips builds then fall through +# to their platform skip lists, which already exclude the Intel-specific contribs. +selects.config_setting_group( + name = "using_boringssl_fips_on_linux_x86_64", + match_all = [ + "//bazel:using_boringssl_fips", + "//bazel:linux_x86_64", + ], + visibility = ["//visibility:public"], +) + exports_files([ "extensions_metadata.yaml", "contrib_build_config.bzl", diff --git a/contrib/all_contrib_extensions.bzl b/contrib/all_contrib_extensions.bzl index bb5a765ba5eda..cf04b7b0d75e6 100644 --- a/contrib/all_contrib_extensions.bzl +++ b/contrib/all_contrib_extensions.bzl @@ -56,6 +56,6 @@ SELECTED_CONTRIB_EXTENSIONS = select({ "//bazel:linux_aarch64": envoy_all_contrib_extensions(ARM64_SKIP_CONTRIB_TARGETS), "//bazel:linux_ppc": envoy_all_contrib_extensions(PPC_SKIP_CONTRIB_TARGETS), "//contrib:using_aws_lc_on_linux_x86_64": envoy_all_contrib_extensions(AWS_LC_SKIP_CONTRIB_TARGETS), - "//bazel:using_boringssl_fips": envoy_all_contrib_extensions(BORINGSSL_FIPS_SKIP_CONTRIB_TARGETS), + "//contrib:using_boringssl_fips_on_linux_x86_64": envoy_all_contrib_extensions(BORINGSSL_FIPS_SKIP_CONTRIB_TARGETS), "//conditions:default": envoy_all_contrib_extensions(X86_SKIP_CONTRIB_TARGETS), }) From d19f8e87067523d9480a5ca6957ef45c176587d2 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 14 May 2026 15:13:53 +0100 Subject: [PATCH 308/750] github/ci: Continue on docker login error (#45066) Signed-off-by: Ryan Northey --- .github/workflows/_run.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 3bab87a3a75d5..a7292a4b6b0d4 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -403,6 +403,7 @@ jobs: with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.dockerhub-token }} + continue-on-error: true # NOTE: This is where untrusted code can be run!!! # Only post-failure notification steps (which don't use any repo code) should follow this step. From 4f19551ad2417d4e8cf77d64ce25c3ba5a95ca78 Mon Sep 17 00:00:00 2001 From: botengyao Date: Thu, 14 May 2026 11:41:07 -0400 Subject: [PATCH 309/750] =?UTF-8?q?Revert=20"http:=20wire=20ConnectionLife?= =?UTF-8?q?timeCallbacks=20into=20pool=20connection=20e=E2=80=A6=20(#45056?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …vents (#43702)" This reverts commit 5a363b3671386273bb17d0e0ce84dccbd3bc6ae6. The above PR is causing multiple issues on the callback management, and the impl of `onConnectionOpen()` and `onConnectionDrain()`. Will revert now until we fully fix or understand it. Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Boteng Yao --- envoy/http/conn_pool.h | 4 - source/common/http/codec_client.h | 3 - source/common/http/conn_pool_base.cc | 31 --- source/common/http/conn_pool_base.h | 12 - source/common/http/conn_pool_grid.cc | 21 -- source/common/http/conn_pool_grid.h | 13 -- .../common/upstream/cluster_manager_impl.cc | 2 - test/common/http/conn_pool_grid_test.cc | 215 ------------------ test/common/http/http2/BUILD | 1 - test/common/http/http2/conn_pool_test.cc | 181 --------------- test/common/http/http3/BUILD | 1 - test/common/http/http3/conn_pool_test.cc | 57 ----- .../upstream/cluster_manager_impl_test.cc | 23 -- .../cluster_manager_lifecycle_test.cc | 2 - test/mocks/http/conn_pool.h | 12 - 15 files changed, 578 deletions(-) diff --git a/envoy/http/conn_pool.h b/envoy/http/conn_pool.h index e6a5f9eaef292..f3656893bfa05 100644 --- a/envoy/http/conn_pool.h +++ b/envoy/http/conn_pool.h @@ -2,7 +2,6 @@ #include #include -#include #include "envoy/common/conn_pool.h" #include "envoy/common/pure.h" @@ -120,9 +119,6 @@ class Instance : public Envoy::ConnectionPool::Instance, public Event::DeferredD * @return absl::string_view a protocol description for logging. */ virtual absl::string_view protocolDescription() const PURE; - - virtual void setLifetimeCallbacks(OptRef callbacks, - std::vector hash_key) PURE; }; using InstancePtr = std::unique_ptr; diff --git a/source/common/http/codec_client.h b/source/common/http/codec_client.h index 0eec3f2618733..1a94807107b96 100644 --- a/source/common/http/codec_client.h +++ b/source/common/http/codec_client.h @@ -155,9 +155,6 @@ class CodecClient : protected Logger::Loggable, bool connectCalled() const { return connect_called_; } - // Get the underlying connection of the codec client for lifetimeCallbacks. - const Network::Connection& connection() const { return *connection_; } - protected: /** * Create a codec client and connect to a remote host/port. diff --git a/source/common/http/conn_pool_base.cc b/source/common/http/conn_pool_base.cc index 1e89fcb7f9f90..4de1405a92d1a 100644 --- a/source/common/http/conn_pool_base.cc +++ b/source/common/http/conn_pool_base.cc @@ -104,24 +104,6 @@ void HttpConnPoolImplBase::onPoolReady(Envoy::ConnectionPool::ActiveClient& clie http_client->codec_client_->protocol()); } -void HttpConnPoolImplBase::setLifetimeCallbacks( - OptRef callbacks, std::vector hash_key) { - callbacks_ = callbacks; - hash_key_ = std::move(hash_key); -} - -void HttpConnPoolImplBase::onConnectionOpen(const Network::Connection& connection) { - if (callbacks_.has_value()) { - callbacks_->onConnectionOpen(*this, hash_key_, connection); - } -} - -void HttpConnPoolImplBase::onConnectionDraining(const Network::Connection& connection) { - if (callbacks_.has_value()) { - callbacks_->onConnectionDraining(*this, hash_key_, connection); - } -} - // All streams are 2^31. Client streams are half that, minus stream 0. Just to be on the safe // side we do 2^29. constexpr uint32_t DEFAULT_MAX_STREAMS = 1U << 29; @@ -133,7 +115,6 @@ void MultiplexedActiveClientBase::onGoAway(Http::GoAwayErrorCode) { if (codec_client_->numActiveRequests() == 0) { codec_client_->close(); } else { - parent().onConnectionDraining(codec_client_->connection()); parent_.transitionActiveClientState(*this, ActiveClient::State::Draining); } } @@ -241,17 +222,5 @@ MultiplexedActiveClientBase::newStreamEncoder(ResponseDecoderHandlePtr response_ return codec_client_->newStream(std::move(response_decoder_handle)); } -void MultiplexedActiveClientBase::onEvent(Network::ConnectionEvent event) { - if (event == Network::ConnectionEvent::Connected || - event == Network::ConnectionEvent::ConnectedZeroRtt) { - parent().onConnectionOpen(codec_client_->connection()); - } else if (event == Network::ConnectionEvent::LocalClose || - event == Network::ConnectionEvent::RemoteClose) { - parent().onConnectionDraining(codec_client_->connection()); - } - - ActiveClient::onEvent(event); -} - } // namespace Http } // namespace Envoy diff --git a/source/common/http/conn_pool_base.h b/source/common/http/conn_pool_base.h index 4cf74e1e6a7d0..014c7a76a9e4b 100644 --- a/source/common/http/conn_pool_base.h +++ b/source/common/http/conn_pool_base.h @@ -98,13 +98,6 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, virtual absl::optional& origin() { return origin_; } virtual Http::HttpServerPropertiesCacheSharedPtr cache() { return nullptr; } - void setLifetimeCallbacks(OptRef callbacks, - std::vector hash_key) override; - - void onConnectionOpen(const Network::Connection& connection); - - void onConnectionDraining(const Network::Connection& connection); - protected: friend class ActiveClient; @@ -114,8 +107,6 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, private: absl::optional origin_; - OptRef callbacks_; - std::vector hash_key_; }; // An implementation of Envoy::ConnectionPool::ActiveClient for HTTP/1.1 and HTTP/2 @@ -251,9 +242,6 @@ class MultiplexedActiveClientBase : public CodecClientCallbacks, void onGoAway(Http::GoAwayErrorCode error_code) override; void onSettings(ReceivedSettings& settings) override; - // Override to provide the lifetimeCallbacks. - void onEvent(Network::ConnectionEvent event) override; - private: bool closed_with_active_rq_{}; }; diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index fc30d1057c0c7..a7ba00f208b5d 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -417,7 +417,6 @@ ConnectionPool::InstancePtr ConnectivityGrid::createHttp3Pool(bool attempt_alter void ConnectivityGrid::setupPool(ConnectionPool::Instance& pool) { pool.addIdleCallback([this]() { onIdleReceived(); }); - pool.setLifetimeCallbacks(makeOptRefFromPtr(this), hash_key_); } bool ConnectivityGrid::hasActiveConnections() const { @@ -611,25 +610,5 @@ void ConnectivityGrid::onZeroRttHandshakeFailed() { getHttp3StatusTracker().markHttp3FailedRecently(); } -void ConnectivityGrid::onConnectionOpen(ConnectionPool::Instance&, std::vector&, - const Network::Connection& connection) { - if (callbacks_.has_value()) { - callbacks_->onConnectionOpen(*this, hash_key_, connection); - } -} - -void ConnectivityGrid::onConnectionDraining(ConnectionPool::Instance&, std::vector&, - const Network::Connection& connection) { - if (callbacks_.has_value()) { - callbacks_->onConnectionDraining(*this, hash_key_, connection); - } -} - -void ConnectivityGrid::setLifetimeCallbacks( - OptRef callbacks, std::vector hash_key) { - callbacks_ = callbacks; - hash_key_ = std::move(hash_key); -} - } // namespace Http } // namespace Envoy diff --git a/source/common/http/conn_pool_grid.h b/source/common/http/conn_pool_grid.h index 76fd1f90e37a2..b8f1a09a106b3 100644 --- a/source/common/http/conn_pool_grid.h +++ b/source/common/http/conn_pool_grid.h @@ -30,7 +30,6 @@ namespace Http { // the host's address list. class ConnectivityGrid : public ConnectionPool::Instance, public Http3::PoolConnectResultCallback, - public ConnectionPool::ConnectionLifetimeCallbacks, protected Logger::Loggable { public: struct ConnectivityOptions { @@ -229,15 +228,6 @@ class ConnectivityGrid : public ConnectionPool::Instance, void onHandshakeComplete() override; void onZeroRttHandshakeFailed() override; - // ConnectionPool::ConnectionLifetimeCallbacks - void onConnectionOpen(ConnectionPool::Instance& pool, std::vector& hash_key, - const Network::Connection& connection) override; - void onConnectionDraining(ConnectionPool::Instance& pool, std::vector& hash_key, - const Network::Connection& connection) override; - - void setLifetimeCallbacks(OptRef callbacks, - std::vector hash_key) override; - protected: // Set the required idle callback on the pool. void setupPool(ConnectionPool::Instance& pool); @@ -318,9 +308,6 @@ class ConnectivityGrid : public ConnectionPool::Instance, bool deferred_deleting_{}; OptRef network_observer_registry_; - - OptRef callbacks_; - std::vector hash_key_; }; } // namespace Http diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 23676d4eb8630..b21d23a497fd8 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -2023,8 +2023,6 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::httpConnPoolImp parent.httpConnPoolIsIdle(host, priority, hash_key); }); - pool->setLifetimeCallbacks(lb_->lifetimeCallbacks(), hash_key); - return pool; }); diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index 80662a6ecfeaf..f52d88c3ed6ca 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -85,13 +85,6 @@ class ConnectivityGridForTest : public ConnectivityGrid { void createHttp3AlternatePool() { ConnectionPool::MockInstance* instance = new NiceMock(); - EXPECT_CALL(*instance, setLifetimeCallbacks(_, _)) - .Times(testing::AtLeast(1)) - .WillRepeatedly( - Invoke([this, instance](OptRef cb, - std::vector key) { - captured_lifetime_callbacks_[instance] = {cb, std::move(key)}; - })); setupPool(*instance); http3_alternate_pool_.reset(instance); pools_.push_back(instance); @@ -127,13 +120,6 @@ class ConnectivityGridForTest : public ConnectivityGrid { return cancel_; })); EXPECT_CALL(*instance, protocolDescription()).Times(AnyNumber()).WillRepeatedly(Return(type)); - EXPECT_CALL(*instance, setLifetimeCallbacks(_, _)) - .Times(testing::AtLeast(1)) - .WillRepeatedly( - Invoke([this, instance](OptRef cb, - std::vector key) { - captured_lifetime_callbacks_[instance] = {cb, std::move(key)}; - })); return absl::WrapUnique(instance); } @@ -162,21 +148,6 @@ class ConnectivityGridForTest : public ConnectivityGrid { return http3_status_tracker.isHttp3Confirmed(); } - // Simulate an inner pool firing onConnectionOpen to the grid, - // using the callbacks that setupPool wired via setLifetimeCallbacks. - void simulateConnectionOpen(ConnectionPool::Instance& pool, - const Network::Connection& connection) { - auto it = captured_lifetime_callbacks_.find(&pool); - ASSERT(it != captured_lifetime_callbacks_.end()); - it->second.callbacks->onConnectionOpen(pool, it->second.hash_key, connection); - } - void simulateConnectionDraining(ConnectionPool::Instance& pool, - const Network::Connection& connection) { - auto it = captured_lifetime_callbacks_.find(&pool); - ASSERT(it != captured_lifetime_callbacks_.end()); - it->second.callbacks->onConnectionDraining(pool, it->second.hash_key, connection); - } - StreamInfo::MockStreamInfo* info_; NiceMock* encoder_; void setDestroying() { destroying_ = true; } @@ -189,12 +160,6 @@ class ConnectivityGridForTest : public ConnectivityGrid { bool alternate_immediate_{true}; bool alternate_failure_{true}; - - struct CapturedCallbacks { - OptRef callbacks; - std::vector hash_key; - }; - absl::flat_hash_map captured_lifetime_callbacks_; }; namespace { @@ -1857,186 +1822,6 @@ TEST_F(ConnectivityGridTest, ConnectionCloseDuringAsyncConnect) { #endif -// Test 1: Without lifetime callbacks, all pool types (H3, H3 alt, H2) work normally. -// setupPool unconditionally wires the grid as the inner pool's callback target, -// so this verifies no crashes or unexpected behavior when no outer callbacks are set. -TEST_F(ConnectivityGridTest, PoolsWorkWithoutLifetimeCallbacks) { - initialize(); - addHttp3AlternateProtocol(); - - // Create the HTTP/3 pool via newStream, no lifetime callbacks set on the grid. - grid_->immediate_success_ = true; - auto* cancel = grid_->newStream(decoder_, callbacks_, - {/*can_send_early_data_=*/false, - /*can_use_http3_=*/true}); - EXPECT_EQ(cancel, nullptr); - EXPECT_NE(grid_->http3Pool(), nullptr); - - // Inner pool fires events to the grid via captured callbacks; no outer callbacks, no crash. - NiceMock connection; - grid_->simulateConnectionOpen(*grid_->http3Pool(), connection); - grid_->simulateConnectionDraining(*grid_->http3Pool(), connection); -} - -TEST_F(ConnectivityGridTest, H2PoolWorksWithoutLifetimeCallbacks) { - initialize(); - addHttp3AlternateProtocol(); - - // Create H3 pool, then fail it to trigger H2 pool creation. - EXPECT_NE(grid_->newStream(decoder_, callbacks_, - {/*can_send_early_data_=*/false, - /*can_use_http3_=*/true}), - nullptr); - EXPECT_NE(grid_->http3Pool(), nullptr); - - grid_->immediate_success_ = true; - grid_->callbacks()->onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, - "reason", host_); - EXPECT_NE(grid_->http2Pool(), nullptr); - - NiceMock connection; - grid_->simulateConnectionOpen(*grid_->http2Pool(), connection); - grid_->simulateConnectionDraining(*grid_->http2Pool(), connection); -} - -TEST_F(ConnectivityGridTest, H3AltPoolWorksWithoutLifetimeCallbacks) { - initialize(); - addHttp3AlternateProtocol(); - grid_->createHttp3AlternatePool(); - - EXPECT_NE(grid_->alternate(), nullptr); - - NiceMock connection; - grid_->simulateConnectionOpen(*grid_->alternate(), connection); - grid_->simulateConnectionDraining(*grid_->alternate(), connection); -} - -// Test 2: With lifetime callbacks set, verify events from inner pools flow through -// to the mock load balancer with the grid's identity (not the inner pool's). -TEST_F(ConnectivityGridTest, LifetimeCallbacksH3Only) { - initialize(); - addHttp3AlternateProtocol(); - - ConnectionPool::MockConnectionLifetimeCallbacks lifetime_callbacks; - std::vector hash_key = {1, 2, 3}; - grid_->setLifetimeCallbacks( - makeOptRef(lifetime_callbacks), hash_key); - - EXPECT_NE(grid_->newStream(decoder_, callbacks_, - {/*can_send_early_data_=*/false, - /*can_use_http3_=*/true}), - nullptr); - EXPECT_NE(grid_->http3Pool(), nullptr); - - NiceMock connection; - - EXPECT_CALL(lifetime_callbacks, - onConnectionOpen(testing::Ref(*grid_), testing::ContainerEq(hash_key), - testing::Ref(connection))); - grid_->simulateConnectionOpen(*grid_->http3Pool(), connection); - - EXPECT_CALL(lifetime_callbacks, - onConnectionDraining(testing::Ref(*grid_), testing::ContainerEq(hash_key), - testing::Ref(connection))); - grid_->simulateConnectionDraining(*grid_->http3Pool(), connection); -} - -TEST_F(ConnectivityGridTest, LifetimeCallbacksH3AndH2) { - initialize(); - addHttp3AlternateProtocol(); - - ConnectionPool::MockConnectionLifetimeCallbacks lifetime_callbacks; - std::vector hash_key = {4, 5, 6}; - grid_->setLifetimeCallbacks( - makeOptRef(lifetime_callbacks), hash_key); - - // Create H3 pool. - EXPECT_NE(grid_->newStream(decoder_, callbacks_, - {/*can_send_early_data_=*/false, - /*can_use_http3_=*/true}), - nullptr); - EXPECT_NE(grid_->http3Pool(), nullptr); - - // Fail H3 to create H2 pool. - grid_->callbacks()->onPoolFailure(ConnectionPool::PoolFailureReason::LocalConnectionFailure, - "reason", host_); - EXPECT_NE(grid_->http2Pool(), nullptr); - - NiceMock h3_conn; - NiceMock h2_conn; - - // Events from H3 pool forwarded with grid identity. - EXPECT_CALL(lifetime_callbacks, - onConnectionOpen(testing::Ref(*grid_), testing::ContainerEq(hash_key), - testing::Ref(h3_conn))); - grid_->simulateConnectionOpen(*grid_->http3Pool(), h3_conn); - - // Events from H2 pool also forwarded with grid identity. - EXPECT_CALL(lifetime_callbacks, - onConnectionOpen(testing::Ref(*grid_), testing::ContainerEq(hash_key), - testing::Ref(h2_conn))); - grid_->simulateConnectionOpen(*grid_->http2Pool(), h2_conn); - - // Draining from H2 pool forwarded with grid identity. - EXPECT_CALL(lifetime_callbacks, - onConnectionDraining(testing::Ref(*grid_), testing::ContainerEq(hash_key), - testing::Ref(h2_conn))); - grid_->simulateConnectionDraining(*grid_->http2Pool(), h2_conn); -} - -TEST_F(ConnectivityGridTest, LifetimeCallbacksH3Alt) { - initialize(); - addHttp3AlternateProtocol(); - - ConnectionPool::MockConnectionLifetimeCallbacks lifetime_callbacks; - std::vector hash_key = {9, 10}; - grid_->setLifetimeCallbacks( - makeOptRef(lifetime_callbacks), hash_key); - - grid_->createHttp3AlternatePool(); - EXPECT_NE(grid_->alternate(), nullptr); - - NiceMock connection; - - // Events from the alternate H3 pool forwarded with grid identity. - EXPECT_CALL(lifetime_callbacks, - onConnectionOpen(testing::Ref(*grid_), testing::ContainerEq(hash_key), - testing::Ref(connection))); - grid_->simulateConnectionOpen(*grid_->alternate(), connection); -} - -// Verify that setting lifetime callbacks after pools already exist still works: -// events from inner pools are visible at the load balancer. -TEST_F(ConnectivityGridTest, LifetimeCallbacksSetAfterPoolCreation) { - initialize(); - addHttp3AlternateProtocol(); - - // Create the HTTP/3 pool before setting any callbacks. - EXPECT_NE(grid_->newStream(decoder_, callbacks_, - {/*can_send_early_data_=*/false, - /*can_use_http3_=*/true}), - nullptr); - EXPECT_NE(grid_->http3Pool(), nullptr); - - // Set callbacks after pool already exists. - ConnectionPool::MockConnectionLifetimeCallbacks lifetime_callbacks; - std::vector hash_key = {11, 12}; - grid_->setLifetimeCallbacks( - makeOptRef(lifetime_callbacks), hash_key); - - // Events from the pre-existing H3 pool are visible at the load balancer. - NiceMock connection; - EXPECT_CALL(lifetime_callbacks, - onConnectionOpen(testing::Ref(*grid_), testing::ContainerEq(hash_key), - testing::Ref(connection))); - grid_->simulateConnectionOpen(*grid_->http3Pool(), connection); - - EXPECT_CALL(lifetime_callbacks, - onConnectionDraining(testing::Ref(*grid_), testing::ContainerEq(hash_key), - testing::Ref(connection))); - grid_->simulateConnectionDraining(*grid_->http3Pool(), connection); -} - } // namespace } // namespace Http } // namespace Envoy diff --git a/test/common/http/http2/BUILD b/test/common/http/http2/BUILD index 57c45eb774918..93cd8f69c37b4 100644 --- a/test/common/http/http2/BUILD +++ b/test/common/http/http2/BUILD @@ -68,7 +68,6 @@ envoy_cc_test( "//test/common/http:common_lib", "//test/common/upstream:utility_lib", "//test/mocks/event:event_mocks", - "//test/mocks/http:conn_pool_mocks", "//test/mocks/http:http_mocks", "//test/mocks/http:http_server_properties_cache_mocks", "//test/mocks/network:network_mocks", diff --git a/test/common/http/http2/conn_pool_test.cc b/test/common/http/http2/conn_pool_test.cc index d3632968c4137..e082c1274be2c 100644 --- a/test/common/http/http2/conn_pool_test.cc +++ b/test/common/http/http2/conn_pool_test.cc @@ -11,7 +11,6 @@ #include "test/common/http/common.h" #include "test/common/upstream/utility.h" #include "test/mocks/event/mocks.h" -#include "test/mocks/http/conn_pool.h" #include "test/mocks/http/http_server_properties_cache.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" @@ -2088,186 +2087,6 @@ TEST_F(Http2ConnPoolImplTest, RequestTrackingConnectionFailureNoMetric) { EXPECT_EQ(1U, cluster_->traffic_stats_->upstream_cx_destroy_remote_.value()); } -// Normal pool lifecycle works without any lifetime callbacks set. -TEST_F(Http2ConnPoolImplTest, ConnectionLifecycleNoopsWithoutCallbacks) { - InSequence s; - - expectClientCreate(); - ActiveTestRequest r1(*this, 0, false); - expectClientConnect(0, r1); - completeRequestCloseUpstream(0, r1); -} - -// Connected event fires onConnectionOpen with correct pool, hash_key, and connection args. -TEST_F(Http2ConnPoolImplTest, ConnectedEventFiresOpenCallbackWithCorrectArgs) { - InSequence s; - - ConnectionPool::MockConnectionLifetimeCallbacks lifetime_callbacks; - std::vector hash_key = {1, 2, 3}; - pool_->setLifetimeCallbacks( - makeOptRef(lifetime_callbacks), hash_key); - - expectClientCreate(); - ActiveTestRequest r1(*this, 0, false); - - EXPECT_CALL(lifetime_callbacks, - onConnectionOpen(testing::Ref(*pool_), testing::ContainerEq(hash_key), - testing::Ref(*test_clients_[0].connection_))); - expectClientConnect(0, r1); - - completeRequest(r1); - - EXPECT_CALL(lifetime_callbacks, - onConnectionDraining(testing::Ref(*pool_), testing::ContainerEq(hash_key), - testing::Ref(*test_clients_[0].connection_))); - EXPECT_CALL(*this, onClientDestroy()); - test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); - dispatcher_.clearDeferredDeleteList(); -} - -// RemoteClose fires onConnectionDraining with correct args. -TEST_F(Http2ConnPoolImplTest, RemoteCloseFiresDrainingCallbackWithCorrectArgs) { - InSequence s; - - ConnectionPool::MockConnectionLifetimeCallbacks lifetime_callbacks; - std::vector hash_key = {4, 5, 6}; - pool_->setLifetimeCallbacks( - makeOptRef(lifetime_callbacks), hash_key); - - expectClientCreate(); - ActiveTestRequest r1(*this, 0, false); - - EXPECT_CALL(lifetime_callbacks, onConnectionOpen(_, _, _)); - expectClientConnect(0, r1); - - completeRequest(r1); - - EXPECT_CALL(lifetime_callbacks, - onConnectionDraining(testing::Ref(*pool_), testing::ContainerEq(hash_key), - testing::Ref(*test_clients_[0].connection_))); - EXPECT_CALL(*this, onClientDestroy()); - test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); - dispatcher_.clearDeferredDeleteList(); -} - -// LocalClose fires onConnectionDraining with correct args. -TEST_F(Http2ConnPoolImplTest, LocalCloseFiresDrainingCallbackWithCorrectArgs) { - InSequence s; - - ConnectionPool::MockConnectionLifetimeCallbacks lifetime_callbacks; - std::vector hash_key = {4, 5, 6}; - pool_->setLifetimeCallbacks( - makeOptRef(lifetime_callbacks), hash_key); - - expectClientCreate(); - ActiveTestRequest r1(*this, 0, false); - - EXPECT_CALL(lifetime_callbacks, onConnectionOpen(_, _, _)); - expectClientConnect(0, r1); - - completeRequest(r1); - - EXPECT_CALL(lifetime_callbacks, - onConnectionDraining(testing::Ref(*pool_), testing::ContainerEq(hash_key), - testing::Ref(*test_clients_[0].connection_))); - EXPECT_CALL(*this, onClientDestroy()); - test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::LocalClose); - dispatcher_.clearDeferredDeleteList(); -} - -// GOAWAY with an active stream fires onConnectionDraining once from the GOAWAY handler -// and again when the connection closes after the stream completes. -TEST_F(Http2ConnPoolImplTest, GoAwayWithActiveStreamFiresDrainingCallback) { - InSequence s; - - ConnectionPool::MockConnectionLifetimeCallbacks lifetime_callbacks; - std::vector hash_key = {7, 8, 9}; - pool_->setLifetimeCallbacks( - makeOptRef(lifetime_callbacks), hash_key); - - expectClientCreate(); - ActiveTestRequest r1(*this, 0, false); - - EXPECT_CALL(lifetime_callbacks, onConnectionOpen(_, _, _)); - expectClientConnect(0, r1); - - EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - EXPECT_TRUE( - r1.callbacks_.outer_encoder_ - ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) - .ok()); - - // GOAWAY with active request fires draining from the goaway handler. - EXPECT_CALL(lifetime_callbacks, - onConnectionDraining(testing::Ref(*pool_), testing::ContainerEq(hash_key), - testing::Ref(*test_clients_[0].connection_))); - test_clients_[0].codec_client_->raiseGoAway(Http::GoAwayErrorCode::NoError); - - // Completing the request triggers close on the draining connection, firing draining again. - EXPECT_CALL(lifetime_callbacks, onConnectionDraining(_, _, _)); - EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); - EXPECT_CALL(*this, onClientDestroy()); - r1.inner_decoder_->decodeHeaders( - ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); - dispatcher_.clearDeferredDeleteList(); -} - -// GOAWAY on an idle connection closes immediately; draining fires from the close event only. -TEST_F(Http2ConnPoolImplTest, GoAwayIdleConnectionFiresDrainingDuetoClose) { - InSequence s; - - ConnectionPool::MockConnectionLifetimeCallbacks lifetime_callbacks; - std::vector hash_key = {10, 11, 12}; - pool_->setLifetimeCallbacks( - makeOptRef(lifetime_callbacks), hash_key); - - expectClientCreate(); - ActiveTestRequest r1(*this, 0, false); - - EXPECT_CALL(lifetime_callbacks, onConnectionOpen(_, _, _)); - expectClientConnect(0, r1); - - completeRequest(r1); - - EXPECT_CALL(lifetime_callbacks, - onConnectionDraining(testing::Ref(*pool_), testing::ContainerEq(hash_key), - testing::Ref(*test_clients_[0].connection_))); - EXPECT_CALL(*this, onClientDestroy()); - test_clients_[0].codec_client_->raiseGoAway(Http::GoAwayErrorCode::NoError); - dispatcher_.clearDeferredDeleteList(); -} - -// Calling setLifetimeCallbacks a second time replaces the first; only the latest fires. -TEST_F(Http2ConnPoolImplTest, SetLifetimeCallbacksReplacesExisting) { - InSequence s; - - ConnectionPool::MockConnectionLifetimeCallbacks first_callbacks; - ConnectionPool::MockConnectionLifetimeCallbacks second_callbacks; - std::vector hash_key = {1}; - - pool_->setLifetimeCallbacks( - makeOptRef(first_callbacks), hash_key); - pool_->setLifetimeCallbacks( - makeOptRef(second_callbacks), hash_key); - - expectClientCreate(); - ActiveTestRequest r1(*this, 0, false); - - EXPECT_CALL(first_callbacks, onConnectionOpen(_, _, _)).Times(0); - EXPECT_CALL(second_callbacks, - onConnectionOpen(testing::Ref(*pool_), testing::ContainerEq(hash_key), _)); - expectClientConnect(0, r1); - - completeRequest(r1); - - EXPECT_CALL(first_callbacks, onConnectionDraining(_, _, _)).Times(0); - EXPECT_CALL(second_callbacks, - onConnectionDraining(testing::Ref(*pool_), testing::ContainerEq(hash_key), _)); - EXPECT_CALL(*this, onClientDestroy()); - test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); - dispatcher_.clearDeferredDeleteList(); -} - } // namespace Http2 } // namespace Http } // namespace Envoy diff --git a/test/common/http/http3/BUILD b/test/common/http/http3/BUILD index b59b1939090d3..450c9c266d8d3 100644 --- a/test/common/http/http3/BUILD +++ b/test/common/http/http3/BUILD @@ -23,7 +23,6 @@ envoy_cc_test( "//test/common/quic:test_utils_lib", "//test/common/upstream:utility_lib", "//test/mocks/event:event_mocks", - "//test/mocks/http:conn_pool_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", diff --git a/test/common/http/http3/conn_pool_test.cc b/test/common/http/http3/conn_pool_test.cc index 8dd6f8fd70696..bb982ddcec1af 100644 --- a/test/common/http/http3/conn_pool_test.cc +++ b/test/common/http/http3/conn_pool_test.cc @@ -1,7 +1,6 @@ #include #include -#include "source/common/http/conn_pool_base.h" #include "source/common/http/http3/conn_pool.h" #include "source/common/quic/quic_client_transport_socket_factory.h" @@ -10,7 +9,6 @@ #include "test/common/upstream/utility.h" #include "test/mocks/common.h" #include "test/mocks/event/mocks.h" -#include "test/mocks/http/conn_pool.h" #include "test/mocks/server/overload_manager.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/ssl/mocks.h" @@ -308,61 +306,6 @@ TEST_F(Http3ConnPoolImplTest, GetNetworkChangeEvents) { observers_.onNetworkDisconnected(-1); } -// Connected fires onConnectionOpen, then GOAWAY on an idle connection closes it -// which fires onConnectionDraining. Exercises the full MultiplexedActiveClientBase -// callback path through the HTTP/3 pool. -TEST_F(Http3ConnPoolImplTest, LifetimeCallbackOpenAndGoAwayDraining) { - EXPECT_CALL(mockHost(), address()).WillRepeatedly(Return(test_address_)); - initialize(); - - Http::ConnectionPool::MockConnectionLifetimeCallbacks lifetime_callbacks; - std::vector hash_key = {1, 2, 3}; - pool_->setLifetimeCallbacks( - makeOptRef(lifetime_callbacks), hash_key); - - MockResponseDecoder decoder; - ConnPoolCallbacks callbacks; - mockHost().cluster_.cluster_socket_options_ = std::make_shared(); - std::shared_ptr cluster_socket_option{new Network::MockSocketOption()}; - mockHost().cluster_.cluster_socket_options_->push_back(cluster_socket_option); - EXPECT_CALL(*mockHost().cluster_.upstream_local_address_selector_, - getUpstreamLocalAddressImpl(_, _)) - .WillOnce(Invoke( - [&](const Network::Address::InstanceConstSharedPtr& address, - OptRef) -> Upstream::UpstreamLocalAddress { - EXPECT_EQ(address, test_address_); - Network::ConnectionSocket::OptionsSharedPtr options = - std::make_shared(); - Network::Socket::appendOptions(options, mockHost().cluster_.cluster_socket_options_); - return Upstream::UpstreamLocalAddress({nullptr, options}); - })); - EXPECT_CALL(*cluster_socket_option, setOption(_, _)).Times(3u); - EXPECT_CALL(*socket_option_, setOption(_, _)).Times(3u); - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - auto* async_connect_callback = new NiceMock(&dispatcher_); - ConnectionPool::Cancellable* cancellable = pool_->newStream( - decoder, callbacks, {/*can_send_early_data_=*/false, /*can_use_http3_=*/true}); - EXPECT_NE(nullptr, cancellable); - async_connect_callback->invokeCallback(); - - std::list& clients = - Http3ConnPoolImplPeer::connectingClients(*pool_); - EXPECT_EQ(1u, clients.size()); - auto* client = static_cast(clients.front().get()); - - // ConnectedZeroRtt event fires onConnectionOpen. - EXPECT_CALL(lifetime_callbacks, - onConnectionOpen(testing::Ref(*pool_), testing::ContainerEq(hash_key), testing::_)); - client->onEvent(Network::ConnectionEvent::ConnectedZeroRtt); - - // GOAWAY on idle connection -> closes -> LocalClose -> onConnectionDraining. - EXPECT_CALL(lifetime_callbacks, onConnectionDraining(testing::Ref(*pool_), - testing::ContainerEq(hash_key), testing::_)); - EXPECT_CALL(dispatcher_, deferredDelete_(_)); - client->onGoAway(Http::GoAwayErrorCode::NoError); - dispatcher_.to_delete_.clear(); -} - } // namespace Http3 } // namespace Http } // namespace Envoy diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 43e5d5b4ea3a9..e99e4a260d0a1 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -1736,25 +1736,6 @@ TEST_F(ClusterManagerImplTest, UpstreamSocketOptionsPassedToConnPool) { EXPECT_TRUE(opt_cp.has_value()); } -// Verify that httpConnPool calls setLifetimeCallbacks on the newly created pool. -TEST_F(ClusterManagerImplTest, LifetimeCallbacksPassedToConnPool) { - createWithBasicStaticCluster(); - NiceMock context; - - Http::ConnectionPool::MockInstance* to_create = - new NiceMock(); - - EXPECT_CALL(factory_, allocateConnPool_(_, _, _, _, _, _, _)).WillOnce(Return(to_create)); - EXPECT_CALL(*to_create, setLifetimeCallbacks(_, _)); - - auto opt_cp = - cluster_manager_->getThreadLocalCluster("cluster_1") - ->httpConnPool( - cluster_manager_->getThreadLocalCluster("cluster_1")->chooseHost(nullptr).host, - ResourcePriority::Default, Http::Protocol::Http11, &context); - EXPECT_TRUE(opt_cp.has_value()); -} - TEST_F(ClusterManagerImplTest, UpstreamSocketOptionsUsedInConnPoolHash) { NiceMock context1; NiceMock context2; @@ -1844,7 +1825,6 @@ TEST_F(ClusterManagerImplTest, HttpPoolDataForwardsCallsToConnectionPool) { EXPECT_CALL(factory_, allocateConnPool_(_, _, _, _, _, _, _)).WillOnce(Return(pool_mock)); EXPECT_CALL(*pool_mock, addIdleCallback(_)); - EXPECT_CALL(*pool_mock, setLifetimeCallbacks(_, _)); auto opt_cp = cluster_manager_->getThreadLocalCluster("cluster_1") @@ -2308,7 +2288,6 @@ TEST_F(ClusterManagerImplTest, ConnectionPoolPerDownstreamConnection) { for (size_t i = 0; i < 3; ++i) { conn_pool_vector.push_back(new Http::ConnectionPool::MockInstance()); EXPECT_CALL(*conn_pool_vector.back(), addIdleCallback(_)); - EXPECT_CALL(*conn_pool_vector.back(), setLifetimeCallbacks(_, _)); EXPECT_CALL(factory_, allocateConnPool_(_, _, _, _, _, _, _)) .WillOnce(Return(conn_pool_vector.back())); EXPECT_CALL(downstream_connection, hashKey) @@ -2389,7 +2368,6 @@ TEST_F(ClusterManagerImplTest, PassDownNetworkObserverRegistryToConnectionPool) auto* pool = new Http::ConnectionPool::MockInstance(); Quic::EnvoyQuicNetworkObserverRegistry* created_registry = nullptr; EXPECT_CALL(*pool, addIdleCallback(_)); - EXPECT_CALL(*pool, setLifetimeCallbacks(_, _)); EXPECT_CALL(factory_, allocateConnPool_(_, _, _, _, _, _, _)) .WillOnce(testing::WithArg<5>( Invoke([pool, created_registry_ptr = &created_registry]( @@ -2408,7 +2386,6 @@ TEST_F(ClusterManagerImplTest, PassDownNetworkObserverRegistryToConnectionPool) pool = new Http::ConnectionPool::MockInstance(); EXPECT_CALL(*pool, addIdleCallback(_)); - EXPECT_CALL(*pool, setLifetimeCallbacks(_, _)); EXPECT_CALL(factory_, allocateConnPool_(_, _, _, _, _, _, _)) .WillOnce(testing::WithArg<5>( Invoke([pool, created_registry]( diff --git a/test/common/upstream/cluster_manager_lifecycle_test.cc b/test/common/upstream/cluster_manager_lifecycle_test.cc index e4cf9efe24bb4..ed02a278c22df 100644 --- a/test/common/upstream/cluster_manager_lifecycle_test.cc +++ b/test/common/upstream/cluster_manager_lifecycle_test.cc @@ -742,7 +742,6 @@ TEST_P(ClusterManagerLifecycleTest, DynamicAddRemove) { Http::ConnectionPool::Instance::IdleCb idle_cb; EXPECT_CALL(factory_, allocateConnPool_(_, _, _, _, _, _, _)).WillOnce(Return(cp)); EXPECT_CALL(*cp, addIdleCallback(_)).WillOnce(SaveArg<0>(&idle_cb)); - EXPECT_CALL(*cp, setLifetimeCallbacks(_, _)); EXPECT_EQ( cp, HttpPoolDataPeer::getPool( @@ -1942,7 +1941,6 @@ TEST_P(ClusterManagerLifecycleTest, ConnPoolDestroyWithDraining) { Http::ConnectionPool::Instance::IdleCb drained_cb; EXPECT_CALL(factory_, allocateConnPool_(_, _, _, _, _, _, _)).WillOnce(Return(mock_cp)); EXPECT_CALL(*mock_cp, addIdleCallback(_)).WillOnce(SaveArg<0>(&drained_cb)); - EXPECT_CALL(*mock_cp, setLifetimeCallbacks(_, _)); EXPECT_CALL(*mock_cp, drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); MockTcpConnPoolWithDestroy* mock_tcp = new NiceMock(); diff --git a/test/mocks/http/conn_pool.h b/test/mocks/http/conn_pool.h index 58e015611834c..fc5c90f3dd0aa 100644 --- a/test/mocks/http/conn_pool.h +++ b/test/mocks/http/conn_pool.h @@ -39,23 +39,11 @@ class MockInstance : public Instance { MOCK_METHOD(bool, maybePreconnect, (float)); MOCK_METHOD(Upstream::HostDescriptionConstSharedPtr, host, (), (const)); MOCK_METHOD(absl::string_view, protocolDescription, (), (const)); - MOCK_METHOD(void, setLifetimeCallbacks, - (OptRef callbacks, std::vector hash_key)); std::shared_ptr> host_; IdleCb idle_cb_; }; -class MockConnectionLifetimeCallbacks : public ConnectionLifetimeCallbacks { -public: - MOCK_METHOD(void, onConnectionOpen, - (Instance & pool, std::vector& hash_key, - const Network::Connection& connection)); - MOCK_METHOD(void, onConnectionDraining, - (Instance & pool, std::vector& hash_key, - const Network::Connection& connection)); -}; - } // namespace ConnectionPool } // namespace Http } // namespace Envoy From b1baea2bf77a8df15ac61dc3df651a4c8f415843 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 14 May 2026 17:07:53 +0100 Subject: [PATCH 310/750] deps/actions: Bump toolshed actions -> 0.4.17 (#45070) adds retry to failing buildah (docker push) action Signed-off-by: Ryan Northey --- .github/workflows/_check_coverage.yml | 4 +-- .github/workflows/_cve_fetch.yml | 2 +- .github/workflows/_cve_scan.yml | 2 +- .github/workflows/_finish.yml | 8 ++--- .github/workflows/_load.yml | 10 +++--- .github/workflows/_load_env.yml | 8 ++--- .github/workflows/_precheck_publish.yml | 2 +- .github/workflows/_publish_release.yml | 4 +-- .../workflows/_publish_release_container.yml | 6 ++-- .github/workflows/_publish_verify.yml | 2 +- .github/workflows/_request.yml | 24 ++++++------- .github/workflows/_request_cache_bazel.yml | 10 +++--- .github/workflows/_request_cache_docker.yml | 10 +++--- .github/workflows/_request_checks.yml | 10 +++--- .github/workflows/_run.yml | 28 +++++++-------- .github/workflows/_upload_gcs.yml | 6 ++-- .github/workflows/codeql-daily.yml | 4 +-- .github/workflows/codeql-push.yml | 4 +-- .github/workflows/command.yml | 6 ++-- .github/workflows/envoy-dependency.yml | 18 +++++----- .github/workflows/envoy-release.yml | 34 +++++++++---------- .github/workflows/envoy-sync.yml | 4 +-- .github/workflows/mobile-android_build.yml | 12 +++---- .github/workflows/mobile-ios_build.yml | 4 +-- 24 files changed, 111 insertions(+), 111 deletions(-) diff --git a/.github/workflows/_check_coverage.yml b/.github/workflows/_check_coverage.yml index b4132e70cf55d..517f0ca224114 100644 --- a/.github/workflows/_check_coverage.yml +++ b/.github/workflows/_check_coverage.yml @@ -61,7 +61,7 @@ jobs: upload-name: coverage upload-path: generated/coverage/html steps-post: | - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: output-path: generated/coverage/html/gcs-metadata.json input-format: yaml @@ -82,7 +82,7 @@ jobs: - target: fuzz_coverage name: Fuzz coverage steps-post: | - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: output-path: generated/fuzz_coverage/html/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_cve_fetch.yml b/.github/workflows/_cve_fetch.yml index 7947f7340f89a..d8a9a523be554 100644 --- a/.github/workflows/_cve_fetch.yml +++ b/.github/workflows/_cve_fetch.yml @@ -35,7 +35,7 @@ jobs: else echo "weekly_run=false" >> $GITHUB_OUTPUT fi - - uses: envoyproxy/toolshed/actions/gcp/setup@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/gcp/setup@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_cve_scan.yml b/.github/workflows/_cve_scan.yml index c1a7c9d6ff873..e6cb672e6a289 100644 --- a/.github/workflows/_cve_scan.yml +++ b/.github/workflows/_cve_scan.yml @@ -28,7 +28,7 @@ jobs: id: vars run: | echo "cve-data-path=${{ inputs.cve-data-path }}" > $GITHUB_OUTPUT - - uses: envoyproxy/toolshed/actions/gcp/setup@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/gcp/setup@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Setup GCP with: key: ${{ secrets.gcs-cve-key }} diff --git a/.github/workflows/_finish.yml b/.github/workflows/_finish.yml index 55dc28fde80ac..d54566adb340d 100644 --- a/.github/workflows/_finish.yml +++ b/.github/workflows/_finish.yml @@ -36,7 +36,7 @@ jobs: actions: read contents: read steps: - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Incoming data id: needs with: @@ -87,7 +87,7 @@ jobs: summary: "Check has finished", text: $text}}}} - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Print summary with: input: ${{ toJSON(steps.needs.outputs.value).summary-title }} @@ -95,13 +95,13 @@ jobs: "## \(.)" options: -Rr output-path: GITHUB_STEP_SUMMARY - - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/checks@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Update check with: action: update diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index 6e01033fae624..3ab02410c91c0 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -102,7 +102,7 @@ jobs: # Handle any failure in triggering job # Remove any `checks` we dont care about # Prepare a check request - - uses: envoyproxy/toolshed/actions/github/env/load@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/env/load@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Load env id: data with: @@ -113,13 +113,13 @@ jobs: GH_TOKEN: ${{ github.token }} # Update the check - - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/checks@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Update check if: ${{ fromJSON(steps.data.outputs.data).data.check.action == 'RUN' }} with: @@ -127,7 +127,7 @@ jobs: checks: ${{ toJSON(fromJSON(steps.data.outputs.data).checks) }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Print request summary with: input: | @@ -147,7 +147,7 @@ jobs: | $summary.summary as $summary | "${{ inputs.template-request-summary }}" - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: request-output name: Load request with: diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index 2406b36ab1188..4fdc144736595 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -62,18 +62,18 @@ jobs: request: ${{ steps.env.outputs.data }} trusted: true steps: - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: checkout name: Checkout Envoy repository - name: Generate environment variables - uses: envoyproxy/toolshed/actions/envoy/ci/env@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/envoy/ci/env@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: env with: branch-name: ${{ inputs.branch-name }} @@ -85,7 +85,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/env/summary@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} diff --git a/.github/workflows/_precheck_publish.yml b/.github/workflows/_precheck_publish.yml index e5cca6ee73a06..48cbc6fe720f3 100644 --- a/.github/workflows/_precheck_publish.yml +++ b/.github/workflows/_precheck_publish.yml @@ -88,7 +88,7 @@ jobs: upload-name: docs upload-path: generated/docs steps-post: | - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: output-path: generated/docs/gcs-metadata.json input-format: yaml diff --git a/.github/workflows/_publish_release.yml b/.github/workflows/_publish_release.yml index 1e678f089ddf1..dcf3f2e347809 100644 --- a/.github/workflows/_publish_release.yml +++ b/.github/workflows/_publish_release.yml @@ -142,12 +142,12 @@ jobs: needs: - release steps: - - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/actions/dispatch@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/dispatch@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: ref: main repository: ${{ fromJSON(inputs.request).request.version.dev && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} diff --git a/.github/workflows/_publish_release_container.yml b/.github/workflows/_publish_release_container.yml index 6dfd1abc188dc..5a23c59651593 100644 --- a/.github/workflows/_publish_release_container.yml +++ b/.github/workflows/_publish_release_container.yml @@ -58,7 +58,7 @@ jobs: - name: Generate manifest configuration (dev) id: dev-config if: ${{ inputs.dev && inputs.target-branch == 'main' }} - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: input-format: yaml filter: >- @@ -137,7 +137,7 @@ jobs: - tools-dev-${{ github.sha }} - name: Generate manifest configuration (release) - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: release-config if: ${{ ! inputs.dev || ! inputs.target-branch != 'main' }} with: @@ -227,7 +227,7 @@ jobs: - tools-v${{ inputs.version-major }}.${{ inputs.version-minor }}-latest - name: Collect and push OCI artifacts - uses: envoyproxy/toolshed/actions/oci/collector@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/oci/collector@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: artifacts-pattern: oci.* manifest-config: ${{ steps.dev-config.outputs.value || steps.release-config.outputs.value }} diff --git a/.github/workflows/_publish_verify.yml b/.github/workflows/_publish_verify.yml index 00906ed8ba42b..0700c4e4eae54 100644 --- a/.github/workflows/_publish_verify.yml +++ b/.github/workflows/_publish_verify.yml @@ -114,7 +114,7 @@ jobs: export NO_BUILD_SETUP=1 steps-pre: | - id: version-support - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: input: | version_major: ${{ fromJSON(inputs.request).request.version.major }} diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index 2bd7ef7bf1e00..52790930d524a 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -55,14 +55,14 @@ jobs: caches: ${{ steps.caches.outputs.value }} config: ${{ steps.config.outputs.config }} steps: - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: checkout name: Checkout Envoy repository (requested) with: @@ -76,7 +76,7 @@ jobs: # *ALL* variables collected should be treated as untrusted and should be sanitized before # use - name: Generate environment variables from commit - uses: envoyproxy/toolshed/actions/envoy/ci/request@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/envoy/ci/request@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: env with: branch-name: ${{ steps.checkout.outputs.branch-name }} @@ -87,7 +87,7 @@ jobs: vars: ${{ toJSON(vars) }} working-directory: requested - - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -95,7 +95,7 @@ jobs: config: | fetch-depth: 1 path: target - - uses: envoyproxy/toolshed/actions/hashfiles@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/hashfiles@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: bazel-cache-hash name: Bazel cache hash with: @@ -104,7 +104,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/actions/github/env/summary@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/env/summary@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} @@ -120,28 +120,28 @@ jobs: target-branch: ${{ fromJSON(steps.env.outputs.data).request.target-branch }} - id: cache-id-bazel-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-arm64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-arm64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-docs-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-docs-x64 wf-path: .github/workflows/request.yml - id: cache-id-bazel-external-x64 - uses: envoyproxy/toolshed/actions/github/artifact/cache/id@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/artifact/cache/id@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: name: ${{ steps.bazel-cache-hash.outputs.value }}-external-x64 wf-path: .github/workflows/request.yml - name: Environment data - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: data with: input: | @@ -189,7 +189,7 @@ jobs: path: /tmp/cache key: ${{ fromJSON(steps.data.outputs.value).request.build-image.default }}-arm64 - name: Caches - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: caches with: input-format: yaml diff --git a/.github/workflows/_request_cache_bazel.yml b/.github/workflows/_request_cache_bazel.yml index 45ba16bb182b9..56508565a7030 100644 --- a/.github/workflows/_request_cache_bazel.yml +++ b/.github/workflows/_request_cache_bazel.yml @@ -59,7 +59,7 @@ jobs: && ! fromJSON(inputs.caches).bazel[format('{0}-{1}', inputs.output-base, inputs.arch)]) }} steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/bind-mounts@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: mounts: | - src: /mnt/workspace @@ -73,9 +73,9 @@ jobs: target: /build chown: "runner:docker" - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/diskspace@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 if: inputs.arch == 'x64' && github.event.repository.private - - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: checkout-target name: Checkout Envoy repository (target branch) with: @@ -83,14 +83,14 @@ jobs: config: | fetch-depth: 1 - - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/cache/prime@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/cache/prime@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: bazel-cache name: Prime Bazel cache with: diff --git a/.github/workflows/_request_cache_docker.yml b/.github/workflows/_request_cache_docker.yml index 454eb09207fe3..01da07ef8d34b 100644 --- a/.github/workflows/_request_cache_docker.yml +++ b/.github/workflows/_request_cache_docker.yml @@ -39,7 +39,7 @@ on: # For a job that does, you can restore with something like: # # steps: -# - uses: envoyproxy/toolshed/actions/docker/cache/restore@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 +# - uses: envoyproxy/toolshed/actions/docker/cache/restore@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 # with: # key: "${{ needs.env.outputs.build-image }}" # @@ -51,13 +51,13 @@ jobs: name: "[${{ inputs.arch }}] Prime Docker cache" if: ${{ ! fromJSON(inputs.caches).docker[inputs.arch] }} steps: - - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/docker/cache/prime@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/docker/cache/prime@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: docker name: Prime Docker cache (${{ inputs.image-tag }}${{ inputs.cache-suffix }}) with: @@ -65,7 +65,7 @@ jobs: key-suffix: ${{ inputs.cache-suffix }} lock-token: ${{ steps.appauth.outputs.token }} lock-repository: ${{ inputs.lock-repository }} - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: data name: Cache data with: @@ -73,7 +73,7 @@ jobs: input: | cached: ${{ steps.docker.outputs.cached }} key: ${{ inputs.image-tag }}${{ inputs.cache-suffix }} - - uses: envoyproxy/toolshed/actions/json/table@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/json/table@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Summary with: json: ${{ steps.data.outputs.value }} diff --git a/.github/workflows/_request_checks.yml b/.github/workflows/_request_checks.yml index 9edbaa2c4b447..465111305067a 100644 --- a/.github/workflows/_request_checks.yml +++ b/.github/workflows/_request_checks.yml @@ -55,7 +55,7 @@ jobs: runs-on: ${{ fromJSON(inputs.env).config.ci.agent-ubuntu }} name: Start checks steps: - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: check-config name: Prepare check data with: @@ -78,13 +78,13 @@ jobs: | .skipped.output.summary = "${{ inputs.skipped-summary }}" | .skipped.output.text = "" - - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/actions/github/checks@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/checks@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Start checks id: checks with: @@ -95,7 +95,7 @@ jobs: ${{ fromJSON(inputs.env).summary.summary }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/json/table@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/json/table@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Summary with: collapse-open: true @@ -119,7 +119,7 @@ jobs: output-path: GITHUB_STEP_SUMMARY title: Checks started/skipped - - uses: envoyproxy/toolshed/actions/github/env/save@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/env/save@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Save env id: data with: diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index a7292a4b6b0d4..f9e9723462985 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -146,7 +146,7 @@ on: summary-post: type: string default: | - - uses: envoyproxy/toolshed/actions/envoy/run/summary@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/envoy/run/summary@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: context: %{{ inputs.context }} steps-pre: @@ -215,7 +215,7 @@ jobs: name: ${{ inputs.target-suffix && format('[{0}] ', inputs.target-suffix) || '' }}${{ inputs.command }} ${{ inputs.target }} timeout-minutes: ${{ inputs.timeout-minutes }} steps: - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: started name: Create timestamp with: @@ -223,7 +223,7 @@ jobs: filter: | now # This controls which input vars are exposed to the run action (and related steps) - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Context id: context with: @@ -256,14 +256,14 @@ jobs: fi echo "mnt-available=$MNT_AVAILABLE" >> "$GITHUB_OUTPUT" id: disk - - uses: envoyproxy/toolshed/actions/github/remnt@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/remnt@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 if: steps.disk.outputs.should-remnt == 'true' - - uses: envoyproxy/toolshed/actions/bind-mounts@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/bind-mounts@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 if: inputs.bind-mount && steps.disk.outputs.mnt-available == 'true' with: mounts: ${{ inputs.bind-mounts }} - name: Free diskspace - uses: envoyproxy/toolshed/actions/diskspace@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/diskspace@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 if: inputs.diskspace-hack || steps.disk.outputs.mnt-available != 'true' with: to_remove: ${{ inputs.diskspace-hack-paths }} @@ -271,7 +271,7 @@ jobs: mount df -h - - uses: envoyproxy/toolshed/actions/bson@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/bson@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Configure Docker if: runner.os == 'Linux' with: @@ -287,7 +287,7 @@ jobs: | "${{ inputs.template-docker-configure }}" # Caches - - uses: envoyproxy/toolshed/actions/cache/restore@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/cache/restore@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 if: >- fromJSON(inputs.bazel-cache) name: >- @@ -328,12 +328,12 @@ jobs: key: ${{ inputs.cache-build-image }}${{ inputs.cache-build-image-key-suffix }} - if: ${{ inputs.cache-build-image && steps.cache-lookup.outputs.cache-hit == 'true' }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/actions/docker/cache/restore@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/docker/cache/restore@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: image-tag: ${{ inputs.cache-build-image }} key-suffix: ${{ inputs.cache-build-image-key-suffix }} - - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: appauth name: Appauth if: ${{ inputs.trusted }} @@ -344,7 +344,7 @@ jobs: # - the workaround is to allow the token to be passed through. token: ${{ github.token }} token-ok: true - - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: checkout name: Checkout Envoy repository with: @@ -360,7 +360,7 @@ jobs: token: ${{ inputs.trusted && steps.appauth.outputs.token || github.token }} # This is currently only use by mobile-docs and can be removed once they are updated to the newer website - - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: checkout-extra name: Checkout extra repository (for publishing) if: ${{ inputs.checkout-extra }} @@ -369,7 +369,7 @@ jobs: ssh-key: ${{ inputs.trusted && inputs.ssh-key-extra || '' }} - name: Import GPG key - uses: envoyproxy/toolshed/actions/gpg/import@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/gpg/import@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 if: ${{ inputs.import-gpg }} with: key: ${{ secrets.gpg-key }} @@ -407,7 +407,7 @@ jobs: # NOTE: This is where untrusted code can be run!!! # Only post-failure notification steps (which don't use any repo code) should follow this step. - - uses: envoyproxy/toolshed/actions/github/run@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/run@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: ci-run name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: diff --git a/.github/workflows/_upload_gcs.yml b/.github/workflows/_upload_gcs.yml index 4c100dcdc7f9d..f17484eef0ce5 100644 --- a/.github/workflows/_upload_gcs.yml +++ b/.github/workflows/_upload_gcs.yml @@ -29,17 +29,17 @@ jobs: with: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} - - uses: envoyproxy/toolshed/actions/jq@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/jq@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: metadata with: input: ${{ matrix.artifact }}/gcs-metadata.json input-format: json-path - run: | rm -rf ${{ matrix.artifact }}/gcs-metadata.json - - uses: envoyproxy/toolshed/actions/gcp/setup@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/gcp/setup@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: key: ${{ secrets.gcp-key }} - - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/gcs/artefact/sync@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: bucket: ${{ fromJSON(steps.metadata.outputs.value).bucket }} path: ${{ matrix.artifact }} diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 4a552b229b436..d99b7391d6812 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -28,7 +28,7 @@ jobs: steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/bind-mounts@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 if: | ! github.event.repository.private with: @@ -43,7 +43,7 @@ jobs: if: | env.BUILD_TARGETS != '' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/diskspace@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index a67ea2735d991..d2072383dc459 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-22.04 if: github.repository == 'envoyproxy/envoy' steps: - - uses: envoyproxy/toolshed/actions/bind-mounts@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/bind-mounts@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 if: ! github.event.repository.private with: mounts: | @@ -64,7 +64,7 @@ jobs: if: | env.CPP_CHANGED == 'true' && github.event.repository.private - uses: envoyproxy/toolshed/actions/diskspace@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/diskspace@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: to_remove: | /usr/local/.ghcup diff --git a/.github/workflows/command.yml b/.github/workflows/command.yml index 62466c22993eb..a644b04d903f6 100644 --- a/.github/workflows/command.yml +++ b/.github/workflows/command.yml @@ -28,7 +28,7 @@ jobs: && github.actor != 'dependabot[bot]' }} steps: - - uses: envoyproxy/toolshed/actions/github/command@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/command@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Parse command from comment id: command with: @@ -37,14 +37,14 @@ jobs: ^/(retest) # /retest - - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 if: ${{ steps.command.outputs.command == 'retest' }} id: appauth-retest name: Appauth (retest) with: key: ${{ secrets.ENVOY_CI_APP_KEY }} app_id: ${{ secrets.ENVOY_CI_APP_ID }} - - uses: envoyproxy/toolshed/actions/retest@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/retest@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 if: ${{ steps.command.outputs.command == 'retest' }} name: Retest with: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 9291c983211b1..a4ca8fd334167 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -53,16 +53,16 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/bson@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/bson@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: update name: Update dependency (${{ inputs.dependency }}) with: @@ -97,13 +97,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/actions/upload/diff@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/upload/diff@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.update.outputs.output }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/actions/github/pr@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/pr@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: base: main body: | @@ -134,11 +134,11 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: checkout name: Checkout Envoy repository with: @@ -191,7 +191,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/actions/docker/shas@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/docker/shas@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: images: | sha-ci: docker.io/envoyproxy/envoy-build:ci-${{ steps.build-tools.outputs.tag }} @@ -229,7 +229,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/pr@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 06f429057f376..ac920e3bf0292 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -60,14 +60,14 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} @@ -88,10 +88,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/actions/email/validate@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/email/validate@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/actions/github/run@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/run@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Create release with: source: | @@ -116,7 +116,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/pr@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: base: ${{ github.ref_name }} commit: false @@ -142,19 +142,19 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/run@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Re-open branch with: command: >- @@ -169,7 +169,7 @@ jobs: name: Dev version id: dev - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/pr@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: base: ${{ github.ref_name }} commit: false @@ -193,20 +193,20 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} strip-prefix: release/ token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/actions/github/run@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/github/run@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 name: Sync version histories with: command: >- @@ -216,7 +216,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/actions/github/pr@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/pr@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: append-commit-message: true base: ${{ github.ref_name }} @@ -243,13 +243,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: config: | fetch-depth: 0 @@ -279,13 +279,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - name: Checkout repository - uses: envoyproxy/toolshed/actions/github/checkout@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index ce6db6199f196..75e6b2f25a19a 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -42,13 +42,13 @@ jobs: echo "skip=true" >> "$GITHUB_OUTPUT" fi - if: steps.tip.outputs.skip != 'true' - uses: envoyproxy/toolshed/actions/appauth@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/appauth@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - if: steps.tip.outputs.skip != 'true' - uses: envoyproxy/toolshed/actions/dispatch@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + uses: envoyproxy/toolshed/actions/dispatch@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index 9ffa30dccea7e..32ddcb1d7d3db 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -107,9 +107,9 @@ jobs: target: kotlin-hello-world runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/envoy/android/post@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: apk: bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity @@ -152,7 +152,7 @@ jobs: target: ${{ matrix.target }} runs-on: ubuntu-22.04 steps-pre: | - - uses: envoyproxy/toolshed/actions/envoy/android/pre@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/envoy/android/pre@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 steps-post: ${{ matrix.steps-post }} timeout-minutes: 50 trusted: ${{ needs.load.outputs.trusted && fromJSON(needs.load.outputs.trusted) || false }} @@ -163,7 +163,7 @@ jobs: include: - name: java-hello-world steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/envoy/android/post@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: apk: bazel-bin/examples/java/hello_world/hello_envoy.apk app: io.envoyproxy.envoymobile.helloenvoy/.MainActivity @@ -186,7 +186,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/baseline:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/envoy/android/post@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: apk: bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity @@ -203,7 +203,7 @@ jobs: --config=mobile-android-release //test/kotlin/apps/experimental:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/actions/envoy/android/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + - uses: envoyproxy/toolshed/actions/envoy/android/post@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 with: apk: bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 7501b3cd03602..0aaec3167529a 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -103,7 +103,7 @@ jobs: # source ./ci/mac_ci_setup.sh # bazel shutdown # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} @@ -148,7 +148,7 @@ jobs: # source: | # source ./ci/mac_ci_setup.sh # steps-post: | - # - uses: envoyproxy/toolshed/actions/envoy/ios/post@719447d2ab39e226e7bdceb58fd61340821d8473 # actions-v0.4.16 + # - uses: envoyproxy/toolshed/actions/envoy/ios/post@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 # with: # app: ${{ matrix.app }} # args: ${{ matrix.args || '--config=mobile-ios' }} From bf2f1d8243b2fd0ced0622606e968e1883c23274 Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 14 May 2026 13:05:24 -0400 Subject: [PATCH 311/750] mobile: update python integration tests to fallback to IPv6 loopback (#45049) Commit Message: change the test to run under IPv6 if the environment doesn't support IPv4. Risk Level: low Testing: existing tests pass Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A --------- Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- .../async_client/async_client_fetch_test.py | 12 +++--- mobile/test/python/echo_test_server.py | 41 ++++++++++++++++++- mobile/test/python/fetch_test.py | 7 ++-- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/mobile/test/python/async_client/async_client_fetch_test.py b/mobile/test/python/async_client/async_client_fetch_test.py index 2018481d7bd78..bcbdaec9a1e0c 100644 --- a/mobile/test/python/async_client/async_client_fetch_test.py +++ b/mobile/test/python/async_client/async_client_fetch_test.py @@ -2,7 +2,6 @@ import asyncio import json -import random import unittest from test.python.echo_test_server import EchoTestServer @@ -16,10 +15,9 @@ class TestAsyncClientFetch(unittest.TestCase): @classmethod def setUpClass(cls): """Set up an echo test server for the tests to hit.""" - port = random.randint(2**14, 2**16) - cls._echo_server = EchoTestServer("127.0.0.1", port) + cls._echo_server = EchoTestServer() cls._echo_server.start() - cls._echo_server_url = f"http://127.0.0.1:{port}" + cls._echo_server_url = f"http://{cls._echo_server.url}" @classmethod def tearDownClass(cls): @@ -28,7 +26,11 @@ def tearDownClass(cls): def _make_client_builder(self): # factory to keep constructor logic consistent without sharing instances - builder = EngineBuilder().set_log_level(LogLevel.trace) + builder = ( + EngineBuilder() + .set_log_level(LogLevel.trace) + .add_runtime_guard("getaddrinfo_no_ai_flags", True) + ) return builder def test_simple_get_request(self): diff --git a/mobile/test/python/echo_test_server.py b/mobile/test/python/echo_test_server.py index 79118b518d354..dbb6107408ab8 100644 --- a/mobile/test/python/echo_test_server.py +++ b/mobile/test/python/echo_test_server.py @@ -3,6 +3,8 @@ from threading import Event from threading import Thread import json +import random +import socket class EchoServerHandler(BaseHTTPRequestHandler): @@ -67,10 +69,44 @@ def log_message(self, *args, **kwargs): pass +class HTTPServerV6(HTTPServer): + address_family = socket.AF_INET6 + + +def is_ipv4_supported(): + try: + socket.getaddrinfo("127.0.0.1", None, family=socket.AF_INET) + return True + except socket.gaierror: + return False + + +def is_ipv6_supported(): + try: + socket.getaddrinfo("::1", None, family=socket.AF_INET6) + return True + except socket.gaierror: + return False + + class EchoTestServer: - def __init__(self, ip, port): - self._server = HTTPServer((ip, port), EchoServerHandler) + def __init__(self): + use_v4 = is_ipv4_supported() + if not (use_v4 or is_ipv6_supported()): + raise RuntimeError("Neither IPv4 nor IPv6 is supported by the environment") + + port = random.randint(2**14, 2**16) + server = None + if use_v4: + server = HTTPServer(("127.0.0.1", port), EchoServerHandler) + self.url = f"127.0.0.1:{port}" + else: + server = HTTPServerV6(("::1", port), EchoServerHandler) + self.url = f"[::1]:{port}" + + self._server = server + assert self._server is not None self._server_thread = Thread(target=self._server.serve_forever, daemon=True) self._server.socket.settimeout(10.0) @@ -78,5 +114,6 @@ def start(self): self._server_thread.start() def stop(self): + assert self._server is not None self._server.shutdown() self._server_thread.join() diff --git a/mobile/test/python/fetch_test.py b/mobile/test/python/fetch_test.py index 17010c98252fc..df2c768bd2d3b 100644 --- a/mobile/test/python/fetch_test.py +++ b/mobile/test/python/fetch_test.py @@ -1,6 +1,5 @@ """Integration tests for the Envoy Mobile Python bindings.""" -import random import threading import unittest from test.python.echo_test_server import EchoTestServer @@ -21,10 +20,9 @@ class TestFetchRequest(unittest.TestCase): @classmethod def setUpClass(cls): """Set up an echo test server for the tests to hit.""" - port = random.randint(2**14, 2**16) - cls._echo_server = EchoTestServer("127.0.0.1", port) + cls._echo_server = EchoTestServer() cls._echo_server.start() - cls._echo_server_url = f"127.0.0.1:{port}" + cls._echo_server_url = cls._echo_server.url @classmethod def tearDownClass(cls): @@ -38,6 +36,7 @@ def _build_engine(self): EngineBuilder() .set_log_level(LogLevel.trace) .add_runtime_guard("dns_cache_set_ip_version_to_remove", True) + .add_runtime_guard("getaddrinfo_no_ai_flags", True) .set_on_engine_running(lambda: engine_running.set()) .enable_worker_thread(True) .build() From 02edba2591d89918043d27916ca83204d54107c3 Mon Sep 17 00:00:00 2001 From: Reuben Tanner Date: Thu, 14 May 2026 14:06:31 -0400 Subject: [PATCH 312/750] Change `envoy_reloadable_features_http_inspector_use_balsa_parser` to default true (#44679) Commit Message: Change `envoy_reloadable_features_http_inspector_use_balsa_parser` to default true Additional Description: Balsa has been the open source default for a while. We are ready to delete HttpParserImpl code from Envoy but need to migrate this filter first. We begin the 6 month clock now! See the [commit](https://github.com/envoyproxy/envoy/commit/6ccad8dd190d32dd04eaa0175782acb820d5eab6) and [bug](https://github.com/envoyproxy/envoy/issues/36433) which tracked this work. Risk Level: Low Testing: Parameterized //test/extensions/filters/listener/http_inspector:http_inspector_integration_test and adjusted expectations based on parser differences. All other tests pass. Docs Changes: None. Release Notes: Updated change log. Platform Specific Features: None. --------- Signed-off-by: Reuben Tanner --- changelogs/current.yaml | 10 ++++ source/common/runtime/runtime_features.cc | 5 +- .../filters/listener/http_inspector/BUILD | 1 + .../http_inspector_integration_test.cc | 49 ++++++++++++++++--- 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index b7c4e52da8c64..4a51686156567 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -200,5 +200,15 @@ new_features: change: | Added substitution commands ``%DOWNSTREAM_TLS_GROUP%`` and ``%UPSTREAM_TLS_GROUP%``. The TLS group may be used to discern if a TLS connection used a post quantum safe key exchange (e.g. X25519MLKEM768). +- area: http_inspector + change: | + Enabled Balsa parser for HTTP inspector by default. This behavior can be temporarily + reverted by setting the runtime guard ``envoy.reloadable_features.http_inspector_use_balsa_parser`` + to ``false``. This runtime guard will be removed in a future release of Envoy. +- area: http_inspector + change: | + Enabled Balsa parser for HTTP inspector by default. This behavior can be temporarily + reverted by setting the runtime guard ``envoy.reloadable_features.http_inspector_use_balsa_parser`` + to ``false``. This runtime guard will be removed in a future release of Envoy. deprecated: diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index cb461bd701b33..c8f1e2049ad33 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -66,6 +66,7 @@ RUNTIME_GUARD(envoy_reloadable_features_hide_transport_failure_reason_in_respons RUNTIME_GUARD(envoy_reloadable_features_http1_close_connection_on_zombie_stream_complete); RUNTIME_GUARD(envoy_reloadable_features_http2_discard_host_header); RUNTIME_GUARD(envoy_reloadable_features_http_async_client_retry_respect_buffer_limits); +RUNTIME_GUARD(envoy_reloadable_features_http_inspector_use_balsa_parser); // Delay deprecation and decommission until UHV is enabled. RUNTIME_GUARD(envoy_reloadable_features_http_reject_path_with_fragment); RUNTIME_GUARD(envoy_reloadable_features_jwt_authn_add_verification_status_header); @@ -118,13 +119,11 @@ RUNTIME_GUARD(envoy_reloadable_features_websocket_allow_4xx_5xx_through_filter_c RUNTIME_GUARD(envoy_reloadable_features_websocket_enable_timeout_on_upgrade_response); RUNTIME_GUARD(envoy_reloadable_features_xds_failover_to_primary_enabled); RUNTIME_GUARD(envoy_reloadable_features_xds_legacy_delta_skip_subsequent_node); - RUNTIME_GUARD(envoy_restart_features_move_locality_schedulers_to_lb); RUNTIME_GUARD(envoy_restart_features_raise_file_limits); RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); RUNTIME_GUARD(envoy_restart_features_validate_http3_pseudo_headers); RUNTIME_GUARD(envoy_restart_features_worker_threads_watchdog_fix); - // Begin false flags. Most of them should come with a TODO to flip true. // Sentinel and test flag. @@ -164,8 +163,6 @@ FALSE_RUNTIME_GUARD(envoy_restart_features_xds_failover_support); FALSE_RUNTIME_GUARD(envoy_reloadable_features_dns_cache_set_ip_version_to_remove); // TODO(fredyw): evaluate and either make this a config knob or remove. FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_no_tcp_delay); -// Adding runtime flag to use balsa_parser for http_inspector. -FALSE_RUNTIME_GUARD(envoy_reloadable_features_http_inspector_use_balsa_parser); // TODO(danzh) re-enable it when the issue of preferring TCP over v6 rather than QUIC over v4 is // fixed. FALSE_RUNTIME_GUARD(envoy_reloadable_features_http3_happy_eyeballs); diff --git a/test/extensions/filters/listener/http_inspector/BUILD b/test/extensions/filters/listener/http_inspector/BUILD index c9afde3e766ee..46184d532eeb4 100644 --- a/test/extensions/filters/listener/http_inspector/BUILD +++ b/test/extensions/filters/listener/http_inspector/BUILD @@ -87,6 +87,7 @@ envoy_extension_cc_test( "//source/extensions/transport_sockets/raw_buffer:config", "//test/integration:base_integration_test_lib", "//test/integration:common_extensions_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto", diff --git a/test/extensions/filters/listener/http_inspector/http_inspector_integration_test.cc b/test/extensions/filters/listener/http_inspector/http_inspector_integration_test.cc index a9429c43487fc..14e6f42581eb2 100644 --- a/test/extensions/filters/listener/http_inspector/http_inspector_integration_test.cc +++ b/test/extensions/filters/listener/http_inspector/http_inspector_integration_test.cc @@ -9,6 +9,7 @@ #include "test/integration/fake_upstream.h" #include "test/integration/server.h" #include "test/test_common/printers.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -24,30 +25,62 @@ void insertHttpInspectorConfigModifier(envoy::config::bootstrap::v3::Bootstrap& ppv_filter->set_name("http_inspector"); ppv_filter->mutable_typed_config()->PackFrom(http_inspector); } + +std::string testParamToString( + const ::testing::TestParamInfo>& + info) { + ::testing::TestParamInfo ip_info(std::get<0>(info.param), + info.index); + return TestUtility::ipTestParamsToString(ip_info) + "_" + + TestUtility::http1ParserImplToString(std::get<1>(info.param)); +} + } // namespace -class HttpInspectorTcpIntegrationTest : public testing::TestWithParam, - public BaseIntegrationTest { +class HttpInspectorTcpIntegrationTest + : public testing::TestWithParam>, + public BaseIntegrationTest { public: HttpInspectorTcpIntegrationTest() - : BaseIntegrationTest(GetParam(), ConfigHelper::tcpProxyConfig()) { + : BaseIntegrationTest(std::get<0>(GetParam()), ConfigHelper::tcpProxyConfig()), + parser_impl_(std::get<1>(GetParam())) { config_helper_.addConfigModifier(insertHttpInspectorConfigModifier); config_helper_.renameListener("tcp_proxy"); + + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + scoped_runtime_.mergeValues( + {{"envoy.reloadable_features.http_inspector_use_balsa_parser", "true"}}); + } else { + scoped_runtime_.mergeValues( + {{"envoy.reloadable_features.http_inspector_use_balsa_parser", "false"}}); + } } + + const Http1ParserImpl parser_impl_; + TestScopedRuntime scoped_runtime_; }; -INSTANTIATE_TEST_SUITE_P(IpVersions, HttpInspectorTcpIntegrationTest, - testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - TestUtility::ipTestParamsToString); +INSTANTIATE_TEST_SUITE_P( + ParsersAndIp, HttpInspectorTcpIntegrationTest, + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + testing::Values(Http1ParserImpl::HttpParser, Http1ParserImpl::BalsaParser)), + testParamToString); TEST_P(HttpInspectorTcpIntegrationTest, DetectNoHttp) { initialize(); + std::string data = "hello"; + size_t expected_bytes = 5; + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + data = "hello\r\n"; + expected_bytes = 7; + } + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); - ASSERT_TRUE(tcp_client->write("hello", false)); + ASSERT_TRUE(tcp_client->write(data, false)); FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); - ASSERT_TRUE(fake_upstream_connection->waitForData(5)); + ASSERT_TRUE(fake_upstream_connection->waitForData(expected_bytes)); ASSERT_TRUE(fake_upstream_connection->close()); tcp_client->close(); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); From 47d1595038220574c73443538d0cfff195cd1e70 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 14 May 2026 19:42:55 +0100 Subject: [PATCH 313/750] tests: Narrow test mock includes more generally (diwydu) (#45037) a better if more invasive fix would be to do some refactoring on the mock libs themselves, and that can be addressed in follow ups for now this should have 2 benefits: reduce cache busting on mock lib change - probably low impact on basis that these change infrequently reduce bandwidth/setup/teardown/build runtimes for tests - this should have much bigger impact, and its hoped it will mittigate the fairly frequent test build timeouts (potential swapper/oom) that we get in ci --------- Signed-off-by: Ryan Northey --- .../filters/network/test/codecs/kafka/BUILD | 2 +- .../network/test/codecs/kafka/config_test.cc | 2 +- .../matching/input_matchers/test/BUILD | 2 +- .../input_matchers/test/config_test.cc | 2 +- contrib/hyperscan/regex_engines/test/BUILD | 2 +- .../regex_engines/test/config_test.cc | 2 +- .../load_balancing_policies/test/BUILD | 2 +- .../test/config_test.cc | 2 +- .../rocketmq_proxy/filters/network/test/BUILD | 1 - .../filters/network/test/conn_manager_test.cc | 1 - contrib/stat_sinks/wasm_filter/test/BUILD | 1 - .../wasm_filter/test/config_test.cc | 1 - .../network_configuration_filter_test.cc | 1 - test/common/config/BUILD | 2 +- .../config/config_provider_impl_test.cc | 2 +- test/common/http/BUILD | 6 +-- test/common/http/conn_manager_utility_test.cc | 2 +- test/common/http/hcm_router_fuzz_test.cc | 2 +- test/common/http/http1/BUILD | 2 +- test/common/http/http1/codec_impl_test.cc | 2 +- test/common/http/utility_test.cc | 2 +- test/common/io/BUILD | 2 +- .../io/io_uring_worker_factory_impl_test.cc | 2 +- test/common/matcher/BUILD | 2 +- test/common/matcher/actions/BUILD | 3 +- .../actions/string_returning_action_test.cc | 2 +- test/common/matcher/matcher_test.cc | 2 +- test/common/network/BUILD | 3 -- .../network/filter_manager_impl_test.cc | 2 - test/common/network/udp_fuzz.cc | 1 - .../udp_listener_impl_batch_writer_test.cc | 1 - test/common/quic/active_quic_listener_test.cc | 1 - test/common/rds/BUILD | 2 +- test/common/rds/rds_test.cc | 2 +- test/common/router/BUILD | 15 ++++--- .../config_impl_headermap_benchmark_test.cc | 2 +- test/common/router/config_impl_speed_test.cc | 2 +- test/common/router/config_impl_test.cc | 2 +- test/common/router/route_fuzz_test.cc | 2 +- .../router/router_outlier_detection_test.cc | 1 - test/common/router/router_ratelimit_test.cc | 2 +- test/common/router/scoped_rds_test.cc | 2 +- test/common/router/vhds_test.cc | 2 +- test/common/signal/BUILD | 1 - test/common/signal/fatal_action_test.cc | 1 - test/common/ssl/matching/BUILD | 2 +- .../ssl/matching/inputs_integration_test.cc | 2 +- test/common/tcp_proxy/BUILD | 1 + test/common/tcp_proxy/tcp_proxy_test.cc | 1 - test/common/tcp_proxy/tcp_proxy_test_base.h | 1 - test/common/tracing/BUILD | 2 +- .../tracing/tracer_manager_impl_test.cc | 2 +- test/common/upstream/BUILD | 39 ++++++++++--------- test/common/upstream/cds_api_impl_test.cc | 2 +- .../upstream/cluster_factory_impl_test.cc | 2 +- .../deferred_cluster_initialization_test.cc | 1 - test/common/upstream/hds_test.cc | 2 +- .../upstream/health_checker_impl_test.cc | 1 - test/common/upstream/od_cds_api_impl_test.cc | 2 +- test/common/upstream/upstream_impl_test.cc | 2 +- .../upstream/xdstp_od_cds_api_impl_test.cc | 2 +- .../access_loggers/dynamic_modules/BUILD | 3 +- .../dynamic_modules/config_test.cc | 3 +- test/extensions/access_loggers/fluentd/BUILD | 5 ++- .../fluentd/substitution_formatter_test.cc | 1 - test/extensions/access_loggers/stats/BUILD | 4 +- .../access_loggers/stats/stats_speed_test.cc | 2 +- .../access_loggers/stats/stats_test.cc | 1 - .../upstream_socket_interface/BUILD | 4 +- .../config_validation_test.cc | 2 +- ...tream_reverse_connection_io_handle_test.cc | 1 - test/extensions/bootstrap/wasm/BUILD | 4 +- .../bootstrap/wasm/wasm_speed_test.cc | 2 +- test/extensions/bootstrap/wasm/wasm_test.cc | 2 +- test/extensions/clusters/aggregate/BUILD | 2 +- .../clusters/aggregate/cluster_test.cc | 2 +- .../clusters/aggregate/cluster_update_test.cc | 1 - .../extensions/clusters/dynamic_modules/BUILD | 2 +- .../clusters/dynamic_modules/cluster_test.cc | 2 +- test/extensions/clusters/eds/BUILD | 7 ++-- .../extensions/clusters/eds/eds_speed_test.cc | 2 +- test/extensions/clusters/eds/eds_test.cc | 2 +- test/extensions/clusters/eds/leds_test.cc | 2 +- test/extensions/clusters/logical_dns/BUILD | 2 +- .../logical_dns/logical_dns_cluster_test.cc | 2 +- .../clusters/mcp_multicluster/BUILD | 2 +- .../clusters/mcp_multicluster/cluster_test.cc | 1 - test/extensions/clusters/original_dst/BUILD | 2 +- .../original_dst/original_dst_cluster_test.cc | 2 +- test/extensions/clusters/redis/BUILD | 2 +- .../clusters/redis/redis_cluster_test.cc | 2 +- .../clusters/reverse_connection/BUILD | 1 - .../reverse_connection_cluster_test.cc | 1 - test/extensions/common/async_files/BUILD | 9 +++-- .../async_file_handle_thread_pool_test.cc | 5 ++- .../async_file_manager_factory_test.cc | 2 +- .../async_file_manager_thread_pool_test.cc | 3 +- ...ile_manager_thread_pool_with_mocks_test.cc | 2 +- .../common/dynamic_forward_proxy/BUILD | 2 +- .../dns_cache_impl_test.cc | 2 +- test/extensions/common/matcher/BUILD | 4 +- .../common/matcher/domain_matcher_test.cc | 2 +- .../common/matcher/ip_range_matcher_test.cc | 2 +- test/extensions/common/tap/BUILD | 2 +- test/extensions/common/tap/admin_test.cc | 2 +- test/extensions/common/wasm/BUILD | 5 ++- test/extensions/common/wasm/foreign_test.cc | 1 - .../extensions/common/wasm/wasm_speed_test.cc | 1 - test/extensions/common/wasm/wasm_test.cc | 4 +- test/extensions/content_parsers/json/BUILD | 2 +- .../content_parsers/json/config_test.cc | 2 +- .../dynamic_modules/bootstrap/BUILD | 2 +- .../dynamic_modules/bootstrap/factory_test.cc | 2 +- test/extensions/filters/common/rbac/BUILD | 4 +- .../filters/common/rbac/engine_impl_test.cc | 2 +- .../filters/common/rbac/utility_test.cc | 2 +- .../filters/common/set_filter_state/BUILD | 2 +- .../set_filter_state/filter_config_test.cc | 2 +- test/extensions/filters/http/composite/BUILD | 1 - .../filters/http/composite/filter_test.cc | 1 - .../filters/http/custom_response/BUILD | 3 +- .../custom_response_filter_test.cc | 3 +- .../filters/http/file_system_buffer/BUILD | 3 +- .../http/file_system_buffer/config_test.cc | 3 +- .../http/file_system_buffer/filter_test.cc | 1 - .../filters/http/health_check/BUILD | 2 +- .../http/health_check/health_check_test.cc | 2 +- .../filters/http/json_to_metadata/BUILD | 1 - .../http/json_to_metadata/filter_test.cc | 1 - .../filters/http/local_ratelimit/BUILD | 2 +- .../http/local_ratelimit/filter_test.cc | 2 +- .../http/proto_message_extraction/BUILD | 2 +- .../filter_config_test.cc | 2 +- test/extensions/filters/http/ratelimit/BUILD | 1 - .../filters/http/ratelimit/config_test.cc | 1 - test/extensions/filters/http/rbac/BUILD | 2 +- .../filters/http/rbac/rbac_filter_test.cc | 2 +- .../filters/http/sse_to_metadata/BUILD | 2 +- .../http/sse_to_metadata/filter_test.cc | 2 +- .../filters/http/thrift_to_metadata/BUILD | 1 - .../http/thrift_to_metadata/filter_test.cc | 1 - .../filters/network/dubbo_proxy/BUILD | 2 +- .../network/dubbo_proxy/route_matcher_test.cc | 2 +- .../filters/network/generic_proxy/BUILD | 5 +-- .../network/generic_proxy/codecs/http1/BUILD | 2 +- .../generic_proxy/codecs/http1/config_test.cc | 2 +- .../network/generic_proxy/integration_test.cc | 1 - .../network/generic_proxy/route_test.cc | 2 +- .../network/generic_proxy/router/BUILD | 3 +- .../generic_proxy/router/upstream_test.cc | 5 ++- .../network/generic_proxy/stats_test.cc | 2 +- test/extensions/filters/network/geoip/BUILD | 1 - .../network/geoip/geoip_filter_test.cc | 1 - .../config_filter_chain_test.cc | 1 - .../config_filter_dependencies_test.cc | 1 - .../filters/network/ratelimit/BUILD | 1 - .../filters/network/ratelimit/config_test.cc | 1 - test/extensions/filters/network/rbac/BUILD | 2 +- .../filters/network/rbac/filter_test.cc | 2 +- test/extensions/filters/network/wasm/BUILD | 1 - .../filters/network/wasm/wasm_filter_test.cc | 1 - test/extensions/filters/udp/dns_filter/BUILD | 2 - .../dns_filter/dns_filter_access_log_test.cc | 1 - .../filters/udp/dns_filter/dns_filter_test.cc | 1 - test/extensions/filters/udp/udp_proxy/BUILD | 2 +- .../filters/udp/udp_proxy/router_impl_test.cc | 2 +- test/extensions/health_checkers/redis/BUILD | 1 - .../health_checkers/redis/config_test.cc | 1 - test/extensions/health_checkers/thrift/BUILD | 1 - .../health_checkers/thrift/config_test.cc | 1 - .../header_validators/envoy_default/BUILD | 2 +- .../header_validator_factory_test.cc | 2 +- .../http/stateful_session/cookie/BUILD | 2 +- .../stateful_session/cookie/config_test.cc | 2 +- .../http/stateful_session/envelope/BUILD | 2 +- .../stateful_session/envelope/config_test.cc | 2 +- .../http/stateful_session/header/BUILD | 2 +- .../stateful_session/header/config_test.cc | 2 +- .../client_side_weighted_round_robin/BUILD | 2 +- .../config_test.cc | 2 +- .../cluster_provided/BUILD | 2 +- .../cluster_provided/config_test.cc | 2 +- .../dynamic_modules/BUILD | 2 +- .../dynamic_modules/config_test.cc | 2 +- .../least_request/BUILD | 2 +- .../least_request/config_test.cc | 2 +- .../load_balancing_policies/maglev/BUILD | 2 +- .../maglev/config_test.cc | 2 +- .../load_balancing_policies/random/BUILD | 2 +- .../random/config_test.cc | 2 +- .../load_balancing_policies/ring_hash/BUILD | 2 +- .../ring_hash/config_test.cc | 2 +- .../load_balancing_policies/round_robin/BUILD | 2 +- .../round_robin/config_test.cc | 2 +- .../load_balancing_policies/subset/BUILD | 4 +- .../subset/subset_benchmark.cc | 2 +- .../subset/subset_test.cc | 2 +- .../wrr_locality/BUILD | 2 +- .../wrr_locality/config_test.cc | 2 +- .../common_inputs/environment_variable/BUILD | 2 +- .../environment_variable/config_test.cc | 2 +- .../matching/input_matchers/cel_matcher/BUILD | 2 +- .../cel_matcher/cel_matcher_test.cc | 2 +- .../input_matchers/consistent_hashing/BUILD | 2 +- .../consistent_hashing/config_test.cc | 2 +- .../input_matchers/dynamic_modules/BUILD | 2 +- .../dynamic_modules/config_test.cc | 2 +- .../matching/input_matchers/ip/BUILD | 2 +- .../matching/input_matchers/ip/config_test.cc | 2 +- .../matching/input_matchers/metadata/BUILD | 2 +- .../metadata/dyn_meta_matcher_test.cc | 2 +- .../input_matchers/runtime_fraction/BUILD | 2 +- .../runtime_fraction/config_test.cc | 2 +- test/extensions/matching/network/common/BUILD | 2 +- .../network/common/inputs_integration_test.cc | 2 +- test/extensions/path/match/uri_template/BUILD | 4 +- .../path/match/uri_template/config_test.cc | 3 +- .../path/match/uri_template/library_test.cc | 2 +- .../path/rewrite/uri_template/BUILD | 4 +- .../path/rewrite/uri_template/config_test.cc | 3 +- .../path/rewrite/uri_template/library_test.cc | 3 +- .../rate_limit_descriptors/expr/BUILD | 2 +- .../expr/config_test.cc | 2 +- .../router/cluster_specifiers/lua/BUILD | 4 +- .../cluster_specifiers/lua/config_test.cc | 2 +- .../lua/lua_cluster_specifier_test.cc | 2 +- .../router/cluster_specifiers/matcher/BUILD | 4 +- .../cluster_specifiers/matcher/config_test.cc | 2 +- .../matcher/matcher_cluster_specifier_test.cc | 2 +- test/extensions/stats_sinks/hystrix/BUILD | 2 +- .../stats_sinks/hystrix/hystrix_test.cc | 2 +- .../stats_sinks/open_telemetry/BUILD | 3 +- .../open_telemetry_benchmark.cc | 2 +- .../open_telemetry_impl_test.cc | 2 +- test/extensions/stats_sinks/wasm/BUILD | 3 +- .../stats_sinks/wasm/config_test.cc | 3 +- .../opentelemetry/resource_detectors/BUILD | 2 +- .../resource_detectors/dynatrace/BUILD | 3 +- .../dynatrace/config_test.cc | 2 +- .../dynatrace_resource_detector_test.cc | 3 +- .../resource_detectors/environment/BUILD | 2 +- .../environment/config_test.cc | 2 +- .../resource_provider_test.cc | 2 +- .../resource_detectors/static/BUILD | 2 +- .../resource_detectors/static/config_test.cc | 2 +- test/extensions/tracers/xray/BUILD | 1 - test/extensions/tracers/xray/config_test.cc | 1 - test/extensions/upstreams/http/tcp/BUILD | 1 - .../http/tcp/upstream_request_test.cc | 1 - test/extensions/upstreams/http/udp/BUILD | 2 - .../http/udp/upstream_request_test.cc | 2 - test/integration/fake_upstream.h | 1 - test/server/BUILD | 2 +- test/server/admin/BUILD | 5 ++- test/server/admin/admin_filter_test.cc | 7 +++- test/server/config_validation/BUILD | 2 +- .../config_validation/cluster_manager_test.cc | 2 +- test/server/configuration_impl_test.cc | 1 - test/server/hot_restarting_child_test.cc | 2 +- test/tools/router_check/router.h | 2 +- 260 files changed, 276 insertions(+), 318 deletions(-) diff --git a/contrib/generic_proxy/filters/network/test/codecs/kafka/BUILD b/contrib/generic_proxy/filters/network/test/codecs/kafka/BUILD index e6e280243d1bd..e80262232e0c9 100644 --- a/contrib/generic_proxy/filters/network/test/codecs/kafka/BUILD +++ b/contrib/generic_proxy/filters/network/test/codecs/kafka/BUILD @@ -17,6 +17,6 @@ envoy_cc_test( deps = [ "//contrib/generic_proxy/filters/network/source/codecs/kafka:config", "//test/extensions/filters/network/generic_proxy/mocks:codec_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/network:connection_mocks", ], ) diff --git a/contrib/generic_proxy/filters/network/test/codecs/kafka/config_test.cc b/contrib/generic_proxy/filters/network/test/codecs/kafka/config_test.cc index 2925622f8d9de..41385708c9ac5 100644 --- a/contrib/generic_proxy/filters/network/test/codecs/kafka/config_test.cc +++ b/contrib/generic_proxy/filters/network/test/codecs/kafka/config_test.cc @@ -2,7 +2,7 @@ #include #include "test/extensions/filters/network/generic_proxy/mocks/codec.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/network/connection.h" #include "contrib/generic_proxy/filters/network/source/codecs/kafka/config.h" #include "contrib/kafka/filters/network/source/external/requests.h" diff --git a/contrib/hyperscan/matching/input_matchers/test/BUILD b/contrib/hyperscan/matching/input_matchers/test/BUILD index c3c64a5fe5a97..6161e002b7e08 100644 --- a/contrib/hyperscan/matching/input_matchers/test/BUILD +++ b/contrib/hyperscan/matching/input_matchers/test/BUILD @@ -15,7 +15,7 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ "//contrib/hyperscan/matching/input_matchers/source:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", ], ) diff --git a/contrib/hyperscan/matching/input_matchers/test/config_test.cc b/contrib/hyperscan/matching/input_matchers/test/config_test.cc index 4a7953a0758f8..1eb5b1775d749 100644 --- a/contrib/hyperscan/matching/input_matchers/test/config_test.cc +++ b/contrib/hyperscan/matching/input_matchers/test/config_test.cc @@ -1,4 +1,4 @@ -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "contrib/hyperscan/matching/input_matchers/source/config.h" #include "gtest/gtest.h" diff --git a/contrib/hyperscan/regex_engines/test/BUILD b/contrib/hyperscan/regex_engines/test/BUILD index e10fda5b1c14b..09fa24f0f76dd 100644 --- a/contrib/hyperscan/regex_engines/test/BUILD +++ b/contrib/hyperscan/regex_engines/test/BUILD @@ -14,7 +14,7 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ "//contrib/hyperscan/regex_engines/source:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", ], ) diff --git a/contrib/hyperscan/regex_engines/test/config_test.cc b/contrib/hyperscan/regex_engines/test/config_test.cc index 8d9e68d51e981..8b00061a31ba2 100644 --- a/contrib/hyperscan/regex_engines/test/config_test.cc +++ b/contrib/hyperscan/regex_engines/test/config_test.cc @@ -1,4 +1,4 @@ -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "contrib/hyperscan/regex_engines/source/config.h" #include "gtest/gtest.h" diff --git a/contrib/peak_ewma/load_balancing_policies/test/BUILD b/contrib/peak_ewma/load_balancing_policies/test/BUILD index 745c6dc6cbc70..c3b6f4a101da2 100644 --- a/contrib/peak_ewma/load_balancing_policies/test/BUILD +++ b/contrib/peak_ewma/load_balancing_policies/test/BUILD @@ -30,7 +30,7 @@ envoy_cc_test( "//source/common/protobuf:utility_lib", "//test/common/stats:stat_test_utility_lib", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:utility_lib", "@envoy_api//contrib/envoy/extensions/load_balancing_policies/peak_ewma/v3alpha:pkg_cc_proto", diff --git a/contrib/peak_ewma/load_balancing_policies/test/config_test.cc b/contrib/peak_ewma/load_balancing_policies/test/config_test.cc index 8ecbebee8448e..041b8f79dc5a0 100644 --- a/contrib/peak_ewma/load_balancing_policies/test/config_test.cc +++ b/contrib/peak_ewma/load_balancing_policies/test/config_test.cc @@ -7,7 +7,7 @@ #include "test/common/stats/stat_test_utility.h" #include "test/mocks/common.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/priority_set.h" #include "test/test_common/utility.h" diff --git a/contrib/rocketmq_proxy/filters/network/test/BUILD b/contrib/rocketmq_proxy/filters/network/test/BUILD index 5adbf7bd341df..2a394d9eeba4a 100644 --- a/contrib/rocketmq_proxy/filters/network/test/BUILD +++ b/contrib/rocketmq_proxy/filters/network/test/BUILD @@ -73,7 +73,6 @@ envoy_cc_test( "//test/common/upstream:utility_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:utility_lib", ], diff --git a/contrib/rocketmq_proxy/filters/network/test/conn_manager_test.cc b/contrib/rocketmq_proxy/filters/network/test/conn_manager_test.cc index 2cbd77621f6f9..1c5d64001cafb 100644 --- a/contrib/rocketmq_proxy/filters/network/test/conn_manager_test.cc +++ b/contrib/rocketmq_proxy/filters/network/test/conn_manager_test.cc @@ -5,7 +5,6 @@ #include "test/mocks/network/connection.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "contrib/rocketmq_proxy/filters/network/source/config.h" #include "contrib/rocketmq_proxy/filters/network/source/conn_manager.h" diff --git a/contrib/stat_sinks/wasm_filter/test/BUILD b/contrib/stat_sinks/wasm_filter/test/BUILD index 7e773ca78462e..d5464c685893c 100644 --- a/contrib/stat_sinks/wasm_filter/test/BUILD +++ b/contrib/stat_sinks/wasm_filter/test/BUILD @@ -26,7 +26,6 @@ envoy_cc_test( srcs = ["config_test.cc"], deps = [ "//contrib/stat_sinks/wasm_filter/source:config_lib", - "//test/mocks/server:server_mocks", "//test/test_common:utility_lib", "@envoy_api//contrib/envoy/extensions/stat_sinks/wasm_filter/v3:pkg_cc_proto", ], diff --git a/contrib/stat_sinks/wasm_filter/test/config_test.cc b/contrib/stat_sinks/wasm_filter/test/config_test.cc index ff23a5466b698..fb0b81405f759 100644 --- a/contrib/stat_sinks/wasm_filter/test/config_test.cc +++ b/contrib/stat_sinks/wasm_filter/test/config_test.cc @@ -2,7 +2,6 @@ #include "source/common/protobuf/protobuf.h" -#include "test/mocks/server/mocks.h" #include "test/test_common/utility.h" #include "contrib/envoy/extensions/stat_sinks/wasm_filter/v3/wasm_filter.pb.validate.h" diff --git a/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc b/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc index c76f5a176c528..ac587c9ecf9c7 100644 --- a/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc +++ b/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc @@ -3,7 +3,6 @@ #include "test/extensions/common/dynamic_forward_proxy/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/server/factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/common/config/BUILD b/test/common/config/BUILD index f2ca47573c0fd..f61e9da302819 100644 --- a/test/common/config/BUILD +++ b/test/common/config/BUILD @@ -183,7 +183,7 @@ envoy_cc_test( ":dummy_config_proto_cc_proto", "//source/common/config:config_provider_lib", "//source/common/protobuf:utility_lib", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:simulated_time_system_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", diff --git a/test/common/config/config_provider_impl_test.cc b/test/common/config/config_provider_impl_test.cc index d6617dbfb3036..b11719780bfd8 100644 --- a/test/common/config/config_provider_impl_test.cc +++ b/test/common/config/config_provider_impl_test.cc @@ -8,7 +8,7 @@ #include "source/common/protobuf/utility.h" #include "test/common/config/dummy_config.pb.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" diff --git a/test/common/http/BUILD b/test/common/http/BUILD index 8f03184eff993..0e3f92888950a 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -346,7 +346,7 @@ envoy_cc_test( "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:test_runtime_lib", @@ -492,7 +492,7 @@ envoy_cc_test( "//source/common/http:utility_lib", "//source/common/network:address_lib", "//test/mocks/http:http_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", @@ -719,7 +719,7 @@ envoy_cc_fuzz_test( "//test/mocks/network:network_mocks", "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:overload_manager_mocks", "//test/mocks/server:server_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/tracing:tracing_mocks", diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index ed66b55451dd1..9b25a93afe9c4 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -23,7 +23,7 @@ #include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/printers.h" diff --git a/test/common/http/hcm_router_fuzz_test.cc b/test/common/http/hcm_router_fuzz_test.cc index 0d18358ceb94b..022842232b531 100644 --- a/test/common/http/hcm_router_fuzz_test.cc +++ b/test/common/http/hcm_router_fuzz_test.cc @@ -18,7 +18,7 @@ #include "test/fuzz/utility.h" #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/overload_manager.h" #include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" diff --git a/test/common/http/http1/BUILD b/test/common/http/http1/BUILD index f6f6cfddfc5c7..017acc2874cc6 100644 --- a/test/common/http/http1/BUILD +++ b/test/common/http/http1/BUILD @@ -36,8 +36,8 @@ envoy_cc_test( "//test/mocks/buffer:buffer_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/server:factory_context_mocks", "//test/mocks/server:overload_manager_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:logging_lib", diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 55d1242fdd96b..4d5d75a632abe 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -22,8 +22,8 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/server/overload_manager.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/logging.h" #include "test/test_common/printers.h" #include "test/test_common/test_runtime.h" diff --git a/test/common/http/utility_test.cc b/test/common/http/utility_test.cc index c9f4ccd4db309..2e494591226fe 100644 --- a/test/common/http/utility_test.cc +++ b/test/common/http/utility_test.cc @@ -15,7 +15,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/printers.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/common/io/BUILD b/test/common/io/BUILD index a13cd925ea928..7b92d4dd44375 100644 --- a/test/common/io/BUILD +++ b/test/common/io/BUILD @@ -71,7 +71,7 @@ envoy_cc_test( }), rbe_pool = "6gig", deps = [ - "//test/mocks/server:server_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", ] + select({ diff --git a/test/common/io/io_uring_worker_factory_impl_test.cc b/test/common/io/io_uring_worker_factory_impl_test.cc index f6eebbc664449..78c597fda259a 100644 --- a/test/common/io/io_uring_worker_factory_impl_test.cc +++ b/test/common/io/io_uring_worker_factory_impl_test.cc @@ -1,7 +1,7 @@ #include "source/common/io/io_uring_impl.h" #include "source/common/io/io_uring_worker_factory_impl.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" diff --git a/test/common/matcher/BUILD b/test/common/matcher/BUILD index a47472f101d69..2e4189d23327e 100644 --- a/test/common/matcher/BUILD +++ b/test/common/matcher/BUILD @@ -84,7 +84,7 @@ envoy_cc_test( "//source/common/matcher:matcher_lib", "//test/mocks/http:http_mocks", "//test/mocks/matcher:matcher_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", diff --git a/test/common/matcher/actions/BUILD b/test/common/matcher/actions/BUILD index 56ac2b4626066..134084f61649e 100644 --- a/test/common/matcher/actions/BUILD +++ b/test/common/matcher/actions/BUILD @@ -13,10 +13,9 @@ envoy_cc_test( srcs = ["string_returning_action_test.cc"], rbe_pool = "6gig", deps = [ - # formatter_extension_lib is required for DYNAMIC_METADATA to function in test. "//source/common/formatter:formatter_extension_lib", "//source/common/matcher/actions:string_returning_action_lib", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", - "//test/mocks/server:instance_mocks", ], ) diff --git a/test/common/matcher/actions/string_returning_action_test.cc b/test/common/matcher/actions/string_returning_action_test.cc index 22ec090024f87..74d57a92da197 100644 --- a/test/common/matcher/actions/string_returning_action_test.cc +++ b/test/common/matcher/actions/string_returning_action_test.cc @@ -1,7 +1,7 @@ #include "source/common/matcher/actions/string_returning_action.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "gtest/gtest.h" diff --git a/test/common/matcher/matcher_test.cc b/test/common/matcher/matcher_test.cc index 4439880aa80e1..10f071da25a98 100644 --- a/test/common/matcher/matcher_test.cc +++ b/test/common/matcher/matcher_test.cc @@ -12,7 +12,7 @@ #include "test/common/matcher/test_utility.h" #include "test/mocks/matcher/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/common/network/BUILD b/test/common/network/BUILD index e57064f567cb3..977d33a8b2b10 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -163,8 +163,6 @@ envoy_cc_test( "//test/mocks/buffer:buffer_mocks", "//test/mocks/network:network_mocks", "//test/mocks/ratelimit:ratelimit_mocks", - "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/mocks/upstream:host_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", @@ -430,7 +428,6 @@ envoy_cc_fuzz_test( "//source/common/stats:stats_lib", "//test/common/network:listener_impl_test_base_lib", "//test/mocks/network:network_mocks", - "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", "//test/test_common:threadsafe_singleton_injector_lib", diff --git a/test/common/network/filter_manager_impl_test.cc b/test/common/network/filter_manager_impl_test.cc index e156aa6d772e0..1bfe38ac00490 100644 --- a/test/common/network/filter_manager_impl_test.cc +++ b/test/common/network/filter_manager_impl_test.cc @@ -13,8 +13,6 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/ratelimit/mocks.h" -#include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/mocks/upstream/host.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" diff --git a/test/common/network/udp_fuzz.cc b/test/common/network/udp_fuzz.cc index 536d1faedcf0c..0dc3edf20263f 100644 --- a/test/common/network/udp_fuzz.cc +++ b/test/common/network/udp_fuzz.cc @@ -13,7 +13,6 @@ #include "test/common/network/udp_listener_impl_test_base.h" #include "test/fuzz/fuzz_runner.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/threadsafe_singleton_injector.h" diff --git a/test/common/network/udp_listener_impl_batch_writer_test.cc b/test/common/network/udp_listener_impl_batch_writer_test.cc index 3e90d25f75f2d..a5ec75522d27b 100644 --- a/test/common/network/udp_listener_impl_batch_writer_test.cc +++ b/test/common/network/udp_listener_impl_batch_writer_test.cc @@ -30,7 +30,6 @@ #include "test/common/network/udp_listener_impl_test_base.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/threadsafe_singleton_injector.h" diff --git a/test/common/quic/active_quic_listener_test.cc b/test/common/quic/active_quic_listener_test.cc index f87bd248f5574..14e867b2c7f5f 100644 --- a/test/common/quic/active_quic_listener_test.cc +++ b/test/common/quic/active_quic_listener_test.cc @@ -22,7 +22,6 @@ #include "test/common/quic/test_proof_source.h" #include "test/common/quic/test_utils.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/listener_factory_context.h" #include "test/mocks/ssl/mocks.h" #include "test/test_common/environment.h" diff --git a/test/common/rds/BUILD b/test/common/rds/BUILD index 8a44d738cd69a..6066855ba0321 100644 --- a/test/common/rds/BUILD +++ b/test/common/rds/BUILD @@ -14,7 +14,7 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ "//source/common/rds:rds_lib", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/thrift_proxy/v3:pkg_cc_proto", diff --git a/test/common/rds/rds_test.cc b/test/common/rds/rds_test.cc index 3417aa67a8578..303f6eed1a902 100644 --- a/test/common/rds/rds_test.cc +++ b/test/common/rds/rds_test.cc @@ -20,7 +20,7 @@ #include "source/common/rds/static_route_config_provider_impl.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/simulated_time_system.h" #include "gmock/gmock.h" diff --git a/test/common/router/BUILD b/test/common/router/BUILD index f7d3909fa46c0..8bd99ef4dc7f3 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -49,7 +49,7 @@ envoy_cc_test_library( "//test/extensions/filters/http/common:empty_http_filter_config_lib", "//test/fuzz:utility_lib", "//test/mocks/router:router_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:retry_priority_mocks", "//test/test_common:environment_lib", "//test/test_common:registry_lib", @@ -130,7 +130,7 @@ envoy_cc_benchmark_binary( deps = [ "//source/common/http:header_map_lib", "//source/common/router:config_lib", - "//test/mocks/server:server_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", "@benchmark", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", @@ -230,7 +230,7 @@ envoy_cc_test( "//test/mocks/matcher:matcher_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/router:router_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", @@ -254,7 +254,7 @@ envoy_cc_test( "//source/common/router:vhds_lib", "//source/server/admin:admin_lib", "//test/mocks/config:config_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", @@ -308,7 +308,7 @@ envoy_cc_fuzz_test( ":route_fuzz_proto_cc_proto", "//source/common/router:config_lib", "//test/fuzz:utility_lib", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", ], ) @@ -334,7 +334,7 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/ratelimit:ratelimit_mocks", "//test/mocks/router:router_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", @@ -481,7 +481,6 @@ envoy_cc_test( deps = [ ":router_test_base_lib", "//source/common/router:router_lib", - "//test/mocks/server:factory_context_mocks", "@envoy_api//envoy/extensions/filters/http/router/v3:pkg_cc_proto", ], ) @@ -576,7 +575,7 @@ envoy_cc_benchmark_binary( deps = [ "//source/common/common:assert_lib", "//source/common/router:config_lib", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:utility_lib", "@benchmark", diff --git a/test/common/router/config_impl_headermap_benchmark_test.cc b/test/common/router/config_impl_headermap_benchmark_test.cc index b3226990a1028..f77e0be943226 100644 --- a/test/common/router/config_impl_headermap_benchmark_test.cc +++ b/test/common/router/config_impl_headermap_benchmark_test.cc @@ -5,7 +5,7 @@ #include "source/common/http/header_map_impl.h" #include "source/common/router/config_impl.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "benchmark/benchmark.h" diff --git a/test/common/router/config_impl_speed_test.cc b/test/common/router/config_impl_speed_test.cc index 76d3cc2774070..a30adac0c7220 100644 --- a/test/common/router/config_impl_speed_test.cc +++ b/test/common/router/config_impl_speed_test.cc @@ -4,7 +4,7 @@ #include "source/common/common/assert.h" #include "source/common/router/config_impl.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 2beb08e00bc21..79f8bd6a0dcb6 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -27,7 +27,7 @@ #include "test/extensions/filters/http/common/empty_http_filter_config.h" #include "test/fuzz/utility.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/retry_priority.h" #include "test/mocks/upstream/retry_priority_factory.h" #include "test/mocks/upstream/test_retry_host_predicate_factory.h" diff --git a/test/common/router/route_fuzz_test.cc b/test/common/router/route_fuzz_test.cc index 61a30820d6018..15f1af03027e2 100644 --- a/test/common/router/route_fuzz_test.cc +++ b/test/common/router/route_fuzz_test.cc @@ -9,7 +9,7 @@ #include "test/common/router/route_fuzz.pb.validate.h" #include "test/fuzz/fuzz_runner.h" #include "test/fuzz/utility.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" namespace Envoy { namespace Router { diff --git a/test/common/router/router_outlier_detection_test.cc b/test/common/router/router_outlier_detection_test.cc index a17519f36ba2c..46fc6294823d7 100644 --- a/test/common/router/router_outlier_detection_test.cc +++ b/test/common/router/router_outlier_detection_test.cc @@ -3,7 +3,6 @@ #include "source/common/router/router.h" #include "test/common/router/router_test_base.h" -#include "test/mocks/server/factory_context.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/common/router/router_ratelimit_test.cc b/test/common/router/router_ratelimit_test.cc index cf936248c432b..e8b416c16eaf7 100644 --- a/test/common/router/router_ratelimit_test.cc +++ b/test/common/router/router_ratelimit_test.cc @@ -15,7 +15,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/ratelimit/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/printers.h" #include "test/test_common/test_runtime.h" diff --git a/test/common/router/scoped_rds_test.cc b/test/common/router/scoped_rds_test.cc index c320433718624..0a8555a20aefa 100644 --- a/test/common/router/scoped_rds_test.cc +++ b/test/common/router/scoped_rds_test.cc @@ -24,7 +24,7 @@ #include "test/mocks/matcher/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/common/router/vhds_test.cc b/test/common/router/vhds_test.cc index cb2b311e4b578..f48b9e19f3a68 100644 --- a/test/common/router/vhds_test.cc +++ b/test/common/router/vhds_test.cc @@ -18,7 +18,7 @@ #endif #include "test/mocks/config/mocks.h" #include "test/mocks/init/mocks.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" diff --git a/test/common/signal/BUILD b/test/common/signal/BUILD index 80da57e871e7f..c3f27a0ec4192 100644 --- a/test/common/signal/BUILD +++ b/test/common/signal/BUILD @@ -31,7 +31,6 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ "//source/common/signal:fatal_error_handler_lib", - "//test/mocks/server:instance_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/common/signal/fatal_action_test.cc b/test/common/signal/fatal_action_test.cc index b6b43470c60f3..dfc8b877f4bea 100644 --- a/test/common/signal/fatal_action_test.cc +++ b/test/common/signal/fatal_action_test.cc @@ -6,7 +6,6 @@ #include "source/common/signal/fatal_action.h" #include "source/common/signal/fatal_error_handler.h" -#include "test/mocks/server/instance.h" #include "test/test_common/utility.h" #include "absl/synchronization/notification.h" diff --git a/test/common/ssl/matching/BUILD b/test/common/ssl/matching/BUILD index e9e7dbe4512b3..a721c77ea56d4 100644 --- a/test/common/ssl/matching/BUILD +++ b/test/common/ssl/matching/BUILD @@ -32,7 +32,7 @@ envoy_cc_test( "//source/common/ssl/matching:inputs_lib", "//test/common/matcher:test_utility_lib", "//test/mocks/matcher:matcher_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:test_time_lib", ], ) diff --git a/test/common/ssl/matching/inputs_integration_test.cc b/test/common/ssl/matching/inputs_integration_test.cc index 8e97120925191..fef8f08daf20e 100644 --- a/test/common/ssl/matching/inputs_integration_test.cc +++ b/test/common/ssl/matching/inputs_integration_test.cc @@ -6,7 +6,7 @@ #include "test/common/matcher/test_utility.h" #include "test/mocks/matcher/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/ssl/mocks.h" #include "test/test_common/test_time.h" diff --git a/test/common/tcp_proxy/BUILD b/test/common/tcp_proxy/BUILD index d85cc1453662f..ee863dddd5dee 100644 --- a/test/common/tcp_proxy/BUILD +++ b/test/common/tcp_proxy/BUILD @@ -68,6 +68,7 @@ envoy_cc_test( deps = [ ":tcp_proxy_test_base", "//source/common/router:string_accessor_lib", + "//test/mocks/server:factory_context_mocks", "@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 9ab2ebd03a053..2ba8c8047e648 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -33,7 +33,6 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/tcp/mocks.h" diff --git a/test/common/tcp_proxy/tcp_proxy_test_base.h b/test/common/tcp_proxy/tcp_proxy_test_base.h index 54c5dfadcf3a0..a4a2d40bec406 100644 --- a/test/common/tcp_proxy/tcp_proxy_test_base.h +++ b/test/common/tcp_proxy/tcp_proxy_test_base.h @@ -27,7 +27,6 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/tcp/mocks.h" #include "test/mocks/upstream/host.h" diff --git a/test/common/tracing/BUILD b/test/common/tracing/BUILD index 2781ade6e4e5d..e0e3732d1ceed 100644 --- a/test/common/tracing/BUILD +++ b/test/common/tracing/BUILD @@ -67,7 +67,7 @@ envoy_cc_test( "//source/common/tracing:tracer_config_lib", "//source/common/tracing:tracer_lib", "//source/common/tracing:tracer_manager_lib", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/server:tracer_factory_mocks", "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", diff --git a/test/common/tracing/tracer_manager_impl_test.cc b/test/common/tracing/tracer_manager_impl_test.cc index c19a798e7f840..f97ed8127c965 100644 --- a/test/common/tracing/tracer_manager_impl_test.cc +++ b/test/common/tracing/tracer_manager_impl_test.cc @@ -2,7 +2,7 @@ #include "source/common/tracing/tracer_impl.h" #include "source/common/tracing/tracer_manager_impl.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/server/tracer_factory.h" #include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 95916b289680e..24a149cf5f954 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -24,7 +24,7 @@ envoy_cc_test( "//source/common/upstream:od_cds_api_lib", "//test/mocks/config:xds_manager_mocks", "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:missing_cluster_notifier_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -41,7 +41,7 @@ envoy_cc_test( "//source/common/upstream:od_cds_api_lib", "//test/mocks/config:xds_manager_mocks", "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:missing_cluster_notifier_mocks", "//test/test_common:test_runtime_lib", @@ -60,7 +60,7 @@ envoy_cc_test( "//source/common/protobuf:utility_lib", "//source/common/upstream:cds_api_lib", "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:cluster_priority_set_mocks", "//test/test_common:utility_lib", @@ -323,7 +323,6 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/server:factory_context_mocks", "//test/mocks/server:health_checker_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:cluster_priority_set_mocks", @@ -443,7 +442,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_factory_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:cluster_manager_mocks", @@ -581,11 +580,8 @@ envoy_cc_test( data = ["//test/common/tls/test_data:certs"], rbe_pool = "6gig", deps = [ - ":utility_lib", ":test_local_address_selector", - "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", - "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", + ":utility_lib", "//envoy/api:api_interface", "//envoy/http:codec_interface", "//envoy/upstream:cluster_manager_interface", @@ -598,8 +594,15 @@ envoy_cc_test( "//source/extensions/clusters/eds:eds_lib", # TODO(mattklein123): Split this into 2 tests for each cluster. "//source/extensions/clusters/static:static_cluster_lib", - "//source/extensions/transport_sockets/raw_buffer:config", + "//source/extensions/load_balancing_policies/cluster_provided:config", + "//source/extensions/load_balancing_policies/least_request:config", + "//source/extensions/load_balancing_policies/maglev:config", + "//source/extensions/load_balancing_policies/random:config", + "//source/extensions/load_balancing_policies/ring_hash:config", + "//source/extensions/load_balancing_policies/round_robin:config", + "//source/extensions/load_balancing_policies/subset:config", "//source/extensions/transport_sockets/http_11_proxy:upstream_config", + "//source/extensions/transport_sockets/raw_buffer:config", "//source/extensions/transport_sockets/tls:config", "//source/extensions/upstreams/http:config", "//source/extensions/upstreams/tcp:config", @@ -609,7 +612,9 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:admin_mocks", + "//test/mocks/server:options_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:cluster_manager_mocks", @@ -617,16 +622,12 @@ envoy_cc_test( "//test/mocks/upstream:priority_set_mocks", "//test/mocks/upstream:thread_aware_load_balancer_mocks", "//test/mocks/upstream:typed_load_balancer_factory_mocks", - "//source/extensions/load_balancing_policies/cluster_provided:config", - "//source/extensions/load_balancing_policies/least_request:config", - "//source/extensions/load_balancing_policies/maglev:config", - "//source/extensions/load_balancing_policies/random:config", - "//source/extensions/load_balancing_policies/ring_hash:config", - "//source/extensions/load_balancing_policies/round_robin:config", - "//source/extensions/load_balancing_policies/subset:config", "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", ] + envoy_select_enable_http3([ "//source/common/quic:quic_transport_socket_factory_lib", ]), @@ -716,8 +717,8 @@ envoy_cc_test( "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", "//test/mocks/server:options_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/test_common:registry_lib", "//test/test_common:utility_lib", diff --git a/test/common/upstream/cds_api_impl_test.cc b/test/common/upstream/cds_api_impl_test.cc index 6cbc51f1b10a8..9e6d6325defdf 100644 --- a/test/common/upstream/cds_api_impl_test.cc +++ b/test/common/upstream/cds_api_impl_test.cc @@ -14,7 +14,7 @@ #include "test/common/upstream/utility.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/cluster_priority_set.h" #include "test/test_common/printers.h" diff --git a/test/common/upstream/cluster_factory_impl_test.cc b/test/common/upstream/cluster_factory_impl_test.cc index 5f5769e81dc3d..8caff564347eb 100644 --- a/test/common/upstream/cluster_factory_impl_test.cc +++ b/test/common/upstream/cluster_factory_impl_test.cc @@ -21,8 +21,8 @@ #include "test/mocks/common.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/options.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/ssl/mocks.h" #include "test/test_common/registry.h" diff --git a/test/common/upstream/deferred_cluster_initialization_test.cc b/test/common/upstream/deferred_cluster_initialization_test.cc index 8afb826d7a3d8..209314ab767ae 100644 --- a/test/common/upstream/deferred_cluster_initialization_test.cc +++ b/test/common/upstream/deferred_cluster_initialization_test.cc @@ -13,7 +13,6 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/config/xds_manager.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/server/instance.h" #include "test/test_common/utility.h" namespace Envoy { diff --git a/test/common/upstream/hds_test.cc b/test/common/upstream/hds_test.cc index 92194721ab5b7..b8f851f069e63 100644 --- a/test/common/upstream/hds_test.cc +++ b/test/common/upstream/hds_test.cc @@ -17,7 +17,7 @@ #include "test/mocks/grpc/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/cluster_info_factory.h" #include "test/mocks/upstream/cluster_manager.h" diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index aaeddf49fe1bc..7d64db788237b 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -32,7 +32,6 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/server/health_checker_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/cluster_priority_set.h" diff --git a/test/common/upstream/od_cds_api_impl_test.cc b/test/common/upstream/od_cds_api_impl_test.cc index 82b1d915fc6f9..059223697c27c 100644 --- a/test/common/upstream/od_cds_api_impl_test.cc +++ b/test/common/upstream/od_cds_api_impl_test.cc @@ -6,7 +6,7 @@ #include "test/mocks/config/xds_manager.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/missing_cluster_notifier.h" diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index d3898794c0084..556a836f4d0d0 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -40,8 +40,8 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/options.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/health_checker.h" diff --git a/test/common/upstream/xdstp_od_cds_api_impl_test.cc b/test/common/upstream/xdstp_od_cds_api_impl_test.cc index d4ece12be41db..717d3af8eee48 100644 --- a/test/common/upstream/xdstp_od_cds_api_impl_test.cc +++ b/test/common/upstream/xdstp_od_cds_api_impl_test.cc @@ -8,7 +8,7 @@ #include "test/mocks/config/xds_manager.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/missing_cluster_notifier.h" #include "test/test_common/test_runtime.h" diff --git a/test/extensions/access_loggers/dynamic_modules/BUILD b/test/extensions/access_loggers/dynamic_modules/BUILD index dec6098d0b1a7..71e891a4c68aa 100644 --- a/test/extensions/access_loggers/dynamic_modules/BUILD +++ b/test/extensions/access_loggers/dynamic_modules/BUILD @@ -25,7 +25,8 @@ envoy_cc_test( "//source/extensions/access_loggers/dynamic_modules:config", "//test/extensions/dynamic_modules:util", "//test/mocks/access_log:access_log_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:options_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/access_loggers/dynamic_modules/config_test.cc b/test/extensions/access_loggers/dynamic_modules/config_test.cc index 7245c137945db..1cf01a7396375 100644 --- a/test/extensions/access_loggers/dynamic_modules/config_test.cc +++ b/test/extensions/access_loggers/dynamic_modules/config_test.cc @@ -4,7 +4,8 @@ #include "test/extensions/dynamic_modules/util.h" #include "test/mocks/access_log/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/options.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/access_loggers/fluentd/BUILD b/test/extensions/access_loggers/fluentd/BUILD index 78bd85662304c..cb27e7ed459d0 100644 --- a/test/extensions/access_loggers/fluentd/BUILD +++ b/test/extensions/access_loggers/fluentd/BUILD @@ -33,9 +33,10 @@ envoy_extension_cc_test( extension_names = ["envoy.access_loggers.fluentd"], rbe_pool = "6gig", deps = [ - "//source/common/formatter:formatter_extension_lib", + "//source/common/formatter:http_speicific_formatter_extension_lib", + "//source/common/formatter:substitution_format_string_lib", "//source/extensions/access_loggers/fluentd:substitution_formatter_lib", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", ], diff --git a/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc b/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc index a3245ee45634e..c4e26c1a433ea 100644 --- a/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc +++ b/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc @@ -4,7 +4,6 @@ #include "source/common/json/json_loader.h" #include "source/extensions/access_loggers/fluentd/substitution_formatter.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/access_loggers/stats/BUILD b/test/extensions/access_loggers/stats/BUILD index 7e2b64685a00a..9e87a3c364608 100644 --- a/test/extensions/access_loggers/stats/BUILD +++ b/test/extensions/access_loggers/stats/BUILD @@ -40,7 +40,7 @@ envoy_extension_cc_test( "//source/extensions/matching/actions/transform_stat:config", "//test/common/memory:memory_test_utility_lib", "//test/mocks/config:config_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:logging_lib", "//test/test_common:status_utility_lib", @@ -78,7 +78,7 @@ envoy_cc_benchmark_binary( deps = [ "//source/common/stats:thread_local_store_lib", "//source/extensions/access_loggers/stats:stats_lib", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:utility_lib", "@benchmark", diff --git a/test/extensions/access_loggers/stats/stats_speed_test.cc b/test/extensions/access_loggers/stats/stats_speed_test.cc index 85fa299314d50..ef40405530966 100644 --- a/test/extensions/access_loggers/stats/stats_speed_test.cc +++ b/test/extensions/access_loggers/stats/stats_speed_test.cc @@ -4,7 +4,7 @@ #include "source/common/stats/thread_local_store.h" #include "source/extensions/access_loggers/stats/stats.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/access_loggers/stats/stats_test.cc b/test/extensions/access_loggers/stats/stats_test.cc index 1f17cfb8cbef2..9d2f84ef1ac6e 100644 --- a/test/extensions/access_loggers/stats/stats_test.cc +++ b/test/extensions/access_loggers/stats/stats_test.cc @@ -9,7 +9,6 @@ #include "test/common/memory/memory_test_utility.h" #include "test/mocks/config/mocks.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/stream_info/mocks.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD index 9f5d51b630ca9..e67a33a44c030 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/BUILD @@ -90,7 +90,7 @@ envoy_cc_test( "//source/common/network:utility_lib", "//source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface:reverse_tunnel_acceptor_lib", "//test/mocks/network:network_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:instance_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:logging_lib", ], @@ -103,7 +103,7 @@ envoy_cc_test( deps = [ "//source/extensions/access_loggers/stream:config", "//source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface:reverse_tunnel_acceptor_lib", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/access_loggers/stream/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/v3:pkg_cc_proto", diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/config_validation_test.cc b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/config_validation_test.cc index cfff6655e9d1f..0a7442edb900b 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/config_validation_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/config_validation_test.cc @@ -4,7 +4,7 @@ #include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h" #include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_reverse_connection_io_handle_test.cc b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_reverse_connection_io_handle_test.cc index 4c37b43e658bb..20449ab3b995c 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_reverse_connection_io_handle_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_reverse_connection_io_handle_test.cc @@ -9,7 +9,6 @@ #include "source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/server/instance.h" #include "gmock/gmock.h" diff --git a/test/extensions/bootstrap/wasm/BUILD b/test/extensions/bootstrap/wasm/BUILD index 5f83c02dc0b79..ee195b7b4ec65 100644 --- a/test/extensions/bootstrap/wasm/BUILD +++ b/test/extensions/bootstrap/wasm/BUILD @@ -41,7 +41,7 @@ envoy_extension_cc_test( "//source/extensions/common/wasm:wasm_lib", "//test/extensions/bootstrap/wasm/test_data:stats_cpp_plugin", "//test/extensions/common/wasm:wasm_runtime", - "//test/mocks/server:server_mocks", + "//test/mocks/local_info:local_info_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", @@ -108,7 +108,7 @@ envoy_extension_cc_test_binary( "//source/extensions/common/wasm:wasm_lib", "//test/extensions/bootstrap/wasm/test_data:speed_cpp_plugin", "//test/extensions/common/wasm:wasm_runtime", - "//test/mocks/server:server_mocks", + "//test/mocks/local_info:local_info_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", diff --git a/test/extensions/bootstrap/wasm/wasm_speed_test.cc b/test/extensions/bootstrap/wasm/wasm_speed_test.cc index 2c1333fbb8a39..fd7c995dd0e87 100644 --- a/test/extensions/bootstrap/wasm/wasm_speed_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_speed_test.cc @@ -8,7 +8,7 @@ #include "source/common/stats/isolated_store_impl.h" #include "source/extensions/common/wasm/wasm.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/local_info/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" diff --git a/test/extensions/bootstrap/wasm/wasm_test.cc b/test/extensions/bootstrap/wasm/wasm_test.cc index 0d9e895445de8..6242ae2225308 100644 --- a/test/extensions/bootstrap/wasm/wasm_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_test.cc @@ -3,7 +3,7 @@ #include "source/extensions/common/wasm/wasm.h" #include "test/extensions/common/wasm/wasm_runtime.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/local_info/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" diff --git a/test/extensions/clusters/aggregate/BUILD b/test/extensions/clusters/aggregate/BUILD index 27775f37caef4..81871517dcfef 100644 --- a/test/extensions/clusters/aggregate/BUILD +++ b/test/extensions/clusters/aggregate/BUILD @@ -22,7 +22,7 @@ envoy_extension_cc_test( "//source/extensions/transport_sockets/raw_buffer:config", "//test/common/upstream:utility_lib", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:load_balancer_context_mock", "//test/mocks/upstream:load_balancer_mocks", "//test/mocks/upstream:priority_set_mocks", diff --git a/test/extensions/clusters/aggregate/cluster_test.cc b/test/extensions/clusters/aggregate/cluster_test.cc index e2ece779d9bf5..b87eeece96a39 100644 --- a/test/extensions/clusters/aggregate/cluster_test.cc +++ b/test/extensions/clusters/aggregate/cluster_test.cc @@ -7,7 +7,7 @@ #include "test/common/upstream/utility.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/load_balancer.h" #include "test/mocks/upstream/load_balancer_context.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/clusters/aggregate/cluster_update_test.cc b/test/extensions/clusters/aggregate/cluster_update_test.cc index 0c99a5c78ba16..00e8c972742ec 100644 --- a/test/extensions/clusters/aggregate/cluster_update_test.cc +++ b/test/extensions/clusters/aggregate/cluster_update_test.cc @@ -12,7 +12,6 @@ #include "test/common/upstream/utility.h" #include "test/mocks/config/xds_manager.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" #include "test/mocks/upstream/cluster_update_callbacks.h" #include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" diff --git a/test/extensions/clusters/dynamic_modules/BUILD b/test/extensions/clusters/dynamic_modules/BUILD index 34ecf00e5d820..eb99f547812ba 100644 --- a/test/extensions/clusters/dynamic_modules/BUILD +++ b/test/extensions/clusters/dynamic_modules/BUILD @@ -29,7 +29,7 @@ envoy_cc_test( "//test/extensions/dynamic_modules:util", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:load_balancer_context_mock", "//test/mocks/upstream:priority_set_mocks", diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index 6729ff7a268f5..37bf05dee2447 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -12,7 +12,7 @@ #include "test/extensions/dynamic_modules/util.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/connection.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/load_balancer_context.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/clusters/eds/BUILD b/test/extensions/clusters/eds/BUILD index ba43b15e5ae29..63d45862a57c0 100644 --- a/test/extensions/clusters/eds/BUILD +++ b/test/extensions/clusters/eds/BUILD @@ -29,7 +29,7 @@ envoy_cc_test( "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:health_checker_mocks", @@ -63,7 +63,8 @@ envoy_cc_benchmark_binary( "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:options_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:test_runtime_lib", @@ -97,7 +98,7 @@ envoy_cc_test( "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:utility_lib", diff --git a/test/extensions/clusters/eds/eds_speed_test.cc b/test/extensions/clusters/eds/eds_speed_test.cc index 67d045b8bc07c..7c01947c9248d 100644 --- a/test/extensions/clusters/eds/eds_speed_test.cc +++ b/test/extensions/clusters/eds/eds_speed_test.cc @@ -25,8 +25,8 @@ #include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/options.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/clusters/eds/eds_test.cc b/test/extensions/clusters/eds/eds_test.cc index d6f0e9d08516c..6bec9dfe88d6c 100644 --- a/test/extensions/clusters/eds/eds_test.cc +++ b/test/extensions/clusters/eds/eds_test.cc @@ -17,7 +17,7 @@ #include "test/mocks/config/eds_resources_cache.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/health_checker.h" diff --git a/test/extensions/clusters/eds/leds_test.cc b/test/extensions/clusters/eds/leds_test.cc index 39e25ca15ef5d..09675ccddf51a 100644 --- a/test/extensions/clusters/eds/leds_test.cc +++ b/test/extensions/clusters/eds/leds_test.cc @@ -13,7 +13,7 @@ #include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/utility.h" diff --git a/test/extensions/clusters/logical_dns/BUILD b/test/extensions/clusters/logical_dns/BUILD index b95be0057a6cf..28d2432da0c66 100644 --- a/test/extensions/clusters/logical_dns/BUILD +++ b/test/extensions/clusters/logical_dns/BUILD @@ -26,7 +26,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc b/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc index 98c31ec08b6b8..7e91711fd8a9c 100644 --- a/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc +++ b/test/extensions/clusters/logical_dns/logical_dns_cluster_test.cc @@ -24,7 +24,7 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/clusters/mcp_multicluster/BUILD b/test/extensions/clusters/mcp_multicluster/BUILD index 7eb0fbd0a8791..8858a2ccad707 100644 --- a/test/extensions/clusters/mcp_multicluster/BUILD +++ b/test/extensions/clusters/mcp_multicluster/BUILD @@ -24,7 +24,7 @@ envoy_extension_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/network:connection_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/mocks/upstream:load_balancer_context_mock", diff --git a/test/extensions/clusters/mcp_multicluster/cluster_test.cc b/test/extensions/clusters/mcp_multicluster/cluster_test.cc index 80f29fb11a46e..9b36b1ba662b4 100644 --- a/test/extensions/clusters/mcp_multicluster/cluster_test.cc +++ b/test/extensions/clusters/mcp_multicluster/cluster_test.cc @@ -8,7 +8,6 @@ #include "test/mocks/http/conn_pool.h" #include "test/mocks/network/connection.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/upstream/cluster_info.h" diff --git a/test/extensions/clusters/original_dst/BUILD b/test/extensions/clusters/original_dst/BUILD index b226dcc7b33a0..fc09a1a57a0f2 100644 --- a/test/extensions/clusters/original_dst/BUILD +++ b/test/extensions/clusters/original_dst/BUILD @@ -24,7 +24,7 @@ envoy_cc_test( "//test/mocks:common_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", diff --git a/test/extensions/clusters/original_dst/original_dst_cluster_test.cc b/test/extensions/clusters/original_dst/original_dst_cluster_test.cc index 05ea8478c3e3f..fbd9c357339a7 100644 --- a/test/extensions/clusters/original_dst/original_dst_cluster_test.cc +++ b/test/extensions/clusters/original_dst/original_dst_cluster_test.cc @@ -20,7 +20,7 @@ #include "test/mocks/common.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/utility.h" diff --git a/test/extensions/clusters/redis/BUILD b/test/extensions/clusters/redis/BUILD index 2f16341c58744..95d1981d201b1 100644 --- a/test/extensions/clusters/redis/BUILD +++ b/test/extensions/clusters/redis/BUILD @@ -39,7 +39,7 @@ envoy_extension_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:health_check_event_logger_mocks", diff --git a/test/extensions/clusters/redis/redis_cluster_test.cc b/test/extensions/clusters/redis/redis_cluster_test.cc index 363b56f18ec9b..582f25bd83893 100644 --- a/test/extensions/clusters/redis/redis_cluster_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_test.cc @@ -23,7 +23,7 @@ #include "test/mocks/common.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/cluster_priority_set.h" #include "test/mocks/upstream/health_check_event_logger.h" diff --git a/test/extensions/clusters/reverse_connection/BUILD b/test/extensions/clusters/reverse_connection/BUILD index 1d57201e14b7d..b07e0c32759d6 100644 --- a/test/extensions/clusters/reverse_connection/BUILD +++ b/test/extensions/clusters/reverse_connection/BUILD @@ -29,7 +29,6 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:factory_context_mocks", "//test/mocks/server:instance_mocks", "//test/test_common:registry_lib", "//test/test_common:threadsafe_singleton_injector_lib", diff --git a/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc b/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc index 937895d94b425..69375f67706ae 100644 --- a/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc +++ b/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc @@ -28,7 +28,6 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/server/instance.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/upstream/cluster_manager.h" diff --git a/test/extensions/common/async_files/BUILD b/test/extensions/common/async_files/BUILD index 4c26b5dbef575..5cec63c89f472 100644 --- a/test/extensions/common/async_files/BUILD +++ b/test/extensions/common/async_files/BUILD @@ -15,10 +15,12 @@ envoy_cc_test( rbe_pool = "6gig", tags = ["skip_on_windows"], deps = [ + "//source/common/singleton:manager_impl_lib", "//source/extensions/common/async_files", "//test/mocks/api:api_mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/buffer:buffer_mocks", "//test/test_common:status_utility_lib", + "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/common/async_files/v3:pkg_cc_proto", ], ) @@ -32,9 +34,10 @@ envoy_cc_test( rbe_pool = "6gig", tags = ["skip_on_windows"], deps = [ + "//source/common/api:os_sys_calls_lib", + "//source/common/singleton:manager_impl_lib", "//source/extensions/common/async_files", "//test/mocks/api:api_mocks", - "//test/mocks/server:server_mocks", "//test/test_common:status_utility_lib", "@envoy_api//envoy/extensions/common/async_files/v3:pkg_cc_proto", ], @@ -48,9 +51,9 @@ envoy_cc_test( rbe_pool = "6gig", tags = ["skip_on_windows"], deps = [ + "//source/common/singleton:manager_impl_lib", "//source/extensions/common/async_files", "//test/mocks/api:api_mocks", - "//test/mocks/server:server_mocks", "//test/test_common:status_utility_lib", "@envoy_api//envoy/extensions/common/async_files/v3:pkg_cc_proto", ], diff --git a/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc b/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc index 71fa10e5111e9..3713a565a7015 100644 --- a/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc +++ b/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc @@ -8,13 +8,16 @@ #include "envoy/extensions/common/async_files/v3/async_file_manager.pb.h" #include "source/common/buffer/buffer_impl.h" +#include "source/common/singleton/manager_impl.h" #include "source/extensions/common/async_files/async_file_action.h" #include "source/extensions/common/async_files/async_file_handle.h" #include "source/extensions/common/async_files/async_file_manager.h" #include "source/extensions/common/async_files/async_file_manager_factory.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/api/mocks.h" +#include "test/mocks/buffer/mocks.h" #include "test/test_common/status_utility.h" +#include "test/test_common/utility.h" #include "absl/status/statusor.h" #include "gmock/gmock.h" diff --git a/test/extensions/common/async_files/async_file_manager_factory_test.cc b/test/extensions/common/async_files/async_file_manager_factory_test.cc index f2afd824354c1..1ba78a6dc9279 100644 --- a/test/extensions/common/async_files/async_file_manager_factory_test.cc +++ b/test/extensions/common/async_files/async_file_manager_factory_test.cc @@ -5,13 +5,13 @@ #include "envoy/extensions/common/async_files/v3/async_file_manager.pb.h" +#include "source/common/singleton/manager_impl.h" #include "source/extensions/common/async_files/async_file_action.h" #include "source/extensions/common/async_files/async_file_handle.h" #include "source/extensions/common/async_files/async_file_manager.h" #include "source/extensions/common/async_files/async_file_manager_factory.h" #include "test/mocks/api/mocks.h" -#include "test/mocks/server/mocks.h" #include "test/test_common/utility.h" #include "absl/base/thread_annotations.h" diff --git a/test/extensions/common/async_files/async_file_manager_thread_pool_test.cc b/test/extensions/common/async_files/async_file_manager_thread_pool_test.cc index 7956b61db2cce..f66525b3069e9 100644 --- a/test/extensions/common/async_files/async_file_manager_thread_pool_test.cc +++ b/test/extensions/common/async_files/async_file_manager_thread_pool_test.cc @@ -6,12 +6,13 @@ #include "envoy/extensions/common/async_files/v3/async_file_manager.pb.h" +#include "source/common/api/os_sys_calls_impl.h" +#include "source/common/singleton/manager_impl.h" #include "source/extensions/common/async_files/async_file_action.h" #include "source/extensions/common/async_files/async_file_handle.h" #include "source/extensions/common/async_files/async_file_manager.h" #include "source/extensions/common/async_files/async_file_manager_factory.h" -#include "test/mocks/server/mocks.h" #include "test/test_common/status_utility.h" #include "test/test_common/utility.h" diff --git a/test/extensions/common/async_files/async_file_manager_thread_pool_with_mocks_test.cc b/test/extensions/common/async_files/async_file_manager_thread_pool_with_mocks_test.cc index 8e058436217d3..c6d98e738410a 100644 --- a/test/extensions/common/async_files/async_file_manager_thread_pool_with_mocks_test.cc +++ b/test/extensions/common/async_files/async_file_manager_thread_pool_with_mocks_test.cc @@ -7,13 +7,13 @@ #include "envoy/extensions/common/async_files/v3/async_file_manager.pb.h" +#include "source/common/singleton/manager_impl.h" #include "source/extensions/common/async_files/async_file_action.h" #include "source/extensions/common/async_files/async_file_handle.h" #include "source/extensions/common/async_files/async_file_manager.h" #include "source/extensions/common/async_files/async_file_manager_factory.h" #include "test/mocks/api/mocks.h" -#include "test/mocks/server/mocks.h" #include "test/test_common/status_utility.h" #include "test/test_common/utility.h" diff --git a/test/extensions/common/dynamic_forward_proxy/BUILD b/test/extensions/common/dynamic_forward_proxy/BUILD index c85e0369b61ba..481bb3379756d 100644 --- a/test/extensions/common/dynamic_forward_proxy/BUILD +++ b/test/extensions/common/dynamic_forward_proxy/BUILD @@ -20,7 +20,7 @@ envoy_cc_test( "//source/extensions/common/dynamic_forward_proxy:dns_cache_manager_impl", "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:registry_lib", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", diff --git a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc index e68c91ab49038..2604233951e1c 100644 --- a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc +++ b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc @@ -10,7 +10,7 @@ #include "test/extensions/common/dynamic_forward_proxy/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/registry.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" diff --git a/test/extensions/common/matcher/BUILD b/test/extensions/common/matcher/BUILD index 345fe7fc6fa52..1015148ca3b3c 100644 --- a/test/extensions/common/matcher/BUILD +++ b/test/extensions/common/matcher/BUILD @@ -35,7 +35,7 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/matcher:matcher_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:registry_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -56,7 +56,7 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/matcher:matcher_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:registry_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/common/matcher/domain_matcher_test.cc b/test/extensions/common/matcher/domain_matcher_test.cc index c867776496f4a..87a6928283273 100644 --- a/test/extensions/common/matcher/domain_matcher_test.cc +++ b/test/extensions/common/matcher/domain_matcher_test.cc @@ -13,7 +13,7 @@ #include "test/common/matcher/test_utility.h" #include "test/mocks/matcher/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/registry.h" #include "test/test_common/utility.h" diff --git a/test/extensions/common/matcher/ip_range_matcher_test.cc b/test/extensions/common/matcher/ip_range_matcher_test.cc index 410d9d3f0a37d..22d7dcc07ee8e 100644 --- a/test/extensions/common/matcher/ip_range_matcher_test.cc +++ b/test/extensions/common/matcher/ip_range_matcher_test.cc @@ -13,7 +13,7 @@ #include "test/common/matcher/test_utility.h" #include "test/mocks/matcher/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/registry.h" #include "test/test_common/utility.h" diff --git a/test/extensions/common/tap/BUILD b/test/extensions/common/tap/BUILD index ccfb7cddc3e20..d2205d85f9745 100644 --- a/test/extensions/common/tap/BUILD +++ b/test/extensions/common/tap/BUILD @@ -30,7 +30,7 @@ envoy_cc_test( "//source/extensions/common/tap:tap_config_base", "//test/mocks/server:admin_mocks", "//test/mocks/server:admin_stream_mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:logging_lib", "//test/test_common:registry_lib", "@envoy_api//envoy/config/tap/v3:pkg_cc_proto", diff --git a/test/extensions/common/tap/admin_test.cc b/test/extensions/common/tap/admin_test.cc index ec3c7e2cabb92..63763db6b5334 100644 --- a/test/extensions/common/tap/admin_test.cc +++ b/test/extensions/common/tap/admin_test.cc @@ -9,7 +9,7 @@ #include "test/mocks/server/admin.h" #include "test/mocks/server/admin_stream.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/logging.h" #include "test/test_common/registry.h" diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index f185858a54c80..6e39c3bb1b222 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -57,7 +57,9 @@ envoy_cc_test( "//test/extensions/common/wasm/test_data:test_cpp_plugin", "//test/extensions/common/wasm/test_data:test_restriction_cpp_plugin", "//test/mocks/local_reply:local_reply_mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:overload_manager_mocks", + "//test/mocks/server:server_factory_context_mocks", + "//test/mocks/server:server_lifecycle_notifier_mocks", "//test/test_common:environment_lib", "//test/test_common:registry_lib", "//test/test_common:simulated_time_system_lib", @@ -87,7 +89,6 @@ envoy_cc_test_binary( "//source/common/event:dispatcher_lib", "//source/extensions/common/wasm:wasm_lib", "//test/extensions/common/wasm:wasm_runtime", - "//test/mocks/server:server_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", "@abseil-cpp//absl/types:optional", diff --git a/test/extensions/common/wasm/foreign_test.cc b/test/extensions/common/wasm/foreign_test.cc index 2edea699acd26..7d1634074d3ba 100644 --- a/test/extensions/common/wasm/foreign_test.cc +++ b/test/extensions/common/wasm/foreign_test.cc @@ -9,7 +9,6 @@ #include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/http/mocks.h" #include "test/mocks/local_info/mocks.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/utility.h" #include "test/test_common/wasm_base.h" diff --git a/test/extensions/common/wasm/wasm_speed_test.cc b/test/extensions/common/wasm/wasm_speed_test.cc index f2e9371267e04..61380ef3a0f69 100644 --- a/test/extensions/common/wasm/wasm_speed_test.cc +++ b/test/extensions/common/wasm/wasm_speed_test.cc @@ -2,7 +2,6 @@ #include "source/common/common/thread_synchronizer.h" #include "source/extensions/common/wasm/wasm.h" -#include "test/mocks/server/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/thread_factory_for_test.h" diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index 6c3701599b299..6d8e60e65e541 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -11,7 +11,9 @@ #include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/local_reply/mocks.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/server/overload_manager.h" +#include "test/mocks/server/server_factory_context.h" +#include "test/mocks/server/server_lifecycle_notifier.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/environment.h" diff --git a/test/extensions/content_parsers/json/BUILD b/test/extensions/content_parsers/json/BUILD index d2be6f6fce2fc..df78cc048f417 100644 --- a/test/extensions/content_parsers/json/BUILD +++ b/test/extensions/content_parsers/json/BUILD @@ -28,7 +28,7 @@ envoy_extension_cc_test( extension_names = ["envoy.content_parsers.json"], deps = [ "//source/extensions/content_parsers/json:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/content_parsers/json/config_test.cc b/test/extensions/content_parsers/json/config_test.cc index b31eb343a5047..0a8d17e706315 100644 --- a/test/extensions/content_parsers/json/config_test.cc +++ b/test/extensions/content_parsers/json/config_test.cc @@ -1,6 +1,6 @@ #include "source/extensions/content_parsers/json/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/dynamic_modules/bootstrap/BUILD b/test/extensions/dynamic_modules/bootstrap/BUILD index abfef68d91226..10da32d344e16 100644 --- a/test/extensions/dynamic_modules/bootstrap/BUILD +++ b/test/extensions/dynamic_modules/bootstrap/BUILD @@ -102,7 +102,7 @@ envoy_cc_test( ], deps = [ "//source/extensions/bootstrap/dynamic_modules:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/bootstrap/dynamic_modules/v3:pkg_cc_proto", diff --git a/test/extensions/dynamic_modules/bootstrap/factory_test.cc b/test/extensions/dynamic_modules/bootstrap/factory_test.cc index 77099f6e3e6f8..8864bbf6b3f4f 100644 --- a/test/extensions/dynamic_modules/bootstrap/factory_test.cc +++ b/test/extensions/dynamic_modules/bootstrap/factory_test.cc @@ -3,7 +3,7 @@ #include "source/common/protobuf/protobuf.h" #include "source/extensions/bootstrap/dynamic_modules/factory.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/common/rbac/BUILD b/test/extensions/filters/common/rbac/BUILD index f28647786dcdc..31a6b10730e0a 100644 --- a/test/extensions/filters/common/rbac/BUILD +++ b/test/extensions/filters/common/rbac/BUILD @@ -46,7 +46,7 @@ envoy_extension_cc_test( "//source/extensions/filters/http/rbac:rbac_filter_lib", "//source/extensions/matching/network/common:inputs_lib", "//test/mocks/network:network_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:utility_lib", @@ -65,7 +65,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/common/rbac:utility_lib", "//source/extensions/filters/http/rbac:rbac_filter_lib", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/common/rbac/engine_impl_test.cc b/test/extensions/filters/common/rbac/engine_impl_test.cc index 4c333bd7758ca..e1a61011b62ed 100644 --- a/test/extensions/filters/common/rbac/engine_impl_test.cc +++ b/test/extensions/filters/common/rbac/engine_impl_test.cc @@ -9,7 +9,7 @@ #include "source/extensions/filters/http/rbac/rbac_filter.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/common/rbac/utility_test.cc b/test/extensions/filters/common/rbac/utility_test.cc index 37525eb004aa1..9fa7d0bbdcab1 100644 --- a/test/extensions/filters/common/rbac/utility_test.cc +++ b/test/extensions/filters/common/rbac/utility_test.cc @@ -1,7 +1,7 @@ #include "source/extensions/filters/common/rbac/utility.h" #include "source/extensions/filters/http/rbac/rbac_filter.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" namespace Envoy { diff --git a/test/extensions/filters/common/set_filter_state/BUILD b/test/extensions/filters/common/set_filter_state/BUILD index 20adfff075e80..555ef1fd8473e 100644 --- a/test/extensions/filters/common/set_filter_state/BUILD +++ b/test/extensions/filters/common/set_filter_state/BUILD @@ -18,7 +18,7 @@ envoy_cc_test( "//source/common/router:string_accessor_lib", "//source/extensions/filters/common/set_filter_state:filter_config_lib", "//source/server:generic_factory_context_lib", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:utility_lib", ], diff --git a/test/extensions/filters/common/set_filter_state/filter_config_test.cc b/test/extensions/filters/common/set_filter_state/filter_config_test.cc index bd4fd4b9a9450..8483e7462c190 100644 --- a/test/extensions/filters/common/set_filter_state/filter_config_test.cc +++ b/test/extensions/filters/common/set_filter_state/filter_config_test.cc @@ -4,7 +4,7 @@ #include "source/extensions/filters/common/set_filter_state/filter_config.h" #include "source/server/generic_factory_context.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/composite/BUILD b/test/extensions/filters/http/composite/BUILD index 1aa7ff94f89c8..93ee5fa808c1a 100644 --- a/test/extensions/filters/http/composite/BUILD +++ b/test/extensions/filters/http/composite/BUILD @@ -28,7 +28,6 @@ envoy_extension_cc_test( "//test/mocks/access_log:access_log_mocks", "//test/mocks/http:http_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:registry_lib", ], diff --git a/test/extensions/filters/http/composite/filter_test.cc b/test/extensions/filters/http/composite/filter_test.cc index bd920a42de943..8cfaa464541f8 100644 --- a/test/extensions/filters/http/composite/filter_test.cc +++ b/test/extensions/filters/http/composite/filter_test.cc @@ -15,7 +15,6 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/registry.h" diff --git a/test/extensions/filters/http/custom_response/BUILD b/test/extensions/filters/http/custom_response/BUILD index ed5e284435508..6689aa5c2415d 100644 --- a/test/extensions/filters/http/custom_response/BUILD +++ b/test/extensions/filters/http/custom_response/BUILD @@ -67,8 +67,7 @@ envoy_extension_cc_test( "//source/extensions/http/custom_response/redirect_policy:redirect_policy_lib", "//source/extensions/matching/network/common:inputs_lib", "//test/common/http:common_lib", - "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/custom_response/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/custom_response/custom_response_filter_test.cc b/test/extensions/filters/http/custom_response/custom_response_filter_test.cc index deb68ce3b86e1..bae354695df1b 100644 --- a/test/extensions/filters/http/custom_response/custom_response_filter_test.cc +++ b/test/extensions/filters/http/custom_response/custom_response_filter_test.cc @@ -6,8 +6,7 @@ #include "test/common/http/common.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/server/factory_context.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/file_system_buffer/BUILD b/test/extensions/filters/http/file_system_buffer/BUILD index 938bf923ceca0..3794c07a2230e 100644 --- a/test/extensions/filters/http/file_system_buffer/BUILD +++ b/test/extensions/filters/http/file_system_buffer/BUILD @@ -37,7 +37,8 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/file_system_buffer:config", "//test/extensions/common/async_files:mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:upstream_mocks", ], ) diff --git a/test/extensions/filters/http/file_system_buffer/config_test.cc b/test/extensions/filters/http/file_system_buffer/config_test.cc index 0a2afdd10dff8..03aad39266008 100644 --- a/test/extensions/filters/http/file_system_buffer/config_test.cc +++ b/test/extensions/filters/http/file_system_buffer/config_test.cc @@ -1,7 +1,8 @@ #include "source/extensions/filters/http/file_system_buffer/config.h" #include "source/extensions/filters/http/file_system_buffer/filter.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/http/file_system_buffer/filter_test.cc b/test/extensions/filters/http/file_system_buffer/filter_test.cc index ec34383cbc903..adc18e07526e5 100644 --- a/test/extensions/filters/http/file_system_buffer/filter_test.cc +++ b/test/extensions/filters/http/file_system_buffer/filter_test.cc @@ -6,7 +6,6 @@ #include "test/extensions/common/async_files/mocks.h" #include "test/mocks/buffer/mocks.h" -#include "test/mocks/server/mocks.h" #include "test/mocks/upstream/mocks.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/http/health_check/BUILD b/test/extensions/filters/http/health_check/BUILD index b113a0e72d974..c00b5ef98fcf3 100644 --- a/test/extensions/filters/http/health_check/BUILD +++ b/test/extensions/filters/http/health_check/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//source/common/buffer:buffer_lib", "//source/common/http:header_utility_lib", "//source/extensions/filters/http/health_check:health_check_lib", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/health_check/health_check_test.cc b/test/extensions/filters/http/health_check/health_check_test.cc index be949c6e88915..51ef16edbe56d 100644 --- a/test/extensions/filters/http/health_check/health_check_test.cc +++ b/test/extensions/filters/http/health_check/health_check_test.cc @@ -8,7 +8,7 @@ #include "source/common/upstream/upstream_impl.h" #include "source/extensions/filters/http/health_check/health_check.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/json_to_metadata/BUILD b/test/extensions/filters/http/json_to_metadata/BUILD index 6bf95f46ac7fc..4aaf9b15cc350 100644 --- a/test/extensions/filters/http/json_to_metadata/BUILD +++ b/test/extensions/filters/http/json_to_metadata/BUILD @@ -19,7 +19,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/json_to_metadata:json_to_metadata_lib", "//test/common/stream_info:test_util", - "//test/mocks/server:server_mocks", "//test/mocks/upstream:upstream_mocks", ], ) diff --git a/test/extensions/filters/http/json_to_metadata/filter_test.cc b/test/extensions/filters/http/json_to_metadata/filter_test.cc index 9b301f24e1e1b..58290f8e1b5c8 100644 --- a/test/extensions/filters/http/json_to_metadata/filter_test.cc +++ b/test/extensions/filters/http/json_to_metadata/filter_test.cc @@ -8,7 +8,6 @@ #include "test/common/stream_info/test_util.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/server/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/local_ratelimit/BUILD b/test/extensions/filters/http/local_ratelimit/BUILD index 366f0e675f80e..fc9974258d517 100644 --- a/test/extensions/filters/http/local_ratelimit/BUILD +++ b/test/extensions/filters/http/local_ratelimit/BUILD @@ -22,7 +22,7 @@ envoy_extension_cc_test( "//source/extensions/filters/http/local_ratelimit:local_ratelimit_lib", "//test/common/stream_info:test_util", "//test/mocks/http:http_mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/local_ratelimit/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/local_ratelimit/filter_test.cc b/test/extensions/filters/http/local_ratelimit/filter_test.cc index 4f45e155e8de5..624fd18260e25 100644 --- a/test/extensions/filters/http/local_ratelimit/filter_test.cc +++ b/test/extensions/filters/http/local_ratelimit/filter_test.cc @@ -4,7 +4,7 @@ #include "source/extensions/filters/http/local_ratelimit/local_ratelimit.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/test_runtime.h" #include "test/test_common/thread_factory_for_test.h" diff --git a/test/extensions/filters/http/proto_message_extraction/BUILD b/test/extensions/filters/http/proto_message_extraction/BUILD index 309af62ead1f1..b6ebbb6989dd5 100644 --- a/test/extensions/filters/http/proto_message_extraction/BUILD +++ b/test/extensions/filters/http/proto_message_extraction/BUILD @@ -43,7 +43,7 @@ envoy_cc_test( "//source/extensions/filters/http/proto_message_extraction:extractor_impl", "//source/extensions/filters/http/proto_message_extraction:filter_config", "//test/mocks/http:http_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/proto:apikeys_proto_cc_proto", "//test/proto:bookstore_proto_cc_proto", "//test/test_common:environment_lib", diff --git a/test/extensions/filters/http/proto_message_extraction/filter_config_test.cc b/test/extensions/filters/http/proto_message_extraction/filter_config_test.cc index 5d5500d168f19..88e7f87efd7cf 100644 --- a/test/extensions/filters/http/proto_message_extraction/filter_config_test.cc +++ b/test/extensions/filters/http/proto_message_extraction/filter_config_test.cc @@ -4,7 +4,7 @@ #include "source/extensions/filters/http/proto_message_extraction/filter_config.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/proto/apikeys.pb.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" diff --git a/test/extensions/filters/http/ratelimit/BUILD b/test/extensions/filters/http/ratelimit/BUILD index da4e6b9414767..463f1ff3f01a4 100644 --- a/test/extensions/filters/http/ratelimit/BUILD +++ b/test/extensions/filters/http/ratelimit/BUILD @@ -43,7 +43,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/ratelimit:config", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/ratelimit/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/ratelimit/config_test.cc b/test/extensions/filters/http/ratelimit/config_test.cc index 699b1e664a875..fb942555b21e0 100644 --- a/test/extensions/filters/http/ratelimit/config_test.cc +++ b/test/extensions/filters/http/ratelimit/config_test.cc @@ -5,7 +5,6 @@ #include "source/extensions/filters/http/ratelimit/config.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "absl/strings/str_cat.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/http/rbac/BUILD b/test/extensions/filters/http/rbac/BUILD index 549ff23508a4b..d9369c6cf692d 100644 --- a/test/extensions/filters/http/rbac/BUILD +++ b/test/extensions/filters/http/rbac/BUILD @@ -47,7 +47,7 @@ envoy_extension_cc_test( "//test/extensions/filters/http/rbac:route_config_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "@envoy_api//envoy/config/rbac/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/rbac/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/matching/common_inputs/network/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/rbac/rbac_filter_test.cc b/test/extensions/filters/http/rbac/rbac_filter_test.cc index f47846665285e..2f4444c675a32 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_test.cc @@ -15,7 +15,7 @@ #include "test/extensions/filters/http/rbac/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "xds/type/matcher/v3/matcher.pb.h" diff --git a/test/extensions/filters/http/sse_to_metadata/BUILD b/test/extensions/filters/http/sse_to_metadata/BUILD index ade1cc3708922..f4a6d8a8d423c 100644 --- a/test/extensions/filters/http/sse_to_metadata/BUILD +++ b/test/extensions/filters/http/sse_to_metadata/BUILD @@ -33,7 +33,7 @@ envoy_extension_cc_test( "//source/extensions/filters/http/sse_to_metadata:sse_to_metadata_lib", "//test/common/stream_info:test_util", "//test/mocks/http:http_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/content_parsers/json/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/sse_to_metadata/filter_test.cc b/test/extensions/filters/http/sse_to_metadata/filter_test.cc index f0234da64c1f9..d5e8422fb52e3 100644 --- a/test/extensions/filters/http/sse_to_metadata/filter_test.cc +++ b/test/extensions/filters/http/sse_to_metadata/filter_test.cc @@ -6,7 +6,7 @@ #include "test/common/stream_info/test_util.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/http/thrift_to_metadata/BUILD b/test/extensions/filters/http/thrift_to_metadata/BUILD index 89dec79622f18..f4b04a82b70fd 100644 --- a/test/extensions/filters/http/thrift_to_metadata/BUILD +++ b/test/extensions/filters/http/thrift_to_metadata/BUILD @@ -21,7 +21,6 @@ envoy_extension_cc_test( "//source/extensions/filters/network/thrift_proxy:protocol_converter_lib", "//test/common/buffer:utility_lib", "//test/common/stream_info:test_util", - "//test/mocks/server:server_mocks", "//test/mocks/upstream:upstream_mocks", ], ) diff --git a/test/extensions/filters/http/thrift_to_metadata/filter_test.cc b/test/extensions/filters/http/thrift_to_metadata/filter_test.cc index cc863757bdf3c..61aa585e4f4f4 100644 --- a/test/extensions/filters/http/thrift_to_metadata/filter_test.cc +++ b/test/extensions/filters/http/thrift_to_metadata/filter_test.cc @@ -9,7 +9,6 @@ #include "test/common/buffer/utility.h" #include "test/common/stream_info/test_util.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/server/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/network/dubbo_proxy/BUILD b/test/extensions/filters/network/dubbo_proxy/BUILD index dc2e6e2a1f18b..7b650afd70e41 100644 --- a/test/extensions/filters/network/dubbo_proxy/BUILD +++ b/test/extensions/filters/network/dubbo_proxy/BUILD @@ -132,7 +132,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/network/dubbo_proxy:metadata_lib", "//source/extensions/filters/network/dubbo_proxy/router:route_matcher", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "@envoy_api//envoy/extensions/filters/network/dubbo_proxy/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc b/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc index 649fa639fb8b8..3224420f5b1c2 100644 --- a/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc @@ -7,7 +7,7 @@ #include "source/extensions/filters/network/dubbo_proxy/message_impl.h" #include "source/extensions/filters/network/dubbo_proxy/router/route_matcher.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/network/generic_proxy/BUILD b/test/extensions/filters/network/generic_proxy/BUILD index d317667047610..4a9a3e3bd6c76 100644 --- a/test/extensions/filters/network/generic_proxy/BUILD +++ b/test/extensions/filters/network/generic_proxy/BUILD @@ -34,7 +34,7 @@ envoy_cc_test( "//source/extensions/filters/network/generic_proxy:route_lib", "//test/extensions/filters/network/generic_proxy/mocks:filter_mocks", "//test/extensions/filters/network/generic_proxy/mocks:route_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", @@ -115,7 +115,6 @@ envoy_cc_test( "//test/extensions/filters/network/generic_proxy/mocks:route_mocks", "//test/integration:base_integration_test_lib", "//test/integration:common_extensions_lib", - "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/test_common:registry_lib", "//test/test_common:utility_lib", @@ -180,6 +179,6 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/filters/network/generic_proxy:stats_lib", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", ], ) diff --git a/test/extensions/filters/network/generic_proxy/codecs/http1/BUILD b/test/extensions/filters/network/generic_proxy/codecs/http1/BUILD index 567a824949e13..ce8ff6d588dc1 100644 --- a/test/extensions/filters/network/generic_proxy/codecs/http1/BUILD +++ b/test/extensions/filters/network/generic_proxy/codecs/http1/BUILD @@ -17,6 +17,6 @@ envoy_cc_test( deps = [ "//source/extensions/filters/network/generic_proxy/codecs/http1:config", "//test/extensions/filters/network/generic_proxy/mocks:codec_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", ], ) diff --git a/test/extensions/filters/network/generic_proxy/codecs/http1/config_test.cc b/test/extensions/filters/network/generic_proxy/codecs/http1/config_test.cc index b445334b652ac..6fa83087e1134 100644 --- a/test/extensions/filters/network/generic_proxy/codecs/http1/config_test.cc +++ b/test/extensions/filters/network/generic_proxy/codecs/http1/config_test.cc @@ -4,7 +4,7 @@ #include "source/extensions/filters/network/generic_proxy/codecs/http1/config.h" #include "test/extensions/filters/network/generic_proxy/mocks/codec.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/network/generic_proxy/integration_test.cc b/test/extensions/filters/network/generic_proxy/integration_test.cc index 94c8bda2ea709..8751a6bc03b78 100644 --- a/test/extensions/filters/network/generic_proxy/integration_test.cc +++ b/test/extensions/filters/network/generic_proxy/integration_test.cc @@ -11,7 +11,6 @@ #include "test/extensions/filters/network/generic_proxy/mocks/filter.h" #include "test/extensions/filters/network/generic_proxy/mocks/route.h" #include "test/integration/base_integration_test.h" -#include "test/mocks/server/factory_context.h" #include "test/test_common/registry.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/network/generic_proxy/route_test.cc b/test/extensions/filters/network/generic_proxy/route_test.cc index 3eb597570f12b..379b239483a33 100644 --- a/test/extensions/filters/network/generic_proxy/route_test.cc +++ b/test/extensions/filters/network/generic_proxy/route_test.cc @@ -7,7 +7,7 @@ #include "test/extensions/filters/network/generic_proxy/fake_codec.h" #include "test/extensions/filters/network/generic_proxy/mocks/filter.h" #include "test/extensions/filters/network/generic_proxy/mocks/route.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/filters/network/generic_proxy/router/BUILD b/test/extensions/filters/network/generic_proxy/router/BUILD index ec391e52e7f3e..abdfd5b5cd2dc 100644 --- a/test/extensions/filters/network/generic_proxy/router/BUILD +++ b/test/extensions/filters/network/generic_proxy/router/BUILD @@ -39,7 +39,8 @@ envoy_cc_test( "//test/extensions/filters/network/generic_proxy/mocks:codec_mocks", "//test/extensions/filters/network/generic_proxy/mocks:filter_mocks", "//test/extensions/filters/network/generic_proxy/mocks:route_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/network:connection_mocks", + "//test/mocks/upstream:thread_local_cluster_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/network/generic_proxy/router/upstream_test.cc b/test/extensions/filters/network/generic_proxy/router/upstream_test.cc index a7b683eb8f0ae..b31c742182b1c 100644 --- a/test/extensions/filters/network/generic_proxy/router/upstream_test.cc +++ b/test/extensions/filters/network/generic_proxy/router/upstream_test.cc @@ -7,12 +7,15 @@ #include "test/extensions/filters/network/generic_proxy/mocks/codec.h" #include "test/extensions/filters/network/generic_proxy/mocks/filter.h" #include "test/extensions/filters/network/generic_proxy/mocks/route.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/network/connection.h" +#include "test/mocks/upstream/thread_local_cluster.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::_; +using testing::ByMove; using testing::NiceMock; using testing::Return; diff --git a/test/extensions/filters/network/generic_proxy/stats_test.cc b/test/extensions/filters/network/generic_proxy/stats_test.cc index 738952a872a9f..e1770c6053265 100644 --- a/test/extensions/filters/network/generic_proxy/stats_test.cc +++ b/test/extensions/filters/network/generic_proxy/stats_test.cc @@ -1,6 +1,6 @@ #include "source/extensions/filters/network/generic_proxy/stats.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/network/geoip/BUILD b/test/extensions/filters/network/geoip/BUILD index 10755f469668e..39cf2cf0a2c6d 100644 --- a/test/extensions/filters/network/geoip/BUILD +++ b/test/extensions/filters/network/geoip/BUILD @@ -21,7 +21,6 @@ envoy_cc_test( "//source/extensions/filters/network/geoip:geoip_filter_lib", "//test/extensions/filters/http/geoip:geoip_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/server:factory_context_mocks", "//test/mocks/stats:stats_mocks", "//test/test_common:logging_lib", "//test/test_common:registry_lib", diff --git a/test/extensions/filters/network/geoip/geoip_filter_test.cc b/test/extensions/filters/network/geoip/geoip_filter_test.cc index f710b814fd635..afc1c35741f37 100644 --- a/test/extensions/filters/network/geoip/geoip_filter_test.cc +++ b/test/extensions/filters/network/geoip/geoip_filter_test.cc @@ -8,7 +8,6 @@ #include "test/extensions/filters/http/geoip/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/registry.h" diff --git a/test/extensions/filters/network/http_connection_manager/config_filter_chain_test.cc b/test/extensions/filters/network/http_connection_manager/config_filter_chain_test.cc index 6ec85637ce1d3..7a23f381c81a5 100644 --- a/test/extensions/filters/network/http_connection_manager/config_filter_chain_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_filter_chain_test.cc @@ -4,7 +4,6 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc b/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc index 5af63a048f207..00cca4f73378b 100644 --- a/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc @@ -6,7 +6,6 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" #include "test/test_common/registry.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/network/ratelimit/BUILD b/test/extensions/filters/network/ratelimit/BUILD index 1fceaa756d117..383d690c27c27 100644 --- a/test/extensions/filters/network/ratelimit/BUILD +++ b/test/extensions/filters/network/ratelimit/BUILD @@ -46,7 +46,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/network/ratelimit:config", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/ratelimit/v3:pkg_cc_proto", diff --git a/test/extensions/filters/network/ratelimit/config_test.cc b/test/extensions/filters/network/ratelimit/config_test.cc index d1f04d3805c38..40d1338394dc5 100644 --- a/test/extensions/filters/network/ratelimit/config_test.cc +++ b/test/extensions/filters/network/ratelimit/config_test.cc @@ -5,7 +5,6 @@ #include "source/extensions/filters/network/ratelimit/config.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/network/rbac/BUILD b/test/extensions/filters/network/rbac/BUILD index 4f08179478663..39d1452065289 100644 --- a/test/extensions/filters/network/rbac/BUILD +++ b/test/extensions/filters/network/rbac/BUILD @@ -37,7 +37,7 @@ envoy_extension_cc_test( "//source/extensions/filters/network/rbac:rbac_filter", "//source/extensions/matching/network/common:inputs_lib", "//test/mocks/network:network_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "@envoy_api//envoy/config/rbac/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/rbac/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/matching/common_inputs/network/v3:pkg_cc_proto", diff --git a/test/extensions/filters/network/rbac/filter_test.cc b/test/extensions/filters/network/rbac/filter_test.cc index 4229b293b5cbb..eff59b8854c15 100644 --- a/test/extensions/filters/network/rbac/filter_test.cc +++ b/test/extensions/filters/network/rbac/filter_test.cc @@ -12,7 +12,7 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "xds/type/matcher/v3/matcher.pb.h" diff --git a/test/extensions/filters/network/wasm/BUILD b/test/extensions/filters/network/wasm/BUILD index b561b390e23bb..ca44955fd2bc2 100644 --- a/test/extensions/filters/network/wasm/BUILD +++ b/test/extensions/filters/network/wasm/BUILD @@ -60,7 +60,6 @@ envoy_extension_cc_test( "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/filters/network/wasm/test_data:test_cpp_plugin", "//test/mocks/network:network_mocks", - "//test/mocks/server:server_mocks", "//test/test_common:wasm_lib", ], ) diff --git a/test/extensions/filters/network/wasm/wasm_filter_test.cc b/test/extensions/filters/network/wasm/wasm_filter_test.cc index 979642483d5cf..04b8702be0cec 100644 --- a/test/extensions/filters/network/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/network/wasm/wasm_filter_test.cc @@ -6,7 +6,6 @@ #include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/mocks.h" #include "test/test_common/wasm_base.h" #include "gmock/gmock.h" diff --git a/test/extensions/filters/udp/dns_filter/BUILD b/test/extensions/filters/udp/dns_filter/BUILD index 59209cfa5f101..d6f58b28d1338 100644 --- a/test/extensions/filters/udp/dns_filter/BUILD +++ b/test/extensions/filters/udp/dns_filter/BUILD @@ -37,7 +37,6 @@ envoy_extension_cc_test( ":dns_filter_test_lib", "//source/extensions/filters/udp/dns_filter:dns_filter_lib", "//source/extensions/network/dns_resolver/getaddrinfo:config", - "//test/mocks/server:instance_mocks", "//test/mocks/server:listener_factory_context_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", @@ -58,7 +57,6 @@ envoy_extension_cc_test( "//source/extensions/network/dns_resolver/cares:config", "//source/extensions/network/dns_resolver/getaddrinfo:config", "//test/mocks/access_log:access_log_mocks", - "//test/mocks/server:instance_mocks", "//test/mocks/server:listener_factory_context_mocks", "//test/test_common:environment_lib", "//test/test_common:registry_lib", diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc index 0977d04619dc1..f81ce17379024 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc @@ -8,7 +8,6 @@ #include "source/extensions/filters/udp/dns_filter/dns_filter_utils.h" #include "test/mocks/event/mocks.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/listener_factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc index 4d2573c8fd85a..d49153c61c47a 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc @@ -6,7 +6,6 @@ #include "source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.h" #include "test/mocks/event/mocks.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/listener_factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/registry.h" diff --git a/test/extensions/filters/udp/udp_proxy/BUILD b/test/extensions/filters/udp/udp_proxy/BUILD index 5390915796c26..557d894429c3b 100644 --- a/test/extensions/filters/udp/udp_proxy/BUILD +++ b/test/extensions/filters/udp/udp_proxy/BUILD @@ -85,7 +85,7 @@ envoy_extension_cc_test( "//source/common/network:utility_lib", "//source/extensions/filters/udp/udp_proxy/router:router_lib", "//source/extensions/matching/network/common:inputs_lib", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/udp/udp_proxy/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/udp/udp_proxy/router_impl_test.cc b/test/extensions/filters/udp/udp_proxy/router_impl_test.cc index 06aab24dcd5ab..a7105f22cda90 100644 --- a/test/extensions/filters/udp/udp_proxy/router_impl_test.cc +++ b/test/extensions/filters/udp/udp_proxy/router_impl_test.cc @@ -5,7 +5,7 @@ #include "source/common/protobuf/protobuf.h" #include "source/extensions/filters/udp/udp_proxy/router/router_impl.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/health_checkers/redis/BUILD b/test/extensions/health_checkers/redis/BUILD index a9e38d42af9b6..0edf6f98c6a1d 100644 --- a/test/extensions/health_checkers/redis/BUILD +++ b/test/extensions/health_checkers/redis/BUILD @@ -47,7 +47,6 @@ envoy_extension_cc_test( "//source/common/upstream:health_checker_lib", "//source/extensions/health_checkers/redis:config", "//test/common/upstream:utility_lib", - "//test/mocks/server:factory_context_mocks", "//test/mocks/server:health_checker_factory_context_mocks", "//test/mocks/upstream:health_checker_mocks", "//test/mocks/upstream:priority_set_mocks", diff --git a/test/extensions/health_checkers/redis/config_test.cc b/test/extensions/health_checkers/redis/config_test.cc index 3ebd2fa485d3b..0dc325cea37e7 100644 --- a/test/extensions/health_checkers/redis/config_test.cc +++ b/test/extensions/health_checkers/redis/config_test.cc @@ -5,7 +5,6 @@ #include "source/extensions/health_checkers/redis/config.h" #include "test/common/upstream/utility.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/server/health_checker_factory_context.h" #include "test/mocks/upstream/health_checker.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/health_checkers/thrift/BUILD b/test/extensions/health_checkers/thrift/BUILD index f227a9ee6a3aa..a6b7e85029cf0 100644 --- a/test/extensions/health_checkers/thrift/BUILD +++ b/test/extensions/health_checkers/thrift/BUILD @@ -75,7 +75,6 @@ envoy_extension_cc_test( "//source/common/upstream:health_checker_lib", "//source/extensions/health_checkers/thrift:config", "//test/common/upstream:utility_lib", - "//test/mocks/server:factory_context_mocks", "//test/mocks/server:health_checker_factory_context_mocks", "//test/mocks/upstream:health_checker_mocks", "//test/mocks/upstream:priority_set_mocks", diff --git a/test/extensions/health_checkers/thrift/config_test.cc b/test/extensions/health_checkers/thrift/config_test.cc index 280ab53339aba..cb8845cfe1326 100644 --- a/test/extensions/health_checkers/thrift/config_test.cc +++ b/test/extensions/health_checkers/thrift/config_test.cc @@ -4,7 +4,6 @@ #include "source/extensions/health_checkers/thrift/config.h" #include "test/common/upstream/utility.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/server/health_checker_factory_context.h" #include "test/mocks/upstream/health_checker.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/http/header_validators/envoy_default/BUILD b/test/extensions/http/header_validators/envoy_default/BUILD index f97d0c48c6ed8..72609c4321abd 100644 --- a/test/extensions/http/header_validators/envoy_default/BUILD +++ b/test/extensions/http/header_validators/envoy_default/BUILD @@ -113,7 +113,7 @@ envoy_extension_cc_test( "//source/common/network:utility_lib", "//source/extensions/http/header_validators/envoy_default:config", "//test/mocks/http:header_validator_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/http/header_validators/envoy_default/v3:pkg_cc_proto", ], diff --git a/test/extensions/http/header_validators/envoy_default/header_validator_factory_test.cc b/test/extensions/http/header_validators/envoy_default/header_validator_factory_test.cc index 9831a345cf72c..a29538b7f2704 100644 --- a/test/extensions/http/header_validators/envoy_default/header_validator_factory_test.cc +++ b/test/extensions/http/header_validators/envoy_default/header_validator_factory_test.cc @@ -6,7 +6,7 @@ #include "source/extensions/http/header_validators/envoy_default/http2_header_validator.h" #include "test/mocks/http/header_validator.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/http/stateful_session/cookie/BUILD b/test/extensions/http/stateful_session/cookie/BUILD index 9bface0708487..bd37739c80952 100644 --- a/test/extensions/http/stateful_session/cookie/BUILD +++ b/test/extensions/http/stateful_session/cookie/BUILD @@ -33,7 +33,7 @@ envoy_extension_cc_test( deps = [ "//envoy/registry", "//source/extensions/http/stateful_session/cookie:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/http/stateful_session/cookie/config_test.cc b/test/extensions/http/stateful_session/cookie/config_test.cc index 47a3383aaff47..7c94334f478f8 100644 --- a/test/extensions/http/stateful_session/cookie/config_test.cc +++ b/test/extensions/http/stateful_session/cookie/config_test.cc @@ -1,6 +1,6 @@ #include "source/extensions/http/stateful_session/cookie/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/http/stateful_session/envelope/BUILD b/test/extensions/http/stateful_session/envelope/BUILD index 0b318c0b529ea..10faf8452d247 100644 --- a/test/extensions/http/stateful_session/envelope/BUILD +++ b/test/extensions/http/stateful_session/envelope/BUILD @@ -32,7 +32,7 @@ envoy_extension_cc_test( deps = [ "//envoy/registry", "//source/extensions/http/stateful_session/envelope:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/http/stateful_session/envelope/config_test.cc b/test/extensions/http/stateful_session/envelope/config_test.cc index 743de9ea38066..f25ad9da15475 100644 --- a/test/extensions/http/stateful_session/envelope/config_test.cc +++ b/test/extensions/http/stateful_session/envelope/config_test.cc @@ -1,6 +1,6 @@ #include "source/extensions/http/stateful_session/envelope/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/http/stateful_session/header/BUILD b/test/extensions/http/stateful_session/header/BUILD index 5b0e2070e92e9..c897d68e25332 100644 --- a/test/extensions/http/stateful_session/header/BUILD +++ b/test/extensions/http/stateful_session/header/BUILD @@ -32,7 +32,7 @@ envoy_extension_cc_test( deps = [ "//envoy/registry", "//source/extensions/http/stateful_session/header:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/http/stateful_session/header/config_test.cc b/test/extensions/http/stateful_session/header/config_test.cc index 2a763669158b1..bc22e8d902d61 100644 --- a/test/extensions/http/stateful_session/header/config_test.cc +++ b/test/extensions/http/stateful_session/header/config_test.cc @@ -1,6 +1,6 @@ #include "source/extensions/http/stateful_session/header/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD index 3fe95cd238e71..eb2daa60ff5a9 100644 --- a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD +++ b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD @@ -18,7 +18,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/load_balancing_policies/client_side_weighted_round_robin:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:priority_set_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/config_test.cc b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/config_test.cc index bd0c734599b8a..d4c6d478fcf91 100644 --- a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/config_test.cc +++ b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/config_test.cc @@ -2,7 +2,7 @@ #include "source/extensions/load_balancing_policies/client_side_weighted_round_robin/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/load_balancing_policies/cluster_provided/BUILD b/test/extensions/load_balancing_policies/cluster_provided/BUILD index ef527f5ab1b4b..0467581e1091f 100644 --- a/test/extensions/load_balancing_policies/cluster_provided/BUILD +++ b/test/extensions/load_balancing_policies/cluster_provided/BUILD @@ -18,7 +18,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/load_balancing_policies/cluster_provided:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:priority_set_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/load_balancing_policies/cluster_provided/config_test.cc b/test/extensions/load_balancing_policies/cluster_provided/config_test.cc index f50d8358fb700..ab5daab867cfd 100644 --- a/test/extensions/load_balancing_policies/cluster_provided/config_test.cc +++ b/test/extensions/load_balancing_policies/cluster_provided/config_test.cc @@ -2,7 +2,7 @@ #include "source/extensions/load_balancing_policies/cluster_provided/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/load_balancing_policies/dynamic_modules/BUILD b/test/extensions/load_balancing_policies/dynamic_modules/BUILD index df8769440d786..31bee36c1d1bf 100644 --- a/test/extensions/load_balancing_policies/dynamic_modules/BUILD +++ b/test/extensions/load_balancing_policies/dynamic_modules/BUILD @@ -26,7 +26,7 @@ envoy_cc_test( "//source/extensions/load_balancing_policies/dynamic_modules:config", "//source/extensions/load_balancing_policies/dynamic_modules:load_balancer_lib", "//test/extensions/dynamic_modules:util", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:host_mocks", "//test/mocks/upstream:host_set_mocks", diff --git a/test/extensions/load_balancing_policies/dynamic_modules/config_test.cc b/test/extensions/load_balancing_policies/dynamic_modules/config_test.cc index 65bc98434e964..7d24e3eab96fc 100644 --- a/test/extensions/load_balancing_policies/dynamic_modules/config_test.cc +++ b/test/extensions/load_balancing_policies/dynamic_modules/config_test.cc @@ -8,7 +8,7 @@ #include "source/extensions/load_balancing_policies/dynamic_modules/load_balancer.h" #include "test/extensions/dynamic_modules/util.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/host.h" #include "test/mocks/upstream/host_set.h" diff --git a/test/extensions/load_balancing_policies/least_request/BUILD b/test/extensions/load_balancing_policies/least_request/BUILD index bcf201898c573..db88ea982caf1 100644 --- a/test/extensions/load_balancing_policies/least_request/BUILD +++ b/test/extensions/load_balancing_policies/least_request/BUILD @@ -22,7 +22,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/load_balancing_policies/least_request:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:priority_set_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/load_balancing_policies/least_request/config_test.cc b/test/extensions/load_balancing_policies/least_request/config_test.cc index 16d3ee562ee18..f85ccf31d4c3b 100644 --- a/test/extensions/load_balancing_policies/least_request/config_test.cc +++ b/test/extensions/load_balancing_policies/least_request/config_test.cc @@ -2,7 +2,7 @@ #include "source/extensions/load_balancing_policies/least_request/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/load_balancing_policies/maglev/BUILD b/test/extensions/load_balancing_policies/maglev/BUILD index 107e91912fad1..4de66be68799d 100644 --- a/test/extensions/load_balancing_policies/maglev/BUILD +++ b/test/extensions/load_balancing_policies/maglev/BUILD @@ -64,7 +64,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/load_balancing_policies/maglev:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:priority_set_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/load_balancing_policies/maglev/config_test.cc b/test/extensions/load_balancing_policies/maglev/config_test.cc index d482456685f44..11e3b6b502a1e 100644 --- a/test/extensions/load_balancing_policies/maglev/config_test.cc +++ b/test/extensions/load_balancing_policies/maglev/config_test.cc @@ -3,7 +3,7 @@ #include "source/extensions/load_balancing_policies/maglev/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/load_balancing_policies/random/BUILD b/test/extensions/load_balancing_policies/random/BUILD index 0014f2cec84b6..174c24166009a 100644 --- a/test/extensions/load_balancing_policies/random/BUILD +++ b/test/extensions/load_balancing_policies/random/BUILD @@ -22,7 +22,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/load_balancing_policies/random:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:priority_set_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/load_balancing_policies/random/config_test.cc b/test/extensions/load_balancing_policies/random/config_test.cc index 2dc9a367e2595..f851a11f55834 100644 --- a/test/extensions/load_balancing_policies/random/config_test.cc +++ b/test/extensions/load_balancing_policies/random/config_test.cc @@ -2,7 +2,7 @@ #include "source/extensions/load_balancing_policies/random/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/load_balancing_policies/ring_hash/BUILD b/test/extensions/load_balancing_policies/ring_hash/BUILD index 20a51cbfaf54a..563c874cc4b52 100644 --- a/test/extensions/load_balancing_policies/ring_hash/BUILD +++ b/test/extensions/load_balancing_policies/ring_hash/BUILD @@ -45,7 +45,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/load_balancing_policies/ring_hash:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:priority_set_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/load_balancing_policies/ring_hash/config_test.cc b/test/extensions/load_balancing_policies/ring_hash/config_test.cc index 8437ae4595c65..0e60b9c0d1489 100644 --- a/test/extensions/load_balancing_policies/ring_hash/config_test.cc +++ b/test/extensions/load_balancing_policies/ring_hash/config_test.cc @@ -4,7 +4,7 @@ #include "source/extensions/load_balancing_policies/ring_hash/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/load_balancing_policies/round_robin/BUILD b/test/extensions/load_balancing_policies/round_robin/BUILD index 9be16c6636430..a71259f8495d0 100644 --- a/test/extensions/load_balancing_policies/round_robin/BUILD +++ b/test/extensions/load_balancing_policies/round_robin/BUILD @@ -22,7 +22,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/load_balancing_policies/round_robin:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:priority_set_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/load_balancing_policies/round_robin/config_test.cc b/test/extensions/load_balancing_policies/round_robin/config_test.cc index f9cf3feac72b0..d89a8c9ce72ab 100644 --- a/test/extensions/load_balancing_policies/round_robin/config_test.cc +++ b/test/extensions/load_balancing_policies/round_robin/config_test.cc @@ -2,7 +2,7 @@ #include "source/extensions/load_balancing_policies/round_robin/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/load_balancing_policies/subset/BUILD b/test/extensions/load_balancing_policies/subset/BUILD index 5ad2b1d01fd73..6eafd449593bc 100644 --- a/test/extensions/load_balancing_policies/subset/BUILD +++ b/test/extensions/load_balancing_policies/subset/BUILD @@ -65,7 +65,7 @@ envoy_extension_cc_test( "//test/common/upstream:utility_lib", "//test/mocks:common_lib", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:host_mocks", "//test/mocks/upstream:host_set_mocks", @@ -88,7 +88,7 @@ envoy_extension_cc_benchmark_binary( "//source/extensions/load_balancing_policies/random:config", "//source/extensions/load_balancing_policies/subset:config", "//test/extensions/load_balancing_policies/common:benchmark_base_tester_lib", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:load_balancer_mocks", "@benchmark", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", diff --git a/test/extensions/load_balancing_policies/subset/subset_benchmark.cc b/test/extensions/load_balancing_policies/subset/subset_benchmark.cc index 9f9bc953be066..455b9c16114b5 100644 --- a/test/extensions/load_balancing_policies/subset/subset_benchmark.cc +++ b/test/extensions/load_balancing_policies/subset/subset_benchmark.cc @@ -15,7 +15,7 @@ #include "test/benchmark/main.h" #include "test/common/upstream/utility.h" #include "test/extensions/load_balancing_policies/common/benchmark_base_tester.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/load_balancer.h" #include "test/test_common/simulated_time_system.h" diff --git a/test/extensions/load_balancing_policies/subset/subset_test.cc b/test/extensions/load_balancing_policies/subset/subset_test.cc index 46c0bee0cf4fa..e05514c52183f 100644 --- a/test/extensions/load_balancing_policies/subset/subset_test.cc +++ b/test/extensions/load_balancing_policies/subset/subset_test.cc @@ -19,7 +19,7 @@ #include "test/common/upstream/utility.h" #include "test/mocks/common.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/host.h" #include "test/mocks/upstream/host_set.h" diff --git a/test/extensions/load_balancing_policies/wrr_locality/BUILD b/test/extensions/load_balancing_policies/wrr_locality/BUILD index 9ceecb2e80392..7bd52f60ca491 100644 --- a/test/extensions/load_balancing_policies/wrr_locality/BUILD +++ b/test/extensions/load_balancing_policies/wrr_locality/BUILD @@ -19,7 +19,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/load_balancing_policies/random:config", "//source/extensions/load_balancing_policies/wrr_locality:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:priority_set_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/load_balancing_policies/wrr_locality/config_test.cc b/test/extensions/load_balancing_policies/wrr_locality/config_test.cc index cec21f327bb49..8e900c71fde7d 100644 --- a/test/extensions/load_balancing_policies/wrr_locality/config_test.cc +++ b/test/extensions/load_balancing_policies/wrr_locality/config_test.cc @@ -2,7 +2,7 @@ #include "source/extensions/load_balancing_policies/wrr_locality/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/priority_set.h" diff --git a/test/extensions/matching/common_inputs/environment_variable/BUILD b/test/extensions/matching/common_inputs/environment_variable/BUILD index 4a50fb77f9de4..d79a871f92820 100644 --- a/test/extensions/matching/common_inputs/environment_variable/BUILD +++ b/test/extensions/matching/common_inputs/environment_variable/BUILD @@ -18,7 +18,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/matching/common_inputs/environment_variable:config", - "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/matching/common_inputs/environment_variable/config_test.cc b/test/extensions/matching/common_inputs/environment_variable/config_test.cc index 70a795cfae636..ea86b3ca94999 100644 --- a/test/extensions/matching/common_inputs/environment_variable/config_test.cc +++ b/test/extensions/matching/common_inputs/environment_variable/config_test.cc @@ -1,8 +1,8 @@ #include "source/common/config/utility.h" #include "source/extensions/matching/common_inputs/environment_variable/config.h" -#include "test/mocks/server/factory_context.h" #include "test/test_common/environment.h" +#include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/matching/input_matchers/cel_matcher/BUILD b/test/extensions/matching/input_matchers/cel_matcher/BUILD index cb94d0d14454b..ba4ad3bf0a692 100644 --- a/test/extensions/matching/input_matchers/cel_matcher/BUILD +++ b/test/extensions/matching/input_matchers/cel_matcher/BUILD @@ -33,7 +33,7 @@ envoy_extension_cc_test( "//test/common/matcher:test_utility_lib", "//test/mocks/http:http_mocks", "//test/mocks/matcher:matcher_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", diff --git a/test/extensions/matching/input_matchers/cel_matcher/cel_matcher_test.cc b/test/extensions/matching/input_matchers/cel_matcher/cel_matcher_test.cc index 5f6b5c10f9807..c137f420b51fc 100644 --- a/test/extensions/matching/input_matchers/cel_matcher/cel_matcher_test.cc +++ b/test/extensions/matching/input_matchers/cel_matcher/cel_matcher_test.cc @@ -17,7 +17,7 @@ #include "test/common/matcher/test_utility.h" #include "test/mocks/matcher/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/registry.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" diff --git a/test/extensions/matching/input_matchers/consistent_hashing/BUILD b/test/extensions/matching/input_matchers/consistent_hashing/BUILD index 18f823ccae322..bfc20c8668670 100644 --- a/test/extensions/matching/input_matchers/consistent_hashing/BUILD +++ b/test/extensions/matching/input_matchers/consistent_hashing/BUILD @@ -18,7 +18,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/matching/input_matchers/consistent_hashing:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", ], ) diff --git a/test/extensions/matching/input_matchers/consistent_hashing/config_test.cc b/test/extensions/matching/input_matchers/consistent_hashing/config_test.cc index 821b9b8998c0e..af5d859a1436b 100644 --- a/test/extensions/matching/input_matchers/consistent_hashing/config_test.cc +++ b/test/extensions/matching/input_matchers/consistent_hashing/config_test.cc @@ -1,6 +1,6 @@ #include "source/extensions/matching/input_matchers/consistent_hashing/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "gtest/gtest.h" diff --git a/test/extensions/matching/input_matchers/dynamic_modules/BUILD b/test/extensions/matching/input_matchers/dynamic_modules/BUILD index ae7d1e74b4f25..240009ff6e659 100644 --- a/test/extensions/matching/input_matchers/dynamic_modules/BUILD +++ b/test/extensions/matching/input_matchers/dynamic_modules/BUILD @@ -23,7 +23,7 @@ envoy_cc_test( "//envoy/registry", "//source/extensions/matching/input_matchers/dynamic_modules:config", "//test/extensions/dynamic_modules:util", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/matching/input_matchers/dynamic_modules/config_test.cc b/test/extensions/matching/input_matchers/dynamic_modules/config_test.cc index cf9b526f20eaf..e39ab36a06264 100644 --- a/test/extensions/matching/input_matchers/dynamic_modules/config_test.cc +++ b/test/extensions/matching/input_matchers/dynamic_modules/config_test.cc @@ -3,7 +3,7 @@ #include "source/extensions/matching/input_matchers/dynamic_modules/config.h" #include "test/extensions/dynamic_modules/util.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/matching/input_matchers/ip/BUILD b/test/extensions/matching/input_matchers/ip/BUILD index b3e1c7950cdb3..d6d504ec0a777 100644 --- a/test/extensions/matching/input_matchers/ip/BUILD +++ b/test/extensions/matching/input_matchers/ip/BUILD @@ -18,7 +18,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/matching/input_matchers/ip:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", ], ) diff --git a/test/extensions/matching/input_matchers/ip/config_test.cc b/test/extensions/matching/input_matchers/ip/config_test.cc index ef5b99d0e5136..2461a4d7af651 100644 --- a/test/extensions/matching/input_matchers/ip/config_test.cc +++ b/test/extensions/matching/input_matchers/ip/config_test.cc @@ -1,6 +1,6 @@ #include "source/extensions/matching/input_matchers/ip/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "gtest/gtest.h" diff --git a/test/extensions/matching/input_matchers/metadata/BUILD b/test/extensions/matching/input_matchers/metadata/BUILD index 862c59c2c214c..baa762bb77875 100644 --- a/test/extensions/matching/input_matchers/metadata/BUILD +++ b/test/extensions/matching/input_matchers/metadata/BUILD @@ -24,7 +24,7 @@ envoy_extension_cc_test( "//test/common/matcher:test_utility_lib", "//test/mocks/http:http_mocks", "//test/mocks/matcher:matcher_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:registry_lib", "@envoy_api//envoy/config/common/matcher/v3:pkg_cc_proto", diff --git a/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc b/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc index a7a30744b72d1..9e2b7df53fa1f 100644 --- a/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc +++ b/test/extensions/matching/input_matchers/metadata/dyn_meta_matcher_test.cc @@ -14,7 +14,7 @@ #include "test/common/matcher/test_utility.h" #include "test/mocks/matcher/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/registry.h" #include "test/test_common/utility.h" diff --git a/test/extensions/matching/input_matchers/runtime_fraction/BUILD b/test/extensions/matching/input_matchers/runtime_fraction/BUILD index c93c71e3825e6..b4aa38620b421 100644 --- a/test/extensions/matching/input_matchers/runtime_fraction/BUILD +++ b/test/extensions/matching/input_matchers/runtime_fraction/BUILD @@ -18,7 +18,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/matching/input_matchers/runtime_fraction:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", ], ) diff --git a/test/extensions/matching/input_matchers/runtime_fraction/config_test.cc b/test/extensions/matching/input_matchers/runtime_fraction/config_test.cc index d00b487b61a5f..7cd5df18d18dc 100644 --- a/test/extensions/matching/input_matchers/runtime_fraction/config_test.cc +++ b/test/extensions/matching/input_matchers/runtime_fraction/config_test.cc @@ -1,6 +1,6 @@ #include "source/extensions/matching/input_matchers/runtime_fraction/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "gtest/gtest.h" diff --git a/test/extensions/matching/network/common/BUILD b/test/extensions/matching/network/common/BUILD index df62868323811..da1594b9bb328 100644 --- a/test/extensions/matching/network/common/BUILD +++ b/test/extensions/matching/network/common/BUILD @@ -38,6 +38,6 @@ envoy_cc_test( "//test/common/matcher:test_utility_lib", "//test/mocks/matcher:matcher_mocks", "//test/mocks/network:network_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", ], ) diff --git a/test/extensions/matching/network/common/inputs_integration_test.cc b/test/extensions/matching/network/common/inputs_integration_test.cc index 8c368b71e627b..1b520fae9e607 100644 --- a/test/extensions/matching/network/common/inputs_integration_test.cc +++ b/test/extensions/matching/network/common/inputs_integration_test.cc @@ -8,7 +8,7 @@ #include "test/common/matcher/test_utility.h" #include "test/mocks/matcher/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "gtest/gtest.h" diff --git a/test/extensions/path/match/uri_template/BUILD b/test/extensions/path/match/uri_template/BUILD index 7463db5b5e02c..1107287f5d9b7 100644 --- a/test/extensions/path/match/uri_template/BUILD +++ b/test/extensions/path/match/uri_template/BUILD @@ -18,7 +18,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/path/match/uri_template:config", - "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", ], ) @@ -30,6 +30,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/path/match/uri_template:config", "//source/extensions/path/match/uri_template:uri_template_match_lib", - "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/path/match/uri_template/config_test.cc b/test/extensions/path/match/uri_template/config_test.cc index c99108c566ae6..4385abd9c6550 100644 --- a/test/extensions/path/match/uri_template/config_test.cc +++ b/test/extensions/path/match/uri_template/config_test.cc @@ -1,8 +1,7 @@ #include "source/common/config/utility.h" #include "source/extensions/path/match/uri_template/config.h" -#include "test/mocks/server/factory_context.h" -#include "test/test_common/environment.h" +#include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/path/match/uri_template/library_test.cc b/test/extensions/path/match/uri_template/library_test.cc index 002fea5a8c689..69ecfe0693865 100644 --- a/test/extensions/path/match/uri_template/library_test.cc +++ b/test/extensions/path/match/uri_template/library_test.cc @@ -2,8 +2,8 @@ #include "source/extensions/path/match/uri_template/config.h" #include "source/extensions/path/match/uri_template/uri_template_match.h" -#include "test/mocks/server/factory_context.h" #include "test/test_common/environment.h" +#include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/path/rewrite/uri_template/BUILD b/test/extensions/path/rewrite/uri_template/BUILD index ab9f6866c257d..fb2aefaf8c893 100644 --- a/test/extensions/path/rewrite/uri_template/BUILD +++ b/test/extensions/path/rewrite/uri_template/BUILD @@ -18,7 +18,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/path/rewrite/uri_template:config", - "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", ], ) @@ -32,6 +32,6 @@ envoy_extension_cc_test( "//source/extensions/path/match/uri_template:uri_template_match_lib", "//source/extensions/path/rewrite/uri_template:config", "//source/extensions/path/rewrite/uri_template:uri_template_rewrite_lib", - "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/path/rewrite/uri_template/config_test.cc b/test/extensions/path/rewrite/uri_template/config_test.cc index 96e7d564e820d..bd65e583e7e6d 100644 --- a/test/extensions/path/rewrite/uri_template/config_test.cc +++ b/test/extensions/path/rewrite/uri_template/config_test.cc @@ -1,8 +1,7 @@ #include "source/common/config/utility.h" #include "source/extensions/path/rewrite/uri_template/config.h" -#include "test/mocks/server/factory_context.h" -#include "test/test_common/environment.h" +#include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/path/rewrite/uri_template/library_test.cc b/test/extensions/path/rewrite/uri_template/library_test.cc index 4783b8725f638..8959a58cf8c51 100644 --- a/test/extensions/path/rewrite/uri_template/library_test.cc +++ b/test/extensions/path/rewrite/uri_template/library_test.cc @@ -3,8 +3,7 @@ #include "source/extensions/path/rewrite/uri_template/config.h" #include "source/extensions/path/rewrite/uri_template/uri_template_rewrite.h" -#include "test/mocks/server/factory_context.h" -#include "test/test_common/environment.h" +#include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/rate_limit_descriptors/expr/BUILD b/test/extensions/rate_limit_descriptors/expr/BUILD index 902cb6019396f..b98f42d2f6d43 100644 --- a/test/extensions/rate_limit_descriptors/expr/BUILD +++ b/test/extensions/rate_limit_descriptors/expr/BUILD @@ -29,7 +29,7 @@ envoy_extension_cc_test( "//source/extensions/rate_limit_descriptors/expr:config", "//test/mocks/http:http_mocks", "//test/mocks/ratelimit:ratelimit_mocks", - "//test/mocks/server:server_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/rate_limit_descriptors/expr/v3:pkg_cc_proto", diff --git a/test/extensions/rate_limit_descriptors/expr/config_test.cc b/test/extensions/rate_limit_descriptors/expr/config_test.cc index 49810f6e6c490..295c8060cd9a8 100644 --- a/test/extensions/rate_limit_descriptors/expr/config_test.cc +++ b/test/extensions/rate_limit_descriptors/expr/config_test.cc @@ -9,7 +9,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/ratelimit/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" diff --git a/test/extensions/router/cluster_specifiers/lua/BUILD b/test/extensions/router/cluster_specifiers/lua/BUILD index 0e8111608812f..0a122e94432a1 100644 --- a/test/extensions/router/cluster_specifiers/lua/BUILD +++ b/test/extensions/router/cluster_specifiers/lua/BUILD @@ -19,7 +19,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/router/cluster_specifiers/lua:lua_cluster_specifier_lib", "//test/mocks/router:router_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], @@ -32,7 +32,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/router/cluster_specifiers/lua:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/router/cluster_specifiers/lua/config_test.cc b/test/extensions/router/cluster_specifiers/lua/config_test.cc index ff23f0a556b6a..613880109e1a0 100644 --- a/test/extensions/router/cluster_specifiers/lua/config_test.cc +++ b/test/extensions/router/cluster_specifiers/lua/config_test.cc @@ -1,6 +1,6 @@ #include "source/extensions/router/cluster_specifiers/lua/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/router/cluster_specifiers/lua/lua_cluster_specifier_test.cc b/test/extensions/router/cluster_specifiers/lua/lua_cluster_specifier_test.cc index 83f5591f3f275..b803c6a1172ac 100644 --- a/test/extensions/router/cluster_specifiers/lua/lua_cluster_specifier_test.cc +++ b/test/extensions/router/cluster_specifiers/lua/lua_cluster_specifier_test.cc @@ -1,7 +1,7 @@ #include "source/extensions/router/cluster_specifiers/lua/lua_cluster_specifier.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/router/cluster_specifiers/matcher/BUILD b/test/extensions/router/cluster_specifiers/matcher/BUILD index 137ae291e545b..1dd776a9cfa68 100644 --- a/test/extensions/router/cluster_specifiers/matcher/BUILD +++ b/test/extensions/router/cluster_specifiers/matcher/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//source/extensions/router/cluster_specifiers/matcher:config", "//source/extensions/router/cluster_specifiers/matcher:matcher_cluster_specifier_lib", "//test/mocks/router:router_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], @@ -33,7 +33,7 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/router/cluster_specifiers/matcher:config", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/router/cluster_specifiers/matcher/config_test.cc b/test/extensions/router/cluster_specifiers/matcher/config_test.cc index 00a732e702060..ca75f515ac4c1 100644 --- a/test/extensions/router/cluster_specifiers/matcher/config_test.cc +++ b/test/extensions/router/cluster_specifiers/matcher/config_test.cc @@ -1,6 +1,6 @@ #include "source/extensions/router/cluster_specifiers/matcher/config.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/router/cluster_specifiers/matcher/matcher_cluster_specifier_test.cc b/test/extensions/router/cluster_specifiers/matcher/matcher_cluster_specifier_test.cc index 513ccbdecef4a..7e0813cfdf08f 100644 --- a/test/extensions/router/cluster_specifiers/matcher/matcher_cluster_specifier_test.cc +++ b/test/extensions/router/cluster_specifiers/matcher/matcher_cluster_specifier_test.cc @@ -2,7 +2,7 @@ #include "source/extensions/router/cluster_specifiers/matcher/matcher_cluster_specifier.h" #include "test/mocks/router/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/stats_sinks/hystrix/BUILD b/test/extensions/stats_sinks/hystrix/BUILD index 2a8de832918c7..277b605416980 100644 --- a/test/extensions/stats_sinks/hystrix/BUILD +++ b/test/extensions/stats_sinks/hystrix/BUILD @@ -40,7 +40,7 @@ envoy_extension_cc_test( "//source/extensions/stat_sinks/hystrix:hystrix_lib", "//test/mocks/server:admin_mocks", "//test/mocks/server:admin_stream_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stats:stats_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:cluster_manager_mocks", diff --git a/test/extensions/stats_sinks/hystrix/hystrix_test.cc b/test/extensions/stats_sinks/hystrix/hystrix_test.cc index 6b185039009a2..768bd795aa09e 100644 --- a/test/extensions/stats_sinks/hystrix/hystrix_test.cc +++ b/test/extensions/stats_sinks/hystrix/hystrix_test.cc @@ -8,7 +8,7 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/server/admin.h" #include "test/mocks/server/admin_stream.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/cluster_manager.h" diff --git a/test/extensions/stats_sinks/open_telemetry/BUILD b/test/extensions/stats_sinks/open_telemetry/BUILD index 1d1ac7f0da865..cc7261b9b5c84 100644 --- a/test/extensions/stats_sinks/open_telemetry/BUILD +++ b/test/extensions/stats_sinks/open_telemetry/BUILD @@ -34,7 +34,7 @@ envoy_extension_cc_test( "//source/extensions/stat_sinks/open_telemetry:config", "//source/extensions/stat_sinks/open_telemetry:open_telemetry_lib", "//test/mocks/grpc:grpc_mocks", - "//test/mocks/server:instance_mocks", + "//test/mocks/server:server_factory_context_mocks", ], ) @@ -74,6 +74,7 @@ envoy_cc_benchmark_binary( "//source/common/stats:isolated_store_lib", "//source/extensions/stat_sinks/open_telemetry:open_telemetry_lib", "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stats:stats_mocks", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/container:inlined_vector", diff --git a/test/extensions/stats_sinks/open_telemetry/open_telemetry_benchmark.cc b/test/extensions/stats_sinks/open_telemetry/open_telemetry_benchmark.cc index 6256d2ee32946..1a5f8e9ac93b1 100644 --- a/test/extensions/stats_sinks/open_telemetry/open_telemetry_benchmark.cc +++ b/test/extensions/stats_sinks/open_telemetry/open_telemetry_benchmark.cc @@ -8,7 +8,7 @@ #include "source/common/stats/histogram_impl.h" #include "source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stats/mocks.h" #include "absl/container/inlined_vector.h" diff --git a/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc b/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc index 532ad67363992..80694b2be3cf5 100644 --- a/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc +++ b/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc @@ -7,7 +7,7 @@ #include "test/mocks/common.h" #include "test/mocks/grpc/mocks.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stats/mocks.h" #include "gtest/gtest.h" diff --git a/test/extensions/stats_sinks/wasm/BUILD b/test/extensions/stats_sinks/wasm/BUILD index d82a35e1b9e2f..ea7ef34e7c635 100644 --- a/test/extensions/stats_sinks/wasm/BUILD +++ b/test/extensions/stats_sinks/wasm/BUILD @@ -28,7 +28,8 @@ envoy_extension_cc_test( "//source/extensions/stat_sinks/wasm:config", "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/stats_sinks/wasm/test_data:test_context_cpp_plugin", - "//test/mocks/server:server_mocks", + "//test/mocks/server:server_factory_context_mocks", + "//test/mocks/server:server_lifecycle_notifier_mocks", "@envoy_api//envoy/extensions/stat_sinks/wasm/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/stats_sinks/wasm/config_test.cc b/test/extensions/stats_sinks/wasm/config_test.cc index 7214cf83a637c..511d523b6e95b 100644 --- a/test/extensions/stats_sinks/wasm/config_test.cc +++ b/test/extensions/stats_sinks/wasm/config_test.cc @@ -7,7 +7,8 @@ #include "source/extensions/stat_sinks/wasm/wasm_stat_sink_impl.h" #include "test/extensions/common/wasm/wasm_runtime.h" -#include "test/mocks/server/mocks.h" +#include "test/mocks/server/server_factory_context.h" +#include "test/mocks/server/server_lifecycle_notifier.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/BUILD b/test/extensions/tracers/opentelemetry/resource_detectors/BUILD index a6b1d45bcb339..8c2282143861d 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/BUILD +++ b/test/extensions/tracers/opentelemetry/resource_detectors/BUILD @@ -16,7 +16,7 @@ envoy_cc_test( "//envoy/registry", "//source/common/version:version_lib", "//source/extensions/tracers/opentelemetry/resource_detectors:resource_detector_lib", - "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:registry_lib", "//test/test_common:utility_lib", ], diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/BUILD b/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/BUILD index 20be396bc8fb2..7484fcc88a813 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/BUILD +++ b/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//envoy/registry", "//source/extensions/tracers/opentelemetry/resource_detectors/dynatrace:config", "//source/extensions/tracers/opentelemetry/resource_detectors/dynatrace:dynatrace_resource_detector_lib", - "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) @@ -36,7 +36,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/tracers/opentelemetry/resource_detectors/dynatrace:config", "//source/extensions/tracers/opentelemetry/resource_detectors/dynatrace:dynatrace_resource_detector_lib", - "//test/mocks/server:tracer_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/tracers/opentelemetry/resource_detectors/v3:pkg_cc_proto", ], diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/config_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/config_test.cc index 6891bd2da4204..850aff9e852f4 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/config_test.cc +++ b/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/config_test.cc @@ -2,7 +2,7 @@ #include "source/extensions/tracers/opentelemetry/resource_detectors/dynatrace/config.h" -#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector_test.cc index 9f1d94409ee4f..1da24a53347d8 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector_test.cc +++ b/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector_test.cc @@ -7,13 +7,14 @@ #include "source/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_metadata_file_reader.h" #include "source/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector.h" -#include "test/mocks/server/tracer_factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::_; +using testing::NiceMock; using testing::Return; namespace Envoy { diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD b/test/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD index 264622838abc4..95db1a61c77e3 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD +++ b/test/extensions/tracers/opentelemetry/resource_detectors/environment/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//envoy/registry", "//source/extensions/tracers/opentelemetry/resource_detectors/environment:config", "//source/extensions/tracers/opentelemetry/resource_detectors/environment:environment_resource_detector_lib", - "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/environment/config_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/environment/config_test.cc index 1d7180d604c13..a6b6d873be26f 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/environment/config_test.cc +++ b/test/extensions/tracers/opentelemetry/resource_detectors/environment/config_test.cc @@ -2,7 +2,7 @@ #include "source/extensions/tracers/opentelemetry/resource_detectors/environment/config.h" -#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc index dcbf3ce57da68..99ce5cb6cced7 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc +++ b/test/extensions/tracers/opentelemetry/resource_detectors/resource_provider_test.cc @@ -5,7 +5,7 @@ #include "source/common/version/version.h" #include "source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h" -#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/registry.h" #include "test/test_common/utility.h" diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/static/BUILD b/test/extensions/tracers/opentelemetry/resource_detectors/static/BUILD index 72f9283397457..aeabe26136e28 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/static/BUILD +++ b/test/extensions/tracers/opentelemetry/resource_detectors/static/BUILD @@ -20,7 +20,7 @@ envoy_extension_cc_test( "//envoy/registry", "//source/extensions/tracers/opentelemetry/resource_detectors/static:config", "//source/extensions/tracers/opentelemetry/resource_detectors/static:static_config_resource_detector_lib", - "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/tracers/opentelemetry/resource_detectors/v3:pkg_cc_proto", ], diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/static/config_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/static/config_test.cc index 59e40fda2e30e..2659764f12289 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/static/config_test.cc +++ b/test/extensions/tracers/opentelemetry/resource_detectors/static/config_test.cc @@ -5,7 +5,7 @@ #include "source/common/config/utility.h" #include "source/extensions/tracers/opentelemetry/resource_detectors/static/config.h" -#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" diff --git a/test/extensions/tracers/xray/BUILD b/test/extensions/tracers/xray/BUILD index d66fdf913d22d..df1d9d969b6ed 100644 --- a/test/extensions/tracers/xray/BUILD +++ b/test/extensions/tracers/xray/BUILD @@ -45,7 +45,6 @@ envoy_extension_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/tracers/xray:config", - "//test/mocks/server:instance_mocks", "//test/mocks/server:tracer_factory_context_mocks", "//test/mocks/server:tracer_factory_mocks", "//test/test_common:environment_lib", diff --git a/test/extensions/tracers/xray/config_test.cc b/test/extensions/tracers/xray/config_test.cc index ef146def35bb2..14749e0199337 100644 --- a/test/extensions/tracers/xray/config_test.cc +++ b/test/extensions/tracers/xray/config_test.cc @@ -5,7 +5,6 @@ #include "source/extensions/tracers/xray/config.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/tracer_factory.h" #include "test/mocks/server/tracer_factory_context.h" #include "test/test_common/environment.h" diff --git a/test/extensions/upstreams/http/tcp/BUILD b/test/extensions/upstreams/http/tcp/BUILD index f07d993db1f89..9641d1d1e17a6 100644 --- a/test/extensions/upstreams/http/tcp/BUILD +++ b/test/extensions/upstreams/http/tcp/BUILD @@ -24,7 +24,6 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/router:router_filter_interface", "//test/mocks/router:router_mocks", - "//test/mocks/server:factory_context_mocks", "//test/mocks/server:instance_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", diff --git a/test/extensions/upstreams/http/tcp/upstream_request_test.cc b/test/extensions/upstreams/http/tcp/upstream_request_test.cc index 4b31b65f744fb..8cb67175701d3 100644 --- a/test/extensions/upstreams/http/tcp/upstream_request_test.cc +++ b/test/extensions/upstreams/http/tcp/upstream_request_test.cc @@ -11,7 +11,6 @@ #include "test/mocks/common.h" #include "test/mocks/router/mocks.h" #include "test/mocks/router/router_filter_interface.h" -#include "test/mocks/server/factory_context.h" #include "test/mocks/server/instance.h" #include "test/mocks/tcp/mocks.h" #include "test/test_common/utility.h" diff --git a/test/extensions/upstreams/http/udp/BUILD b/test/extensions/upstreams/http/udp/BUILD index cb60783dd62b1..0863dd0cdb31a 100644 --- a/test/extensions/upstreams/http/udp/BUILD +++ b/test/extensions/upstreams/http/udp/BUILD @@ -26,8 +26,6 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/router:router_filter_interface", "//test/mocks/router:router_mocks", - "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", diff --git a/test/extensions/upstreams/http/udp/upstream_request_test.cc b/test/extensions/upstreams/http/udp/upstream_request_test.cc index 24121817b1857..13aa250367890 100644 --- a/test/extensions/upstreams/http/udp/upstream_request_test.cc +++ b/test/extensions/upstreams/http/udp/upstream_request_test.cc @@ -17,8 +17,6 @@ #include "test/mocks/common.h" #include "test/mocks/router/mocks.h" #include "test/mocks/router/router_filter_interface.h" -#include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "test/test_common/utility.h" diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index 277189bdfcb81..c64e751d94edd 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -38,7 +38,6 @@ #include "test/mocks/http/header_validator.h" #include "test/mocks/protobuf/mocks.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/listener_factory_context.h" #if defined(ENVOY_ENABLE_QUIC) diff --git a/test/server/BUILD b/test/server/BUILD index aa0539636b11d..577fd3bf4d9e2 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -71,7 +71,6 @@ envoy_cc_test( "//test/common/upstream:utility_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", @@ -194,6 +193,7 @@ envoy_cc_test( "//source/common/stats:stats_lib", "//source/server:hot_restart_lib", "//source/server:hot_restarting_child", + "//test/mocks/api:api_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", "//test/test_common:threadsafe_singleton_injector_lib", diff --git a/test/server/admin/BUILD b/test/server/admin/BUILD index e3bcbf81d16c3..1322d6dbf0d1a 100644 --- a/test/server/admin/BUILD +++ b/test/server/admin/BUILD @@ -78,9 +78,12 @@ envoy_cc_test( srcs = envoy_select_admin_functionality(["admin_filter_test.cc"]), rbe_pool = "6gig", deps = [ + "//source/common/buffer:buffer_lib", "//source/server/admin:admin_lib", - "//test/mocks/server:instance_mocks", + "//test/mocks/http:http_mocks", + "//test/mocks/server:admin_mocks", "//test/test_common:environment_lib", + "//test/test_common:utility_lib", ], ) diff --git a/test/server/admin/admin_filter_test.cc b/test/server/admin/admin_filter_test.cc index fbf6c93cd72dc..22e3ba1b5cb61 100644 --- a/test/server/admin/admin_filter_test.cc +++ b/test/server/admin/admin_filter_test.cc @@ -1,12 +1,16 @@ +#include "source/common/buffer/buffer_impl.h" #include "source/server/admin/admin.h" #include "source/server/admin/admin_filter.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/server/admin.h" #include "test/test_common/environment.h" +#include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::_; using testing::ByMove; using testing::InSequence; using testing::NiceMock; @@ -23,7 +27,6 @@ class AdminFilterTest : public testing::TestWithParam admin_; - Stats::IsolatedStoreImpl listener_scope_; AdminFilter filter_; NiceMock callbacks_; Http::TestRequestHeaderMapImpl request_headers_; diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index a1b2aa1791921..87de10c0602a4 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -21,8 +21,8 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:admin_mocks", - "//test/mocks/server:instance_mocks", "//test/mocks/server:options_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", ], diff --git a/test/server/config_validation/cluster_manager_test.cc b/test/server/config_validation/cluster_manager_test.cc index 67c200e5088d3..ad6ca064ae537 100644 --- a/test/server/config_validation/cluster_manager_test.cc +++ b/test/server/config_validation/cluster_manager_test.cc @@ -12,8 +12,8 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/admin.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/options.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" namespace Envoy { diff --git a/test/server/configuration_impl_test.cc b/test/server/configuration_impl_test.cc index c916ebd031643..9fd281ee61885 100644 --- a/test/server/configuration_impl_test.cc +++ b/test/server/configuration_impl_test.cc @@ -19,7 +19,6 @@ #include "test/mocks/common.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/factory_context.h" -#include "test/mocks/server/instance.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" diff --git a/test/server/hot_restarting_child_test.cc b/test/server/hot_restarting_child_test.cc index 874a06b42e222..bdfdb389e9b3d 100644 --- a/test/server/hot_restarting_child_test.cc +++ b/test/server/hot_restarting_child_test.cc @@ -5,8 +5,8 @@ #include "source/server/hot_restarting_child.h" #include "source/server/hot_restarting_parent.h" +#include "test/mocks/api/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/server/instance.h" #include "test/mocks/server/listener_manager.h" #include "test/server/hot_restart_udp_forwarding_test_helper.h" #include "test/server/utility.h" diff --git a/test/tools/router_check/router.h b/test/tools/router_check/router.h index be6d25b27d350..13b9ecdc5b858 100644 --- a/test/tools/router_check/router.h +++ b/test/tools/router_check/router.h @@ -15,7 +15,7 @@ #include "source/common/stats/symbol_table.h" #include "source/common/stream_info/stream_info_impl.h" -#include "test/mocks/server/instance.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/global.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" From 544c65e04385cd6dbf8ca45249e89adbb6e164ba Mon Sep 17 00:00:00 2001 From: Ting Pan Date: Thu, 14 May 2026 17:41:36 -0400 Subject: [PATCH 314/750] udp: modify UdpPacketWriterFactory to accept ListenerFactoryContext (#45021) Commit Message: modify UdpPacketWriterFactory to accept ListenerFactoryContext Additional Description: This change modifies the UdpPacketWriterFactoryFactory interface to pass a ListenerFactoryContext to the createUdpPacketWriterFactory method. This enables UDP packet writer factories to access listener-scoped resources such as stats, runtime, and singletons. Risk Level: low Testing: n/a Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Ting Pan --- envoy/network/BUILD | 11 ++++++ .../udp_packet_writer_factory_factory.h | 35 +++++++++++++++++++ envoy/network/udp_packet_writer_handler.h | 22 ------------ source/common/listener_manager/BUILD | 1 + .../common/listener_manager/listener_impl.cc | 3 +- .../udp_packet_writer/default/BUILD | 1 + .../udp_packet_writer/default/config.h | 5 +-- source/extensions/udp_packet_writer/gso/BUILD | 2 +- .../extensions/udp_packet_writer/gso/config.h | 5 +-- .../udp_packet_writer/default/BUILD | 1 + .../udp_packet_writer/default/config_test.cc | 6 +++- test/extensions/udp_packet_writer/gso/BUILD | 1 + .../udp_packet_writer/gso/config_test.cc | 7 +++- 13 files changed, 70 insertions(+), 30 deletions(-) create mode 100644 envoy/network/udp_packet_writer_factory_factory.h diff --git a/envoy/network/BUILD b/envoy/network/BUILD index be56ae3afa56b..ed804860d89f5 100644 --- a/envoy/network/BUILD +++ b/envoy/network/BUILD @@ -85,6 +85,17 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "udp_packet_writer_factory_factory_interface", + hdrs = ["udp_packet_writer_factory_factory.h"], + deps = [ + ":udp_packet_writer_handler_interface", + "//envoy/config:typed_config_interface", + "//envoy/server:factory_context_interface", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "dns_interface", hdrs = ["dns.h"], diff --git a/envoy/network/udp_packet_writer_factory_factory.h b/envoy/network/udp_packet_writer_factory_factory.h new file mode 100644 index 0000000000000..e2ec4ab126d2e --- /dev/null +++ b/envoy/network/udp_packet_writer_factory_factory.h @@ -0,0 +1,35 @@ +#pragma once + +#include "envoy/config/core/v3/extension.pb.h" +#include "envoy/config/typed_config.h" +#include "envoy/network/udp_packet_writer_handler.h" +#include "envoy/server/factory_context.h" + +namespace Envoy { +namespace Network { + +/** + * UdpPacketWriterFactoryFactory adds an extra layer of indirection In order to + * support a UdpPacketWriterFactory whose behavior depends on the + * TypedConfig for that factory. The UdpPacketWriterFactoryFactory is created + * with a no-arg constructor based on the type of the config. Then this + * `createUdpPacketWriterFactory` can be called with the config to + * create an actual UdpPacketWriterFactory. + */ +class UdpPacketWriterFactoryFactory : public Envoy::Config::TypedFactory { +public: + ~UdpPacketWriterFactoryFactory() override = default; + + /** + * Creates an UdpPacketWriterFactory based on the specified config. + * @return the UdpPacketWriterFactory created. + */ + virtual UdpPacketWriterFactoryPtr + createUdpPacketWriterFactory(const envoy::config::core::v3::TypedExtensionConfig& config, + Server::Configuration::ListenerFactoryContext& context) PURE; + + std::string category() const override { return "envoy.udp_packet_writer"; } +}; + +} // namespace Network +} // namespace Envoy diff --git a/envoy/network/udp_packet_writer_handler.h b/envoy/network/udp_packet_writer_handler.h index 99cee531d6f35..e8b2042247d6a 100644 --- a/envoy/network/udp_packet_writer_handler.h +++ b/envoy/network/udp_packet_writer_handler.h @@ -125,27 +125,5 @@ class UdpPacketWriterFactory { using UdpPacketWriterFactoryPtr = std::unique_ptr; -/** - * UdpPacketWriterFactoryFactory adds an extra layer of indirection In order to - * support a UdpPacketWriterFactory whose behavior depends on the - * TypedConfig for that factory. The UdpPacketWriterFactoryFactory is created - * with a no-arg constructor based on the type of the config. Then this - * `createUdpPacketWriterFactory1 can be called with the config to - * create an actual UdpPacketWriterFactory. - */ -class UdpPacketWriterFactoryFactory : public Envoy::Config::TypedFactory { -public: - ~UdpPacketWriterFactoryFactory() override = default; - - /** - * Creates an UdpPacketWriterFactory based on the specified config. - * @return the UdpPacketWriterFactory created. - */ - virtual UdpPacketWriterFactoryPtr - createUdpPacketWriterFactory(const envoy::config::core::v3::TypedExtensionConfig& config) PURE; - - std::string category() const override { return "envoy.udp_packet_writer"; } -}; - } // namespace Network } // namespace Envoy diff --git a/source/common/listener_manager/BUILD b/source/common/listener_manager/BUILD index 1e9288ef03180..252785c87f01f 100644 --- a/source/common/listener_manager/BUILD +++ b/source/common/listener_manager/BUILD @@ -30,6 +30,7 @@ envoy_cc_library( "//envoy/config:typed_metadata_interface", "//envoy/network:connection_interface", "//envoy/network:listener_interface", + "//envoy/network:udp_packet_writer_factory_factory_interface", "//envoy/server:api_listener_interface", "//envoy/server:filter_config_interface", "//envoy/server:listener_manager_interface", diff --git a/source/common/listener_manager/listener_impl.cc b/source/common/listener_manager/listener_impl.cc index 8883d2415946a..b4af3b9feaea5 100644 --- a/source/common/listener_manager/listener_impl.cc +++ b/source/common/listener_manager/listener_impl.cc @@ -10,6 +10,7 @@ #include "envoy/extensions/filters/listener/proxy_protocol/v3/proxy_protocol.pb.h" #include "envoy/extensions/udp_packet_writer/v3/udp_default_writer_factory.pb.h" #include "envoy/network/exception.h" +#include "envoy/network/udp_packet_writer_factory_factory.h" #include "envoy/registry/registry.h" #include "envoy/server/options.h" #include "envoy/server/transport_socket_config.h" @@ -701,7 +702,7 @@ ListenerImpl::buildUdpListenerFactory(const envoy::config::listener::v3::Listene auto* factory_factory = Config::Utility::getFactory( config.udp_listener_config().udp_packet_packet_writer_config()); udp_listener_config_->writer_factory_ = factory_factory->createUdpPacketWriterFactory( - config.udp_listener_config().udp_packet_packet_writer_config()); + config.udp_listener_config().udp_packet_packet_writer_config(), *listener_factory_context_); } if (config.udp_listener_config().has_quic_options()) { #ifdef ENVOY_ENABLE_QUIC diff --git a/source/extensions/udp_packet_writer/default/BUILD b/source/extensions/udp_packet_writer/default/BUILD index ccdecc3bdc08e..01d1b9cf9536e 100644 --- a/source/extensions/udp_packet_writer/default/BUILD +++ b/source/extensions/udp_packet_writer/default/BUILD @@ -22,6 +22,7 @@ envoy_cc_extension( ], deps = [ "//envoy/config:typed_config_interface", + "//envoy/network:udp_packet_writer_factory_factory_interface", "//envoy/registry", "//source/common/network:udp_packet_writer_handler_lib", "@envoy_api//envoy/extensions/udp_packet_writer/v3:pkg_cc_proto", diff --git a/source/extensions/udp_packet_writer/default/config.h b/source/extensions/udp_packet_writer/default/config.h index e714f0268a2f4..9b09acea2105f 100644 --- a/source/extensions/udp_packet_writer/default/config.h +++ b/source/extensions/udp_packet_writer/default/config.h @@ -1,7 +1,7 @@ #pragma once #include "envoy/extensions/udp_packet_writer/v3/udp_default_writer_factory.pb.h" -#include "envoy/network/udp_packet_writer_handler.h" +#include "envoy/network/udp_packet_writer_factory_factory.h" #include "envoy/registry/registry.h" #include "source/common/network/udp_packet_writer_handler_impl.h" @@ -13,7 +13,8 @@ class UdpDefaultWriterFactoryFactory : public Network::UdpPacketWriterFactoryFac public: std::string name() const override { return "envoy.udp_packet_writer.default"; } UdpPacketWriterFactoryPtr - createUdpPacketWriterFactory(const envoy::config::core::v3::TypedExtensionConfig&) override { + createUdpPacketWriterFactory(const envoy::config::core::v3::TypedExtensionConfig&, + Server::Configuration::ListenerFactoryContext&) override { return std::make_unique(); } ProtobufTypes::MessagePtr createEmptyConfigProto() override { diff --git a/source/extensions/udp_packet_writer/gso/BUILD b/source/extensions/udp_packet_writer/gso/BUILD index e98e170c410b5..b0210d72cbc15 100644 --- a/source/extensions/udp_packet_writer/gso/BUILD +++ b/source/extensions/udp_packet_writer/gso/BUILD @@ -23,7 +23,7 @@ envoy_cc_extension( ], deps = [ "//envoy/config:typed_config_interface", - "//envoy/network:udp_packet_writer_handler_interface", + "//envoy/network:udp_packet_writer_factory_factory_interface", "//envoy/registry", "@envoy_api//envoy/extensions/udp_packet_writer/v3:pkg_cc_proto", ] + envoy_select_enable_http3([ diff --git a/source/extensions/udp_packet_writer/gso/config.h b/source/extensions/udp_packet_writer/gso/config.h index b25c7dfe88fc0..8bba803c2635c 100644 --- a/source/extensions/udp_packet_writer/gso/config.h +++ b/source/extensions/udp_packet_writer/gso/config.h @@ -1,7 +1,7 @@ #pragma once #include "envoy/extensions/udp_packet_writer/v3/udp_gso_batch_writer_factory.pb.h" -#include "envoy/network/udp_packet_writer_handler.h" +#include "envoy/network/udp_packet_writer_factory_factory.h" #include "envoy/registry/registry.h" #ifdef ENVOY_ENABLE_QUIC @@ -17,7 +17,8 @@ class UdpGsoBatchWriterFactoryFactory : public Network::UdpPacketWriterFactoryFa public: std::string name() const override { return "envoy.udp_packet_writer.gso"; } Network::UdpPacketWriterFactoryPtr - createUdpPacketWriterFactory(const envoy::config::core::v3::TypedExtensionConfig&) override { + createUdpPacketWriterFactory(const envoy::config::core::v3::TypedExtensionConfig&, + Server::Configuration::ListenerFactoryContext&) override { #ifdef ENVOY_ENABLE_QUIC return std::make_unique(); #else diff --git a/test/extensions/udp_packet_writer/default/BUILD b/test/extensions/udp_packet_writer/default/BUILD index 0d20434045997..92a0d839c2be0 100644 --- a/test/extensions/udp_packet_writer/default/BUILD +++ b/test/extensions/udp_packet_writer/default/BUILD @@ -14,6 +14,7 @@ envoy_cc_test( rbe_pool = "6gig", deps = [ "//source/extensions/udp_packet_writer/default:config", + "//test/mocks/server:listener_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/udp_packet_writer/v3:pkg_cc_proto", ], diff --git a/test/extensions/udp_packet_writer/default/config_test.cc b/test/extensions/udp_packet_writer/default/config_test.cc index 1926ab0bc1fc5..90c2d66d1f149 100644 --- a/test/extensions/udp_packet_writer/default/config_test.cc +++ b/test/extensions/udp_packet_writer/default/config_test.cc @@ -2,6 +2,9 @@ #include "source/extensions/udp_packet_writer/default/config.h" +#include "test/mocks/server/listener_factory_context.h" + +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace Envoy { @@ -22,7 +25,8 @@ TEST(FactoryTest, CreateUdpPacketWriterFactory) { envoy::extensions::udp_packet_writer::v3::UdpDefaultWriterFactory writer_config; envoy::config::core::v3::TypedExtensionConfig config; config.mutable_typed_config()->PackFrom(writer_config); - EXPECT_TRUE(factory.createUdpPacketWriterFactory(config) != nullptr); + testing::NiceMock listener_context; + EXPECT_TRUE(factory.createUdpPacketWriterFactory(config, listener_context) != nullptr); } } // namespace Quic diff --git a/test/extensions/udp_packet_writer/gso/BUILD b/test/extensions/udp_packet_writer/gso/BUILD index f5751aaf2ecd5..2d4e982fb7827 100644 --- a/test/extensions/udp_packet_writer/gso/BUILD +++ b/test/extensions/udp_packet_writer/gso/BUILD @@ -14,6 +14,7 @@ envoy_cc_test( srcs = ["config_test.cc"], rbe_pool = "6gig", deps = [ + "//test/mocks/server:listener_factory_context_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/udp_packet_writer/v3:pkg_cc_proto", ] + envoy_select_enable_http3([ diff --git a/test/extensions/udp_packet_writer/gso/config_test.cc b/test/extensions/udp_packet_writer/gso/config_test.cc index b88bfdfa5f772..09dc80aa098ba 100644 --- a/test/extensions/udp_packet_writer/gso/config_test.cc +++ b/test/extensions/udp_packet_writer/gso/config_test.cc @@ -4,6 +4,10 @@ #include "source/extensions/udp_packet_writer/gso/config.h" +#include "test/mocks/server/listener_factory_context.h" + +#include "gmock/gmock.h" + #if UDP_GSO_BATCH_WRITER_COMPILETIME_SUPPORT #include "gtest/gtest.h" @@ -26,7 +30,8 @@ TEST(FactoryTest, CreateUdpPacketWriterFactory) { envoy::extensions::udp_packet_writer::v3::UdpGsoBatchWriterFactory writer_config; envoy::config::core::v3::TypedExtensionConfig config; config.mutable_typed_config()->PackFrom(writer_config); - EXPECT_TRUE(factory.createUdpPacketWriterFactory(config) != nullptr); + testing::NiceMock listener_context; + EXPECT_TRUE(factory.createUdpPacketWriterFactory(config, listener_context) != nullptr); } } // namespace Quic From c84848d4634139b48c35ceb4f036fb227f42ce01 Mon Sep 17 00:00:00 2001 From: code Date: Fri, 15 May 2026 09:02:45 +0800 Subject: [PATCH 315/750] retry: add refresh cluster on retry to enable cross cluster retry (#44719) Commit Message: retry: add refresh cluster on retry to enable cross cluster retry Additional Description: This PR added a new feature to enable cross clusters retry. For every retry, the refreshClusterEntry will be called to refresh the target cluster of the route and the new cluster will be used. Risk Level: low. Testing: unit. Docs Changes: n/a. Release Notes: added. Platform Specific Features: n/a. --------- Signed-off-by: wbpcode/wangbaiping Signed-off-by: code Co-authored-by: Adi (Suissa) Peleg --- .../config/route/v3/route_components.proto | 14 ++- changelogs/current.yaml | 7 ++ envoy/router/router.h | 5 + source/common/router/retry_policy_impl.cc | 3 +- source/common/router/retry_policy_impl.h | 2 + source/common/router/router.cc | 31 ++++- source/common/router/router.h | 6 + test/common/router/config_impl_test.cc | 9 ++ test/common/router/router_test.cc | 114 ++++++++++++++++++ test/mocks/router/mocks.h | 2 + 10 files changed, 190 insertions(+), 3 deletions(-) diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index f58349caa3c74..1adb7e7873c00 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -1564,7 +1564,7 @@ message RouteAction { } // HTTP retry :ref:`architecture overview `. -// [#next-free-field: 14] +// [#next-free-field: 15] message RetryPolicy { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RetryPolicy"; @@ -1788,6 +1788,18 @@ message RetryPolicy { // HTTP headers which must be present in the request for retries to be attempted. repeated HeaderMatcher retriable_request_headers = 10; + + // By default, the target upstream cluster of a retry request is the same as the original request, + // and Envoy will not try to refresh it when retrying. + // If this field is set to true, Envoy will try to refresh the target upstream cluster when + // retrying a request. This is useful when users want to try different upstream cluster for + // each retry attempt. + // + // .. note:: + // This currently works when the route cluster specifier support the dynamic refresh, + // e.g. :ref:`matcher cluster specifier + // `. + bool refresh_cluster_on_retry = 14; } // HTTP request hedging :ref:`architecture overview `. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 4a51686156567..e1e4e7ccb6f79 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -149,6 +149,13 @@ new_features: metric processing logic (tag enrichment, name rewriting, custom metric injection) into the proxy itself. Configured via :ref:`WasmFilterStatsSinkConfig `. +- area: router + change: | + Added :ref:`refresh_cluster_on_retry ` + to :ref:`retry policies ` so retry attempts can + refresh the route-selected upstream cluster before being sent. This enables cross-cluster + retries for dynamic cluster selection such as the :ref:`matcher cluster specifier + `. - area: mysql_proxy change: | Added SSL termination support to the MySQL proxy filter with RSA-mediated ``caching_sha2_password`` diff --git a/envoy/router/router.h b/envoy/router/router.h index 306474c4489be..6bfe70f47b69c 100644 --- a/envoy/router/router.h +++ b/envoy/router/router.h @@ -332,6 +332,11 @@ class RetryPolicy { * back-off interval parsed from response headers. */ virtual std::chrono::milliseconds resetMaxInterval() const PURE; + + /** + * @return whether the route-selected upstream cluster should be refreshed before a retry attempt. + */ + virtual bool refreshClusterOnRetry() const PURE; }; /** diff --git a/source/common/router/retry_policy_impl.cc b/source/common/router/retry_policy_impl.cc index 2fd80ecd56dda..eb0bf289cef99 100644 --- a/source/common/router/retry_policy_impl.cc +++ b/source/common/router/retry_policy_impl.cc @@ -31,7 +31,8 @@ RetryPolicyImpl::RetryPolicyImpl(const envoy::config::route::v3::RetryPolicy& re retry_policy.retriable_headers(), common_context)), retriable_request_headers_(Http::HeaderUtility::buildHeaderMatcherVector( retry_policy.retriable_request_headers(), common_context)), - validation_visitor_(&validation_visitor) { + validation_visitor_(&validation_visitor), + refresh_cluster_on_retry_(retry_policy.refresh_cluster_on_retry()) { per_try_timeout_ = std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(retry_policy, per_try_timeout, 0)); per_try_idle_timeout_ = diff --git a/source/common/router/retry_policy_impl.h b/source/common/router/retry_policy_impl.h index 3acdfdda38b6a..0d621c4067a76 100644 --- a/source/common/router/retry_policy_impl.h +++ b/source/common/router/retry_policy_impl.h @@ -56,6 +56,7 @@ class RetryPolicyImpl : public RetryPolicy { return reset_headers_; } std::chrono::milliseconds resetMaxInterval() const override { return reset_max_interval_; } + bool refreshClusterOnRetry() const override { return refresh_cluster_on_retry_; } private: RetryPolicyImpl(const envoy::config::route::v3::RetryPolicy& retry_policy, @@ -86,6 +87,7 @@ class RetryPolicyImpl : public RetryPolicy { std::chrono::milliseconds reset_max_interval_{300000}; ProtobufMessage::ValidationVisitor* validation_visitor_{}; std::vector retry_options_predicates_; + const bool refresh_cluster_on_retry_{false}; }; } // namespace Router diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 7410a86dd5486..72e5d3d127fcf 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -833,9 +833,21 @@ bool Filter::continueDecodeHeaders(Upstream::ThreadLocalCluster* cluster, // Ensure an http transport scheme is selected before continuing with decoding. ASSERT(headers.Scheme()); + const auto* effective_retry_policy = getEffectiveRetryPolicy(); retry_state_ = - createRetryState(*getEffectiveRetryPolicy(), headers, *cluster_, config_->factory_context_, + createRetryState(*effective_retry_policy, headers, *cluster_, config_->factory_context_, callbacks_->dispatcher(), route_entry_->priority()); + if (retry_state_ != nullptr) { + // Cross-cluster retry is enabled only if the retry policy requests cluster refresh and hedging + // is not active (hedging with cross-cluster retry is not supported). + // + // If the hedging policy is enabled, there would be multiple request attempts in parallel and + // different clusters may be selected for different attempts. It would make the retry logic more + // complicated. + cross_cluster_retry_ = effective_retry_policy->refreshClusterOnRetry() && + !hedging_params_.hedge_on_per_try_timeout_ && + callbacks_->downstreamCallbacks().has_value(); + } absl::InlinedVector, 2> active_shadow_policies; @@ -2310,6 +2322,16 @@ void Filter::doRetry(bool can_send_early_data, bool can_use_http3, TimeoutRetry host_selection_cancelable_.reset(); } + if (cross_cluster_retry_) { + // If the cross cluster retry is enabled, we need to refresh the route cluster for this attempt. + // + // TODO(wbpcode): In current implementation, although we will refresh the target upstream + // cluster for this retry attempt. But part of initial cluster's configuration like circuit + // breaking, retry policy and so on will still be used for this request because it will bring + // lots of complexity to refresh all these state and bring limited benefit. + callbacks_->downstreamCallbacks()->refreshRouteCluster(); + } + // Clusters can technically get removed by CDS during a retry. Make sure it still exists. const auto cluster = config_->cm_.getThreadLocalCluster(route_entry_->clusterName()); std::unique_ptr generic_conn_pool; @@ -2319,6 +2341,13 @@ void Filter::doRetry(bool can_send_early_data, bool can_use_http3, TimeoutRetry return; } + if (auto cluster_info = cluster->info(); cluster_info != cluster_) { + ENVOY_STREAM_LOG(debug, "cross cluster retry from {} to {}", *callbacks_, cluster_->name(), + cluster_info->name()); + expired_clusters_.emplace_back(std::move(cluster_)); + cluster_ = std::move(cluster_info); + } + // Update retry stats for the retry attempt before doing host selection, so that the stats are // updated even if host selection fails or is slow. This also ensures that if the retry attempt // fails due to a timeout during host selection, the retry attempt will be counted in the retry diff --git a/source/common/router/router.h b/source/common/router/router.h index a4823300c3579..921b4e592523f 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -646,6 +646,9 @@ class Filter : Logger::Loggable, Http::StreamDecoderFilterCallbacks* callbacks_{}; RouteConstSharedPtr route_; const RouteEntry* route_entry_{}; + // Expired clusters that we tried. Keep track of them to ensure they stay alive until the request + // is complete. + absl::InlinedVector expired_clusters_; Upstream::ClusterInfoConstSharedPtr cluster_; std::unique_ptr alt_stat_prefix_; const VirtualCluster* request_vcluster_{}; @@ -695,6 +698,9 @@ class Filter : Logger::Loggable, bool request_buffer_overflowed_ : 1 = false; const bool allow_multiplexed_upstream_half_close_ : 1 = false; bool upstream_request_started_ : 1 = false; + // True if cross-cluster retry is enabled: refreshClusterOnRetry is set in the effective retry + // policy and there is no hedge policy (hedging is incompatible with cross-cluster retry). + bool cross_cluster_retry_ : 1 = false; // Indicate that ORCA report is received to process it only once in either response headers or // trailers. bool orca_load_report_received_ : 1 = false; diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 79f8bd6a0dcb6..c3fc9d1dc7dec 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -4961,6 +4961,7 @@ TEST_F(RouteMatcherTest, Retry) { per_try_idle_timeout: 5s num_retries: 3 retry_on: 5xx,gateway-error,connect-failure,reset + refresh_cluster_on_retry: true )EOF"; factory_context_.cluster_manager_.initializeClusters({"www2"}, {}); @@ -5021,6 +5022,14 @@ TEST_F(RouteMatcherTest, Retry) { ->routeEntry() ->retryPolicy() ->retryOn()); + EXPECT_FALSE(config.route(genHeaders("www.lyft.com", "/foo", "GET"), 0) + ->routeEntry() + ->retryPolicy() + ->refreshClusterOnRetry()); + EXPECT_TRUE(config.route(genHeaders("www.lyft.com", "/", "GET"), 0) + ->routeEntry() + ->retryPolicy() + ->refreshClusterOnRetry()); } class TestRetryOptionsPredicateFactory : public Upstream::RetryOptionsPredicateFactory { diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index e037f8ecb62d1..ec34ce30dbe7c 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -2738,6 +2738,55 @@ TEST_F(RouterTest, HedgedPerTryTimeoutFirstRequestSucceeds) { // TODO: Verify hedge stats here once they are implemented. } +TEST_F(RouterTest, RetryUpstream5xxRefreshesClusterOnRetry) { + callbacks_.route_->route_entry_.retry_policy_->refresh_cluster_on_retry_ = true; + cm_.initializeThreadLocalClusters({"fake_cluster", "alt_cluster"}); + + NiceMock encoder1; + Http::ResponseDecoder* response_decoder = nullptr; + EXPECT_CALL( + cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginConnectSuccess, absl::optional{})); + expectNewStreamWithImmediateEncoder(encoder1, &response_decoder, Http::Protocol::Http10); + + expectResponseTimerCreate(); + + Http::TestRequestHeaderMapImpl headers{{"x-envoy-retry-on", "5xx"}, {"x-envoy-internal", "true"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_->decodeHeaders(headers, true); + + router_->retry_state_->expectHeadersRetry(); + Http::ResponseHeaderMapPtr response_headers1( + new Http::TestResponseHeaderMapImpl{{":status", "503"}}); + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(_, absl::optional(503))); + ASSERT(response_decoder); + response_decoder->decodeHeaders(std::move(response_headers1), true); + + EXPECT_CALL(callbacks_.downstream_callbacks_, refreshRouteCluster()).WillOnce(Invoke([this]() { + callbacks_.route_->route_entry_.cluster_name_ = "alt_cluster"; + cm_.thread_local_cluster_.cluster_.info_->name_ = "alt_cluster"; + })); + EXPECT_CALL(cm_, getThreadLocalCluster(absl::string_view("alt_cluster"))) + .WillOnce(Return(&cm_.thread_local_cluster_)); + + NiceMock encoder2; + EXPECT_CALL( + cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginConnectSuccess, absl::optional{})); + expectNewStreamWithImmediateEncoder(encoder2, &response_decoder, Http::Protocol::Http10); + + router_->retry_state_->callback_(); + + EXPECT_CALL(*router_->retry_state_, shouldRetryHeaders(_, _, _)) + .WillOnce(Return(RetryStatus::No)); + Http::ResponseHeaderMapPtr response_headers2( + new Http::TestResponseHeaderMapImpl{{":status", "200"}}); + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(_, absl::optional(200))); + response_decoder->decodeHeaders(std::move(response_headers2), true); +} + // Tests that an upstream request is reset even if it can't be retried as long as there is // another in-flight request we're waiting on. // Sequence: @@ -2836,6 +2885,71 @@ TEST_F(RouterTest, HedgedPerTryTimeoutResetsOnBadHeaders) { // TODO: Verify hedge stats here once they are implemented. } +TEST_F(RouterTest, HedgedRetryDoesNotRefreshClusterOnRetry) { + callbacks_.route_->route_entry_.retry_policy_->refresh_cluster_on_retry_ = true; + enableHedgeOnPerTryTimeout(); + + NiceMock encoder1; + Http::ResponseDecoder* response_decoder1 = nullptr; + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) + .WillOnce( + Invoke([&](Http::ResponseDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks, + const Http::ConnectionPool::Instance::StreamOptions&) + -> Http::ConnectionPool::Cancellable* { + response_decoder1 = &decoder; + EXPECT_CALL(*router_->retry_state_, onHostAttempted(_)); + callbacks.onPoolReady(encoder1, cm_.thread_local_cluster_.conn_pool_.host_, + upstream_stream_info_, Http::Protocol::Http10); + return nullptr; + })); + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginConnectSuccess, + absl::optional(absl::nullopt))) + .Times(2); + expectPerTryTimerCreate(); + expectResponseTimerCreate(); + + Http::TestRequestHeaderMapImpl headers{{"x-envoy-upstream-rq-per-try-timeout-ms", "5"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_->decodeHeaders(headers, true); + + router_->retry_state_->expectHedgedPerTryTimeoutRetry(); + EXPECT_CALL( + cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginTimeout, absl::optional(504))); + per_try_timeout_->invokeCallback(); + + EXPECT_CALL(callbacks_.downstream_callbacks_, refreshRouteCluster()).Times(0); + NiceMock encoder2; + Http::ResponseDecoder* response_decoder2 = nullptr; + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) + .WillOnce( + Invoke([&](Http::ResponseDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks, + const Http::ConnectionPool::Instance::StreamOptions&) + -> Http::ConnectionPool::Cancellable* { + response_decoder2 = &decoder; + EXPECT_CALL(*router_->retry_state_, onHostAttempted(_)); + callbacks.onPoolReady(encoder2, cm_.thread_local_cluster_.conn_pool_.host_, + upstream_stream_info_, Http::Protocol::Http10); + return nullptr; + })); + expectPerTryTimerCreate(); + router_->retry_state_->callback_(); + ASSERT(response_decoder2); + + Http::ResponseHeaderMapPtr response_headers( + new Http::TestResponseHeaderMapImpl{{":status", "200"}}); + EXPECT_CALL(*router_->retry_state_, wouldRetryFromHeaders(_, _, _)) + .WillOnce(Return(RetryState::RetryDecision::NoRetry)); + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(_, absl::optional(200))); + EXPECT_CALL(encoder1.stream_, resetStream(_)).Times(0); + EXPECT_CALL(encoder2.stream_, resetStream(_)); + EXPECT_CALL(callbacks_, encodeHeaders_(_, true)); + ASSERT(response_decoder1); + response_decoder1->decodeHeaders(std::move(response_headers), true); +} + // Three requests sent: 1) 5xx error, 2) per try timeout, 3) gets good response // headers. TEST_F(RouterTest, HedgedPerTryTimeoutThirdRequestSucceeds) { diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index efc99aa69c878..1f3c96ec9a0ea 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -162,6 +162,7 @@ class TestRetryPolicy : public RetryPolicy { const std::vector& resetHeaders() const override { return reset_headers_; } + bool refreshClusterOnRetry() const override { return refresh_cluster_on_retry_; } std::chrono::milliseconds per_try_timeout_{0}; std::chrono::milliseconds per_try_idle_timeout_{0}; @@ -175,6 +176,7 @@ class TestRetryPolicy : public RetryPolicy { absl::optional max_interval_; std::vector reset_headers_; std::chrono::milliseconds reset_max_interval_{300000}; + bool refresh_cluster_on_retry_{false}; std::vector retry_options_predicates_; }; From cd89465b50fc853c9ae29ccf7ef3bd1a6ecdad82 Mon Sep 17 00:00:00 2001 From: Kuat Date: Thu, 14 May 2026 19:29:40 -0700 Subject: [PATCH 316/750] runtime: delete unused use_eds_cache_for_ads (#45079) Change-Id: I432cb1d8bc7d8463a0f7e134de8ba84f44a32fbb Commit Message: the flag is not used so can be deleted Additional Description: Risk Level: low Testing: Docs Changes: Release Notes: Platform Specific Features: Signed-off-by: Kuat Yessenov --- source/common/runtime/runtime_features.cc | 1 - test/integration/ads_integration_test.cc | 1 - 2 files changed, 2 deletions(-) diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index c8f1e2049ad33..3f2b0086a7a1d 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -121,7 +121,6 @@ RUNTIME_GUARD(envoy_reloadable_features_xds_failover_to_primary_enabled); RUNTIME_GUARD(envoy_reloadable_features_xds_legacy_delta_skip_subsequent_node); RUNTIME_GUARD(envoy_restart_features_move_locality_schedulers_to_lb); RUNTIME_GUARD(envoy_restart_features_raise_file_limits); -RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); RUNTIME_GUARD(envoy_restart_features_validate_http3_pseudo_headers); RUNTIME_GUARD(envoy_restart_features_worker_threads_watchdog_fix); // Begin false flags. Most of them should come with a TODO to flip true. diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index bf63d32cf3090..4482e08c318f5 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -817,7 +817,6 @@ TEST_P(AdsIntegrationTest, CdsKeepEdsAfterWarmingFailure) { TEST_P(AdsIntegrationTest, CdsKeepEdsDropOverloadAfterWarmingFailure) { // This test should be kept after the runtime guard is deprecated - config_helper_.addRuntimeOverride("envoy.restart_features.use_eds_cache_for_ads", "true"); initialize(); EXPECT_TRUE(compareDiscoveryRequest(Config::TestTypeUrl::get().Cluster, "", {}, {}, {}, true)); envoy::config::cluster::v3::Cluster cluster = buildCluster("cluster_0"); From bd28a2f5eea1c7185959565df2ede272ac8430f3 Mon Sep 17 00:00:00 2001 From: Xin Date: Thu, 14 May 2026 23:08:41 -0400 Subject: [PATCH 317/750] ext_proc: avoid copies introduced by auto assignment (#44657) Commit Message: auto is great, not so much when it creates unnecessary copies Additional Description: part of issue https://github.com/envoyproxy/envoy/issues/44795 Risk Level: LOW Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Xin Zhuang --- source/extensions/filters/http/ext_proc/config.cc | 4 ++-- source/extensions/filters/http/ext_proc/ext_proc.cc | 12 ++++++------ .../http/ext_proc/http_client/http_client_impl.cc | 13 ++++++++----- .../filters/http/ext_proc/matching_utils.cc | 6 ++++-- .../filters/http/ext_proc/mutation_utils.cc | 4 ++-- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/source/extensions/filters/http/ext_proc/config.cc b/source/extensions/filters/http/ext_proc/config.cc index 7d90c81b03e73..156320d9c8a24 100644 --- a/source/extensions/filters/http/ext_proc/config.cc +++ b/source/extensions/filters/http/ext_proc/config.cc @@ -88,7 +88,7 @@ ExternalProcessingFilterConfig::createFilterFactoryFromProtoTyped( PROTOBUF_GET_MS_OR_DEFAULT(proto_config, message_timeout, DefaultMessageTimeoutMs); const uint32_t max_message_timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config, max_message_timeout, DefaultMaxMessageTimeoutMs); - const auto filter_config = std::make_shared( + auto filter_config = std::make_shared( proto_config, std::chrono::milliseconds(message_timeout_ms), max_message_timeout_ms, dual_info.scope, stats_prefix, dual_info.is_upstream, Envoy::Extensions::Filters::Common::Expr::getBuilder(context), context); @@ -140,7 +140,7 @@ ExternalProcessingFilterConfig::createFilterFactoryFromProtoWithServerContextTyp PROTOBUF_GET_MS_OR_DEFAULT(proto_config, message_timeout, DefaultMessageTimeoutMs); const uint32_t max_message_timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config, max_message_timeout, DefaultMaxMessageTimeoutMs); - const auto filter_config = std::make_shared( + auto filter_config = std::make_shared( proto_config, std::chrono::milliseconds(message_timeout_ms), max_message_timeout_ms, server_context.scope(), stats_prefix, false, Envoy::Extensions::Filters::Common::Expr::getBuilder(server_context), server_context); diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 8c6e42bd41e55..fcd478f4aa1e3 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -718,7 +718,7 @@ void Filter::sendRequest(const ProcessorState& state, ProcessingRequest&& req, b void Filter::onComplete(ProcessingResponse& response) { ENVOY_STREAM_LOG(debug, "Received successful response from server", *decoder_callbacks_); - std::unique_ptr resp_ptr = std::make_unique(response); + auto resp_ptr = std::make_unique(response); onReceiveMessage(std::move(resp_ptr)); } @@ -1538,7 +1538,7 @@ void Filter::addDynamicMetadata(const ProcessorState& state, ProcessingRequest& envoy::config::core::v3::Metadata forwarding_metadata; // Forward cluster metadata if so configured. - const auto cluster_info = cb->streamInfo().upstreamClusterInfo(); + const auto& cluster_info = cb->streamInfo().upstreamClusterInfo(); if (cluster_info) { const auto& cluster_metadata = cluster_info->metadata().filter_metadata(); for (const auto& context_key : state.untypedClusterMetadataForwardingNamespaces()) { @@ -1599,7 +1599,7 @@ void Filter::addDynamicMetadata(const ProcessorState& state, ProcessingRequest& } } - *req.mutable_metadata_context() = forwarding_metadata; + *req.mutable_metadata_context() = std::move(forwarding_metadata); } void Filter::addAttributes(ProcessorState& state, ProcessingRequest& req) { @@ -1615,7 +1615,7 @@ void Filter::addAttributes(ProcessorState& state, ProcessingRequest& req) { auto attributes = state.evaluateAttributes(config_->expressionManager(), *activation_ptr); state.setSentAttributes(true); - (*req.mutable_attributes())[FilterName] = attributes; + (*req.mutable_attributes())[FilterName] = std::move(attributes); } void Filter::setDynamicMetadata(Http::StreamFilterCallbacks* cb, const ProcessorState& state, @@ -1630,8 +1630,8 @@ void Filter::setDynamicMetadata(Http::StreamFilterCallbacks* cb, const Processor return; } - auto response_metadata = response.dynamic_metadata().fields(); - auto receiving_namespaces = state.untypedReceivingMetadataNamespaces(); + const auto& response_metadata = response.dynamic_metadata().fields(); + const auto& receiving_namespaces = state.untypedReceivingMetadataNamespaces(); for (const auto& context_key : response_metadata) { bool found_allowed_namespace = false; if (auto metadata_it = diff --git a/source/extensions/filters/http/ext_proc/http_client/http_client_impl.cc b/source/extensions/filters/http/ext_proc/http_client/http_client_impl.cc index 7b03ac17f7ea8..6c19ba701f8e5 100644 --- a/source/extensions/filters/http/ext_proc/http_client/http_client_impl.cc +++ b/source/extensions/filters/http/ext_proc/http_client/http_client_impl.cc @@ -46,7 +46,8 @@ void ExtProcHttpClient::sendRequest(envoy::service::ext_proc::v3::ProcessingRequ // Transcode req message into JSON string. auto req_in_json = MessageUtil::getJsonStringFromMessage(req); if (req_in_json.ok()) { - const auto http_uri = config_.http_service().http_service().http_uri(); + const envoy::config::core::v3::HttpUri& http_uri = + config_.http_service().http_service().http_uri(); Http::RequestHeaderMapPtr headers = buildHttpRequestHeaders(http_uri.uri(), stream_id); headers_applicator_->apply(*headers); @@ -76,12 +77,14 @@ void ExtProcHttpClient::onSuccess(const Http::AsyncClient::Request&, if (status.has_value()) { uint64_t status_code = status.value(); if (status_code == Envoy::enumToInt(Envoy::Http::Code::OK)) { - std::string msg_body = response->body().toString(); - ENVOY_LOG(debug, "Response status is OK, message body length {}", msg_body.size()); + const size_t body_len = response->body().length(); envoy::service::ext_proc::v3::ProcessingResponse response_msg; - if (!msg_body.empty()) { + if (body_len > 0) { + const absl::string_view msg_body( + static_cast(response->body().linearize(body_len)), body_len); bool has_unknown_field; - auto status = MessageUtil::loadFromJsonNoThrow(msg_body, response_msg, has_unknown_field); + const absl::Status& status = + MessageUtil::loadFromJsonNoThrow(msg_body, response_msg, has_unknown_field); if (!status.ok()) { ENVOY_LOG( error, diff --git a/source/extensions/filters/http/ext_proc/matching_utils.cc b/source/extensions/filters/http/ext_proc/matching_utils.cc index 29e36000985ca..8638dce27d92e 100644 --- a/source/extensions/filters/http/ext_proc/matching_utils.cc +++ b/source/extensions/filters/http/ext_proc/matching_utils.cc @@ -19,7 +19,8 @@ ExpressionManager::initExpressions(const Protobuf::RepeatedPtrField if (expressions.contains(matcher)) { continue; } - auto parse_status = google::api::expr::parser::Parse(matcher); + const absl::StatusOr parse_status = + google::api::expr::parser::Parse(matcher); if (!parse_status.ok()) { throw EnvoyException("Unable to parse descriptor expression: " + parse_status.status().ToString()); @@ -55,7 +56,8 @@ ExpressionManager::evaluateAttributes(const Filters::Common::Expr::Activation& a for (const auto& hash_entry : expr) { Protobuf::Arena arena; - const auto result = hash_entry.second.evaluate(activation, &arena); + const absl::StatusOr& result = + hash_entry.second.evaluate(activation, &arena); if (!result.ok()) { // TODO: Stats? continue; diff --git a/source/extensions/filters/http/ext_proc/mutation_utils.cc b/source/extensions/filters/http/ext_proc/mutation_utils.cc index 249b70e8b4934..3092ffa764c99 100644 --- a/source/extensions/filters/http/ext_proc/mutation_utils.cc +++ b/source/extensions/filters/http/ext_proc/mutation_utils.cc @@ -76,8 +76,8 @@ void MutationUtils::headersToProto( &disallowed_headers](const Http::HeaderEntry& e) -> Http::HeaderMap::Iterate { if (headerCanBeForwarded(e.key().getStringView(), allowed_headers, disallowed_headers)) { auto* new_header = proto_out.add_headers(); - new_header->set_key(std::string(e.key().getStringView())); - new_header->set_raw_value(std::string(e.value().getStringView())); + new_header->set_key(e.key().getStringView()); + new_header->set_raw_value(e.value().getStringView()); } return Http::HeaderMap::Iterate::Continue; }); From 1ece0cd9f825be38cbbd646d8547c4637428bda6 Mon Sep 17 00:00:00 2001 From: code Date: Fri, 15 May 2026 12:52:50 +0800 Subject: [PATCH 318/750] clang tidy: fix a minor left clang tidy problem of source (#45080) --- .../common/ext_authz/ext_authz_http_impl.cc | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc index 0284a0c7d97ab..61a988aa2c0ab 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc @@ -38,25 +38,12 @@ Http::Code zeroHttpCode() { return static_cast(0); } -// Static response used for creating authorization ERROR responses. +// Response used for creating authorization ERROR responses. // Note: status_code is left unset so the filter can use the configured status_on_error // configuration. -const Response& errorResponse() { - CONSTRUCT_ON_FIRST_USE(Response, Response{CheckStatus::Error, - UnsafeHeaderVector{}, - UnsafeHeaderVector{}, - UnsafeHeaderVector{}, - UnsafeHeaderVector{}, - UnsafeHeaderVector{}, - UnsafeHeaderVector{}, - UnsafeHeaderVector{}, - false, - {{}}, - Http::Utility::QueryParamsVector{}, - {}, - EMPTY_STRING, - zeroHttpCode(), - Protobuf::Struct{}}); +ResponsePtr errorResponse() { + return std::make_unique( + Response{.status = CheckStatus::Error, .status_code = zeroHttpCode()}); } // Static matcher that never matches anything. Used for matchers that are not applicable @@ -376,7 +363,7 @@ void RawHttpClientImpl::check(RequestCallbacks& callbacks, if (thread_local_cluster == nullptr) { // TODO(dio): Add stats related to this. ENVOY_LOG(debug, "ext_authz cluster '{}' does not exist", cluster); - callbacks_->onComplete(std::make_unique(errorResponse())); + callbacks_->onComplete(errorResponse()); callbacks_ = nullptr; } else { // Do not enforce a sampling decision on this span; instead keep the parent's sampling status. @@ -409,7 +396,7 @@ void RawHttpClientImpl::onFailure(const Http::AsyncClient::Request&, // TODO(botengyao): handle different failure reasons. ASSERT(reason == Http::AsyncClient::FailureReason::Reset || reason == Http::AsyncClient::FailureReason::ExceedResponseBufferLimit); - callbacks_->onComplete(std::make_unique(errorResponse())); + callbacks_->onComplete(errorResponse()); callbacks_ = nullptr; } @@ -432,7 +419,7 @@ ResponsePtr RawHttpClientImpl::toResponse(Http::ResponseMessagePtr message) { // codes. A Forbidden response is sent to the client if the filter has not been configured with // failure_mode_allow. if (Http::CodeUtility::is5xx(status_code)) { - return std::make_unique(errorResponse()); + return errorResponse(); } // Extract headers-to-remove from the storage header coming from the From 5032a1d452364defe5f11a313798c22888eb45e8 Mon Sep 17 00:00:00 2001 From: basundhara-c Date: Thu, 14 May 2026 23:45:34 -0700 Subject: [PATCH 319/750] dynamic_modules: add cluster filter-state read ABI (#45040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit Message: Adds two ABI callbacks so that a dynamic-module cluster's load balancer can read filter state set by an upstream HTTP filter (or any other producer) when picking a host. Additional Description: Dynamic-module clusters get two new ABI callbacks (get_filter_state_bytes and get_filter_state_typed) so a custom load balancer can read filter state during host selection. Today the cluster LB side can only see things like SNI and route metadata — it can't reach into filter state that an upstream HTTP filter (or any other producer) attached to the request. With these callbacks, an HTTP filter can compute or look up a value, stash it on filter state, and a downstream dynamic-module cluster can use that value to pick a host. API shape Both callbacks mirror the existing HTTP-filter equivalents so module authors see a familiar surface: - envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes — reads a Router::StringAccessor-typed value. Returns a view that borrows directly from the request's filter state. - envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed — reads any registered filter-state object and returns its serializeAsString() bytes. Risk Level: Low Testing: Integration test added Docs Changes: N.A Release Notes: N.A Platform Specific Features: N.A Signed-off-by: Basundhara Chakrabarty --- changelogs/current.yaml | 7 + .../extensions/clusters/dynamic_modules/BUILD | 1 + .../clusters/dynamic_modules/abi_impl.cc | 65 +++++++ source/extensions/dynamic_modules/abi/abi.h | 42 +++++ source/extensions/dynamic_modules/abi_impl.cc | 16 ++ .../dynamic_modules/sdk/rust/src/cluster.rs | 68 +++++++- .../dynamic_modules/sdk/rust/src/lib_test.rs | 18 ++ .../extensions/clusters/dynamic_modules/BUILD | 6 + .../clusters/dynamic_modules/cluster_test.cc | 146 ++++++++++++++++ .../dynamic_modules/integration_test.cc | 100 +++++++++++ .../dynamic_modules/test_data/rust/BUILD | 2 + .../rust/cluster_filter_state_test.rs | 158 ++++++++++++++++++ 12 files changed, 626 insertions(+), 3 deletions(-) create mode 100644 test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs diff --git a/changelogs/current.yaml b/changelogs/current.yaml index e1e4e7ccb6f79..13c2ea9c0a7ff 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -134,6 +134,13 @@ new_features: change: | Added ``envoy_dynamic_module_callback_is_validation_mode`` ABI callback that allows dynamic modules to check if the server is running in config validation mode. +- area: dynamic_modules + change: | + Added ``envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes`` and + ``envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed`` ABI callbacks so + that a dynamic-module cluster's load balancer can read filter state set by an upstream HTTP + filter (or any other producer) when picking a host. The Rust SDK exposes these as + ``ClusterLbContext::get_filter_state_bytes`` and ``ClusterLbContext::get_filter_state_typed``. - area: access_log change: | Supported the singleton stats scope in the :ref:`stats access logger `. diff --git a/source/extensions/clusters/dynamic_modules/BUILD b/source/extensions/clusters/dynamic_modules/BUILD index eb432c53a167e..bf429e7a3973d 100644 --- a/source/extensions/clusters/dynamic_modules/BUILD +++ b/source/extensions/clusters/dynamic_modules/BUILD @@ -25,6 +25,7 @@ envoy_cc_library( "//source/common/http:message_lib", "//source/common/network:address_lib", "//source/common/network:utility_lib", + "//source/common/router:string_accessor_lib", "//source/common/stats:utility_lib", "//source/common/upstream:cluster_factory_lib", "//source/common/upstream:upstream_includes", diff --git a/source/extensions/clusters/dynamic_modules/abi_impl.cc b/source/extensions/clusters/dynamic_modules/abi_impl.cc index 13bc58819c8ca..c9bca9324c330 100644 --- a/source/extensions/clusters/dynamic_modules/abi_impl.cc +++ b/source/extensions/clusters/dynamic_modules/abi_impl.cc @@ -5,6 +5,7 @@ #include "source/common/common/assert.h" #include "source/common/common/thread.h" #include "source/common/http/message_impl.h" +#include "source/common/router/string_accessor_impl.h" #include "source/extensions/clusters/dynamic_modules/cluster.h" #include "source/extensions/dynamic_modules/abi/abi.h" @@ -831,6 +832,70 @@ bool envoy_dynamic_module_callback_cluster_lb_context_get_downstream_connection_ return true; } +bool envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes( + envoy_dynamic_module_type_cluster_lb_context_envoy_ptr context_envoy_ptr, + envoy_dynamic_module_type_module_buffer key, envoy_dynamic_module_type_envoy_buffer* result) { + if (context_envoy_ptr == nullptr || result == nullptr) { + return false; + } + auto* stream_info = getContext(context_envoy_ptr)->requestStreamInfo(); + if (!stream_info) { + ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::dynamic_modules), debug, + "stream info is not available"); + return false; + } + absl::string_view key_view(key.ptr, key.length); + auto filter_state = + stream_info->filterState()->getDataReadOnly(key_view); + if (!filter_state) { + ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::dynamic_modules), debug, + "key '{}' not found in filter state", key_view); + return false; + } + absl::string_view str = filter_state->asString(); + *result = {const_cast(str.data()), str.size()}; + return true; +} + +bool envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + envoy_dynamic_module_type_cluster_lb_context_envoy_ptr context_envoy_ptr, + envoy_dynamic_module_type_module_buffer key, envoy_dynamic_module_type_envoy_buffer* result) { + if (context_envoy_ptr == nullptr || result == nullptr) { + return false; + } + auto* stream_info = getContext(context_envoy_ptr)->requestStreamInfo(); + if (!stream_info) { + ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::dynamic_modules), debug, + "stream info is not available"); + return false; + } + + absl::string_view key_view(key.ptr, key.length); + const auto* object = stream_info->filterState()->getDataReadOnlyGeneric(key_view); + if (object == nullptr) { + ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::dynamic_modules), debug, + "key '{}' not found in filter state", key_view); + return false; + } + + auto serialized = object->serializeAsString(); + if (!serialized.has_value()) { + ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::dynamic_modules), debug, + "filter state object for key '{}' does not support serialization", + key_view); + return false; + } + + // Cluster host selection runs on a worker thread serially per request. We stash the + // serialized buffer on a thread-local so its address survives until the next call to + // this function on the same thread (matching the documented lifetime in abi.h). + thread_local std::string last_serialized_filter_state; + last_serialized_filter_state = std::move(serialized.value()); + result->ptr = const_cast(last_serialized_filter_state.data()); + result->length = last_serialized_filter_state.size(); + return true; +} + envoy_dynamic_module_type_cluster_scheduler_module_ptr envoy_dynamic_module_callback_cluster_scheduler_new( envoy_dynamic_module_type_cluster_envoy_ptr cluster_envoy_ptr) { diff --git a/source/extensions/dynamic_modules/abi/abi.h b/source/extensions/dynamic_modules/abi/abi.h index a2c63c472df9d..b6ab7f493f29d 100644 --- a/source/extensions/dynamic_modules/abi/abi.h +++ b/source/extensions/dynamic_modules/abi/abi.h @@ -9438,6 +9438,48 @@ bool envoy_dynamic_module_callback_cluster_lb_context_get_downstream_connection_ envoy_dynamic_module_type_cluster_lb_context_envoy_ptr context_envoy_ptr, envoy_dynamic_module_type_envoy_buffer* result_buffer); +/** + * envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes is called by the module + * to get the bytes value of the request's filter state with the given key. This lets a + * dynamic-module cluster consume filter state set by an upstream HTTP filter (or any other + * producer that stores a ``Router::StringAccessor``) when picking a host. If the filter state is + * not accessible, the key does not exist, or the value is not a ``Router::StringAccessor``, this + * returns false. + * + * @param context_envoy_ptr is the per-request load balancer context. + * @param key is the key of the filter state. + * @param result is the pointer to the pointer variable where the pointer to the buffer + * of the bytes value will be stored. + * @return true if the operation is successful, false otherwise. + * + * Note that the buffer pointed by the pointer stored in result is owned by Envoy, and + * is guaranteed to be valid for the duration of the current host-selection callback. + */ +bool envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes( + envoy_dynamic_module_type_cluster_lb_context_envoy_ptr context_envoy_ptr, + envoy_dynamic_module_type_module_buffer key, envoy_dynamic_module_type_envoy_buffer* result); + +/** + * envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed is called by the module + * to get the serialized bytes value of a typed filter state object with the given key. It + * retrieves the object generically and calls serializeAsString to get the bytes representation, + * so it works with any filter state object type (e.g. one set by an upstream HTTP filter through + * a registered ``ObjectFactory``), not just ``Router::StringAccessor``. + * + * @param context_envoy_ptr is the per-request load balancer context. + * @param key is the key of the filter state. + * @param result is the pointer to the buffer where the serialized value will be stored. + * @return true if the operation is successful, false if the stream info is not available, the key + * does not exist, or the object does not support serialization. + * + * Note that the buffer pointed by the pointer stored in result is owned by Envoy, and is + * guaranteed to be valid until the next invocation of this callback on the same worker thread + * or until the end of the current host-selection callback, whichever comes first. + */ +bool envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + envoy_dynamic_module_type_cluster_lb_context_envoy_ptr context_envoy_ptr, + envoy_dynamic_module_type_module_buffer key, envoy_dynamic_module_type_envoy_buffer* result); + /** * envoy_dynamic_module_callback_cluster_lb_async_host_selection_complete is called by the module * to deliver the result of an asynchronous host selection. This must be called exactly once for diff --git a/source/extensions/dynamic_modules/abi_impl.cc b/source/extensions/dynamic_modules/abi_impl.cc index 062c8c8c1d15e..ef30d3622774c 100644 --- a/source/extensions/dynamic_modules/abi_impl.cc +++ b/source/extensions/dynamic_modules/abi_impl.cc @@ -745,6 +745,22 @@ envoy_dynamic_module_callback_cluster_lb_context_get_downstream_connection_sni( return false; } +__attribute__((weak)) bool envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes( + envoy_dynamic_module_type_cluster_lb_context_envoy_ptr, envoy_dynamic_module_type_module_buffer, + envoy_dynamic_module_type_envoy_buffer*) { + IS_ENVOY_BUG("envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes: " + "not implemented in this context"); + return false; +} + +__attribute__((weak)) bool envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + envoy_dynamic_module_type_cluster_lb_context_envoy_ptr, envoy_dynamic_module_type_module_buffer, + envoy_dynamic_module_type_envoy_buffer*) { + IS_ENVOY_BUG("envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed: " + "not implemented in this context"); + return false; +} + __attribute__((weak)) envoy_dynamic_module_type_cluster_scheduler_module_ptr envoy_dynamic_module_callback_cluster_scheduler_new(envoy_dynamic_module_type_cluster_envoy_ptr) { IS_ENVOY_BUG("envoy_dynamic_module_callback_cluster_scheduler_new: " diff --git a/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs b/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs index f74a2c8df0584..78329966e45f9 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs @@ -1,8 +1,9 @@ use crate::buffer::EnvoyBuffer; use crate::{ - abi, drop_wrapped_c_void_ptr, str_to_module_buffer, strs_to_module_buffers, wrap_into_c_void_ptr, - CompletionCallback, EnvoyCounterId, EnvoyCounterVecId, EnvoyGaugeId, EnvoyGaugeVecId, - EnvoyHistogramId, EnvoyHistogramVecId, NEW_CLUSTER_CONFIG_FUNCTION, + abi, bytes_to_module_buffer, drop_wrapped_c_void_ptr, str_to_module_buffer, + strs_to_module_buffers, wrap_into_c_void_ptr, CompletionCallback, EnvoyCounterId, + EnvoyCounterVecId, EnvoyGaugeId, EnvoyGaugeVecId, EnvoyHistogramId, EnvoyHistogramVecId, + NEW_CLUSTER_CONFIG_FUNCTION, }; use mockall::*; use std::panic::{catch_unwind, AssertUnwindSafe}; @@ -226,6 +227,29 @@ pub trait ClusterLbContext { /// /// Returns `None` if the downstream connection or SNI is not available. fn get_downstream_connection_sni(&self) -> Option; + + /// Returns the bytes value of a `Router::StringAccessor` filter state stored on the request. + /// + /// This lets a cluster consume filter state that an upstream HTTP filter set via + /// `EnvoyHttpFilter::set_filter_state_bytes` (or anything else that stores a `StringAccessor`) + /// to make a host-selection decision. + /// + /// Returns `None` if the request has no stream info, the key is not present, or the stored + /// value is not a `StringAccessor`. The returned buffer borrows from Envoy and is valid for + /// the duration of the current host-selection callback. + fn get_filter_state_bytes<'a>(&'a self, key: &[u8]) -> Option>; + + /// Returns the serialized bytes of a typed filter state object stored on the request. + /// + /// Works for any filter state object whose registered `ObjectFactory` produces an object that + /// supports `serializeAsString` — e.g. one set by an upstream HTTP filter via + /// `EnvoyHttpFilter::set_filter_state_typed`. + /// + /// Returns `None` if the request has no stream info, the key is not present, or the object + /// does not support serialization. The returned buffer borrows from Envoy and is valid until + /// the next call to `get_filter_state_typed` on the same worker thread, or until the end of + /// the current host-selection callback, whichever comes first. + fn get_filter_state_typed<'a>(&'a self, key: &[u8]) -> Option>; } /// Envoy-side cluster operations available to the module. @@ -1830,6 +1854,44 @@ impl ClusterLbContext for ClusterLbContextImpl { }; Some(sni.into_owned()) } + + fn get_filter_state_bytes(&self, key: &[u8]) -> Option> { + let mut result = abi::envoy_dynamic_module_type_envoy_buffer { + ptr: std::ptr::null_mut(), + length: 0, + }; + let success = unsafe { + abi::envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes( + self.raw_context, + bytes_to_module_buffer(key), + &mut result, + ) + }; + if success { + Some(unsafe { EnvoyBuffer::new_from_raw(result.ptr as *const _, result.length) }) + } else { + None + } + } + + fn get_filter_state_typed(&self, key: &[u8]) -> Option> { + let mut result = abi::envoy_dynamic_module_type_envoy_buffer { + ptr: std::ptr::null_mut(), + length: 0, + }; + let success = unsafe { + abi::envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + self.raw_context, + bytes_to_module_buffer(key), + &mut result, + ) + }; + if success { + Some(unsafe { EnvoyBuffer::new_from_raw(result.ptr as *const _, result.length) }) + } else { + None + } + } } // Cluster Event Hook Implementations diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs index 4b8a808dd446b..dc6734f0a03f9 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs @@ -4483,6 +4483,24 @@ pub extern "C" fn envoy_dynamic_module_callback_cluster_lb_context_get_downstrea false } +#[no_mangle] +pub extern "C" fn envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes( + _context_envoy_ptr: abi::envoy_dynamic_module_type_cluster_lb_context_envoy_ptr, + _key: abi::envoy_dynamic_module_type_module_buffer, + _result: *mut abi::envoy_dynamic_module_type_envoy_buffer, +) -> bool { + false +} + +#[no_mangle] +pub extern "C" fn envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + _context_envoy_ptr: abi::envoy_dynamic_module_type_cluster_lb_context_envoy_ptr, + _key: abi::envoy_dynamic_module_type_module_buffer, + _result: *mut abi::envoy_dynamic_module_type_envoy_buffer, +) -> bool { + false +} + #[no_mangle] pub extern "C" fn envoy_dynamic_module_callback_cluster_http_callout( _cluster_envoy_ptr: abi::envoy_dynamic_module_type_cluster_envoy_ptr, diff --git a/test/extensions/clusters/dynamic_modules/BUILD b/test/extensions/clusters/dynamic_modules/BUILD index eb99f547812ba..8838ad0139ffb 100644 --- a/test/extensions/clusters/dynamic_modules/BUILD +++ b/test/extensions/clusters/dynamic_modules/BUILD @@ -21,6 +21,8 @@ envoy_cc_test( deps = [ "//source/common/config:metadata_lib", "//source/common/http:message_lib", + "//source/common/router:string_accessor_lib", + "//source/common/stream_info:filter_state_lib", "//source/extensions/clusters/dynamic_modules:cluster_lib", "//source/extensions/dynamic_modules:abi_impl", "//source/extensions/load_balancing_policies/cluster_provided:config", @@ -30,6 +32,7 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/mocks/server:server_factory_context_mocks", + "//test/mocks/stream_info:stream_info_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:load_balancer_context_mock", "//test/mocks/upstream:priority_set_mocks", @@ -46,12 +49,15 @@ envoy_cc_test( name = "integration_test", srcs = ["integration_test.cc"], data = [ + "//test/extensions/dynamic_modules/test_data/rust:cluster_filter_state_test", "//test/extensions/dynamic_modules/test_data/rust:cluster_integration_test", ], rbe_pool = "6gig", deps = [ + "//source/common/router:string_accessor_lib", "//source/extensions/clusters/dynamic_modules:cluster", "//source/extensions/dynamic_modules:abi_impl", + "//source/extensions/filters/http/dynamic_modules:factory_registration", "//source/extensions/load_balancing_policies/cluster_provided:config", "//test/extensions/dynamic_modules:util", "//test/integration:http_integration_lib", diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index 37bf05dee2447..1ca55f05236cc 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -6,6 +6,8 @@ #include "source/common/config/metadata.h" #include "source/common/http/message_impl.h" +#include "source/common/router/string_accessor_impl.h" +#include "source/common/stream_info/filter_state_impl.h" #include "source/extensions/clusters/dynamic_modules/cluster.h" #include "test/common/upstream/utility.h" @@ -13,6 +15,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/network/connection.h" #include "test/mocks/server/server_factory_context.h" +#include "test/mocks/stream_info/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/mocks/upstream/load_balancer_context.h" #include "test/mocks/upstream/priority_set.h" @@ -2121,6 +2124,149 @@ TEST_F(DynamicModuleClusterTest, LbContextGetDownstreamConnectionSniNullResult) context_ptr, nullptr)); } +namespace { + +// A filter state object that does not support serializeAsString — used to exercise the typed-get +// "object does not support serialization" failure branch. +class UnserializableFilterStateObject : public StreamInfo::FilterState::Object {}; + +} // namespace + +// Test get_filter_state_bytes with nullptr context. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateBytesNullContext) { + std::string key = "k"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + envoy_dynamic_module_type_envoy_buffer result; + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes( + nullptr, key_buf, &result)); +} + +// Test get_filter_state_bytes with nullptr result. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateBytesNullResult) { + NiceMock context; + auto* context_ptr = static_cast(&context); + std::string key = "k"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes( + context_ptr, key_buf, nullptr)); +} + +// Test get_filter_state_bytes when the request has no stream info. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateBytesNoStreamInfo) { + NiceMock context; + ON_CALL(context, requestStreamInfo()).WillByDefault(Return(nullptr)); + auto* context_ptr = static_cast(&context); + std::string key = "k"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + envoy_dynamic_module_type_envoy_buffer result; + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes( + context_ptr, key_buf, &result)); +} + +// Test get_filter_state_bytes when the key is not present. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateBytesNotFound) { + NiceMock context; + NiceMock stream_info; + ON_CALL(context, requestStreamInfo()).WillByDefault(Return(&stream_info)); + auto* context_ptr = static_cast(&context); + std::string key = "missing"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + envoy_dynamic_module_type_envoy_buffer result; + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes( + context_ptr, key_buf, &result)); +} + +// Test get_filter_state_bytes happy path: producer stored a StringAccessor; cluster reads it. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateBytesFound) { + NiceMock context; + NiceMock stream_info; + stream_info.filter_state_->setData("k", std::make_unique("v"), + StreamInfo::FilterState::StateType::ReadOnly); + ON_CALL(context, requestStreamInfo()).WillByDefault(Return(&stream_info)); + auto* context_ptr = static_cast(&context); + std::string key = "k"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + envoy_dynamic_module_type_envoy_buffer result; + EXPECT_TRUE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes( + context_ptr, key_buf, &result)); + EXPECT_EQ("v", absl::string_view(result.ptr, result.length)); +} + +// Test get_filter_state_typed with nullptr context. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateTypedNullContext) { + std::string key = "k"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + envoy_dynamic_module_type_envoy_buffer result; + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + nullptr, key_buf, &result)); +} + +// Test get_filter_state_typed with nullptr result. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateTypedNullResult) { + NiceMock context; + auto* context_ptr = static_cast(&context); + std::string key = "k"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + context_ptr, key_buf, nullptr)); +} + +// Test get_filter_state_typed when the request has no stream info. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateTypedNoStreamInfo) { + NiceMock context; + ON_CALL(context, requestStreamInfo()).WillByDefault(Return(nullptr)); + auto* context_ptr = static_cast(&context); + std::string key = "k"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + envoy_dynamic_module_type_envoy_buffer result; + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + context_ptr, key_buf, &result)); +} + +// Test get_filter_state_typed when the key is not present. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateTypedNotFound) { + NiceMock context; + NiceMock stream_info; + ON_CALL(context, requestStreamInfo()).WillByDefault(Return(&stream_info)); + auto* context_ptr = static_cast(&context); + std::string key = "missing"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + envoy_dynamic_module_type_envoy_buffer result; + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + context_ptr, key_buf, &result)); +} + +// Test get_filter_state_typed when the stored object does not support serialization. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateTypedNotSerializable) { + NiceMock context; + NiceMock stream_info; + stream_info.filter_state_->setData("k", std::make_unique(), + StreamInfo::FilterState::StateType::ReadOnly); + ON_CALL(context, requestStreamInfo()).WillByDefault(Return(&stream_info)); + auto* context_ptr = static_cast(&context); + std::string key = "k"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + envoy_dynamic_module_type_envoy_buffer result; + EXPECT_FALSE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + context_ptr, key_buf, &result)); +} + +// Test get_filter_state_typed happy path: producer stored a serializable object; cluster reads it. +TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateTypedFound) { + NiceMock context; + NiceMock stream_info; + stream_info.filter_state_->setData("k", std::make_unique("typed-v"), + StreamInfo::FilterState::StateType::ReadOnly); + ON_CALL(context, requestStreamInfo()).WillByDefault(Return(&stream_info)); + auto* context_ptr = static_cast(&context); + std::string key = "k"; + envoy_dynamic_module_type_module_buffer key_buf = {key.data(), key.size()}; + envoy_dynamic_module_type_envoy_buffer result; + EXPECT_TRUE(envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed( + context_ptr, key_buf, &result)); + EXPECT_EQ("typed-v", absl::string_view(result.ptr, result.length)); +} + // ================================================================================================= // Async Host Selection Tests // ================================================================================================= diff --git a/test/extensions/clusters/dynamic_modules/integration_test.cc b/test/extensions/clusters/dynamic_modules/integration_test.cc index 97388f3842263..610c710dffec7 100644 --- a/test/extensions/clusters/dynamic_modules/integration_test.cc +++ b/test/extensions/clusters/dynamic_modules/integration_test.cc @@ -1,8 +1,11 @@ #include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/extensions/clusters/dynamic_modules/v3/cluster.pb.h" +#include "envoy/registry/registry.h" +#include "envoy/stream_info/filter_state.h" #include "source/common/protobuf/protobuf.h" +#include "source/common/router/string_accessor_impl.h" #include "test/extensions/dynamic_modules/util.h" #include "test/integration/http_integration.h" @@ -12,6 +15,25 @@ namespace Extensions { namespace Clusters { namespace DynamicModules { +namespace { + +// ObjectFactory used by the cluster filter-state read test: the dynamic-module HTTP filter +// writes through this factory via envoy_dynamic_module_callback_http_set_filter_state_typed, +// and the dynamic-module cluster reads it back during host selection via +// envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed. +class ClusterTypedObjectFactory : public StreamInfo::FilterState::ObjectFactory { +public: + std::string name() const override { return "envoy.test.cluster_typed_object"; } + std::unique_ptr + createFromBytes(absl::string_view data) const override { + return std::make_unique(data); + } +}; + +REGISTER_FACTORY(ClusterTypedObjectFactory, StreamInfo::FilterState::ObjectFactory); + +} // namespace + class DynamicModuleClusterIntegrationTest : public testing::TestWithParam, public HttpIntegrationTest { @@ -133,6 +155,84 @@ TEST_P(DynamicModuleClusterIntegrationTest, LifecycleCallbacks) { EXPECT_EQ("200", response->headers().getStatusValue()); } +// ============================================================================= +// Filter-state read ABI: an upstream HTTP filter writes filter state on the +// request path; the dynamic-module cluster reads it back during host selection. +// ============================================================================= +class DynamicModuleClusterFilterStateIntegrationTest + : public testing::TestWithParam, + public HttpIntegrationTest { +public: + DynamicModuleClusterFilterStateIntegrationTest() + : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) {} + + void initializeWithProducerAndReader() { + TestEnvironment::setEnvVar( + "ENVOY_DYNAMIC_MODULES_SEARCH_PATH", + TestEnvironment::runfilesPath("test/extensions/dynamic_modules/test_data/rust"), 1); + + // Prepend the dynamic-module HTTP filter so it writes filter state on every + // request before the router runs. + constexpr absl::string_view producer_filter_config = R"EOF( +name: envoy.extensions.filters.http.dynamic_modules +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_modules.v3.DynamicModuleFilter + dynamic_module_config: + name: cluster_filter_state_test + filter_name: filter_state_producer + filter_config: + "@type": type.googleapis.com/google.protobuf.StringValue + value: "" +)EOF"; + config_helper_.prependFilter(std::string(producer_filter_config)); + + // Replace cluster_0 with a dynamic-module cluster whose Rust load balancer + // reads the filter state we just wrote. + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(0); + const std::string upstream_address = fake_upstreams_[0]->localAddress()->asString(); + + cluster->set_name("cluster_0"); + cluster->set_lb_policy(envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED); + cluster->clear_load_assignment(); + + envoy::extensions::clusters::dynamic_modules::v3::ClusterConfig reader_config; + reader_config.mutable_dynamic_module_config()->set_name("cluster_filter_state_test"); + reader_config.set_cluster_name("filter_state_reader"); + + Protobuf::StringValue config_proto; + config_proto.set_value(upstream_address); + reader_config.mutable_cluster_config()->PackFrom(config_proto); + + cluster->mutable_cluster_type()->set_name("envoy.clusters.dynamic_modules"); + cluster->mutable_cluster_type()->mutable_typed_config()->PackFrom(reader_config); + }); + + HttpIntegrationTest::initialize(); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, DynamicModuleClusterFilterStateIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +// Verifies that filter state written by an upstream HTTP filter on the request +// path is observable in the dynamic-module cluster's choose_host callback via +// both the bytes and the typed filter-state ABI accessors. The cluster returns +// its only host only when both values match the producer's payload, so a 200 +// response proves the round trip. +TEST_P(DynamicModuleClusterFilterStateIntegrationTest, ReadsFilterStateProducedByHttpFilter) { + initializeWithProducerAndReader(); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + + auto response = + sendRequestAndWaitForResponse(default_request_headers_, 0, default_response_headers_, 0); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); +} + } // namespace DynamicModules } // namespace Clusters } // namespace Extensions diff --git a/test/extensions/dynamic_modules/test_data/rust/BUILD b/test/extensions/dynamic_modules/test_data/rust/BUILD index 9185767f5fcef..dd86138a01889 100644 --- a/test/extensions/dynamic_modules/test_data/rust/BUILD +++ b/test/extensions/dynamic_modules/test_data/rust/BUILD @@ -59,3 +59,5 @@ test_program(name = "access_log_integration_test") test_program(name = "upstream_http_tcp_bridge") test_program(name = "cluster_integration_test") + +test_program(name = "cluster_filter_state_test") diff --git a/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs b/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs new file mode 100644 index 0000000000000..a344bc1566ee7 --- /dev/null +++ b/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs @@ -0,0 +1,158 @@ +//! Integration test for the cluster filter-state read ABI. +//! +//! Exposes both an HTTP filter and a cluster from the same shared library: +//! - The HTTP filter writes two filter state entries (bytes + typed) during ``on_request_headers``. +//! - The cluster reads those entries in ``choose_host`` and returns its pre-registered upstream +//! only when both values match the expected payload, otherwise it returns ``NoHost`` so the +//! request fails with a 503. +//! +//! The C++ test harness registers an ``ObjectFactory`` under +//! ``envoy.test.cluster_typed_object`` so the typed write/read path is exercised. + +use envoy_proxy_dynamic_modules_rust_sdk::*; +use std::sync::{Arc, Mutex}; + +const BYTES_KEY: &[u8] = b"test.cluster_filter_state.bytes_key"; +const BYTES_VALUE: &[u8] = b"bytes_value"; +const TYPED_KEY: &[u8] = b"envoy.test.cluster_typed_object"; +const TYPED_VALUE: &[u8] = b"typed_value"; + +declare_all_init_functions!( + my_program_init, + http: new_http_filter_config_fn, + cluster: new_cluster_config_fn, +); + +fn my_program_init() -> bool { + true +} + +// ------------------------------------------------------------------------------------- +// HTTP filter that produces filter state. +// ------------------------------------------------------------------------------------- + +fn new_http_filter_config_fn( + _envoy_filter_config: &mut EC, + name: &str, + _config: &[u8], +) -> Option>> { + match name { + "filter_state_producer" => Some(Box::new(FilterStateProducerConfig {})), + _ => None, + } +} + +struct FilterStateProducerConfig {} + +impl HttpFilterConfig for FilterStateProducerConfig { + fn new_http_filter(&self, _envoy: &mut EHF) -> Box> { + Box::new(FilterStateProducerFilter {}) + } +} + +struct FilterStateProducerFilter {} + +impl HttpFilter for FilterStateProducerFilter { + fn on_request_headers( + &mut self, + envoy_filter: &mut EHF, + _end_of_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + assert!(envoy_filter.set_filter_state_bytes(BYTES_KEY, BYTES_VALUE)); + assert!(envoy_filter.set_filter_state_typed(TYPED_KEY, TYPED_VALUE)); + abi::envoy_dynamic_module_type_on_http_filter_request_headers_status::Continue + } +} + +// ------------------------------------------------------------------------------------- +// Cluster that reads filter state during host selection. +// ------------------------------------------------------------------------------------- + +struct HostList(Vec); +// SAFETY: Host pointers are stable addresses managed by Envoy across threads. +unsafe impl Send for HostList {} +unsafe impl Sync for HostList {} + +type SharedHostList = Arc>; + +fn new_cluster_config_fn( + name: &str, + config: &[u8], + _envoy_cluster_metrics: Arc, +) -> Option> { + let upstream_address = std::str::from_utf8(config).unwrap_or("").to_string(); + match name { + "filter_state_reader" => Some(Box::new(FilterStateReaderClusterConfig { + upstream_address, + })), + _ => None, + } +} + +struct FilterStateReaderClusterConfig { + upstream_address: String, +} + +impl ClusterConfig for FilterStateReaderClusterConfig { + fn new_cluster(&self, _envoy_cluster: &dyn EnvoyCluster) -> Box { + Box::new(FilterStateReaderCluster { + upstream_address: self.upstream_address.clone(), + hosts: Arc::new(Mutex::new(HostList(Vec::new()))), + }) + } +} + +struct FilterStateReaderCluster { + upstream_address: String, + hosts: SharedHostList, +} + +impl Cluster for FilterStateReaderCluster { + fn on_init(&mut self, envoy_cluster: &dyn EnvoyCluster) { + let addresses = vec![self.upstream_address.clone()]; + let weights = vec![1u32]; + if let Some(host_ptrs) = envoy_cluster.add_hosts(&addresses, &weights) { + self.hosts.lock().unwrap().0 = host_ptrs; + } + envoy_cluster.pre_init_complete(); + } + + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { + Box::new(FilterStateReaderLb { + hosts: self.hosts.clone(), + }) + } +} + +struct FilterStateReaderLb { + hosts: SharedHostList, +} + +impl ClusterLb for FilterStateReaderLb { + fn choose_host( + &mut self, + context: Option<&dyn ClusterLbContext>, + _async_completion: Box, + ) -> HostSelectionResult { + let Some(ctx) = context else { + return HostSelectionResult::NoHost; + }; + let bytes = ctx.get_filter_state_bytes(BYTES_KEY); + let typed = ctx.get_filter_state_typed(TYPED_KEY); + let bytes_match = bytes.as_ref().map(|b| b.as_slice()) == Some(BYTES_VALUE); + let typed_match = typed.as_ref().map(|b| b.as_slice()) == Some(TYPED_VALUE); + if !bytes_match || !typed_match { + envoy_log_info!( + "filter_state_reader: mismatch — bytes_match={} typed_match={}", + bytes_match, + typed_match + ); + return HostSelectionResult::NoHost; + } + let hosts = self.hosts.lock().unwrap(); + if hosts.0.is_empty() { + return HostSelectionResult::NoHost; + } + HostSelectionResult::Selected(hosts.0[0]) + } +} From 410e0890a8839b86dc1ad0578840cde1ecd2c3b1 Mon Sep 17 00:00:00 2001 From: Guilherme Silva <26658340+GuilhermeJSilva@users.noreply.github.com> Date: Fri, 15 May 2026 09:13:06 +0100 Subject: [PATCH 320/750] tcp_proxy: add the ability to delay route selection (#44109) Commit Message: tcp_proxy: add the ability to delay route selection Additional Description: Before this commit route selection is always performed on new connection. Even when the connect mode is set to ON_DOWNSTREAM_DATA or ON_DOWNSTREAM_TLS_HANDSHAKE. This adds a runtime guard to delay the route selection to just before the connection is established. This allows the selection of a cluster (via filter state) by other filters when data is received from downstream or when the downstream TLS handshake terminates. Risk Level: Low, feature is behind a runtime guard Testing: Added tests Docs Changes: Documented the runtime guard in the tcp_proxy page Release Notes: Platform Specific Features: Runtime guard: `envoy.reloadable_features.tcp_proxy_delay_route_selection` Fixes #44088 --------- Signed-off-by: Guilherme Silva <26658340+GuilhermeJSilva@users.noreply.github.com> Co-authored-by: Guilherme Silva --- changelogs/current.yaml | 6 + .../network_filters/tcp_proxy_filter.rst | 18 +++ source/common/runtime/runtime_features.cc | 3 + source/common/tcp_proxy/tcp_proxy.cc | 27 ++++- source/common/tcp_proxy/tcp_proxy.h | 2 + .../filters/network/set_filter_state/BUILD | 3 + test/common/tcp_proxy/tcp_proxy_test.cc | 108 +++++++++++++++++- test/common/tcp_proxy/tcp_proxy_test_base.h | 19 ++- test/integration/BUILD | 1 + .../integration/tcp_proxy_integration_test.cc | 59 ++++++++++ 10 files changed, 236 insertions(+), 10 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 13c2ea9c0a7ff..204a97d93ba4d 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -225,4 +225,10 @@ new_features: reverted by setting the runtime guard ``envoy.reloadable_features.http_inspector_use_balsa_parser`` to ``false``. This runtime guard will be removed in a future release of Envoy. +- area: tcp_proxy + change: | + Added ``envoy.reloadable_features.tcp_proxy_delay_route_selection`` to delay selecting a route until just before the upstream + connection is established. The selection moment depends on the value of + :ref:`upstream_connect_mode`. + deprecated: diff --git a/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst index 662f324bb06b2..84142589cd3f0 100644 --- a/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst +++ b/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst @@ -104,6 +104,23 @@ Example configuration: The ``ON_DOWNSTREAM_DATA`` mode is not suitable for server-first protocols where the server sends the initial greeting (e.g., SMTP, MySQL, POP3). For such protocols, use ``IMMEDIATE`` mode. +Delay Route Selection +^^^^^^^^^^^^^^^^^^^^^ + +The ``envoy.reloadable_features.tcp_proxy_delay_route_selection`` runtime guard controls whether we delay the +route selection until the filter needs to establish the upstream connection. When unset, the filter will select a +route on new connection. + +The moment the selection occurs depends on the connection mode: + +* ``IMMEDIATE`` with route selection delay: It has no effect. Route selection still happens on a new connection +* ``ON_DOWNSTREAM_DATA`` with route selection delay: Route selection will happen when the filter first receives + data from downstream +* ``ON_DOWNSTREAM_TLS_HANDSHAKE`` with route selection delay: Route selection will happen when the downstream TLS + handshake completes + +This will also delay updates to any statistics that are dependent on route selection (e.g. ``downstream_cx_no_route``). + Filter state configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -226,3 +243,4 @@ The downstream statistics are rooted at *tcp..* with the following on_demand_cluster_timeout, Counter, Total number of connections closed due to on demand cluster lookup timeout upstream_flush_total, Counter, Total number of connections that continued to flush upstream data after the downstream connection was closed upstream_flush_active, Gauge, Total connections currently continuing to flush upstream data after the downstream connection was closed + route_delayed_total, Counter, Total number of route selections that were delayed until the filter needs to establish the downstream connection diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 3f2b0086a7a1d..4b745f5aee55a 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -236,6 +236,9 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_disable_data_read_immediately // TODO(yavlasov): Flip to true after prod testing. FALSE_RUNTIME_GUARD(envoy_reloadable_features_strict_chunk_parsing); +// Delay route selection in tcp_proxy until just before the upstream connection is established +FALSE_RUNTIME_GUARD(envoy_reloadable_features_tcp_proxy_delay_route_selection); + // Block of non-boolean flags. Use of int flags is deprecated. Do not add more. ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT ABSL_FLAG(uint64_t, re2_max_program_size_warn_level, // NOLINT diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index ca54cf5245c63..7f2c4f5f91d6d 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -441,6 +441,11 @@ void Filter::initialize(Network::ReadFilterCallbacks& callbacks, bool set_connec // Initialize connection establishment mode. connect_mode_ = config_->upstreamConnectMode(); + // Delay route selection needs to set on initialization since mid execution changes + // could lead to inconsistent behavior + delay_route_selection_ = + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.tcp_proxy_delay_route_selection"); + // Check if early data buffering is enabled. if (config_->maxEarlyDataBytes().has_value()) { receive_before_connect_ = true; @@ -1092,7 +1097,10 @@ Network::FilterStatus Filter::onData(Buffer::Instance& data, bool end_stream) { "Initial data received, establishing upstream connection. " "early_data_buffer_.length()={}", read_callbacks_->connection(), early_data_buffer_.length()); - // Route should already be set in onNewConnection(). + if (delay_route_selection_) { + route_ = pickRoute(); + } + // If delay_route_selection_ is unset, route should already be set in onNewConnection(). ASSERT(route_ != nullptr); establishUpstreamConnection(); } @@ -1173,9 +1181,15 @@ Network::FilterStatus Filter::onNewConnection() { return establishUpstreamConnection(); } - // For ON_DOWNSTREAM_DATA or ON_DOWNSTREAM_TLS_HANDSHAKE modes, delay the connection. - // Pre-pick the route so it's available when connection is triggered. - route_ = pickRoute(); + if (!delay_route_selection_) { + // For ON_DOWNSTREAM_DATA or ON_DOWNSTREAM_TLS_HANDSHAKE modes, delay the connection. + // Pre-pick the route so it's available when connection is triggered. + route_ = pickRoute(); + } else { + config_->stats().route_delayed_total_.inc(); + ENVOY_CONN_LOG(debug, "Delaying picking route until upstream connection establishement", + read_callbacks_->connection()); + } // Log the specific delay reason. if (connect_mode_ == UpstreamConnectMode::ON_DOWNSTREAM_DATA) { @@ -1505,7 +1519,10 @@ void Filter::onDownstreamTlsHandshakeComplete() { // For ON_DOWNSTREAM_TLS_HANDSHAKE mode, establish the upstream connection now. if (connect_mode_ == UpstreamConnectMode::ON_DOWNSTREAM_TLS_HANDSHAKE) { - // Route should already be set in onNewConnection(). + if (delay_route_selection_) { + route_ = pickRoute(); + } + // If delay_route_selection_ is unset, route should already be set in onNewConnection(). ASSERT(route_ != nullptr); establishUpstreamConnection(); } diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index f9e6b08b120a9..e5eec1c2a495c 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -70,6 +70,7 @@ constexpr absl::string_view ReceiveBeforeConnectKey = "envoy.tcp_proxy.receive_b COUNTER(idle_timeout) \ COUNTER(max_downstream_connection_duration) \ COUNTER(upstream_flush_total) \ + COUNTER(route_delayed_total) \ GAUGE(downstream_cx_rx_bytes_buffered, Accumulate) \ GAUGE(downstream_cx_tx_bytes_buffered, Accumulate) \ GAUGE(upstream_flush_active, Accumulate) @@ -768,6 +769,7 @@ class Filter : public Network::ReadFilter, bool initial_data_received_{false}; bool read_disabled_due_to_buffer_{false}; // Track if we disabled reading due to buffer overflow. uint32_t max_buffered_bytes_{65536}; // Default 64KB. + bool delay_route_selection_{false}; }; // This class deals with an upstream connection that needs to finish flushing, when the downstream diff --git a/source/extensions/filters/network/set_filter_state/BUILD b/source/extensions/filters/network/set_filter_state/BUILD index 90b9ea589e5b1..d5b0caff6a556 100644 --- a/source/extensions/filters/network/set_filter_state/BUILD +++ b/source/extensions/filters/network/set_filter_state/BUILD @@ -12,6 +12,9 @@ envoy_cc_extension( name = "config", srcs = ["config.cc"], hdrs = ["config.h"], + extra_visibility = [ + "//test/integration:__subpackages__", + ], deps = [ "//envoy/formatter:substitution_formatter_interface", "//envoy/registry", diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 2ba8c8047e648..4aaccc5fdd48b 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -2813,7 +2813,8 @@ TEST_P(TcpProxyTest, LargeBufferScenario) { EXPECT_FALSE(conn_pool_callbacks_.empty()); } -INSTANTIATE_TEST_SUITE_P(WithOrWithoutUpstream, TcpProxyTest, ::testing::Bool()); +INSTANTIATE_TEST_SUITE_P(WithOrWithoutUpstream, TcpProxyTest, + ::testing::ValuesIn(TcpProxyTestBase::getRuntimeFlagsForTest())); TEST(PerConnectionCluster, ObjectFactory) { const std::string name = "envoy.tcp_proxy.cluster"; @@ -3069,6 +3070,56 @@ TEST_P(TcpProxyTlsHandshakeTest, TlsHandshakeMode_WithTlsConnection_WaitsForHand EXPECT_FALSE(conn_pool_callbacks_.empty()); } +TEST_P(TcpProxyTlsHandshakeTest, + TlsHandshakeMode_WithTlsConnectionAndDelayedSelection_SelectsOnHandshake) { + // Setup SSL connection before initializing the filter. + auto ssl_connection = std::make_shared>(); + EXPECT_CALL(filter_callbacks_.connection_, ssl()).WillRepeatedly(Return(ssl_connection)); + setupTlsMode(); + + if (scoped_runtime_.loader().snapshot().runtimeFeatureEnabled( + "envoy.reloadable_features.tcp_proxy_delay_route_selection")) { + EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_, + getThreadLocalCluster("state_fake_cluster")) + .WillOnce(Return( + &factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_)); + } else { + EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_, + getThreadLocalCluster("fake_cluster")) + .WillOnce(Return( + &factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_)); + } + + // Call onNewConnection() to initialize the filter. + // With max_early_data_bytes: 0, receive_before_connect=true, so it returns Continue. + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Simulate setting the filter state after the onNewConnection + filter_callbacks_.connection_.stream_info_.filter_state_->setData( + Envoy::TcpProxy::PerConnectionCluster::key(), + std::make_shared("state_fake_cluster"), + StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + + // Set up connection pool expectations for when TLS handshake completes. + EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_, + tcpConnPool(_, _, _)) + .WillOnce(Return(Upstream::TcpPoolData([]() {}, &conn_pool_))); + EXPECT_CALL(conn_pool_, newConnection(_)) + .WillOnce( + Invoke([&](Tcp::ConnectionPool::Callbacks& cb) -> Tcp::ConnectionPool::Cancellable* { + conn_pool_callbacks_.push_back(&cb); + return conn_pool_handles_ + .emplace_back(std::make_unique>()) + .get(); + })); + + // Simulate TLS handshake completion. + filter_->onDownstreamTlsHandshakeComplete(); + + // Verify connection establishment was triggered. + EXPECT_FALSE(conn_pool_callbacks_.empty()); +} + TEST_P(TcpProxyTlsHandshakeTest, TlsHandshakeMode_WithNonTlsConnection_ImmediateConnect) { // No SSL connection. EXPECT_CALL(filter_callbacks_.connection_, ssl()).WillRepeatedly(Return(nullptr)); @@ -3176,7 +3227,7 @@ TEST_P(TcpProxyTlsHandshakeTest, TlsHandshakeViaConnectedEvent) { // Instantiate parameterized tests with both values of the runtime feature flag. INSTANTIATE_TEST_SUITE_P(TcpProxyTlsHandshakeTestParams, TcpProxyTlsHandshakeTest, - testing::Values(false, true)); + ::testing::ValuesIn(TcpProxyTestBase::getRuntimeFlagsForTest())); // Test that IMMEDIATE mode can be combined with max_early_data_bytes. TEST(TcpProxyConfigTest, OrthogonalityImmediateModeWithEarlyData) { @@ -3323,6 +3374,59 @@ TEST_P(TcpProxyTest, MultipleTinyChunksSetInitialDataReceivedOnce) { EXPECT_EQ(conn_pool_callbacks_.size(), 1); // Still only one connection attempt. } +// Test that delay route selection picks the correct cluster when filter state is set +TEST_P(TcpProxyTest, DelayRouteSelectionAllowsFilterStateChanges) { + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); + config.set_upstream_connect_mode( + envoy::extensions::filters::network::tcp_proxy::v3::ON_DOWNSTREAM_DATA); + config.mutable_max_early_data_bytes()->set_value(1024); + + if (scoped_runtime_.loader().snapshot().runtimeFeatureEnabled( + "envoy.reloadable_features.tcp_proxy_delay_route_selection")) { + EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_, + getThreadLocalCluster("state_fake_cluster")) + .WillOnce(Return( + &factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_)); + } else { + EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_, + getThreadLocalCluster("fake_cluster")) + .WillOnce(Return( + &factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_)); + } + + // Use setupOnDownstreamDataMode to avoid conflicting expectations from base setup. + setupOnDownstreamDataMode(config, false /* receive_before_connect */); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onNewConnection()); + + // Simulate setting the filter state after the onNewConnection + filter_callbacks_.connection_.stream_info_.filter_state_->setData( + Envoy::TcpProxy::PerConnectionCluster::key(), + std::make_shared("state_fake_cluster"), + StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + + EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_, + tcpConnPool(_, _, _)) + .WillOnce(Return(Upstream::TcpPoolData([]() {}, &conn_pool_))); + EXPECT_CALL(conn_pool_, newConnection(_)) + .WillOnce( + Invoke([&](Tcp::ConnectionPool::Callbacks& cb) -> Tcp::ConnectionPool::Cancellable* { + conn_pool_callbacks_.push_back(&cb); + return conn_pool_handles_ + .emplace_back(std::make_unique>()) + .get(); + })); + + // First tiny chunk. It should trigger connection but NOT readDisable. + Buffer::OwnedImpl chunk1("h"); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(chunk1, false)); + EXPECT_EQ(conn_pool_callbacks_.size(), 1); + + // Second tiny chunk. It should NOT trigger connection again. + Buffer::OwnedImpl chunk2("e"); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(chunk2, false)); + EXPECT_EQ(conn_pool_callbacks_.size(), 1); // Still only one connection attempt. +} + // Test that data with max_buffered_bytes=0 triggers connection and readDisable. TEST_P(TcpProxyTest, ZeroBufferTriggersReadDisable) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); diff --git a/test/common/tcp_proxy/tcp_proxy_test_base.h b/test/common/tcp_proxy/tcp_proxy_test_base.h index a4a2d40bec406..90196b22d7f76 100644 --- a/test/common/tcp_proxy/tcp_proxy_test_base.h +++ b/test/common/tcp_proxy/tcp_proxy_test_base.h @@ -54,11 +54,11 @@ inline Config constructConfigFromYaml(const std::string& yaml, return {tcp_proxy, context}; } -class TcpProxyTestBase : public testing::TestWithParam { +class TcpProxyTestBase + : public testing::TestWithParam> { public: TcpProxyTestBase() { - scoped_runtime_.mergeValues({{"envoy.restart_features.upstream_http_filters_with_tcp_proxy", - GetParam() ? "true" : "false"}}); + scoped_runtime_.mergeValues(GetParam()); ON_CALL(*factory_context_.server_factory_context_.access_log_manager_.file_, write(_)) .WillByDefault(SaveArg<0>(&access_log_data_)); ON_CALL(filter_callbacks_.connection_.stream_info_, setUpstreamClusterInfo(_)) @@ -79,6 +79,19 @@ class TcpProxyTestBase : public testing::TestWithParam { } } + static std::vector> getRuntimeFlagsForTest() { + std::vector> parameters; + for (auto upstream_http_filters_value : {false, true}) { + for (auto delay_route_selection_value : {false, true}) { + parameters.push_back({{"envoy.restart_features.upstream_http_filters_with_tcp_proxy", + upstream_http_filters_value ? "true" : "false"}, + {"envoy.reloadable_features.tcp_proxy_delay_route_selection", + delay_route_selection_value ? "true" : "false"}}); + } + } + return parameters; + } + void configure(const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy& config) { config_ = std::make_shared(config, factory_context_); } diff --git a/test/integration/BUILD b/test/integration/BUILD index 4d090d4b32eeb..9770089022f10 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -2085,6 +2085,7 @@ envoy_cc_test( "//source/common/tls:context_lib", "//source/extensions/access_loggers/file:config", "//source/extensions/filters/network/common:factory_base_lib", + "//source/extensions/filters/network/set_filter_state:config", "//source/extensions/filters/network/tcp_proxy:config", "//source/extensions/load_balancing_policies/subset:config", "//source/extensions/transport_sockets/tls:config", diff --git a/test/integration/tcp_proxy_integration_test.cc b/test/integration/tcp_proxy_integration_test.cc index a18d060ad8d89..c6ee0d3da971b 100644 --- a/test/integration/tcp_proxy_integration_test.cc +++ b/test/integration/tcp_proxy_integration_test.cc @@ -3121,4 +3121,63 @@ TEST_P(TcpProxyIntegrationTest, DownstreamClosedWithEndStreamNoData) { tcp_client->close(); } +// Test route delayed route selection based on filter state +TEST_P(TcpProxyIntegrationTest, DelayRouteSelectionWithSetFilterStateOnData) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.tcp_proxy_delay_route_selection", + "true"); + fake_upstreams_count_ = 2; + config_helper_.addNetworkFilter(R"EOF( +name: envoy.filters.network.set_filter_state +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.set_filter_state.v3.Config + on_downstream_data: + - object_key: envoy.tcp_proxy.cluster + format_string: + text_format_source: + inline_string: "cluster_1" +)EOF"); + + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + // Add cluster_1 configuration (cluster_0 already exists by default) + auto* cluster = bootstrap.mutable_static_resources()->add_clusters(); + cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + cluster->set_name("cluster_1"); + + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + auto* filter_chain = listener->mutable_filter_chains(0); + auto* filter = filter_chain->mutable_filters(1); + + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy tcp_proxy; + filter->typed_config().UnpackTo(&tcp_proxy); + + tcp_proxy.set_upstream_connect_mode( + envoy::extensions::filters::network::tcp_proxy::v3::ON_DOWNSTREAM_DATA); + tcp_proxy.mutable_max_early_data_bytes()->set_value(1024); + + filter->mutable_typed_config()->PackFrom(tcp_proxy); + }); + + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + + // Wait to ensure no connection is established without data. + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_FALSE(fake_upstreams_[1]->waitForRawConnection(fake_upstream_connection, + std::chrono::milliseconds(1000))); + + // Now send actual data. + ASSERT_TRUE(tcp_client->write("data")); + + // Connection should now be established. + ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->waitForData(4)); + + tcp_client->close(); + + EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.upstream_cx_total")->value()); + EXPECT_EQ(1, test_server_->counter("tcp.tcpproxy_stats.route_delayed_total")->value()); + EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.upstream_cx_total")->value()); +} + } // namespace Envoy From 892206bc69f7f355e8748a2938b8592c37e36103 Mon Sep 17 00:00:00 2001 From: Rudrakh Panigrahi Date: Fri, 15 May 2026 13:52:15 +0530 Subject: [PATCH 321/750] perf: populate route match context once across entries (#44663) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit Message: `perf: populate route match context once across entries` Additional Description: In case of multiple route entries these values are evaluated per route today which can be done only once to save compute. Risk Level: Low Testing: Existing tests + unit tests Docs Changes: N/A Release Notes: No Platform Specific Features: N/A Fixes #20609 ## Benchmarks Two benchmarks added to `test/common/router/config_impl_speed_test.cc`, each run against `main` and this branch (`-c opt`, `--benchmark_min_time=3s`). **`bmPlainRoutes`** — N prefix-only routes, no query/cookie matching. Request matches the last route. Measures overhead of `RouteMatchContext` when cached values are never accessed. **`bmMixedRoutes`** — N routes where the first half share a common prefix with a non-matching query parameter, and the last route is a plain prefix match. Exercises the query-string caching benefit. ### Results #### bmPlainRoutes (ns/iter) | N | main | this PR | speedup | |-------|--------|---------|---------| | 64 | 1,141 | 961 | 1.19x | | 128 | 2,340 | 1,941 | 1.21x | | 256 | 4,613 | 3,883 | 1.19x | | 512 | 9,184 | 7,705 | 1.19x | | 1,024 | 18,647 | 15,811 | 1.18x | | 2,048 | 37,336 | 32,002 | 1.17x | #### bmMixedRoutes (ns/iter) | N | main | this PR | speedup | |-------|--------|---------|---------| | 64 | 3,112 | 1,421 | 2.19x | | 128 | 6,173 | 2,786 | 2.21x | | 256 | 12,109 | 5,558 | 2.18x | | 512 | 24,656 | 10,916 | 2.26x | | 1,024 | 48,172 | 21,793 | 2.21x | | 2,048 | 95,990 | 43,077 | 2.23x | No regression on plain routes (~1.2x improvement from single `sanitizePathBeforePathMatching()` instead of per-route). Consistent **~2.2x speedup** on mixed routes where query-string parsing previously ran once per route. --------- Signed-off-by: Rudrakh Panigrahi --- source/common/router/BUILD | 1 + source/common/router/config_impl.cc | 88 +++++++------- source/common/router/config_impl.h | 118 ++++++++++++++++--- test/common/router/config_impl_speed_test.cc | 80 +++++++++++++ test/common/router/config_impl_test.cc | 118 +++++++++++++++++++ 5 files changed, 345 insertions(+), 60 deletions(-) diff --git a/source/common/router/BUILD b/source/common/router/BUILD index 865d159df0d48..faf2ce7b11f91 100644 --- a/source/common/router/BUILD +++ b/source/common/router/BUILD @@ -66,6 +66,7 @@ envoy_cc_library( "//source/common/config:utility_lib", "//source/common/config:well_known_names", "//source/common/formatter:substitution_format_string_lib", + "//source/common/grpc:common_lib", "//source/common/http:hash_policy_lib", "//source/common/http:header_mutation_lib", "//source/common/http:header_utility_lib", diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 6cd9f27b6d767..167cdca0f079c 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -646,12 +646,6 @@ RouteEntryImplBase::RouteEntryImplBase(const CommonVirtualHostSharedPtr& vhost, config_cookies_.push_back( std::make_unique(cookie_matcher, factory_context)); } - if (!config_cookies_.empty()) { - config_cookie_names_.reserve(config_cookies_.size()); - for (const auto& matcher : config_cookies_) { - config_cookie_names_.insert(matcher->name()); - } - } if (!route.route().hash_policy().empty()) { hash_policy_ = THROW_OR_RETURN_VALUE( @@ -852,7 +846,7 @@ bool RouteEntryImplBase::isRedirect() const { redirect_config_->regex_rewrite_redirect_ != nullptr; } -bool RouteEntryImplBase::matchRoute(const Http::RequestHeaderMap& headers, +bool RouteEntryImplBase::matchRoute(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const { bool matches = true; @@ -864,31 +858,26 @@ bool RouteEntryImplBase::matchRoute(const Http::RequestHeaderMap& headers, } if (match_grpc_) { - matches &= Grpc::Common::isGrpcRequestHeaders(headers); - if (!matches) { + if (!route_match_context.isGrpc()) { return false; } } + const Http::RequestHeaderMap& headers = route_match_context.headers(); matches &= Http::HeaderUtility::matchHeaders(headers, config_headers_); if (!matches) { return false; } if (!config_query_parameters_.empty()) { - auto query_parameters = - Http::Utility::QueryParamsMulti::parseQueryString(headers.getPathValue()); - matches &= ConfigUtility::matchQueryParams(query_parameters, config_query_parameters_); + matches &= ConfigUtility::matchQueryParams(route_match_context.queryParams(), + config_query_parameters_); if (!matches) { return false; } } if (!config_cookies_.empty()) { - const auto cookies = - Http::Utility::parseCookies(headers, [this](absl::string_view key) -> bool { - return config_cookie_names_.find(key) != config_cookie_names_.end(); - }); - if (!ConfigUtility::matchCookies(cookies, config_cookies_)) { + if (!ConfigUtility::matchCookies(route_match_context.cookies(), config_cookies_)) { return false; } } @@ -1401,12 +1390,12 @@ std::string UriTemplateMatcherRouteEntryImpl::currentUrlPathAfterRewrite( } RouteConstSharedPtr -UriTemplateMatcherRouteEntryImpl::matches(const Http::RequestHeaderMap& headers, +UriTemplateMatcherRouteEntryImpl::matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const { - if (RouteEntryImplBase::matchRoute(headers, stream_info, random_value) && - path_matcher_->match(headers.getPathValue())) { - return clusterEntry(headers, stream_info, random_value); + if (RouteEntryImplBase::matchRoute(route_match_context, stream_info, random_value) && + path_matcher_->match(route_match_context.path())) { + return clusterEntry(route_match_context.headers(), stream_info, random_value); } return nullptr; } @@ -1434,12 +1423,12 @@ PrefixRouteEntryImpl::currentUrlPathAfterRewrite(const Http::RequestHeaderMap& h return currentUrlPathAfterRewriteWithMatchedPath(headers, context, stream_info, matcher()); } -RouteConstSharedPtr PrefixRouteEntryImpl::matches(const Http::RequestHeaderMap& headers, +RouteConstSharedPtr PrefixRouteEntryImpl::matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const { - if (RouteEntryImplBase::matchRoute(headers, stream_info, random_value) && - path_matcher_->match(sanitizePathBeforePathMatching(headers.getPathValue()))) { - return clusterEntry(headers, stream_info, random_value); + if (RouteEntryImplBase::matchRoute(route_match_context, stream_info, random_value) && + path_matcher_->match(route_match_context.sanitizedPath())) { + return clusterEntry(route_match_context.headers(), stream_info, random_value); } return nullptr; } @@ -1468,12 +1457,12 @@ PathRouteEntryImpl::currentUrlPathAfterRewrite(const Http::RequestHeaderMap& hea return currentUrlPathAfterRewriteWithMatchedPath(headers, context, stream_info, matcher()); } -RouteConstSharedPtr PathRouteEntryImpl::matches(const Http::RequestHeaderMap& headers, +RouteConstSharedPtr PathRouteEntryImpl::matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const { - if (RouteEntryImplBase::matchRoute(headers, stream_info, random_value) && - path_matcher_->match(sanitizePathBeforePathMatching(headers.getPathValue()))) { - return clusterEntry(headers, stream_info, random_value); + if (RouteEntryImplBase::matchRoute(route_match_context, stream_info, random_value) && + path_matcher_->match(route_match_context.sanitizedPath())) { + return clusterEntry(route_match_context.headers(), stream_info, random_value); } return nullptr; @@ -1509,12 +1498,12 @@ RegexRouteEntryImpl::currentUrlPathAfterRewrite(const Http::RequestHeaderMap& he return currentUrlPathAfterRewriteWithMatchedPath(headers, context, stream_info, path); } -RouteConstSharedPtr RegexRouteEntryImpl::matches(const Http::RequestHeaderMap& headers, +RouteConstSharedPtr RegexRouteEntryImpl::matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const { - if (RouteEntryImplBase::matchRoute(headers, stream_info, random_value)) { - if (path_matcher_->match(sanitizePathBeforePathMatching(headers.getPathValue()))) { - return clusterEntry(headers, stream_info, random_value); + if (RouteEntryImplBase::matchRoute(route_match_context, stream_info, random_value)) { + if (path_matcher_->match(route_match_context.sanitizedPath())) { + return clusterEntry(route_match_context.headers(), stream_info, random_value); } } return nullptr; @@ -1540,12 +1529,13 @@ ConnectRouteEntryImpl::currentUrlPathAfterRewrite(const Http::RequestHeaderMap& return currentUrlPathAfterRewriteWithMatchedPath(headers, context, stream_info, path); } -RouteConstSharedPtr ConnectRouteEntryImpl::matches(const Http::RequestHeaderMap& headers, +RouteConstSharedPtr ConnectRouteEntryImpl::matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const { + const Http::RequestHeaderMap& headers = route_match_context.headers(); if ((Http::HeaderUtility::isConnect(headers) || Http::HeaderUtility::isConnectUdpRequest(headers)) && - RouteEntryImplBase::matchRoute(headers, stream_info, random_value)) { + RouteEntryImplBase::matchRoute(route_match_context, stream_info, random_value)) { return clusterEntry(headers, stream_info, random_value); } return nullptr; @@ -1574,19 +1564,18 @@ std::string PathSeparatedPrefixRouteEntryImpl::currentUrlPathAfterRewrite( } RouteConstSharedPtr -PathSeparatedPrefixRouteEntryImpl::matches(const Http::RequestHeaderMap& headers, +PathSeparatedPrefixRouteEntryImpl::matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const { - if (!RouteEntryImplBase::matchRoute(headers, stream_info, random_value)) { + if (!RouteEntryImplBase::matchRoute(route_match_context, stream_info, random_value)) { return nullptr; } - absl::string_view sanitized_path = sanitizePathBeforePathMatching( - Http::PathUtil::removeQueryAndFragment(headers.getPathValue())); + const absl::string_view sanitized_path = route_match_context.sanitizedPathWithoutQuery(); const size_t sanitized_size = sanitized_path.size(); const size_t matcher_size = matcher().size(); if (sanitized_size >= matcher_size && path_matcher_->match(sanitized_path) && (sanitized_size == matcher_size || sanitized_path[matcher_size] == '/')) { - return clusterEntry(headers, stream_info, random_value); + return clusterEntry(route_match_context.headers(), stream_info, random_value); } return nullptr; } @@ -1808,15 +1797,16 @@ VirtualHostImpl::VirtualHostImpl(const envoy::config::route::v3::VirtualHost& vi } RouteConstSharedPtr VirtualHostImpl::getRouteFromRoutes( - const RouteCallback& cb, const Http::RequestHeaderMap& headers, + const RouteCallback& cb, const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value, absl::Span routes) const { for (auto route = routes.begin(); route != routes.end(); ++route) { - if (!headers.Path() && !(*route)->supportsPathlessHeaders()) { + if (!route_match_context.headers().Path() && !(*route)->supportsPathlessHeaders()) { continue; } - RouteConstSharedPtr route_entry = (*route)->matches(headers, stream_info, random_value); + RouteConstSharedPtr route_entry = + (*route)->matches(route_match_context, stream_info, random_value); if (route_entry == nullptr) { continue; } @@ -1867,6 +1857,11 @@ RouteConstSharedPtr VirtualHostImpl::getRouteFromEntries(const RouteCallback& cb return ssl_redirect_route_; } + // Constructed once per request; derived values (query params, cookies, etc.) are computed + // lazily on first access and reused across all route entries evaluated for this request. + const RouteMatchContext route_match_context( + headers, shared_virtual_host_->globalRouteConfig().ignorePathParametersInPathMatching()); + if (matcher_) { Http::Matching::HttpMatchingDataImpl data(stream_info); data.onRequestHeaders(headers); @@ -1878,11 +1873,12 @@ RouteConstSharedPtr VirtualHostImpl::getRouteFromEntries(const RouteCallback& cb const auto result = match_result.actionByMove(); if (result->typeUrl() == RouteMatchAction::staticTypeUrl()) { return getRouteFromRoutes( - cb, headers, stream_info, random_value, + cb, route_match_context, stream_info, random_value, {std::dynamic_pointer_cast(std::move(result))}); } else if (result->typeUrl() == RouteListMatchAction::staticTypeUrl()) { const RouteListMatchAction& action = result->getTyped(); - return getRouteFromRoutes(cb, headers, stream_info, random_value, action.routes()); + return getRouteFromRoutes(cb, route_match_context, stream_info, random_value, + action.routes()); } PANIC("Action in router matcher should be Route or RouteList"); } @@ -1894,7 +1890,7 @@ RouteConstSharedPtr VirtualHostImpl::getRouteFromEntries(const RouteCallback& cb } // Check for a route that matches the request. - return getRouteFromRoutes(cb, headers, stream_info, random_value, routes_); + return getRouteFromRoutes(cb, route_match_context, stream_info, random_value, routes_); } const VirtualHostImpl* RouteMatcher::findWildcardVirtualHost( diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index 1518dc2e77342..73e851e499194 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -25,9 +25,12 @@ #include "source/common/common/packed_struct.h" #include "source/common/config/datasource.h" #include "source/common/config/metadata.h" +#include "source/common/grpc/common.h" #include "source/common/http/hash_policy.h" #include "source/common/http/header_mutation.h" #include "source/common/http/header_utility.h" +#include "source/common/http/path_utility.h" +#include "source/common/http/utility.h" #include "source/common/matcher/matcher.h" #include "source/common/router/config_utility.h" #include "source/common/router/header_parser.h" @@ -63,6 +66,92 @@ class OriginalConnectPort : public StreamInfo::FilterState::Object { const uint32_t port_; }; +/** + * Lazily derives and caches per-request values used during route matching + * (path, query params, cookies, gRPC flag, etc.) so each is computed at most + * once across all route entries evaluated for a single request. + */ +class RouteMatchContext { +public: + RouteMatchContext(const Http::RequestHeaderMap& headers, bool ignore_path_params) + : headers_(headers), ignore_path_params_(ignore_path_params) {} + + const Http::RequestHeaderMap& headers() const { return headers_; } + + absl::string_view path() const { return headers_.getPathValue(); } + + absl::string_view pathWithoutQuery() const { + if (!path_without_query_computed_) { + path_without_query_ = Http::PathUtil::removeQueryAndFragment(path()); + path_without_query_computed_ = true; + } + return path_without_query_; + } + + absl::string_view sanitizedPath() const { + if (!sanitized_path_computed_) { + sanitized_path_ = ignore_path_params_ ? stripPathParams(path()) : path(); + sanitized_path_computed_ = true; + } + return sanitized_path_; + } + + absl::string_view sanitizedPathWithoutQuery() const { + if (!sanitized_path_without_query_computed_) { + sanitized_path_without_query_ = + ignore_path_params_ ? stripPathParams(pathWithoutQuery()) : pathWithoutQuery(); + sanitized_path_without_query_computed_ = true; + } + return sanitized_path_without_query_; + } + + const Http::Utility::QueryParamsMulti& queryParams() const { + if (!query_params_computed_) { + query_params_ = Http::Utility::QueryParamsMulti::parseQueryString(path()); + query_params_computed_ = true; + } + return query_params_; + } + + bool isGrpc() const { + if (!is_grpc_computed_) { + is_grpc_ = Grpc::Common::isGrpcRequestHeaders(headers_); + is_grpc_computed_ = true; + } + return is_grpc_; + } + + const absl::flat_hash_map& cookies() const { + if (!cookies_computed_) { + cookies_ = Http::Utility::parseCookies(headers_); + cookies_computed_ = true; + } + return cookies_; + } + +private: + static absl::string_view stripPathParams(absl::string_view path) { + const auto pos = path.find(';'); + return pos != absl::string_view::npos ? path.substr(0, pos) : path; + } + + const Http::RequestHeaderMap& headers_; + mutable absl::string_view path_without_query_; + mutable absl::string_view sanitized_path_; + mutable absl::string_view sanitized_path_without_query_; + mutable Http::Utility::QueryParamsMulti query_params_; + mutable absl::flat_hash_map cookies_; + // Keep bools at the end to reduce alignment overhead + const bool ignore_path_params_{}; + mutable bool path_without_query_computed_ : 1 {}; + mutable bool sanitized_path_computed_ : 1 {}; + mutable bool sanitized_path_without_query_computed_ : 1 {}; + mutable bool query_params_computed_ : 1 {}; + mutable bool is_grpc_ : 1 {}; + mutable bool is_grpc_computed_ : 1 {}; + mutable bool cookies_computed_ : 1 {}; +}; + /** * Base interface for something that matches a header. */ @@ -71,13 +160,15 @@ class Matchable { virtual ~Matchable() = default; /** - * See if this object matches the incoming headers. - * @param headers supplies the headers to match. + * See if this object matches the incoming request. + * @param route_match_context supplies pre-computed context for the request like path/query + * values. + * @param stream_info supplies the stream info for the request. * @param random_value supplies the random seed to use if a runtime choice is required. This * allows stable choices between calls if desired. - * @return true if input headers match this object. + * @return RouteConstSharedPtr if input matches this object, nullptr otherwise. */ - virtual RouteConstSharedPtr matches(const Http::RequestHeaderMap& headers, + virtual RouteConstSharedPtr matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const PURE; @@ -379,7 +470,7 @@ class VirtualHostImpl : Logger::Loggable { uint64_t random_value) const; RouteConstSharedPtr - getRouteFromRoutes(const RouteCallback& cb, const Http::RequestHeaderMap& headers, + getRouteFromRoutes(const RouteCallback& cb, const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value, absl::Span routes) const; @@ -584,8 +675,8 @@ class RouteEntryImplBase : public RouteEntryAndRoute, bool isRedirect() const; - bool matchRoute(const Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo& stream_info, - uint64_t random_value) const; + bool matchRoute(const RouteMatchContext& route_match_context, + const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const; absl::Status validateClusters(const Upstream::ClusterManager& cluster_manager) const; // Router::RouteEntry @@ -890,7 +981,6 @@ class RouteEntryImplBase : public RouteEntryAndRoute, std::vector config_headers_; std::vector config_query_parameters_; std::vector config_cookies_; - absl::flat_hash_set config_cookie_names_; UpgradeMap upgrade_map_; std::unique_ptr hash_policy_; @@ -938,7 +1028,7 @@ class UriTemplateMatcherRouteEntryImpl : public RouteEntryImplBase { PathMatchType matchType() const override { return PathMatchType::Template; } // Router::Matchable - RouteConstSharedPtr matches(const Http::RequestHeaderMap& headers, + RouteConstSharedPtr matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const override; @@ -973,7 +1063,7 @@ class PrefixRouteEntryImpl : public RouteEntryImplBase { PathMatchType matchType() const override { return PathMatchType::Prefix; } // Router::Matchable - RouteConstSharedPtr matches(const Http::RequestHeaderMap& headers, + RouteConstSharedPtr matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const override; @@ -1008,7 +1098,7 @@ class PathRouteEntryImpl : public RouteEntryImplBase { PathMatchType matchType() const override { return PathMatchType::Exact; } // Router::Matchable - RouteConstSharedPtr matches(const Http::RequestHeaderMap& headers, + RouteConstSharedPtr matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const override; @@ -1042,7 +1132,7 @@ class RegexRouteEntryImpl : public RouteEntryImplBase { PathMatchType matchType() const override { return PathMatchType::Regex; } // Router::Matchable - RouteConstSharedPtr matches(const Http::RequestHeaderMap& headers, + RouteConstSharedPtr matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const override; @@ -1076,7 +1166,7 @@ class ConnectRouteEntryImpl : public RouteEntryImplBase { PathMatchType matchType() const override { return PathMatchType::None; } // Router::Matchable - RouteConstSharedPtr matches(const Http::RequestHeaderMap& headers, + RouteConstSharedPtr matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const override; @@ -1109,7 +1199,7 @@ class PathSeparatedPrefixRouteEntryImpl : public RouteEntryImplBase { PathMatchType matchType() const override { return PathMatchType::PathSeparatedPrefix; } // Router::Matchable - RouteConstSharedPtr matches(const Http::RequestHeaderMap& headers, + RouteConstSharedPtr matches(const RouteMatchContext& route_match_context, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const override; diff --git a/test/common/router/config_impl_speed_test.cc b/test/common/router/config_impl_speed_test.cc index a30adac0c7220..5859f8132dc96 100644 --- a/test/common/router/config_impl_speed_test.cc +++ b/test/common/router/config_impl_speed_test.cc @@ -318,6 +318,86 @@ BENCHMARK(bmRouteTableSizeWithRegexMatch)->RangeMultiplier(2)->Ranges({{1, 2 << BENCHMARK(bmRouteTableSizeWithExactMatcherTree)->RangeMultiplier(2)->Ranges({{1, 2 << 13}}); BENCHMARK(bmRouteTableSizeWithPrefixMatcherTree)->RangeMultiplier(2)->Ranges({{1, 2 << 13}}); +// N plain prefix routes. Route i matches only /api/v{i}/. Last route matches. +static RouteConfiguration genPlainRouteConfig(int n) { + RouteConfiguration route_config; + VirtualHost* v_host = route_config.add_virtual_hosts(); + v_host->set_name("default"); + v_host->add_domains("*"); + for (int i = 0; i < n; ++i) { + Route* route = v_host->add_routes(); + route->mutable_direct_response()->set_status(200); + route->mutable_match()->set_prefix(absl::StrCat("/api/v", i, "/")); + } + return route_config; +} + +// N routes: first n/2 share prefix "/api/" with a non-matching query param; +// last route is a plain prefix match. +static RouteConfiguration genMixedRouteConfig(int n) { + RouteConfiguration route_config; + VirtualHost* v_host = route_config.add_virtual_hosts(); + v_host->set_name("default"); + v_host->add_domains("*"); + const int n_query = n / 2; + for (int i = 0; i < n; ++i) { + Route* route = v_host->add_routes(); + route->mutable_direct_response()->set_status(200); + RouteMatch* match = route->mutable_match(); + if (i < n_query) { + match->set_prefix("/api/"); + auto* qp = match->add_query_parameters(); + qp->set_name("id"); + qp->mutable_string_match()->set_exact(absl::StrCat("nomatch_", i)); + } else if (i < n - 1) { + match->set_prefix(absl::StrCat("/other/v", i, "/")); + } else { + match->set_prefix("/api/"); + } + } + return route_config; +} + +// N plain prefix routes, no query/cookie matching. Request matches the last route. +static void bmPlainRoutes(benchmark::State& state) { + const int n = state.range(0); + Api::ApiPtr api = Api::createApiForTest(); + NiceMock factory_context; + NiceMock stream_info; + ON_CALL(factory_context, api()).WillByDefault(ReturnRef(*api)); + std::shared_ptr config = *ConfigImpl::create( + genPlainRouteConfig(n), factory_context, ProtobufMessage::getNullValidationVisitor(), true); + const std::string path = absl::StrCat("/api/v", n - 1, "/foo"); + Http::TestRequestHeaderMapImpl headers{{":authority", "www.example.com"}, + {":method", "GET"}, + {":path", path}, + {"x-forwarded-proto", "http"}}; + for (auto _ : state) { // NOLINT + config->route(headers, stream_info, 0); + } +} + +// N routes, first half with non-matching query params. Request matches the last route. +static void bmMixedRoutes(benchmark::State& state) { + const int n = state.range(0); + Api::ApiPtr api = Api::createApiForTest(); + NiceMock factory_context; + NiceMock stream_info; + ON_CALL(factory_context, api()).WillByDefault(ReturnRef(*api)); + std::shared_ptr config = *ConfigImpl::create( + genMixedRouteConfig(n), factory_context, ProtobufMessage::getNullValidationVisitor(), true); + Http::TestRequestHeaderMapImpl headers{{":authority", "www.example.com"}, + {":method", "GET"}, + {":path", "/api/foo?id=target"}, + {"x-forwarded-proto", "http"}}; + for (auto _ : state) { // NOLINT + config->route(headers, stream_info, 0); + } +} + +BENCHMARK(bmPlainRoutes)->RangeMultiplier(2)->Ranges({{64, 2 << 10}}); +BENCHMARK(bmMixedRoutes)->RangeMultiplier(2)->Ranges({{64, 2 << 10}}); + } // namespace } // namespace Router } // namespace Envoy diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index c3fc9d1dc7dec..f75f303f3250f 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -360,6 +360,124 @@ class RouteMatcherTest : public testing::Test, public ConfigImplTestBase, public TestScopedRuntime {}; +TEST(RouteMatchContextTest, BasicPathAndQuery) { + Http::TestRequestHeaderMapImpl headers{{":path", "/foo?a=1&b=2"}}; + RouteMatchContext ctx(headers, /*ignore_path_params=*/false); + + EXPECT_EQ(ctx.path(), "/foo?a=1&b=2"); + EXPECT_EQ(ctx.pathWithoutQuery(), "/foo"); + EXPECT_EQ(ctx.sanitizedPath(), "/foo?a=1&b=2"); + EXPECT_EQ(ctx.sanitizedPathWithoutQuery(), "/foo"); + EXPECT_EQ(ctx.queryParams().getFirstValue("a").value(), "1"); + EXPECT_EQ(ctx.queryParams().getFirstValue("b").value(), "2"); +} + +TEST(RouteMatchContextTest, PathWithFragment) { + Http::TestRequestHeaderMapImpl headers{{":path", "/foo?x=1#section"}}; + RouteMatchContext ctx(headers, /*ignore_path_params=*/false); + + EXPECT_EQ(ctx.pathWithoutQuery(), "/foo"); + EXPECT_EQ(ctx.sanitizedPathWithoutQuery(), "/foo"); +} + +TEST(RouteMatchContextTest, NoQueryString) { + Http::TestRequestHeaderMapImpl headers{{":path", "/foo/bar"}}; + RouteMatchContext ctx(headers, /*ignore_path_params=*/false); + + EXPECT_EQ(ctx.path(), "/foo/bar"); + EXPECT_EQ(ctx.pathWithoutQuery(), "/foo/bar"); + EXPECT_FALSE(ctx.queryParams().getFirstValue("any").has_value()); +} + +TEST(RouteMatchContextTest, SanitizedPathStripsPathParams) { + Http::TestRequestHeaderMapImpl headers{{":path", "/foo;env=prod;ver=2?a=1"}}; + RouteMatchContext ctx(headers, /*ignore_path_params=*/true); + + EXPECT_EQ(ctx.sanitizedPath(), "/foo"); + EXPECT_EQ(ctx.sanitizedPathWithoutQuery(), "/foo"); + // Raw accessors are unaffected. + EXPECT_EQ(ctx.path(), "/foo;env=prod;ver=2?a=1"); + EXPECT_EQ(ctx.pathWithoutQuery(), "/foo;env=prod;ver=2"); +} + +TEST(RouteMatchContextTest, SanitizedPathNoOpWhenNotConfigured) { + Http::TestRequestHeaderMapImpl headers{{":path", "/foo;env=prod?a=1"}}; + RouteMatchContext ctx(headers, /*ignore_path_params=*/false); + + EXPECT_EQ(ctx.sanitizedPath(), "/foo;env=prod?a=1"); + EXPECT_EQ(ctx.sanitizedPathWithoutQuery(), "/foo;env=prod"); +} + +TEST(RouteMatchContextTest, IsGrpc) { + { + Http::TestRequestHeaderMapImpl headers{{":path", "/pkg.Svc/Method"}, + {"content-type", "application/grpc"}}; + RouteMatchContext ctx(headers, /*ignore_path_params=*/false); + EXPECT_TRUE(ctx.isGrpc()); + } + { + Http::TestRequestHeaderMapImpl headers{{":path", "/foo"}, {"content-type", "application/json"}}; + RouteMatchContext ctx(headers, /*ignore_path_params=*/false); + EXPECT_FALSE(ctx.isGrpc()); + } +} + +TEST(RouteMatchContextTest, Cookies) { + Http::TestRequestHeaderMapImpl headers{{":path", "/foo"}, {"cookie", "session=abc; user=xyz"}}; + RouteMatchContext ctx(headers, /*ignore_path_params=*/false); + + const auto& cookies = ctx.cookies(); + EXPECT_EQ(cookies.at("session"), "abc"); + EXPECT_EQ(cookies.at("user"), "xyz"); + EXPECT_EQ(cookies.size(), 2); +} + +TEST(RouteMatchContextTest, NoCookies) { + Http::TestRequestHeaderMapImpl headers{{":path", "/foo"}}; + RouteMatchContext ctx(headers, /*ignore_path_params=*/false); + + EXPECT_TRUE(ctx.cookies().empty()); +} + +// Regression test: route 1 checks cookie "session" (no match), route 2 checks +// cookie "user" (match). With the old filtered-cache bug, route 1's evaluation +// would cache only {"session": ...}, causing route 2 to miss "user" and fail. +TEST_F(RouteMatcherTest, CookiesCachedAcrossDifferentNameSets) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: cookie + domains: ["*"] + routes: + - match: + prefix: "/" + cookies: + - name: session + string_match: + exact: expected + route: { cluster: session-cluster } + - match: + prefix: "/" + cookies: + - name: user + string_match: + exact: xyz + route: { cluster: user-cluster } + - match: + prefix: "/" + route: { cluster: default } + )EOF"; + + factory_context_.cluster_manager_.initializeClusters( + {"session-cluster", "user-cluster", "default"}, {}); + TestConfigImpl config(parseRouteConfigurationFromYaml(yaml), factory_context_, true, + creation_status_); + + // session=other → route 1 misses; user=xyz → route 2 matches. + auto headers = genHeaders("www.example.com", "/foo", "GET"); + headers.addCopy("cookie", "session=other; user=xyz"); + EXPECT_EQ("user-cluster", config.route(headers, 0)->routeEntry()->clusterName()); +} + TEST_F(RouteMatcherTest, TestConnectRoutes) { const std::string yaml = R"EOF( virtual_hosts: From aa2553529de0f13ff1ee0281f11a0beee791623e Mon Sep 17 00:00:00 2001 From: Ricardo Date: Fri, 15 May 2026 11:30:07 -0400 Subject: [PATCH 322/750] Update QUICHE from 13384c955 to f129e801d (#45074) https://github.com/google/quiche/compare/13384c955..f129e801d ``` $ git log 13384c955..f129e801d --date=short --no-merges --format="%ad %al %s" 2026-05-14 ricea Automated g4 rollback of changelist 911726734. 2026-05-14 haoyuewang Add a sni() method with a default implementation to QuicCryptoStream. 2026-05-13 quiche-dev Enabling rolled out flags. 2026-05-13 quiche-dev Add support for handling client certificate requests in QUIC TLS. 2026-05-13 martinduke Fix an issue from AI review of cl/914368728. 2026-05-13 martinduke Fix ASAN/MSAN errors in MoqtSessionTest and MoqtTrackTest. 2026-05-13 ianswett Deflake BBR3SimulatorTest 2026-05-13 asedeno Fix OSS QUICHE build. 2026-05-13 vasilvv Use new MOQT control message parser API directly. 2026-05-12 ripere Add support for chunked response decompression. 2026-05-12 ripere Refactor gzip decompression to share common code for chunked support. 2026-05-12 martinduke Allow fragmented MOQT object payloads. 2026-05-12 haoyuewang Maybe copy datagram frames in QuicUnackedPacketMap. 2026-05-12 martinduke Remove PUBLISH_OK message. 2026-05-07 ianswett Update CWND calculations to match the latest BBR draft. https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html 2026-05-07 rch No public description 2026-05-06 ricea Temporarily add a dummy definition of CreateIncomingStream() to quic::QuicSession 2026-05-06 haoyuewang Increase the QUIC_BUG threshold. 2026-05-06 vasilvv Add functions to interop between absl::Cord and QuicheMemSlice. 2026-05-05 rch No public description 2026-05-04 rch Deprecate QUIC reloadable flag quic_close_connection_on_underflow. 2026-05-04 vasilvv Rewrite MOQT control message parser. 2026-05-02 ianswett Update BBR3 to more closely align with the BBRv3 draft. 2026-05-01 ianswett Remove tests for the deprecated kB201 connection option from BBR2 and BBR3 simulator tests. ``` Risk Level: Low Testing: Existing tests pass Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Rickyp --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 079e863ad5dd0..4884b3181936c 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -296,7 +296,7 @@ quiche: project_name: "QUICHE" project_desc: "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols" project_url: "https://github.com/google/quiche" - release_date: "2026-04-30" + release_date: "2026-05-14" use_category: - controlplane - dataplane_core diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 4428cf94c6b5d..bc1aa06d9cd42 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -553,8 +553,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/simdutf/simdutf/releases/download/v{version}/singleheader.zip"], ), quiche = dict( - version = "13384c955f76e3a2b157f7b613ce6c02d594770f", - sha256 = "a2310cab30a46e9ccf533cd10632d44852d46784f4192e202cc53b6e353d5876", + version = "f129e801d183fd095620d3bd68f8119ca9d5c0ea", + sha256 = "f70ab89a950bb46f239c29e7e9757b3ce3082403a65e9e1920ccd2cf3fcedbeb", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", ), From fc9300216ce370b7f5c7fbba062e9b6cf4e05486 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 15 May 2026 18:01:44 +0100 Subject: [PATCH 323/750] deps: Bump `fips_go_*` -> 1.26.3 (#45085) Fix CVES: CVE-2026-33811 CVE-2026-33814 CVE-2026-39817 CVE-2026-39819 CVE-2026-39820 CVE-2026-39823 CVE-2026-39825 CVE-2026-39826 CVE-2026-39836 CVE-2026-42499 CVE-2026-42501 Signed-off-by: Ryan Northey --- bazel/deps.yaml | 6 +++--- bazel/repository_locations.bzl | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 4884b3181936c..0dd259f478dc8 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -854,7 +854,7 @@ fips_go_linux_amd64: project_name: "Go (Linux amd64)" project_desc: "Go programming language (Linux amd64)" project_url: "https://golang.org/" - release_date: "2025-06-05" + release_date: "2025-05-07" use_category: - build - dataplane_core @@ -867,7 +867,7 @@ fips_go_linux_arm64: project_name: "Go (Linux arm64)" project_desc: "Go programming language (Linux arm64)" project_url: "https://golang.org/" - release_date: "2025-06-05" + release_date: "2025-05-07" use_category: - build - dataplane_core @@ -906,7 +906,7 @@ fips_go_ppc64le: project_name: "Go (Linux ppc64le)" project_desc: "Go programming language (Linux ppc64le)" project_url: "https://golang.org/" - release_date: "2026-04-07" + release_date: "2025-05-07" use_category: - build - dataplane_core diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index bc1aa06d9cd42..bfa0107a2b2be 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -720,14 +720,14 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/Kitware/CMake/releases/download/v{version}/cmake-{version}-linux-aarch64.tar.gz"], ), fips_go_linux_amd64 = dict( - version = "1.26.2", - sha256 = "990e6b4bbba816dc3ee129eaeaf4b42f17c2800b88a2166c265ac1a200262282", + version = "1.26.3", + sha256 = "2b2cfc7148493da5e73981bffbf3353af381d5f93e789c82c79aff64962eb556", strip_prefix = "go", urls = ["https://dl.google.com/go/go{version}.linux-amd64.tar.gz"], ), fips_go_linux_arm64 = dict( - version = "1.26.2", - sha256 = "c958a1fe1b361391db163a485e21f5f228142d6f8b584f6bef89b26f66dc5b23", + version = "1.26.3", + sha256 = "9d89a3ea57d141c2b22d70083f2c8459ba3890f2d9e818e7e933b75614936565", strip_prefix = "go", urls = ["https://dl.google.com/go/go{version}.linux-arm64.tar.gz"], ), @@ -738,8 +738,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/clang+llvm-{version}-powerpc64le-linux-ubuntu-18.04.tar.xz"], ), fips_go_ppc64le = dict( - version = "1.26.2", - sha256 = "62b7645dd2404052535617c59e91cf03c7aa28e332dbaddbe4c0d7de7bcc6736", + version = "1.26.3", + sha256 = "dbd82b50530ead2beb1fd72215117380df3cb16332b51467116dc35b3691dd75", strip_prefix = "go", urls = ["https://dl.google.com/go/go{version}.linux-ppc64le.tar.gz"], ), From b48eb083d88d009e54a131ffd6257cdfae67cc0a Mon Sep 17 00:00:00 2001 From: danzh Date: Fri, 15 May 2026 13:13:40 -0400 Subject: [PATCH 324/750] mobile: relax AsyncClient Python API to take different engine builders (#45078) Commit Message: the Python class now takes a custom Protocol `EngineBuilderLike`. Additional Description: also remove google's private PyPI mirror url override Risk Level: low Testing: existing tests passs Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- mobile/MODULE.bazel | 1 - .../python/envoy_mobile/async_client/client.py | 17 +++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/mobile/MODULE.bazel b/mobile/MODULE.bazel index a1cbd57f26808..410ba70a4422f 100644 --- a/mobile/MODULE.bazel +++ b/mobile/MODULE.bazel @@ -24,7 +24,6 @@ local_path_override( ) pip.parse( - experimental_index_url = "https://us-python.pkg.dev/artifact-foundry-prod/python-3p-trusted/simple/", hub_name = "pypi", python_version = PYTHON_VERSION, requirements_lock = "//:requirements.txt", diff --git a/mobile/library/python/envoy_mobile/async_client/client.py b/mobile/library/python/envoy_mobile/async_client/client.py index c5dfc347a9566..821edbee4f491 100644 --- a/mobile/library/python/envoy_mobile/async_client/client.py +++ b/mobile/library/python/envoy_mobile/async_client/client.py @@ -1,10 +1,9 @@ """High level asyncio client built on top of the Envoy Mobile bindings.""" import asyncio -from typing import Any, Dict, List, Optional, Union +from typing import Any, Callable, Dict, List, Optional, Protocol, Union from .. import envoy_engine -from ..envoy_engine import EngineBuilder from .executor import AsyncioExecutor, Executor from .response import ClientResponseError, Response @@ -13,6 +12,16 @@ ) +class EngineBuilderLike(Protocol): + def set_on_engine_running(self, closure: Callable[[], None]) -> "EngineBuilderLike": + """Set callback to be invoked when engine is running.""" + ... + + def build(self) -> Any: + """Build and return the engine instance.""" + ... + + class AsyncClient: """A very small HTTP client that speaks the subset of envoy_requests that we care about. @@ -25,11 +34,11 @@ class AsyncClient: Use as an async context manager: ``async with AsyncClient(engine_builder) as client:`` """ - def __init__(self, engine_builder: EngineBuilder) -> None: + def __init__(self, engine_builder: EngineBuilderLike) -> None: """Construct a new AsyncClient. Args: - engine_builder: A pre-configured EngineBuilder to finalize and build. + engine_builder: A pre-configured EngineBuilder (or compatible builder object) to finalize and build. """ self._engine_builder = engine_builder self._engine = None From 2a6a450be7dad050c92eb1311141050f4977423d Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 15 May 2026 19:31:20 +0100 Subject: [PATCH 325/750] deps: Bump toolshed py deps and cleanup (#45091) Signed-off-by: Ryan Northey --- tools/base/requirements.in | 30 +++---- tools/base/requirements.txt | 165 +++++++++++++----------------------- 2 files changed, 68 insertions(+), 127 deletions(-) diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 372a17636b384..2abf30611eae9 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -1,24 +1,16 @@ -aio.api.bazel>=0.1.0 -aio.api.github>=0.3.0 -aio.core>=0.11.0 +aio.api.bazel>=0.1.1 +aio.api.github>=0.3.1 +aio.core>=0.11.1 aiohttp>=3.13.3 aioquic>=0.9.21 -dependatool>=0.3.0 -envoy.base.utils>=0.6.0 -envoy.ci.report>=0.1.0 -envoy.code.check>=0.6.0 -envoy.dependency.check>=0.2.0 -# 0.0.13 pins envoy-base-utils==0.5.11 (old-style strict pin) conflicting with >=0.6.0 era -envoy.distribution.distrotest>=0.0.12 -# 0.1.0 has a contradictory envoy-github-release==0.1.0 / envoy.github.release==0.1.1.dev dep -envoy.distribution.release>=0.0.9 -# 0.0.10 pins envoy-base-utils==0.5.11 (old-style strict pin) conflicting with >=0.6.0 era -envoy.distribution.repo>=0.0.8 -envoy.distribution.verify>=0.1.0 -envoy.gpg.identity>=0.1.2 -envoy.gpg.sign>=0.3.0 -flake8>=6 -frozendict>=2.3.7 +dependatool>=0.3.1 +envoy.base.utils>=0.6.3 +envoy.ci.report>=0.1.1 +envoy.code.check>=0.6.3 +envoy.dependency.check>=0.2.1 +envoy.distribution.release>=0.1.2 +envoy.distribution.verify>=0.1.1 +envoy.gpg.sign>=0.3.1 gitpython icalendar>=6.1.1 orjson>=3.10.15 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index b444096d54122..d47f9f8828520 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --generate-hashes requirements.in +# bazel run //tools/base:requirements.update # abstracts==0.2.0 \ --hash=sha256:1dd6df509a67cae1722c0dd9f58cdd6dee6e42f45bf0d7197e159b2e4c5e98e9 \ @@ -19,24 +19,22 @@ abstracts==0.2.0 \ # envoy-code-check # envoy-dependency-check # envoy-distribution-release - # envoy-distribution-repo - # envoy-github-abstract # envoy-github-release -aio-api-bazel==0.1.0 \ - --hash=sha256:4fafbae68311190b78fb0a7a921beb37f2a6f80b299678204c8041c4921c6553 \ - --hash=sha256:995fda8552e66f3124d4b11c04c07dd068f64b248a5aa8afd1c1c406c008c836 +aio-api-bazel==0.1.1 \ + --hash=sha256:04ee9aa1dddc2998b674ca0a6a791cb7ab9d49f1f400bd19fcd431e00ea54587 \ + --hash=sha256:59a5050d43f4d5ebf19bb669b4a386c74a66520002dc72e5d900ebd058f4d5d7 # via -r tools/base/requirements.in -aio-api-github==0.3.0 \ - --hash=sha256:502dde7cbcdd20ce24dfad44a3b29430387a29815aaf071db64ab8c338e755a9 \ - --hash=sha256:5844bf9015fcfe23c6d1000cad70e60a8e874440cdd107974d13441395e47bab +aio-api-github==0.3.1 \ + --hash=sha256:04d2e3fb4fcb55927b75f9a3cefae7673cefabd2387dd2416848ff19326d719c \ + --hash=sha256:d083ca09e5577d0c4217138e45b18ea63d57d0cbb030eb336cc2a979f8b244b4 # via # -r tools/base/requirements.in # envoy-base-utils # envoy-ci-report # envoy-dependency-check -aio-core==0.11.0 \ - --hash=sha256:2b7e49ca221052f84eddf32424c76093a32569fc0802cc41da7e683457241fd0 \ - --hash=sha256:88c0646f6360472963d80143cfb23026fb910cfa5bcd724593ffb88b9be454ad +aio-core==0.11.1 \ + --hash=sha256:03f1202029655c68d5d41c2fa1d18e344d61515b71080f770f8cf6f99fbc5f5a \ + --hash=sha256:76608a1dad3985866c1b3ffc81e0d08bd1a740e8e72125bfbf46d1ee6fc3abfe # via # -r tools/base/requirements.in # aio-api-bazel @@ -48,31 +46,25 @@ aio-core==0.11.0 \ # envoy-code-check # envoy-dependency-check # envoy-distribution-release - # envoy-distribution-repo - # envoy-github-abstract # envoy-github-release - # envoy-gpg-identity # envoy-gpg-sign -aio-run-checker==0.6.0 \ - --hash=sha256:6e3be38072506c9b7d30ce79d762e5accf6ff6f3dcc0084370cb490945b7826f \ - --hash=sha256:ff63df32360b17c6c6e3edd250d33f4a2cb328b3fb8967d7a40bbfc6497da162 +aio-run-checker==0.6.1 \ + --hash=sha256:0c929c3534600b5f163de177892841ed36267472a8d0609f550d717a523f28fd \ + --hash=sha256:17f8ccf7976406adc062c5042888dab9507c5332bb00b697c3bd48e56a1ffee0 # via # dependatool # envoy-code-check # envoy-dependency-check - # envoy-distribution-distrotest # envoy-distribution-verify -aio-run-runner==0.4.0 \ - --hash=sha256:6ca926a9a5eb435707a88d03031c27c71fd1402434cfafbbf1bed170de6bd760 \ - --hash=sha256:d89efce2f80627cf00f99e1dcc878ace9a272f56abfa579a32c57515a36c8803 +aio-run-runner==0.4.1 \ + --hash=sha256:03c2978e4c7e1d02a55e25b0c081603d7900387bcce4fe80b8e8af8f3284e05b \ + --hash=sha256:43c80688c50ba3a528d88635fee5653b9f0f40fc4a036e1aeaddf8ada3794d2e # via # aio-api-bazel # aio-run-checker # envoy-base-utils # envoy-ci-report # envoy-distribution-release - # envoy-distribution-repo - # envoy-github-abstract # envoy-github-release # envoy-gpg-sign aiodocker==0.26.0 \ @@ -81,9 +73,9 @@ aiodocker==0.26.0 \ # via # envoy-distribution-verify # envoy-docker-utils -aiofiles==24.1.0 \ - --hash=sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c \ - --hash=sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5 +aiofiles==25.1.0 \ + --hash=sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2 \ + --hash=sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695 # via envoy-github-release aiohappyeyeballs==2.6.1 \ --hash=sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558 \ @@ -217,7 +209,6 @@ aiohttp==3.13.5 \ # envoy-base-utils # envoy-ci-report # envoy-dependency-check - # envoy-github-abstract # envoy-github-release aioquic==1.3.0 \ --hash=sha256:019b16580d53541b5d77b4a44a61966921156554fad2536d74895713c800caa5 \ @@ -488,80 +479,57 @@ cryptography==47.0.0 \ # pyjwt # pyopenssl # service-identity -dependatool==0.3.0 \ - --hash=sha256:12baf8c611f20d2165a0966d3340fda92e0683bca72e47d2883e13e3afc169bc \ - --hash=sha256:2add2aac95e711979bc5bfef8b3d60b0069c410aea8aefd87255e05f4777cc61 +dependatool==0.3.1 \ + --hash=sha256:265fc69e8b1605edb8e33872a36d3a2c2cc055743bec8e58d1ed3c72a17a9431 \ + --hash=sha256:6be19ce9fbdffccf639bad0393e454080b7bba8510a98edc78855a6a93895020 # via -r tools/base/requirements.in -envoy-base-utils==0.6.0 \ - --hash=sha256:1354ad0198b0282fa1e2072324aa9e479eb45d368cc9d84e5f196a277ee631d0 \ - --hash=sha256:e56c9810cbc8b0db988ba4223f6c90281e9f1a16aa5a7bdb781d1062d8b3f8a4 +envoy-base-utils==0.6.3 \ + --hash=sha256:b7a7ccd51948e5feb903dd42b4dab56ab28309c0e1a6b6dfb7840c99cd0eccd8 \ + --hash=sha256:d16caa01af0a9edb03db1e1fc998daace543636b2cd33e6bb769b9d56f8d880f # via # -r tools/base/requirements.in # envoy-code-check # envoy-dependency-check - # envoy-distribution-distrotest # envoy-distribution-release - # envoy-distribution-repo # envoy-distribution-verify # envoy-github-release # envoy-gpg-sign -envoy-ci-report==0.1.0 \ - --hash=sha256:126f4e39d4b8488fef9c923b18face50f9a31450c8fa4f750804c973e9eee889 \ - --hash=sha256:40a8e1c241bf6d6fb8a97717a19ae36dda12987db97b028d1e55a310f7842358 +envoy-ci-report==0.1.1 \ + --hash=sha256:481115be4d05941b83ffbbd8fdac241f10e9823c076f850d8bef7f0308aa382d \ + --hash=sha256:a38da2e996b266b32ff80f2f404018d1887f156ad086e3fa8fd4233bb4cd6fe7 # via -r tools/base/requirements.in -envoy-code-check==0.6.0 \ - --hash=sha256:8fddd82e6f66811c1e49e7c46ae27d87d45aa8688a1619272d8b98bdca187956 \ - --hash=sha256:d4a91b6425bc60bebca01e6a2fac32f41323d07f1817092ab3b6f5d4f5d18639 +envoy-code-check==0.6.3 \ + --hash=sha256:2824fe748328ba88e8424d7f118e6090b5cd605e7654c3e36c0261081dc78444 \ + --hash=sha256:d546e94897c0a60e3c90bec69fe1aee6925623f8328413a048a77f993ea8c89f # via -r tools/base/requirements.in -envoy-dependency-check==0.2.0 \ - --hash=sha256:28b5a22b2da0de21de8c0d15e64073c37e9d362a7848b79d49fbb66c2a9d9350 \ - --hash=sha256:2ce4790e18a88d53b18af7c3b9aea169c73873f13a24d2fb3d25ab1807c17609 +envoy-dependency-check==0.2.1 \ + --hash=sha256:a037ab34b7db9893ecf07247c8e9149481148de26f340f1c27e4adfa7a48e227 \ + --hash=sha256:d91134dc18d2a6a69694623b70a7cb0571647f141a493f0e1bff12d843ec9f7b # via -r tools/base/requirements.in -envoy-distribution-distrotest==0.0.12 \ - --hash=sha256:04f2f5302c039aca82eb4e7d1e8a6fee5fc274a1cdae27130d15754eb3abb4b4 \ - --hash=sha256:2762ff531715c1e8d057f10f0fc6c79807f0f67930fc5032fa5a2f239498a7d7 +envoy-distribution-release==0.1.2 \ + --hash=sha256:0a784b46563d2611eb7c68a10c46dbe1821161f0b9fa0018220e5ec373a3b7f3 \ + --hash=sha256:c15873e88743970143d8ac9fd911bac8cbf34aef6b0315ab78c003a242ef60ef # via -r tools/base/requirements.in -envoy-distribution-release==0.0.9 \ - --hash=sha256:592bdc8bc6847daa7e677011d72163b507e3fee821f5ea13a944e27c2fda334f \ - --hash=sha256:974308468be49d034e5b174745bd6a5671364d090a9a810f0f6f36e81afbcb5d +envoy-distribution-verify==0.1.1 \ + --hash=sha256:21bf9cbb259c81d953ed768fca9b6d96e65b46c731cc406fe1d3cba5398591bc \ + --hash=sha256:f96840fd1c2f37f39e72f2a7e9d11d3076f67fe20f598ecc1d96153e19c7b09c # via -r tools/base/requirements.in -envoy-distribution-repo==0.0.8 \ - --hash=sha256:84151ae1c77e63a6967404b5e4fd1130138010b540d3081a0c016c28a657a170 \ - --hash=sha256:c264232b666964696dbbc0ced1a82a4aefcf8f0af89ffd88c05ca8428f2557b5 - # via -r tools/base/requirements.in -envoy-distribution-verify==0.1.0 \ - --hash=sha256:927dd59a9e146d86ebcb581c588b76103b02f116f0062d7a7eba374178d0561b \ - --hash=sha256:af94e7611a6320e8a672d459f747e883a5de2ce245f811cd9bd014800208892a - # via -r tools/base/requirements.in -envoy-docker-utils==0.1.0 \ - --hash=sha256:5ab22736f371c762bc7462e0fcd12fcf2e6cacfc070fd9b76a12bd1f3e3b5314 \ - --hash=sha256:c4d9b366059afc146301bed0e463ba5ce40def4201a2872cd4cca309e1276fe0 - # via - # envoy-distribution-distrotest - # envoy-distribution-verify -envoy-github-abstract==0.0.22 \ - --hash=sha256:2dd65e2f247a4947d0198b295c82716c13162e30c433b7625c27d59eee7bcf78 \ - --hash=sha256:86de8bbe2ecf9db896ecc4ff30ab48fc44a516d868ab1748cd4ae538facacb10 - # via - # envoy-distribution-release - # envoy-github-release -envoy-github-release==0.0.15 \ - --hash=sha256:955671aa0c664ade3d8dbcd60bc9c72284139b81aeaa3fe72157c4e8383fd35b \ - --hash=sha256:d571422a79e535ec36130dae5a3c56b429040fb20ca60cd3b8cf8588c52b546c +envoy-docker-utils==0.1.1 \ + --hash=sha256:2375ae633783c07652f42c86e2db190cb34f4d0486468b8a475cf3417a8d67b5 \ + --hash=sha256:9c3df7ca912972f17469661f56b29cd7785705bf2176606dbfded283dab85f4d + # via envoy-distribution-verify +envoy-github-release==0.1.2 \ + --hash=sha256:30f31f1767112719c098b1ea3cdb610fd60719944a1e9c23e94d219c12a626d9 \ + --hash=sha256:4b8c8a6e0df215fd19e45a645b53046ca96d3a490eaa07ba1a2581b9d074ce56 # via envoy-distribution-release -envoy-gpg-identity==0.1.2 \ - --hash=sha256:3751c09f5deafdbd96e8ccbec276d13ea431381cd20dea0efa24932ba9e15b8f \ - --hash=sha256:fcb916aa1ca0e2dd6d8d6d09f6b54fbb2a276e6011f985f1bd397d60cdbf130e - # via -r tools/base/requirements.in -envoy-gpg-sign==0.3.0 \ - --hash=sha256:149c00b471c13884df43b1d3eb30b4092ead44a8b4d6eb07e6d12c65d5f18a2e \ - --hash=sha256:22745ac13a087ed4425f2f1c897ca59b67a7b3ba67a3af405273713fabbb24e4 +envoy-gpg-sign==0.3.2 \ + --hash=sha256:307179de846a381675a73bcdd8dc7a46c1423f9596a5eb0926ab97f5f3957350 \ + --hash=sha256:ccfe864e5a47a8f363b3169088a3027db3390208347ee9f1b65b48138f901e17 # via -r tools/base/requirements.in flake8==7.3.0 \ --hash=sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e \ --hash=sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872 # via - # -r tools/base/requirements.in # envoy-code-check # pep8-naming frozendict==2.4.7 \ @@ -657,7 +625,6 @@ frozendict==2.4.7 \ --hash=sha256:ffd1a9f9babec9119712e76a39397d8aa0d72ef8c4ccad917c6175d7e7f81b74 \ --hash=sha256:fff8584e3bbdc5c1713cd016fbf4b88babfffd4e5e89b39020f2a208dd24c900 # via - # -r tools/base/requirements.in # aio-run-runner # envoy-base-utils frozenlist==1.4.1 \ @@ -748,8 +715,6 @@ gidgethub==5.4.0 \ # aio-api-github # envoy-ci-report # envoy-dependency-check - # envoy-distribution-release - # envoy-github-abstract # envoy-github-release gitdb==4.0.11 \ --hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \ @@ -1083,7 +1048,6 @@ packaging==26.2 \ # envoy-base-utils # envoy-code-check # envoy-dependency-check - # envoy-github-abstract # envoy-github-release pathspec==1.1.1 \ --hash=sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a \ @@ -1243,7 +1207,7 @@ pygithub==2.9.1 \ --hash=sha256:2ec78fca30092d51a42d76f4ddb02131b6f0c666a35dfdf364cf302cdda115b9 \ --hash=sha256:59771d7ff63d54d427be2e7d0dad2208dfffc2b0a045fec959263787739b611c # via -r tools/base/requirements.in -pyjwt==2.12.1 \ +pyjwt[crypto]==2.12.1 \ --hash=sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c \ --hash=sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b # via @@ -1289,7 +1253,6 @@ python-gnupg==0.5.6 \ --hash=sha256:b5050a55663d8ab9fcc8d97556d229af337a87a3ebebd7054cbd8b7e2043394a # via # envoy-base-utils - # envoy-gpg-identity # envoy-gpg-sign pyyaml==6.0.3 \ --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ @@ -1371,10 +1334,6 @@ service-identity==24.1.0 \ --hash=sha256:6829c9d62fb832c2e1c435629b0a8c476e1929881f28bee4d20bc24161009221 \ --hash=sha256:a28caf8130c8a5c1c7a6f5293faaf239bbfb7751e4862436920ee6f2616f568a # via aioquic -setuptools==82.0.1 \ - --hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \ - --hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb - # via -r tools/base/requirements.in six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 @@ -1393,20 +1352,6 @@ trycast==1.3.0 \ # via # aio-core # envoy-base-utils -types-orjson==3.6.2 \ - --hash=sha256:22ee9a79236b6b0bfb35a0684eded62ad930a88a56797fa3c449b026cf7dbfe4 \ - --hash=sha256:cf9afcc79a86325c7aff251790338109ed6f6b1bab09d2d4262dd18c85a3c638 - # via aio-core -types-protobuf==7.34.1.20260508 \ - --hash=sha256:1c93e8c294281b76a5255fc21c747db0004694463ac6ea9866ee06da969fa555 \ - --hash=sha256:a5d647381f8651bd505304ed1148b8a7b342781796e0f80e0284c774c2262a09 - # via envoy-base-utils -types-pyyaml==6.0.12.20260508 \ - --hash=sha256:5ae42149c3ebf7aaaf6c65ee49af590c80f0ba52e9e3f75a75c5564b33556fa6 \ - --hash=sha256:edc094ed3a918b0c6232f71a5b67fdf38e76e17517b7d87bfbb9fc27d442fb51 - # via - # aio-core - # envoy-code-check typing-extensions==4.12.2 \ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 @@ -1485,8 +1430,6 @@ verboselogs==1.7 \ --hash=sha256:e33ddedcdfdafcb3a174701150430b11b46ceb64c2a9a26198c76a156568e427 # via # aio-run-runner - # envoy-distribution-repo - # envoy-github-abstract # envoy-github-release # envoy-gpg-sign yamllint==1.38.0 \ @@ -1731,3 +1674,9 @@ zstandard==0.25.0 \ --hash=sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551 \ --hash=sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01 # via envoy-base-utils + +# The following packages are considered to be unsafe in a requirements file: +setuptools==82.0.1 \ + --hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \ + --hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb + # via -r tools/base/requirements.in From ae4fee1c1dad386ab726e9bd1bba898e62ee19b8 Mon Sep 17 00:00:00 2001 From: Biren Roy Date: Fri, 15 May 2026 18:55:02 -0400 Subject: [PATCH 326/750] http2: Moves HTTP/2 GOAWAY load shed point handling to the ConnectionManager (#44736) As implemented, the `http2_server_go_away_on_dispatch` load shed point causes Envoy to send a single `GOAWAY` frame, with the `max_stream_id` set to the highest stream ID observed by the server at that point. This results in the server dropping any requests in flight at that moment, making the load shed point unusable in practice. This PR moves the load shed point to the connection manager, where it can initiate the graceful shutdown sequence described in RFC 9113 Section 6.8, by calling `ConnectionManagerImpl::sendGoAwayAndClose()`. For symmetry, I have also moved the `http2_server_go_away_and_close_on_dispatch` load shed point, maintaining the eager shutdown behavior. Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Biren Roy --- changelogs/current.yaml | 5 + source/common/http/conn_manager_impl.cc | 27 ++++- source/common/http/conn_manager_impl.h | 2 + source/common/http/http2/codec_impl.cc | 25 +++-- source/common/http/http2/codec_impl.h | 1 + source/common/runtime/runtime_features.cc | 1 + test/common/http/conn_manager_impl_test_3.cc | 103 ++++++++++++++++++ test/common/http/http2/codec_impl_test.cc | 4 + test/integration/BUILD | 2 +- test/integration/overload_integration_test.cc | 28 ++++- 10 files changed, 179 insertions(+), 19 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 204a97d93ba4d..1dd4dc0b494c7 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -43,6 +43,11 @@ minor_behavior_changes: graceful close against the reset. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.ssl_socket_report_connection_reset`` to ``false``. +- area: http2 + change: | + The GOAWAY load shed point is fixed to use a graceful two-phase shutdown sequence, to avoid + risk to client traffic. This behavioral change can be temporarily reverted by setting runtime + guard ``envoy.reloadable_features.http2_fix_goaway_loadshed_point`` to ``false``. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 77e44fe6fae18..d6940a569b90d 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -133,6 +133,10 @@ ConnectionManagerImpl::ConnectionManagerImpl( overload_manager.getLoadShedPoint(Server::LoadShedPointName::get().HcmDecodeHeaders)), hcm_ondata_creating_codec_( overload_manager.getLoadShedPoint(Server::LoadShedPointName::get().HcmCodecCreation)), + should_send_go_away_on_dispatch_(overload_manager.getLoadShedPoint( + Server::LoadShedPointName::get().H2ServerGoAwayOnDispatch)), + should_send_go_away_and_close_on_dispatch_(overload_manager.getLoadShedPoint( + Server::LoadShedPointName::get().H2ServerGoAwayAndCloseOnDispatch)), overload_stop_accepting_requests_ref_( overload_state_.getState(Server::OverloadActionNames::get().StopAcceptingRequests)), overload_disable_keepalive_ref_( @@ -155,6 +159,13 @@ ConnectionManagerImpl::ConnectionManagerImpl( ENVOY_LOG_ONCE_IF(trace, hcm_ondata_creating_codec_ == nullptr, "LoadShedPoint envoy.load_shed_points.hcm_ondata_creating_codec is not found. " "Is it configured?"); + ENVOY_LOG_ONCE_IF(trace, should_send_go_away_on_dispatch_ == nullptr, + "LoadShedPoint envoy.load_shed_points.http2_server_go_away_on_dispatch is not " + "found. Is it configured?"); + ENVOY_LOG_ONCE_IF( + trace, should_send_go_away_and_close_on_dispatch_ == nullptr, + "LoadShedPoint envoy.load_shed_points.http2_server_go_away_and_close_on_dispatch is not " + "found. Is it configured?"); } const ResponseHeaderMap& ConnectionManagerImpl::continueHeader() { @@ -514,6 +525,20 @@ Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool createCodec(data); } + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http2_fix_goaway_loadshed_point")) { + if (should_send_go_away_and_close_on_dispatch_ != nullptr && + should_send_go_away_and_close_on_dispatch_->shouldShedLoad()) { + sendGoAwayAndClose(/*graceful=*/false); + handleCodecOverloadError( + "Load shed point http2_server_go_away_and_close_on_dispatch triggered"); + return Network::FilterStatus::StopIteration; + } + if (should_send_go_away_on_dispatch_ != nullptr && + should_send_go_away_on_dispatch_->shouldShedLoad()) { + sendGoAwayAndClose(/*graceful=*/true); + } + } + bool redispatch; do { redispatch = false; @@ -794,7 +819,7 @@ void ConnectionManagerImpl::onDrainTimeout() { } void ConnectionManagerImpl::sendGoAwayAndClose(bool graceful) { - ENVOY_CONN_LOG(trace, "connection manager sendGoAwayAndClose was triggerred from filters.", + ENVOY_CONN_LOG(trace, "connection manager sendGoAwayAndClose was triggered.", read_callbacks_->connection()); if (go_away_sent_) { return; diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 0077c748672ad..f8e17265a1516 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -665,6 +665,8 @@ class ConnectionManagerImpl : Logger::Loggable, Server::ThreadLocalOverloadState& overload_state_; Server::LoadShedPoint* accept_new_http_stream_{nullptr}; Server::LoadShedPoint* hcm_ondata_creating_codec_{nullptr}; + Server::LoadShedPoint* should_send_go_away_on_dispatch_{nullptr}; + Server::LoadShedPoint* should_send_go_away_and_close_on_dispatch_{nullptr}; // References into the overload manager thread local state map. Using these lets us avoid a // map lookup in the hot path of processing each request. const Server::OverloadActionState& overload_stop_accepting_requests_ref_; diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 57155a0d2a178..52ec0649704c9 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -2447,17 +2447,20 @@ int ServerConnectionImpl::onHeader(int32_t stream_id, HeaderString&& name, Heade Http::Status ServerConnectionImpl::dispatch(Buffer::Instance& data) { // Make sure downstream outbound queue was not flooded by the upstream frames. RETURN_IF_ERROR(protocol_constraints_.checkOutboundFrameLimits()); - if (should_send_go_away_and_close_on_dispatch_ != nullptr && - should_send_go_away_and_close_on_dispatch_->shouldShedLoad()) { - ConnectionImpl::goAway(); - sent_go_away_on_dispatch_ = true; - return envoyOverloadError( - "Load shed point http2_server_go_away_and_close_on_dispatch triggered"); - } - if (should_send_go_away_on_dispatch_ != nullptr && !sent_go_away_on_dispatch_ && - should_send_go_away_on_dispatch_->shouldShedLoad()) { - ConnectionImpl::goAway(); - sent_go_away_on_dispatch_ = true; + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.http2_fix_goaway_loadshed_point")) { + if (should_send_go_away_and_close_on_dispatch_ != nullptr && + should_send_go_away_and_close_on_dispatch_->shouldShedLoad()) { + ConnectionImpl::goAway(); + sent_go_away_on_dispatch_ = true; + return envoyOverloadError( + "Load shed point http2_server_go_away_and_close_on_dispatch triggered"); + } + if (should_send_go_away_on_dispatch_ != nullptr && !sent_go_away_on_dispatch_ && + should_send_go_away_on_dispatch_->shouldShedLoad()) { + ConnectionImpl::goAway(); + sent_go_away_on_dispatch_ = true; + } } return ConnectionImpl::dispatch(data); } diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index d0cebbc36f384..9dfe6cbaa2337 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -887,6 +887,7 @@ class ServerConnectionImpl : public ServerConnection, public ConnectionImpl { // The action to take when a request header name contains underscore characters. envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action_; + // Remove when removing runtime feature `http2_fix_goaway_loadshed_point`. Server::LoadShedPoint* should_send_go_away_on_dispatch_{nullptr}; Server::LoadShedPoint* should_send_go_away_and_close_on_dispatch_{nullptr}; bool sent_go_away_on_dispatch_{false}; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 4b745f5aee55a..ff3a2e02c887f 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -65,6 +65,7 @@ RUNTIME_GUARD(envoy_reloadable_features_health_check_after_cluster_warming); RUNTIME_GUARD(envoy_reloadable_features_hide_transport_failure_reason_in_response_body); RUNTIME_GUARD(envoy_reloadable_features_http1_close_connection_on_zombie_stream_complete); RUNTIME_GUARD(envoy_reloadable_features_http2_discard_host_header); +RUNTIME_GUARD(envoy_reloadable_features_http2_fix_goaway_loadshed_point); RUNTIME_GUARD(envoy_reloadable_features_http_async_client_retry_respect_buffer_limits); RUNTIME_GUARD(envoy_reloadable_features_http_inspector_use_balsa_parser); // Delay deprecation and decommission until UHV is enabled. diff --git a/test/common/http/conn_manager_impl_test_3.cc b/test/common/http/conn_manager_impl_test_3.cc index 2e5affa1f9969..cbb50fa3838de 100644 --- a/test/common/http/conn_manager_impl_test_3.cc +++ b/test/common/http/conn_manager_impl_test_3.cc @@ -848,6 +848,12 @@ TEST_F(HttpConnectionManagerImplTest, CodecCreationLoadShedPointCanCloseConnecti EXPECT_CALL(overload_manager_, getLoadShedPoint(Server::LoadShedPointName::get().HcmDecodeHeaders)) .WillOnce(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().H2ServerGoAwayOnDispatch)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().H2ServerGoAwayAndCloseOnDispatch)) + .WillOnce(Return(nullptr)); setup(); @@ -874,6 +880,12 @@ TEST_F(HttpConnectionManagerImplTest, CodecCreationLoadShedPointBypasscheck) { EXPECT_CALL(overload_manager_, getLoadShedPoint(Server::LoadShedPointName::get().HttpDownstreamFilterCheck)) .WillOnce(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().H2ServerGoAwayOnDispatch)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().H2ServerGoAwayAndCloseOnDispatch)) + .WillOnce(Return(nullptr)); setup(); @@ -904,6 +916,12 @@ TEST_F(HttpConnectionManagerImplTest, DecodeHeaderLoadShedPointCanRejectNewStrea EXPECT_CALL(overload_manager_, getLoadShedPoint(Server::LoadShedPointName::get().HttpDownstreamFilterCheck)) .WillRepeatedly(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().H2ServerGoAwayOnDispatch)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().H2ServerGoAwayAndCloseOnDispatch)) + .WillOnce(Return(nullptr)); setup(); setupFilterChain(1, 0); @@ -938,6 +956,91 @@ TEST_F(HttpConnectionManagerImplTest, DecodeHeaderLoadShedPointCanRejectNewStrea ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true, "details"); } +TEST_F(HttpConnectionManagerImplTest, GoAwayLoadShedPoint) { + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.http2_fix_goaway_loadshed_point")) { + GTEST_SKIP(); + } + Server::MockLoadShedPoint goaway_point; + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HcmDecodeHeaders)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HcmCodecCreation)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HttpDownstreamFilterCheck)) + .WillRepeatedly(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().H2ServerGoAwayOnDispatch)) + .WillOnce(Return(&goaway_point)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().H2ServerGoAwayAndCloseOnDispatch)) + .WillOnce(Return(nullptr)); + + setup(); + setupFilterChain(1, 0); + + EXPECT_CALL(goaway_point, shouldShedLoad()).WillOnce(Return(true)); + + EXPECT_CALL(response_encoder_, encodeHeaders(_, true)) + .WillOnce(Invoke([](const ResponseHeaderMap& headers, bool) -> void { + EXPECT_EQ("200", headers.getStatusValue()); + })); + + // The client will see a warning GOAWAY, but no final GOAWAY. + EXPECT_CALL(*codec_, shutdownNotice()); + EXPECT_CALL(*codec_, goAway()).Times(0); + + startRequest(); + + // Clean up. + expectOnDestroy(); + + decoder_filters_[0]->callbacks_->streamInfo().setResponseCodeDetails(""); + decoder_filters_[0]->callbacks_->encodeHeaders( + ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true, "details"); +} + +TEST_F(HttpConnectionManagerImplTest, GoAwayAndCloseLoadShedPoint) { + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.http2_fix_goaway_loadshed_point")) { + GTEST_SKIP(); + } + Server::MockLoadShedPoint goaway_and_close_point; + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HcmDecodeHeaders)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HcmCodecCreation)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HttpDownstreamFilterCheck)) + .WillRepeatedly(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().H2ServerGoAwayOnDispatch)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().H2ServerGoAwayAndCloseOnDispatch)) + .WillOnce(Return(&goaway_and_close_point)); + + setup(); + + EXPECT_CALL(goaway_and_close_point, shouldShedLoad()).WillOnce(Return(true)); + + EXPECT_CALL(response_encoder_, encodeHeaders(_, _)).Times(0); + + // The client will see the warning GOAWAY and final GOAWAY immediately afterward. + EXPECT_CALL(*codec_, shutdownNotice()); + EXPECT_CALL(*codec_, goAway()); + + // Gives the connection manager an event to handle. + Buffer::OwnedImpl fake_input; + conn_manager_->onData(fake_input, false); + + // No cleanup because the filter chain is not even created. +} + TEST_F(HttpConnectionManagerImplTest, TestStopAllIterationAndBufferOnDecodingPathFirstFilter) { setup(SetupOpts().setTracing(false)); setUpEncoderAndDecoder(true, true); diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index c9808854c1f1b..657270b201f26 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -4592,6 +4592,7 @@ TEST_P(Http2CodecImplTest, ChunkProcessingShouldNotScheduleIfReadDisabled) { } TEST_P(Http2CodecImplTest, ServerDispatchLoadShedPointCanCauseServerToSendGoAway) { + Runtime::maybeSetRuntimeGuard("envoy.reloadable_features.http2_fix_goaway_loadshed_point", false); initialize(); ASSERT_EQ(0, server_stats_store_.counter("http2.goaway_sent").value()); @@ -4613,6 +4614,7 @@ TEST_P(Http2CodecImplTest, ServerDispatchLoadShedPointCanCauseServerToSendGoAway } TEST_P(Http2CodecImplTest, ServerDispatchLoadShedPointSendGoAwayAndClose) { + Runtime::maybeSetRuntimeGuard("envoy.reloadable_features.http2_fix_goaway_loadshed_point", false); expect_buffered_data_on_teardown_ = true; initialize(); @@ -4633,6 +4635,8 @@ TEST_P(Http2CodecImplTest, ServerDispatchLoadShedPointSendGoAwayAndClose) { } TEST_P(Http2CodecImplTest, ServerDispatchLoadShedPointsAreOnlyConsultedOncePerDispatch) { + Runtime::maybeSetRuntimeGuard("envoy.reloadable_features.http2_fix_goaway_loadshed_point", false); + initialize(); int times_shed_load_goaway_invoked = 0; diff --git a/test/integration/BUILD b/test/integration/BUILD index 9770089022f10..97f4e0a491f5a 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1845,7 +1845,7 @@ envoy_cc_test( size = "large", srcs = ["overload_integration_test.cc"], rbe_pool = "6gig", - shard_count = 4, + shard_count = 8, tags = [ "cpu:3", ], diff --git a/test/integration/overload_integration_test.cc b/test/integration/overload_integration_test.cc index 693607e47d7bf..edd8574013898 100644 --- a/test/integration/overload_integration_test.cc +++ b/test/integration/overload_integration_test.cc @@ -1208,6 +1208,11 @@ TEST_P(LoadShedPointIntegrationTest, Http2ServerDispatchSendsGoAwayCompletingPen if (downstreamProtocol() != Http::CodecClient::Type::HTTP2) { return; } + config_helper_.addConfigModifier( + [=](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + cm) -> void { + cm.mutable_drain_timeout()->MergeFrom(ProtobufUtil::TimeUtil::SecondsToDuration(7)); + }); autonomous_upstream_ = true; initializeOverloadManager( TestUtility::parseYaml(R"EOF( @@ -1236,13 +1241,24 @@ TEST_P(LoadShedPointIntegrationTest, Http2ServerDispatchSendsGoAwayCompletingPen first_request_encoder.encodeData(first_request_body, true); ASSERT_TRUE(first_request_decoder->waitForEndStream()); + // This is the initial GOAWAY, with max stream ID. EXPECT_TRUE(codec_client_->sawGoAway()); + + // This waits for the final GOAWAY, with a real stream ID. test_server_->waitForCounter("http2.goaway_sent", Eq(1)); - // The GOAWAY gets submitted with the first created stream as the last stream - // that will be processed on this connection, so the second stream's frames - // are ignored. - EXPECT_FALSE(second_request_decoder->complete()); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http2_fix_goaway_loadshed_point")) { + // Because the load shed operation uses a two-phase GOAWAY, a request initiated before the drain + // timer fires will be processed as usual. + EXPECT_TRUE(second_request_decoder->waitForEndStream()); + + ASSERT_TRUE(codec_client_->waitForDisconnect()); + } else { + // The GOAWAY gets submitted with the first created stream as the last stream + // that will be processed on this connection, so the second stream's frames + // are ignored. + EXPECT_FALSE(second_request_decoder->complete()); + } updateResource(0.80); test_server_->waitForGauge( @@ -1282,9 +1298,9 @@ TEST_P(LoadShedPointIntegrationTest, Http2ServerDispatchSendsGoAwayAndClosesConn ASSERT_TRUE(codec_client_->waitForDisconnect()); EXPECT_TRUE(codec_client_->sawGoAway()); test_server_->waitForCounter("http2.goaway_sent", Eq(1)); - test_server_->waitForCounter("http.config_test.downstream_rq_overload_close", Eq(1)); - // The second request will not complete. + // The second request is ignored and will not complete, since the connection manager stops network + // filter iteration. EXPECT_FALSE(second_request_decoder->complete()); } From 985f9c7173242d825b5805c9d7fc67cf79eb0d1d Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Sun, 17 May 2026 11:34:37 +0200 Subject: [PATCH 327/750] dns_resolver: fix macOS build for Hickory by linking SystemConfiguration (#45104) ## Description The `hickory-resolver` RIST crate's `system-config` feature transitively pulls in the `system-configuration` crate, which links against Apple's SystemConfiguration framework. Envoy does not link this framework by default on Apple platforms, leading to undefined-symbol errors when linking targets that depend on the Hickory extension on macOS. This PR adds a conditional `linkopts` on `hickory_dns_lib` that links the SystemConfiguration framework on Apple platforms. The flag propagates transitively through `cc_library` deps to any binary or test that pulls in the Hickory extension. Fixes #45061 --- **Commit Message:** dns_resolver: fix macOS build for Hickory by linking SystemConfiguration **Additional Description:** Added a conditional `linkopts` on `hickory_dns_lib` that links the SystemConfiguration framework on Apple platforms **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** Added **Release Notes:** Added Signed-off-by: Rohit Agrawal --- changelogs/current.yaml | 6 ++++++ source/extensions/network/dns_resolver/hickory/BUILD | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 1dd4dc0b494c7..cf96135a63b28 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -102,6 +102,12 @@ bug_fixes: Fixed ``Illegal ambiguous match`` error when building contrib targets with ``--config=boringssl-fips`` on aarch64 by restricting the ``using_boringssl_fips`` branch of ``SELECTED_CONTRIB_EXTENSIONS`` to ``linux_x86_64``. Mirrors the approach taken by #44661 for ``aws-lc``. +- area: dns_resolver + change: | + Fixed macOS build failure (#45061) for the Hickory DNS resolver by linking + Apple's ``SystemConfiguration`` framework. The framework is required by the + ``system-configuration`` crate that ``hickory-resolver`` pulls in via its + ``system-config`` feature. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/network/dns_resolver/hickory/BUILD b/source/extensions/network/dns_resolver/hickory/BUILD index 857345201610c..31864c86f786b 100644 --- a/source/extensions/network/dns_resolver/hickory/BUILD +++ b/source/extensions/network/dns_resolver/hickory/BUILD @@ -13,6 +13,14 @@ envoy_cc_library( name = "hickory_dns_lib", srcs = ["hickory_dns_impl.cc"], hdrs = ["hickory_dns_impl.h"], + linkopts = select({ + # Apple's SystemConfiguration framework is required by the + # system-configuration crate, which is pulled in transitively by + # hickory-resolver's `system-config` feature. + # https://github.com/envoyproxy/envoy/issues/45061 + "//bazel:apple": ["-Wl,-framework,SystemConfiguration"], + "//conditions:default": [], + }), deps = [ "//envoy/event:dispatcher_interface", "//envoy/network:dns_interface", From abbadd905fa486ff1085cf3bbe4f3b73eb6dd8e0 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Sun, 17 May 2026 11:34:59 +0200 Subject: [PATCH 328/750] dns_resolver: fix Hickory cancel leak and dispatcher-post UAF (#45105) ## Description This PR fixes two related lifecycle bugs in the Hickory DNS resolver shell. `ActiveDnsQuery::cancel()` only flipped a `cancelled_` flag and forwarded to the Rust cancel FFI. It did not erase the pending query from the resolver's map, decrement the `pending_resolutions` gauge, or free the `HickoryPendingResolution` heap object. The RUST task observed the cancel and skipped its result callback, so `onResolveComplete()` never ran for the cancelled query and the shell state was leaked. Under the dynamic forward proxy timeout scenario in #45058, every timeout grew the gauge by one and leaked one heap object. `cancel()` now performs the full local cleanup synchronously (drop the Rust query box, decrement the gauge, erase the map entry, `delete this`), matching the contract followed by the Apple DNS resolver. The ABI completion callback runs on a Tokio worker thread and posts a lambda to the dispatcher capturing a raw pointer to the resolver. If the resolver's last `shared_ptr` reference was released between the post and the dispatcher draining the queue, the captured pointer dangled and the lambda's dereference was a use-after-free. `HickoryDnsResolver` now derives from `std::enable_shared_from_this` and the posted lambda captures `weak_from_this()`; a failed `lock()` inside the lambda skips the callback without touching freed memory. The pre-existing `shutting_down_` flag is kept as a fast-path optimization to avoid the response copy when destruction is already in progress. After the cancel fix, cancelled queries are no longer in the map by the time `onResolveComplete()` looks them up, so the dead `cancelled_` field, the `if (pending->cancelled_)` branch in `onResolveComplete()`, and the destructor's matching skip are removed. `cancel()` also asserts `dispatcher_.isThreadSafe()` to enforce the DnsResolver threading contract. Fix https://github.com/envoyproxy/envoy/issues/45058 --- **Commit Message:** dns_resolver: fix Hickory cancel leak and dispatcher-post UAF **Additional Description:** **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** Added **Release Notes:** Added Signed-off-by: Rohit Agrawal --- changelogs/current.yaml | 12 ++ .../dns_resolver/hickory/hickory_dns_impl.cc | 78 +++++++---- .../dns_resolver/hickory/hickory_dns_impl.h | 10 +- .../hickory/hickory_dns_impl_test.cc | 126 +++++++++++++++--- tools/spelling/spelling_dictionary.txt | 2 + 5 files changed, 183 insertions(+), 45 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index cf96135a63b28..8d9f5dc0f662a 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -97,6 +97,18 @@ bug_fixes: resulting binary "plaintext" was written back into the ``Cookie:`` request header and tripped a ``HeaderString`` validation assert. Such plaintexts are now rejected and the original cookie value is preserved, matching the behavior already documented for the explicit decryption-failure case. +- area: dns_resolver + change: | + Fixed a memory leak in the Hickory DNS resolver where ``ActiveDnsQuery::cancel()`` + did not free the pending query state or decrement the ``pending_resolutions`` gauge. Cancelled + queries now release their shell object, Rust-side query box, and gauge tick synchronously, + matching the contract followed by the c-ares and Apple DNS resolvers. +- area: dns_resolver + change: | + Fixed a UAF in the Hickory DNS resolver where the dispatcher lambda posted by the + Rust-thread completion callback captured a raw pointer to the resolver. If the resolver was + destroyed before the dispatcher drained the lambda, dereferencing the pointer was undefined + behavior. The lambda now captures a ``std::weak_ptr`` and locks it before touching the resolver. - area: build change: | Fixed ``Illegal ambiguous match`` error when building contrib targets with ``--config=boringssl-fips`` diff --git a/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.cc b/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.cc index 45a95da0fe552..73748d5d3243c 100644 --- a/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.cc +++ b/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.cc @@ -89,8 +89,22 @@ HickoryPendingResolution::HickoryPendingResolution(HickoryDnsResolver& parent, : callback_(std::move(callback)), query_id_(query_id), dns_name_(dns_name), parent_(parent) {} void HickoryPendingResolution::cancel(CancelReason) { - cancelled_ = true; + ASSERT(parent_.dispatcher_.isThreadSafe()); + + // Drop the Rust-side query state. The Rust cancel implementation flips the per-query + // `AtomicBool` so any in-flight Tokio task observes the cancellation and skips invoking + // the resolve-complete callback. The resolver pointer is unused by the cancel FFI; only + // the query pointer is consumed. parent_.config_->on_dns_resolve_cancel_(parent_.resolver_module_ptr_, query_module_ptr_); + query_module_ptr_ = nullptr; + + // Reconcile shell state synchronously so the gauge tracks reality and the + // `HickoryPendingResolution` object is not leaked. Per the `ActiveDnsQuery::cancel()` + // contract, callers must treat the pointer as invalidated after this returns. A late + // `onResolveComplete` for this `query_id` will find no map entry and safely no-op. + parent_.stats_.pending_resolutions_.dec(); + parent_.pending_queries_.erase(query_id_); + delete this; } // -- HickoryDnsResolver ------------------------------------------------------- @@ -110,25 +124,27 @@ HickoryDnsResolver::HickoryDnsResolver(HickoryDnsResolverConfigSharedPtr config, } HickoryDnsResolver::~HickoryDnsResolver() { - // Step 1: Set the C++ shutdown flag so the ABI callback called from `Tokio` threads - // will not post to the dispatcher. This provides TSAN-visible synchronization since - // the Rust code is not instrumented by TSAN. + // Step 1: Set the C++ shutdown flag so the ABI callback called from Tokio threads + // skips posting to the dispatcher. The posted lambda also locks a `weak_ptr` to this + // resolver as the final use-after-free guard. shutting_down_.store(true, std::memory_order_release); - // Step 2: Destroy the module resolver, which shuts down the `Tokio` runtime and - // blocks until all worker threads have exited. + // Step 2: Destroy the module resolver. The Rust `Drop` impl signals its own + // shutting-down flag and then performs `runtime.shutdown_timeout(5s)`, waiting up to + // five seconds for Tokio tasks to finish; any tasks still running when the timeout + // expires are dropped. config_->on_dns_resolver_destroy_(resolver_module_ptr_); - // Step 3: Free Rust-side query objects for all remaining pending queries, and delete - // the C++ pending resolution objects. Already-cancelled queries have their Rust-side - // objects freed by the cancel() call, so only free non-cancelled ones. Decrement the - // pending_resolutions gauge for each query since their callbacks will never arrive. + // Step 3: Free Rust-side query objects for all remaining pending queries and delete + // the C++ pending resolution objects. Cancelled queries are not present here: they + // were removed and freed by `HickoryPendingResolution::cancel()`. Decrement the + // `pending_resolutions` gauge for each remaining query since their callbacks will + // never arrive. for (auto& [id, pending] : pending_queries_) { stats_.pending_resolutions_.dec(); - if (!pending->cancelled_) { - // Safe: on_dns_resolve_cancel_ does not dereference the resolver pointer. - config_->on_dns_resolve_cancel_(resolver_module_ptr_, pending->query_module_ptr_); - } + // Safe: the Rust cancel FFI ignores the resolver pointer; only the query box is + // consumed. See `envoy_dynamic_module_on_dns_resolve_cancel` in the Rust SDK. + config_->on_dns_resolve_cancel_(resolver_module_ptr_, pending->query_module_ptr_); delete pending; } pending_queries_.clear(); @@ -190,12 +206,6 @@ void HickoryDnsResolver::onResolveComplete(uint64_t query_id, stats_.resolve_total_.inc(); stats_.pending_resolutions_.dec(); - if (pending->cancelled_) { - ENVOY_LOG(debug, "dropping cancelled query [{}]", pending->dns_name_); - delete pending; - return; - } - const auto envoy_status = status == envoy_dynamic_module_type_dns_resolution_status_Completed ? ResolutionStatus::Completed : ResolutionStatus::Failure; @@ -208,8 +218,8 @@ void HickoryDnsResolver::onResolveComplete(uint64_t query_id, response.size()); } - // Free the Rust-side query object. The cancel ABI function takes ownership and drops it. - // Calling cancel on an already-completed query is harmless (it only sets the AtomicBool). + // Free the Rust-side query object now that its task has produced a result. The cancel + // ABI function takes ownership of the boxed query and drops it. config_->on_dns_resolve_cancel_(resolver_module_ptr_, pending->query_module_ptr_); pending->query_module_ptr_ = nullptr; @@ -234,6 +244,7 @@ void HickoryDnsResolver::chargeGetAddrInfoErrorStats(absl::string_view details) absl::StatusOr HickoryDnsResolverFactory::createDnsResolver( Event::Dispatcher& dispatcher, Api::Api& api, const envoy::config::core::v3::TypedExtensionConfig& typed_config) const { + ASSERT(dispatcher.isThreadSafe()); envoy::extensions::network::dns_resolver::hickory::v3::HickoryDnsResolverConfig proto_config; RETURN_IF_NOT_OK(Envoy::MessageUtil::unpackTo(typed_config.typed_config(), proto_config)); @@ -260,12 +271,22 @@ void envoy_dynamic_module_callback_dns_resolve_complete( auto* resolver = const_cast( static_cast(resolver_envoy_ptr)); - // Check the C++ shutdown flag before accessing any resolver state. This provides - // TSAN-visible synchronization for accesses from `Tokio` worker threads. + // Fast path: if the resolver is already shutting down, skip the response copy entirely. + // The Rust task is expected to observe the Rust-side shutdown flag and bail out before + // reaching this callback, but this guard also covers the narrow window where the C++ + // destructor has set the flag while the callback is in flight on a Tokio thread. if (resolver->shutting_down_.load(std::memory_order_acquire)) { return; } + // The resolver storage is kept alive across this FFI call because + // `on_dns_resolver_destroy_` (invoked from the C++ destructor) blocks on + // `runtime.shutdown_timeout(5s)` until in-flight FFI calls return. Capture a + // `weak_ptr` so the posted lambda does not extend the resolver's lifetime; if the + // dispatcher fails to drain the lambda before the resolver is destroyed, the + // lambda's `lock()` returns `nullptr` and the callback is safely skipped. + std::weak_ptr weak_resolver = resolver->weak_from_this(); + const std::string details_str = (details.ptr != nullptr && details.length > 0) ? std::string(details.ptr, details.length) : std::string(); @@ -283,8 +304,13 @@ void envoy_dynamic_module_callback_dns_resolve_complete( } } - resolver->dispatcher_.post([resolver, query_id, status, details = std::move(details_str), + resolver->dispatcher_.post([weak_resolver = std::move(weak_resolver), query_id, status, + details = std::move(details_str), response = std::move(response)]() mutable { - resolver->onResolveComplete(query_id, status, details, std::move(response)); + // If the resolver was destroyed between `post()` and now, `lock()` returns `nullptr` + // and the lambda exits safely without touching freed memory. + if (auto resolver_shared = weak_resolver.lock()) { + resolver_shared->onResolveComplete(query_id, status, details, std::move(response)); + } }); } diff --git a/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.h b/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.h index e8e41daa722d4..80f967790886a 100644 --- a/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.h +++ b/source/extensions/network/dns_resolver/hickory/hickory_dns_impl.h @@ -102,7 +102,6 @@ class HickoryPendingResolution : public ActiveDnsQuery { DnsResolver::ResolveCb callback_; const uint64_t query_id_; const std::string dns_name_; - bool cancelled_ = false; HickoryDnsResolver& parent_; }; @@ -111,8 +110,15 @@ class HickoryPendingResolution : public ActiveDnsQuery { * This is a thread-safe resolver: resolve() is called on the dispatcher thread, and the * module may deliver results from any thread. The shell posts results back to the correct * dispatcher thread. + * + * Inherits from `std::enable_shared_from_this` so the ABI completion callback can capture + * a `std::weak_ptr` into the lambda it posts to the dispatcher. Locking the weak pointer + * inside the lambda detects whether the resolver has been destroyed in the window between + * posting and execution, avoiding a use-after-free on the dispatcher drain path. */ -class HickoryDnsResolver : public DnsResolver, protected Logger::Loggable { +class HickoryDnsResolver : public DnsResolver, + public std::enable_shared_from_this, + protected Logger::Loggable { public: HickoryDnsResolver(HickoryDnsResolverConfigSharedPtr config, Event::Dispatcher& dispatcher, Stats::Scope& root_scope); diff --git a/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc b/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc index 0e89b8ae36ff2..5e6b9cd2cd6d4 100644 --- a/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc +++ b/test/extensions/network/dns_resolver/hickory/hickory_dns_impl_test.cc @@ -49,6 +49,9 @@ class HickoryDnsImplTest : public testing::Test { EXPECT_EQ(timeouts, stats_store_.counter("dns.hickory.timeouts").value()); } + // The resolver assigns query IDs starting at 1 and increments monotonically. + static constexpr uint64_t kFirstQueryId = 1; + Stats::TestUtil::TestStore stats_store_; Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; @@ -241,8 +244,19 @@ TEST_F(HickoryDnsImplTest, CancelQuery) { }); EXPECT_NE(query, nullptr); + // The pending_resolutions gauge tracks the live query. + EXPECT_EQ(1, stats_store_ + .gauge("dns.hickory.pending_resolutions", Stats::Gauge::ImportMode::NeverImport) + .value()); + query->cancel(ActiveDnsQuery::CancelReason::QueryAbandoned); + // cancel() must reconcile the gauge synchronously and free the pending object. This is + // the regression test for https://github.com/envoyproxy/envoy/issues/45058. + EXPECT_EQ(0, stats_store_ + .gauge("dns.hickory.pending_resolutions", Stats::Gauge::ImportMode::NeverImport) + .value()); + // Resolve a second query to verify the resolver is still functional after cancel. bool second_callback_called = false; auto* query2 = resolver_->resolve( @@ -420,7 +434,8 @@ TEST_F(HickoryDnsImplTest, CancelledQueryResultIsDropped) { query->cancel(ActiveDnsQuery::CancelReason::QueryAbandoned); // Issue a second query and wait for it to complete. By the time it finishes, the - // first query's async result has also arrived and been dropped by the dispatcher. + // first query's async result has also arrived and been dropped because cancel() + // removed the entry from pending_queries_. bool second_callback_called = false; resolver_->resolve("localhost", DnsLookupFamily::All, [this, &second_callback_called](DnsResolver::ResolutionStatus, @@ -432,6 +447,11 @@ TEST_F(HickoryDnsImplTest, CancelledQueryResultIsDropped) { dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); EXPECT_TRUE(second_callback_called); EXPECT_FALSE(callback_called); + // Only the second query produces a resolve_total bump; the cancelled query does not. + EXPECT_EQ(1, stats_store_.counter("dns.hickory.resolve_total").value()); + EXPECT_EQ(0, stats_store_ + .gauge("dns.hickory.pending_resolutions", Stats::Gauge::ImportMode::NeverImport) + .value()); } TEST_F(HickoryDnsImplTest, OnResolveCompleteForCancelledQuery) { @@ -439,8 +459,9 @@ TEST_F(HickoryDnsImplTest, OnResolveCompleteForCancelledQuery) { auto* hickory_resolver = dynamic_cast(resolver_.get()); ASSERT_NE(hickory_resolver, nullptr); - // Issue a query and immediately cancel it. The query remains in pending_queries_ with - // cancelled_ set to true. + // Issue a query and immediately cancel it. cancel() removes the entry from + // `pending_queries_` synchronously, so a late `onResolveComplete` for the same + // query ID must safely no-op without invoking the user callback. bool callback_called = false; auto* query = resolver_->resolve("localhost", DnsLookupFamily::All, @@ -449,13 +470,14 @@ TEST_F(HickoryDnsImplTest, OnResolveCompleteForCancelledQuery) { EXPECT_NE(query, nullptr); query->cancel(ActiveDnsQuery::CancelReason::QueryAbandoned); - // Simulate the `Tokio` task delivering a result for the cancelled query. The query ID - // is 1 (first query issued on a fresh resolver). This exercises the cancelled path in - // onResolveComplete where the result is dropped without invoking the callback. - hickory_resolver->onResolveComplete(1, envoy_dynamic_module_type_dns_resolution_status_Completed, - "resolved", {}); + // Simulate the Tokio task delivering a late result for the cancelled query. The + // lookup must hit the unknown-id early return because cancel() removed the entry. + hickory_resolver->onResolveComplete( + kFirstQueryId, envoy_dynamic_module_type_dns_resolution_status_Completed, "resolved", {}); EXPECT_FALSE(callback_called); + // resolve_total is not incremented for cancelled queries. + EXPECT_EQ(0, stats_store_.counter("dns.hickory.resolve_total").value()); } TEST_F(HickoryDnsImplTest, DestroyWithPendingQueries) { @@ -469,9 +491,77 @@ TEST_F(HickoryDnsImplTest, DestroyWithPendingQueries) { [](DnsResolver::ResolutionStatus, absl::string_view, std::list&&) {}); } - // Destroy the resolver immediately. The destructor should cancel all pending queries - // and shut down the `Tokio` runtime cleanly. + // Destroy the resolver immediately. The destructor cancels remaining queries, shuts + // down the Tokio runtime, and decrements the gauge for each abandoned query. resolver_.reset(); + EXPECT_EQ(0, stats_store_ + .gauge("dns.hickory.pending_resolutions", Stats::Gauge::ImportMode::NeverImport) + .value()); +} + +// Regression test for https://github.com/envoyproxy/envoy/issues/45058 with high +// cancel volume. The DFP scenario in the issue cancels a query for every timeout, and +// previously each cancel leaked one pending_resolutions gauge tick and one heap object. +TEST_F(HickoryDnsImplTest, RepeatedCancelDoesNotLeakGauge) { + initialize(); + + constexpr int kIterations = 50; + for (int i = 0; i < kIterations; ++i) { + auto* query = resolver_->resolve( + "localhost", DnsLookupFamily::All, + [](DnsResolver::ResolutionStatus, absl::string_view, std::list&&) { + FAIL() << "callback must not fire for a cancelled query"; + }); + ASSERT_NE(query, nullptr); + query->cancel(ActiveDnsQuery::CancelReason::Timeout); + } + + EXPECT_EQ(0, stats_store_ + .gauge("dns.hickory.pending_resolutions", Stats::Gauge::ImportMode::NeverImport) + .value()); + EXPECT_EQ(0, stats_store_.counter("dns.hickory.resolve_total").value()); +} + +// Regression test for a use-after-free in the ABI callback. If the resolver is destroyed +// after the callback has copied response data and posted a lambda to the dispatcher but +// before the dispatcher runs that lambda, the lambda must not dereference a freed +// resolver. The fix uses `std::weak_ptr` to detect destruction inside the lambda. +TEST_F(HickoryDnsImplTest, ResolverDestroyedBeforePostedCallbackRuns) { + initialize(); + auto* hickory_resolver = dynamic_cast(resolver_.get()); + ASSERT_NE(hickory_resolver, nullptr); + + // Issue a query so the first query ID is registered in pending_queries_. + bool callback_called = false; + resolver_->resolve("localhost", DnsLookupFamily::All, + [&callback_called](DnsResolver::ResolutionStatus, absl::string_view, + std::list&&) { callback_called = true; }); + + // Invoke the ABI callback directly to deterministically reproduce the post sequence + // without depending on Tokio timing. This posts a lambda capturing a weak_ptr. + const std::string addr_str = "127.0.0.1:0"; + envoy_dynamic_module_type_dns_address addr; + addr.address_ptr = addr_str.c_str(); + addr.address_length = addr_str.size(); + addr.ttl_seconds = 60; + + envoy_dynamic_module_type_module_buffer details_buf; + details_buf.ptr = nullptr; + details_buf.length = 0; + + envoy_dynamic_module_callback_dns_resolve_complete( + static_cast(hickory_resolver), kFirstQueryId, + envoy_dynamic_module_type_dns_resolution_status_Completed, details_buf, &addr, 1); + + // Destroy the resolver before the dispatcher processes the queued lambda. Without + // weak_ptr, the lambda's captured raw pointer would dangle and dereferencing it would + // be undefined behavior (ASan would catch the use-after-free). + resolver_.reset(); + + // Drain the dispatcher. The lambda's weak_ptr.lock() returns nullptr, so the lambda + // exits without touching the freed resolver and the user callback is not invoked. + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + EXPECT_FALSE(callback_called); } TEST_F(HickoryDnsImplTest, StatsNotFoundOnDirectCallback) { @@ -586,6 +676,12 @@ TEST_F(HickoryDnsImplTest, StatsOnCancelledQuery) { EXPECT_NE(query, nullptr); query->cancel(ActiveDnsQuery::CancelReason::QueryAbandoned); + // cancel() reconciles the gauge synchronously; no need to drive the dispatcher or + // destroy the resolver to observe the decrement. + EXPECT_EQ(0, stats_store_ + .gauge("dns.hickory.pending_resolutions", Stats::Gauge::ImportMode::NeverImport) + .value()); + // Resolve a second query and wait for it to complete. bool second_callback_called = false; resolver_->resolve("localhost", DnsLookupFamily::All, @@ -597,16 +693,12 @@ TEST_F(HickoryDnsImplTest, StatsOnCancelledQuery) { dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); EXPECT_TRUE(second_callback_called); - // The second query is guaranteed to complete. The cancelled query's `Tokio` task may or may - // not have posted a result before seeing the cancel flag, so resolve_total is at least 1. - EXPECT_GE(stats_store_.counter("dns.hickory.resolve_total").value(), 1); + // Exactly one query completed (the second one). The cancelled query never produces a + // resolve_total bump. + EXPECT_EQ(1, stats_store_.counter("dns.hickory.resolve_total").value()); EXPECT_EQ(0, stats_store_.counter("dns.hickory.not_found").value()); EXPECT_EQ(0, stats_store_.counter("dns.hickory.get_addr_failure").value()); EXPECT_EQ(0, stats_store_.counter("dns.hickory.timeouts").value()); - - // Destroying the resolver ensures that any remaining pending queries have their - // pending_resolutions gauge decremented in the destructor. - resolver_.reset(); EXPECT_EQ(0, stats_store_ .gauge("dns.hickory.pending_resolutions", Stats::Gauge::ImportMode::NeverImport) .value()); diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index f9dcb8751fb9f..e360e7154626e 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -214,6 +214,7 @@ FB FCDS FCM FFFF +FFI FIN FIPS FIRSTHDR @@ -1528,6 +1529,7 @@ tokenize tokenized tokenizes tokenizing +tokio toolchain tos traceid From be865a253bd9a2550b187f491c0e7b282448a4a8 Mon Sep 17 00:00:00 2001 From: Hai <20416005+yuehaii@users.noreply.github.com> Date: Mon, 18 May 2026 14:50:16 +0800 Subject: [PATCH 329/750] fix: clear expired oauth2 token on fetch failure (#44753) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **_Commit Message:_** envoy.http.injected_credentials.oauth2: clear stale token when cached token is expired on fetch failure. **_Additional Description:_** when envoy's oauth2 credential injector envoy.http.injected_credentials.oauth2 is configured with client credentials, it periodically fetches a bearer token and caches it in thread-local storage. the token is refreshed at half of its expiry interval via a timer. if the upstream oauth2 server becomes unavailable or suffer network issue, the fetch fails and the filter retries. however, the previously cached token remains in tls regardless of whether it has already expired. as a result, envoy continues to inject the expired token into proxied requests even after its life time has passed, which causes downstream services to reject those requests with 401 unauthorized, a silent, hard-to-debug failure. **_Risk Level:_** low **_Testing:_** > bazel test //test/extensions/http/credential_injector/oauth2:token_provider_test --test_filter="TokenProvider.FetchFailureInjectsStaleTokenWhenNotExpired:TokenProvider.FetchFailureClearsExpiredTokenAndInjectFails" --test_output=all --copt="-Wno-unguarded-availability-new" > INFO: Analyzed target //test/extensions/http/credential_injector/oauth2:token_provider_test (548 packages loaded, 30600 targets configured). > [3,100 / 4,426] 3 actions, 2 running > … > [3,920 / 4,426] 2 actions, 1 running > [3,920 / 4,426] 3 actions, 2 running > INFO: From Testing //test/extensions/http/credential_injector/oauth2:token_provider_test: > ==================== Test output for //test/extensions/http/credential_injector/oauth2:token_provider_test: > Note: Google Test filter = TokenProvider.FetchFailureInjectsStaleTokenWhenNotExpired:TokenProvider.FetchFailureClearsExpiredTokenAndInjectFails > [==========] Running 2 tests from 1 test suite. > [----------] Global test environment set-up. > [----------] 2 tests from TokenProvider > [ RUN ] TokenProvider.FetchFailureInjectsStaleTokenWhenNotExpired > [2026-04-30 01:13:00.801][9317955][error][credential_injector] [source/extensions/http/injected_credentials/oauth2/token_provider.cc:91] asyncGetAccessToken: OAuth cluster not found. Retrying in 5 seconds. > [2026-04-30 01:13:00.802][9317955][error][credential_injector] [source/extensions/http/injected_credentials/oauth2/token_provider.cc:124] onGetAccessTokenFailure: Failed to get access token > [ OK ] TokenProvider.FetchFailureInjectsStaleTokenWhenNotExpired (60 ms) > [ RUN ] TokenProvider.FetchFailureClearsExpiredTokenAndInjectFails > [2026-04-30 01:13:00.847][9317955][error][credential_injector] [source/extensions/http/injected_credentials/oauth2/token_provider.cc:91] asyncGetAccessToken: OAuth cluster not found. Retrying in 5 seconds. > [2026-04-30 01:13:00.847][9317955][error][credential_injector] [source/extensions/http/injected_credentials/oauth2/token_provider.cc:124] onGetAccessTokenFailure: Failed to get access token > [2026-04-30 01:13:00.847][9317955][error][credential_injector] [source/extensions/http/injected_credentials/oauth2/client_credentials_impl.cc:18] Failed to get oauth2 token from token provider > [ OK ] TokenProvider.FetchFailureClearsExpiredTokenAndInjectFails (43 ms) > [----------] 2 tests from TokenProvider (104 ms total) > > [----------] Global test environment tear-down > [==========] 2 tests from 1 test suite ran. (104 ms total) > [ PASSED ] 2 tests. **_Release Notes:_** Fix: envoy.http.injected_credentials.oauth2, clear stale token when cached token is expired on fetch failure. Fixes #44674 --------- Signed-off-by: hai.yue <20416005+yuehaii@users.noreply.github.com> --- .../oauth2/token_provider.cc | 11 +++ .../oauth2/token_provider.h | 3 + .../http/credential_injector/oauth2/BUILD | 4 + .../oauth2/token_provider_test.cc | 86 +++++++++++++++++++ 4 files changed, 104 insertions(+) diff --git a/source/extensions/http/injected_credentials/oauth2/token_provider.cc b/source/extensions/http/injected_credentials/oauth2/token_provider.cc index e549fd600a420..35b3ba025a1f1 100644 --- a/source/extensions/http/injected_credentials/oauth2/token_provider.cc +++ b/source/extensions/http/injected_credentials/oauth2/token_provider.cc @@ -109,6 +109,7 @@ void TokenProvider::onGetAccessTokenSuccess(const std::string& access_token, tls_->set( [value](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { return value; }); + token_expiry_time_ = dispatcher_->timeSource().monotonicTime() + expires_in; stats_.token_fetched_.inc(); ENVOY_LOG(debug, "onGetAccessTokenSuccess: Token fetched successfully, expires in {} seconds.", expires_in.count()); @@ -134,6 +135,16 @@ void TokenProvider::onGetAccessTokenFailure(FailureReason failure_reason) { stats_.token_fetch_failed_on_bad_response_code_.inc(); break; } + + // clear the cached expired token, so that requests are not forwarded with a stale token. + if (dispatcher_->timeSource().monotonicTime() >= token_expiry_time_) { + ENVOY_LOG(warn, "onGetAccessTokenFailure: Cached token has expired, clearing it."); + ThreadLocalOauth2ClientCredentialsTokenSharedPtr empty( + new ThreadLocalOauth2ClientCredentialsToken("")); + tls_->set( + [empty](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { return empty; }); + } + if (!retry) { return; } diff --git a/source/extensions/http/injected_credentials/oauth2/token_provider.h b/source/extensions/http/injected_credentials/oauth2/token_provider.h index dac3382e1e3f8..cac0ff9e21dea 100644 --- a/source/extensions/http/injected_credentials/oauth2/token_provider.h +++ b/source/extensions/http/injected_credentials/oauth2/token_provider.h @@ -1,7 +1,9 @@ #pragma once +#include #include +#include "envoy/common/time.h" #include "envoy/extensions/http/injected_credentials/oauth2/v3/oauth2.pb.h" #include "envoy/extensions/http/injected_credentials/oauth2/v3/oauth2.pb.validate.h" #include "envoy/stats/stats_macros.h" @@ -80,6 +82,7 @@ class TokenProvider : public Common::SecretReader, } std::string token_; + MonotonicTime token_expiry_time_; const Common::SecretReaderConstSharedPtr secret_reader_; ThreadLocal::SlotPtr tls_; std::unique_ptr oauth2_client_; diff --git a/test/extensions/http/credential_injector/oauth2/BUILD b/test/extensions/http/credential_injector/oauth2/BUILD index 257f9b5e48b2e..3d1f8289ae0ea 100644 --- a/test/extensions/http/credential_injector/oauth2/BUILD +++ b/test/extensions/http/credential_injector/oauth2/BUILD @@ -28,13 +28,17 @@ envoy_extension_cc_test( extension_names = ["envoy.filters.http.credential_injector"], rbe_pool = "6gig", deps = [ + "//source/common/http:headers_lib", "//source/extensions/http/injected_credentials/common:secret_reader_lib", + "//source/extensions/http/injected_credentials/oauth2:client_credentials_lib", "//source/extensions/http/injected_credentials/oauth2:token_provider_lib", "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", "//test/mocks/server:server_factory_context_mocks", "//test/mocks/server:server_mocks", "//test/mocks/thread_local:thread_local_mocks", + "//test/mocks/upstream:cluster_manager_mocks", + "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/http/credential_injector/oauth2/token_provider_test.cc b/test/extensions/http/credential_injector/oauth2/token_provider_test.cc index 57bca04fc3eb6..dc6d76945cf0e 100644 --- a/test/extensions/http/credential_injector/oauth2/token_provider_test.cc +++ b/test/extensions/http/credential_injector/oauth2/token_provider_test.cc @@ -1,10 +1,13 @@ +#include "source/common/http/headers.h" #include "source/extensions/http/injected_credentials/common/secret_reader.h" +#include "source/extensions/http/injected_credentials/oauth2/client_credentials_impl.h" #include "source/extensions/http/injected_credentials/oauth2/token_provider.h" #include "test/mocks/event/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -53,6 +56,89 @@ TEST(TokenProvider, TokenProviderTest) { token_provider->onGetAccessTokenFailure(FilterCallbacks::FailureReason::StreamReset)); } +// Simulate a successful token fetch followed by a fetch failure while the token has NOT yet +// expired. +TEST(TokenProvider, FetchFailureInjectsStaleTokenWhenNotExpired) { + const std::string yaml_string = R"EOF( + token_fetch_retry_interval: 5s + token_endpoint: + cluster: non-existing-cluster + timeout: 0.5s + uri: "oauth.com/token" + client_credentials: + client_id: "client-id" + client_secret: {} + )EOF"; + + envoy::extensions::http::injected_credentials::oauth2::v3::OAuth2 proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + NiceMock cluster_manager; + NiceMock context; + NiceMock tls; + NiceMock dispatcher; + auto secret_reader = std::make_shared("client-secret"); + + auto token_provider = + std::make_shared(secret_reader, tls, cluster_manager, proto_config, dispatcher, + "stats_prefix", context.serverFactoryContext().scope()); + + token_provider->onGetAccessTokenSuccess("valid-access-token", std::chrono::seconds(3600)); + EXPECT_EQ("Bearer valid-access-token", token_provider->credential()); + + token_provider->onGetAccessTokenFailure(FilterCallbacks::FailureReason::StreamReset); + EXPECT_EQ("Bearer valid-access-token", token_provider->credential()); + + // inject() succeeds because the token is non-empty and not expired. + auto injector = std::make_shared(token_provider); + Envoy::Http::TestRequestHeaderMapImpl headers; + absl::Status status = injector->inject(headers, false); + EXPECT_TRUE(status.ok()); + EXPECT_EQ( + "Bearer valid-access-token", + headers.get(Envoy::Http::CustomHeaders::get().Authorization)[0]->value().getStringView()); +} + +// Simulate a successful token fetch followed by a fetch failure after the token has already +// expired. +TEST(TokenProvider, FetchFailureClearsExpiredTokenAndInjectFails) { + const std::string yaml_string = R"EOF( + token_fetch_retry_interval: 5s + token_endpoint: + cluster: non-existing-cluster + timeout: 0.5s + uri: "oauth.com/token" + client_credentials: + client_id: "client-id" + client_secret: {} + )EOF"; + + envoy::extensions::http::injected_credentials::oauth2::v3::OAuth2 proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + NiceMock cluster_manager; + NiceMock context; + NiceMock tls; + NiceMock dispatcher; + auto secret_reader = std::make_shared("client-secret"); + + auto token_provider = + std::make_shared(secret_reader, tls, cluster_manager, proto_config, dispatcher, + "stats_prefix", context.serverFactoryContext().scope()); + + // A token is fetched with 0s expiry, making it immediately expired. + token_provider->onGetAccessTokenSuccess("expired-access-token", std::chrono::seconds(0)); + EXPECT_EQ("Bearer expired-access-token", token_provider->credential()); + + // The expired token has been cleared. + token_provider->onGetAccessTokenFailure(FilterCallbacks::FailureReason::StreamReset); + EXPECT_TRUE(token_provider->credential().empty()); + + auto injector = std::make_shared(token_provider); + Envoy::Http::TestRequestHeaderMapImpl headers; + absl::Status status = injector->inject(headers, false); + EXPECT_TRUE(absl::IsNotFound(status)); + EXPECT_TRUE(headers.get(Envoy::Http::CustomHeaders::get().Authorization).empty()); +} + } // namespace OAuth2 } // namespace InjectedCredentials } // namespace Http From 69b4fee8ad913697171910782859477ff3934043 Mon Sep 17 00:00:00 2001 From: Yilin Guo Date: Sun, 17 May 2026 23:51:08 -0700 Subject: [PATCH 330/750] mcp_transcoder: add dynamic metadata request storage option. (#44912) Commit Message: mcp_transcoder: add dynamic metadata request storage option. Additional Description: This PR introduces a new `RequestStorageMode` configuration option to the `McpJsonRestBridge` filter. When this field is set to `DYNAMIC_METADATA`, the filter parses the MCP JSON-RPC request attributes (specifically the `method` and `params`) and stores them in Envoy's dynamic metadata. This enables subsequent filters and loggers to access the parsed request details. Risk Level: Low. The new storage feature is strictly opt-in, as the default state (`MODE_UNSPECIFIED`) does not store anything and preserves the existing behavior. Testing: Added a new unit test, `DynamicMetadataStoredWhenConfigured`, which verifies that the method and parameters from the JSON-RPC request are properly extracted and populated in the stream's dynamic metadata when the feature is enabled. Docs Changes: Added inline API documentation for the new `RequestStorageMode` enum and the `request_storage_mode` configuration field in `mcp_json_rest_bridge.proto`. Release Notes: Added the `request_storage_mode` option to the `mcp_json_rest_bridge` HTTP filter, enabling the ability to store parsed MCP request attributes (such as method and params) in dynamic metadata. Platform Specific Features: N/A --------- Signed-off-by: Yilin Guo --- .../v3/mcp_json_rest_bridge.proto | 20 +++++ .../http/mcp_json_rest_bridge/config.h | 4 +- .../mcp_json_rest_bridge_filter.cc | 30 +++++++ .../mcp_json_rest_bridge_filter.h | 10 +++ .../mcp_json_rest_bridge_filter_test.cc | 82 +++++++++++++++++++ 5 files changed, 144 insertions(+), 2 deletions(-) diff --git a/api/envoy/extensions/filters/http/mcp_json_rest_bridge/v3/mcp_json_rest_bridge.proto b/api/envoy/extensions/filters/http/mcp_json_rest_bridge/v3/mcp_json_rest_bridge.proto index a70e4cb4b4f8d..a1b6f2a8bba33 100644 --- a/api/envoy/extensions/filters/http/mcp_json_rest_bridge/v3/mcp_json_rest_bridge.proto +++ b/api/envoy/extensions/filters/http/mcp_json_rest_bridge/v3/mcp_json_rest_bridge.proto @@ -90,7 +90,20 @@ option (xds.annotations.v3.file_status).work_in_progress = true; // - Body: {"data": "updated value"} // (Only the "payload" field from arguments is used as the body. Other arguments not in the // path, like 'resource_id', become query parameters.) +// [#next-free-field: 6] message McpJsonRestBridge { + // Where to store parsed MCP request attributes. + enum RequestStorageMode { + // Unspecified. Uses default behavior (nothing is stored). + MODE_UNSPECIFIED = 0; + + // Store request attributes in dynamic metadata. The metadata namespace + // is the filter's config name as specified by the ``name`` field in the + // ``http_filters`` list (e.g. ``envoy.filters.http.mcp_json_rest_bridge`` + // if using the canonical filter name). + DYNAMIC_METADATA = 1; + } + // General server information. ServerInfo server_info = 1; @@ -116,6 +129,13 @@ message McpJsonRestBridge { // // Setting it to 0 would disable the limit. It is not recommended to do so in production. google.protobuf.UInt32Value max_response_body_size = 4; + + // Where to store parsed MCP request attributes. + // Default is not storing anything. + // When set to ``DYNAMIC_METADATA``, attributes are stored in dynamic metadata + // using the filter's config name (i.e. the ``name`` field of this filter's entry + // in the ``http_filters`` list) as the metadata namespace. + RequestStorageMode request_storage_mode = 5 [(validate.rules).enum = {defined_only: true}]; } // Configuration for the server metadata. diff --git a/source/extensions/filters/http/mcp_json_rest_bridge/config.h b/source/extensions/filters/http/mcp_json_rest_bridge/config.h index d855bc5fe0c5f..19da64325b6e2 100644 --- a/source/extensions/filters/http/mcp_json_rest_bridge/config.h +++ b/source/extensions/filters/http/mcp_json_rest_bridge/config.h @@ -4,6 +4,7 @@ #include "envoy/extensions/filters/http/mcp_json_rest_bridge/v3/mcp_json_rest_bridge.pb.validate.h" // IWYU pragma: keep #include "source/extensions/filters/http/common/factory_base.h" +#include "source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.h" namespace Envoy { namespace Extensions { @@ -17,8 +18,7 @@ class McpJsonRestBridgeFilterConfigFactory : public Common::ExceptionFreeFactoryBase< envoy::extensions::filters::http::mcp_json_rest_bridge::v3::McpJsonRestBridge> { public: - McpJsonRestBridgeFilterConfigFactory() - : ExceptionFreeFactoryBase("envoy.filters.http.mcp_json_rest_bridge") {} + McpJsonRestBridgeFilterConfigFactory() : ExceptionFreeFactoryBase(FilterName) {} private: absl::StatusOr createFilterFactoryFromProtoTyped( diff --git a/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.cc b/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.cc index 4d7c03d3f5139..7aeb357a7fd84 100644 --- a/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.cc +++ b/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.cc @@ -335,6 +335,12 @@ void McpJsonRestBridgeFilter::handleMcpMethod(const nlohmann::json& json_rpc, generateErrorJsonResponse(-32602, "Unsupported protocol version").dump()); return; } + + if (config_->requestStorageMode() == envoy::extensions::filters::http::mcp_json_rest_bridge::v3:: + McpJsonRestBridge::DYNAMIC_METADATA) { + setDynamicMetadata(method, json_rpc); + } + // TODO(guoyilin42): Consider supporting local response for tools/list in addition to the GET. if (method == McpConstants::Methods::TOOLS_LIST) { absl::StatusOr http_rule = @@ -581,6 +587,30 @@ void McpJsonRestBridgeFilter::sendErrorResponse(Http::Code response_code, response_code_details); } +void McpJsonRestBridgeFilter::setDynamicMetadata(absl::string_view method, + const nlohmann::json& json_rpc) { + Protobuf::Struct metadata; + (*metadata.mutable_fields())[McpConstants::METHOD_FIELD].set_string_value(method); + if (json_rpc.contains(McpConstants::PARAMS_FIELD)) { + const auto& params = json_rpc[McpConstants::PARAMS_FIELD]; + if (params.is_object()) { + Protobuf::Struct params_struct; + absl::Status status = MessageUtil::loadFromJsonNoThrow(params.dump(), params_struct); + if (status.ok()) { + *(*metadata.mutable_fields())[McpConstants::PARAMS_FIELD].mutable_struct_value() = + std::move(params_struct); + } else { + ENVOY_STREAM_LOG(warn, "Failed to parse params as Protobuf Struct: {}", *decoder_callbacks_, + status); + } + } + } + decoder_callbacks_->streamInfo().setDynamicMetadata( + std::string(decoder_callbacks_->filterConfigName()), metadata); + ENVOY_STREAM_LOG(debug, "MCP JSON REST Bridge filter set dynamic metadata: {}", + *decoder_callbacks_, metadata.DebugString()); +} + absl::Status McpJsonRestBridgeFilter::validateJsonRpcIdAndMethod(const nlohmann::json& json_rpc) { absl::StatusOr session_id = getSessionId(json_rpc); if (session_id.ok()) { diff --git a/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.h b/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.h index 3a7efeb8bc1f1..12d78d18068fc 100644 --- a/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.h +++ b/source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.h @@ -24,6 +24,8 @@ namespace Extensions { namespace HttpFilters { namespace McpJsonRestBridge { +inline constexpr char FilterName[] = "envoy.filters.http.mcp_json_rest_bridge"; + /** * Configuration for the MCP JSON REST Bridge filter. */ @@ -43,6 +45,11 @@ class McpJsonRestBridgeFilterConfig : public Logger::Loggable @@ -93,6 +100,9 @@ class McpJsonRestBridgeFilter : public Http::PassThroughFilter, // fails. Otherwise, it returns OK status. absl::Status validateJsonRpcIdAndMethod(const nlohmann::json& json_rpc); + // Sets dynamic metadata for the filter based on the MCP request method and parameters. + void setDynamicMetadata(absl::string_view method, const nlohmann::json& json_rpc); + enum class McpOperation { Unspecified = 0, // Received the "/mcp" URL but has not parsed the request body yet. diff --git a/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter_test.cc b/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter_test.cc index 57a4e37ed0028..af52a8894f726 100644 --- a/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter_test.cc +++ b/test/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter_test.cc @@ -1229,6 +1229,88 @@ TEST_F(McpJsonRestBridgeFilterTest, ResponseBodyExceedsLimitReturnsError) { Http::FilterDataStatus::StopIterationNoBuffer); } +TEST_F(McpJsonRestBridgeFilterTest, DynamicMetadataStoredWhenConfigured) { + envoy::extensions::filters::http::mcp_json_rest_bridge::v3::McpJsonRestBridge proto_config = + ParseTextProtoOrDie(R"pb( + request_storage_mode: DYNAMIC_METADATA + tool_config { + tools { + name: "create_api_key" + http_rule: { + post: "/v1/{parent=projects/*}/apiKeys" + body: "key" + } + } + } + )pb"); + config_ = std::make_shared(proto_config); + filter_ = std::make_unique(config_); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + filter_->setEncoderFilterCallbacks(encoder_callbacks_); + + request_headers_ = {{":method", "POST"}, {":path", "/mcp"}, {"content-type", "application/json"}}; + Buffer::OwnedImpl request_body( + R"json({"jsonrpc":"2.0","id":123,"method":"tools/call","params":{"name":"create_api_key","arguments":{"parent":"projects/test","key":{"displayName":"display-key"}}}})json"); + request_headers_.setContentLength(request_body.toString().size()); + + EXPECT_CALL(decoder_callbacks_.downstream_callbacks_, clearRouteCache()); + + Protobuf::Struct expected_metadata = ParseTextProtoOrDie(R"pb( + fields { + key: "method" + value { string_value: "tools/call" } + } + fields { + key: "params" + value { + struct_value { + fields { + key: "name" + value { string_value: "create_api_key" } + } + fields { + key: "arguments" + value { + struct_value { + fields { + key: "parent" + value { string_value: "projects/test" } + } + fields { + key: "key" + value { + struct_value { + fields { + key: "displayName" + value { string_value: "display-key" } + } + } + } + } + } + } + } + } + } + } + )pb"); + + ON_CALL(decoder_callbacks_, filterConfigName()) + .WillByDefault(Return("envoy.filters.http.mcp_json_rest_bridge")); + + EXPECT_CALL(decoder_callbacks_.stream_info_, + setDynamicMetadata("envoy.filters.http.mcp_json_rest_bridge", _)) + .WillOnce([&](const std::string&, const Protobuf::Struct& metadata) { + EXPECT_THAT(metadata, ProtoEq(expected_metadata)); + }); + + EXPECT_EQ(filter_->decodeHeaders(request_headers_, /*end_stream=*/false), + Http::FilterHeadersStatus::StopIteration); + + EXPECT_EQ(filter_->decodeData(request_body, /*end_stream=*/true), + Http::FilterDataStatus::Continue); +} + class McpHttpMethodFilterTest : public testing::TestWithParam { public: void SetUp() override { From a8d396eb4055083b52d6363b68dbad3a10496543 Mon Sep 17 00:00:00 2001 From: SunBK201 Date: Mon, 18 May 2026 14:52:21 +0800 Subject: [PATCH 331/750] docs: clarify HTTP/1 header casing directions (#44939) Commit Message: docs: clarify HTTP/1 header casing directions Additional Description: Fix the header casing guide to describe the correct directions for HTTP/1 header_key_format configuration. For more details, please see #44924. HttpConnectionManager http_protocol_options controls formatting for upstream request headers, while cluster HttpProtocolOptions controls formatting for downstream response headers. Also update the API comments to describe header_key_format as applying to headers encoded by the HTTP/1 codec rather than only response headers. Fixes #44924 Signed-off-by: SunBK201 --- api/envoy/config/core/v3/protocol.proto | 4 ++-- docs/root/configuration/http/http_conn_man/header_casing.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index d5d66be23423f..d2b0d86039cd8 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -445,8 +445,8 @@ message Http1ProtocolOptions { // This is a no-op if ``accept_http_10`` is not true. string default_host_for_http_10 = 3; - // Describes how the keys for response headers should be formatted. By default, all header keys - // are lower cased. + // Describes how the keys for headers encoded by the HTTP/1 codec should be formatted. By + // default, all header keys are lower cased. HeaderKeyFormat header_key_format = 4; // Enables trailers for HTTP/1. By default the HTTP/1 codec drops proxied trailers. diff --git a/docs/root/configuration/http/http_conn_man/header_casing.rst b/docs/root/configuration/http/http_conn_man/header_casing.rst index ea94c0a2dfd34..b6098bf20537a 100644 --- a/docs/root/configuration/http/http_conn_man/header_casing.rst +++ b/docs/root/configuration/http/http_conn_man/header_casing.rst @@ -11,10 +11,10 @@ To support these use cases, Envoy allows :ref:`configuring a formatting scheme f `, which will have Envoy transform the header keys during serialization. -To configure this formatting on response headers, specify the format in the +To configure this formatting on upstream request headers, specify the format in the :ref:`http_protocol_options `. -To configure this for upstream request headers, specify the formatting in +To configure this for downstream response headers, specify the formatting in :ref:`http_protocol_options ` in the cluster's :ref:`extension_protocol_options`. From eb069979b61614a7b3a53b13778e78ba2a7965df Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Mon, 18 May 2026 10:34:31 +0200 Subject: [PATCH 332/750] [ip_tagging] Support dynamic file based ip tags (#38574) Commit Message: Support async file based reloading of ip tags in ip tagging filter (done via Datasource). The supported file formats for ip tags are yaml and json. One instance of loaded ip tags file exists per process, the bookkeeping logic is handled by custom singleton that hashes and caches loaded file instances based on ip tags file path. Additional Description: Risk Level: Medium (Can be low if guarded by runtime flag) Testing: Unit + integration tests Docs Changes: Done Release Notes: TBD Platform Specific Features: NA --------- Signed-off-by: Kateryna Nezdolii Signed-off-by: Kateryna Nezdolii Signed-off-by: Kateryna Nezdolii Co-authored-by: Kateryna Nezdolii Co-authored-by: Adi (Suissa) Peleg Co-authored-by: phlax Co-authored-by: code --- CODEOWNERS | 2 +- .../http/ip_tagging/v3/ip_tagging.proto | 22 +- docs/BUILD | 1 + .../_include/ip-tagging-filter.yaml | 111 ++ .../http/http_filters/ip_tagging_filter.rst | 78 ++ source/common/common/logger.h | 3 +- source/common/config/datasource.h | 54 +- source/common/network/lc_trie.h | 1 + source/common/protobuf/utility.h | 2 + source/common/protobuf/yaml_utility.cc | 46 + .../extensions/filters/http/ip_tagging/BUILD | 6 + .../filters/http/ip_tagging/config.cc | 6 +- .../http/ip_tagging/ip_tagging_filter.cc | 313 ++++- .../http/ip_tagging/ip_tagging_filter.h | 147 ++- test/extensions/filters/http/ip_tagging/BUILD | 10 + .../http/ip_tagging/ip_tagging_filter_test.cc | 1065 ++++++++++++++--- .../ip_tagging/ip_tagging_integration_test.cc | 193 ++- .../filters/http/ip_tagging/test_data/BUILD | 13 + .../http/ip_tagging/test_data/empty_file.yaml | 0 .../ip_tagging/test_data/invalid_tags.json | 1 + .../ip_tagging/test_data/invalid_tags.yaml | 1 + .../ip_tagging/test_data/ip_tags_both.json | 22 + .../ip_tagging/test_data/ip_tags_both.yaml | 7 + .../test_data/ip_tags_external_request.json | 13 + .../test_data/ip_tags_external_request.yaml | 4 + .../test_data/ip_tags_internal_request.json | 13 + .../test_data/ip_tags_internal_request.yaml | 4 + .../test_data/ip_tags_invalid_cidr.json | 13 + .../test_data/ip_tags_invalid_cidr.yaml | 4 + .../ip_tags_updated_external_request.json | 22 + .../ip_tags_updated_internal_request.yaml | 4 + .../ip_tags_with_duplicate_request.json | 22 + .../ip_tags_with_duplicate_request.yaml | 7 + .../test_data/ip_tags_with_header.json | 13 + .../test_data/ip_tags_with_header.yaml | 4 + .../ip_tagging/test_data/ipv6_request.json | 13 + .../ip_tagging/test_data/ipv6_request.yaml | 4 + tools/spelling/spelling_dictionary.txt | 1 + 38 files changed, 2000 insertions(+), 245 deletions(-) create mode 100644 docs/root/configuration/http/http_filters/_include/ip-tagging-filter.yaml create mode 100644 test/extensions/filters/http/ip_tagging/test_data/BUILD create mode 100644 test/extensions/filters/http/ip_tagging/test_data/empty_file.yaml create mode 100644 test/extensions/filters/http/ip_tagging/test_data/invalid_tags.json create mode 100644 test/extensions/filters/http/ip_tagging/test_data/invalid_tags.yaml create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_both.json create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_both.yaml create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_external_request.json create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_external_request.yaml create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.json create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.yaml create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_invalid_cidr.json create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_invalid_cidr.yaml create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_updated_external_request.json create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_updated_internal_request.yaml create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_duplicate_request.json create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_duplicate_request.yaml create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.json create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.yaml create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ipv6_request.json create mode 100644 test/extensions/filters/http/ip_tagging/test_data/ipv6_request.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 7e44e4c1c33b0..de0bae6870eb9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -355,7 +355,7 @@ extensions/upstreams/tcp @ggreenway @mattklein123 # Thrift to metadata /*/extensions/filters/http/thrift_to_metadata @JuniorHsu @zuercher # IP tagging -/*/extensions/filters/http/ip_tagging @zuercher @JuniorHsu +/*/extensions/filters/http/ip_tagging @zuercher @JuniorHsu @nezdolik # Header to metadata /*/extensions/filters/http/header_to_metadata @zuercher @JuniorHsu # Json to metadata diff --git a/api/envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.proto b/api/envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.proto index 87f725e1c8c25..228cc72f7b2aa 100644 --- a/api/envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.proto +++ b/api/envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.filters.http.ip_tagging.v3; import "envoy/config/core/v3/address.proto"; +import "envoy/config/core/v3/base.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -18,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // IP tagging :ref:`configuration overview `. // [#extension: envoy.filters.http.ip_tagging] -// [#next-free-field: 6] +// [#next-free-field: 7] message IPTagging { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.ip_tagging.v2.IPTagging"; @@ -53,6 +54,12 @@ message IPTagging { repeated config.core.v3.CidrRange ip_list = 2; } + // Specifies the content of the IP tag file. + // Allow the file to be created with no IP tags. + message IPTags { + repeated IPTag ip_tags = 1; + } + // Specify to which header the tags will be written. message IpTagHeader { // Describes how to apply the tags to the headers. @@ -88,13 +95,20 @@ message IPTagging { // The type of request the filter should apply to. RequestType request_type = 1 [(validate.rules).enum = {defined_only: true}]; - // [#comment:TODO(ccaraman): Extend functionality to load IP tags from file system. - // Tracked by issue https://github.com/envoyproxy/envoy/issues/2695] // The set of IP tags for the filter. - repeated IPTag ip_tags = 4 [(validate.rules).repeated = {min_items: 1}]; + // Only one of :ref:`ip_tags ` + // or :ref:`ip_tags_datasource ` + // can be set for the IP Tagging filter. + repeated IPTag ip_tags = 4; // Specify to which header the tags will be written. // // If left unspecified, the tags will be appended to the ``x-envoy-ip-tags`` header. IpTagHeader ip_tag_header = 5; + + // Data source from which to retrieve ip tags. + // Only filename based data source is currently supported for IP tags. + // When using this data source, if a ``watched_directory`` is provided, the IP tags file will be re-read when a file move is detected. + // See :ref:`watched_directory ` for more information about the ``watched_directory`` field. + config.core.v3.DataSource ip_tags_datasource = 6; } diff --git a/docs/BUILD b/docs/BUILD index 810456e91dfe9..ceaa9d1367a09 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -35,6 +35,7 @@ filegroup( "root/configuration/listeners/network_filters/_include/generic_proxy_filter.yaml", "root/configuration/overview/_include/xds_api/oauth-sds-example.yaml", "root/configuration/security/_include/sds-source-example.yaml", + "root/configuration/http/http_filters/_include/ip-tagging-filter.yaml", ], ) + select({ "@envoy//bazel:windows_x86_64": [], diff --git a/docs/root/configuration/http/http_filters/_include/ip-tagging-filter.yaml b/docs/root/configuration/http/http_filters/_include/ip-tagging-filter.yaml new file mode 100644 index 0000000000000..d7b4193873f1c --- /dev/null +++ b/docs/root/configuration/http/http_filters/_include/ip-tagging-filter.yaml @@ -0,0 +1,111 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 8000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + http_filters: + - name: ip_tagging + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ip_tagging.v3.IPTagging + request_type: both + ip_tags: + - ip_tag_name: external_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} + - name: envoy.filters.http.router + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: local_route + virtual_hosts: + - domains: + - '*' + name: local_service + routes: + - match: {prefix: "/"} + route: {cluster: default_service} + - address: + socket_address: + address: 0.0.0.0 + port_value: 9000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress1_http + http_filters: + - name: ip_tagging + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ip_tagging.v3.IPTagging + request_type: both + ip_tags_datasource: + filename: "/geoip/ip-tags.yaml" + watched_directory: + path: "/geoip/" + - name: envoy.filters.http.router + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: local_route + virtual_hosts: + - domains: + - '*' + name: local_service + routes: + - match: {prefix: "/"} + route: {cluster: default_service} + - address: + socket_address: + address: 0.0.0.0 + port_value: 7000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress2_http + http_filters: + - name: ip_tagging + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ip_tagging.v3.IPTagging + request_type: both + ip_tags_datasource: + filename: "/geoip/ip-tags.json" + watched_directory: + path: "/geoip/" + - name: envoy.filters.http.router + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: local_route + virtual_hosts: + - domains: + - '*' + name: local_service + routes: + - match: {prefix: "/"} + route: {cluster: default_service} + clusters: + - name: default_service + load_assignment: + cluster_name: default_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 10001 +admin: + address: + socket_address: + address: 0.0.0.0 + port_value: 9901 diff --git a/docs/root/configuration/http/http_filters/ip_tagging_filter.rst b/docs/root/configuration/http/http_filters/ip_tagging_filter.rst index 73e4a87c05dfd..941d8aee2f867 100644 --- a/docs/root/configuration/http/http_filters/ip_tagging_filter.rst +++ b/docs/root/configuration/http/http_filters/ip_tagging_filter.rst @@ -22,12 +22,82 @@ described in the paper `IP-address lookup using LC-tries `_ by S. Nilsson and G. Karlsson. +IP tags can either be provided directly using the :ref:`ip_tags ` API field or +can be loaded from file if :ref:`ip_tags_datasource ` API field is configured. +For file based IP tags YAML and JSON file formats are supported. +IP tags will be dynamically reloaded if ``watched_directory`` is configured for :ref:`ip_tags_datasource `. Configuration ------------- * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.ip_tagging.v3.IPTagging``. * :ref:`v3 API reference ` +An example configuration of the filter with inline ip tags may look like the following: + +.. literalinclude:: _include/ip-tagging-filter.yaml + :language: yaml + :lines: 13-21 + :lineno-start: 13 + :linenos: + :caption: :download:`ip-tagging-filter.yaml <_include/ip-tagging-filter.yaml>` + +Below is an example configuration of the filter with the file based ip tags in yaml format: + +.. literalinclude:: _include/ip-tagging-filter.yaml + :language: yaml + :lines: 44-54 + :lineno-start: 44 + :linenos: + :caption: :download:`ip-tagging-filter.yaml <_include/ip-tagging-filter.yaml>` + +Where the *ip-tags.yaml* file would have the following content: + +.. code-block:: yaml + + ip_tags: + - ip_tag_name: external_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} + - ip_tag_name: internal_request + ip_list: + - {address_prefix: 1.2.3.5, prefix_len: 32} + +And here is an example configuration of the filter with the file based IP tags in JSON format: + +.. literalinclude:: _include/ip-tagging-filter.yaml + :language: yaml + :lines: 77-87 + :lineno-start: 77 + :linenos: + :caption: :download:`ip-tagging-filter.yaml <_include/ip-tagging-filter.yaml>` + +Where the ``ip-tags.json`` file would have the following content: + +.. code-block:: json + + { + "ip_tags": [ + { + "ip_tag_name": "external_request", + "ip_list": [ + { + "address_prefix": "1.2.3.4", + "prefix_len": 32 + } + ] + }, + { + "ip_tag_name": "internal_request", + "ip_list": [ + { + "address_prefix": "1.2.3.5", + "prefix_len": 32 + } + ] + } + ] + } + Statistics ---------- @@ -42,6 +112,14 @@ the owning HTTP connection manager. no_hit, Counter, Total number of requests with no applicable IP tags total, Counter, Total number of requests the IP Tagging Filter operated on +When file based reload of IP tags is enabled, additional reload stats will be available in the ``http..ip_tagging_reload.`` namespace. + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + success, Counter, Total number of successful reloads of IP tags file + Runtime ------- diff --git a/source/common/common/logger.h b/source/common/common/logger.h index 75352be4712a6..5eff540f76976 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -104,7 +104,8 @@ const static bool should_log = true; FUNCTION(websocket) \ FUNCTION(golang) \ FUNCTION(stats_sinks) \ - FUNCTION(dynamic_modules) + FUNCTION(dynamic_modules) \ + FUNCTION(ip_tagging) // clang-format off enum class Id { diff --git a/source/common/config/datasource.h b/source/common/config/datasource.h index 05c8218cb6e01..4f643b2dfaed6 100644 --- a/source/common/config/datasource.h +++ b/source/common/config/datasource.h @@ -68,6 +68,7 @@ absl::optional getPath(const envoy::config::core::v3::DataSource& s template using DataTransform = std::function>(absl::string_view)>; +using DataUpdateCb = std::function; struct ProviderOptions { // Use an empty string if no DataSource case is specified. @@ -93,9 +94,11 @@ template class DynamicData { ThreadLocal::SlotAllocator& tls, Api::Api& api, DataTransform data_transform_cb, const ProviderOptions& options, std::shared_ptr initial_data, uint64_t initial_hash, - absl::AnyInvocable cleanup, absl::Status& creation_status) + absl::AnyInvocable cleanup, absl::Status& creation_status, + absl::optional> data_update_cb = absl::nullopt) : dispatcher_(main_dispatcher), api_(api), options_(options), filename_(source.filename()), - data_transform_(data_transform_cb), hash_(initial_hash), cleanup_(std::move(cleanup)) { + data_transform_cb_(data_transform_cb), data_update_cb_(data_update_cb), hash_(initial_hash), + cleanup_(std::move(cleanup)) { slot_ = ThreadLocal::TypedSlot::ThreadLocalData>::makeUnique(tls); slot_->set([initial_data = std::move(initial_data)](Event::Dispatcher&) { @@ -148,7 +151,7 @@ template class DynamicData { return absl::OkStatus(); } } - auto transformed_new_data_or_error = data_transform_(new_data_or_error.value()); + auto transformed_new_data_or_error = data_transform_cb_(new_data_or_error.value()); if (!transformed_new_data_or_error.ok()) { // Log an error but don't fail the watch to avoid throwing EnvoyException at runtime. ENVOY_LOG_TO_LOGGER(Logger::Registry::getLog(Logger::Id::config), error, @@ -160,11 +163,14 @@ template class DynamicData { hash_ = new_hash; } - slot_->runOnAllThreads([new_data = std::move(transformed_new_data_or_error.value())]( - OptRef::ThreadLocalData> obj) { + slot_->runOnAllThreads([new_data = std::move(transformed_new_data_or_error.value()), + this](OptRef::ThreadLocalData> obj) { if (obj.has_value()) { obj->data_ = new_data; } + if (data_update_cb_.has_value()) { + (*data_update_cb_)(); + } }); return absl::OkStatus(); } @@ -173,7 +179,8 @@ template class DynamicData { Api::Api& api_; const ProviderOptions options_; const std::string filename_; - DataTransform data_transform_; + DataTransform data_transform_cb_; + absl::optional data_update_cb_; uint64_t hash_; absl::AnyInvocable cleanup_; ThreadLocal::TypedSlotPtr slot_; @@ -204,6 +211,7 @@ template class DataSourceProvider { * @param data_transform_cb transforms content of the DataSource (type std::string) * to the desired `DataType` type. * @param max_size max size limit of file to read, default 0 means no limit. + * @param data_update_cb optional callback that can be invoked upon data update in the DataSource. * @return absl::StatusOr with DataSource contents. or an error * status if any error occurs. * NOTE: If file watch is enabled and the new file content does not meet the @@ -212,15 +220,17 @@ template class DataSourceProvider { static absl::StatusOr> create(const ProtoDataSource& source, Event::Dispatcher& main_dispatcher, ThreadLocal::SlotAllocator& tls, Api::Api& api, bool allow_empty, - DataTransform data_transform_cb, uint64_t max_size) { + DataTransform data_transform_cb, uint64_t max_size, + absl::optional data_update_cb = absl::nullopt) { return create(source, main_dispatcher, tls, api, data_transform_cb, - {.allow_empty = allow_empty, .max_size = max_size}); + {.allow_empty = allow_empty, .max_size = max_size}, {}, data_update_cb); } static absl::StatusOr> create(const ProtoDataSource& source, Event::Dispatcher& main_dispatcher, ThreadLocal::SlotAllocator& tls, Api::Api& api, DataTransform data_transform_cb, - const ProviderOptions& options, absl::AnyInvocable cleanup = {}) { + const ProviderOptions& options, absl::AnyInvocable cleanup = {}, + absl::optional data_update_cb = absl::nullopt) { uint64_t max_size = options.max_size; auto initial_data_or_error = read(source, options.allow_empty, api, max_size); RETURN_IF_NOT_OK_REF(initial_data_or_error.status()); @@ -243,11 +253,11 @@ template class DataSourceProvider { absl::Status creation_status = absl::OkStatus(); const uint64_t hash = options.hash_content ? HashUtil::xxHash64(*initial_data_or_error) : 0; - auto ret = std::unique_ptr(new DataSourceProvider( - std::make_unique>(source, main_dispatcher, tls, api, - data_transform_cb, options, - std::move(transformed_data_or_error).value(), hash, - std::move(cleanup), creation_status))); + auto ret = std::unique_ptr( + new DataSourceProvider(std::make_unique>( + source, main_dispatcher, tls, api, data_transform_cb, options, + std::move(transformed_data_or_error).value(), hash, std::move(cleanup), creation_status, + data_update_cb))); RETURN_IF_NOT_OK(creation_status); return std::move(ret); } @@ -291,15 +301,16 @@ class ProviderSingleton : public Singleton::Instance, public: ProviderSingleton(Event::Dispatcher& main_dispatcher, ThreadLocal::SlotAllocator& tls, Api::Api& api, DataTransform data_transform_cb, - const ProviderOptions& options) - : dispatcher_(main_dispatcher), tls_(tls), api_(api), data_transform_(data_transform_cb), - options_(options) {} + const ProviderOptions& options, + absl::optional data_update_cb = absl::nullopt) + : dispatcher_(main_dispatcher), tls_(tls), api_(api), data_transform_cb_(data_transform_cb), + data_update_cb_(data_update_cb), options_(options) {} absl::StatusOr> getOrCreate(const ProtoDataSource& source) { ASSERT_IS_MAIN_OR_TEST_THREAD(); if (!usesFileWatching(source, options_)) { - return DataSourceProvider::create(source, dispatcher_, tls_, api_, data_transform_, - options_); + return DataSourceProvider::create( + source, dispatcher_, tls_, api_, data_transform_cb_, options_, {}, data_update_cb_); } const size_t config_hash = MessageUtil::hash(source); auto it = dynamic_providers_.find(config_hash); @@ -313,7 +324,7 @@ class ProviderSingleton : public Singleton::Instance, // Cleanup is guaranteed to execute on the main dispatcher during destruction but may happen // after the singleton is released. auto provider_or_error = DataSourceProvider::create( - source, dispatcher_, tls_, api_, data_transform_, options_, + source, dispatcher_, tls_, api_, data_transform_cb_, options_, [weak_this = this->weak_from_this(), config_hash] { if (auto locked_this = weak_this.lock(); locked_this) { locked_this->cleanup(config_hash); @@ -335,7 +346,8 @@ class ProviderSingleton : public Singleton::Instance, Event::Dispatcher& dispatcher_; ThreadLocal::SlotAllocator& tls_; Api::Api& api_; - DataTransform data_transform_; + DataTransform data_transform_cb_; + absl::optional data_update_cb_; const ProviderOptions options_; absl::flat_hash_map>> dynamic_providers_; }; diff --git a/source/common/network/lc_trie.h b/source/common/network/lc_trie.h index 253605d183d51..c01573b34b1fd 100644 --- a/source/common/network/lc_trie.h +++ b/source/common/network/lc_trie.h @@ -41,6 +41,7 @@ constexpr size_t MaxLcTrieNodes = (1 << 20); */ template class LcTrie { public: + LcTrie() = default; /** * @param data supplies a vector of data and CIDR ranges. * @param exclusive if true then only data for the most specific subnet will be returned diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index a3fbc7cd0b36d..04d9dffc185da 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -272,6 +272,8 @@ class MessageUtil { static void loadFromJson(absl::string_view json, Protobuf::Struct& message); static void loadFromYaml(const std::string& yaml, Protobuf::Message& message, ProtobufMessage::ValidationVisitor& validation_visitor); + static absl::Status loadFromYamlNoThrow(const std::string& yaml, Protobuf::Message& message, + ProtobufMessage::ValidationVisitor& validation_visitor); #endif // This function attempts to load Envoy configuration from the specified file diff --git a/source/common/protobuf/yaml_utility.cc b/source/common/protobuf/yaml_utility.cc index de150e50b674f..bd2677e3f535c 100644 --- a/source/common/protobuf/yaml_utility.cc +++ b/source/common/protobuf/yaml_utility.cc @@ -113,6 +113,20 @@ void jsonConvertInternal(const Protobuf::Message& source, MessageUtil::loadFromJson(json, dest, validation_visitor); } +absl::Status jsonConvertInternalNoThrow(const Protobuf::Message& source, + ProtobufMessage::ValidationVisitor& validation_visitor, + Protobuf::Message& dest) { + absl::Status conversion_status = absl::OkStatus(); + Protobuf::util::JsonPrintOptions json_options; + json_options.preserve_proto_field_names = true; + std::string json; + conversion_status = Protobuf::util::MessageToJsonString(source, &json, json_options); + if (conversion_status.ok()) { + MessageUtil::loadFromJson(json, dest, validation_visitor); + } + return conversion_status; +} + } // namespace void MessageUtil::loadFromJson(absl::string_view json, Protobuf::Message& message, @@ -186,6 +200,38 @@ void MessageUtil::loadFromYaml(const std::string& yaml, Protobuf::Message& messa throw EnvoyException("Unable to convert YAML as JSON: " + yaml); } +absl::Status +MessageUtil::loadFromYamlNoThrow(const std::string& yaml, Protobuf::Message& message, + ProtobufMessage::ValidationVisitor& validation_visitor) { + absl::Status load_status = absl::OkStatus(); + Protobuf::Value value; + TRY_NEEDS_AUDIT { value = parseYamlNode(YAML::Load(yaml)); } + END_TRY + catch (YAML::ParserException& e) { + load_status = absl::InvalidArgumentError(fmt::format("Failed to parse yaml: {}", e.what())); + } + catch (YAML::BadConversion& e) { + load_status = + absl::InvalidArgumentError(fmt::format("Failed to convert to yaml: {}", e.what())); + } + catch (std::exception& e) { + // There is a potentially wide space of exceptions thrown by the YAML parser, + // and enumerating them all may be difficult. Envoy doesn't work well with + // unhandled exceptions, so we capture them and record the exception name in + // the status. + load_status = + absl::InvalidArgumentError(fmt::format("Unexpected YAML exception: {}", e.what())); + } + if (value.kind_case() == Protobuf::Value::kStructValue || + value.kind_case() == Protobuf::Value::kListValue) { + load_status = jsonConvertInternalNoThrow(value, validation_visitor, message); + } else { + load_status = + absl::InvalidArgumentError(fmt::format("Unable to convert YAML as JSON: {}", yaml)); + } + return load_status; +} + std::string MessageUtil::getYamlStringFromMessage(const Protobuf::Message& message, const bool block_print, const bool always_print_primitive_fields) { diff --git a/source/extensions/filters/http/ip_tagging/BUILD b/source/extensions/filters/http/ip_tagging/BUILD index 42182217ba627..a60ad1b337861 100644 --- a/source/extensions/filters/http/ip_tagging/BUILD +++ b/source/extensions/filters/http/ip_tagging/BUILD @@ -19,7 +19,12 @@ envoy_cc_library( deps = [ "//envoy/http:filter_interface", "//envoy/runtime:runtime_interface", + "//envoy/thread_local:thread_local_interface", "//source/common/common:assert_lib", + "//source/common/common:callback_impl_lib", + "//source/common/common:thread_lib", + "//source/common/common:thread_synchronizer_lib", + "//source/common/config:datasource_lib", "//source/common/http:header_map_lib", "//source/common/http:headers_lib", "//source/common/network:lc_trie_lib", @@ -35,6 +40,7 @@ envoy_cc_extension( hdrs = ["config.h"], deps = [ "//envoy/registry", + "//envoy/thread_local:thread_local_interface", "//source/common/protobuf:utility_lib", "//source/extensions/filters/http/common:factory_base_lib", "//source/extensions/filters/http/ip_tagging:ip_tagging_filter_lib", diff --git a/source/extensions/filters/http/ip_tagging/config.cc b/source/extensions/filters/http/ip_tagging/config.cc index c8b04f58522de..db63caede19e8 100644 --- a/source/extensions/filters/http/ip_tagging/config.cc +++ b/source/extensions/filters/http/ip_tagging/config.cc @@ -3,6 +3,7 @@ #include "envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.pb.h" #include "envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.pb.validate.h" #include "envoy/registry/registry.h" +#include "envoy/thread_local/thread_local.h" #include "source/common/protobuf/utility.h" #include "source/extensions/filters/http/ip_tagging/ip_tagging_filter.h" @@ -17,7 +18,10 @@ absl::StatusOr IpTaggingFilterFactory::createFilterFactor const std::string& stat_prefix, Server::Configuration::FactoryContext& context) { absl::StatusOr config = IpTaggingFilterConfig::create( - proto_config, stat_prefix, context.scope(), context.serverFactoryContext().runtime()); + proto_config, stat_prefix, context.serverFactoryContext().singletonManager(), context.scope(), + context.serverFactoryContext().runtime(), context.serverFactoryContext().api(), + context.serverFactoryContext().threadLocal(), + context.serverFactoryContext().mainThreadDispatcher(), context.messageValidationVisitor()); RETURN_IF_NOT_OK_REF(config.status()); return [config = std::move(config.value())](Http::FilterChainFactoryCallbacks& callbacks) -> void { diff --git a/source/extensions/filters/http/ip_tagging/ip_tagging_filter.cc b/source/extensions/filters/http/ip_tagging/ip_tagging_filter.cc index b47f6f56c45d9..8afed8adbd018 100644 --- a/source/extensions/filters/http/ip_tagging/ip_tagging_filter.cc +++ b/source/extensions/filters/http/ip_tagging/ip_tagging_filter.cc @@ -3,6 +3,7 @@ #include "envoy/config/core/v3/address.pb.h" #include "envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.pb.h" +#include "source/common/config/datasource.h" #include "source/common/http/header_map_impl.h" #include "source/common/http/headers.h" @@ -13,64 +14,258 @@ namespace Extensions { namespace HttpFilters { namespace IpTagging { -absl::StatusOr IpTaggingFilterConfig::create( - const envoy::extensions::filters::http::ip_tagging::v3::IPTagging& config, - const std::string& stat_prefix, Stats::Scope& scope, Runtime::Loader& runtime) { - absl::Status creation_status = absl::OkStatus(); - auto config_ptr = std::shared_ptr( - new IpTaggingFilterConfig(config, stat_prefix, scope, runtime, creation_status)); - RETURN_IF_NOT_OK(creation_status); - return config_ptr; +namespace { +static const Network::LcTrie::LcTrie EMPTY_TRIE_{{}}; } -IpTaggingFilterConfig::IpTaggingFilterConfig( - const envoy::extensions::filters::http::ip_tagging::v3::IPTagging& config, - const std::string& stat_prefix, Stats::Scope& scope, Runtime::Loader& runtime, - absl::Status& creation_status) - : request_type_(requestTypeEnum(config.request_type())), scope_(scope), runtime_(runtime), - stat_name_set_(scope.symbolTable().makeSet("IpTagging")), - stats_prefix_(stat_name_set_->add(stat_prefix + "ip_tagging")), - no_hit_(stat_name_set_->add("no_hit")), total_(stat_name_set_->add("total")), - unknown_tag_(stat_name_set_->add("unknown_tag.hit")), - ip_tag_header_(config.has_ip_tag_header() ? config.ip_tag_header().header() : ""), - ip_tag_header_action_(config.has_ip_tag_header() - ? config.ip_tag_header().action() - : HeaderAction::IPTagging_IpTagHeader_HeaderAction_SANITIZE) { - - // Once loading IP tags from a file system is supported, the restriction on the size - // of the set should be removed and observability into what tags are loaded needs - // to be implemented. - // TODO(ccaraman): Remove size check once file system support is implemented. - // Work is tracked by issue https://github.com/envoyproxy/envoy/issues/2695. - if (config.ip_tags().empty()) { +IpTagsProvider::IpTagsProvider(const envoy::config::core::v3::DataSource& ip_tags_datasource, + Event::Dispatcher& main_dispatcher, Api::Api& api, + ProtobufMessage::ValidationVisitor& validation_visitor, + ThreadLocal::SlotAllocator& tls, const std::string& stat_prefix, + Stats::ScopeSharedPtr scope, Singleton::InstanceSharedPtr owner, + absl::Status& creation_status) + : api_(api), owner_(owner) { + const auto datasource_filename = ip_tags_datasource.filename(); + if (!datasource_filename.empty()) { + if (!absl::EndsWith(datasource_filename, MessageUtil::FileExtensions::get().Yaml) && + !absl::EndsWith(datasource_filename, MessageUtil::FileExtensions::get().Json)) { + creation_status = absl::InvalidArgumentError( + "Unsupported file format, unable to parse ip tags from file " + datasource_filename); + return; + } + auto provider_or_error = + Config::DataSource::DataSourceProvider>::create( + ip_tags_datasource, main_dispatcher, tls, api_, false, + // data_transform_cb + [this, datasource_filename, &validation_visitor, scope, + stat_prefix](absl::string_view new_data) -> absl::StatusOr { + IPTagsProto ip_tags_proto; + if (absl::EndsWith(datasource_filename, MessageUtil::FileExtensions::get().Yaml)) { + auto data = std::string(new_data); + auto load_status = + // TODO(nezdolik) remove string casting once yaml utility has been migrated to + // string_view. + MessageUtil::loadFromYamlNoThrow(data, ip_tags_proto, validation_visitor); + if (!load_status.ok()) { + return load_status; + } + } else if (absl::EndsWith(datasource_filename, + MessageUtil::FileExtensions::get().Json)) { + bool has_unknown_field; + auto load_status = + MessageUtil::loadFromJsonNoThrow(new_data, ip_tags_proto, has_unknown_field); + if (!load_status.ok()) { + return load_status; + } + } + auto new_loader = std::make_shared(stat_prefix, scope); + auto result = new_loader->parseIpTagsAsProto(ip_tags_proto.ip_tags()); + if (result.ok()) { + absl::MutexLock lock(&tags_loader_mu_); + tags_loader_ = std::move(new_loader); + } + return result; + }, + 0, + // data_update_cb + absl::make_optional([this]() { getTagsLoader()->incIpTagsReloadSuccess(); })); + if (!provider_or_error.status().ok()) { + creation_status = absl::InvalidArgumentError( + fmt::format("unable to create data source '{}'", provider_or_error.status().message())); + return; + } + data_source_provider_ = std::move(provider_or_error.value()); + } else { creation_status = - absl::InvalidArgumentError("HTTP IP Tagging Filter requires ip_tags to be specified."); - return; + absl::InvalidArgumentError("Cannot load tags from empty filename in datasource."); } +} +IpTagsProvider::~IpTagsProvider() { ENVOY_LOG(debug, "Shutting down ip tags provider"); }; + +LcTrieSharedPtr IpTagsProvider::ipTags() { + return data_source_provider_->data() == nullptr + ? std::make_shared>() + : data_source_provider_->data(); +} + +std::shared_ptr IpTagsProvider::getTagsLoader() { + absl::MutexLock lock(&tags_loader_mu_); + return tags_loader_; +} + +void IpTagsProvider::incHit(absl::string_view tag) { + if (auto loader = getTagsLoader()) { + loader->incHit(tag); + } +} + +void IpTagsProvider::incTotal() { + if (auto loader = getTagsLoader()) { + loader->incTotal(); + } +} + +void IpTagsProvider::incNoHit() { + if (auto loader = getTagsLoader()) { + loader->incNoHit(); + } +} + +absl::StatusOr> IpTagsRegistrySingleton::getOrCreateProvider( + const envoy::config::core::v3::DataSource& ip_tags_datasource, Api::Api& api, + ProtobufMessage::ValidationVisitor& validation_visitor, ThreadLocal::SlotAllocator& tls, + Event::Dispatcher& main_dispatcher, const std::string& stat_prefix, Stats::Scope& scope, + std::shared_ptr singleton) { + if (scope_ == nullptr) { + scope_ = scope.createScope(""); + } + std::shared_ptr ip_tags_provider; + absl::Status creation_status = absl::OkStatus(); + const uint64_t key = std::hash()(ip_tags_datasource.filename()); + auto it = ip_tags_registry_.find(key); + if (it != ip_tags_registry_.end()) { + if (std::shared_ptr provider = it->second.lock()) { + ip_tags_provider = provider; + } else { + ip_tags_provider = std::make_shared(ip_tags_datasource, main_dispatcher, api, + validation_visitor, tls, stat_prefix, + scope_, singleton, creation_status); + if (!creation_status.ok()) { + return creation_status; + } + ip_tags_registry_[key] = ip_tags_provider; + } + } else { + ip_tags_provider = std::make_shared(ip_tags_datasource, main_dispatcher, api, + validation_visitor, tls, stat_prefix, + scope_, singleton, creation_status); + if (!creation_status.ok()) { + return creation_status; + } + ip_tags_registry_[key] = ip_tags_provider; + } + return ip_tags_provider; +} + +IpTagsLoader::IpTagsLoader(const std::string& stat_prefix, Stats::ScopeSharedPtr scope) + : scope_(scope), stat_name_set_(scope->symbolTable().makeSet("IpTaggingReload")), + stats_prefix_(stat_name_set_->add(stat_prefix + "ip_tagging")), + unknown_tag_(stat_name_set_->add("unknown_tag.hit")), total_(stat_name_set_->add("total")), + no_hit_(stat_name_set_->add("no_hit")), + reload_success_(stat_name_set_->add("reload_success")) {} + +void IpTagsLoader::incHit(absl::string_view tag) { + incCounter(stat_name_set_->getBuiltin(absl::StrCat(tag, ".hit"), unknown_tag_)); +} + +void IpTagsLoader::incCounter(Stats::StatName name) { + Stats::SymbolTable::StoragePtr storage = scope_->symbolTable().join({stats_prefix_, name}); + scope_->counterFromStatName(Stats::StatName(storage.get())).inc(); +} + +absl::StatusOr IpTagsLoader::parseIpTagsAsProto( + const Protobuf::RepeatedPtrField< + envoy::extensions::filters::http::ip_tagging::v3::IPTagging::IPTag>& ip_tags) { std::vector>> tag_data; - tag_data.reserve(config.ip_tags().size()); - for (const auto& ip_tag : config.ip_tags()) { + tag_data.reserve(ip_tags.size()); + for (const auto& ip_tag : ip_tags) { std::vector cidr_set; cidr_set.reserve(ip_tag.ip_list().size()); for (const envoy::config::core::v3::CidrRange& entry : ip_tag.ip_list()) { - absl::StatusOr cidr_or_error = Network::Address::CidrRange::create(entry); if (cidr_or_error.status().ok()) { cidr_set.emplace_back(std::move(cidr_or_error.value())); } else { - creation_status = absl::InvalidArgumentError( + return absl::InvalidArgumentError( fmt::format("invalid ip/mask combo '{}/{}' (format is /<# mask bits>)", entry.address_prefix(), entry.prefix_len().value())); - return; } } - tag_data.emplace_back(ip_tag.ip_tag_name(), cidr_set); stat_name_set_->rememberBuiltin(absl::StrCat(ip_tag.ip_tag_name(), ".hit")); } - trie_ = std::make_unique>(tag_data); + return std::make_shared>(tag_data); +} + +SINGLETON_MANAGER_REGISTRATION(ip_tags_registry); + +absl::StatusOr IpTaggingFilterConfig::create( + const envoy::extensions::filters::http::ip_tagging::v3::IPTagging& config, + const std::string& stat_prefix, Singleton::Manager& singleton_manager, Stats::Scope& scope, + Runtime::Loader& runtime, Api::Api& api, ThreadLocal::SlotAllocator& tls, + Event::Dispatcher& dispatcher, ProtobufMessage::ValidationVisitor& validation_visitor) { + absl::Status creation_status = absl::OkStatus(); + auto ret = std::shared_ptr( + new IpTaggingFilterConfig(config, stat_prefix, singleton_manager, scope, runtime, api, tls, + dispatcher, validation_visitor, creation_status)); + RETURN_IF_NOT_OK(creation_status); + return ret; +} + +IpTaggingFilterConfig::IpTaggingFilterConfig( + const envoy::extensions::filters::http::ip_tagging::v3::IPTagging& config, + const std::string& stat_prefix, Singleton::Manager& singleton_manager, Stats::Scope& scope, + Runtime::Loader& runtime, Api::Api& api, ThreadLocal::SlotAllocator& tls, + Event::Dispatcher& dispatcher, ProtobufMessage::ValidationVisitor& validation_visitor, + absl::Status& creation_status) + : request_type_(requestTypeEnum(config.request_type())), scope_(scope), runtime_(runtime), + stats_prefix_str_(stat_prefix), stat_name_set_(scope_.symbolTable().makeSet("IpTagging")), + stats_prefix_(stat_name_set_->add(stats_prefix_str_ + "ip_tagging")), + no_hit_(stat_name_set_->add("no_hit")), total_(stat_name_set_->add("total")), + unknown_tag_(stat_name_set_->add("unknown_tag.hit")), + ip_tag_header_(config.has_ip_tag_header() ? config.ip_tag_header().header() : ""), + ip_tag_header_action_(config.has_ip_tag_header() + ? config.ip_tag_header().action() + : HeaderAction::IPTagging_IpTagHeader_HeaderAction_SANITIZE), + ip_tags_registry_(singleton_manager.getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(ip_tags_registry), + [] { return std::make_shared(); })) { + if (config.ip_tags().empty() && !config.has_ip_tags_datasource()) { + creation_status = absl::InvalidArgumentError( + "HTTP IP Tagging Filter requires either ip_tags or ip_tags_datasource to be specified."); + } + + if (!config.ip_tags().empty() && config.has_ip_tags_datasource()) { + creation_status = + absl::InvalidArgumentError("Only one of ip_tags or ip_tags_datasource can be configured."); + } + + RETURN_ONLY_IF_NOT_OK_REF(creation_status); + if (!config.ip_tags().empty()) { + std::vector>> tag_data; + for (const auto& ip_tag : config.ip_tags()) { + std::vector cidr_set; + cidr_set.reserve(ip_tag.ip_list().size()); + for (const envoy::config::core::v3::CidrRange& entry : ip_tag.ip_list()) { + absl::StatusOr cidr_or_error = + Network::Address::CidrRange::create(entry); + if (cidr_or_error.status().ok()) { + cidr_set.emplace_back(std::move(cidr_or_error.value())); + } else { + creation_status = absl::InvalidArgumentError( + fmt::format("invalid ip/mask combo '{}/{}' (format is /<# mask bits>)", + entry.address_prefix(), entry.prefix_len().value())); + return; + } + } + tag_data.emplace_back(ip_tag.ip_tag_name(), cidr_set); + stat_name_set_->rememberBuiltin(absl::StrCat(ip_tag.ip_tag_name(), ".hit")); + trie_ = std::make_unique>(tag_data); + } + } else { + auto provider_or_error = ip_tags_registry_->getOrCreateProvider( + config.ip_tags_datasource(), api, validation_visitor, tls, dispatcher, stat_prefix, scope, + ip_tags_registry_); + if (provider_or_error.status().ok()) { + provider_ = provider_or_error.value(); + } else { + creation_status = provider_or_error.status(); + return; + } + trie_ = provider_->ipTags(); + } } void IpTaggingFilterConfig::incCounter(Stats::StatName name) { @@ -78,6 +273,47 @@ void IpTaggingFilterConfig::incCounter(Stats::StatName name) { scope_.counterFromStatName(Stats::StatName(storage.get())).inc(); } +void IpTaggingFilterConfig::incHit(absl::string_view tag) { + if (provider_) { + provider_->incHit(tag); + } else { + incCounter(stat_name_set_->getBuiltin(absl::StrCat(tag, ".hit"), unknown_tag_)); + } +} + +void IpTaggingFilterConfig::incNoHit() { + if (provider_) { + provider_->incNoHit(); + } else { + incCounter(no_hit_); + } +} + +void IpTaggingFilterConfig::incTotal() { + if (provider_) { + provider_->incTotal(); + } else { + incCounter(total_); + } +} + +const Network::LcTrie::LcTrie& IpTaggingFilterConfig::trie() const { + if (provider_) { + return *provider_->ipTags(); + } + if (trie_ != nullptr) { + return *trie_; + } + return EMPTY_TRIE_; +} + +OptRef IpTaggingFilterConfig::ipTagHeader() const { + if (ip_tag_header_.get().empty()) { + return absl::nullopt; + } + return ip_tag_header_; +} + IpTaggingFilter::IpTaggingFilter(IpTaggingFilterConfigSharedPtr config) : config_(config) {} IpTaggingFilter::~IpTaggingFilter() = default; @@ -85,6 +321,7 @@ IpTaggingFilter::~IpTaggingFilter() = default; void IpTaggingFilter::onDestroy() {} Http::FilterHeadersStatus IpTaggingFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool) { + const bool is_internal_request = headers.EnvoyInternalRequest() && (headers.EnvoyInternalRequest()->value() == Http::Headers::get().EnvoyInternalRequestValues.True.c_str()); @@ -98,6 +335,8 @@ Http::FilterHeadersStatus IpTaggingFilter::decodeHeaders(Http::RequestHeaderMap& std::vector tags = config_->trie().getData(callbacks_->streamInfo().downstreamAddressProvider().remoteAddress()); + // Used for testing. + synchronizer_.syncPoint("_trie_lookup_complete"); applyTags(headers, tags); if (!tags.empty()) { // For a large number(ex > 1000) of tags, stats cardinality will be an issue. diff --git a/source/extensions/filters/http/ip_tagging/ip_tagging_filter.h b/source/extensions/filters/http/ip_tagging/ip_tagging_filter.h index 66f834f9209c8..4a19662bb7877 100644 --- a/source/extensions/filters/http/ip_tagging/ip_tagging_filter.h +++ b/source/extensions/filters/http/ip_tagging/ip_tagging_filter.h @@ -12,7 +12,12 @@ #include "envoy/http/filter.h" #include "envoy/runtime/runtime.h" #include "envoy/stats/scope.h" +#include "envoy/thread_local/thread_local.h" +#include "source/common/common/callback_impl.h" +#include "source/common/common/thread.h" +#include "source/common/common/thread_synchronizer.h" +#include "source/common/config/datasource.h" #include "source/common/network/cidr_range.h" #include "source/common/network/lc_trie.h" #include "source/common/stats/symbol_table.h" @@ -22,6 +27,101 @@ namespace Extensions { namespace HttpFilters { namespace IpTagging { +using IPTagsProto = envoy::extensions::filters::http::ip_tagging::v3::IPTagging::IPTags; +using LcTrieSharedPtr = std::shared_ptr>; + +class IpTagsLoader : public Logger::Loggable, + public std::enable_shared_from_this { +public: + IpTagsLoader(const std::string& stat_prefix, Stats::ScopeSharedPtr scope); + + void incIpTagsReloadSuccess() { incCounter(reload_success_); } + + void incHit(absl::string_view tag); + + void incNoHit() { incCounter(no_hit_); } + void incTotal() { incCounter(total_); } + + /** + * Parses ip tags in a proto format into a trie structure. + * @param ip_tags Collection of ip tags in proto format. + * @return Valid LcTrieSharedPtr if parsing succeeded or error status otherwise. + */ + absl::StatusOr + parseIpTagsAsProto(const Protobuf::RepeatedPtrField< + envoy::extensions::filters::http::ip_tagging::v3::IPTagging::IPTag>& ip_tags); + +private: + Stats::ScopeSharedPtr scope_; + Stats::StatNameSetPtr stat_name_set_; + const Stats::StatName stats_prefix_; + const Stats::StatName unknown_tag_; + const Stats::StatName total_; + const Stats::StatName no_hit_; + const Stats::StatName reload_success_; + void incCounter(Stats::StatName name); +}; + +/** + * This class owns ip tags trie structure for a configured absolute file path and provides access to + * the ip tags data. It also performs periodic refresh of ip tags data. + */ +class IpTagsProvider : public Logger::Loggable { +public: + IpTagsProvider(const envoy::config::core::v3::DataSource& ip_tags_datasource, + Event::Dispatcher& main_dispatcher, Api::Api& api, + ProtobufMessage::ValidationVisitor& validation_visitor, + ThreadLocal::SlotAllocator& tls, const std::string& stat_prefix, + Stats::ScopeSharedPtr scope, Singleton::InstanceSharedPtr owner, + absl::Status& creation_status); + + ~IpTagsProvider(); + + LcTrieSharedPtr ipTags(); + + void incHit(absl::string_view tag); + void incNoHit(); + void incTotal(); + +private: + std::shared_ptr getTagsLoader(); + + Api::Api& api_; + Envoy::Config::DataSource::DataSourceProviderPtr> + data_source_provider_; + // A shared_ptr to keep the provider singleton alive as long as any of its providers are in use. + const Singleton::InstanceSharedPtr owner_; + mutable absl::Mutex tags_loader_mu_; + std::shared_ptr tags_loader_ ABSL_GUARDED_BY(tags_loader_mu_); +}; + +using IpTagsProviderSharedPtr = std::shared_ptr; + +/** + * A singleton for file based loading of ip tags and looking up parsed trie data structures with Ip + * tags. When given equivalent file paths to the Ip tags, the singleton returns pointers to the same + * trie structure. + */ +class IpTagsRegistrySingleton : public Envoy::Singleton::Instance { +public: + IpTagsRegistrySingleton() {} + + absl::StatusOr> + getOrCreateProvider(const envoy::config::core::v3::DataSource& ip_tags_datasource, Api::Api& api, + ProtobufMessage::ValidationVisitor& validation_visitor, + ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_dispatcher, + const std::string& stat_prefix, Stats::Scope& scope, + std::shared_ptr singleton); + +private: + // Each provider stores shared_ptrs to this singleton, which keeps the singleton + // from being destroyed unless it's no longer keeping track of any providers. + // Each entry in this map consists of a key (hash of an absolute file path to ip tags file) + // and and value (instance of `IpTagsProvider` that owns ip tags data). + absl::flat_hash_map> ip_tags_registry_; + Stats::ScopeSharedPtr scope_; +}; + /** * Type of requests the filter should apply to. */ @@ -30,36 +130,37 @@ enum class FilterRequestType { INTERNAL, EXTERNAL, BOTH }; /** * Configuration for the HTTP IP Tagging filter. */ -class IpTaggingFilterConfig { +class IpTaggingFilterConfig : public std::enable_shared_from_this, + public Logger::Loggable { public: using HeaderAction = envoy::extensions::filters::http::ip_tagging::v3::IPTagging::IpTagHeader::HeaderAction; + static absl::StatusOr> create(const envoy::extensions::filters::http::ip_tagging::v3::IPTagging& config, - const std::string& stat_prefix, Stats::Scope& scope, Runtime::Loader& runtime); + const std::string& stat_prefix, Singleton::Manager& singleton_manager, Stats::Scope& scope, + Runtime::Loader& runtime, Api::Api& api, ThreadLocal::SlotAllocator& tls, + Event::Dispatcher& dispatcher, ProtobufMessage::ValidationVisitor& validation_visitor); Runtime::Loader& runtime() { return runtime_; } FilterRequestType requestType() const { return request_type_; } - const Network::LcTrie::LcTrie& trie() const { return *trie_; } + const Network::LcTrie::LcTrie& trie() const; - OptRef ipTagHeader() const { - if (ip_tag_header_.get().empty()) { - return absl::nullopt; - } - return ip_tag_header_; - } + OptRef ipTagHeader() const; HeaderAction ipTagHeaderAction() const { return ip_tag_header_action_; } - void incHit(absl::string_view tag) { - incCounter(stat_name_set_->getBuiltin(absl::StrCat(tag, ".hit"), unknown_tag_)); - } - void incNoHit() { incCounter(no_hit_); } - void incTotal() { incCounter(total_); } + void incHit(absl::string_view tag); + + void incNoHit(); + void incTotal(); private: IpTaggingFilterConfig(const envoy::extensions::filters::http::ip_tagging::v3::IPTagging& config, - const std::string& stat_prefix, Stats::Scope& scope, - Runtime::Loader& runtime, absl::Status& creation_status); + const std::string& stat_prefix, Singleton::Manager& singleton_manager, + Stats::Scope& scope, Runtime::Loader& runtime, Api::Api& api, + ThreadLocal::SlotAllocator& tls, Event::Dispatcher& dispatcher, + ProtobufMessage::ValidationVisitor& validation_visitor, + absl::Status& creation_status); static FilterRequestType requestTypeEnum( envoy::extensions::filters::http::ip_tagging::v3::IPTagging::RequestType request_type) { @@ -76,19 +177,25 @@ class IpTaggingFilterConfig { } void incCounter(Stats::StatName name); - + // Allow the unit test to have access to private members. + friend class IpTaggingFilterConfigPeer; const FilterRequestType request_type_; Stats::Scope& scope_; Runtime::Loader& runtime_; + const std::string stats_prefix_str_; Stats::StatNameSetPtr stat_name_set_; const Stats::StatName stats_prefix_; const Stats::StatName no_hit_; const Stats::StatName total_; const Stats::StatName unknown_tag_; - std::unique_ptr> trie_; const Http::LowerCaseString ip_tag_header_; // An empty string indicates that no ip_tag_header is set. const HeaderAction ip_tag_header_action_; + // A shared_ptr to keep the ip tags registry singleton alive as long as any of its trie structures + // are in use. + const std::shared_ptr ip_tags_registry_; + LcTrieSharedPtr trie_; + std::shared_ptr provider_; }; using IpTaggingFilterConfigSharedPtr = std::shared_ptr; @@ -117,6 +224,10 @@ class IpTaggingFilter : public Http::StreamDecoderFilter { IpTaggingFilterConfigSharedPtr config_; Http::StreamDecoderFilterCallbacks* callbacks_{}; + // Used for testing only. + mutable Thread::ThreadSynchronizer synchronizer_; + // Allow the unit test to have access to private members. + friend class IpTaggingFilterPeer; }; } // namespace IpTagging diff --git a/test/extensions/filters/http/ip_tagging/BUILD b/test/extensions/filters/http/ip_tagging/BUILD index f801cdd0aa39c..28b9dc8a78b28 100644 --- a/test/extensions/filters/http/ip_tagging/BUILD +++ b/test/extensions/filters/http/ip_tagging/BUILD @@ -14,6 +14,9 @@ envoy_package() envoy_extension_cc_test( name = "ip_tagging_filter_test", srcs = ["ip_tagging_filter_test.cc"], + data = [ + "//test/extensions/filters/http/ip_tagging/test_data:ip_tagging_files", + ], extension_names = ["envoy.filters.http.ip_tagging"], rbe_pool = "6gig", deps = [ @@ -21,11 +24,15 @@ envoy_extension_cc_test( "//source/common/http:header_map_lib", "//source/common/network:address_lib", "//source/common/network:utility_lib", + "//source/common/singleton:manager_impl_lib", "//source/extensions/filters/http/ip_tagging:config", "//source/extensions/filters/http/ip_tagging:ip_tagging_filter_lib", + "//test/mocks/api:api_mocks", "//test/mocks/http:http_mocks", + "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/stats:stats_mocks", + "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/ip_tagging/v3:pkg_cc_proto", ], @@ -35,6 +42,9 @@ envoy_extension_cc_test( name = "ip_tagging_integration_test", size = "large", srcs = ["ip_tagging_integration_test.cc"], + data = [ + "//test/extensions/filters/http/ip_tagging/test_data:ip_tagging_files", + ], extension_names = ["envoy.filters.http.ip_tagging"], rbe_pool = "6gig", deps = [ diff --git a/test/extensions/filters/http/ip_tagging/ip_tagging_filter_test.cc b/test/extensions/filters/http/ip_tagging/ip_tagging_filter_test.cc index 5cbe8fd5413fb..106d7911dc434 100644 --- a/test/extensions/filters/http/ip_tagging/ip_tagging_filter_test.cc +++ b/test/extensions/filters/http/ip_tagging/ip_tagging_filter_test.cc @@ -5,49 +5,272 @@ #include "source/common/buffer/buffer_impl.h" #include "source/common/network/address_impl.h" #include "source/common/network/utility.h" +#include "source/common/singleton/manager_impl.h" #include "source/extensions/filters/http/ip_tagging/ip_tagging_filter.h" +#include "test/mocks/api/mocks.h" #include "test/mocks/http/mocks.h" +#include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/stats/mocks.h" +#include "test/mocks/thread_local/mocks.h" +#include "test/test_common/environment.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::InvokeWithoutArgs; using testing::Return; namespace Envoy { namespace Extensions { namespace HttpFilters { namespace IpTagging { -namespace { -class IpTaggingFilterTest : public testing::Test { +class IpTaggingFilterConfigPeer { public: - IpTaggingFilterTest() { - ON_CALL(runtime_.snapshot_, featureEnabled("ip_tagging.http_filter_enabled", 100)) - .WillByDefault(Return(true)); + static std::shared_ptr ipTagsProvider(IpTaggingFilterConfig& filter_config) { + return filter_config.provider_; + } + static const std::shared_ptr& + ipTagsRegistry(const IpTaggingFilterConfig& filter_config) { + return filter_config.ip_tags_registry_; + } + + static void resetTrie(IpTaggingFilterConfig& filter_config) { + if (filter_config.trie_) { + filter_config.trie_.reset(); + } } +}; + +class IpTaggingFilterPeer { +public: + static Thread::ThreadSynchronizer& synchronizer(std::unique_ptr& filter) { + return filter->synchronizer_; + } +}; + +namespace { +namespace { - const std::string internal_request_yaml = R"EOF( +const std::string internal_request_config = R"EOF( request_type: internal ip_tags: - ip_tag_name: internal_request ip_list: - {address_prefix: 1.2.3.5, prefix_len: 32} +)EOF"; + +const std::string internal_request_with_json_file_config = R"EOF( + request_type: internal + ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.json" + )EOF"; + +const std::string internal_request_with_yaml_file_with_reload_config = R"EOF( + request_type: internal + ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.yaml" + watched_directory: + path: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data" + )EOF"; + +const std::string internal_request_with_yaml_file_config = R"EOF( + request_type: internal + ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.yaml" + )EOF"; + +const std::string external_request_config = R"EOF( +request_type: external +ip_tags: + - ip_tag_name: external_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} +)EOF"; + +const std::string external_request_with_json_file_config = R"EOF( + request_type: external + ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_external_request.json" + )EOF"; + +const std::string external_request_with_yaml_file_config = R"EOF( + request_type: external + ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_external_request.yaml" + )EOF"; + +const std::string both_request_config = R"EOF( +request_type: both +ip_tags: + - ip_tag_name: external_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} + - ip_tag_name: internal_request + ip_list: + - {address_prefix: 1.2.3.5, prefix_len: 32} +)EOF"; + +const std::string both_request_with_json_file_config = R"EOF( +request_type: both +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_both.json" +)EOF"; + +const std::string both_request_with_yaml_file_config = R"EOF( +request_type: both +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_both.yaml" +)EOF"; + +const std::string internal_request_with_header_config = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header +ip_tags: + - ip_tag_name: internal_request_with_optional_header + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} +)EOF"; + +const std::string internal_request_with_header_with_json_file_config = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.json" +)EOF"; +const std::string internal_request_with_header_with_yaml_file_config = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.yaml" +)EOF"; + +const std::string internal_request_with_replace_header_config = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header + action: SANITIZE +ip_tags: + - ip_tag_name: internal_request_with_optional_header + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} +)EOF"; + +const std::string internal_request_with_replace_header_with_json_file_config = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header + action: SANITIZE +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.json" +)EOF"; + +const std::string internal_request_with_replace_header_with_yaml_file_config = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header + action: SANITIZE +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.yaml" )EOF"; +const std::string internal_request_with_append_or_add_config = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header + action: APPEND_IF_EXISTS_OR_ADD +ip_tags: + - ip_tag_name: internal_request_with_optional_header + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} +)EOF"; + +const std::string internal_request_with_append_or_add_with_json_file_config = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header + action: APPEND_IF_EXISTS_OR_ADD +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.json" +)EOF"; + +const std::string internal_request_with_append_or_add_with_yaml_file_config = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header + action: APPEND_IF_EXISTS_OR_ADD +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.yaml" +)EOF"; + +const std::string duplicate_request_config = R"EOF( +request_type: both +ip_tags: + - ip_tag_name: duplicate_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} + - ip_tag_name: internal_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} +)EOF"; + +const std::string duplicate_request_with_json_file_config = R"EOF( +request_type: both +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_duplicate_request.json" +)EOF"; + +const std::string duplicate_request_with_yaml_file_config = R"EOF( +request_type: both +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_duplicate_request.yaml" +)EOF"; + +const std::string ipv6_config = R"EOF( +ip_tags: + - ip_tag_name: ipv6_request + ip_list: + - {address_prefix: 2001:abcd:ef01:2345:6789:abcd:ef01:234, prefix_len: 64} +)EOF"; + +const std::string ipv6_with_yaml_file_config = R"EOF( +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ipv6_request.yaml" +)EOF"; + +const std::string ipv6_with_json_file_config = R"EOF( +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ipv6_request.json" +)EOF"; + +} // namespace + +class IpTaggingFilterTest : public ::testing::TestWithParam { +public: + IpTaggingFilterTest() + : scope_(stats_.rootScope()), api_(Api::createApiForTest(stats_, time_system_)), + dispatcher_(api_->allocateDispatcher("test_main_thread")) { + ON_CALL(runtime_.snapshot_, featureEnabled("ip_tagging.http_filter_enabled", 100)) + .WillByDefault(Return(true)); + } + void initializeFilter(const std::string& yaml, absl::optional expected_error = absl::nullopt) { envoy::extensions::filters::http::ip_tagging::v3::IPTagging config; - TestUtility::loadFromYaml(yaml, config); + TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), config); auto config_or = - IpTaggingFilterConfig::create(config, "prefix.", *stats_.rootScope(), runtime_); + IpTaggingFilterConfig::create(config, "prefix.", *singleton_manager_, *scope_, runtime_, + *api_, tls_, *dispatcher_, validation_visitor_); if (expected_error.has_value()) { EXPECT_FALSE(config_or.ok()); - EXPECT_EQ(expected_error.value(), absl::StrCat(config_or.status())); + EXPECT_TRUE(absl::StrContains(absl::StrCat(config_or.status()), expected_error.value())); return; } EXPECT_TRUE(config_or.ok()); @@ -62,16 +285,230 @@ request_type: internal } } - NiceMock stats_; + std::unique_ptr singleton_manager_ = + std::make_unique(); + Envoy::Stats::TestUtil::TestStore stats_; + Stats::ScopeSharedPtr scope_; + Event::SimulatedTimeSystem time_system_; + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + NiceMock tls_; IpTaggingFilterConfigSharedPtr config_; std::unique_ptr filter_; NiceMock filter_callbacks_; Buffer::OwnedImpl data_; NiceMock runtime_; + NiceMock validation_visitor_; }; -TEST_F(IpTaggingFilterTest, InternalRequest) { - initializeFilter(internal_request_yaml); +TEST_F(IpTaggingFilterTest, NoIpTagsConfigured) { + const std::string config_yaml = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header + action: SANITIZE +)EOF"; + initializeFilter(config_yaml, "INVALID_ARGUMENT: HTTP IP Tagging Filter requires either ip_tags " + "or ip_tags_datasource to be specified."); +} + +TEST_F(IpTaggingFilterTest, BothIpTagsAndIpTagsFileConfigured) { + const std::string config_yaml = R"EOF( +request_type: internal +ip_tag_header: + header: x-envoy-optional-header + action: SANITIZE +ip_tags: + - ip_tag_name: internal_request + ip_list: + - {address_prefix: 1.2.3.5, prefix_len: 32} +ip_tags_datasource: + filename: /test/tags.yaml +)EOF"; + initializeFilter( + config_yaml, + "INVALID_ARGUMENT: Only one of ip_tags or ip_tags_datasource can be configured."); +} + +TEST_F(IpTaggingFilterTest, EmptyIpTagsFile) { + const std::string config_yaml = R"EOF( +request_type: internal +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/empty_file.yaml" +)EOF"; + std::string file = TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/empty_file.yaml"); + initializeFilter( + config_yaml, + absl::StrCat("INVALID_ARGUMENT: unable to create data source 'file ", file, " is empty'")); +} + +TEST_F(IpTaggingFilterTest, EmptyFilenameInDatasourceConfigured) { + const std::string config_yaml = R"EOF( +request_type: internal +ip_tags_datasource: + filename: +)EOF"; + initializeFilter(config_yaml, + "INVALID_ARGUMENT: Cannot load tags from empty filename in datasource."); +} + +TEST_F(IpTaggingFilterTest, UnsupportedFormatForIpTagsFile) { + const std::string config_yaml = R"EOF( +request_type: internal +ip_tags_datasource: + filename: /test/tags.csv +)EOF"; + initializeFilter(config_yaml, "INVALID_ARGUMENT: Unsupported file format, unable to parse ip " + "tags from file /test/tags.csv"); +} + +TEST_F(IpTaggingFilterTest, InvalidYamlFile) { + const std::string config_yaml = R"EOF( + request_type: internal + ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/invalid_tags.yaml" + )EOF"; + initializeFilter( + config_yaml, + "INVALID_ARGUMENT: unable to create data source 'Unable to convert YAML as JSON: ip_tags"); +} + +TEST_F(IpTaggingFilterTest, InvalidJsonFile) { + const std::string config_yaml = R"EOF( + request_type: internal + ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/invalid_tags.json" + )EOF"; + initializeFilter(config_yaml, "INVALID_ARGUMENT: unable to create data source"); +} + +TEST_F(IpTaggingFilterTest, InvalidCidr) { + const std::string external_request_yaml = R"EOF( +request_type: external +ip_tags: + - ip_tag_name: fooooooo + ip_list: + - {address_prefix: 12345.12345.12345.12345, prefix_len: 999999} +)EOF"; + initializeFilter( + external_request_yaml, + "INVALID_ARGUMENT: invalid ip/mask combo '12345.12345.12345.12345/999999' (format is " + "/<# mask bits>)"); +} + +TEST_F(IpTaggingFilterTest, InvalidCidrInYamlFile) { + const std::string config_yaml = R"EOF( +request_type: external +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_invalid_cidr.yaml" +)EOF"; + initializeFilter(config_yaml, + "INVALID_ARGUMENT: unable to create data source 'invalid ip/mask combo " + "'12345.12345.12345.12345/999999'"); +} + +TEST_F(IpTaggingFilterTest, InvalidCidrInJsonFile) { + const std::string config_yaml = R"EOF( +request_type: external +ip_tags_datasource: + filename: "{{ test_rundir }}/test/extensions/filters/http/ip_tagging/test_data/ip_tags_invalid_cidr.json" +)EOF"; + initializeFilter(config_yaml, + "INVALID_ARGUMENT: unable to create data source 'invalid ip/mask combo " + "'12345.12345.12345.12345/999999'"); +} + +TEST_F(IpTaggingFilterTest, RecreatesProviderWhenWeakPtrExpired) { + envoy::extensions::filters::http::ip_tagging::v3::IPTagging proto_config1; + TestUtility::loadFromYaml(TestEnvironment::substitute(internal_request_with_json_file_config), + proto_config1); + // Create first config to register a provider in the singleton registry. + absl::StatusOr config1_result = + IpTaggingFilterConfig::create(proto_config1, "prefix.", *singleton_manager_, *scope_, + runtime_, *api_, tls_, *dispatcher_, validation_visitor_); + EXPECT_TRUE(config1_result.ok()); + auto config1 = config1_result.value(); + auto provider1 = IpTaggingFilterConfigPeer::ipTagsProvider(*config1); + EXPECT_NE(nullptr, provider1); + + // Destroy the first config, which releases the provider. The weak_ptr in the registry expires. + config1.reset(); + provider1.reset(); + + // Create second config with the same filename. The registry should detect the expired weak_ptr + // and create a new provider. + envoy::extensions::filters::http::ip_tagging::v3::IPTagging proto_config2; + TestUtility::loadFromYaml(TestEnvironment::substitute(internal_request_with_json_file_config), + proto_config2); + absl::StatusOr config2_result = + IpTaggingFilterConfig::create(proto_config2, "prefix.", *singleton_manager_, *scope_, + runtime_, *api_, tls_, *dispatcher_, validation_visitor_); + EXPECT_TRUE(config2_result.ok()); + auto config2 = config2_result.value(); + auto provider2 = IpTaggingFilterConfigPeer::ipTagsProvider(*config2); + EXPECT_NE(nullptr, provider2); +} + +TEST_F(IpTaggingFilterTest, ReusesIpTagsProviderInstanceForSameFilePath) { + envoy::extensions::filters::http::ip_tagging::v3::IPTagging proto_config1; + TestUtility::loadFromYaml(TestEnvironment::substitute(internal_request_with_json_file_config), + proto_config1); + absl::StatusOr config1_result = + IpTaggingFilterConfig::create(proto_config1, "prefix.", *singleton_manager_, *scope_, + runtime_, *api_, tls_, *dispatcher_, validation_visitor_); + EXPECT_TRUE(config1_result.ok()); + auto config1 = config1_result.value(); + envoy::extensions::filters::http::ip_tagging::v3::IPTagging proto_config2; + TestUtility::loadFromYaml(TestEnvironment::substitute(internal_request_with_json_file_config), + proto_config2); + absl::StatusOr config2_result = + IpTaggingFilterConfig::create(proto_config2, "prefix.", *singleton_manager_, *scope_, + runtime_, *api_, tls_, *dispatcher_, validation_visitor_); + EXPECT_TRUE(config2_result.ok()); + auto config2 = config2_result.value(); + auto ip_tags_registry1 = IpTaggingFilterConfigPeer::ipTagsRegistry(*config1); + auto ip_tags_registry2 = IpTaggingFilterConfigPeer::ipTagsRegistry(*config2); + EXPECT_EQ(ip_tags_registry1.get(), ip_tags_registry2.get()); + auto provider1 = IpTaggingFilterConfigPeer::ipTagsProvider(*config1); + auto provider2 = IpTaggingFilterConfigPeer::ipTagsProvider(*config2); + EXPECT_NE(nullptr, provider1); + EXPECT_NE(nullptr, provider2); + EXPECT_EQ(provider1->ipTags(), provider2->ipTags()); +} + +TEST_F(IpTaggingFilterTest, DifferentIpTagsProviderInstanceForDifferentFilePath) { + envoy::extensions::filters::http::ip_tagging::v3::IPTagging proto_config1; + TestUtility::loadFromYaml(TestEnvironment::substitute(internal_request_with_json_file_config), + proto_config1); + absl::StatusOr config1_result = + IpTaggingFilterConfig::create(proto_config1, "prefix.", *singleton_manager_, *scope_, + runtime_, *api_, tls_, *dispatcher_, validation_visitor_); + EXPECT_TRUE(config1_result.ok()); + auto config1 = config1_result.value(); + envoy::extensions::filters::http::ip_tagging::v3::IPTagging proto_config2; + TestUtility::loadFromYaml(TestEnvironment::substitute(external_request_with_json_file_config), + proto_config2); + absl::StatusOr config2_result = + IpTaggingFilterConfig::create(proto_config2, "prefix.", *singleton_manager_, *scope_, + runtime_, *api_, tls_, *dispatcher_, validation_visitor_); + EXPECT_TRUE(config2_result.ok()); + auto config2 = config2_result.value(); + auto ip_tags_registry1 = IpTaggingFilterConfigPeer::ipTagsRegistry(*config1); + auto ip_tags_registry2 = IpTaggingFilterConfigPeer::ipTagsRegistry(*config2); + EXPECT_EQ(ip_tags_registry1.get(), ip_tags_registry2.get()); + auto provider1 = IpTaggingFilterConfigPeer::ipTagsProvider(*config1); + auto provider2 = IpTaggingFilterConfigPeer::ipTagsProvider(*config2); + EXPECT_NE(nullptr, provider1); + EXPECT_NE(nullptr, provider2); + EXPECT_NE(provider1->ipTags(), provider2->ipTags()); + ::testing::Mock::VerifyAndClearExpectations(&filter_callbacks_); +} + +class InternalRequestIpTaggingFilterTest : public IpTaggingFilterTest {}; + +TEST_F(InternalRequestIpTaggingFilterTest, InternalRequestNoCrashWhenTrieIsNull) { + initializeFilter(internal_request_config); EXPECT_EQ(FilterRequestType::INTERNAL, config_->requestType()); Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}}; @@ -80,12 +517,41 @@ TEST_F(IpTaggingFilterTest, InternalRequest) { filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( remote_address); - EXPECT_CALL(stats_, counter("prefix.ip_tagging.internal_request.hit")); - EXPECT_CALL(stats_, counter("prefix.ip_tagging.total")); + IpTaggingFilterConfigPeer::resetTrie(*config_); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ("", request_headers.get_(Http::Headers::get().EnvoyIpTags)); + + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.internal_request.hit").value(), 0); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 1); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); + Http::TestRequestTrailerMapImpl request_trailers; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); + + // Check external requests don't get a tag. + request_headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_FALSE(request_headers.has(Http::Headers::get().EnvoyIpTags)); +} + +TEST_P(InternalRequestIpTaggingFilterTest, InternalRequest) { + const std::string config = GetParam(); + initializeFilter(config); + EXPECT_EQ(FilterRequestType::INTERNAL, config_->requestType()); + Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}}; + + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddressNoThrow("1.2.3.5"); + filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( + remote_address); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); EXPECT_EQ("internal_request", request_headers.get_(Http::Headers::get().EnvoyIpTags)); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.internal_request.hit").value(), 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 1); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); Http::TestRequestTrailerMapImpl request_trailers; EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); @@ -96,21 +562,19 @@ TEST_F(IpTaggingFilterTest, InternalRequest) { EXPECT_FALSE(request_headers.has(Http::Headers::get().EnvoyIpTags)); } -TEST_F(IpTaggingFilterTest, ExternalRequest) { - const std::string external_request_yaml = R"EOF( -request_type: external -ip_tags: - - ip_tag_name: external_request - ip_list: - - {address_prefix: 1.2.3.4, prefix_len: 32} -)EOF"; - initializeFilter(external_request_yaml); +INSTANTIATE_TEST_CASE_P(InternalRequest, InternalRequestIpTaggingFilterTest, + ::testing::ValuesIn({internal_request_config, + internal_request_with_json_file_config, + internal_request_with_yaml_file_config})); + +class ExternalRequestIpTaggingFilterTest : public IpTaggingFilterTest {}; + +TEST_P(ExternalRequestIpTaggingFilterTest, ExternalRequest) { + const std::string config = GetParam(); + initializeFilter(config); EXPECT_EQ(FilterRequestType::EXTERNAL, config_->requestType()); Http::TestRequestHeaderMapImpl request_headers; - EXPECT_CALL(stats_, counter("prefix.ip_tagging.total")); - EXPECT_CALL(stats_, counter("prefix.ip_tagging.external_request.hit")); - Network::Address::InstanceConstSharedPtr remote_address = Network::Utility::parseInternetAddressNoThrow("1.2.3.4"); filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -119,6 +583,9 @@ request_type: external EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); EXPECT_EQ("external_request", request_headers.get_(Http::Headers::get().EnvoyIpTags)); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.external_request.hit").value(), 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 1); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); Http::TestRequestTrailerMapImpl request_trailers; EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); @@ -129,26 +596,19 @@ request_type: external EXPECT_FALSE(request_headers.has(Http::Headers::get().EnvoyIpTags)); } -TEST_F(IpTaggingFilterTest, BothRequest) { - const std::string both_request_yaml = R"EOF( -request_type: both -ip_tags: - - ip_tag_name: external_request - ip_list: - - {address_prefix: 1.2.3.4, prefix_len: 32} - - ip_tag_name: internal_request - ip_list: - - {address_prefix: 1.2.3.5, prefix_len: 32} -)EOF"; +INSTANTIATE_TEST_CASE_P(ExternalRequest, ExternalRequestIpTaggingFilterTest, + ::testing::ValuesIn({external_request_config, + external_request_with_json_file_config, + external_request_with_yaml_file_config})); + +class BothRequestIpTaggingFilterTest : public IpTaggingFilterTest {}; - initializeFilter(both_request_yaml); +TEST_P(BothRequestIpTaggingFilterTest, BothRequest) { + const std::string config = GetParam(); + initializeFilter(config); EXPECT_EQ(FilterRequestType::BOTH, config_->requestType()); Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}}; - EXPECT_CALL(stats_, counter("prefix.ip_tagging.total")).Times(2); - EXPECT_CALL(stats_, counter("prefix.ip_tagging.internal_request.hit")); - EXPECT_CALL(stats_, counter("prefix.ip_tagging.external_request.hit")); - Network::Address::InstanceConstSharedPtr remote_address = Network::Utility::parseInternetAddressNoThrow("1.2.3.5"); filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -164,10 +624,22 @@ request_type: both EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); EXPECT_EQ("external_request", request_headers.get_(Http::Headers::get().EnvoyIpTags)); + + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.internal_request.hit").value(), 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.external_request.hit").value(), 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 2); } -TEST_F(IpTaggingFilterTest, NoHits) { - initializeFilter(internal_request_yaml); +INSTANTIATE_TEST_CASE_P(BothRequest, BothRequestIpTaggingFilterTest, + ::testing::ValuesIn({both_request_config, + both_request_with_json_file_config, + both_request_with_yaml_file_config})); + +class NoHitsIpTaggingFilterTest : public IpTaggingFilterTest {}; + +TEST_P(NoHitsIpTaggingFilterTest, NoHits) { + const std::string config = GetParam(); + initializeFilter(config); Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}}; Network::Address::InstanceConstSharedPtr remote_address = @@ -175,19 +647,27 @@ TEST_F(IpTaggingFilterTest, NoHits) { filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( remote_address); - EXPECT_CALL(stats_, counter("prefix.ip_tagging.no_hit")); - EXPECT_CALL(stats_, counter("prefix.ip_tagging.total")); - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); EXPECT_FALSE(request_headers.has(Http::Headers::get().EnvoyIpTags)); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.no_hit").value(), 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 1); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); Http::TestRequestTrailerMapImpl request_trailers; EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); } -TEST_F(IpTaggingFilterTest, AppendEntry) { - initializeFilter(internal_request_yaml); +INSTANTIATE_TEST_CASE_P(NoHits, NoHitsIpTaggingFilterTest, + ::testing::ValuesIn({internal_request_config, + internal_request_with_json_file_config, + internal_request_with_yaml_file_config})); + +class AppendEntryFilterTest : public IpTaggingFilterTest {}; + +TEST_P(AppendEntryFilterTest, AppendEntry) { + const std::string config = GetParam(); + initializeFilter(config); Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}, {"x-envoy-ip-tags", "test"}}; @@ -204,19 +684,17 @@ TEST_F(IpTaggingFilterTest, AppendEntry) { EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); } -TEST_F(IpTaggingFilterTest, ReplaceAlternateHeaderWhenActionIsDefaulted) { - const std::string internal_request_yaml = R"EOF( -request_type: internal -ip_tag_header: - header: x-envoy-optional-header -ip_tags: - - ip_tag_name: internal_request_with_optional_header - ip_list: - - {address_prefix: 1.2.3.4, prefix_len: 32} -)EOF"; +INSTANTIATE_TEST_CASE_P(AppendEntry, AppendEntryFilterTest, + ::testing::ValuesIn({internal_request_config, + internal_request_with_json_file_config, + internal_request_with_yaml_file_config})); - initializeFilter(internal_request_yaml); +class ReplaceAlternateHeaderWhenActionIsDefaultedFilterTest : public IpTaggingFilterTest {}; +TEST_P(ReplaceAlternateHeaderWhenActionIsDefaultedFilterTest, + ReplaceAlternateHeaderWhenActionIsDefaulted) { + const std::string config = GetParam(); + initializeFilter(config); Http::TestRequestHeaderMapImpl request_headers{ {"x-envoy-internal", "true"}, {"x-envoy-optional-header", "foo"}, // foo will be removed @@ -237,20 +715,17 @@ request_type: internal EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); } -TEST_F(IpTaggingFilterTest, ReplaceAlternateHeader) { - const std::string internal_request_yaml = R"EOF( -request_type: internal -ip_tag_header: - header: x-envoy-optional-header - action: SANITIZE -ip_tags: - - ip_tag_name: internal_request_with_optional_header - ip_list: - - {address_prefix: 1.2.3.4, prefix_len: 32} -)EOF"; +INSTANTIATE_TEST_CASE_P(ReplaceAlternateHeaderWhenActionIsDefaulted, + ReplaceAlternateHeaderWhenActionIsDefaultedFilterTest, + ::testing::ValuesIn({internal_request_with_header_config, + internal_request_with_header_with_json_file_config, + internal_request_with_header_with_yaml_file_config})); - initializeFilter(internal_request_yaml); +class ReplaceAlternateHeaderFilterTest : public IpTaggingFilterTest {}; +TEST_P(ReplaceAlternateHeaderFilterTest, ReplaceAlternateHeader) { + const std::string config = GetParam(); + initializeFilter(config); Http::TestRequestHeaderMapImpl request_headers{ {"x-envoy-internal", "true"}, {"x-envoy-optional-header", "foo"}}; // foo will be removed Network::Address::InstanceConstSharedPtr remote_address = @@ -267,22 +742,23 @@ request_type: internal EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); } -TEST_F(IpTaggingFilterTest, ClearAlternateHeaderWhenUnmatchedAndSanitized) { - const std::string internal_request_yaml = R"EOF( -request_type: internal -ip_tag_header: - header: x-envoy-optional-header - action: SANITIZE -ip_tags: - - ip_tag_name: internal_request_with_optional_header - ip_list: - - {address_prefix: 1.2.3.4, prefix_len: 32} -)EOF"; +INSTANTIATE_TEST_CASE_P( + ReplaceAlternateHeader, ReplaceAlternateHeaderFilterTest, + ::testing::ValuesIn({internal_request_with_replace_header_config, + internal_request_with_replace_header_with_json_file_config, + internal_request_with_replace_header_with_yaml_file_config})); - initializeFilter(internal_request_yaml); +class ClearAlternateHeaderWhenUnmatchedAndSanitizedFilterTest : public IpTaggingFilterTest {}; - Http::TestRequestHeaderMapImpl request_headers{ - {"x-envoy-internal", "true"}, {"x-envoy-optional-header", "foo"}}; // header will be removed +TEST_P(ClearAlternateHeaderWhenUnmatchedAndSanitizedFilterTest, + ClearAlternateHeaderWhenUnmatchedAndSanitized) { + const std::string config = GetParam(); + initializeFilter(config); + Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}, + {"x-envoy-optional-header", "foo"}}; // header + // will + // be + // removed Network::Address::InstanceConstSharedPtr remote_address = Network::Utility::parseInternetAddressNoThrow("1.2.3.5"); filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -296,20 +772,18 @@ request_type: internal EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); } -TEST_F(IpTaggingFilterTest, AppendForwardAlternateHeader) { - const std::string internal_request_yaml = R"EOF( -request_type: internal -ip_tag_header: - header: x-envoy-optional-header - action: APPEND_IF_EXISTS_OR_ADD -ip_tags: - - ip_tag_name: internal_request_with_optional_header - ip_list: - - {address_prefix: 1.2.3.4, prefix_len: 32} -)EOF"; +INSTANTIATE_TEST_CASE_P( + ClearAlternateHeaderWhenUnmatchedAndSanitized, + ClearAlternateHeaderWhenUnmatchedAndSanitizedFilterTest, + ::testing::ValuesIn({internal_request_with_replace_header_config, + internal_request_with_replace_header_with_json_file_config, + internal_request_with_replace_header_with_yaml_file_config})); - initializeFilter(internal_request_yaml); +class AppendForwardAlternateHeaderFilterTest : public IpTaggingFilterTest {}; +TEST_P(AppendForwardAlternateHeaderFilterTest, AppendForwardAlternateHeader) { + const std::string config = GetParam(); + initializeFilter(config); Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}, {"x-envoy-optional-header", "foo"}}; Network::Address::InstanceConstSharedPtr remote_address = @@ -326,20 +800,19 @@ request_type: internal EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); } -TEST_F(IpTaggingFilterTest, RetainAlternateHeaderWhenUnmatchedAndAppendForwarded) { - const std::string internal_request_yaml = R"EOF( -request_type: internal -ip_tag_header: - header: x-envoy-optional-header - action: APPEND_IF_EXISTS_OR_ADD -ip_tags: - - ip_tag_name: internal_request_with_optional_header - ip_list: - - {address_prefix: 1.2.3.4, prefix_len: 32} -)EOF"; +INSTANTIATE_TEST_CASE_P( + AppendForwardAlternateHeader, AppendForwardAlternateHeaderFilterTest, + ::testing::ValuesIn({internal_request_with_append_or_add_config, + internal_request_with_append_or_add_with_json_file_config, + internal_request_with_append_or_add_with_yaml_file_config})); - initializeFilter(internal_request_yaml); +class RetainAlternateHeaderWhenUnmatchedAndAppendForwardedFilterTest : public IpTaggingFilterTest { +}; +TEST_P(RetainAlternateHeaderWhenUnmatchedAndAppendForwardedFilterTest, + RetainAlternateHeaderWhenUnmatchedAndAppendForwarded) { + const std::string config = GetParam(); + initializeFilter(config); Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}, {"x-envoy-optional-header", "foo"}}; Network::Address::InstanceConstSharedPtr remote_address = @@ -355,19 +828,18 @@ request_type: internal EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); } -TEST_F(IpTaggingFilterTest, NestedPrefixes) { - const std::string duplicate_request_yaml = R"EOF( -request_type: both -ip_tags: - - ip_tag_name: duplicate_request - ip_list: - - {address_prefix: 1.2.3.4, prefix_len: 32} - - ip_tag_name: internal_request - ip_list: - - {address_prefix: 1.2.3.4, prefix_len: 32} -)EOF"; +INSTANTIATE_TEST_CASE_P( + RetainAlternateHeaderWhenUnmatchedAndAppendForwarded, + RetainAlternateHeaderWhenUnmatchedAndAppendForwardedFilterTest, + ::testing::ValuesIn({internal_request_with_append_or_add_config, + internal_request_with_append_or_add_with_json_file_config, + internal_request_with_append_or_add_with_yaml_file_config})); + +class NestedPrefixesFilterTest : public IpTaggingFilterTest {}; - initializeFilter(duplicate_request_yaml); +TEST_P(NestedPrefixesFilterTest, NestedPrefixes) { + const std::string config = GetParam(); + initializeFilter(config); Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}, {"x-envoy-ip-tags", "test"}}; @@ -376,12 +848,12 @@ request_type: both filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( remote_address); - EXPECT_CALL(stats_, counter("prefix.ip_tagging.total")); - EXPECT_CALL(stats_, counter("prefix.ip_tagging.internal_request.hit")); - EXPECT_CALL(stats_, counter("prefix.ip_tagging.duplicate_request.hit")); - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.internal_request.hit").value(), 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.duplicate_request.hit").value(), 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 1); + // There is no guarantee for the order tags are returned by the LC-Trie. const std::string header_tag_data = request_headers.get_(Http::Headers::get().EnvoyIpTags.get()); EXPECT_NE(std::string::npos, header_tag_data.find("test")); @@ -393,14 +865,16 @@ request_type: both EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); } -TEST_F(IpTaggingFilterTest, Ipv6Address) { - const std::string ipv6_addresses_yaml = R"EOF( -ip_tags: - - ip_tag_name: ipv6_request - ip_list: - - {address_prefix: 2001:abcd:ef01:2345:6789:abcd:ef01:234, prefix_len: 64} -)EOF"; - initializeFilter(ipv6_addresses_yaml); +INSTANTIATE_TEST_CASE_P(NestedPrefixes, NestedPrefixesFilterTest, + ::testing::ValuesIn({duplicate_request_config, + duplicate_request_with_json_file_config, + duplicate_request_with_yaml_file_config})); + +class Ipv6AddressTest : public IpTaggingFilterTest {}; + +TEST_P(Ipv6AddressTest, Ipv6Address) { + const std::string config = GetParam(); + initializeFilter(config); Http::TestRequestHeaderMapImpl request_headers; Network::Address::InstanceConstSharedPtr remote_address = @@ -416,8 +890,15 @@ TEST_F(IpTaggingFilterTest, Ipv6Address) { EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); } -TEST_F(IpTaggingFilterTest, RuntimeDisabled) { - initializeFilter(internal_request_yaml); +INSTANTIATE_TEST_CASE_P(Ipv6Address, Ipv6AddressTest, + ::testing::ValuesIn({ipv6_config, ipv6_with_json_file_config, + ipv6_with_yaml_file_config})); + +class RuntimeDisabledTest : public IpTaggingFilterTest {}; + +TEST_P(RuntimeDisabledTest, RuntimeDisabled) { + const std::string config = GetParam(); + initializeFilter(config); Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}}; EXPECT_CALL(runtime_.snapshot_, featureEnabled("ip_tagging.http_filter_enabled", 100)) @@ -429,8 +910,16 @@ TEST_F(IpTaggingFilterTest, RuntimeDisabled) { EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); } -TEST_F(IpTaggingFilterTest, ClearRouteCache) { - initializeFilter(internal_request_yaml); +INSTANTIATE_TEST_CASE_P(RuntimeDisabled, RuntimeDisabledTest, + ::testing::ValuesIn({internal_request_config, + internal_request_with_json_file_config, + internal_request_with_yaml_file_config})); + +class ClearRouteCacheTest : public IpTaggingFilterTest {}; + +TEST_P(ClearRouteCacheTest, ClearRouteCache) { + const std::string config = GetParam(); + initializeFilter(config); Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}}; Network::Address::InstanceConstSharedPtr remote_address = @@ -449,26 +938,294 @@ TEST_F(IpTaggingFilterTest, ClearRouteCache) { EXPECT_FALSE(request_headers.has(Http::Headers::get().EnvoyIpTags)); } -TEST_F(IpTaggingFilterTest, EmptyIpTags) { - const std::string external_request_yaml = R"EOF( -request_type: external -)EOF"; - initializeFilter(external_request_yaml, - "INVALID_ARGUMENT: HTTP IP Tagging Filter requires ip_tags to be specified."); +INSTANTIATE_TEST_CASE_P(ClearRouteCache, ClearRouteCacheTest, + ::testing::ValuesIn({internal_request_config, + internal_request_with_json_file_config, + internal_request_with_yaml_file_config})); + +TEST_F(IpTaggingFilterTest, InternalRequestWithReload) { + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml").c_str()); + + TestEnvironment::createPath(TestEnvironment::temporaryPath("ip_tagging_test")); + + const std::string yaml = + fmt::format(R"EOF( +request_type: internal +ip_tags_datasource: + filename: "{}" + watched_directory: + path: "{}" + )EOF", + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test")); + + TestEnvironment::writeStringToFileForTest( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), R"EOF( +ip_tags: + - ip_tag_name: internal_request + ip_list: + - {address_prefix: 1.2.3.5, prefix_len: 32} + )EOF", + true); + + TestEnvironment::writeStringToFileForTest( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml"), R"EOF( +ip_tags: + - ip_tag_name: internal_updated_request + ip_list: + - {address_prefix: 1.2.3.5, prefix_len: 32} + )EOF", + true); + initializeFilter(yaml); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + EXPECT_EQ(FilterRequestType::INTERNAL, config_->requestType()); + Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}}; + + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddressNoThrow("1.2.3.5"); + filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( + remote_address); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ("internal_request", request_headers.get_(Http::Headers::get().EnvoyIpTags)); + + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.internal_request.hit").value(), 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 1); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); + Http::TestRequestTrailerMapImpl request_trailers; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); + + // Check external requests don't get a tag. + request_headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_FALSE(request_headers.has(Http::Headers::get().EnvoyIpTags)); + + // Update the symlink to point to the new file. + TestEnvironment::renameFile( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test/watcher_old_target.yaml")); + TestEnvironment::renameFile( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml")); + + // Handle the events if any. + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + EXPECT_TRUE(TestUtility::waitForCounter(stats_, "prefix.ip_tagging.reload_success", + testing::Ge(1UL), time_system_)); + + filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( + remote_address); + + request_headers = {{"x-envoy-internal", "true"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ("internal_updated_request", request_headers.get_(Http::Headers::get().EnvoyIpTags)); + + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.internal_updated_request.hit").value(), 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 2); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); + request_trailers = Http::TestRequestTrailerMapImpl{}; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); + + // Remove the files. + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_old_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml").c_str()); } -TEST_F(IpTaggingFilterTest, InvalidCidr) { - const std::string external_request_yaml = R"EOF( -request_type: external +TEST_F(IpTaggingFilterTest, InternalRequestWithFailedReloadUsesOldData) { + time_system_.advanceTimeAsyncImpl(std::chrono::seconds(1)); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml").c_str()); + + TestEnvironment::createPath(TestEnvironment::temporaryPath("ip_tagging_test")); + + const std::string yaml = + fmt::format(R"EOF( +request_type: internal +ip_tags_datasource: + filename: "{}" + watched_directory: + path: "{}" + )EOF", + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test")); + + TestEnvironment::writeStringToFileForTest( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), R"EOF( ip_tags: - - ip_tag_name: fooooooo + - ip_tag_name: internal_request ip_list: - - {address_prefix: 12345.12345.12345.12345, prefix_len: 999999} -)EOF"; - initializeFilter( - external_request_yaml, - "INVALID_ARGUMENT: invalid ip/mask combo '12345.12345.12345.12345/999999' (format is " - "/<# mask bits>)"); + - {address_prefix: 1.2.3.5, prefix_len: 32} + )EOF", + true); + + TestEnvironment::writeStringToFileForTest( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml"), R"EOF( +ip_tags + )EOF", + true); + initializeFilter(yaml); + + EXPECT_EQ(FilterRequestType::INTERNAL, config_->requestType()); + Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}}; + + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddressNoThrow("1.2.3.5"); + filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( + remote_address); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ("internal_request", request_headers.get_(Http::Headers::get().EnvoyIpTags)); + + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.internal_request.hit").value(), 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 1); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); + Http::TestRequestTrailerMapImpl request_trailers; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); + + // Check external requests don't get a tag. + request_headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_FALSE(request_headers.has(Http::Headers::get().EnvoyIpTags)); + + // Handle the events if any. + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + // Update the symlink to point to the new file. + TestEnvironment::renameFile( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test/watcher_old_target.yaml")); + TestEnvironment::renameFile( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml")); + + // Handle the events if any. + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( + remote_address); + + request_headers = {{"x-envoy-internal", "true"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ("internal_request", request_headers.get_(Http::Headers::get().EnvoyIpTags)); + + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.internal_request.hit").value(), 2); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 2); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); + request_trailers = Http::TestRequestTrailerMapImpl{}; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); + + // Remove the files. + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_old_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml").c_str()); + dispatcher_->exit(); +} + +TEST_F(IpTaggingFilterTest, IpTagsReloadedInFlightRequestsNotAffected) { + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml").c_str()); + + TestEnvironment::createPath(TestEnvironment::temporaryPath("ip_tagging_test")); + + const std::string yaml = + fmt::format(R"EOF( +request_type: internal +ip_tags_datasource: + filename: "{}" + watched_directory: + path: "{}" + )EOF", + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test")); + + TestEnvironment::writeStringToFileForTest( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), R"EOF( +ip_tags: + - ip_tag_name: internal_request + ip_list: + - {address_prefix: 1.2.3.5, prefix_len: 32} + )EOF", + true); + + TestEnvironment::writeStringToFileForTest( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml"), R"EOF( +ip_tags: + - ip_tag_name: internal_updated_request + ip_list: + - {address_prefix: 1.2.3.5, prefix_len: 32} + )EOF", + true); + initializeFilter(yaml); + EXPECT_EQ(FilterRequestType::INTERNAL, config_->requestType()); + IpTaggingFilterPeer::synchronizer(filter_).enable(); + std::string sync_point_name = "_trie_lookup_complete"; + // Start a thread that issues request for ip tagging filter and wait in the worker thread right + // before performing lookup from the trie with ip tags. + IpTaggingFilterPeer::synchronizer(filter_).waitOn(sync_point_name); + std::thread t0([&] { + Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}}; + + Network::Address::InstanceConstSharedPtr remote_address = + Network::Utility::parseInternetAddressNoThrow("1.2.3.5"); + filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( + remote_address); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ("internal_request", request_headers.get_(Http::Headers::get().EnvoyIpTags)); + + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 1); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); + Http::TestRequestTrailerMapImpl request_trailers; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); + // Second request should get the updated ip tags. + filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( + remote_address); + + request_headers = {{"x-envoy-internal", "true"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ("internal_updated_request", request_headers.get_(Http::Headers::get().EnvoyIpTags)); + + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.internal_updated_request.hit").value(), + 1); + EXPECT_EQ(stats_.counterFromString("prefix.ip_tagging.total").value(), 2); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); + request_trailers = Http::TestRequestTrailerMapImpl{}; + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers)); + }); + // Wait until the thread is actually waiting. + IpTaggingFilterPeer::synchronizer(filter_).barrierOn(sync_point_name); + + // Update the symlink to point to the new file. + TestEnvironment::renameFile( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test/watcher_old_target.yaml")); + TestEnvironment::renameFile( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml")); + + // Handle the events if any. + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + EXPECT_TRUE(TestUtility::waitForCounter(stats_, "prefix.ip_tagging.reload_success", + testing::Ge(1UL), time_system_)); + + IpTaggingFilterPeer::synchronizer(filter_).signal(sync_point_name); + t0.join(); + // Remove the files. + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_old_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml").c_str()); + dispatcher_->exit(); } } // namespace diff --git a/test/extensions/filters/http/ip_tagging/ip_tagging_integration_test.cc b/test/extensions/filters/http/ip_tagging/ip_tagging_integration_test.cc index 0aef174daafa0..82ed4bd4f5d33 100644 --- a/test/extensions/filters/http/ip_tagging/ip_tagging_integration_test.cc +++ b/test/extensions/filters/http/ip_tagging/ip_tagging_integration_test.cc @@ -1,4 +1,5 @@ #include "test/integration/http_integration.h" +#include "test/test_common/environment.h" namespace Envoy { namespace { @@ -13,7 +14,10 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, IpTaggingIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); // Just IP tagging for now. -const char ExampleIpTaggingConfig[] = R"EOF( +const std::string ExampleIpTaggingConfig = R"EOF( + name: ip_tagging + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ip_tagging.v3.IPTagging request_type: both ip_tags: - ip_tag_name: external_request @@ -23,15 +27,188 @@ const char ExampleIpTaggingConfig[] = R"EOF( // Make sure that Envoy starts up with an ip tagging filter. TEST_P(IpTaggingIntegrationTest, IpTaggingV3StaticTypedStructConfig) { - config_helper_.prependFilter(absl::StrCat(R"EOF( -name: ip_tagging -typed_config: - "@type": type.googleapis.com/xds.type.v3.TypedStruct - type_url: type.googleapis.com/envoy.extensions.filters.http.ip_tagging.v3.IPTagging - value: + config_helper_.prependFilter(ExampleIpTaggingConfig); + initialize(); +} + +TEST_P(IpTaggingIntegrationTest, FileBasedIpTaggingWithReload) { + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml").c_str()); + + TestEnvironment::createPath(TestEnvironment::temporaryPath("ip_tagging_test")); + + const std::string yaml = + fmt::format(R"EOF( + name: ip_tagging + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ip_tagging.v3.IPTagging + request_type: both + ip_tags_datasource: + filename: "{}" + watched_directory: + path: "{}" + )EOF", + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test")); + + TestEnvironment::writeStringToFileForTest( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), R"EOF( +ip_tags: + - ip_tag_name: external_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} + )EOF", + true); + config_helper_.prependFilter(TestEnvironment::substitute(yaml)); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "1.2.3.4"}}); + + waitForNextUpstreamRequest(); + EXPECT_EQ(upstream_request_->headers().get(Http::Headers::get().EnvoyIpTags)[0]->value(), + "external_request"); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + // Simulate file update. + TestEnvironment::writeStringToFileForTest( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml"), R"EOF( +ip_tags: + - ip_tag_name: external_updated_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} + )EOF", + true); + + // Update the symlink to point to the new file. + TestEnvironment::renameFile( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test/watcher_old_target.yaml")); + TestEnvironment::renameFile( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml")); + + test_server_->waitForCounter("http.config_test.ip_tagging.reload_success", testing::Ge(2)); + + response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "1.2.3.4"}}); + + waitForNextUpstreamRequest(); + std::cerr << upstream_request_->headers() + .get(Http::Headers::get().EnvoyIpTags)[0] + ->value() + .getStringView() + << std::endl; + EXPECT_EQ(upstream_request_->headers().get(Http::Headers::get().EnvoyIpTags)[0]->value(), + "external_updated_request"); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + + // Remove the files. + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_old_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml").c_str()); +} + +TEST_P(IpTaggingIntegrationTest, IptaggingFilterWithReloadNoCrashOnLdsUpdate) { + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml").c_str()); + + TestEnvironment::createPath(TestEnvironment::temporaryPath("ip_tagging_test")); + + const std::string yaml = + fmt::format(R"EOF( + name: ip_tagging + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ip_tagging.v3.IPTagging + request_type: both + ip_tags_datasource: + filename: "{}" + watched_directory: + path: "{}" )EOF", - ExampleIpTaggingConfig)); + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test")); + + TestEnvironment::writeStringToFileForTest( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), R"EOF( +ip_tags: + - ip_tag_name: external_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} + )EOF", + true); + config_helper_.prependFilter(TestEnvironment::substitute(yaml)); initialize(); + + // LDS update to modify the listener and corresponding drain. + { + ConfigHelper new_config_helper(version_, config_helper_.bootstrap()); + new_config_helper.addConfigModifier( + [](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + listener->mutable_listener_filters_timeout()->set_seconds(10); + }); + new_config_helper.setLds("1"); + test_server_->waitForGauge("listener_manager.total_listeners_active", testing::Eq(1)); + test_server_->waitForCounter("listener_manager.lds.update_success", testing::Eq(1)); + test_server_->waitForGauge("listener_manager.total_listeners_draining", testing::Eq(0)); + } + + // Simulate file update. + TestEnvironment::writeStringToFileForTest( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml"), R"EOF( +ip_tags: + - ip_tag_name: external_updated_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} + )EOF", + true); + + // Update the symlink to point to the new file. + TestEnvironment::renameFile( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test/watcher_old_target.yaml")); + TestEnvironment::renameFile( + TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml"), + TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml")); + + test_server_->waitForCounter("http.config_test.ip_tagging.reload_success", testing::Ge(2)); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "1.2.3.4"}}); + + waitForNextUpstreamRequest(); + EXPECT_EQ(upstream_request_->headers().get(Http::Headers::get().EnvoyIpTags)[0]->value(), + "external_updated_request"); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + + // Remove the files. + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_old_target.yaml").c_str()); + unlink(TestEnvironment::temporaryPath("ip_tagging_test/watcher_new_target.yaml").c_str()); } } // namespace diff --git a/test/extensions/filters/http/ip_tagging/test_data/BUILD b/test/extensions/filters/http/ip_tagging/test_data/BUILD new file mode 100644 index 0000000000000..92f9b6dfe9ac6 --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/BUILD @@ -0,0 +1,13 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +filegroup( + name = "ip_tagging_files", + srcs = glob(["*"]), +) diff --git a/test/extensions/filters/http/ip_tagging/test_data/empty_file.yaml b/test/extensions/filters/http/ip_tagging/test_data/empty_file.yaml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/extensions/filters/http/ip_tagging/test_data/invalid_tags.json b/test/extensions/filters/http/ip_tagging/test_data/invalid_tags.json new file mode 100644 index 0000000000000..3b12f20bec6c0 --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/invalid_tags.json @@ -0,0 +1 @@ +ip_tags diff --git a/test/extensions/filters/http/ip_tagging/test_data/invalid_tags.yaml b/test/extensions/filters/http/ip_tagging/test_data/invalid_tags.yaml new file mode 100644 index 0000000000000..3b12f20bec6c0 --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/invalid_tags.yaml @@ -0,0 +1 @@ +ip_tags diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_both.json b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_both.json new file mode 100644 index 0000000000000..19481df2a6c62 --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_both.json @@ -0,0 +1,22 @@ +{ +"ip_tags": [ + { + "ip_tag_name": "external_request", + "ip_list": [ + { + "address_prefix": "1.2.3.4", + "prefix_len": 32 + } + ] + }, + { + "ip_tag_name": "internal_request", + "ip_list": [ + { + "address_prefix": "1.2.3.5", + "prefix_len": 32 + } + ] + } +] +} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_both.yaml b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_both.yaml new file mode 100644 index 0000000000000..639fce3146c6d --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_both.yaml @@ -0,0 +1,7 @@ +ip_tags: +- ip_tag_name: external_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} +- ip_tag_name: internal_request + ip_list: + - {address_prefix: 1.2.3.5, prefix_len: 32} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_external_request.json b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_external_request.json new file mode 100644 index 0000000000000..0b5f0ddc4f97d --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_external_request.json @@ -0,0 +1,13 @@ +{ +"ip_tags": [ + { + "ip_tag_name": "external_request", + "ip_list": [ + { + "address_prefix": "1.2.3.4", + "prefix_len": 32 + } + ] + } +] +} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_external_request.yaml b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_external_request.yaml new file mode 100644 index 0000000000000..fbea0dc37e732 --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_external_request.yaml @@ -0,0 +1,4 @@ +ip_tags: +- ip_tag_name: external_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.json b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.json new file mode 100644 index 0000000000000..ce55c2d9af943 --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.json @@ -0,0 +1,13 @@ +{ + "ip_tags": [ + { + "ip_tag_name": "internal_request", + "ip_list": [ + { + "address_prefix": "1.2.3.5", + "prefix_len": 32 + } + ] + } + ] +} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.yaml b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.yaml new file mode 100644 index 0000000000000..f535ae03a6465 --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_internal_request.yaml @@ -0,0 +1,4 @@ +ip_tags: +- ip_tag_name: internal_request + ip_list: + - {address_prefix: 1.2.3.5, prefix_len: 32} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_invalid_cidr.json b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_invalid_cidr.json new file mode 100644 index 0000000000000..7c36e57ca54fa --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_invalid_cidr.json @@ -0,0 +1,13 @@ +{ + "ip_tags": [ + { + "ip_tag_name": "fooooooo", + "ip_list": [ + { + "address_prefix": "12345.12345.12345.12345", + "prefix_len": 999999 + } + ] + } + ] +} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_invalid_cidr.yaml b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_invalid_cidr.yaml new file mode 100644 index 0000000000000..f5c21970f215e --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_invalid_cidr.yaml @@ -0,0 +1,4 @@ +ip_tags: +- ip_tag_name: fooooooo + ip_list: + - {address_prefix: 12345.12345.12345.12345, prefix_len: 999999} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_updated_external_request.json b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_updated_external_request.json new file mode 100644 index 0000000000000..cbe852f4fc078 --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_updated_external_request.json @@ -0,0 +1,22 @@ +{ +"ip_tags": [ + { + "ip_tag_name": "external_updated_request", + "ip_list": [ + { + "address_prefix": "1.2.3.4", + "prefix_len": 32 + } + ] + }, + { + "ip_tag_name": "gcp_zone_a_request", + "ip_list": [ + { + "address_prefix": "1.2.3.4", + "prefix_len": 32 + } + ] + } +] +} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_updated_internal_request.yaml b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_updated_internal_request.yaml new file mode 100644 index 0000000000000..178179d3675c1 --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_updated_internal_request.yaml @@ -0,0 +1,4 @@ +ip_tags: +- ip_tag_name: internal_updated_request + ip_list: + - {address_prefix: 1.2.3.5, prefix_len: 32} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_duplicate_request.json b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_duplicate_request.json new file mode 100644 index 0000000000000..9e5aaedd7dfce --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_duplicate_request.json @@ -0,0 +1,22 @@ +{ + "ip_tags": [ + { + "ip_tag_name": "duplicate_request", + "ip_list": [ + { + "address_prefix": "1.2.3.4", + "prefix_len": 32 + } + ] + }, + { + "ip_tag_name": "internal_request", + "ip_list": [ + { + "address_prefix": "1.2.3.4", + "prefix_len": 32 + } + ] + } + ] +} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_duplicate_request.yaml b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_duplicate_request.yaml new file mode 100644 index 0000000000000..340c59fc6af0f --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_duplicate_request.yaml @@ -0,0 +1,7 @@ +ip_tags: +- ip_tag_name: duplicate_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} +- ip_tag_name: internal_request + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.json b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.json new file mode 100644 index 0000000000000..036582c21edbe --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.json @@ -0,0 +1,13 @@ +{ +"ip_tags": [ + { + "ip_tag_name": "internal_request_with_optional_header", + "ip_list": [ + { + "address_prefix": "1.2.3.4", + "prefix_len": 32 + } + ] + } +] +} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.yaml b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.yaml new file mode 100644 index 0000000000000..9d0df491dc1ea --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ip_tags_with_header.yaml @@ -0,0 +1,4 @@ +ip_tags: +- ip_tag_name: internal_request_with_optional_header + ip_list: + - {address_prefix: 1.2.3.4, prefix_len: 32} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ipv6_request.json b/test/extensions/filters/http/ip_tagging/test_data/ipv6_request.json new file mode 100644 index 0000000000000..a30a77b87a62c --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ipv6_request.json @@ -0,0 +1,13 @@ +{ + "ip_tags": [ + { + "ip_tag_name": "ipv6_request", + "ip_list": [ + { + "address_prefix": "2001:abcd:ef01:2345:6789:abcd:ef01:234", + "prefix_len": 64 + } + ] + } + ] +} diff --git a/test/extensions/filters/http/ip_tagging/test_data/ipv6_request.yaml b/test/extensions/filters/http/ip_tagging/test_data/ipv6_request.yaml new file mode 100644 index 0000000000000..9eb84ff19abde --- /dev/null +++ b/test/extensions/filters/http/ip_tagging/test_data/ipv6_request.yaml @@ -0,0 +1,4 @@ +ip_tags: +- ip_tag_name: ipv6_request + ip_list: + - {address_prefix: 2001:abcd:ef01:2345:6789:abcd:ef01:234, prefix_len: 64} diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index e360e7154626e..27cbbb37425ca 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1442,6 +1442,7 @@ str streambuf strerr strerror +string stringbuf stringified stringify From 0d379507474959fed589df5f6d1cb166338bd6ac Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 18 May 2026 10:47:07 -0400 Subject: [PATCH 333/750] Add configuration for RLS response metadata namespace (#44531) Add an option for modifying the default namespace where HTTP filter stores metadata from the rate limit service response. This allows rate limit results to influence behavior of other extensions. Risk Level: low (new config option) Testing: unit tests Docs Changes: yes Release Notes: yes Platform Specific Features: no --------- Signed-off-by: Yan Avlasov --- .../http/ratelimit/v3/rate_limit.proto | 6 +- api/envoy/service/ratelimit/v3/rls.proto | 4 +- changelogs/current.yaml | 4 +- .../http/http_filters/rate_limit_filter.rst | 4 ++ .../filters/http/ratelimit/ratelimit.cc | 2 +- .../filters/http/ratelimit/ratelimit.h | 6 +- .../filters/http/ratelimit/ratelimit_test.cc | 58 +++++++++++++++++++ 7 files changed, 79 insertions(+), 5 deletions(-) diff --git a/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto b/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto index cd8152eb156ae..0286617ee92ee 100644 --- a/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto +++ b/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto @@ -23,7 +23,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Rate limit :ref:`configuration overview `. // [#extension: envoy.filters.http.ratelimit] -// [#next-free-field: 18] +// [#next-free-field: 19] message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.rate_limit.v2.RateLimit"; @@ -186,6 +186,10 @@ message RateLimit { // 3. :ref:`disable_key `. // 4. :ref:`override limit `. repeated config.route.v3.RateLimit rate_limits = 17; + + // The namespace where dynamic metadata from rate limit response is saved. + // If not set, the default is "envoy.filters.http.ratelimit". + string metadata_namespace = 18; } message RateLimitPerRoute { diff --git a/api/envoy/service/ratelimit/v3/rls.proto b/api/envoy/service/ratelimit/v3/rls.proto index 63f2477a664e6..8399255fe8391 100644 --- a/api/envoy/service/ratelimit/v3/rls.proto +++ b/api/envoy/service/ratelimit/v3/rls.proto @@ -209,7 +209,9 @@ message RateLimitResponse { // filter. This metadata lives in a namespace specified by the canonical name of extension filter // that requires it: // - // - :ref:`envoy.filters.http.ratelimit ` for HTTP filter. + // - :ref:`envoy.filters.http.ratelimit ` for HTTP filter. The default namespace can + // be modified by setting the :ref:`metadata_namespace ` + // in the filter configuration. // - :ref:`envoy.filters.network.ratelimit ` for network filter. // - :ref:`envoy.filters.thrift.rate_limit ` for Thrift filter. google.protobuf.Struct dynamic_metadata = 6; diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8d9f5dc0f662a..7dd63fd635767 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -232,7 +232,6 @@ new_features: change: | Added ``upstream.server_name`` CEL attribute returning the SNI from the established upstream TLS connection. - - area: tls change: | Added substitution commands ``%DOWNSTREAM_TLS_GROUP%`` and ``%UPSTREAM_TLS_GROUP%``. The TLS group may be used @@ -247,6 +246,9 @@ new_features: Enabled Balsa parser for HTTP inspector by default. This behavior can be temporarily reverted by setting the runtime guard ``envoy.reloadable_features.http_inspector_use_balsa_parser`` to ``false``. This runtime guard will be removed in a future release of Envoy. +- area: ratelimit + change: | + Make namespace for storing rate limit service response metadata configurable. - area: tcp_proxy change: | diff --git a/docs/root/configuration/http/http_filters/rate_limit_filter.rst b/docs/root/configuration/http/http_filters/rate_limit_filter.rst index 69823cdbab17b..0e349ce0d36d0 100644 --- a/docs/root/configuration/http/http_filters/rate_limit_filter.rst +++ b/docs/root/configuration/http/http_filters/rate_limit_filter.rst @@ -173,6 +173,10 @@ The ratelimit filter emits dynamic metadata as an opaque ``google.protobuf.Struc ` with a filled :ref:`dynamic_metadata ` field. +By default metadata is stored under the ``envoy.filters.http.ratelimit`` namespace. The namespace can be changed by setting the +:ref:`metadata_namespace ` in the filter +configuration. + Runtime ------- diff --git a/source/extensions/filters/http/ratelimit/ratelimit.cc b/source/extensions/filters/http/ratelimit/ratelimit.cc index be876e74d712c..feea04556827e 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit.cc +++ b/source/extensions/filters/http/ratelimit/ratelimit.cc @@ -227,7 +227,7 @@ void Filter::complete(Filters::Common::RateLimit::LimitStatus status, Filters::Common::RateLimit::StatNames& stat_names = config_->statNames(); if (dynamic_metadata != nullptr && !dynamic_metadata->fields().empty()) { - callbacks_->streamInfo().setDynamicMetadata("envoy.filters.http.ratelimit", *dynamic_metadata); + callbacks_->streamInfo().setDynamicMetadata(config_->metadataNamespace(), *dynamic_metadata); } switch (status) { diff --git a/source/extensions/filters/http/ratelimit/ratelimit.h b/source/extensions/filters/http/ratelimit/ratelimit.h index 049bae1732949..d2ea75446f5fa 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit.h +++ b/source/extensions/filters/http/ratelimit/ratelimit.h @@ -79,7 +79,9 @@ class FilterConfig { ? absl::optional( Envoy::Runtime::FractionalPercent( config.failure_mode_deny_percent(), runtime_)) - : absl::nullopt) { + : absl::nullopt), + metadata_namespace_(config.metadata_namespace().empty() ? "envoy.filters.http.ratelimit" + : config.metadata_namespace()) { absl::StatusOr response_headers_parser_or_ = Envoy::Router::HeaderParser::configure(config.response_headers_to_add()); SET_AND_RETURN_IF_NOT_OK(response_headers_parser_or_.status(), creation_status); @@ -89,6 +91,7 @@ class FilterConfig { } const std::string& domain() const { return domain_; } + const std::string& metadataNamespace() const { return metadata_namespace_; } const LocalInfo::LocalInfo& localInfo() const { return local_info_; } uint64_t stage() const { return stage_; } Runtime::Loader& runtime() { return runtime_; } @@ -172,6 +175,7 @@ class FilterConfig { const absl::optional filter_enforced_; const absl::optional failure_mode_deny_percent_; std::unique_ptr rate_limit_config_; + const std::string metadata_namespace_; }; using FilterConfigSharedPtr = std::shared_ptr; diff --git a/test/extensions/filters/http/ratelimit/ratelimit_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_test.cc index 5f6de2adc7cc1..13d5aa7f7d0d9 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_test.cc @@ -844,6 +844,64 @@ TEST_F(HttpRateLimitFilterTest, LimitResponseWithDynamicMetadata) { EXPECT_EQ("request_rate_limited", filter_callbacks_.details()); } +TEST_F(HttpRateLimitFilterTest, LimitResponseWithCustomDynamicMetadataNamespace) { + const std::string filter_config_with_custom_metadata_namespace = R"EOF( + domain: foo + metadata_namespace: custom.namespace + )EOF"; + + setUpTest(filter_config_with_custom_metadata_namespace); + InSequence s; + + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); + EXPECT_CALL(*client_, limit(_, _, _, _, _, 0)) + .WillOnce( + WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { + request_callbacks_ = &callbacks; + }))); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers_, false)); + + Filters::Common::RateLimit::DynamicMetadataPtr dynamic_metadata = + std::make_unique(); + auto* fields = dynamic_metadata->mutable_fields(); + (*fields)["name"] = ValueUtil::stringValue("my-limit"); + (*fields)["x"] = ValueUtil::numberValue(3); + EXPECT_CALL(filter_callbacks_.stream_info_, setDynamicMetadata(_, _)) + .WillOnce(Invoke([&dynamic_metadata](const std::string& ns, + const Protobuf::Struct& returned_dynamic_metadata) { + EXPECT_EQ(ns, "custom.namespace"); + EXPECT_TRUE(TestUtility::protoEqual(returned_dynamic_metadata, *dynamic_metadata)); + })); + + EXPECT_CALL(filter_callbacks_.stream_info_, + setResponseFlag(StreamInfo::CoreResponseFlag::RateLimited)); + + Http::ResponseHeaderMapPtr h{new Http::TestResponseHeaderMapImpl()}; + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "429"}, + {"x-envoy-ratelimited", Http::Headers::get().EnvoyRateLimitedValues.True}}; + EXPECT_CALL(filter_callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), true)); + EXPECT_CALL(filter_callbacks_, continueDecoding()).Times(0); + + request_callbacks_->complete(Filters::Common::RateLimit::LimitStatus::OverLimit, nullptr, + std::move(h), nullptr, "", std::move(dynamic_metadata)); + + EXPECT_EQ(1U, filter_callbacks_.clusterInfo() + ->statsScope() + .counterFromStatName(ratelimit_over_limit_) + .value()); + EXPECT_EQ( + 1U, + filter_callbacks_.clusterInfo()->statsScope().counterFromStatName(upstream_rq_4xx_).value()); + EXPECT_EQ( + 1U, + filter_callbacks_.clusterInfo()->statsScope().counterFromStatName(upstream_rq_429_).value()); + EXPECT_EQ("request_rate_limited", filter_callbacks_.details()); +} + TEST_F(HttpRateLimitFilterTest, LimitResponseWithHeaders) { setUpTest(filter_config_); InSequence s; From a403873c2ac89b2e80b01e0d17b2f1f6677f59d2 Mon Sep 17 00:00:00 2001 From: Paul Ogilby Date: Mon, 18 May 2026 11:21:53 -0400 Subject: [PATCH 334/750] mobile: support Python 3.12, 3.13, and 3.14 for Envoy Mobile wheels (#45067) Commit Message: mobile: support Python 3.12, 3.13, and 3.14 for Envoy Mobile wheels Additional Description: - Removed hardcoded Python 3.12 ABI detection. - Registered Python 3.13 and 3.14 toolchains. - Added custom python_version flag to allow selecting target version. - Updated py_wheel to use select for tags based on selected version. - Upgraded pybind11 to 2.13.6 to support Python 3.13+. - Removed unused mobile/bazel/python.bzl. Signed-off-by: Paul Ogilby --- mobile/WORKSPACE | 13 +++-- mobile/bazel/envoy_mobile_repositories.bzl | 6 +-- mobile/bazel/python.bzl | 62 ---------------------- mobile/library/python/BUILD | 33 ++++++++++-- 4 files changed, 42 insertions(+), 72 deletions(-) delete mode 100644 mobile/bazel/python.bzl diff --git a/mobile/WORKSPACE b/mobile/WORKSPACE index d05da15a484d9..abc7e68b5ac0f 100644 --- a/mobile/WORKSPACE +++ b/mobile/WORKSPACE @@ -73,11 +73,16 @@ load("@envoy_mobile//bazel:platforms.bzl", "envoy_mobile_platforms") envoy_mobile_platforms() -load("//bazel:python.bzl", "declare_python_abi") +load("@rules_python//python:repositories.bzl", "python_register_toolchains") -declare_python_abi( - name = "python_abi", - python_version = "3", +python_register_toolchains( + name = "python3_13", + python_version = "3.13.0", +) + +python_register_toolchains( + name = "python3_14", + python_version = "3.14.0", ) load("@mobile_pip3//:requirements.bzl", pip_dependencies = "install_deps") diff --git a/mobile/bazel/envoy_mobile_repositories.bzl b/mobile/bazel/envoy_mobile_repositories.bzl index ec48107d1e0f7..ac6c1d1739fc7 100644 --- a/mobile/bazel/envoy_mobile_repositories.bzl +++ b/mobile/bazel/envoy_mobile_repositories.bzl @@ -25,9 +25,9 @@ def python_repos(): http_archive( name = "pybind11", build_file = "@pybind11_bazel//:pybind11-BUILD.bazel", - sha256 = "d475978da0cdc2d43b73f30910786759d593a9d8ee05b1b6846d1eb16c6d2e0c", - strip_prefix = "pybind11-2.11.1", - urls = ["https://github.com/pybind/pybind11/archive/refs/tags/v2.11.1.tar.gz"], + sha256 = "e08cb87f4773da97fa7b5f035de8763abc656d87d5773e62f6da0587d1f0ec20", + strip_prefix = "pybind11-2.13.6", + urls = ["https://github.com/pybind/pybind11/archive/refs/tags/v2.13.6.tar.gz"], ) diff --git a/mobile/bazel/python.bzl b/mobile/bazel/python.bzl deleted file mode 100644 index f6e4beecc6bbd..0000000000000 --- a/mobile/bazel/python.bzl +++ /dev/null @@ -1,62 +0,0 @@ -abi_bzl_template = """\ -def python_tag(): - return "{python_tag}" - -def abi_tag(): - return "{abi_tag}" -""" - -def _get_python_bin(rctx): - python_label = Label("@python3_12_host//:bin/python3") - python_bin = str(rctx.path(python_label)) - if not python_bin: - fail("failed to get python bin") - return python_bin - -def _get_python_tag(rctx, python_bin): - result = rctx.execute([ - python_bin, - "-c", - "import platform;" + - "assert platform.python_implementation() == 'CPython';" + - "version = platform.python_version_tuple();" + - "print(f'cp{version[0]}{version[1]}')", - ]) - if result.return_code != 0: - fail("Failed to get python tag: " + result.stderr) - return result.stdout.splitlines()[0] - -def _get_abi_tag(rctx, python_bin): - result = rctx.execute([ - python_bin, - "-c", - "import platform;" + - "import sys;" + - "assert platform.python_implementation() == 'CPython';" + - "version = platform.python_version_tuple();" + - "print(f'cp{version[0]}{version[1]}{sys.abiflags}')", - ]) - if result.return_code != 0: - fail("Failed to get abi tag: " + result.stderr) - return result.stdout.splitlines()[0] - -def _declare_python_abi_impl(rctx): - python_bin = _get_python_bin(rctx) - python_tag = _get_python_tag(rctx, python_bin) - abi_tag = _get_abi_tag(rctx, python_bin) - rctx.file("BUILD") - rctx.file( - "abi.bzl", - abi_bzl_template.format( - python_tag = python_tag, - abi_tag = abi_tag, - ), - ) - -declare_python_abi = repository_rule( - implementation = _declare_python_abi_impl, - attrs = { - "python_version": attr.string(mandatory = True), - }, - local = True, -) diff --git a/mobile/library/python/BUILD b/mobile/library/python/BUILD index 9b21450cd4341..ee0c2198d1f19 100644 --- a/mobile/library/python/BUILD +++ b/mobile/library/python/BUILD @@ -1,7 +1,6 @@ load("@bazel_skylib//rules:common_settings.bzl", "string_flag") load("@mobile_pip3//:requirements.bzl", "requirement") load("@pybind11_bazel//:build_defs.bzl", "pybind_extension", "pybind_library") -load("@python_abi//:abi.bzl", "abi_tag", "python_tag") load("@rules_python//python:defs.bzl", "py_library") load("@rules_python//python:packaging.bzl", "py_package", "py_wheel") @@ -29,6 +28,26 @@ config_setting( flag_values = {":python_platform": "macosx_11_0_arm64"}, ) +string_flag( + name = "python_version", + build_setting_default = "3.12", +) + +config_setting( + name = "is_python_3_12", + flag_values = {":python_version": "3.12"}, +) + +config_setting( + name = "is_python_3_13", + flag_values = {":python_version": "3.13"}, +) + +config_setting( + name = "is_python_3_14", + flag_values = {":python_version": "3.14"}, +) + pybind_library( name = "envoy_engine_lib", srcs = [ @@ -111,7 +130,11 @@ py_package( # --//library/python:python_platform="manylinux2014_x86_64" \ py_wheel( name = "envoy_mobile_wheel", - abi = abi_tag(), + abi = select({ + ":is_python_3_14": "cp314", + ":is_python_3_13": "cp313", + "//conditions:default": "cp312", + }), distribution = "envoy-mobile-client", platform = select({ ":is_linux_x86_64": "manylinux2014_x86_64", @@ -119,7 +142,11 @@ py_wheel( ":is_macos_arm64": "macosx_11_0_arm64", "//conditions:default": "any", }), - python_tag = python_tag(), + python_tag = select({ + ":is_python_3_14": "cp314", + ":is_python_3_13": "cp313", + "//conditions:default": "cp312", + }), strip_path_prefixes = [ "mobile/library/python", "library/python", From 55120d7a55b8e0f792ccc95986328564a2488dfe Mon Sep 17 00:00:00 2001 From: "Antonio V. Leonti" <53806445+antoniovleonti@users.noreply.github.com> Date: Mon, 18 May 2026 11:38:15 -0400 Subject: [PATCH 335/750] gcp_authn: Move token fetching logic into client interface (#44929) Commit Message: (gcp_authn) Move token fetching logic into client interface Additional Description: This is to prepare the filter to support multiple token types (jwt [currently supported], bound jwt [new], access tokens [new], bound access tokens [new]). jwt and access tokens require different code for sending requests & parsing responses. this will be abstracted behind this client interface. For now this is a pure refactor. No behavior change. Risk Level: low / none (no behavior change) Testing: existing tests pass, new tests pass Docs Changes: none (no behavior change) Release Notes: none I used generative AI to help with this change. --------- Signed-off-by: antoniovleonti --- .../extensions/filters/http/gcp_authn/BUILD | 23 +- .../filters/http/gcp_authn/gcp_authn_client.h | 52 +++++ ...authn_impl.cc => gcp_authn_client_impl.cc} | 48 ++-- .../http/gcp_authn/gcp_authn_client_impl.h | 52 +++++ .../http/gcp_authn/gcp_authn_filter.cc | 43 ++-- .../filters/http/gcp_authn/gcp_authn_filter.h | 11 +- test/extensions/filters/http/gcp_authn/BUILD | 18 ++ .../gcp_authn/gcp_authn_client_impl_test.cc | 220 ++++++++++++++++++ .../http/gcp_authn/gcp_authn_filter_test.cc | 49 ++-- .../extensions/filters/http/gcp_authn/mocks.h | 10 +- 10 files changed, 451 insertions(+), 75 deletions(-) create mode 100644 source/extensions/filters/http/gcp_authn/gcp_authn_client.h rename source/extensions/filters/http/gcp_authn/{gcp_authn_impl.cc => gcp_authn_client_impl.cc} (67%) create mode 100644 source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.h create mode 100644 test/extensions/filters/http/gcp_authn/gcp_authn_client_impl_test.cc diff --git a/source/extensions/filters/http/gcp_authn/BUILD b/source/extensions/filters/http/gcp_authn/BUILD index 18aca1f7e04cd..c16d38a602243 100644 --- a/source/extensions/filters/http/gcp_authn/BUILD +++ b/source/extensions/filters/http/gcp_authn/BUILD @@ -14,8 +14,8 @@ envoy_cc_library( srcs = ["gcp_authn_filter.cc"], hdrs = ["gcp_authn_filter.h"], deps = [ - "gcp_authn_lib", "token_cache", + ":gcp_authn_client_impl", "//source/common/http:headers_lib", "//source/common/http:message_lib", "//source/common/http:utility_lib", @@ -27,17 +27,26 @@ envoy_cc_library( ) envoy_cc_library( - name = "gcp_authn_lib", - srcs = ["gcp_authn_impl.cc"], - hdrs = ["gcp_authn_impl.h"], + name = "gcp_authn_client_interface", + hdrs = ["gcp_authn_client.h"], deps = [ - "token_cache", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/types:optional", + "@envoy_api//envoy/extensions/filters/http/gcp_authn/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "gcp_authn_client_impl", + srcs = ["gcp_authn_client_impl.cc"], + hdrs = ["gcp_authn_client_impl.h"], + deps = [ + ":gcp_authn_client_interface", "//source/common/http:headers_lib", "//source/common/http:message_lib", "//source/common/http:utility_lib", "//source/extensions/filters/http/common:factory_base_lib", - "//source/extensions/filters/http/common:pass_through_filter_lib", - "@abseil-cpp//absl/types:optional", + "@abseil-cpp//absl/status:statusor", "@envoy_api//envoy/extensions/filters/http/gcp_authn/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/http/gcp_authn/gcp_authn_client.h b/source/extensions/filters/http/gcp_authn/gcp_authn_client.h new file mode 100644 index 0000000000000..2b31d7232e7ff --- /dev/null +++ b/source/extensions/filters/http/gcp_authn/gcp_authn_client.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.pb.h" + +#include "absl/status/statusor.h" +#include "absl/types/optional.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace GcpAuthn { + +/** + * Abstract interface for GcpAuthnClient. + */ +class GcpAuthnClient { +public: + class Callbacks { + public: + virtual ~Callbacks() = default; + + /** + * Called on completion of a token request. + * + * @param token the StatusOr containing the retrieved token string or an error status. + */ + virtual void onComplete(absl::StatusOr token) PURE; + }; + + virtual ~GcpAuthnClient() = default; + + /** + * Fetch a token. + * + * @param audience the Audience proto containing the audience URL. + * @param callbacks the callbacks to be notified when the token fetch completes. + */ + virtual void fetchToken(const envoy::extensions::filters::http::gcp_authn::v3::Audience& audience, + Callbacks& callbacks) PURE; + + /** + * Cancel the active request. + */ + virtual void cancel() PURE; +}; + +} // namespace GcpAuthn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/gcp_authn/gcp_authn_impl.cc b/source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.cc similarity index 67% rename from source/extensions/filters/http/gcp_authn/gcp_authn_impl.cc rename to source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.cc index 8dfa68cbab0ce..a6de71254b8d3 100644 --- a/source/extensions/filters/http/gcp_authn/gcp_authn_impl.cc +++ b/source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.cc @@ -1,16 +1,21 @@ -#include "source/extensions/filters/http/gcp_authn/gcp_authn_impl.h" +#include "source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.h" #include "source/common/common/enum_to_int.h" #include "source/common/http/header_map_impl.h" #include "source/common/http/utility.h" #include "absl/strings/str_format.h" +#include "absl/strings/str_replace.h" namespace Envoy { namespace Extensions { namespace HttpFilters { namespace GcpAuthn { +namespace { +constexpr absl::string_view UrlString = + "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/" + "identity?audience=[AUDIENCE]"; constexpr char MetadataFlavorKey[] = "Metadata-Flavor"; constexpr char MetadataFlavor[] = "Google"; @@ -27,8 +32,11 @@ Http::RequestMessagePtr buildRequest(absl::string_view url) { return std::make_unique(std::move(headers)); } +} // namespace -void GcpAuthnClient::fetchToken(RequestCallbacks& callbacks, Http::RequestMessagePtr&& request) { +void GcpAuthnClientImpl::fetchToken( + const envoy::extensions::filters::http::gcp_authn::v3::Audience& audience, + GcpAuthnClient::Callbacks& callbacks) { // Cancel any active requests. cancel(); ASSERT(callbacks_ == nullptr); @@ -41,9 +49,8 @@ void GcpAuthnClient::fetchToken(RequestCallbacks& callbacks, Http::RequestMessag // Failed to fetch the token if the cluster is not configured. if (thread_local_cluster == nullptr) { - ENVOY_LOG(error, "Failed to fetch the token: [cluster = {}] is not found or configured.", - cluster); - onError(); + onError(absl::StrFormat("Failed to fetch the token: [cluster = %s] is not found or configured.", + cluster)); return; } @@ -64,54 +71,53 @@ void GcpAuthnClient::fetchToken(RequestCallbacks& callbacks, Http::RequestMessag options.setBufferBodyForRetry(true); } + std::string final_url = absl::StrReplaceAll(UrlString, {{"[AUDIENCE]", audience.url()}}); active_request_ = - thread_local_cluster->httpAsyncClient().send(std::move(request), *this, options); + thread_local_cluster->httpAsyncClient().send(buildRequest(final_url), *this, options); } -void GcpAuthnClient::onSuccess(const Http::AsyncClient::Request&, - Http::ResponseMessagePtr&& response) { +void GcpAuthnClientImpl::onSuccess(const Http::AsyncClient::Request&, + Http::ResponseMessagePtr&& response) { auto status = Envoy::Http::Utility::getResponseStatusOrNullopt(response->headers()); active_request_ = nullptr; if (status.has_value()) { uint64_t status_code = status.value(); if (status_code == Envoy::enumToInt(Envoy::Http::Code::OK)) { ASSERT(callbacks_ != nullptr); - callbacks_->onComplete(response.get()); + callbacks_->onComplete(response->bodyAsString()); callbacks_ = nullptr; } else { - ENVOY_LOG(error, "Response status is not OK, status: {}", status_code); - onError(); + onError(absl::StrFormat("Response status is not OK, status: %d", status_code)); } } else { // This occurs if the response headers are invalid. - ENVOY_LOG(error, "Failed to get the response because response headers are not valid."); - onError(); + onError("Failed to get the response because response headers are not valid."); } } -void GcpAuthnClient::onFailure(const Http::AsyncClient::Request&, - Http::AsyncClient::FailureReason reason) { - // TODO(botengyao): handle different failure reasons. +void GcpAuthnClientImpl::onFailure(const Http::AsyncClient::Request&, + Http::AsyncClient::FailureReason reason) { ASSERT(reason == Http::AsyncClient::FailureReason::Reset || reason == Http::AsyncClient::FailureReason::ExceedResponseBufferLimit); - ENVOY_LOG(error, "Request failed: stream has been reset"); active_request_ = nullptr; - onError(); + onError(absl::StrFormat("Request failed with reason: %d", enumToInt(reason))); } -void GcpAuthnClient::cancel() { +void GcpAuthnClientImpl::cancel() { if (active_request_) { active_request_->cancel(); active_request_ = nullptr; } } -void GcpAuthnClient::onError() { +void GcpAuthnClientImpl::onError(absl::string_view error_msg) { + ENVOY_LOG(error, "{}", error_msg); + // Cancel if the request is active. cancel(); ASSERT(callbacks_ != nullptr); - callbacks_->onComplete(/*response_ptr=*/nullptr); + callbacks_->onComplete(absl::InternalError(error_msg)); callbacks_ = nullptr; } diff --git a/source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.h b/source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.h new file mode 100644 index 0000000000000..3495f46a3a411 --- /dev/null +++ b/source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.pb.h" + +#include "source/common/http/message_impl.h" +#include "source/extensions/filters/http/common/factory_base.h" +#include "source/extensions/filters/http/gcp_authn/gcp_authn_client.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace GcpAuthn { + +class GcpAuthnClientImpl : public GcpAuthnClient, + public Http::AsyncClient::Callbacks, + public Logger::Loggable { +public: + GcpAuthnClientImpl( + const envoy::extensions::filters::http::gcp_authn::v3::GcpAuthnFilterConfig& config, + Server::Configuration::FactoryContext& context) + : config_(config), context_(context) {} + + ~GcpAuthnClientImpl() override { cancel(); } + + void onBeforeFinalizeUpstreamSpan(Tracing::Span&, const Http::ResponseHeaderMap*) override {} + + // GcpAuthnClient implemented by this class. + void fetchToken(const envoy::extensions::filters::http::gcp_authn::v3::Audience& audience, + GcpAuthnClient::Callbacks& callbacks) override; + void cancel() override; + + // Http::AsyncClient::Callbacks implemented by this class. + void onSuccess(const Http::AsyncClient::Request& request, + Http::ResponseMessagePtr&& response) override; + void onFailure(const Http::AsyncClient::Request& request, + Http::AsyncClient::FailureReason reason) override; + +private: + void onError(absl::string_view error_msg); + const envoy::extensions::filters::http::gcp_authn::v3::GcpAuthnFilterConfig& config_; + Server::Configuration::FactoryContext& context_; + Http::AsyncClient::Request* active_request_{}; + GcpAuthnClient::Callbacks* callbacks_{}; +}; + +} // namespace GcpAuthn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/gcp_authn/gcp_authn_filter.cc b/source/extensions/filters/http/gcp_authn/gcp_authn_filter.cc index 4fd6ca9f27ff6..e0e76838a5ec6 100644 --- a/source/extensions/filters/http/gcp_authn/gcp_authn_filter.cc +++ b/source/extensions/filters/http/gcp_authn/gcp_authn_filter.cc @@ -2,13 +2,24 @@ #include #include +#include + +#include "envoy/common/exception.h" +#include "envoy/http/filter.h" +#include "envoy/http/header_map.h" +#include "envoy/router/router.h" +#include "envoy/upstream/thread_local_cluster.h" #include "source/common/common/enum_to_int.h" -#include "source/common/http/header_map_impl.h" -#include "source/common/http/utility.h" -#include "source/common/runtime/runtime_features.h" +#include "source/common/common/logger.h" +#include "source/common/jwt/jwt.h" +#include "source/common/jwt/status.h" +#include "source/common/protobuf/utility.h" -#include "absl/strings/str_replace.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" namespace Envoy { namespace Extensions { @@ -42,6 +53,7 @@ Http::FilterHeadersStatus GcpAuthnFilter::decodeHeaders(Http::RequestHeaderMap& state_ = State::Calling; initiating_call_ = true; + envoy::extensions::filters::http::gcp_authn::v3::Audience audience_proto; Envoy::Upstream::ThreadLocalCluster* cluster = context_.serverFactoryContext().clusterManager().getThreadLocalCluster( route->routeEntry()->clusterName()); @@ -51,9 +63,8 @@ Http::FilterHeadersStatus GcpAuthnFilter::decodeHeaders(Http::RequestHeaderMap& auto filter_metadata = cluster->info()->metadata().typed_filter_metadata(); const auto filter_it = filter_metadata.find(std::string(FilterName)); if (filter_it != filter_metadata.end()) { - envoy::extensions::filters::http::gcp_authn::v3::Audience audience; - THROW_IF_NOT_OK(MessageUtil::unpackTo(filter_it->second, audience)); - audience_str_ = audience.url(); + THROW_IF_NOT_OK(MessageUtil::unpackTo(filter_it->second, audience_proto)); + audience_str_ = audience_proto.url(); } } @@ -70,14 +81,8 @@ Http::FilterHeadersStatus GcpAuthnFilter::decodeHeaders(Http::RequestHeaderMap& // Save the pointer to the request headers for header manipulation based on http response later. request_header_map_ = &hdrs; - // Audience is URL of receiving service that will perform authentication. - // The URL format is - // "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=[AUDIENCE]" - // So, we add the audience from the config to the final url by substituting the `[AUDIENCE]` - // with real audience string from the config. - - std::string final_url = absl::StrReplaceAll(UrlString, {{"[AUDIENCE]", audience_str_}}); - client_->fetchToken(*this, buildRequest(final_url)); + + client_->fetchToken(audience_proto, *this); initiating_call_ = false; } else { // There is no need to fetch the token if no audience is specified because no @@ -96,13 +101,13 @@ void GcpAuthnFilter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallback decoder_callbacks_ = &callbacks; } -void GcpAuthnFilter::onComplete(const Http::ResponseMessage* response) { +void GcpAuthnFilter::onComplete(absl::StatusOr token) { state_ = State::Complete; if (!initiating_call_) { - if (response != nullptr) { + if (token.ok()) { // Modify the request header to include the ID token in a header (by default, the // `Authorization: Bearer ID_TOKEN` header). - std::string token_str = response->bodyAsString(); + std::string token_str = *token; if (request_header_map_ != nullptr) { addTokenToRequest(*request_header_map_, token_str, filter_config_->token_header()); } else { @@ -119,6 +124,8 @@ void GcpAuthnFilter::onComplete(const Http::ResponseMessage* response) { } else { ENVOY_LOG(error, "Failed to parse the token string, status : {}", Envoy::enumToInt(status)); } + } else { + ENVOY_LOG(error, "Failed to fetch token: {}", token.status().message()); } decoder_callbacks_->continueDecoding(); } diff --git a/source/extensions/filters/http/gcp_authn/gcp_authn_filter.h b/source/extensions/filters/http/gcp_authn/gcp_authn_filter.h index 238145d9cc72d..459f7543b208e 100644 --- a/source/extensions/filters/http/gcp_authn/gcp_authn_filter.h +++ b/source/extensions/filters/http/gcp_authn/gcp_authn_filter.h @@ -5,10 +5,9 @@ #include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.pb.h" #include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.pb.validate.h" -#include "source/common/http/message_impl.h" -#include "source/extensions/filters/http/common/factory_base.h" #include "source/extensions/filters/http/common/pass_through_filter.h" -#include "source/extensions/filters/http/gcp_authn/gcp_authn_impl.h" +#include "source/extensions/filters/http/gcp_authn/gcp_authn_client.h" +#include "source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.h" #include "source/extensions/filters/http/gcp_authn/token_cache.h" namespace Envoy { @@ -36,7 +35,7 @@ using FilterConfigSharedPtr = std::shared_ptr; class GcpAuthnFilter : public Http::PassThroughFilter, - public RequestCallbacks, + public GcpAuthnClient::Callbacks, public Logger::Loggable { public: // State of this filter's communication with the external authentication service. @@ -48,13 +47,13 @@ class GcpAuthnFilter : public Http::PassThroughFilter, Server::Configuration::FactoryContext& context, const std::string& stats_prefix, TokenCacheImpl* token_cache) : filter_config_(std::move(filter_config)), context_(context), - client_(std::make_unique(*filter_config_, context_)), + client_(std::make_unique(*filter_config_, context_)), stats_(generateStats(stats_prefix, context_.scope())), jwt_token_cache_(token_cache) {} Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) override; void onDestroy() override; - void onComplete(const Http::ResponseMessage* response_ptr) override; + void onComplete(absl::StatusOr token) override; void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override; State state() { return state_; } diff --git a/test/extensions/filters/http/gcp_authn/BUILD b/test/extensions/filters/http/gcp_authn/BUILD index caa8b206e7878..f4cbb1bb0c333 100644 --- a/test/extensions/filters/http/gcp_authn/BUILD +++ b/test/extensions/filters/http/gcp_authn/BUILD @@ -45,6 +45,24 @@ envoy_extension_cc_test( ], ) +envoy_extension_cc_test( + name = "gcp_authn_client_impl_test", + size = "small", + srcs = ["gcp_authn_client_impl_test.cc"], + extension_names = ["envoy.filters.http.gcp_authn"], + deps = [ + ":gcp_authn_mocks", + "//source/extensions/filters/http/gcp_authn", + "//source/extensions/filters/http/gcp_authn:config", + "//test/common/http:common_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_mocks", + "//test/test_common:test_runtime_lib", + "@envoy_api//envoy/extensions/filters/http/gcp_authn/v3:pkg_cc_proto", + ], +) + envoy_extension_cc_mock( name = "gcp_authn_mocks", hdrs = ["mocks.h"], diff --git a/test/extensions/filters/http/gcp_authn/gcp_authn_client_impl_test.cc b/test/extensions/filters/http/gcp_authn/gcp_authn_client_impl_test.cc new file mode 100644 index 0000000000000..c8d7178619fb2 --- /dev/null +++ b/test/extensions/filters/http/gcp_authn/gcp_authn_client_impl_test.cc @@ -0,0 +1,220 @@ +#include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.pb.h" + +#include "source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.h" + +#include "test/common/http/common.h" +#include "test/extensions/filters/http/gcp_authn/mocks.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/init/mocks.h" +#include "test/mocks/server/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace GcpAuthn { +namespace { + +using ::envoy::extensions::filters::http::gcp_authn::v3::GcpAuthnFilterConfig; +using Server::Configuration::MockFactoryContext; +using ::testing::_; +using ::testing::Invoke; +using ::testing::NiceMock; +using testing::Return; +using Upstream::MockThreadLocalCluster; + +constexpr char DefaultConfig[] = R"EOF( + http_uri: + uri: http://testhost/path/test + cluster: test_cluster + timeout: + seconds: 5 + retry_policy: + retry_back_off: + base_interval: 1s + max_interval: 10s + num_retries: 5 + )EOF"; + +class GcpAuthnClientImplTest : public testing::Test { +public: + GcpAuthnClientImplTest() { + // Initialize the default configuration. + TestUtility::loadFromYaml(DefaultConfig, config_); + } + + void setupMockObjects() { + EXPECT_CALL(context_.server_factory_context_.cluster_manager_, getThreadLocalCluster(_)) + .WillRepeatedly(Return(&thread_local_cluster_)); + EXPECT_CALL(thread_local_cluster_.async_client_, send_(_, _, _)) + .WillRepeatedly(Invoke([&](Envoy::Http::RequestMessagePtr& message, + Envoy::Http::AsyncClient::Callbacks& callback, + const Envoy::Http::AsyncClient::RequestOptions& options) + -> Http::AsyncClient::Request* { + message_.swap(message); + client_callback_ = &callback; + options_ = options; + return &client_request_; + })); + } + + void createClient() { client_ = std::make_unique(config_, context_); } + + NiceMock context_; + NiceMock thread_local_cluster_; + NiceMock client_request_{ + &thread_local_cluster_.async_client_}; + NiceMock request_callbacks_; + + // Mocks for http request. + Envoy::Http::AsyncClient::Callbacks* client_callback_; + Envoy::Http::RequestMessagePtr message_; + Envoy::Http::AsyncClient::RequestOptions options_; + + std::unique_ptr client_; + GcpAuthnFilterConfig config_; +}; + +TEST_F(GcpAuthnClientImplTest, Success) { + setupMockObjects(); + createClient(); + + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); + EXPECT_EQ(message_->headers().Method()->value().getStringView(), "GET"); + EXPECT_EQ(message_->headers().Path()->value().getStringView(), + "/computeMetadata/v1/instance/service-accounts/default/identity?audience=http://" + "test_audience"); + + EXPECT_EQ(options_.retry_policy->num_retries().value(), 5); + EXPECT_EQ(options_.retry_policy->retry_back_off().base_interval().seconds(), 1); + EXPECT_EQ(options_.retry_policy->retry_back_off().max_interval().seconds(), 10); + EXPECT_EQ(options_.retry_policy->retry_on(), "5xx,gateway-error,connect-failure,reset"); + + Envoy::Http::ResponseHeaderMapPtr resp_headers(new Envoy::Http::TestResponseHeaderMapImpl({ + {":status", "200"}, + })); + Envoy::Http::ResponseMessagePtr response( + new Envoy::Http::ResponseMessageImpl(std::move(resp_headers))); + response->body().add("token_string"); + + EXPECT_CALL(request_callbacks_, onComplete(absl::StatusOr("token_string"))); + client_callback_->onSuccess(client_request_, std::move(response)); +} + +TEST_F(GcpAuthnClientImplTest, NoCluster) { + std::string no_cluster_config = R"EOF( + http_uri: + uri: http://testhost/path/test + timeout: + seconds: 5 + retry_policy: + retry_back_off: + base_interval: 1s + max_interval: 10s + num_retries: 5 + )EOF"; + + EXPECT_CALL(context_.server_factory_context_.cluster_manager_, getThreadLocalCluster(_)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(context_.server_factory_context_.cluster_manager_.thread_local_cluster_, + httpAsyncClient()) + .Times(0); + + EXPECT_CALL(request_callbacks_, onComplete(_)); + GcpAuthnFilterConfig config; + TestUtility::loadFromYaml(no_cluster_config, config); + config_ = config; + createClient(); + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); +} + +TEST_F(GcpAuthnClientImplTest, Failure) { + setupMockObjects(); + createClient(); + EXPECT_CALL(request_callbacks_, onComplete(_)); + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); + client_callback_->onFailure(client_request_, Http::AsyncClient::FailureReason::Reset); +} + +TEST_F(GcpAuthnClientImplTest, NotOkResponse) { + setupMockObjects(); + createClient(); + + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); + + Envoy::Http::ResponseHeaderMapPtr resp_headers(new Envoy::Http::TestResponseHeaderMapImpl({ + {":status", "504"}, + })); + Envoy::Http::ResponseMessagePtr response( + new Envoy::Http::ResponseMessageImpl(std::move(resp_headers))); + EXPECT_CALL(request_callbacks_, onComplete(_)); + client_callback_->onSuccess(client_request_, std::move(response)); +} + +TEST_F(GcpAuthnClientImplTest, EmptyResponseHeader) { + setupMockObjects(); + createClient(); + + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); + + Envoy::Http::ResponseHeaderMapPtr empty_resp_headers( + new Envoy::Http::TestResponseHeaderMapImpl({})); + Envoy::Http::ResponseMessagePtr empty_response( + new Envoy::Http::ResponseMessageImpl(std::move(empty_resp_headers))); + EXPECT_CALL(request_callbacks_, onComplete(_)); + client_callback_->onSuccess(client_request_, std::move(empty_response)); +} + +TEST_F(GcpAuthnClientImplTest, Cancel) { + setupMockObjects(); + createClient(); + + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); + + EXPECT_CALL(client_request_, cancel()); + client_->cancel(); +} + +TEST_F(GcpAuthnClientImplTest, NoRetryPolicy) { + std::string no_retry_config = R"EOF( + http_uri: + uri: http://testhost/path/test + cluster: test_cluster + timeout: + seconds: 5 + )EOF"; + + GcpAuthnFilterConfig config; + TestUtility::loadFromYaml(no_retry_config, config); + config_ = config; + + setupMockObjects(); + createClient(); + + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); + + EXPECT_FALSE(options_.retry_policy.has_value()); +} + +} // namespace +} // namespace GcpAuthn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/gcp_authn/gcp_authn_filter_test.cc b/test/extensions/filters/http/gcp_authn/gcp_authn_filter_test.cc index 7eeae367515a6..cecded5beedaa 100644 --- a/test/extensions/filters/http/gcp_authn/gcp_authn_filter_test.cc +++ b/test/extensions/filters/http/gcp_authn/gcp_authn_filter_test.cc @@ -1,8 +1,8 @@ #include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.pb.h" #include "source/common/http/header_map_impl.h" +#include "source/extensions/filters/http/gcp_authn/gcp_authn_client_impl.h" #include "source/extensions/filters/http/gcp_authn/gcp_authn_filter.h" -#include "source/extensions/filters/http/gcp_authn/gcp_authn_impl.h" #include "test/common/http/common.h" #include "test/extensions/filters/http/gcp_authn/mocks.h" @@ -69,13 +69,13 @@ class GcpAuthnFilterTest : public testing::Test { filter_->setDecoderFilterCallbacks(decoder_callbacks_); } - void setupMockFilterMetadata(bool valid) { + void setupMockFilterMetadata(bool valid, const std::string& audience_url = "test") { // Set up mock filter metadata. cluster_info_ = std::make_shared>(); EXPECT_CALL(thread_local_cluster_, info()).WillRepeatedly(Return(cluster_info_)); if (valid) { envoy::extensions::filters::http::gcp_authn::v3::Audience audience; - audience.set_url("test"); + audience.set_url(audience_url); (*metadata_.mutable_typed_filter_metadata()) [std::string(Envoy::Extensions::HttpFilters::GcpAuthn::FilterName)] @@ -91,7 +91,7 @@ class GcpAuthnFilterTest : public testing::Test { config); } - void createClient() { client_ = std::make_unique(config_, context_); } + void createClient() { client_ = std::make_unique(config_, context_); } NiceMock context_; NiceMock thread_local_cluster_; @@ -99,14 +99,14 @@ class GcpAuthnFilterTest : public testing::Test { NiceMock decoder_callbacks_; NiceMock client_request_{ &thread_local_cluster_.async_client_}; - MockRequestCallbacks request_callbacks_; + NiceMock request_callbacks_; // Mocks for http request. Envoy::Http::AsyncClient::Callbacks* client_callback_; Envoy::Http::RequestMessagePtr message_; Envoy::Http::AsyncClient::RequestOptions options_; - std::unique_ptr client_; + std::unique_ptr client_; std::unique_ptr filter_; GcpAuthnFilterConfig config_; FilterConfigSharedPtr filter_config_; @@ -120,10 +120,13 @@ TEST_F(GcpAuthnFilterTest, Success) { // Create the client object. createClient(); - client_->fetchToken(request_callbacks_, buildRequest(config_.http_uri().uri())); + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); EXPECT_EQ(message_->headers().Method()->value().getStringView(), "GET"); - EXPECT_EQ(message_->headers().Host()->value().getStringView(), "testhost"); - EXPECT_EQ(message_->headers().Path()->value().getStringView(), "/path/test"); + EXPECT_EQ(message_->headers().Path()->value().getStringView(), + "/computeMetadata/v1/instance/service-accounts/default/identity?audience=http://" + "test_audience"); EXPECT_EQ(options_.retry_policy->num_retries().value(), 5); EXPECT_EQ(options_.retry_policy->retry_back_off().base_interval().seconds(), 1); @@ -135,8 +138,9 @@ TEST_F(GcpAuthnFilterTest, Success) { })); Envoy::Http::ResponseMessagePtr response( new Envoy::Http::ResponseMessageImpl(std::move(resp_headers))); + response->body().add("token_string"); - EXPECT_CALL(request_callbacks_, onComplete(response.get())); + EXPECT_CALL(request_callbacks_, onComplete(absl::StatusOr("token_string"))); client_callback_->onSuccess(client_request_, std::move(response)); } @@ -161,20 +165,24 @@ TEST_F(GcpAuthnFilterTest, NoCluster) { httpAsyncClient()) .Times(0); - EXPECT_CALL(request_callbacks_, onComplete(/*response_ptr=*/nullptr)); + EXPECT_CALL(request_callbacks_, onComplete(_)); GcpAuthnFilterConfig config; TestUtility::loadFromYaml(no_cluster_config, config); overrideConfig(config); createClient(); - client_->fetchToken(request_callbacks_, buildRequest(config.http_uri().uri())); + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); } TEST_F(GcpAuthnFilterTest, Failure) { setupMockObjects(); // Create the client object. createClient(); - EXPECT_CALL(request_callbacks_, onComplete(/*response_ptr=*/nullptr)); - client_->fetchToken(request_callbacks_, buildRequest(config_.http_uri().uri())); + EXPECT_CALL(request_callbacks_, onComplete(_)); + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); client_callback_->onFailure(client_request_, Http::AsyncClient::FailureReason::Reset); } @@ -183,14 +191,16 @@ TEST_F(GcpAuthnFilterTest, NotOkResponse) { // Create the client object. createClient(); - client_->fetchToken(request_callbacks_, buildRequest(config_.http_uri().uri())); + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); Envoy::Http::ResponseHeaderMapPtr resp_headers(new Envoy::Http::TestResponseHeaderMapImpl({ {":status", "504"}, })); Envoy::Http::ResponseMessagePtr response( new Envoy::Http::ResponseMessageImpl(std::move(resp_headers))); - EXPECT_CALL(request_callbacks_, onComplete(/*response_ptr=*/nullptr)); + EXPECT_CALL(request_callbacks_, onComplete(_)); client_callback_->onSuccess(client_request_, std::move(response)); } @@ -199,13 +209,15 @@ TEST_F(GcpAuthnFilterTest, EmptyResponseHeader) { // Create the client object. createClient(); - client_->fetchToken(request_callbacks_, buildRequest(config_.http_uri().uri())); + envoy::extensions::filters::http::gcp_authn::v3::Audience audience; + audience.set_url("http://test_audience"); + client_->fetchToken(audience, request_callbacks_); Envoy::Http::ResponseHeaderMapPtr empty_resp_headers( new Envoy::Http::TestResponseHeaderMapImpl({})); Envoy::Http::ResponseMessagePtr empty_response( new Envoy::Http::ResponseMessageImpl(std::move(empty_resp_headers))); - EXPECT_CALL(request_callbacks_, onComplete(/*response_ptr=*/nullptr)); + EXPECT_CALL(request_callbacks_, onComplete(_)); client_callback_->onSuccess(client_request_, std::move(empty_response)); } @@ -243,6 +255,7 @@ TEST_F(GcpAuthnFilterTest, ResumeFilterChainIteration) { })); Envoy::Http::ResponseMessagePtr response( new Envoy::Http::ResponseMessageImpl(std::move(resp_headers))); + response->body().add("token_string"); // continueDecoding() is expected to be called to resume the filter chain iteration after // onSuccess(). EXPECT_CALL(decoder_callbacks_, continueDecoding()); diff --git a/test/extensions/filters/http/gcp_authn/mocks.h b/test/extensions/filters/http/gcp_authn/mocks.h index e22da9209087c..42ee0bf5e0b9e 100644 --- a/test/extensions/filters/http/gcp_authn/mocks.h +++ b/test/extensions/filters/http/gcp_authn/mocks.h @@ -2,7 +2,7 @@ #include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.pb.h" -#include "source/extensions/filters/http/gcp_authn/gcp_authn_filter.h" +#include "source/extensions/filters/http/gcp_authn/gcp_authn_client.h" #include "gmock/gmock.h" @@ -11,12 +11,12 @@ namespace Extensions { namespace HttpFilters { namespace GcpAuthn { -class MockRequestCallbacks : public RequestCallbacks { +class MockGcpAuthnClientCallbacks : public GcpAuthnClient::Callbacks { public: - MockRequestCallbacks() = default; - ~MockRequestCallbacks() override = default; + MockGcpAuthnClientCallbacks() = default; + ~MockGcpAuthnClientCallbacks() override = default; - MOCK_METHOD(void, onComplete, (const Http::ResponseMessage* response)); + MOCK_METHOD(void, onComplete, (absl::StatusOr token)); }; } // namespace GcpAuthn From 43cfec1742af20b12fb7324aa5ad0666377ba8ca Mon Sep 17 00:00:00 2001 From: Thomas Gschwendtner <35153175+tgschwen@users.noreply.github.com> Date: Mon, 18 May 2026 11:49:33 -0400 Subject: [PATCH 336/750] Add Adi Peleg to the Envoy security team (#45122) --- OWNERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/OWNERS.md b/OWNERS.md index d2df8fd6b85ce..ae8e0052d28a5 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -105,6 +105,7 @@ without further review. * Paul Ogilby ([paul-r-gall](https://github.com/paul-r-gall)) (pgal@google.com) * Peng Gao ([penguingao](https://github.com/penguingao)) (pengg@google.com) * Jonh Wendell ([jwendell](https://github.com/jwendell)) (jwendell@redhat.com) +* Adi (Suissa) Peleg ([adisuissa](https://github.com/adisuissa)) (adip@google.com) # Emeritus maintainers From 3d1c6b416e58d256a994fb63376903c9ca2653cb Mon Sep 17 00:00:00 2001 From: botengyao Date: Mon, 18 May 2026 12:08:11 -0400 Subject: [PATCH 337/750] security: add Ignasi to security team (#45126) Signed-off-by: Boteng Yao --- OWNERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/OWNERS.md b/OWNERS.md index ae8e0052d28a5..db28134e79066 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -106,6 +106,7 @@ without further review. * Peng Gao ([penguingao](https://github.com/penguingao)) (pengg@google.com) * Jonh Wendell ([jwendell](https://github.com/jwendell)) (jwendell@redhat.com) * Adi (Suissa) Peleg ([adisuissa](https://github.com/adisuissa)) (adip@google.com) +* Ignasi Barrera ([nacx](https://github.com/nacx)) (nacx@apache.org) # Emeritus maintainers From 839f03a22c61d59f8b7f19902e6971cfd1ba5707 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 18 May 2026 09:21:35 -0700 Subject: [PATCH 338/750] upstream: correctly handle h2 and h3 stream resets with no error (#44749) Fixed a bug where an upstream HTTP/2 or HTTP/3 ``RST_STREAM(NO_ERROR)`` received after a complete response would cause the response to be discarded and replaced with an error. This behavior is common with some gRPC clients and servers, but is often intermittent. Runtime guard: envoy.reloadable_features.http_preserve_rst_no_error Fixes #38206 Signed-off-by: Greg Greenway --- changelogs/current.yaml | 7 + .../cluster_manager/cluster_stats.rst | 3 +- envoy/http/stream_reset_handler.h | 4 + envoy/upstream/upstream.h | 1 + source/common/http/conn_pool_base.cc | 3 + source/common/http/http2/codec_impl.cc | 72 ++- source/common/http/utility.cc | 2 + .../common/quic/envoy_quic_client_stream.cc | 24 +- source/common/quic/envoy_quic_utils.cc | 2 + source/common/router/retry_state_impl.cc | 3 +- source/common/router/router.cc | 25 + source/common/router/upstream_request.cc | 13 +- source/common/runtime/runtime_features.cc | 1 + source/common/tcp_proxy/upstream.cc | 1 + test/common/http/http2/codec_impl_test.cc | 2 +- test/common/http/http2/conn_pool_test.cc | 24 + test/common/router/router_test.cc | 61 +++ .../multiplexed_integration_test.cc | 483 +++++++++++++++++- test/integration/protocol_integration_test.cc | 106 ++++ .../integration/websocket_integration_test.cc | 4 +- 20 files changed, 794 insertions(+), 47 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 7dd63fd635767..115087ba33431 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -62,6 +62,13 @@ bug_fixes: Fixed a bug in load stats reporting where reports were dropped if only custom metrics or completed requests were present in a reporting interval. This behavioral change can be reverted by setting the runtime guard ``envoy.reloadable_features.report_load_for_non_zero_stats`` to ``false``. +- area: http + change: | + Fixed a bug where an upstream HTTP/2 or HTTP/3 ``RST_STREAM(NO_ERROR)`` received after a complete + response would cause the response to be discarded and replaced with an error. This behavior is + common with some gRPC clients and servers, but is often intermittent. This behavior can be + temporarily reverted by setting the runtime flag + ``envoy.reloadable_features.http_preserve_rst_no_error`` to ``false``. - area: spiffe_validator change: | Fixed a bug where if an invalid custom SAN matcher was configured it would later lead to a crash diff --git a/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst index e3c0c9608f931..d9cd684476c08 100644 --- a/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst +++ b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst @@ -89,7 +89,8 @@ Every cluster has a statistics tree rooted at *cluster..* with the followi upstream_rq_timeout, Counter, Total requests that timed out waiting for a response upstream_rq_max_duration_reached, Counter, Total requests closed due to max duration reached upstream_rq_per_try_timeout, Counter, Total requests that hit the per try timeout (except when request hedging is enabled) - upstream_rq_rx_reset, Counter, Total requests that were reset remotely + upstream_rq_rx_reset, Counter, Total requests that were reset remotely with an error + upstream_rq_rx_reset_no_error, Counter, Total requests that were reset remotely with no error upstream_rq_tx_reset, Counter, Total requests that were reset locally upstream_rq_retry, Counter, Total request retries upstream_rq_retry_backoff_exponential, Counter, Total retries using the exponential backoff strategy diff --git a/envoy/http/stream_reset_handler.h b/envoy/http/stream_reset_handler.h index 09e3b1dd80383..74d5cd862f81e 100644 --- a/envoy/http/stream_reset_handler.h +++ b/envoy/http/stream_reset_handler.h @@ -39,6 +39,10 @@ enum class StreamResetReason { OverloadManager, // If stream was locally reset due to HTTP/1 upstream half closing before downstream. Http1PrematureUpstreamHalfClose, + // If a remote RST_STREAM(NO_ERROR) was received after a complete response. + // Per RFC 9113 Section 8.1 (HTTP/2) and RFC 9114 Section 4.1 (HTTP/3), this is not an error, and + // the client should not discard the response. + RemoteResetNoError, }; /** diff --git a/envoy/upstream/upstream.h b/envoy/upstream/upstream.h index d593610adffe5..cc1c9142954a6 100644 --- a/envoy/upstream/upstream.h +++ b/envoy/upstream/upstream.h @@ -765,6 +765,7 @@ class PrioritySet { COUNTER(upstream_rq_retry_overflow) \ COUNTER(upstream_rq_retry_success) \ COUNTER(upstream_rq_rx_reset) \ + COUNTER(upstream_rq_rx_reset_no_error) \ COUNTER(upstream_rq_timeout) \ COUNTER(upstream_rq_total) \ COUNTER(upstream_rq_tx_reset) \ diff --git a/source/common/http/conn_pool_base.cc b/source/common/http/conn_pool_base.cc index 4de1405a92d1a..df542b6417238 100644 --- a/source/common/http/conn_pool_base.cc +++ b/source/common/http/conn_pool_base.cc @@ -184,6 +184,9 @@ void MultiplexedActiveClientBase::onStreamReset(Http::StreamResetReason reason) case StreamResetReason::RemoteReset: parent_.host()->cluster().trafficStats()->upstream_rq_rx_reset_.inc(); break; + case StreamResetReason::RemoteResetNoError: + parent_.host()->cluster().trafficStats()->upstream_rq_rx_reset_no_error_.inc(); + break; case StreamResetReason::LocalRefusedStreamReset: case StreamResetReason::RemoteRefusedStreamReset: case StreamResetReason::Overflow: diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 52ec0649704c9..6235482e5060a 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -179,6 +179,8 @@ int reasonToReset(StreamResetReason reason, bool response_end_stream_sent) { return OGHTTP2_REFUSED_STREAM; case StreamResetReason::ConnectError: return OGHTTP2_CONNECT_ERROR; + case StreamResetReason::RemoteResetNoError: + return OGHTTP2_NO_ERROR; case StreamResetReason::ProtocolError: if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.reset_with_error")) { return OGHTTP2_NO_ERROR; @@ -1527,42 +1529,56 @@ Status ConnectionImpl::onStreamClose(StreamImpl* stream, uint32_t error_code) { } if (should_reset_stream) { - StreamResetReason reason; - if (stream->reset_due_to_messaging_error_) { - // Unfortunately, the nghttp2 API makes it incredibly difficult to clearly understand - // the flow of resets. I.e., did the reset originate locally? Was it remote? Here, - // we attempt to track cases in which we sent a reset locally due to an invalid frame - // received from the remote. We only do that in two cases currently (HTTP messaging layer - // errors from https://tools.ietf.org/html/rfc7540#section-8 which nghttp2 is very strict - // about). In other cases we treat invalid frames as a protocol error and just kill - // the connection. - - // Get ClientConnectionImpl or ServerConnectionImpl specific stream reset reason, - // depending whether the connection is upstream or downstream. - reason = getMessagingErrorResetReason(); + // RFC 9113 Section 8.1: A server MAY send RST_STREAM(NO_ERROR) after sending + // a complete response. The complete response MUST NOT be discarded. + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http_preserve_rst_no_error") && + stream->remote_end_stream_ && error_code == OGHTTP2_NO_ERROR && + !stream->reset_reason_.has_value()) { + if (stream->stream_manager_.hasBufferedBodyOrTrailers()) { + ENVOY_CONN_LOG(debug, "buffered onStreamClose for stream: {}", connection_, stream_id); + stream->stream_manager_.buffered_on_stream_close_ = true; + stats_.deferred_stream_close_.inc(); + return okStatus(); + } + stream->runResetCallbacks(StreamResetReason::RemoteResetNoError, absl::string_view()); } else { - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.reset_with_error")) { - reason = errorCodeToResetReason(error_code); - if (error_code == OGHTTP2_REFUSED_STREAM) { - stream->setDetails(Http2ResponseCodeDetails::get().remote_refused); - } else { - stream->setDetails(Http2ResponseCodeDetails::get().remote_reset); - } + StreamResetReason reason; + if (stream->reset_due_to_messaging_error_) { + // Unfortunately, the nghttp2 API makes it incredibly difficult to clearly understand + // the flow of resets. I.e., did the reset originate locally? Was it remote? Here, + // we attempt to track cases in which we sent a reset locally due to an invalid frame + // received from the remote. We only do that in two cases currently (HTTP messaging layer + // errors from https://tools.ietf.org/html/rfc7540#section-8 which nghttp2 is very strict + // about). In other cases we treat invalid frames as a protocol error and just kill + // the connection. + + // Get ClientConnectionImpl or ServerConnectionImpl specific stream reset reason, + // depending whether the connection is upstream or downstream. + reason = getMessagingErrorResetReason(); } else { - if (error_code == OGHTTP2_REFUSED_STREAM) { - reason = StreamResetReason::RemoteRefusedStreamReset; - stream->setDetails(Http2ResponseCodeDetails::get().remote_refused); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.reset_with_error")) { + reason = errorCodeToResetReason(error_code); + if (error_code == OGHTTP2_REFUSED_STREAM) { + stream->setDetails(Http2ResponseCodeDetails::get().remote_refused); + } else { + stream->setDetails(Http2ResponseCodeDetails::get().remote_reset); + } } else { - if (error_code == OGHTTP2_CONNECT_ERROR) { - reason = StreamResetReason::ConnectError; + if (error_code == OGHTTP2_REFUSED_STREAM) { + reason = StreamResetReason::RemoteRefusedStreamReset; + stream->setDetails(Http2ResponseCodeDetails::get().remote_refused); } else { - reason = StreamResetReason::RemoteReset; + if (error_code == OGHTTP2_CONNECT_ERROR) { + reason = StreamResetReason::ConnectError; + } else { + reason = StreamResetReason::RemoteReset; + } + stream->setDetails(Http2ResponseCodeDetails::get().remote_reset); } - stream->setDetails(Http2ResponseCodeDetails::get().remote_reset); } } + stream->runResetCallbacks(reason, absl::string_view()); } - stream->runResetCallbacks(reason, absl::string_view()); } else if (!stream->reset_reason_.has_value() && stream->stream_manager_.hasBufferedBodyOrTrailers()) { diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index 7a207abd0911a..e71bad63fa29d 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -1188,6 +1188,8 @@ const std::string Utility::resetReasonToString(const Http::StreamResetReason res return "overload manager reset"; case Http::StreamResetReason::Http1PrematureUpstreamHalfClose: return "HTTP/1 premature upstream half close"; + case Http::StreamResetReason::RemoteResetNoError: + return "remote reset (no error)"; } return ""; diff --git a/source/common/quic/envoy_quic_client_stream.cc b/source/common/quic/envoy_quic_client_stream.cc index 6228d625b6b12..a3f932c9b2d15 100644 --- a/source/common/quic/envoy_quic_client_stream.cc +++ b/source/common/quic/envoy_quic_client_stream.cc @@ -279,8 +279,15 @@ bool EnvoyQuicClientStream::OnStopSending(quic::QuicResetStreamError error) { if (read_side_closed() && !end_stream_encoded) { // If both directions are closed but end stream hasn't been encoded yet, notify reset callbacks. // Treat this as a remote reset, since the stream will be closed in both directions. + Http::StreamResetReason reason; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http_preserve_rst_no_error") && + error.internal_code() == quic::QUIC_STREAM_NO_ERROR) { + reason = Http::StreamResetReason::RemoteResetNoError; + } else { + reason = quicRstErrorToEnvoyRemoteResetReason(error.internal_code()); + } runResetCallbacks( - quicRstErrorToEnvoyRemoteResetReason(error.internal_code()), + reason, absl::StrCat(quic::QuicRstStreamErrorCodeToString(error.internal_code()), "|FROM_PEER")); } return true; @@ -383,14 +390,23 @@ void EnvoyQuicClientStream::maybeDecodeTrailers() { void EnvoyQuicClientStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { ENVOY_STREAM_LOG(debug, "received reset code={}", *this, static_cast(frame.error_code)); stats_.rx_reset_.inc(); - bool end_stream_decoded_and_encoded = read_side_closed() && local_end_stream_; + // Capture pre-reset state: OnStreamReset closes the read side, so read_side_closed() + // will be true afterwards regardless of whether the response was fully received. + bool read_side_was_closed = read_side_closed(); + bool end_stream_decoded_and_encoded = read_side_was_closed && local_end_stream_; // This closes read side in IETF Quic, but doesn't close write side. quic::QuicSpdyClientStream::OnStreamReset(frame); ASSERT(read_side_closed()); if (write_side_closed() && !end_stream_decoded_and_encoded) { + Http::StreamResetReason reason; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http_preserve_rst_no_error") && + frame.error_code == quic::QUIC_STREAM_NO_ERROR && read_side_was_closed) { + reason = Http::StreamResetReason::RemoteResetNoError; + } else { + reason = quicRstErrorToEnvoyRemoteResetReason(frame.error_code); + } runResetCallbacks( - quicRstErrorToEnvoyRemoteResetReason(frame.error_code), - absl::StrCat(quic::QuicRstStreamErrorCodeToString(frame.error_code), "|FROM_PEER")); + reason, absl::StrCat(quic::QuicRstStreamErrorCodeToString(frame.error_code), "|FROM_PEER")); } } diff --git a/source/common/quic/envoy_quic_utils.cc b/source/common/quic/envoy_quic_utils.cc index fb6e9bb23774b..62454f15b70b5 100644 --- a/source/common/quic/envoy_quic_utils.cc +++ b/source/common/quic/envoy_quic_utils.cc @@ -147,6 +147,8 @@ quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetRea case Http::StreamResetReason::Http1PrematureUpstreamHalfClose: IS_ENVOY_BUG("H/1 premature response reset is not applicable to H/3."); break; + case Http::StreamResetReason::RemoteResetNoError: + return quic::QUIC_STREAM_NO_ERROR; } ENVOY_LOG_MISC(error, absl::StrCat("Unknown reset reason: ", reason)); diff --git a/source/common/router/retry_state_impl.cc b/source/common/router/retry_state_impl.cc index 71c7377dec4ed..72541678935d7 100644 --- a/source/common/router/retry_state_impl.cc +++ b/source/common/router/retry_state_impl.cc @@ -431,7 +431,8 @@ RetryStateImpl::wouldRetryFromReset(const Http::StreamResetReason reset_reason, ASSERT(!disable_http3); // First check "never retry" conditions so we can short circuit (we never // retry if the reset reason is overflow). - if (reset_reason == Http::StreamResetReason::Overflow) { + if (reset_reason == Http::StreamResetReason::Overflow || + reset_reason == Http::StreamResetReason::RemoteResetNoError) { return RetryDecision::NoRetry; } diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 72e5d3d127fcf..1554e048ef2d3 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -1610,6 +1610,28 @@ void Filter::onUpstreamReset(Http::StreamResetReason reset_reason, *callbacks_, Http::Utility::resetReasonToString(reset_reason), transport_failure_reason); + if (reset_reason == Http::StreamResetReason::RemoteResetNoError) { + auto request_ptr = upstream_request.removeFromList(upstream_requests_); + callbacks_->dispatcher().deferredDelete(std::move(request_ptr)); + if (numRequestsAwaitingHeaders() == 0 && pending_retries_ == 0) { + if (downstream_response_started_) { + // If the entire response, including `end_stream`, has already been sent, this will + // be translated into `NO_ERROR` by the codec. + callbacks_->resetStream(reset_reason, ""); + } else { + // This shouldn't happen: RemoteResetNoError requires a complete upstream response, + // which means headers were already forwarded, setting downstream_response_started_. + // Handle defensively to avoid a downstream request hang. + onUpstreamAbort(Http::Code::ServiceUnavailable, + StreamInfo::CoreResponseFlag::UpstreamRemoteReset, "", false, + StreamInfo::ResponseCodeDetails::get().EarlyUpstreamReset); + IS_ENVOY_BUG("StreamResetReason::RemoteResetNoError should not be raised unless the entire " + "response was received already."); + } + } + return; + } + const bool dropped = reset_reason == Http::StreamResetReason::Overflow; // Ignore upstream reset caused by a resource overflow. @@ -1715,6 +1737,9 @@ Filter::streamResetReasonToResponseFlag(Http::StreamResetReason reset_reason) { return StreamInfo::CoreResponseFlag::UpstreamRemoteReset; case Http::StreamResetReason::ProtocolError: return StreamInfo::CoreResponseFlag::UpstreamProtocolError; + case Http::StreamResetReason::RemoteResetNoError: + IS_ENVOY_BUG("unexpected RemoteResetNoError in streamResetReasonToResponseFlag"); + return StreamInfo::CoreResponseFlag::UpstreamRemoteReset; case Http::StreamResetReason::OverloadManager: return StreamInfo::CoreResponseFlag::OverloadManager; } diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index 0b3fdb03b4faf..fca8eae28d989 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -473,15 +473,18 @@ void UpstreamRequest::onResetStream(Http::StreamResetReason reason, absl::string_view transport_failure_reason) { ScopeTrackerScopeState scope(&parent_.callbacks()->scope(), parent_.callbacks()->dispatcher()); - if (span_ != nullptr) { - // Add tags about reset. - span_->setTag(Tracing::Tags::get().Error, Tracing::Tags::get().True); - span_->setTag(Tracing::Tags::get().ErrorReason, Http::Utility::resetReasonToString(reason)); + if (reason != Http::StreamResetReason::RemoteResetNoError) { + if (span_ != nullptr) { + // Add tags about reset. + span_->setTag(Tracing::Tags::get().Error, Tracing::Tags::get().True); + span_->setTag(Tracing::Tags::get().ErrorReason, Http::Utility::resetReasonToString(reason)); + } + + stream_info_.setResponseFlag(Filter::streamResetReasonToResponseFlag(reason)); } clearRequestEncoder(); awaiting_headers_ = false; - stream_info_.setResponseFlag(Filter::streamResetReasonToResponseFlag(reason)); parent_.onUpstreamReset(reason, transport_failure_reason, *this); } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index ff3a2e02c887f..977335204c93b 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -68,6 +68,7 @@ RUNTIME_GUARD(envoy_reloadable_features_http2_discard_host_header); RUNTIME_GUARD(envoy_reloadable_features_http2_fix_goaway_loadshed_point); RUNTIME_GUARD(envoy_reloadable_features_http_async_client_retry_respect_buffer_limits); RUNTIME_GUARD(envoy_reloadable_features_http_inspector_use_balsa_parser); +RUNTIME_GUARD(envoy_reloadable_features_http_preserve_rst_no_error); // Delay deprecation and decommission until UHV is enabled. RUNTIME_GUARD(envoy_reloadable_features_http_reject_path_with_fragment); RUNTIME_GUARD(envoy_reloadable_features_jwt_authn_add_verification_status_header); diff --git a/source/common/tcp_proxy/upstream.cc b/source/common/tcp_proxy/upstream.cc index ba48851e16084..ca67015122c91 100644 --- a/source/common/tcp_proxy/upstream.cc +++ b/source/common/tcp_proxy/upstream.cc @@ -254,6 +254,7 @@ void HttpUpstream::onResetStream(Http::StreamResetReason reason, absl::string_vi case Http::StreamResetReason::RemoteReset: case Http::StreamResetReason::RemoteRefusedStreamReset: case Http::StreamResetReason::RemoteConnectionFailure: + case Http::StreamResetReason::RemoteResetNoError: event = Network::ConnectionEvent::RemoteClose; detected_close_type_ = StreamInfo::DetectedCloseType::RemoteReset; break; diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index 657270b201f26..5760764cfdfa0 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -1863,7 +1863,7 @@ TEST_P(Http2CodecImplDeferredResetTest, DeferredResetServerIfLocalEndStreamBefor EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); EXPECT_CALL(response_decoder_, decodeData(_, false)).Times(AnyNumber()); EXPECT_CALL(response_decoder_, decodeData(_, true)); - EXPECT_CALL(client_stream_callbacks, onResetStream(StreamResetReason::RemoteReset, _)); + EXPECT_CALL(client_stream_callbacks, onResetStream(StreamResetReason::RemoteResetNoError, _)); driveToCompletion(); EXPECT_TRUE(client_wrapper_->status_.ok()); } diff --git a/test/common/http/http2/conn_pool_test.cc b/test/common/http/http2/conn_pool_test.cc index e082c1274be2c..db4deca01a54e 100644 --- a/test/common/http/http2/conn_pool_test.cc +++ b/test/common/http/http2/conn_pool_test.cc @@ -1094,6 +1094,30 @@ TEST_F(Http2ConnPoolImplTest, RemoteReset) { EXPECT_EQ(0U, cluster_->traffic_stats_->upstream_cx_active_.value()); } +TEST_F(Http2ConnPoolImplTest, RemoteResetNoError) { + InSequence s; + + expectClientCreate(); + ActiveTestRequest r1(*this, 0, false); + expectClientConnect(0, r1); + EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, false)); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, false) + .ok()); + r1.inner_encoder_.stream_.resetStream(Http::StreamResetReason::RemoteResetNoError); + + test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); + EXPECT_CALL(*this, onClientDestroy()); + dispatcher_.clearDeferredDeleteList(); + EXPECT_EQ(1U, cluster_->traffic_stats_->upstream_cx_destroy_.value()); + EXPECT_EQ(1U, cluster_->traffic_stats_->upstream_cx_destroy_remote_.value()); + EXPECT_EQ(0U, cluster_->traffic_stats_->upstream_rq_rx_reset_.value()); + EXPECT_EQ(1U, cluster_->traffic_stats_->upstream_rq_rx_reset_no_error_.value()); + EXPECT_EQ(0U, cluster_->circuit_breakers_stats_.rq_open_.value()); + EXPECT_EQ(0U, cluster_->traffic_stats_->upstream_cx_active_.value()); +} + TEST_F(Http2ConnPoolImplTest, DrainDisconnectWithActiveRequest) { InSequence s; cluster_->max_requests_per_connection_ = 1; diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index ec34ce30dbe7c..5807b365daf44 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -8576,5 +8576,66 @@ TEST_F(RouterTest, OrcaLoadReportSkipsEntriesNotInterestedInOrca) { response_decoder->decodeHeaders(std::move(response_headers), true); } +// Verify that RemoteResetNoError after response headers have been sent downstream +// (downstream_response_started_ = true) resets the downstream stream without error flags. +TEST_F(RouterTest, RemoteResetNoErrorAfterResponseStarted) { + NiceMock encoder; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder, &response_decoder, Http::Protocol::Http10); + + EXPECT_CALL(callbacks_, removeDownstreamWatermarkCallbacks(_)); + EXPECT_CALL(callbacks_, addDownstreamWatermarkCallbacks(_)); + + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginConnectSuccess, + absl::optional(absl::nullopt))); + router_->decodeHeaders(headers, false); + + // Upstream sends response headers (not end_stream), setting downstream_response_started_. + Http::ResponseHeaderMapPtr response_headers( + new Http::TestResponseHeaderMapImpl{{":status", "200"}}); + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(_, absl::optional(200))); + response_decoder->decodeHeaders(std::move(response_headers), false); + + // RemoteResetNoError arrives — should reset downstream stream, no error. + EXPECT_CALL(callbacks_, resetStream(Http::StreamResetReason::RemoteResetNoError, _)); + encoder.stream_.resetStream(Http::StreamResetReason::RemoteResetNoError); + + // No error response flags should be set. + EXPECT_FALSE( + callbacks_.stream_info_.hasResponseFlag(StreamInfo::CoreResponseFlag::UpstreamRemoteReset)); + + router_->onDestroy(); +} + +// Verify that RemoteResetNoError before the downstream response has started (defensive case) +// sends a local reply instead of silently dropping the request. +TEST_F(RouterTest, RemoteResetNoErrorBeforeResponseStarted) { + NiceMock encoder; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder, &response_decoder, Http::Protocol::Http10); + + EXPECT_CALL(callbacks_, removeDownstreamWatermarkCallbacks(_)); + EXPECT_CALL(callbacks_, addDownstreamWatermarkCallbacks(_)); + + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_.host_->outlier_detector_, + putResult(Upstream::Outlier::Result::LocalOriginConnectSuccess, + absl::optional(absl::nullopt))); + router_->decodeHeaders(headers, true); + + // RemoteResetNoError arrives before any response headers — should trigger IS_ENVOY_BUG + // and send a local reply to avoid a downstream request hang. + EXPECT_ENVOY_BUG(encoder.stream_.resetStream(Http::StreamResetReason::RemoteResetNoError), + "RemoteResetNoError should not be raised unless the entire " + "response was received already."); + + router_->onDestroy(); +} + } // namespace Router } // namespace Envoy diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index dfad5a80eb51d..11d82ae5b62f0 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -3595,7 +3595,7 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, ResetPropogation) { // is not complete yet, it will finally result in resetting of the downstream stream. upstream_request_->encodeResetStream(Http::StreamResetReason::ProtocolError); ASSERT_TRUE(response->waitForReset()); - EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason()); + EXPECT_EQ(Http::StreamResetReason::RemoteResetNoError, response->resetReason()); cleanupUpstreamAndDownstream(); }); @@ -3626,7 +3626,7 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, ResetPropogation) { // is not complete yet, it will finally result in resetting of the stream. upstream_request_->encodeResetStream(Http::StreamResetReason::LocalReset); ASSERT_TRUE(response->waitForReset()); - EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason()); + EXPECT_EQ(Http::StreamResetReason::RemoteResetNoError, response->resetReason()); cleanupUpstreamAndDownstream(); }); @@ -3696,7 +3696,7 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, ResetPropogationToD // is not complete yet, it will finally result in resetting of the stream. upstream_request_->encodeResetStream(Http::StreamResetReason::LocalReset); ASSERT_TRUE(response->waitForReset()); - EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason()); + EXPECT_EQ(Http::StreamResetReason::RemoteResetNoError, response->resetReason()); cleanupUpstreamAndDownstream(); }); @@ -3708,8 +3708,8 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, ResetPropogationLeg std::vector reasons = {Http::StreamResetReason::ProtocolError, Http::StreamResetReason::LocalReset}; - std::vector result_reasons = {Http::StreamResetReason::RemoteReset, - Http::StreamResetReason::RemoteReset}; + std::vector result_reasons = { + Http::StreamResetReason::RemoteResetNoError, Http::StreamResetReason::RemoteResetNoError}; // There are four streams created in total, client stream, Envoy server stream, // Envoy client stream and upstream server stream. @@ -3741,4 +3741,477 @@ TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, ResetPropogationLeg } } +// RFC 9113 Section 8.1: When the upstream sends a complete gRPC response (HEADERS + DATA + +// trailers with END_STREAM) followed by RST_STREAM(NO_ERROR), and the response body is large +// enough to trigger chunked decoding in the upstream H2 codec (body > connection buffer limit), +// the trailers get buffered. The RST_STREAM(NO_ERROR) is then processed before the buffered +// trailers are delivered, causing the RST_STREAM to reach the router as RemoteReset instead of +// being silently consumed by the preemptive reset in onUpstreamComplete. +// This test verifies that the complete response (including trailers) is forwarded to the client. +TEST_P(Http2FrameIntegrationTest, UpstreamRstStreamNoErrorWithBufferedTrailers) { + config_helper_.setBufferLimits(1024, 1024); + beginSession(); + + const uint32_t stream_idx = 1; + sendFrame(Http2Frame::makePostRequest(stream_idx, "host", "/")); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->write(std::string(Http2Frame::makeEmptySettingsFrame()))); + + // HEADERS (200, EndHeaders, no EndStream) + const auto response_headers = Http2Frame::makeHeadersFrameWithStatus( + "200", stream_idx, Http2Frame::HeadersFlags::EndHeaders); + + // DATA: >1024 bytes to trigger chunked decoding (defer_processing_segment_size_ = buffer limit). + // This causes body_buffered_ = true after the first chunk is decoded, which in turn causes + // trailers to be buffered via maybeDeferDecodeTrailers(). + const std::string large_body(2000, 'a'); + const auto response_data = + Http2Frame::makeDataFrame(stream_idx, large_body, Http2Frame::DataFlags::None); + + // Trailers: HEADERS with EndStream | EndHeaders containing "grpc-status: 0". + std::string hpack_trailers; + hpack_trailers.push_back(0x00); + hpack_trailers.push_back(0x0b); + hpack_trailers.append("grpc-status"); + hpack_trailers.push_back(0x01); + hpack_trailers.push_back('0'); + const auto trailers = + Http2Frame::makeRawFrame(Http2Frame::Type::Headers, + static_cast(Http2Frame::HeadersFlags::EndStream) | + static_cast(Http2Frame::HeadersFlags::EndHeaders), + stream_idx, hpack_trailers); + + // RST_STREAM (NO_ERROR) + const auto rst_stream = + Http2Frame::makeResetStreamFrame(stream_idx, Http2Frame::ErrorCode::NoError); + + // Send all frames in a single write so they're processed in the same dispatch. + // The codec will: decode headers (immediate), chunk data (buffer remainder), + // buffer trailers (body_buffered_), then process RST_STREAM. + ASSERT_TRUE(fake_upstream_connection->write( + absl::StrCat(std::string(response_headers), std::string(response_data), std::string(trailers), + std::string(rst_stream)))); + + auto readNextResponseFrame = [this]() -> Http2Frame { + while (true) { + auto frame = readFrame(); + if (frame.type() != Http2Frame::Type::WindowUpdate && + frame.type() != Http2Frame::Type::Settings && frame.type() != Http2Frame::Type::Ping) { + return frame; + } + } + }; + + // Response HEADERS — should be 200 OK, not a 503 local error reply. + auto headers_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, headers_frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, headers_frame.responseStatus()) + << "Expected 200 OK from upstream, not a local error reply"; + + // Response DATA — may be split across multiple frames due to buffer limits. + Http2Frame next_frame; + do { + next_frame = readNextResponseFrame(); + } while (next_frame.type() == Http2Frame::Type::Data && !next_frame.endStream()); + + // Response trailers (HEADERS with END_STREAM) — the complete response must be forwarded. + // If the bug triggers, we'll get RST_STREAM here instead of trailers. + auto trailers_frame = next_frame; + if (trailers_frame.type() == Http2Frame::Type::RstStream) { + ASSERT_GE(trailers_frame.size(), Http2Frame::HeaderSize + 4); + const uint8_t* p = trailers_frame.data() + Http2Frame::HeaderSize; + const uint32_t error_code = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + EXPECT_EQ(Http2Frame::Type::Headers, trailers_frame.type()) + << "Expected trailers (HEADERS with END_STREAM) but got RST_STREAM with error code " + << error_code + << ". The RST_STREAM(NO_ERROR) from upstream was likely processed before the buffered " + "trailers were delivered, causing the response to be truncated."; + } else { + EXPECT_EQ(Http2Frame::Type::Headers, trailers_frame.type()) + << "Expected trailers (HEADERS with END_STREAM) but got frame type " + << static_cast(trailers_frame.type()); + EXPECT_TRUE(trailers_frame.endStream()); + } + + tcp_client_->close(); +} + +// Verify old behavior when the runtime guard is disabled: RST_STREAM(NO_ERROR) after a complete +// response with buffered trailers causes the response to be truncated (trailers discarded) and +// an error RST_STREAM sent downstream instead. +TEST_P(Http2FrameIntegrationTest, UpstreamRstStreamNoErrorWithBufferedTrailersLegacy) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.http_preserve_rst_no_error", + "false"); + config_helper_.setBufferLimits(1024, 1024); + beginSession(); + + const uint32_t stream_idx = 1; + sendFrame(Http2Frame::makePostRequest(stream_idx, "host", "/")); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->write(std::string(Http2Frame::makeEmptySettingsFrame()))); + + const auto response_headers = Http2Frame::makeHeadersFrameWithStatus( + "200", stream_idx, Http2Frame::HeadersFlags::EndHeaders); + + const std::string large_body(2000, 'a'); + const auto response_data = + Http2Frame::makeDataFrame(stream_idx, large_body, Http2Frame::DataFlags::None); + + std::string hpack_trailers; + hpack_trailers.push_back(0x00); + hpack_trailers.push_back(0x0b); + hpack_trailers.append("grpc-status"); + hpack_trailers.push_back(0x01); + hpack_trailers.push_back('0'); + const auto trailers = + Http2Frame::makeRawFrame(Http2Frame::Type::Headers, + static_cast(Http2Frame::HeadersFlags::EndStream) | + static_cast(Http2Frame::HeadersFlags::EndHeaders), + stream_idx, hpack_trailers); + + const auto rst_stream = + Http2Frame::makeResetStreamFrame(stream_idx, Http2Frame::ErrorCode::NoError); + + ASSERT_TRUE(fake_upstream_connection->write( + absl::StrCat(std::string(response_headers), std::string(response_data), std::string(trailers), + std::string(rst_stream)))); + + auto readNextResponseFrame = [this]() -> Http2Frame { + while (true) { + auto frame = readFrame(); + if (frame.type() != Http2Frame::Type::WindowUpdate && + frame.type() != Http2Frame::Type::Settings && frame.type() != Http2Frame::Type::Ping) { + return frame; + } + } + }; + + auto headers_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, headers_frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, headers_frame.responseStatus()); + + // Consume DATA frames, then expect RST_STREAM (old buggy behavior: trailers discarded). + auto next_frame = readNextResponseFrame(); + while (next_frame.type() == Http2Frame::Type::Data) { + next_frame = readNextResponseFrame(); + } + EXPECT_EQ(Http2Frame::Type::RstStream, next_frame.type()) + << "With runtime guard disabled, expected RST_STREAM (old behavior) but got frame type " + << static_cast(next_frame.type()); + ASSERT_GE(next_frame.size(), Http2Frame::HeaderSize + 4); + const uint8_t* p = next_frame.data() + Http2Frame::HeaderSize; + const uint32_t error_code = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + EXPECT_NE(0u, error_code) << "With runtime guard disabled, RST_STREAM error code should not be " + "NO_ERROR (old behavior sends an error reset)"; + + tcp_client_->close(); +} + +// Verify that RST_STREAM with an actual error code (not NO_ERROR) still correctly resets the stream +// even when trailers are buffered. +TEST_P(Http2FrameIntegrationTest, UpstreamRstStreamWithErrorAndBufferedTrailers) { + config_helper_.setBufferLimits(1024, 1024); + beginSession(); + + const uint32_t stream_idx = 1; + sendFrame(Http2Frame::makePostRequest(stream_idx, "host", "/")); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->write(std::string(Http2Frame::makeEmptySettingsFrame()))); + + const auto response_headers = Http2Frame::makeHeadersFrameWithStatus( + "200", stream_idx, Http2Frame::HeadersFlags::EndHeaders); + + const std::string large_body(2000, 'a'); + const auto response_data = + Http2Frame::makeDataFrame(stream_idx, large_body, Http2Frame::DataFlags::None); + + std::string hpack_trailers; + hpack_trailers.push_back(0x00); + hpack_trailers.push_back(0x0b); + hpack_trailers.append("grpc-status"); + hpack_trailers.push_back(0x01); + hpack_trailers.push_back('0'); + const auto trailers = + Http2Frame::makeRawFrame(Http2Frame::Type::Headers, + static_cast(Http2Frame::HeadersFlags::EndStream) | + static_cast(Http2Frame::HeadersFlags::EndHeaders), + stream_idx, hpack_trailers); + + // RST_STREAM with INTERNAL_ERROR (not NO_ERROR) + const auto rst_stream = + Http2Frame::makeResetStreamFrame(stream_idx, Http2Frame::ErrorCode::InternalError); + + ASSERT_TRUE(fake_upstream_connection->write( + absl::StrCat(std::string(response_headers), std::string(response_data), std::string(trailers), + std::string(rst_stream)))); + + auto readNextResponseFrame = [this]() -> Http2Frame { + while (true) { + auto frame = readFrame(); + if (frame.type() != Http2Frame::Type::WindowUpdate && + frame.type() != Http2Frame::Type::Settings && frame.type() != Http2Frame::Type::Ping) { + return frame; + } + } + }; + + auto headers_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, headers_frame.type()); + + // With an actual error, the stream should be reset — we expect RST_STREAM downstream, + // not the complete response with trailers. Consume any DATA frames first. + auto next_frame = readNextResponseFrame(); + while (next_frame.type() == Http2Frame::Type::Data) { + next_frame = readNextResponseFrame(); + } + EXPECT_EQ(Http2Frame::Type::RstStream, next_frame.type()) + << "Expected RST_STREAM for error reset but got frame type " + << static_cast(next_frame.type()); + ASSERT_GE(next_frame.size(), Http2Frame::HeaderSize + 4); + const uint8_t* p = next_frame.data() + Http2Frame::HeaderSize; + const uint32_t error_code = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + EXPECT_NE(0u, error_code) << "RST_STREAM error code should not be NO_ERROR for an error reset"; + + tcp_client_->close(); +} + +// RFC 9113 Section 8.1: A server MAY send RST_STREAM(NO_ERROR) after sending a complete response +// to request that the client stop sending the request body. The client MUST NOT discard the +// response as a result of receiving such a RST_STREAM, and the proxy MUST NOT translate a +// RST_STREAM(NO_ERROR) into an error. +// +// Verify that when the upstream sends a complete response (with END_STREAM on DATA) followed by +// RST_STREAM(NO_ERROR) before the client has sent END_STREAM, Envoy forwards the response and +// sends RST_STREAM(NO_ERROR) downstream (not an error code). Uses raw H2 frames on both sides +// to ensure compliance without depending on Envoy codec interpretation for the upstream and +// downstream. +TEST_P(Http2FrameIntegrationTest, UpstreamCompleteResponseFollowedByRstStreamNoError) { + beginSession(); + + const uint32_t stream_idx = 1; + sendFrame(Http2Frame::makePostRequest(stream_idx, "host", "/")); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->write(std::string(Http2Frame::makeEmptySettingsFrame()))); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + + const auto response_headers = Http2Frame::makeHeadersFrameWithStatus( + "200", stream_idx, Http2Frame::HeadersFlags::EndHeaders); + const auto response_data = + Http2Frame::makeDataFrame(stream_idx, "hello", Http2Frame::DataFlags::EndStream); + const auto rst_stream = + Http2Frame::makeResetStreamFrame(stream_idx, Http2Frame::ErrorCode::NoError); + + ASSERT_TRUE(fake_upstream_connection->write(absl::StrCat( + std::string(response_headers), std::string(response_data), std::string(rst_stream)))); + + auto readNextResponseFrame = [this]() -> Http2Frame { + while (true) { + auto frame = readFrame(); + if (frame.type() != Http2Frame::Type::WindowUpdate && + frame.type() != Http2Frame::Type::Settings && frame.type() != Http2Frame::Type::Ping) { + return frame; + } + } + }; + + auto headers_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, headers_frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, headers_frame.responseStatus()) + << "Expected 200 OK from upstream, not a local error reply"; + + auto data_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Data, data_frame.type()); + EXPECT_TRUE(data_frame.endStream()); + + auto rst_frame = readNextResponseFrame(); + ASSERT_EQ(Http2Frame::Type::RstStream, rst_frame.type()); + ASSERT_GE(rst_frame.size(), Http2Frame::HeaderSize + 4); + const uint8_t* p = rst_frame.data() + Http2Frame::HeaderSize; + const uint32_t error_code = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + EXPECT_EQ(0u, error_code) << "RST_STREAM error code must be NO_ERROR (0) per RFC 9113 " + "Section 8.1, but got " + << error_code; + + tcp_client_->close(); +} + +// Same scenario as above, but with allow_multiplexed_upstream_half_close enabled and using +// gRPC-style trailers (HEADERS with END_STREAM) instead of DATA with END_STREAM. +// This exercises the path where the upstream stream stays open after the response is complete +// (onUpstreamComplete returns early), so the server's RST_STREAM(NO_ERROR) reaches the router +// as a RemoteReset via onUpstreamReset. The downstream RST_STREAM must still be NO_ERROR. +TEST_P(Http2FrameIntegrationTest, UpstreamTrailersAndRstStreamNoErrorWithHalfClose) { + config_helper_.addRuntimeOverride( + "envoy.reloadable_features.allow_multiplexed_upstream_half_close", "true"); + beginSession(); + + const uint32_t stream_idx = 1; + sendFrame(Http2Frame::makePostRequest(stream_idx, "host", "/")); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->write(std::string(Http2Frame::makeEmptySettingsFrame()))); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + + // HEADERS (200, EndHeaders, no EndStream) + const auto response_headers = Http2Frame::makeHeadersFrameWithStatus( + "200", stream_idx, Http2Frame::HeadersFlags::EndHeaders); + + // DATA ("hello", no EndStream) + const auto response_data = + Http2Frame::makeDataFrame(stream_idx, "hello", Http2Frame::DataFlags::None); + + // Trailers: a HEADERS frame with EndStream | EndHeaders, containing "grpc-status: 0". + // HPACK literal header without indexing, new name: + // 0x00 | name_len(0x0b) | "grpc-status" | value_len(0x01) | "0" + std::string hpack_trailers; + hpack_trailers.push_back(0x00); + hpack_trailers.push_back(0x0b); + hpack_trailers.append("grpc-status"); + hpack_trailers.push_back(0x01); + hpack_trailers.push_back('0'); + const auto trailers = + Http2Frame::makeRawFrame(Http2Frame::Type::Headers, + static_cast(Http2Frame::HeadersFlags::EndStream) | + static_cast(Http2Frame::HeadersFlags::EndHeaders), + stream_idx, hpack_trailers); + + // RST_STREAM (NO_ERROR) + const auto rst_stream = + Http2Frame::makeResetStreamFrame(stream_idx, Http2Frame::ErrorCode::NoError); + + // Send response + RST_STREAM in a single write. + ASSERT_TRUE(fake_upstream_connection->write( + absl::StrCat(std::string(response_headers), std::string(response_data), std::string(trailers), + std::string(rst_stream)))); + + auto readNextResponseFrame = [this]() -> Http2Frame { + while (true) { + auto frame = readFrame(); + if (frame.type() != Http2Frame::Type::WindowUpdate && + frame.type() != Http2Frame::Type::Settings && frame.type() != Http2Frame::Type::Ping) { + return frame; + } + } + }; + + // Response HEADERS (200 OK). + auto headers_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, headers_frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, headers_frame.responseStatus()) + << "Expected 200 OK from upstream, not a local error reply"; + + // Response DATA. + auto data_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Data, data_frame.type()); + + // Response trailers (HEADERS with END_STREAM). + auto trailers_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, trailers_frame.type()); + EXPECT_TRUE(trailers_frame.endStream()); + + // RST_STREAM must be NO_ERROR per RFC 9113 Section 8.1. + auto rst_frame = readNextResponseFrame(); + ASSERT_EQ(Http2Frame::Type::RstStream, rst_frame.type()); + ASSERT_GE(rst_frame.size(), Http2Frame::HeaderSize + 4); + const uint8_t* p = rst_frame.data() + Http2Frame::HeaderSize; + const uint32_t error_code = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + EXPECT_EQ(0u, error_code) << "RST_STREAM error code must be NO_ERROR (0) per RFC 9113 " + "Section 8.1, but got " + << error_code; + + tcp_client_->close(); +} + +// Same as UpstreamTrailersAndRstStreamNoErrorWithHalfClose, but the RST_STREAM is sent in a +// separate write from the response, simulating separate TCP packets. This tests the case where +// the RST_STREAM(NO_ERROR) arrives in a different event loop iteration from the response. +TEST_P(Http2FrameIntegrationTest, UpstreamTrailersAndSeparateRstStreamNoError) { + beginSession(); + + const uint32_t stream_idx = 1; + sendFrame(Http2Frame::makePostRequest(stream_idx, "host", "/")); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->write(std::string(Http2Frame::makeEmptySettingsFrame()))); + test_server_->waitForGauge("cluster.cluster_0.upstream_rq_active", Eq(1)); + + // HEADERS (200, EndHeaders, no EndStream) + const auto response_headers = Http2Frame::makeHeadersFrameWithStatus( + "200", stream_idx, Http2Frame::HeadersFlags::EndHeaders); + + // DATA ("hello", no EndStream) + const auto response_data = + Http2Frame::makeDataFrame(stream_idx, "hello", Http2Frame::DataFlags::None); + + // Trailers with EndStream | EndHeaders containing "grpc-status: 0". + std::string hpack_trailers; + hpack_trailers.push_back(0x00); + hpack_trailers.push_back(0x0b); + hpack_trailers.append("grpc-status"); + hpack_trailers.push_back(0x01); + hpack_trailers.push_back('0'); + const auto trailers = + Http2Frame::makeRawFrame(Http2Frame::Type::Headers, + static_cast(Http2Frame::HeadersFlags::EndStream) | + static_cast(Http2Frame::HeadersFlags::EndHeaders), + stream_idx, hpack_trailers); + + // Send the response (headers + data + trailers) first. + ASSERT_TRUE(fake_upstream_connection->write(absl::StrCat( + std::string(response_headers), std::string(response_data), std::string(trailers)))); + + auto readNextResponseFrame = [this]() -> Http2Frame { + while (true) { + auto frame = readFrame(); + if (frame.type() != Http2Frame::Type::WindowUpdate && + frame.type() != Http2Frame::Type::Settings && frame.type() != Http2Frame::Type::Ping) { + return frame; + } + } + }; + + // Read the response frames first — this implicitly waits for them to be forwarded. + // Response HEADERS (200 OK). + auto headers_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, headers_frame.type()); + EXPECT_EQ(Http2Frame::ResponseStatus::Ok, headers_frame.responseStatus()) + << "Expected 200 OK from upstream, not a local error reply"; + + // Response DATA. + auto data_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Data, data_frame.type()); + + // Response trailers (HEADERS with END_STREAM). + auto trailers_frame = readNextResponseFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, trailers_frame.type()); + EXPECT_TRUE(trailers_frame.endStream()); + + // Now send RST_STREAM(NO_ERROR) in a separate write, after the response is confirmed forwarded. + const auto rst_stream = + Http2Frame::makeResetStreamFrame(stream_idx, Http2Frame::ErrorCode::NoError); + ASSERT_TRUE(fake_upstream_connection->write(std::string(rst_stream))); + + // RST_STREAM must be NO_ERROR. + auto rst_frame = readNextResponseFrame(); + ASSERT_EQ(Http2Frame::Type::RstStream, rst_frame.type()); + ASSERT_GE(rst_frame.size(), Http2Frame::HeaderSize + 4); + const uint8_t* p = rst_frame.data() + Http2Frame::HeaderSize; + const uint32_t error_code = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + EXPECT_EQ(0u, error_code) << "RST_STREAM error code must be NO_ERROR (0) per RFC 9113 " + "Section 8.1, but got " + << error_code; + + tcp_client_->close(); +} + } // namespace Envoy diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index a78d3ac715c4b..9c1c38b05f06e 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -5959,4 +5959,110 @@ TEST_P(DownstreamProtocolIntegrationTest, OptionsWithNoBodyNotChunked) { EXPECT_EQ(response->headers().TransferEncoding(), nullptr); } +// RFC 9113 Section 8.1: When the upstream sends a complete response followed by +// RST_STREAM(NO_ERROR) while the request body hasn't been fully sent, the response +// must be preserved and forwarded to the downstream client. +// This test verifies behavior for all downstream protocols. +TEST_P(ProtocolIntegrationTest, UpstreamRstStreamNoErrorAfterCompleteResponse) { + if (upstreamProtocol() == Http::CodecType::HTTP1) { + // HTTP/1.1 upstream has no RST_STREAM concept. + return; + } + // Without this, onUpstreamComplete() preemptively sends a local RST_STREAM to the upstream + // when the complete response is received, closing the stream before the upstream's + // RST_STREAM(NO_ERROR) arrives. With this enabled, the upstream stream stays open (half-closed + // remote), allowing the RST_STREAM(NO_ERROR) to reach the router through RemoteResetNoError. + config_helper_.addRuntimeOverride( + "envoy.reloadable_features.allow_multiplexed_upstream_half_close", "true"); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Send request headers without end_stream (simulating a POST with pending body). + auto encoder_decoder = codec_client_->startRequest(default_request_headers_); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + + // Wait for the upstream to receive headers, but not end_stream (request body is still pending). + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + + // Upstream sends a complete response (headers + body with END_STREAM), then RST_STREAM(NO_ERROR). + // Send both before waiting so they are in the pipeline together. + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request_->encodeData("hello", true); + // Since response_end_stream was sent, LocalReset maps to NO_ERROR on the wire. + upstream_request_->encodeResetStream(Http::StreamResetReason::LocalReset); + + // The complete response must be forwarded regardless of the RST_STREAM. + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + EXPECT_EQ("hello", response->body()); + + if (downstreamProtocol() == Http::CodecType::HTTP1) { + // For HTTP/1.1: the connection is closed (the only way to signal "stop sending request body"). + ASSERT_TRUE(codec_client_->waitForDisconnect()); + } else { + // For HTTP/2 and HTTP/3: the RST_STREAM(NO_ERROR) arrives after the response. RST_STREAM reason + // is verified by the + // Http2FrameIntegrationTest.UpstreamCompleteResponseFollowedByRstStreamNoError test which uses + // raw frames for precise control. + codec_client_->close(); + } +} + +// RFC 9113 Section 8.1: When the upstream sends a complete response (HEADERS + large DATA + +// trailers with END_STREAM) followed by RST_STREAM(NO_ERROR), and the response body exceeds +// the connection buffer limit, trailers get buffered. The RST_STREAM(NO_ERROR) arrives before +// the buffered trailers are delivered. The deferred close mechanism must ensure the complete +// response (including trailers) is forwarded before handling the RST_STREAM. +// Unlike UpstreamRstStreamNoErrorAfterCompleteResponse, +// this does NOT need `allow_multiplexed_upstream_half_close` because the RST_STREAM arrives +// while trailers are still buffered (before onUpstreamComplete), so no preemptive local +// RST_STREAM has been sent yet. +TEST_P(ProtocolIntegrationTest, UpstreamRstStreamNoErrorWithBufferedTrailers) { + if (upstreamProtocol() == Http::CodecType::HTTP1) { + return; + } + config_helper_.addConfigModifier(setEnableDownstreamTrailersHttp1()); + config_helper_.setBufferLimits(1024, 1024); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto encoder_decoder = codec_client_->startRequest(default_request_headers_); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + + // Wait for the upstream to receive headers, but not end_stream (request body is still pending). + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + + // Upstream sends headers, a body larger than the buffer limit (triggers trailer buffering), + // trailers with END_STREAM, then RST_STREAM(NO_ERROR). + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request_->encodeData(std::string(2000, 'a'), false); + Http::TestResponseTrailerMapImpl response_trailers{{"grpc-status", "0"}}; + upstream_request_->encodeTrailers(response_trailers); + upstream_request_->encodeResetStream(Http::StreamResetReason::LocalReset); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + EXPECT_EQ(2000U, response->body().size()); + if (downstreamProtocol() != Http::CodecType::HTTP1) { + ASSERT_TRUE(response->trailers()); + EXPECT_EQ("0", response->trailers()->getGrpcStatusValue()); + } + + if (downstreamProtocol() == Http::CodecType::HTTP1) { + ASSERT_TRUE(codec_client_->waitForDisconnect()); + } else { + ASSERT_TRUE(response->waitForReset()); + EXPECT_EQ(Http::StreamResetReason::RemoteResetNoError, response->resetReason()); + codec_client_->close(); + } +} + } // namespace Envoy diff --git a/test/integration/websocket_integration_test.cc b/test/integration/websocket_integration_test.cc index c1343969f17f0..b3013a9dd63ba 100644 --- a/test/integration/websocket_integration_test.cc +++ b/test/integration/websocket_integration_test.cc @@ -371,7 +371,7 @@ TEST_P(WebsocketIntegrationTest, WebsocketCustomFilterChain) { codec_client_->sendData(encoder_decoder.first, large_req_str, false); ASSERT_TRUE(response_->waitForEndStream()); EXPECT_EQ("413", response_->headers().getStatusValue()); - waitForClientDisconnectOrReset(); + waitForClientDisconnectOrReset(Http::StreamResetReason::RemoteResetNoError); codec_client_->close(); } @@ -389,7 +389,7 @@ TEST_P(WebsocketIntegrationTest, WebsocketCustomFilterChain) { ASSERT_TRUE(response_->waitForEndStream()); EXPECT_EQ("413", response_->headers().getStatusValue()); if (downstreamProtocol() != Http::CodecType::HTTP3) { - waitForClientDisconnectOrReset(); + waitForClientDisconnectOrReset(Http::StreamResetReason::RemoteResetNoError); } codec_client_->close(); } From 6937fe4c7a5e1542bc955c6d2851a68bea174da3 Mon Sep 17 00:00:00 2001 From: Ethan Truong Date: Mon, 18 May 2026 21:07:09 -0400 Subject: [PATCH 339/750] Use cryptographically secure HMAC comparison in oauth2 (#45125) The timing side-channel attack is not feasible remotely as network jitter is much, much greater than the the time differences in string comparisons. However, cryptographic hardening may still be appropriate. Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Ethan Truong --- .../extensions/filters/http/oauth2/filter.cc | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/source/extensions/filters/http/oauth2/filter.cc b/source/extensions/filters/http/oauth2/filter.cc index 940e15f14f805..33347a53809ce 100644 --- a/source/extensions/filters/http/oauth2/filter.cc +++ b/source/extensions/filters/http/oauth2/filter.cc @@ -29,6 +29,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/strings/str_split.h" +#include "openssl/mem.h" #include "openssl/rand.h" using namespace std::chrono_literals; @@ -42,6 +43,14 @@ namespace { Http::RegisterCustomInlineHeader authorization_handle(Http::CustomHeaders::get().Authorization); +// Cryptographically safe HMAC comparison of two string views. +bool safeStringViewEqual(absl::string_view s1, absl::string_view s2) { + if (s1.length() != s2.length()) { + return false; + } + return CRYPTO_memcmp(s1.data(), s2.data(), s1.length()) == 0; +} + constexpr const char* CookieDeleteFormatString = "{}=deleted; path={}; expires=Thu, 01 Jan 1970 00:00:00 GMT"; constexpr const char* CookieTailHttpOnlyFormatString = ";path={};Max-Age={};secure;HttpOnly{}"; @@ -275,7 +284,7 @@ bool validateCsrfTokenHmac(const std::string& hmac_secret, const std::string& cs std::string token = std::string(csrf_token.substr(0, pos)); std::string hmac = std::string(csrf_token.substr(pos + 1)); std::vector hmac_secret_vec(hmac_secret.begin(), hmac_secret.end()); - return generateHmacBase64(hmac_secret_vec, token) == hmac; + return safeStringViewEqual(generateHmacBase64(hmac_secret_vec, token), hmac); } // Generates a PKCE code verifier with 32 octets of randomness. @@ -590,10 +599,12 @@ bool OAuth2CookieValidator::hmacIsValid() const { if (!cookie_domain_.empty()) { cookie_domain = cookie_domain_; } - return ((encodeHmacBase64(secret_, cookie_domain, expires_, access_token_, id_token_, - refresh_token_) == hmac_) || - (encodeHmacHexBase64(secret_, cookie_domain, expires_, access_token_, id_token_, - refresh_token_) == hmac_)); + return (safeStringViewEqual(encodeHmacBase64(secret_, cookie_domain, expires_, access_token_, + id_token_, refresh_token_), + hmac_) || + safeStringViewEqual(encodeHmacHexBase64(secret_, cookie_domain, expires_, access_token_, + id_token_, refresh_token_), + hmac_)); } bool OAuth2CookieValidator::timestampIsValid() const { @@ -1622,7 +1633,7 @@ bool OAuth2Filter::validateCsrfToken(const Http::RequestHeaderMap& headers, return false; } - if (cookie_value.value() != csrf_token) { + if (!safeStringViewEqual(cookie_value.value(), csrf_token)) { return false; } return validateCsrfTokenHmac(config_->hmacSecret(), csrf_token); From 5d71bc2fcf995dd052fd9ace4c848d63217de687 Mon Sep 17 00:00:00 2001 From: code Date: Tue, 19 May 2026 10:05:39 +0800 Subject: [PATCH 340/750] change log: add missed change log for oauth2 fix (#45137) Commit Message: change log: add missed change log for oauth2 fix Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: wbpcode/wangbaiping --- changelogs/current.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 115087ba33431..76298bb732f07 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -127,6 +127,10 @@ bug_fixes: Apple's ``SystemConfiguration`` framework. The framework is required by the ``system-configuration`` crate that ``hickory-resolver`` pulls in via its ``system-config`` feature. +- area: oauth2 + change: | + Fixed a bug where HMAC verification may exposure a timing side channel that leaks information of + HMAC secret validity. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` From 92e8ed01e1eb34c6dd0da277b4d0f71ce366ff11 Mon Sep 17 00:00:00 2001 From: Krishna Sharma <113549196+roll-no-21@users.noreply.github.com> Date: Tue, 19 May 2026 10:27:11 +0530 Subject: [PATCH 341/750] reverse_connection: update cluster membership stats on host creation and cleanup (#44734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description The `RevConCluster` creates hosts on-demand in its internal `host_map_` but never calls `priority_set_.updateHosts()`, leaving `membership_total`, `membership_healthy`, and `membership_change` permanently at 0 even when reverse connections are active and serving traffic. This makes monitoring and alerting on reverse connection cluster health impossible. This PR mirrors the `OriginalDstCluster` pattern: - **Host addition**: When `chooseHost()` creates a new host, it posts `addHostToHostSet()` to the main thread via `dispatcher_.post()`, which calls `priority_set_.updateHosts()` to register the host. A `weak_ptr` reference ensures safe lifetime management. - **Host removal**: `cleanup()` now also calls `priority_set_.updateHosts()` when removing stale hosts, so membership stats decrement correctly. The lock scope is narrowed to avoid calling `updateHosts()` while holding `host_map_lock_`. ## Risk Level Low — additive change to an existing cluster type. No behavioral change to request routing; only cluster membership stats reporting is affected. ## Testing - 4 new unit tests covering host creation, host reuse (no double-count), multiple hosts, and cleanup removal from the priority set. - All existing reverse connection cluster tests continue to pass. - Manual E2E: ran initiator + responder envoy configs with `--concurrency 1`, verified `membership_total` goes from 0→1 after the first request through the tunnel. ## Docs Changes N/A ## Release Notes N/A --------- Signed-off-by: k-sharma_data Signed-off-by: Krishna Sharma --- .../reverse_connection/reverse_connection.cc | 66 ++++-- .../reverse_connection/reverse_connection.h | 5 +- .../reverse_connection_cluster_test.cc | 208 +++++++++++++++++- 3 files changed, 264 insertions(+), 15 deletions(-) diff --git a/source/extensions/clusters/reverse_connection/reverse_connection.cc b/source/extensions/clusters/reverse_connection/reverse_connection.cc index e3bd4a9c59976..9300c29628e21 100644 --- a/source/extensions/clusters/reverse_connection/reverse_connection.cc +++ b/source/extensions/clusters/reverse_connection/reverse_connection.cc @@ -94,10 +94,26 @@ RevConCluster::LoadBalancer::chooseHost(Upstream::LoadBalancerContext* context) } ENVOY_LOG(debug, "reverse_connection: using host identifier: {}", final_host_id); - return parent_->checkAndCreateHost(final_host_id); + + Upstream::HostSharedPtr created_host; + auto response = parent_->checkAndCreateHost(final_host_id, created_host); + + if (created_host != nullptr) { + std::weak_ptr weak_parent = parent_; + parent_->dispatcher_.post([weak_parent, created_host]() { + if (auto parent = weak_parent.lock()) { + parent->addHostToHostSet(created_host); + } + }); + } + + return response; } -Upstream::HostSelectionResponse RevConCluster::checkAndCreateHost(absl::string_view host_id) { +Upstream::HostSelectionResponse +RevConCluster::checkAndCreateHost(absl::string_view host_id, + Upstream::HostSharedPtr& created_host) { + created_host = nullptr; // Get the SocketManager to resolve cluster ID to node ID. // The bootstrap extension is validated during cluster creation, and TLS is initialized before // request handling, so socket_manager should always be available. @@ -146,25 +162,49 @@ Upstream::HostSelectionResponse RevConCluster::checkAndCreateHost(absl::string_v ENVOY_LOG(trace, "reverse_connection: created HostImpl {} for {}.", *host, node_id); host_map_[node_id] = host; + created_host = host; return {host}; } +void RevConCluster::addHostToHostSet(Upstream::HostSharedPtr host) { + const auto& first_host_set = priority_set_.getOrCreateHostSet(0); + Upstream::HostVectorSharedPtr all_hosts(new Upstream::HostVector(first_host_set.hosts())); + all_hosts->emplace_back(host); + ENVOY_LOG(debug, "reverse_connection: adding host to priority set, total hosts: {}", + all_hosts->size()); + priority_set_.updateHosts( + 0, Upstream::HostSetImpl::partitionHosts(all_hosts, Upstream::HostsPerLocalityImpl::empty()), + {}, {std::move(host)}, {}, absl::nullopt, absl::nullopt); +} + void RevConCluster::cleanup() { - absl::WriterMutexLock wlock(host_map_lock_); + Upstream::HostVector to_be_removed; + Upstream::HostVectorSharedPtr keeping_hosts; - for (auto iter = host_map_.begin(); iter != host_map_.end();) { - // Check if the host handle is acquired by any connection pool container or not. If not - // clean those host to prevent memory leakage. - const auto& host = iter->second; - if (!host->used()) { - ENVOY_LOG(debug, "Removing stale host: {}", *host); - host_map_.erase(iter++); - } else { - ++iter; + { + absl::WriterMutexLock wlock(host_map_lock_); + keeping_hosts = std::make_shared(); + + for (auto iter = host_map_.begin(); iter != host_map_.end();) { + const auto& host = iter->second; + if (!host->used()) { + ENVOY_LOG(debug, "Removing stale host: {}", *host); + to_be_removed.push_back(host); + host_map_.erase(iter++); + } else { + keeping_hosts->push_back(host); + ++iter; + } } } - // Reschedule the cleanup after cleanup_interval_ duration. + if (!to_be_removed.empty()) { + priority_set_.updateHosts(0, + Upstream::HostSetImpl::partitionHosts( + keeping_hosts, Upstream::HostsPerLocalityImpl::empty()), + {}, {}, to_be_removed, absl::nullopt, absl::nullopt); + } + cleanup_timer_->enableTimer(cleanup_interval_); } diff --git a/source/extensions/clusters/reverse_connection/reverse_connection.h b/source/extensions/clusters/reverse_connection/reverse_connection.h index 133d8b576649e..9860cb1d0e658 100644 --- a/source/extensions/clusters/reverse_connection/reverse_connection.h +++ b/source/extensions/clusters/reverse_connection/reverse_connection.h @@ -203,7 +203,10 @@ class RevConCluster : public Upstream::ClusterImplBase { void cleanup(); // Checks if a host exists for a given host identifier and if not creates and caches it. - Upstream::HostSelectionResponse checkAndCreateHost(absl::string_view host_id); + Upstream::HostSelectionResponse checkAndCreateHost(absl::string_view host_id, + Upstream::HostSharedPtr& created_host); + + void addHostToHostSet(Upstream::HostSharedPtr host); // Get the upstream socket manager from the thread-local registry. BootstrapReverseConnection::UpstreamSocketManager* getUpstreamSocketManager() const; diff --git a/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc b/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc index 69375f67706ae..f4492b560d0d0 100644 --- a/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc +++ b/test/extensions/clusters/reverse_connection/reverse_connection_cluster_test.cc @@ -256,7 +256,7 @@ class ReverseConnectionClusterTest : public Event::TestUsingSimulatedTime, publi NiceMock validation_visitor_; std::shared_ptr cluster_; - ReadyWatcher membership_updated_; + NiceMock membership_updated_; ReadyWatcher initialized_; Event::MockTimer* cleanup_timer_; ::Envoy::Common::CallbackHandlePtr priority_update_cb_; @@ -2077,6 +2077,212 @@ TEST_F(ReverseConnectionClusterWithTenantIsolationTest, ClusterUsesRequestStream EXPECT_EQ(result.host->address()->logicalName(), "tenant1:node1"); } +// --- Cluster membership stats tests --- + +// Verify that chooseHost() posts a priority set update for new hosts, and that +// membership_total/membership_healthy/membership_change reflect the actual state. +TEST_F(ReverseConnectionClusterTest, HostCreationUpdatesMembership) { + const std::string yaml = R"EOF( + name: name + connect_timeout: 0.25s + lb_policy: CLUSTER_PROVIDED + cleanup_interval: 1s + cluster_type: + name: envoy.clusters.reverse_connection + typed_config: + "@type": type.googleapis.com/envoy.extensions.clusters.reverse_connection.v3.ReverseConnectionClusterConfig + cleanup_interval: 10s + host_id_format: "%REQ(x-remote-node-id)%" + )EOF"; + + setupFromYaml(yaml); + setupUpstreamExtension(); + setupThreadLocalSlot(); + addTestSocket("test-node-1", "test-cluster-1"); + + // Capture the callback posted to the dispatcher by chooseHost(). + Event::PostCb post_cb; + EXPECT_CALL(server_context_.dispatcher_, post(_)) + .WillOnce(testing::Invoke([&post_cb](Event::PostCb cb) { post_cb = std::move(cb); })); + + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + + RevConCluster::LoadBalancer lb(cluster_); + NiceMock connection; + TestLoadBalancerContext lb_context(&connection); + lb_context.downstream_headers_ = Http::RequestHeaderMapPtr{ + new Http::TestRequestHeaderMapImpl{{"x-remote-node-id", "test-node-1"}}}; + + auto result = lb.chooseHost(&lb_context); + ASSERT_NE(result.host, nullptr); + + // Priority set not yet updated (post hasn't run). + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + + // Simulate main thread processing the posted callback. + EXPECT_CALL(membership_updated_, ready()); + post_cb(); + + // Now membership should reflect the new host. + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +} + +// Verify that reusing an existing host does not post another priority set update. +TEST_F(ReverseConnectionClusterTest, HostReuseDoesNotDoubleMembership) { + const std::string yaml = R"EOF( + name: name + connect_timeout: 0.25s + lb_policy: CLUSTER_PROVIDED + cleanup_interval: 1s + cluster_type: + name: envoy.clusters.reverse_connection + typed_config: + "@type": type.googleapis.com/envoy.extensions.clusters.reverse_connection.v3.ReverseConnectionClusterConfig + cleanup_interval: 10s + host_id_format: "%REQ(x-remote-node-id)%" + )EOF"; + + setupFromYaml(yaml); + setupUpstreamExtension(); + setupThreadLocalSlot(); + addTestSocket("test-node-1", "test-cluster-1"); + + Event::PostCb post_cb; + // Expect exactly one post (for the first chooseHost only). + EXPECT_CALL(server_context_.dispatcher_, post(_)) + .WillOnce(testing::Invoke([&post_cb](Event::PostCb cb) { post_cb = std::move(cb); })); + + RevConCluster::LoadBalancer lb(cluster_); + NiceMock connection; + TestLoadBalancerContext lb_context(&connection); + lb_context.downstream_headers_ = Http::RequestHeaderMapPtr{ + new Http::TestRequestHeaderMapImpl{{"x-remote-node-id", "test-node-1"}}}; + + // First call creates the host and posts. + auto result1 = lb.chooseHost(&lb_context); + ASSERT_NE(result1.host, nullptr); + + // Run the posted callback. + EXPECT_CALL(membership_updated_, ready()); + post_cb(); + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + + // Second call reuses the host — no additional post expected. + auto result2 = lb.chooseHost(&lb_context); + ASSERT_NE(result2.host, nullptr); + EXPECT_EQ(result1.host, result2.host); + + // Membership unchanged. + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); +} + +// Verify that multiple distinct hosts each trigger a membership update. +TEST_F(ReverseConnectionClusterTest, MultipleHostsMembershipUpdate) { + const std::string yaml = R"EOF( + name: name + connect_timeout: 0.25s + lb_policy: CLUSTER_PROVIDED + cleanup_interval: 1s + cluster_type: + name: envoy.clusters.reverse_connection + typed_config: + "@type": type.googleapis.com/envoy.extensions.clusters.reverse_connection.v3.ReverseConnectionClusterConfig + cleanup_interval: 10s + host_id_format: "%REQ(x-remote-node-id)%" + )EOF"; + + setupFromYaml(yaml); + setupUpstreamExtension(); + setupThreadLocalSlot(); + addTestSocket("node-a", "cluster-a"); + addTestSocket("node-b", "cluster-b"); + + std::vector post_cbs; + EXPECT_CALL(server_context_.dispatcher_, post(_)) + .Times(2) + .WillRepeatedly( + testing::Invoke([&post_cbs](Event::PostCb cb) { post_cbs.push_back(std::move(cb)); })); + + RevConCluster::LoadBalancer lb(cluster_); + + // Create host A. + { + NiceMock connection; + TestLoadBalancerContext lb_context(&connection); + lb_context.downstream_headers_ = Http::RequestHeaderMapPtr{ + new Http::TestRequestHeaderMapImpl{{"x-remote-node-id", "node-a"}}}; + auto result = lb.chooseHost(&lb_context); + ASSERT_NE(result.host, nullptr); + } + + // Create host B. + { + NiceMock connection; + TestLoadBalancerContext lb_context(&connection); + lb_context.downstream_headers_ = Http::RequestHeaderMapPtr{ + new Http::TestRequestHeaderMapImpl{{"x-remote-node-id", "node-b"}}}; + auto result = lb.chooseHost(&lb_context); + ASSERT_NE(result.host, nullptr); + } + + ASSERT_EQ(2UL, post_cbs.size()); + + // Run both posted callbacks. + EXPECT_CALL(membership_updated_, ready()).Times(2); + post_cbs[0](); + post_cbs[1](); + + EXPECT_EQ(2UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(2UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +} + +// Verify that cleanup() removes stale hosts from the priority set. +TEST_F(ReverseConnectionClusterTest, CleanupRemovesHostsFromPrioritySet) { + const std::string yaml = R"EOF( + name: name + connect_timeout: 0.25s + lb_policy: CLUSTER_PROVIDED + cleanup_interval: 1s + cluster_type: + name: envoy.clusters.reverse_connection + typed_config: + "@type": type.googleapis.com/envoy.extensions.clusters.reverse_connection.v3.ReverseConnectionClusterConfig + cleanup_interval: 10s + host_id_format: "%REQ(x-remote-node-id)%" + )EOF"; + + setupFromYaml(yaml); + setupUpstreamExtension(); + setupThreadLocalSlot(); + addTestSocket("stale-node", "stale-cluster"); + + Event::PostCb post_cb; + EXPECT_CALL(server_context_.dispatcher_, post(_)) + .WillOnce(testing::Invoke([&post_cb](Event::PostCb cb) { post_cb = std::move(cb); })); + + RevConCluster::LoadBalancer lb(cluster_); + NiceMock connection; + TestLoadBalancerContext lb_context(&connection); + lb_context.downstream_headers_ = Http::RequestHeaderMapPtr{ + new Http::TestRequestHeaderMapImpl{{"x-remote-node-id", "stale-node"}}}; + + auto result = lb.chooseHost(&lb_context); + ASSERT_NE(result.host, nullptr); + + // Run the post to add to priority set. + EXPECT_CALL(membership_updated_, ready()); + post_cb(); + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + + // The host is not "used" (no active connection pool holds it), so cleanup should remove it. + EXPECT_CALL(membership_updated_, ready()); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); + callCleanup(); + + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); +} + } // namespace ReverseConnection } // namespace Extensions } // namespace Envoy From ae96a25fdb7d189cabc94c917432cb9042758454 Mon Sep 17 00:00:00 2001 From: Isaac Wilson Date: Tue, 19 May 2026 08:08:39 -0600 Subject: [PATCH 342/750] orca: add out-of-band reporting for client-side WRR (#44403) Commit Message: orca: add out-of-band reporting for client-side WRR Additional Description: Adds ORCA out-of-band load reporting support for `client_side_weighted_round_robin`. When `enable_oob_load_report` is true, Envoy opens a per-host HTTP/2 gRPC stream to `xds.service.orca.v3.OpenRcaService/StreamCoreMetrics`, decodes server-streamed `OrcaLoadReport`s, and feeds them into the existing ORCA weight manager used by in-band reports. This also adds a dedicated host connection path for ORCA reporting, including LogicalHost address snapshotting, and cluster-scoped `lb_orca_oob.*` stats for reporting/session state. Risk Level: Low Testing: Added unit and integration coverage for: - ORCA OOB manager lifecycle, report decoding, transient failures, GOAWAY handling, and terminal `UNIMPLEMENTED` - host/logical-host ORCA reporting connection behavior - CSWRR OOB config defaults/overrides - basic OOB integration stats/report flow Docs Changes: N/A Release Notes: Added changelog entry for `client_side_weighted_round_robin` ORCA out-of-band reporting Platform Specific Features: N/A xRef #44318 xRef #44329 AI Disclosure: AI was used during implementation --------- Signed-off-by: jukie <10012479+jukie@users.noreply.github.com> Signed-off-by: Isaac Wilson Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- changelogs/current.yaml | 7 + envoy/upstream/host_description.h | 6 + envoy/upstream/upstream.h | 15 + source/common/upstream/upstream_impl.cc | 13 + source/common/upstream/upstream_impl.h | 7 + .../clusters/common/logical_host.cc | 24 + .../extensions/clusters/common/logical_host.h | 9 + .../client_side_weighted_round_robin/BUILD | 1 + .../client_side_weighted_round_robin_lb.cc | 23 +- .../client_side_weighted_round_robin_lb.h | 9 + .../load_balancing_policies/common/BUILD | 33 + .../common/orca_oob_manager.cc | 371 +++++++++ .../common/orca_oob_manager.h | 178 +++++ test/common/upstream/BUILD | 1 + test/common/upstream/upstream_impl_test.cc | 60 ++ test/extensions/clusters/common/BUILD | 2 + .../clusters/common/logical_host_test.cc | 59 ++ .../client_side_weighted_round_robin/BUILD | 1 + ...lient_side_weighted_round_robin_lb_test.cc | 17 + .../integration_test.cc | 129 +++ .../load_balancing_policies/common/BUILD | 24 + .../common/orca_oob_manager_test.cc | 738 ++++++++++++++++++ test/mocks/upstream/host.h | 10 + 23 files changed, 1736 insertions(+), 1 deletion(-) create mode 100644 source/extensions/load_balancing_policies/common/orca_oob_manager.cc create mode 100644 source/extensions/load_balancing_policies/common/orca_oob_manager.h create mode 100644 test/extensions/load_balancing_policies/common/orca_oob_manager_test.cc diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 76298bb732f07..917dca38444c3 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -206,6 +206,13 @@ new_features: RSA public key exchange on behalf of the client. Added a new :ref:`downstream_ssl ` config option with ``DISABLE``, ``REQUIRE``, and ``ALLOW`` modes. +- area: load_balancing + change: | + client_side_weighted_round_robin: implemented out-of-band ORCA load reporting via + server-streaming gRPC (``xds.service.orca.v3.OpenRcaService.StreamCoreMetrics``) when + :ref:`enable_oob_load_report + ` + is true. Cluster-scoped stats are emitted under the ``lb_orca_oob.`` prefix. - area: quic change: | Added support for TLS session ticket resumption in QUIC using configured session ticket keys from diff --git a/envoy/upstream/host_description.h b/envoy/upstream/host_description.h index 434f326505d45..8c1fef1120ec0 100644 --- a/envoy/upstream/host_description.h +++ b/envoy/upstream/host_description.h @@ -263,6 +263,12 @@ class HostDescription { */ virtual Network::Address::InstanceConstSharedPtr healthCheckAddress() const PURE; + /** + * @return the address used to dial ORCA out-of-band load reporting streams + * (xds.service.orca.v3.OpenRcaService). + */ + virtual Network::Address::InstanceConstSharedPtr orcaReportingAddress() const PURE; + /** * @return the priority of the host. */ diff --git a/envoy/upstream/upstream.h b/envoy/upstream/upstream.h index cc1c9142954a6..9d6fa485afdad 100644 --- a/envoy/upstream/upstream.h +++ b/envoy/upstream/upstream.h @@ -216,6 +216,21 @@ class Host : virtual public HostDescription { Network::TransportSocketOptionsConstSharedPtr transport_socket_options, const envoy::config::core::v3::Metadata* metadata) const PURE; + /** + * Create a dedicated connection for ORCA out-of-band load reporting per gRFC A51 + * (xds.service.orca.v3.OpenRcaService), separate from request and health-check pools. + * Dials the address returned by orcaReportingAddress(). + * @param dispatcher supplies the owning dispatcher. + * @param transport_socket_options supplies the transport options that will be set on the new + * connection. + * @param metadata when non-null drives transport socket factory resolution. + * @return the connection data. + */ + virtual CreateConnectionData createOrcaReportingConnection( + Event::Dispatcher& dispatcher, + Network::TransportSocketOptionsConstSharedPtr transport_socket_options, + const envoy::config::core::v3::Metadata* metadata) const PURE; + /** * @return host specific gauges. */ diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index bc8bf43f55927..ce598593b2d74 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -603,6 +603,19 @@ Host::CreateConnectionData HostImplBase::createHealthCheckConnection( transport_socket_options, shared_from_this()); } +Host::CreateConnectionData HostImplBase::createOrcaReportingConnection( + Event::Dispatcher& dispatcher, + Network::TransportSocketOptionsConstSharedPtr transport_socket_options, + const envoy::config::core::v3::Metadata* metadata) const { + const auto orca_address = orcaReportingAddress(); + Network::UpstreamTransportSocketFactory& factory = + (metadata != nullptr) + ? resolveTransportSocketFactory(orca_address, metadata, transport_socket_options) + : transportSocketFactory(); + return createConnection(dispatcher, cluster(), orca_address, addressListOrNull(), factory, + /*options=*/nullptr, transport_socket_options, shared_from_this()); +} + absl::optional HostImplBase::maybeGetProxyRedirectAddress( const Network::TransportSocketOptionsConstSharedPtr transport_socket_options, HostDescriptionConstSharedPtr host, diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 3572b1cf32033..eea24b84d0df7 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -324,6 +324,9 @@ class HostDescriptionImpl : public HostDescriptionImplBase { Network::Address::InstanceConstSharedPtr healthCheckAddress() const override { return health_check_address_; } + Network::Address::InstanceConstSharedPtr orcaReportingAddress() const override { + return address_; + } SharedConstAddressVector addressListOrNull() const override { return address_list_or_null_; } protected: @@ -379,6 +382,10 @@ class HostImplBase : public Host, Event::Dispatcher& dispatcher, Network::TransportSocketOptionsConstSharedPtr transport_socket_options, const envoy::config::core::v3::Metadata* metadata) const override; + CreateConnectionData createOrcaReportingConnection( + Event::Dispatcher& dispatcher, + Network::TransportSocketOptionsConstSharedPtr transport_socket_options, + const envoy::config::core::v3::Metadata* metadata) const override; std::vector> gauges() const override { diff --git a/source/extensions/clusters/common/logical_host.cc b/source/extensions/clusters/common/logical_host.cc index de95aa6beda25..6de330b341959 100644 --- a/source/extensions/clusters/common/logical_host.cc +++ b/source/extensions/clusters/common/logical_host.cc @@ -77,6 +77,11 @@ Network::Address::InstanceConstSharedPtr LogicalHost::address() const { return address_; } +Network::Address::InstanceConstSharedPtr LogicalHost::orcaReportingAddress() const { + absl::MutexLock lock(address_lock_); + return address_; +} + Upstream::Host::CreateConnectionData LogicalHost::createConnection( Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options, Network::TransportSocketOptionsConstSharedPtr transport_socket_options) const { @@ -108,5 +113,24 @@ Upstream::Host::CreateConnectionData LogicalHost::createConnection( std::make_shared(address, shared_from_this())); } +Upstream::Host::CreateConnectionData LogicalHost::createOrcaReportingConnection( + Event::Dispatcher& dispatcher, + Network::TransportSocketOptionsConstSharedPtr transport_socket_options, + const envoy::config::core::v3::Metadata* metadata) const { + const Network::Address::InstanceConstSharedPtr address = orcaReportingAddress(); + const SharedConstAddressVector address_list_or_null = addressListOrNull(); + // Use override_transport_socket_options if set, otherwise use the passed options. + const auto& effective_options = override_transport_socket_options_ != nullptr + ? override_transport_socket_options_ + : transport_socket_options; + Network::UpstreamTransportSocketFactory& factory = + (metadata != nullptr) ? resolveTransportSocketFactory(address, metadata, effective_options) + : transportSocketFactory(); + return HostImplBase::createConnection( + dispatcher, cluster(), address, address_list_or_null, factory, + /*options=*/nullptr, effective_options, + std::make_shared(address, shared_from_this())); +} + } // namespace Upstream } // namespace Envoy diff --git a/source/extensions/clusters/common/logical_host.h b/source/extensions/clusters/common/logical_host.h index f50a8db560494..2262857f392f5 100644 --- a/source/extensions/clusters/common/logical_host.h +++ b/source/extensions/clusters/common/logical_host.h @@ -46,11 +46,16 @@ class LogicalHost : public HostImplBase, public HostDescriptionImplBase { CreateConnectionData createConnection( Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options, Network::TransportSocketOptionsConstSharedPtr transport_socket_options) const override; + Upstream::Host::CreateConnectionData createOrcaReportingConnection( + Event::Dispatcher& dispatcher, + Network::TransportSocketOptionsConstSharedPtr transport_socket_options, + const envoy::config::core::v3::Metadata* metadata) const override; // Upstream::HostDescription SharedConstAddressVector addressListOrNull() const override; Network::Address::InstanceConstSharedPtr address() const override; Network::Address::InstanceConstSharedPtr healthCheckAddress() const override; + Network::Address::InstanceConstSharedPtr orcaReportingAddress() const override; protected: LogicalHost( @@ -121,6 +126,10 @@ class RealHostDescription : public HostDescription { // Should never be called since real hosts are used only for forwarding. return nullptr; } + Network::Address::InstanceConstSharedPtr orcaReportingAddress() const override { + // Should never be called since real hosts are used only for forwarding. + return nullptr; + } absl::optional lastHcPassTime() const override { return logical_host_->lastHcPassTime(); } diff --git a/source/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD b/source/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD index 6518f77d0ebfd..9210351ff2b35 100644 --- a/source/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD +++ b/source/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD @@ -30,6 +30,7 @@ envoy_cc_library( "//envoy/thread_local:thread_local_interface", "//source/common/common:callback_impl_lib", "//source/extensions/load_balancing_policies/common:load_balancer_lib", + "//source/extensions/load_balancing_policies/common:orca_oob_manager_lib", "//source/extensions/load_balancing_policies/common:orca_weight_manager_lib", "//source/extensions/load_balancing_policies/round_robin:round_robin_lb_lib", "@envoy_api//envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3:pkg_cc_proto", diff --git a/source/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb.cc b/source/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb.cc index 90b92cb2a3f96..6e12d7bfe4454 100644 --- a/source/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb.cc +++ b/source/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb.cc @@ -3,6 +3,8 @@ #include #include +#include "envoy/common/exception.h" + #include "source/common/protobuf/utility.h" #include "source/extensions/load_balancing_policies/common/load_balancer_impl.h" @@ -41,6 +43,10 @@ ClientSideWeightedRoundRobinLbConfig::ClientSideWeightedRoundRobinLbConfig( weight_update_period = std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(lb_proto, weight_update_period, 1000)); + enable_oob_load_report = lb_proto.enable_oob_load_report().value(); + oob_reporting_period = + std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(lb_proto, oob_reporting_period, 10000)); + if (lb_proto.has_slow_start_config()) { *round_robin_overrides_.mutable_slow_start_config() = lb_proto.slow_start_config(); } @@ -113,10 +119,25 @@ ClientSideWeightedRoundRobinLoadBalancer::ClientSideWeightedRoundRobinLoadBalanc std::make_unique( orca_config, priority_set, time_source, typed_lb_config->main_thread_dispatcher_, [factory = factory_]() { factory->applyWeightsToAllWorkers(); }); + + // Init order relies on PrioritySetImpl::updateHosts() firing priority callbacks + // (OrcaWeightManager attaches OrcaHostLbPolicyData) before member callbacks (OrcaOobManager + // opens the session), so the data is in place before the first OOB report. + if (typed_lb_config->enable_oob_load_report) { + orca_oob_manager_ = + std::make_unique( + typed_lb_config->oob_reporting_period, priority_set, + typed_lb_config->main_thread_dispatcher_, random, cluster_info.statsScope(), + orca_weight_manager_->reportHandler()); + } } absl::Status ClientSideWeightedRoundRobinLoadBalancer::initialize() { - return orca_weight_manager_->initialize(); + RETURN_IF_NOT_OK(orca_weight_manager_->initialize()); + if (orca_oob_manager_ != nullptr) { + RETURN_IF_NOT_OK(orca_oob_manager_->initialize()); + } + return absl::OkStatus(); } } // namespace Upstream diff --git a/source/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb.h b/source/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb.h index f032f450ad537..45b468f3a7e90 100644 --- a/source/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb.h +++ b/source/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb.h @@ -8,6 +8,7 @@ #include "source/common/common/callback_impl.h" #include "source/extensions/load_balancing_policies/common/load_balancer_impl.h" +#include "source/extensions/load_balancing_policies/common/orca_oob_manager.h" #include "source/extensions/load_balancing_policies/common/orca_weight_manager.h" #include "source/extensions/load_balancing_policies/round_robin/round_robin_lb.h" @@ -38,6 +39,9 @@ class ClientSideWeightedRoundRobinLbConfig : public Upstream::LoadBalancerConfig std::chrono::milliseconds weight_expiration_period; std::chrono::milliseconds weight_update_period; + bool enable_oob_load_report; + std::chrono::milliseconds oob_reporting_period; + // Round robin proto overrides that we want to propagate to the worker RR LB (e.g., slow start). RoundRobinConfig round_robin_overrides_; @@ -126,6 +130,11 @@ class ClientSideWeightedRoundRobinLoadBalancer : public Upstream::ThreadAwareLoa // ORCA weight manager handles all weight computation and host data management. std::unique_ptr orca_weight_manager_; + + // ORCA out-of-band manager. Constructed only when enable_oob_load_report is true; null + // otherwise. Shares the OrcaWeightManager's report handler so OOB reports feed the same + // per-host atomics as in-band reports. + std::unique_ptr orca_oob_manager_; }; } // namespace Upstream diff --git a/source/extensions/load_balancing_policies/common/BUILD b/source/extensions/load_balancing_policies/common/BUILD index 937f63d139d1b..aa55192d543e1 100644 --- a/source/extensions/load_balancing_policies/common/BUILD +++ b/source/extensions/load_balancing_policies/common/BUILD @@ -61,6 +61,39 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "orca_oob_manager_lib", + srcs = ["orca_oob_manager.cc"], + hdrs = ["orca_oob_manager.h"], + deps = [ + ":orca_weight_manager_lib", + "//envoy/common:random_generator_interface", + "//envoy/event:dispatcher_interface", + "//envoy/event:timer_interface", + "//envoy/grpc:status", + "//envoy/http:codec_interface", + "//envoy/http:codes_interface", + "//envoy/network:connection_interface", + "//envoy/stats:stats_interface", + "//envoy/stats:stats_macros", + "//envoy/upstream:upstream_interface", + "//source/common/buffer:zero_copy_input_stream_lib", + "//source/common/common:backoff_lib", + "//source/common/common:callback_impl_lib", + "//source/common/common:minimal_logger_lib", + "//source/common/grpc:codec_lib", + "//source/common/grpc:common_lib", + "//source/common/grpc:status_lib", + "//source/common/http:codec_client_lib", + "//source/common/http:headers_lib", + "//source/common/http:utility_lib", + "@abseil-cpp//absl/container:node_hash_map", + "@abseil-cpp//absl/status", + "@xds//xds/data/orca/v3:pkg_cc_proto", + "@xds//xds/service/orca/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "load_balancer_lib", srcs = ["load_balancer_impl.cc"], diff --git a/source/extensions/load_balancing_policies/common/orca_oob_manager.cc b/source/extensions/load_balancing_policies/common/orca_oob_manager.cc new file mode 100644 index 0000000000000..5f88d80889b75 --- /dev/null +++ b/source/extensions/load_balancing_policies/common/orca_oob_manager.cc @@ -0,0 +1,371 @@ +#include "source/extensions/load_balancing_policies/common/orca_oob_manager.h" + +#include "envoy/http/codes.h" +#include "envoy/upstream/upstream.h" + +#include "source/common/buffer/zero_copy_input_stream_impl.h" +#include "source/common/grpc/common.h" +#include "source/common/http/codec_client.h" +#include "source/common/http/headers.h" +#include "source/common/http/utility.h" +#include "source/common/protobuf/protobuf.h" + +#include "xds/service/orca/v3/orca.pb.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolicies { +namespace Common { + +namespace { +constexpr absl::string_view kOrcaOobStatsPrefix = "lb_orca_oob."; +constexpr uint64_t kOobBackoffBaseIntervalMs = 1000; +constexpr uint64_t kOobBackoffMaxIntervalMs = 30000; +constexpr absl::string_view kOrcaOobServiceFullName = "xds.service.orca.v3.OpenRcaService"; +constexpr absl::string_view kStreamCoreMetricsMethod = "StreamCoreMetrics"; +// Multiplier on reporting_period_ used to size the per-session inactivity watchdog. +// 3x gives the server two missed reports of slack before we treat the stream as +// stalled and reconnect via handleTransientFailure. +constexpr uint64_t kInactivityWatchdogMultiplier = 3; + +// Max allowed size of a single gRPC frame accepted on an OOB OrcaLoadReport stream. +constexpr uint32_t kMaxOrcaReportFrameBytes = 64 * 1024; +} // namespace + +OrcaOobManager::OrcaOobManager(std::chrono::milliseconds reporting_period, + const Upstream::PrioritySet& priority_set, + Event::Dispatcher& dispatcher, Random::RandomGenerator& random, + Stats::Scope& stats_scope, + OrcaLoadReportHandlerSharedPtr report_handler) + : dispatcher_(dispatcher), random_(random), reporting_period_(reporting_period), + priority_set_(priority_set), report_handler_(std::move(report_handler)), + oob_stats_(generateOrcaOobStats(stats_scope)) {} + +OrcaOobManager::~OrcaOobManager() { + for (auto& [host, session] : oob_sessions_) { + session->disarm(); + dispatcher_.deferredDelete(std::move(session)); + } + oob_sessions_.clear(); + oob_stats_.active_sessions_.set(0); +} + +absl::Status OrcaOobManager::initialize() { + for (const auto& host_set : priority_set_.hostSetsPerPriority()) { + onHostsAdded(host_set->hosts()); + } + member_update_cb_ = priority_set_.addMemberUpdateCb( + [this](const Upstream::HostVector& hosts_added, + const Upstream::HostVector& hosts_removed) -> absl::Status { + onHostsRemoved(hosts_removed); + onHostsAdded(hosts_added); + return absl::OkStatus(); + }); + return absl::OkStatus(); +} + +OrcaOobStats OrcaOobManager::generateOrcaOobStats(Stats::Scope& scope) { + return {ALL_ORCA_OOB_STATS(POOL_COUNTER_PREFIX(scope, kOrcaOobStatsPrefix), + POOL_GAUGE_PREFIX(scope, kOrcaOobStatsPrefix))}; +} + +void OrcaOobManager::onHostsAdded(const Upstream::HostVector& hosts) { + const size_t prior_size = oob_sessions_.size(); + for (const Upstream::HostSharedPtr& host : hosts) { + auto [it, inserted] = oob_sessions_.try_emplace(host, nullptr); + if (!inserted) { + continue; + } + const uint64_t period_ms = reporting_period_.count(); + const std::chrono::milliseconds initial_delay(period_ms == 0 ? 0 + : random_.random() % period_ms); + it->second = std::make_unique(*this, host, initial_delay); + } + if (oob_sessions_.size() != prior_size) { + oob_stats_.active_sessions_.set(oob_sessions_.size()); + } +} + +void OrcaOobManager::onHostsRemoved(const Upstream::HostVector& hosts) { + const size_t prior_size = oob_sessions_.size(); + for (const Upstream::HostSharedPtr& host : hosts) { + auto it = oob_sessions_.find(host); + if (it == oob_sessions_.end()) { + continue; + } + it->second->disarm(); + dispatcher_.deferredDelete(std::move(it->second)); + oob_sessions_.erase(it); + } + if (oob_sessions_.size() != prior_size) { + oob_stats_.active_sessions_.set(oob_sessions_.size()); + } +} + +void OrcaOobManager::onSessionTerminated(OobSession* session) { + auto it = oob_sessions_.find(session->host()); + ASSERT(it != oob_sessions_.end() && it->second.get() == session); + dispatcher_.deferredDelete(std::move(it->second)); + oob_sessions_.erase(it); + oob_stats_.active_sessions_.set(oob_sessions_.size()); +} + +OrcaOobManager::OobSession::OobSession(OrcaOobManager& parent, Upstream::HostConstSharedPtr host, + std::chrono::milliseconds initial_delay) + : parent_(parent), host_(std::move(host)), + backoff_(std::make_unique( + kOobBackoffBaseIntervalMs, kOobBackoffMaxIntervalMs, parent_.random_)) { + attempt_timer_ = parent_.dispatcher_.createTimer([this]() { connectAndStream(); }); + attempt_timer_->enableTimer(initial_delay); + inactivity_timer_ = + parent_.dispatcher_.createTimer([this]() { handleTransientFailure("inactivity timeout"); }); +} + +OrcaOobManager::OobSession::~OobSession() { ASSERT(codec_client_ == nullptr); } + +void OrcaOobManager::OobSession::disarm() { + attempt_timer_->disableTimer(); + inactivity_timer_->disableTimer(); + if (codec_client_ != nullptr) { + tearDownCodec(); + } +} + +void OrcaOobManager::OobSession::tearDownCodec() { + ASSERT(codec_client_ != nullptr); + expect_reset_ = true; + auto client = std::move(codec_client_); + client->close(Network::ConnectionCloseType::Abort); + parent_.dispatcher_.deferredDelete(std::move(client)); + request_encoder_ = nullptr; +} + +void OrcaOobManager::OobSession::decodeHeaders(Http::ResponseHeaderMapPtr&& headers, + bool end_stream) { + if (!Grpc::Common::isGrpcResponseHeaders(*headers, end_stream)) { + const uint64_t http_status = Http::Utility::getResponseStatusOrNullopt(*headers).value_or( + static_cast(Http::Code::InternalServerError)); + const auto http_grpc_status = Grpc::Utility::httpToGrpcStatus(http_status); + const auto status = end_stream + ? Grpc::Common::getGrpcStatus(*headers, /*allow_user_status=*/true) + .value_or(http_grpc_status) + : http_grpc_status; + onRpcComplete(status, "non-grpc response", end_stream); + return; + } + if (end_stream) { + const auto grpc_status = Grpc::Common::getGrpcStatus(*headers, /*allow_user_status=*/true); + onRpcComplete(grpc_status.value_or(Grpc::Status::WellKnownGrpcStatus::Unknown), "trailers-only", + /*end_stream=*/true); + return; + } +} + +void OrcaOobManager::OobSession::decodeData(Buffer::Instance& data, bool end_stream) { + std::vector frames; + if (!decoder_.decode(data, frames).ok()) { + onRpcComplete(Grpc::Status::WellKnownGrpcStatus::Internal, "gRPC frame decode error", + end_stream); + return; + } + for (auto& frame : frames) { + if (frame.flags_ != Grpc::GRPC_FH_DEFAULT) { + onRpcComplete(Grpc::Status::WellKnownGrpcStatus::Internal, + absl::StrCat("invalid frame flags=", static_cast(frame.flags_)), + end_stream); + return; + } + if (frame.length_ == 0) { + continue; + } + xds::data::orca::v3::OrcaLoadReport report; + Buffer::ZeroCopyInputStreamImpl stream(std::move(frame.data_)); + if (!report.ParseFromZeroCopyStream(&stream)) { + onRpcComplete(Grpc::Status::WellKnownGrpcStatus::Internal, "invalid OrcaLoadReport payload", + end_stream); + return; + } + onReport(report); + } + if (end_stream) { + onRpcComplete(Grpc::Status::WellKnownGrpcStatus::Unknown, + "server ended stream without trailers", /*end_stream=*/true); + return; + } + if (received_no_error_goaway_) { + handleTransientFailure("graceful goaway, reconnecting"); + } +} + +void OrcaOobManager::OobSession::decodeTrailers(Http::ResponseTrailerMapPtr&& trailers) { + const auto status = Grpc::Common::getGrpcStatus(*trailers, /*allow_user_status=*/true); + onRpcComplete(status.value_or(Grpc::Status::WellKnownGrpcStatus::Unknown), + Grpc::Common::getGrpcMessage(*trailers), + /*end_stream=*/true); +} + +void OrcaOobManager::OobSession::onResetStream(Http::StreamResetReason reason, absl::string_view) { + const bool expected_reset = expect_reset_; + resetState(); + if (expected_reset) { + return; + } + handleTransientFailure( + absl::StrCat("stream reset: ", Http::Utility::resetReasonToString(reason))); +} + +void OrcaOobManager::OobSession::onGoAway(Http::GoAwayErrorCode error_code) { + if (error_code == Http::GoAwayErrorCode::NoError) { + received_no_error_goaway_ = true; + return; + } + handleTransientFailure(absl::StrCat("goaway error code=", static_cast(error_code))); +} + +void OrcaOobManager::OobSession::connectAndStream() { + ASSERT(codec_client_ == nullptr); + resetState(); + + Upstream::Host::CreateConnectionData connection_data = host_->createOrcaReportingConnection( + parent_.dispatcher_, /*transport_socket_options=*/nullptr, /*metadata=*/nullptr); + codec_client_ = parent_.createCodecClient(connection_data); + if (codec_client_ == nullptr) { + parent_.oob_stats_.stream_failures_.inc(); + ENVOY_LOG(debug, "ORCA OOB transient failure for {}: createCodecClient returned null", + host_->address()->asString()); + attempt_timer_->enableTimer(std::chrono::milliseconds(backoff_->nextBackOffMs())); + return; + } + codec_client_->addConnectionCallbacks(connection_callback_impl_); + codec_client_->setCodecConnectionCallbacks(*this); + request_encoder_ = &codec_client_->newStream(*this); + request_encoder_->getStream().addCallbacks(*this); + + auto headers_message = + Grpc::Common::prepareHeaders(authority(), std::string(kOrcaOobServiceFullName), + std::string(kStreamCoreMetricsMethod), absl::nullopt); + headers_message->headers().setReferenceScheme( + host_->transportSocketFactory().implementsSecureTransport() + ? Http::Headers::get().SchemeValues.Https + : Http::Headers::get().SchemeValues.Http); + + const auto status = + request_encoder_->encodeHeaders(headers_message->headers(), /*end_stream=*/false); + if (!status.ok()) { + handleTransientFailure(absl::StrCat("encodeHeaders failed: ", status.message())); + return; + } + + xds::service::orca::v3::OrcaLoadReportRequest request; + if (parent_.reporting_period_.count() > 0) { + *request.mutable_report_interval() = + Protobuf::util::TimeUtil::MillisecondsToDuration(parent_.reporting_period_.count()); + } + + request_encoder_->encodeData(*Grpc::Common::serializeToGrpcFrame(request), /*end_stream=*/true); + + if (parent_.reporting_period_.count() > 0) { + inactivity_timer_->enableTimer(parent_.reporting_period_ * kInactivityWatchdogMultiplier); + } +} +void OrcaOobManager::OobSession::onConnectionEvent(Network::ConnectionEvent event) { + if (event == Network::ConnectionEvent::Connected || + event == Network::ConnectionEvent::ConnectedZeroRtt) { + return; + } + if (codec_client_ != nullptr) { + handleTransientFailure("connection closed"); + } +} + +void OrcaOobManager::OobSession::handleTransientFailure(absl::string_view reason) { + if (codec_client_ == nullptr) { + return; // codec already torn down by another path. + } + parent_.oob_stats_.stream_failures_.inc(); + ENVOY_CONN_LOG(debug, "ORCA OOB transient failure for {}: {}", *codec_client_, + host_->address()->asString(), reason); + inactivity_timer_->disableTimer(); + tearDownCodec(); + resetState(); + attempt_timer_->enableTimer(std::chrono::milliseconds(backoff_->nextBackOffMs())); +} + +void OrcaOobManager::OobSession::handleTerminal(Grpc::Status::GrpcStatus status, + absl::string_view reason) { + if (codec_client_ == nullptr) { + return; // codec already torn down by another path. + } + parent_.oob_stats_.stream_terminated_.inc(); + ENVOY_CONN_LOG(info, "ORCA OOB terminal for {}: status={} {}", *codec_client_, + host_->address()->asString(), static_cast(status), reason); + inactivity_timer_->disableTimer(); + tearDownCodec(); + attempt_timer_->disableTimer(); + parent_.onSessionTerminated(this); +} + +void OrcaOobManager::OobSession::onRpcComplete(Grpc::Status::GrpcStatus status, + absl::string_view message, bool end_stream) { + if (!end_stream && request_encoder_ != nullptr) { + expect_reset_ = true; + request_encoder_->getStream().resetStream(Http::StreamResetReason::LocalReset); + } + if (status == Grpc::Status::WellKnownGrpcStatus::Unimplemented) { + handleTerminal(status, message); + } else { + handleTransientFailure( + absl::StrCat("rpc complete status=", static_cast(status), " ", message)); + } +} + +void OrcaOobManager::OobSession::onReport(const xds::data::orca::v3::OrcaLoadReport& report) { + parent_.oob_stats_.reports_received_.inc(); + backoff_->reset(); + if (parent_.reporting_period_.count() > 0) { + inactivity_timer_->enableTimer(parent_.reporting_period_ * kInactivityWatchdogMultiplier); + } + auto data_opt = host_->typedLbPolicyData(); + if (!data_opt.has_value()) { + parent_.oob_stats_.report_errors_.inc(); + return; + } + const absl::Status status = + parent_.report_handler_->updateClientSideDataFromOrcaLoadReport(report, *data_opt); + if (!status.ok()) { + parent_.oob_stats_.report_errors_.inc(); + } +} + +void OrcaOobManager::OobSession::resetState() { + expect_reset_ = false; + request_encoder_ = nullptr; + decoder_ = Grpc::Decoder(); + decoder_.setMaxFrameLength(kMaxOrcaReportFrameBytes); + received_no_error_goaway_ = false; +} + +std::string OrcaOobManager::OobSession::authority() const { + if (!host_->hostname().empty()) { + return std::string(host_->hostname()); + } + // For IP addresses, use the full address string which includes brackets for IPv6 and the port. + if (host_->address()->ip() != nullptr) { + return host_->address()->asString(); + } + // Terminal fallback for non-IP addresses (e.g., UDS, Pipe) avoids using a + // socket path as :authority. + return host_->cluster().name(); +} + +Http::CodecClientPtr +ProdOrcaOobManager::createCodecClient(Upstream::Host::CreateConnectionData& data) { + return std::make_unique( + Http::CodecType::HTTP2, std::move(data.connection_), data.host_description_, dispatcher_, + random_, /*transport_socket_options=*/nullptr); +} + +} // namespace Common +} // namespace LoadBalancingPolicies +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/load_balancing_policies/common/orca_oob_manager.h b/source/extensions/load_balancing_policies/common/orca_oob_manager.h new file mode 100644 index 0000000000000..573d5796ab231 --- /dev/null +++ b/source/extensions/load_balancing_policies/common/orca_oob_manager.h @@ -0,0 +1,178 @@ +#pragma once + +#include +#include + +#include "envoy/common/random_generator.h" +#include "envoy/event/dispatcher.h" +#include "envoy/event/timer.h" +#include "envoy/grpc/status.h" +#include "envoy/http/codec.h" +#include "envoy/network/connection.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" +#include "envoy/upstream/upstream.h" + +#include "source/common/common/backoff_strategy.h" +#include "source/common/common/callback_impl.h" +#include "source/common/common/logger.h" +#include "source/common/grpc/codec.h" +#include "source/common/http/codec_client.h" +#include "source/common/http/response_decoder_impl_base.h" +#include "source/extensions/load_balancing_policies/common/orca_weight_manager.h" + +#include "absl/container/node_hash_map.h" +#include "absl/status/status.h" +#include "xds/data/orca/v3/orca_load_report.pb.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolicies { +namespace Common { + +#define ALL_ORCA_OOB_STATS(COUNTER, GAUGE) \ + COUNTER(reports_received) \ + COUNTER(report_errors) \ + COUNTER(stream_failures) \ + COUNTER(stream_terminated) \ + GAUGE(active_sessions, Accumulate) + +struct OrcaOobStats { + ALL_ORCA_OOB_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) +}; + +/** + * Cluster-level manager owning per-host ORCA OOB streams. Modeled on + * source/extensions/health_checkers/grpc/health_checker_impl.h: per-host nested OobSession holds + * a CodecClient and Http stream callbacks; the manager reacts to PrioritySet::addMemberUpdateCb + * to add/remove sessions. All activity runs on the supplied dispatcher's thread; the only + * cross-thread surface is OrcaHostLbPolicyData atomics (workers read; this manager writes via the + * shared OrcaLoadReportHandler). + */ +class OrcaOobManager : protected Logger::Loggable { +public: + OrcaOobManager(std::chrono::milliseconds reporting_period, + const Upstream::PrioritySet& priority_set, Event::Dispatcher& dispatcher, + Random::RandomGenerator& random, Stats::Scope& stats_scope, + OrcaLoadReportHandlerSharedPtr report_handler); + virtual ~OrcaOobManager(); + + // Iterate priority set, open a session per existing host, register member-update callback. + // Init order constraint: caller must invoke OrcaWeightManager::initialize() FIRST so the + // OrcaHostLbPolicyData attachment exists before sessions decode their first report. + absl::Status initialize(); + +protected: + // Test seam: subclass overrides to inject a CodecClientForTest. + virtual Http::CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) PURE; + + Event::Dispatcher& dispatcher_; + Random::RandomGenerator& random_; + +private: + class OobSession : public Event::DeferredDeletable, + public Http::ResponseDecoderImplBase, + public Http::StreamCallbacks, + public Http::ConnectionCallbacks { + public: + OobSession(OrcaOobManager& parent, Upstream::HostConstSharedPtr host, + std::chrono::milliseconds initial_delay); + ~OobSession() override; + + void disarm(); + + const Upstream::HostConstSharedPtr& host() const { return host_; } + + // Http::StreamDecoder + void decodeData(Buffer::Instance& data, bool end_stream) override; + void decodeMetadata(Http::MetadataMapPtr&&) override {} + + // Http::ResponseDecoder + void decode1xxHeaders(Http::ResponseHeaderMapPtr&&) override {} + void decodeHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_stream) override; + void decodeTrailers(Http::ResponseTrailerMapPtr&& trailers) override; + void dumpState(std::ostream&, int) const override {} + + // Http::StreamCallbacks + void onResetStream(Http::StreamResetReason reason, + absl::string_view transport_failure_reason) override; + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + + // Http::ConnectionCallbacks + void onGoAway(Http::GoAwayErrorCode error_code) override; + + private: + // Network::ConnectionCallbacks bridge. + class ConnectionCallbackImpl : public Network::ConnectionCallbacks { + public: + ConnectionCallbackImpl(OobSession& parent) : parent_(parent) {} + void onEvent(Network::ConnectionEvent event) override { parent_.onConnectionEvent(event); } + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + + private: + OobSession& parent_; + }; + + void connectAndStream(); + void onConnectionEvent(Network::ConnectionEvent event); + void handleTransientFailure(absl::string_view reason); + void handleTerminal(Grpc::Status::GrpcStatus status, absl::string_view reason); + // Order matters: close(Abort) raises LocalClose synchronously, which re-enters + // onConnectionEvent. Nulling codec_client_ first makes that re-entry a no-op. + void tearDownCodec(); + void onRpcComplete(Grpc::Status::GrpcStatus status, absl::string_view message, bool end_stream); + void onReport(const xds::data::orca::v3::OrcaLoadReport& report); + void resetState(); + std::string authority() const; + + OrcaOobManager& parent_; + const Upstream::HostConstSharedPtr host_; + ConnectionCallbackImpl connection_callback_impl_{*this}; + Event::TimerPtr attempt_timer_; + Event::TimerPtr inactivity_timer_; + Http::CodecClientPtr codec_client_; + Http::RequestEncoder* request_encoder_{nullptr}; + Grpc::Decoder decoder_; + BackOffStrategyPtr backoff_; + // We initiated a stream reset; suppress the resulting onResetStream callback. + bool expect_reset_{false}; + // Server sent GOAWAY(NoError); defer teardown until the next decoded message so any in-flight + // reports are still delivered. If the server is silent after GOAWAY, the inactivity_timer_ + // catches the stalled session. + bool received_no_error_goaway_{false}; + }; + + using OobSessionPtr = std::unique_ptr; + + void onHostsAdded(const Upstream::HostVector& hosts); + void onHostsRemoved(const Upstream::HostVector& hosts); + void onSessionTerminated(OobSession* session); + + static OrcaOobStats generateOrcaOobStats(Stats::Scope& scope); + + const std::chrono::milliseconds reporting_period_; + const Upstream::PrioritySet& priority_set_; + OrcaLoadReportHandlerSharedPtr report_handler_; + Envoy::Common::CallbackHandlePtr member_update_cb_; + // node_hash_map for pointer/reference stability across rehash. + absl::node_hash_map oob_sessions_; + OrcaOobStats oob_stats_; +}; + +/** + * Production OrcaOobManager that allocates a real HTTP/2 codec client. + */ +class ProdOrcaOobManager : public OrcaOobManager { +public: + using OrcaOobManager::OrcaOobManager; + +protected: + Http::CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) override; +}; + +} // namespace Common +} // namespace LoadBalancingPolicies +} // namespace Extensions +} // namespace Envoy diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 24a149cf5f954..3fe5ef5ef57ab 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -621,6 +621,7 @@ envoy_cc_test( "//test/mocks/upstream:health_checker_mocks", "//test/mocks/upstream:priority_set_mocks", "//test/mocks/upstream:thread_aware_load_balancer_mocks", + "//test/mocks/upstream:transport_socket_match_mocks", "//test/mocks/upstream:typed_load_balancer_factory_mocks", "//test/test_common:registry_lib", "//test/test_common:test_runtime_lib", diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index 556a836f4d0d0..65cb478110e3b 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -47,6 +47,7 @@ #include "test/mocks/upstream/health_checker.h" #include "test/mocks/upstream/priority_set.h" #include "test/mocks/upstream/thread_aware_load_balancer.h" +#include "test/mocks/upstream/transport_socket_match.h" #include "test/mocks/upstream/typed_load_balancer_factory.h" #include "test/test_common/environment.h" #include "test/test_common/registry.h" @@ -1967,6 +1968,65 @@ TEST_F(HostImplTest, CreateConnection) { EXPECT_EQ(host, connection->stream_info_.upstreamInfo()->upstreamHost()); } +TEST_F(HostImplTest, OrcaReportingAddressDefaultsToDataAddress) { + MockClusterMockPrioritySet cluster; + Network::Address::InstanceConstSharedPtr address = + *Network::Utility::resolveUrl("tcp://10.0.0.1:1234"); + auto host = std::shared_ptr(*HostImpl::create( + cluster.info_, "", address, nullptr, nullptr, 1, + std::make_shared(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN)); + EXPECT_EQ(host->orcaReportingAddress(), host->address()); +} + +TEST_F(HostImplTest, CreateOrcaReportingConnectionDialsDataAddress) { + MockClusterMockPrioritySet cluster; + Network::Address::InstanceConstSharedPtr address = + *Network::Utility::resolveUrl("tcp://10.0.0.1:1234"); + auto host = std::shared_ptr(*HostImpl::create( + cluster.info_, "", address, nullptr, nullptr, 1, + std::make_shared(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN)); + + testing::StrictMock dispatcher; + auto* connection = new testing::StrictMock(); + EXPECT_CALL(dispatcher, createClientConnection_(address, _, _, _)).WillOnce(Return(connection)); + EXPECT_CALL(*connection, setBufferLimits(0)); + EXPECT_CALL(*connection, connectionInfoSetter()).Times(testing::AnyNumber()); + EXPECT_CALL(*connection, streamInfo()).Times(testing::AnyNumber()); + Host::CreateConnectionData data = + host->createOrcaReportingConnection(dispatcher, /*transport_socket_options=*/nullptr, + /*metadata=*/nullptr); + EXPECT_EQ(data.host_description_.get(), host.get()); +} + +TEST_F(HostImplTest, CreateOrcaReportingConnectionWithMetadataResolvesTransportSocket) { + MockClusterMockPrioritySet cluster; + auto* matcher = new NiceMock(); + cluster.info_->transport_socket_matcher_.reset(matcher); + Network::Address::InstanceConstSharedPtr address = + *Network::Utility::resolveUrl("tcp://10.0.0.1:1234"); + envoy::config::core::v3::Metadata orca_metadata; + auto host = std::shared_ptr(*HostImpl::create( + cluster.info_, "", address, nullptr, nullptr, 1, + std::make_shared(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN)); + EXPECT_CALL(*matcher, resolve(&orca_metadata, _, _)) + .WillOnce(Return(TransportSocketMatcher::MatchData(*matcher->socket_factory_, matcher->stats_, + "orca-test"))); + testing::StrictMock dispatcher; + auto* connection = new testing::StrictMock(); + EXPECT_CALL(dispatcher, createClientConnection_(address, _, _, _)).WillOnce(Return(connection)); + EXPECT_CALL(*connection, setBufferLimits(0)); + EXPECT_CALL(*connection, connectionInfoSetter()).Times(testing::AnyNumber()); + EXPECT_CALL(*connection, streamInfo()).Times(testing::AnyNumber()); + host->createOrcaReportingConnection(dispatcher, /*transport_socket_options=*/nullptr, + &orca_metadata); +} + TEST_F(HostImplTest, CreateConnectionHappyEyeballs) { MockClusterMockPrioritySet cluster; envoy::config::core::v3::Metadata metadata; diff --git a/test/extensions/clusters/common/BUILD b/test/extensions/clusters/common/BUILD index 43db0369ad3f8..46a90e015b251 100644 --- a/test/extensions/clusters/common/BUILD +++ b/test/extensions/clusters/common/BUILD @@ -30,6 +30,8 @@ envoy_cc_test( "//source/common/router:string_accessor_lib", "//source/common/stream_info:filter_state_lib", "//source/extensions/clusters/common:logical_host_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/network:network_mocks", "//test/mocks/network:transport_socket_mocks", "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:host_mocks", diff --git a/test/extensions/clusters/common/logical_host_test.cc b/test/extensions/clusters/common/logical_host_test.cc index 5de65c2cb0ba3..86ff7c3089c77 100644 --- a/test/extensions/clusters/common/logical_host_test.cc +++ b/test/extensions/clusters/common/logical_host_test.cc @@ -1,8 +1,11 @@ #include "source/common/network/transport_socket_options_impl.h" +#include "source/common/network/utility.h" #include "source/common/router/string_accessor_impl.h" #include "source/common/stream_info/filter_state_impl.h" #include "source/extensions/clusters/common/logical_host.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/network/mocks.h" #include "test/mocks/network/transport_socket.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/host.h" @@ -12,9 +15,11 @@ #include "gtest/gtest.h" using testing::_; +using testing::AnyNumber; using testing::NiceMock; using testing::Return; using testing::ReturnRef; +using testing::StrictMock; namespace Envoy { namespace Extensions { @@ -34,6 +39,7 @@ TEST_F(RealHostDescriptionTest, UnitTest) { description_.metadata(); description_.priority(); EXPECT_EQ(nullptr, description_.healthCheckAddress()); + EXPECT_EQ(nullptr, description_.orcaReportingAddress()); // Pass through functions. EXPECT_CALL(*mock_host_, transportSocketFactory()); @@ -164,6 +170,59 @@ TEST_F(LogicalHostTransportSocketResolutionTest, OverrideTransportSocketOptionsT EXPECT_TRUE(needsPerConnectionResolution(effective_options)); } +// Test fixture for LogicalHost::createOrcaReportingConnection override. This validates that the +// override snapshots address state under the host's lock and routes through the transport socket +// matcher when caller-supplied metadata is non-null. +class LogicalHostOrcaReportingConnectionTest : public testing::Test { +protected: + void SetUp() override { + cluster_info_ = std::make_shared>(); + address_ = *Network::Utility::resolveUrl("tcp://10.0.0.1:1234"); + auto host_or_error = Upstream::LogicalHost::create( + cluster_info_, /*hostname=*/"", address_, /*address_list=*/{}, + /*locality_lb_endpoint=*/envoy::config::endpoint::v3::LocalityLbEndpoints(), + /*lb_endpoint=*/envoy::config::endpoint::v3::LbEndpoint(), + /*override_transport_socket_options=*/nullptr); + ASSERT_TRUE(host_or_error.ok()); + host_ = std::shared_ptr(std::move(*host_or_error)); + } + + std::shared_ptr> cluster_info_; + Network::Address::InstanceConstSharedPtr address_; + std::shared_ptr host_; +}; + +TEST_F(LogicalHostOrcaReportingConnectionTest, DialsHostDataAddress) { + Event::MockDispatcher dispatcher; + StrictMock* connection = + new StrictMock(); + EXPECT_CALL(dispatcher, createClientConnection_(address_, _, _, _)).WillOnce(Return(connection)); + EXPECT_CALL(*connection, setBufferLimits(_)).Times(AnyNumber()); + EXPECT_CALL(*connection, connectionInfoSetter()).Times(AnyNumber()); + EXPECT_CALL(*connection, streamInfo()).Times(AnyNumber()); + Upstream::Host::CreateConnectionData data = + host_->createOrcaReportingConnection(dispatcher, nullptr, nullptr); + EXPECT_EQ(data.host_description_->address(), address_); +} + +TEST_F(LogicalHostOrcaReportingConnectionTest, MetadataRoutesThroughMatcher) { + auto* matcher = dynamic_cast( + cluster_info_->transport_socket_matcher_.get()); + ASSERT_NE(matcher, nullptr); + envoy::config::core::v3::Metadata md; + EXPECT_CALL(*matcher, resolve(&md, _, _)) + .WillOnce(Return(Upstream::TransportSocketMatcher::MatchData(*matcher->socket_factory_, + matcher->stats_, "orca-md"))); + Event::MockDispatcher dispatcher; + StrictMock* connection = + new StrictMock(); + EXPECT_CALL(dispatcher, createClientConnection_(address_, _, _, _)).WillOnce(Return(connection)); + EXPECT_CALL(*connection, setBufferLimits(_)).Times(AnyNumber()); + EXPECT_CALL(*connection, connectionInfoSetter()).Times(AnyNumber()); + EXPECT_CALL(*connection, streamInfo()).Times(AnyNumber()); + host_->createOrcaReportingConnection(dispatcher, nullptr, &md); +} + } // namespace Clusters } // namespace Extensions } // namespace Envoy diff --git a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD index eb2daa60ff5a9..e2bec38fb200a 100644 --- a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD +++ b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD @@ -48,6 +48,7 @@ envoy_extension_cc_test( extension_names = ["envoy.load_balancing_policies.client_side_weighted_round_robin"], rbe_pool = "6gig", deps = [ + "//source/common/grpc:common_lib", "//source/common/protobuf", "//source/extensions/load_balancing_policies/client_side_weighted_round_robin:config", "//test/integration:http_integration_lib", diff --git a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb_test.cc b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb_test.cc index e37c4211b7244..490c51b2a6ea4 100644 --- a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb_test.cc +++ b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb_test.cc @@ -685,6 +685,23 @@ TEST(ClientSideWeightedRoundRobinLoadBalancerTest, 1428); } +TEST(ClientSideWeightedRoundRobinConfigTest, OobConfigDefaultsAndOverridePropagate) { + envoy::extensions::load_balancing_policies::client_side_weighted_round_robin::v3:: + ClientSideWeightedRoundRobin proto; + NiceMock dispatcher; + NiceMock tls; + + ClientSideWeightedRoundRobinLbConfig defaults(proto, dispatcher, tls); + EXPECT_FALSE(defaults.enable_oob_load_report); + EXPECT_EQ(defaults.oob_reporting_period, std::chrono::seconds(10)); + + proto.mutable_enable_oob_load_report()->set_value(true); + proto.mutable_oob_reporting_period()->set_seconds(5); + ClientSideWeightedRoundRobinLbConfig override(proto, dispatcher, tls); + EXPECT_TRUE(override.enable_oob_load_report); + EXPECT_EQ(override.oob_reporting_period, std::chrono::seconds(5)); +} + } // namespace } // namespace Upstream } // namespace Envoy diff --git a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/integration_test.cc b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/integration_test.cc index 5dabd2c476baf..76f54a35f5cf9 100644 --- a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/integration_test.cc +++ b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/integration_test.cc @@ -4,6 +4,7 @@ #include "envoy/config/endpoint/v3/endpoint_components.pb.h" #include "source/common/common/base64.h" +#include "source/common/grpc/common.h" #include "source/common/http/utility.h" #include "source/common/protobuf/protobuf.h" #include "source/extensions/load_balancing_policies/round_robin/config.h" @@ -12,6 +13,7 @@ #include "absl/strings/numbers.h" #include "gtest/gtest.h" +#include "xds/data/orca/v3/orca_load_report.pb.h" using testing::Eq; using testing::Ge; @@ -191,6 +193,133 @@ TEST_P(ClientSideWeightedRoundRobinIntegrationTest, NormalLoadBalancing) { runNormalLoadBalancing(); } +// Minimal happy-path integration test for ORCA out-of-band (OOB) reporting. +// Verifies that when `enable_oob_load_report=true` is set on a +// `client_side_weighted_round_robin` cluster, +// Envoy opens one OOB gRPC stream per upstream host targeting +// /xds.service.orca.v3.OpenRcaService/StreamCoreMetrics, and that +// server-pushed OrcaLoadReports are observed via the cluster-scoped +// `lb_orca_oob.*` stats. +class ClientSideWeightedRoundRobinOobIntegrationTest + : public testing::TestWithParam, + public HttpIntegrationTest { +public: + ClientSideWeightedRoundRobinOobIntegrationTest() + : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) { + // OOB reports use gRPC, so upstream connections must speak HTTP/2. + setUpstreamProtocol(Http::CodecType::HTTP2); + setUpstreamCount(2); + } + + void TearDown() override { cleanupOobStreams(); } + + void initializeConfig() { + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* cluster_0 = bootstrap.mutable_static_resources()->mutable_clusters()->Mutable(0); + ASSERT(cluster_0->name() == "cluster_0"); + auto* endpoint = cluster_0->mutable_load_assignment()->mutable_endpoints()->Mutable(0); + + constexpr absl::string_view endpoints_yaml = R"EOF( + lb_endpoints: + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + )EOF"; + const std::string local_address = Network::Test::getLoopbackAddressString(GetParam()); + TestUtility::loadFromYaml(fmt::format(endpoints_yaml, local_address, local_address), + *endpoint); + + auto* policy = cluster_0->mutable_load_balancing_policy(); + const std::string policy_yaml = R"EOF( + policies: + - typed_extension_config: + name: envoy.load_balancing_policies.client_side_weighted_round_robin + typed_config: + "@type": type.googleapis.com/envoy.extensions.load_balancing_policies.client_side_weighted_round_robin.v3.ClientSideWeightedRoundRobin + enable_oob_load_report: true + oob_reporting_period: + seconds: 1 + blackout_period: + seconds: 1 + weight_expiration_period: + seconds: 180 + weight_update_period: + seconds: 1 + )EOF"; + TestUtility::loadFromYaml(policy_yaml, *policy); + }); + HttpIntegrationTest::initialize(); + } + + // Accept one OOB stream per upstream and reply with one server-streamed + // OrcaLoadReport per stream. The streams remain open (no trailers). + void respondToOobStreams(double host0_qps, double host1_qps) { + for (size_t i = 0; i < 2; ++i) { + FakeHttpConnectionPtr conn; + ASSERT_TRUE(fake_upstreams_[i]->waitForHttpConnection(*dispatcher_, conn)); + + FakeStreamPtr stream; + ASSERT_TRUE(conn->waitForNewStream(*dispatcher_, stream)); + // Envoy ends the request half-stream after sending the single + // OrcaLoadReportRequest message. + ASSERT_TRUE(stream->waitForEndStream(*dispatcher_)); + + // Send gRPC response headers; do NOT send trailers (server-streaming). + Http::TestResponseHeaderMapImpl resp_headers{{":status", "200"}, + {"content-type", "application/grpc"}}; + stream->encodeHeaders(resp_headers, false); + + xds::data::orca::v3::OrcaLoadReport report; + report.set_application_utilization(0.5); + report.set_rps_fractional(i == 0 ? host0_qps : host1_qps); + auto frame = Grpc::Common::serializeToGrpcFrame(report); + stream->encodeData(*frame, false); + + // Keep the connection and stream alive for the lifetime of the test so + // Envoy continues to see an active OOB session. + stream_holder_.push_back(std::move(stream)); + conn_holder_.push_back(std::move(conn)); + } + } + + void cleanupOobStreams() { + for (auto& conn : conn_holder_) { + if (conn != nullptr) { + AssertionResult result = conn->close(); + RELEASE_ASSERT(result, result.message()); + result = conn->waitForDisconnect(); + RELEASE_ASSERT(result, result.message()); + } + } + stream_holder_.clear(); + conn_holder_.clear(); + } + + std::vector conn_holder_; + std::vector stream_holder_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, ClientSideWeightedRoundRobinOobIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); + +TEST_P(ClientSideWeightedRoundRobinOobIntegrationTest, OobReportsApplyWeights) { + initializeConfig(); + // Envoy should open one OOB stream per upstream host. Drive each stream + // through to a single OrcaLoadReport. + respondToOobStreams(/*host0_qps=*/100.0, /*host1_qps=*/1000.0); + + // Verify the OOB plumbing is alive and reporting. + test_server_->waitForCounter("cluster.cluster_0.lb_orca_oob.reports_received", Ge(2)); + test_server_->waitForGauge("cluster.cluster_0.lb_orca_oob.active_sessions", Eq(2)); +} + // Tests to verify the behavior of load balancing policy when cluster is added, // removed, and added again. class ClientSideWeightedRoundRobinXdsIntegrationTest diff --git a/test/extensions/load_balancing_policies/common/BUILD b/test/extensions/load_balancing_policies/common/BUILD index a31541bb6c863..670acf030866f 100644 --- a/test/extensions/load_balancing_policies/common/BUILD +++ b/test/extensions/load_balancing_policies/common/BUILD @@ -122,6 +122,30 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "orca_oob_manager_test", + srcs = ["orca_oob_manager_test.cc"], + deps = [ + "//source/common/buffer:buffer_lib", + "//source/common/grpc:common_lib", + "//source/common/network:utility_lib", + "//source/extensions/load_balancing_policies/common:orca_oob_manager_lib", + "//source/extensions/load_balancing_policies/common:orca_weight_manager_lib", + "//test/common/http:common_lib", + "//test/common/stats:stat_test_utility_lib", + "//test/mocks:common_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/http:http_mocks", + "//test/mocks/network:network_mocks", + "//test/mocks/upstream:host_mocks", + "//test/mocks/upstream:priority_set_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:status_utility_lib", + "//test/test_common:utility_lib", + "@xds//xds/data/orca/v3:pkg_cc_proto", + ], +) + envoy_cc_test_library( name = "load_balancer_base_test_lib", hdrs = ["load_balancer_impl_base_test.h"], diff --git a/test/extensions/load_balancing_policies/common/orca_oob_manager_test.cc b/test/extensions/load_balancing_policies/common/orca_oob_manager_test.cc new file mode 100644 index 0000000000000..24b7f07555197 --- /dev/null +++ b/test/extensions/load_balancing_policies/common/orca_oob_manager_test.cc @@ -0,0 +1,738 @@ +#include +#include +#include + +#include "envoy/upstream/upstream.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/grpc/common.h" +#include "source/common/network/utility.h" +#include "source/extensions/load_balancing_policies/common/orca_oob_manager.h" +#include "source/extensions/load_balancing_policies/common/orca_weight_manager.h" + +#include "test/common/http/common.h" +#include "test/common/stats/stat_test_utility.h" +#include "test/mocks/common.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/upstream/host.h" +#include "test/mocks/upstream/priority_set.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/status_utility.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xds/data/orca/v3/orca_load_report.pb.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolicies { +namespace Common { +namespace { + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::NiceMock; +using ::testing::Return; + +// MOCK_METHOD returns a raw Http::CodecClient*; createCodecClient wraps in unique_ptr to +// transfer ownership to OobSession. +class TestOrcaOobManager : public OrcaOobManager { +public: + using OrcaOobManager::OrcaOobManager; + Http::CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) override { + return Http::CodecClientPtr(createCodecClient_(data)); + } + MOCK_METHOD(Http::CodecClient*, createCodecClient_, + (Upstream::Host::CreateConnectionData & data)); +}; + +class OrcaOobManagerLifecycleTest : public testing::Test, public Event::TestUsingSimulatedTime { +protected: + void SetUp() override { + OrcaWeightManagerConfig weight_config{}; + weight_config.weight_update_period = std::chrono::seconds(1); + weight_config.weight_expiration_period = std::chrono::seconds(180); + weight_config.blackout_period = std::chrono::seconds(10); + report_handler_ = std::make_shared(weight_config, simTime()); + } + + std::unique_ptr makeManager() { + return std::make_unique(std::chrono::seconds(10), priority_set_, + dispatcher_, random_, *stats_store_.rootScope(), + report_handler_); + } + + uint64_t activeOobSessions() { + return stats_store_.gauge("lb_orca_oob.active_sessions", Stats::Gauge::ImportMode::Accumulate) + .value(); + } + + uint64_t oobCounter(absl::string_view name) { + return stats_store_.counter(absl::StrCat("lb_orca_oob.", name)).value(); + } + + Upstream::HostSharedPtr makeHost() { return std::make_shared>(); } + + // OobSession ctor calls createTimer twice (attempt, then inactivity); MockTimer EXPECT_CALLs + // match LIFO, so we push the inactivity mock first and return the attempt mock. + NiceMock* installAttemptTimer() { + new NiceMock(&dispatcher_); // inactivity (consumed second) + return new NiceMock(&dispatcher_); // attempt (consumed first) + } + + NiceMock priority_set_; + NiceMock dispatcher_; + NiceMock random_; + Stats::TestUtil::TestStore stats_store_; + OrcaLoadReportHandlerSharedPtr report_handler_; +}; + +TEST_F(OrcaOobManagerLifecycleTest, HostAddedSchedulesSession) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + EXPECT_EQ(activeOobSessions(), 1); + + // Re-adding the same host pointer hits try_emplace's already-present branch. + priority_set_.runUpdateCallbacks(0, {host}, {}); + EXPECT_EQ(activeOobSessions(), 1); + + EXPECT_CALL(*manager, createCodecClient_(_)).WillOnce(Return(nullptr)); + attempt_timer->invokeCallback(); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +TEST_F(OrcaOobManagerLifecycleTest, HostRemovedDisarmsAndDecrementsGauge) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + installAttemptTimer(); + auto host = makeHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + EXPECT_EQ(activeOobSessions(), 1); + + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + priority_set_.runUpdateCallbacks(0, {}, {host}); + EXPECT_EQ(activeOobSessions(), 0); + + // Removing a host the manager never tracked exercises the find()==end() branch. + priority_set_.runUpdateCallbacks(0, {}, {makeHost()}); + EXPECT_EQ(activeOobSessions(), 0); +} + +TEST_F(OrcaOobManagerLifecycleTest, DestructionDisarmsActiveSessions) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + installAttemptTimer(); + priority_set_.runUpdateCallbacks(0, {makeHost()}, {}); + EXPECT_EQ(activeOobSessions(), 1); + + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + manager.reset(); +} + +// Wire fixture: drives end-to-end ORCA OOB decode path through a real CodecClient +// (CodecClientForTest) layered over Network::MockClientConnection + +// Http::MockClientConnection. Models the gRPC health checker's wire-test pattern +// (test/common/upstream/health_checker_impl_test.cc). +class OrcaOobManagerWireTest : public OrcaOobManagerLifecycleTest { +protected: + struct OobAttempt { + NiceMock* network_connection{nullptr}; + NiceMock* codec{nullptr}; + // Owned: MockRequestEncoder (which owns its MockStream) outlives the OobSession's + // raw pointer to it because a self-initiated close in the test does not actually + // tear the request_encoder down. + std::unique_ptr> request_encoder; + Http::ResponseDecoder* response_decoder{nullptr}; + CodecClientForTest* codec_client{nullptr}; + }; + + // Build the per-attempt mocks. OobSession takes ownership of network_connection (via + // CodecClientForTest) and codec (via codec_->reset). request_encoder lifetime is owned by + // the fixture; MockClientConnection::newStream returns a ref into it. close() is stubbed + // to a no-op so wire tests can assert stream_failures from explicit raise* hooks; see + // SyncLocalCloseDuringTearDownDoesNotDoubleCount for the synchronous-close variant. + std::unique_ptr makeAttempt() { + auto attempt = std::make_unique(); + attempt->network_connection = new NiceMock(); + attempt->codec = new NiceMock(); + attempt->request_encoder = std::make_unique>(); + EXPECT_CALL(*attempt->codec, newStream(_)) + .WillOnce(testing::DoAll(SaveArgAddress(&attempt->response_decoder), + testing::ReturnRef(*attempt->request_encoder))); + EXPECT_CALL(*attempt->request_encoder, encodeHeaders(_, false)) + .WillOnce(testing::Return(absl::OkStatus())); + EXPECT_CALL(*attempt->request_encoder, encodeData(_, true)); + ON_CALL(*attempt->network_connection, close(_, _)).WillByDefault(testing::Return()); + ON_CALL(*attempt->network_connection, close(_)).WillByDefault(testing::Return()); + return attempt; + } + + // Wire host->createOrcaReportingConnection (which delegates to MockHostLight:: + // createConnection_) to hand back the network_connection raw ptr we control. + void wireConnectionFor(Upstream::HostSharedPtr host, OobAttempt& attempt) { + using Upstream::MockHostLight; + host_description_for_codec_ = std::make_shared>(); + EXPECT_CALL(static_cast&>(*host), createConnection_(_, _)) + .WillOnce(testing::InvokeWithoutArgs([this, &attempt]() { + MockHostLight::MockCreateConnectionData data; + data.connection_ = attempt.network_connection; + data.host_description_ = host_description_for_codec_; + return data; + })); + } + + Http::CodecClient* attachCodecClient(OobAttempt& attempt, + Upstream::Host::CreateConnectionData& data) { + attempt.codec_client = + new CodecClientForTest(Http::CodecType::HTTP2, std::move(data.connection_), attempt.codec, + /*destroy_cb=*/nullptr, data.host_description_, dispatcher_); + return attempt.codec_client; + } + + // Capture-by-ref the EXPECT_CALL+Invoke pattern: when the OobSession fires the test seam + // createCodecClient_, hand back a CodecClientForTest layered over the supplied attempt. + void expectCreateCodecClient(TestOrcaOobManager& manager, OobAttempt& attempt) { + EXPECT_CALL(manager, createCodecClient_(_)) + .WillOnce(testing::Invoke([this, &attempt](Upstream::Host::CreateConnectionData& data) { + return attachCodecClient(attempt, data); + })); + } + + void respondHeadersOk(OobAttempt& attempt) { + Http::TestResponseHeaderMapImpl headers{{":status", "200"}, + {"content-type", "application/grpc"}}; + attempt.response_decoder->decodeHeaders( + std::make_unique(headers), false); + } + + void respondReport(OobAttempt& attempt, const xds::data::orca::v3::OrcaLoadReport& report) { + auto buffer = Grpc::Common::serializeToGrpcFrame(report); + attempt.response_decoder->decodeData(*buffer, false); + } + + void respondTrailers(OobAttempt& attempt, Grpc::Status::GrpcStatus status) { + auto trailers = std::make_unique(); + trailers->setGrpcStatus(static_cast(status)); + attempt.response_decoder->decodeTrailers(std::move(trailers)); + } + + // Returns a MockHost with address+hostname+canCreateConnection wired up enough for + // OobSession::connectAndStream to traverse without segfault. + Upstream::HostSharedPtr makeWiredHost() { + auto host = std::make_shared>(); + auto address = *Network::Utility::resolveUrl("tcp://10.0.0.1:80"); + addresses_.push_back(address); + ON_CALL(*host, address()).WillByDefault(testing::Return(address)); + ON_CALL(*host, hostname()).WillByDefault(testing::ReturnRef(empty_hostname_)); + return host; + } + + std::string empty_hostname_; + std::vector addresses_; + Upstream::HostDescriptionConstSharedPtr host_description_for_codec_; +}; + +TEST_F(OrcaOobManagerWireTest, ReportReceivedUpdatesHostWeight) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + // Attach OrcaHostLbPolicyData (in production, OrcaWeightManager does this; here we + // attach directly so the wire test does not depend on in-band manager presence). + host->addLbPolicyData(std::make_unique(report_handler_)); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + xds::data::orca::v3::OrcaLoadReport empty_report; + respondReport(*attempt, empty_report); + EXPECT_EQ(oobCounter("reports_received"), 0); + EXPECT_EQ(oobCounter("stream_failures"), 0); + + xds::data::orca::v3::OrcaLoadReport report; + report.set_application_utilization(0.5); + report.set_rps_fractional(1000); + respondReport(*attempt, report); + EXPECT_EQ(oobCounter("reports_received"), 1); + auto data_opt = host->typedLbPolicyData(); + ASSERT_TRUE(data_opt.has_value()); + // Proves report flowed through; exact formula tested in OrcaWeightManager tests. + EXPECT_GT(data_opt->weight_.load(), 1u); + + xds::data::orca::v3::OrcaLoadReport invalid_report; + invalid_report.set_application_utilization(0.5); + respondReport(*attempt, invalid_report); + EXPECT_EQ(oobCounter("reports_received"), 2); + EXPECT_EQ(oobCounter("report_errors"), 1); + + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + manager.reset(); +} + +TEST_F(OrcaOobManagerWireTest, CodecNoopCallbacksAreSafe) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + attempt_timer->invokeCallback(); + + attempt->response_decoder->decodeMetadata(std::make_unique()); + attempt->response_decoder->decode1xxHeaders(std::make_unique()); + std::ostringstream dump; + attempt->response_decoder->dumpState(dump, 0); + + attempt->request_encoder->stream_.runHighWatermarkCallbacks(); + attempt->request_encoder->stream_.runLowWatermarkCallbacks(); + attempt->network_connection->runHighWatermarkCallbacks(); + attempt->network_connection->runLowWatermarkCallbacks(); + + EXPECT_EQ(oobCounter("stream_failures"), 0); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + manager.reset(); +} + +TEST_F(OrcaOobManagerWireTest, EndStreamDataWithoutTrailersIsTransientFailure) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + Buffer::OwnedImpl empty_data; + attempt->response_decoder->decodeData(empty_data, true); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +TEST_F(OrcaOobManagerWireTest, UnimplementedTrailerIsTerminal) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + respondTrailers(*attempt, Grpc::Status::WellKnownGrpcStatus::Unimplemented); + EXPECT_EQ(oobCounter("stream_terminated"), 1); + EXPECT_EQ(activeOobSessions(), 0); +} + +TEST_F(OrcaOobManagerWireTest, GoAwayNoErrorDefersUntilNextDecode) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + host->addLbPolicyData(std::make_unique(report_handler_)); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + attempt->codec_client->raiseGoAway(Http::GoAwayErrorCode::NoError); + EXPECT_EQ(oobCounter("stream_failures"), 0); + + // Subsequent report is delivered, then deferred GOAWAY tears the session down. + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + xds::data::orca::v3::OrcaLoadReport report; + report.set_application_utilization(0.5); + report.set_rps_fractional(1000); + respondReport(*attempt, report); + EXPECT_EQ(oobCounter("reports_received"), 1); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +TEST_F(OrcaOobManagerWireTest, GoAwayOtherIsImmediateTransient) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + attempt->codec_client->raiseGoAway(Http::GoAwayErrorCode::Other); + respondTrailers(*attempt, Grpc::Status::WellKnownGrpcStatus::Unimplemented); + EXPECT_EQ(oobCounter("stream_failures"), 1); + EXPECT_EQ(oobCounter("stream_terminated"), 0); +} + +TEST_F(OrcaOobManagerWireTest, ReportWithoutLbPolicyDataIncrementsReportErrors) { + // Host has no OrcaHostLbPolicyData attached (would be done by OrcaWeightManager + // in production; this test simulates the init-order race the architecture + // documents as v1-acceptable). onReport increments report_errors and bails. + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + xds::data::orca::v3::OrcaLoadReport report; + report.set_application_utilization(0.5); + report.set_rps_fractional(1000); + respondReport(*attempt, report); + EXPECT_EQ(oobCounter("report_errors"), 1); + EXPECT_EQ(oobCounter("reports_received"), 1); // counter still bumps before the data check + + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + manager.reset(); +} + +TEST_F(OrcaOobManagerWireTest, NonGrpcResponseTransientFailure) { + // Server returns HTTP 500 with non-grpc content-type (e.g., the request was routed + // to a non-gRPC handler). decodeHeaders' isGrpcResponseHeaders branch should + // route through onRpcComplete -> handleTransientFailure. + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + // Non-grpc 500 response, end_stream=true (server bailed cleanly). + Http::TestResponseHeaderMapImpl headers{{":status", "500"}, {"content-type", "text/plain"}}; + attempt->response_decoder->decodeHeaders( + std::make_unique(headers), true); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +// Regression test for the tearDownCodec invariant: production ConnectionImpl::close(Abort) +// raises LocalClose synchronously. OobSession::tearDownCodec must null codec_client_ +// BEFORE calling close() so the re-entry into onConnectionEvent short-circuits. Without +// the move-before-close ordering, this test would record stream_failures==2 (one from +// the GoAway path, one from the sync onConnectionEvent re-entry into handleTransientFailure). +TEST_F(OrcaOobManagerWireTest, NonGrpcResponseEndStreamFalseTransient) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + // Non-grpc 503 with end_stream=false drives onRpcComplete's resetStream branch. + Http::TestResponseHeaderMapImpl headers{{":status", "503"}, {"content-type", "text/plain"}}; + attempt->response_decoder->decodeHeaders( + std::make_unique(headers), false); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +TEST_F(OrcaOobManagerWireTest, TrailersOnlyResponseTreatedAsTerminal) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + // 200 OK + grpc-status + end_stream=true is the trailers-only frame; covers the + // end_stream branch in decodeHeaders that bypasses decodeTrailers entirely. + Http::TestResponseHeaderMapImpl headers{ + {":status", "200"}, + {"content-type", "application/grpc"}, + {"grpc-status", + absl::StrCat(static_cast(Grpc::Status::WellKnownGrpcStatus::Unimplemented))}}; + attempt->response_decoder->decodeHeaders( + std::make_unique(headers), true); + EXPECT_EQ(oobCounter("stream_terminated"), 1); + EXPECT_EQ(activeOobSessions(), 0); +} + +TEST_F(OrcaOobManagerWireTest, MalformedGrpcFrameTriggersTransientFailure) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + Buffer::OwnedImpl bad(std::string("\x02\x00\x00\x00\x00", 5)); + attempt->response_decoder->decodeData(bad, false); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +TEST_F(OrcaOobManagerWireTest, CompressedFrameRejectedAsTransient) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + // Frame decodes cleanly; OobSession rejects compressed frames. + Buffer::OwnedImpl bad(std::string("\x01\x00\x00\x00\x00", 5)); + attempt->response_decoder->decodeData(bad, false); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +TEST_F(OrcaOobManagerWireTest, InvalidProtoPayloadTriggersTransientFailure) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + // Valid frame envelope (flag=0, length=1) wrapping a single-byte payload that is an + // unterminated varint, so OrcaLoadReport::ParseFromZeroCopyStream returns false. + Buffer::OwnedImpl bad(std::string("\x00\x00\x00\x00\x01\x80", 6)); + attempt->response_decoder->decodeData(bad, false); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +TEST_F(OrcaOobManagerWireTest, EncodeHeadersFailureTriggersTransientFailure) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + // Custom attempt: encodeHeaders returns NOT_OK so encodeData is never called. + auto attempt = std::make_unique(); + attempt->network_connection = new NiceMock(); + attempt->codec = new NiceMock(); + attempt->request_encoder = std::make_unique>(); + EXPECT_CALL(*attempt->codec, newStream(_)) + .WillOnce(testing::DoAll(SaveArgAddress(&attempt->response_decoder), + testing::ReturnRef(*attempt->request_encoder))); + EXPECT_CALL(*attempt->request_encoder, encodeHeaders(_, false)) + .WillOnce(testing::Return(absl::InternalError("encode bust"))); + ON_CALL(*attempt->network_connection, close(_, _)).WillByDefault(testing::Return()); + ON_CALL(*attempt->network_connection, close(_)).WillByDefault(testing::Return()); + + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +TEST_F(OrcaOobManagerWireTest, RemoteCloseTriggersTransientFailure) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + // Run OobSession's connection callback before CodecClient's callback so onConnectionEvent sees + // codec_client_ live; CodecClient then resets the stream after OobSession has torn it down. + ASSERT_GE(attempt->network_connection->callbacks_.size(), 2); + auto* oob_callback = attempt->network_connection->callbacks_.back(); + attempt->network_connection->callbacks_.pop_back(); + attempt->network_connection->callbacks_.push_front(oob_callback); + attempt->network_connection->raiseEvent(Network::ConnectionEvent::RemoteClose); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +TEST_F(OrcaOobManagerWireTest, HostnameUsedAsAuthority) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = std::make_shared>(); + auto address = *Network::Utility::resolveUrl("tcp://10.0.0.1:80"); + addresses_.push_back(address); + ON_CALL(*host, address()).WillByDefault(testing::Return(address)); + std::string hostname = "myorca.example"; + ON_CALL(*host, hostname()).WillByDefault(testing::ReturnRef(hostname)); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + // Build the attempt manually so encodeHeaders captures the :authority header. + auto attempt = std::make_unique(); + attempt->network_connection = new NiceMock(); + attempt->codec = new NiceMock(); + attempt->request_encoder = std::make_unique>(); + EXPECT_CALL(*attempt->codec, newStream(_)) + .WillOnce(testing::DoAll(SaveArgAddress(&attempt->response_decoder), + testing::ReturnRef(*attempt->request_encoder))); + std::string captured_authority; + EXPECT_CALL(*attempt->request_encoder, encodeHeaders(_, false)) + .WillOnce(testing::Invoke([&](const Http::RequestHeaderMap& h, bool) -> absl::Status { + captured_authority = std::string(h.getHostValue()); + return absl::OkStatus(); + })); + EXPECT_CALL(*attempt->request_encoder, encodeData(_, true)); + ON_CALL(*attempt->network_connection, close(_, _)).WillByDefault(testing::Return()); + ON_CALL(*attempt->network_connection, close(_)).WillByDefault(testing::Return()); + + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + attempt_timer->invokeCallback(); + EXPECT_EQ(captured_authority, "myorca.example"); + + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + manager.reset(); +} + +TEST_F(OrcaOobManagerWireTest, PipeHostFallsBackToClusterName) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = std::make_shared>(); + auto address = *Network::Utility::resolveUrl("unix:///tmp/orca.sock"); + addresses_.push_back(address); + ON_CALL(*host, address()).WillByDefault(testing::Return(address)); + ON_CALL(*host, hostname()).WillByDefault(testing::ReturnRef(empty_hostname_)); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = std::make_unique(); + attempt->network_connection = new NiceMock(); + attempt->codec = new NiceMock(); + attempt->request_encoder = std::make_unique>(); + EXPECT_CALL(*attempt->codec, newStream(_)) + .WillOnce(testing::DoAll(SaveArgAddress(&attempt->response_decoder), + testing::ReturnRef(*attempt->request_encoder))); + std::string captured_authority; + EXPECT_CALL(*attempt->request_encoder, encodeHeaders(_, false)) + .WillOnce(testing::Invoke([&](const Http::RequestHeaderMap& h, bool) -> absl::Status { + captured_authority = std::string(h.getHostValue()); + return absl::OkStatus(); + })); + EXPECT_CALL(*attempt->request_encoder, encodeData(_, true)); + ON_CALL(*attempt->network_connection, close(_, _)).WillByDefault(testing::Return()); + ON_CALL(*attempt->network_connection, close(_)).WillByDefault(testing::Return()); + + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + attempt_timer->invokeCallback(); + // Pipe address has no ip(); authority falls through to the cluster name. + EXPECT_EQ(captured_authority, "fake_cluster"); + + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + manager.reset(); +} + +TEST_F(OrcaOobManagerWireTest, SyncLocalCloseDuringTearDownDoesNotDoubleCount) { + auto manager = makeManager(); + ASSERT_OK(manager->initialize()); + + auto* attempt_timer = installAttemptTimer(); + auto host = makeWiredHost(); + priority_set_.runUpdateCallbacks(0, {host}, {}); + + auto attempt = makeAttempt(); + // Override the fixture's no-op close() stubs so close() raises sync LocalClose, matching + // production ConnectionImpl::close(Abort) semantics. + auto* raw_conn = attempt->network_connection; + ON_CALL(*raw_conn, close(_, _)).WillByDefault(testing::InvokeWithoutArgs([raw_conn]() { + raw_conn->raiseEvent(Network::ConnectionEvent::LocalClose); + })); + ON_CALL(*raw_conn, close(_)).WillByDefault(testing::InvokeWithoutArgs([raw_conn]() { + raw_conn->raiseEvent(Network::ConnectionEvent::LocalClose); + })); + wireConnectionFor(host, *attempt); + expectCreateCodecClient(*manager, *attempt); + EXPECT_CALL(dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); + attempt_timer->invokeCallback(); + + respondHeadersOk(*attempt); + // GoAway(Other) -> handleTransientFailure -> tearDownCodec -> sync close -> sync + // onConnectionEvent(LocalClose) re-entry. With the fix, the re-entry sees a null + // codec_client_ and returns; stream_failures stays at 1. + attempt->codec_client->raiseGoAway(Http::GoAwayErrorCode::Other); + EXPECT_EQ(oobCounter("stream_failures"), 1); +} + +} // namespace +} // namespace Common +} // namespace LoadBalancingPolicies +} // namespace Extensions +} // namespace Envoy diff --git a/test/mocks/upstream/host.h b/test/mocks/upstream/host.h index aed10c5d7b4ec..891f340b559c9 100644 --- a/test/mocks/upstream/host.h +++ b/test/mocks/upstream/host.h @@ -85,6 +85,7 @@ class MockHostDescription : public HostDescription { MOCK_METHOD(Network::Address::InstanceConstSharedPtr, address, (), (const)); MOCK_METHOD(SharedConstAddressVector, addressListOrNull, (), (const)); MOCK_METHOD(Network::Address::InstanceConstSharedPtr, healthCheckAddress, (), (const)); + MOCK_METHOD(Network::Address::InstanceConstSharedPtr, orcaReportingAddress, (), (const)); MOCK_METHOD(bool, canary, (), (const)); MOCK_METHOD(void, canary, (bool new_canary)); MOCK_METHOD(MetadataConstSharedPtr, metadata, (), (const)); @@ -163,6 +164,14 @@ class MockHostLight : public Host { return {Network::ClientConnectionPtr{data.connection_}, data.host_description_}; } + CreateConnectionData + createOrcaReportingConnection(Event::Dispatcher& dispatcher, + Network::TransportSocketOptionsConstSharedPtr, + const envoy::config::core::v3::Metadata*) const override { + MockCreateConnectionData data = createConnection_(dispatcher, nullptr); + return {Network::ClientConnectionPtr{data.connection_}, data.host_description_}; + } + bool disableActiveHealthCheck() const override { return disable_active_health_check_; } void setDisableActiveHealthCheck(bool disable_active_health_check) override { disable_active_health_check_ = disable_active_health_check; @@ -171,6 +180,7 @@ class MockHostLight : public Host { MOCK_METHOD(Network::Address::InstanceConstSharedPtr, address, (), (const)); MOCK_METHOD(SharedConstAddressVector, addressListOrNull, (), (const)); MOCK_METHOD(Network::Address::InstanceConstSharedPtr, healthCheckAddress, (), (const)); + MOCK_METHOD(Network::Address::InstanceConstSharedPtr, orcaReportingAddress, (), (const)); MOCK_METHOD(bool, canary, (), (const)); MOCK_METHOD(void, canary, (bool new_canary)); MOCK_METHOD(MetadataConstSharedPtr, metadata, (), (const)); From 7ec03a5fd3fcfea15aa4a713f8951b9f34710fc9 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Tue, 19 May 2026 10:14:55 -0400 Subject: [PATCH 343/750] mcp_router: add lazy initialization option (#45129) Add a `lazy_initialization` config option to the MCP router filter. When enabled, the `initialize` response is returned immediately with gateway capabilities without contacting any backends. Each backend is then initialized on-demand when a request first routes to it. This avoids slow or misbehaving backends from blocking client initialization. Signed-off-by: Jonh Wendell Co-authored-by: Claude Opus 4.6 --- .../http/mcp_router/v3/mcp_router.proto | 7 + changelogs/current.yaml | 6 + .../http/http_filters/mcp_router_filter.rst | 13 + .../filters/http/mcp_router/filter_config.cc | 3 +- .../filters/http/mcp_router/filter_config.h | 7 + .../filters/http/mcp_router/mcp_router.cc | 754 ++++++++-- .../filters/http/mcp_router/mcp_router.h | 19 +- .../http/mcp_router/mcp_router_test.cc | 1329 +++++++++++++++++ 8 files changed, 1980 insertions(+), 158 deletions(-) diff --git a/api/envoy/extensions/filters/http/mcp_router/v3/mcp_router.proto b/api/envoy/extensions/filters/http/mcp_router/v3/mcp_router.proto index 1d32449931cd1..185a61e01e60f 100644 --- a/api/envoy/extensions/filters/http/mcp_router/v3/mcp_router.proto +++ b/api/envoy/extensions/filters/http/mcp_router/v3/mcp_router.proto @@ -125,4 +125,11 @@ message McpRouter { // If set, extracts a request "subject" and binds it into the MCP session. // If not set, sessions are created without identity binding. SessionIdentity session_identity = 2; + + // If true, backend initialization is deferred until the first request that targets each backend. + // The ``initialize`` response is returned immediately with gateway capabilities and an empty + // backend session map. Each backend is initialized on-demand when a request first routes to it. + // This avoids blocking the client ``initialize`` on slow or misbehaving backends. + // Default is false (eager initialization of all backends during ``initialize``). + bool lazy_initialization = 3; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 917dca38444c3..e83b1cd5b5fd6 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -175,6 +175,12 @@ new_features: that a dynamic-module cluster's load balancer can read filter state set by an upstream HTTP filter (or any other producer) when picking a host. The Rust SDK exposes these as ``ClusterLbContext::get_filter_state_bytes`` and ``ClusterLbContext::get_filter_state_typed``. +- area: mcp_router + change: | + Added :ref:`lazy_initialization ` + option to the MCP router filter. When enabled, the ``initialize`` response is returned immediately + without contacting backends. Each backend is initialized on-demand when a request first routes to it, + avoiding slow or misbehaving backends from blocking client initialization. - area: access_log change: | Supported the singleton stats scope in the :ref:`stats access logger `. diff --git a/docs/root/configuration/http/http_filters/mcp_router_filter.rst b/docs/root/configuration/http/http_filters/mcp_router_filter.rst index 9fe7648b4a5cb..42ee3b2792654 100644 --- a/docs/root/configuration/http/http_filters/mcp_router_filter.rst +++ b/docs/root/configuration/http/http_filters/mcp_router_filter.rst @@ -23,12 +23,25 @@ Example configuration: - name: envoy.filters.http.mcp_router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.mcp_router.v3.McpRouter + lazy_initialization: true servers: - name: backend1 mcp_cluster: cluster: backend1_cluster path: /mcp +.. _config_http_filters_mcp_router_lazy_initialization: + +Lazy initialization +~~~~~~~~~~~~~~~~~~~ + +By default, the MCP router eagerly initializes all backend servers during the client's ``initialize`` +request, blocking until every backend responds (or times out). When ``lazy_initialization`` is set to +``true``, the ``initialize`` response is returned immediately with gateway capabilities and an empty +backend session map. Each backend is then initialized on-demand when a request first routes to it. + +This is useful when some backends are slow or unreliable and should not block client initialization. + .. _config_http_filters_mcp_router_statistics: Statistics diff --git a/source/extensions/filters/http/mcp_router/filter_config.cc b/source/extensions/filters/http/mcp_router/filter_config.cc index 01512a1193b61..3f87eaa1afb8e 100644 --- a/source/extensions/filters/http/mcp_router/filter_config.cc +++ b/source/extensions/filters/http/mcp_router/filter_config.cc @@ -79,7 +79,8 @@ McpRouterConfigImpl::McpRouterConfigImpl( Server::Configuration::FactoryContext& context) : backends_(parseBackends(proto_config)), default_backend_name_(backends_.size() == 1 ? backends_[0].name : ""), - factory_context_(context), session_identity_(parseSessionIdentity(proto_config)), + factory_context_(context), lazy_initialization_(proto_config.lazy_initialization()), + session_identity_(parseSessionIdentity(proto_config)), metadata_namespace_(Filters::Common::Mcp::metadataNamespace()), stats_(generateStats(stats_prefix, scope)) {} diff --git a/source/extensions/filters/http/mcp_router/filter_config.h b/source/extensions/filters/http/mcp_router/filter_config.h index d80c91ec5ebdd..e9f17480625d1 100644 --- a/source/extensions/filters/http/mcp_router/filter_config.h +++ b/source/extensions/filters/http/mcp_router/filter_config.h @@ -91,6 +91,8 @@ class McpRouterConfig { virtual Server::Configuration::FactoryContext& factoryContext() const = 0; virtual const McpBackendConfig* findBackend(const std::string& name) const = 0; + virtual bool lazyInitialization() const = 0; + virtual bool hasSessionIdentity() const = 0; virtual const SubjectSource& subjectSource() const = 0; virtual ValidationMode validationMode() const = 0; @@ -117,6 +119,8 @@ class McpRouterConfigImpl : public McpRouterConfig { } const McpBackendConfig* findBackend(const std::string& name) const override; + bool lazyInitialization() const override { return lazy_initialization_; } + bool hasSessionIdentity() const override { return !absl::holds_alternative(session_identity_.subject_source); } @@ -133,6 +137,7 @@ class McpRouterConfigImpl : public McpRouterConfig { std::vector backends_; std::string default_backend_name_; Server::Configuration::FactoryContext& factory_context_; + bool lazy_initialization_; SessionIdentityConfig session_identity_; std::string metadata_namespace_; McpRouterStats stats_; @@ -156,6 +161,8 @@ class McpRouterClusterConfigImpl : public McpRouterConfig { } const McpBackendConfig* findBackend(const std::string& name) const override; + bool lazyInitialization() const override { return base_config_->lazyInitialization(); } + bool hasSessionIdentity() const override { return base_config_->hasSessionIdentity(); } const SubjectSource& subjectSource() const override { return base_config_->subjectSource(); } ValidationMode validationMode() const override { return base_config_->validationMode(); } diff --git a/source/extensions/filters/http/mcp_router/mcp_router.cc b/source/extensions/filters/http/mcp_router/mcp_router.cc index b6a6512908abf..4296db668efa4 100644 --- a/source/extensions/filters/http/mcp_router/mcp_router.cc +++ b/source/extensions/filters/http/mcp_router/mcp_router.cc @@ -243,6 +243,12 @@ Http::FilterDataStatus McpRouterFilter::decodeData(Buffer::Instance& data, bool initialized_ = true; + // If lazy init is in progress, buffer data — the completion callback will replay it. + if (lazy_init_pending_) { + lazy_init_request_body_.move(data); + return Http::FilterDataStatus::StopIterationNoBuffer; + } + // Perform body rewriting if needed (e.g., tool/prompt name or URI prefix stripping). // This is done once on the first data chunk after initialization. if (needs_body_rewrite_) { @@ -261,6 +267,12 @@ Http::FilterDataStatus McpRouterFilter::decodeData(Buffer::Instance& data, bool } } + // If lazy init is in progress, buffer data — the completion callback will replay it. + if (lazy_init_pending_) { + lazy_init_request_body_.move(data); + return Http::FilterDataStatus::StopIterationNoBuffer; + } + streamData(data, end_stream); return Http::FilterDataStatus::StopIterationNoBuffer; @@ -604,7 +616,8 @@ ssize_t McpRouterFilter::rewriteAtPosition(Buffer::Instance& buffer, ssize_t pos return size_delta; } -void McpRouterFilter::initializeFanout(AggregationCallback callback) { +void McpRouterFilter::initializeFanout(AggregationCallback callback, + const std::vector& target_backends) { config_->stats().rq_fanout_.inc(); if (config_->backends().empty()) { sendHttpError(500, "No backends configured"); @@ -617,7 +630,21 @@ void McpRouterFilter::initializeFanout(AggregationCallback callback) { return; } - size_t expected = config_->backends().size(); + // Build list of backends to fan out to. + absl::flat_hash_set target_set(target_backends.begin(), target_backends.end()); + std::vector backends_to_use; + for (const auto& backend : config_->backends()) { + if (target_set.empty() || target_set.contains(backend.name)) { + backends_to_use.push_back(&backend); + } + } + + size_t expected = backends_to_use.size(); + if (expected == 0) { + sendHttpError(500, "No matching backends for fanout"); + return; + } + pending_responses_ = std::make_shared>(); pending_responses_->reserve(expected); response_count_ = std::make_shared(0); @@ -626,18 +653,14 @@ void McpRouterFilter::initializeFanout(AggregationCallback callback) { std::vector mux_callbacks; mux_callbacks.reserve(expected); - for (const auto& backend : config_->backends()) { + for (const auto* backend : backends_to_use) { auto responses = pending_responses_; auto count = response_count_; auto expected_count = expected; auto agg_callback = aggregation_callback_; - // Pass request_id and aggregate_mode=true for fanout operations. - // This enables early completion when SSE backends return a valid JSON-RPC response, - // avoiding the need to wait for end_stream (which never comes for SSE). - // Pass parent weak_ptr to enable intermediate event (notification/server request) forwarding. auto stream_cb = std::make_shared( - backend.name, + backend->name, [responses, count, expected_count, agg_callback](BackendResponse resp) { responses->push_back(std::move(resp)); if (++(*count) >= expected_count && agg_callback) { @@ -646,19 +669,17 @@ void McpRouterFilter::initializeFanout(AggregationCallback callback) { }, request_id_, true /* aggregate_mode */, weak_from_this()); - // Create per-backend StreamOptions with the backend-specific timeout. Http::AsyncClient::StreamOptions backend_options; - backend_options.setTimeout(backend.timeout); + backend_options.setTimeout(backend->timeout); stream_callbacks_.push_back(stream_cb); mux_callbacks.push_back({ - .cluster_name = backend.cluster_name, + .cluster_name = backend->cluster_name, .callbacks = std::weak_ptr(stream_cb), .options = backend_options, }); } - // Default options (used as fallback if per-backend options are not set). Http::AsyncClient::StreamOptions default_options; auto multistream_or = muxdemux_->multicast(default_options, mux_callbacks); @@ -673,15 +694,13 @@ void McpRouterFilter::initializeFanout(AggregationCallback callback) { upstream_headers_.clear(); upstream_headers_.reserve(expected); - // Store headers in upstream_headers_ because AsyncStreamImpl::sendHeaders only - // stores a pointer to the headers. auto stream_it = multistream_->begin(); - for (const auto& backend : config_->backends()) { + for (const auto* backend : backends_to_use) { if (stream_it == multistream_->end()) { break; } - auto headers = createUpstreamHeaders(backend, backend_sessions_[backend.name]); + auto headers = createUpstreamHeaders(*backend, backend_sessions_[backend->name]); upstream_headers_.push_back(std::move(headers)); (*stream_it)->sendHeaders(*upstream_headers_.back(), false); ++stream_it; @@ -825,13 +844,29 @@ void McpRouterFilter::handleInitialize() { sendHttpError(403, "Unable to determine session identity"); return; } - // In DISABLED mode, proceed with anonymous session. ENVOY_LOG(debug, "Subject extraction failed, proceeding with anonymous session"); } else { subject = *auth_subject; } } + if (config_->lazyInitialization()) { + ENVOY_LOG(debug, "Lazy init: responding immediately without backend fanout"); + + session_subject_ = subject; + std::vector empty_responses; + std::string response_body = aggregateInitialize(empty_responses); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId(route_name_, subject, empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + encoded_session_id_ = encoded_session; + + sendJsonResponse(response_body, encoded_session); + return; + } + initializeFanout([weak_self = weak_from_this(), subject = std::move(subject)](std::vector responses) { auto self = weak_self.lock(); @@ -858,8 +893,6 @@ void McpRouterFilter::handleInitialize() { return; } - // Only build a composite session if at least one backend returned a session ID. - // If all backends are session-less, don't return a session ID to the client. std::string encoded_session; if (!sessions.empty()) { std::string composite = @@ -886,32 +919,70 @@ void McpRouterFilter::handleNotification(absl::string_view notification_name) { config_->stats().rq_direct_response_.inc(); ENVOY_LOG(debug, "{}: forwarding to {} backends", notification_name, config_->backends().size()); - // Forward notification to all backends and wait for responses. - // Notifications are fire-and-forget, so we respond with 202 Accepted once all backends respond. - initializeFanout([weak_self = weak_from_this()](std::vector) { - auto self = weak_self.lock(); - if (!self) { - ENVOY_LOG(debug, "notifications/initialized callback ignored: filter destroyed"); - return; - } - // All backends have responded (or failed), send 202 to client. - self->sendAccepted(); - }); + if (config_->lazyInitialization() && initialized_backends_.empty()) { + ENVOY_LOG(debug, "{}: no backends initialized yet (lazy init), responding 202", + notification_name); + sendAccepted(); + return; + } + + std::vector targets; + if (config_->lazyInitialization()) { + targets.assign(initialized_backends_.begin(), initialized_backends_.end()); + } + + initializeFanout( + [weak_self = weak_from_this()](std::vector) { + auto self = weak_self.lock(); + if (!self) { + ENVOY_LOG(debug, "notification callback ignored: filter destroyed"); + return; + } + self->sendAccepted(); + }, + targets); } void McpRouterFilter::handleToolsList() { ENVOY_LOG(debug, "tools/list: setting up fanout to {} backends", config_->backends().size()); - initializeFanout([weak_self = weak_from_this()](std::vector responses) { - auto self = weak_self.lock(); - if (!self) { - ENVOY_LOG(debug, "tools/list callback ignored: filter destroyed"); - return; + auto start_tools_list = [this]() { + initializeFanout([weak_self = weak_from_this()](std::vector responses) { + auto self = weak_self.lock(); + if (!self) { + ENVOY_LOG(debug, "tools/list callback ignored: filter destroyed"); + return; + } + std::string response_body = self->aggregateToolsList(responses); + ENVOY_LOG(debug, "tools/list: response body: {}", response_body); + self->sendJsonResponse(response_body, self->encoded_session_id_); + }); + + if (lazy_init_request_body_.length() > 0) { + streamData(lazy_init_request_body_, true); } - std::string response_body = self->aggregateToolsList(responses); - ENVOY_LOG(debug, "tools/list: response body: {}", response_body); - self->sendJsonResponse(response_body, self->encoded_session_id_); - }); + }; + + if (config_->lazyInitialization() && initialized_backends_.size() < config_->backends().size()) { + lazy_init_pending_ = true; + lazyInitFanout([weak_self = weak_from_this(), + start_tools_list = std::move(start_tools_list)](bool success) { + auto self = weak_self.lock(); + if (!self) { + return; + } + self->lazy_init_pending_ = false; + if (!success) { + self->config_->stats().rq_fanout_failure_.inc(); + self->sendHttpError(500, "Failed to initialize backends"); + return; + } + start_tools_list(); + }); + return; + } + + start_tools_list(); } void McpRouterFilter::handleToolsCall() { @@ -930,52 +1001,108 @@ void McpRouterFilter::handleToolsCall() { return; } - // Store the unprefixed tool name for body rewriting. unprefixed_tool_name_ = actual_tool; needs_body_rewrite_ = (tool_name_ != unprefixed_tool_name_); ENVOY_LOG(debug, "tools/call: backend='{}', tool='{}' -> '{}', needs_rewrite={}", backend_name, tool_name_, actual_tool, needs_body_rewrite_); - // Use streaming mode for SSE pass-through, with fallback for JSON responses. - initializeSingleBackend( - *backend, - [weak_self = weak_from_this()](BackendResponse resp) { - auto self = weak_self.lock(); - if (!self) { - return; - } - // This callback is invoked for non-SSE responses (JSON) or errors. - // SSE responses are streamed directly by the parent filter. - if (resp.success) { - if (resp.isSse()) { - // Should not reach here for successful SSE in streaming mode. - ENVOY_LOG(warn, "tools/call: SSE response reached non-streaming path"); - self->sendHttpError(500, "Internal error: streaming failed for SSE response"); + auto start_tools_call = [this, backend]() { + initializeSingleBackend( + *backend, + [weak_self = weak_from_this()](BackendResponse resp) { + auto self = weak_self.lock(); + if (!self) { + return; + } + if (resp.success) { + if (resp.isSse()) { + ENVOY_LOG(warn, "tools/call: SSE response reached non-streaming path"); + self->sendHttpError(500, "Internal error: streaming failed for SSE response"); + } else { + self->sendJsonResponse(resp.body, self->encoded_session_id_); + } } else { - self->sendJsonResponse(resp.body, self->encoded_session_id_); + self->config_->stats().rq_backend_failure_.inc(); + self->sendHttpError(500, resp.error.empty() ? "Backend request failed" : resp.error); } - } else { - self->config_->stats().rq_backend_failure_.inc(); - self->sendHttpError(500, resp.error.empty() ? "Backend request failed" : resp.error); - } - }, - true /* streaming_enabled */); + }, + true /* streaming_enabled */); + + // Replay buffered data if we came from lazy init. + if (lazy_init_request_body_.length() > 0) { + if (needs_body_rewrite_) { + config_->stats().rq_body_rewrite_.inc(); + rewriteToolCallBody(lazy_init_request_body_); + needs_body_rewrite_ = false; + } + streamData(lazy_init_request_body_, true); + } + }; + + if (config_->lazyInitialization() && !initialized_backends_.contains(backend_name)) { + lazy_init_pending_ = true; + lazyInitSingleBackend(*backend, [weak_self = weak_from_this(), + start_tools_call = std::move(start_tools_call), + backend_name](bool success) { + auto self = weak_self.lock(); + if (!self) { + return; + } + self->lazy_init_pending_ = false; + if (!success) { + self->config_->stats().rq_backend_failure_.inc(); + self->sendHttpError(500, fmt::format("Failed to initialize backend '{}'", backend_name)); + return; + } + start_tools_call(); + }); + return; + } + + start_tools_call(); } void McpRouterFilter::handleResourcesList() { ENVOY_LOG(debug, "resources/list: setting up fanout to {} backends", config_->backends().size()); - initializeFanout([weak_self = weak_from_this()](std::vector responses) { - auto self = weak_self.lock(); - if (!self) { - ENVOY_LOG(debug, "resources/list callback ignored: filter destroyed"); - return; + auto start_resources_list = [this]() { + initializeFanout([weak_self = weak_from_this()](std::vector responses) { + auto self = weak_self.lock(); + if (!self) { + ENVOY_LOG(debug, "resources/list callback ignored: filter destroyed"); + return; + } + std::string response_body = self->aggregateResourcesList(responses); + ENVOY_LOG(debug, "resources/list: response body: {}", response_body); + self->sendJsonResponse(response_body, self->encoded_session_id_); + }); + + if (lazy_init_request_body_.length() > 0) { + streamData(lazy_init_request_body_, true); } - std::string response_body = self->aggregateResourcesList(responses); - ENVOY_LOG(debug, "resources/list: response body: {}", response_body); - self->sendJsonResponse(response_body, self->encoded_session_id_); - }); + }; + + if (config_->lazyInitialization() && initialized_backends_.size() < config_->backends().size()) { + lazy_init_pending_ = true; + lazyInitFanout([weak_self = weak_from_this(), + start_resources_list = std::move(start_resources_list)](bool success) { + auto self = weak_self.lock(); + if (!self) { + return; + } + self->lazy_init_pending_ = false; + if (!success) { + self->config_->stats().rq_fanout_failure_.inc(); + self->sendHttpError(500, "Failed to initialize backends"); + return; + } + start_resources_list(); + }); + return; + } + + start_resources_list(); } void McpRouterFilter::handleSingleBackendResourceMethod(absl::string_view method_name) { @@ -1001,18 +1128,51 @@ void McpRouterFilter::handleSingleBackendResourceMethod(absl::string_view method ENVOY_LOG(debug, "{}: backend='{}', uri='{}' -> '{}', needs_rewrite={}", method_name, backend_name, resource_uri_, actual_uri, needs_body_rewrite_); - initializeSingleBackend(*backend, [weak_self = weak_from_this()](BackendResponse resp) { - auto self = weak_self.lock(); - if (!self) { - return; - } - if (resp.success) { - self->sendJsonResponse(resp.body, self->encoded_session_id_); - } else { - self->config_->stats().rq_backend_failure_.inc(); - self->sendHttpError(500, resp.error.empty() ? "Backend request failed" : resp.error); + auto start_resource_method = [this, backend]() { + initializeSingleBackend(*backend, [weak_self = weak_from_this()](BackendResponse resp) { + auto self = weak_self.lock(); + if (!self) { + return; + } + if (resp.success) { + self->sendJsonResponse(resp.body, self->encoded_session_id_); + } else { + self->config_->stats().rq_backend_failure_.inc(); + self->sendHttpError(500, resp.error.empty() ? "Backend request failed" : resp.error); + } + }); + + if (lazy_init_request_body_.length() > 0) { + if (needs_body_rewrite_) { + config_->stats().rq_body_rewrite_.inc(); + rewriteResourceUriBody(lazy_init_request_body_); + needs_body_rewrite_ = false; + } + streamData(lazy_init_request_body_, true); } - }); + }; + + if (config_->lazyInitialization() && !initialized_backends_.contains(backend_name)) { + lazy_init_pending_ = true; + lazyInitSingleBackend(*backend, [weak_self = weak_from_this(), + start_resource_method = std::move(start_resource_method), + backend_name](bool success) { + auto self = weak_self.lock(); + if (!self) { + return; + } + self->lazy_init_pending_ = false; + if (!success) { + self->config_->stats().rq_backend_failure_.inc(); + self->sendHttpError(500, fmt::format("Failed to initialize backend '{}'", backend_name)); + return; + } + start_resource_method(); + }); + return; + } + + start_resource_method(); } void McpRouterFilter::handleResourcesRead() { handleSingleBackendResourceMethod("resources/read"); } @@ -1029,31 +1189,85 @@ void McpRouterFilter::handleResourcesTemplatesList() { ENVOY_LOG(debug, "resources/templates/list: setting up fanout to {} backends", config_->backends().size()); - initializeFanout([weak_self = weak_from_this()](std::vector responses) { - auto self = weak_self.lock(); - if (!self) { - ENVOY_LOG(debug, "resources/templates/list callback ignored: filter destroyed"); - return; + auto start_templates_list = [this]() { + initializeFanout([weak_self = weak_from_this()](std::vector responses) { + auto self = weak_self.lock(); + if (!self) { + ENVOY_LOG(debug, "resources/templates/list callback ignored: filter destroyed"); + return; + } + std::string response_body = self->aggregateResourcesTemplatesList(responses); + ENVOY_LOG(debug, "resources/templates/list: response body: {}", response_body); + self->sendJsonResponse(response_body, self->encoded_session_id_); + }); + + if (lazy_init_request_body_.length() > 0) { + streamData(lazy_init_request_body_, true); } - std::string response_body = self->aggregateResourcesTemplatesList(responses); - ENVOY_LOG(debug, "resources/templates/list: response body: {}", response_body); - self->sendJsonResponse(response_body, self->encoded_session_id_); - }); + }; + + if (config_->lazyInitialization() && initialized_backends_.size() < config_->backends().size()) { + lazy_init_pending_ = true; + lazyInitFanout([weak_self = weak_from_this(), + start_templates_list = std::move(start_templates_list)](bool success) { + auto self = weak_self.lock(); + if (!self) { + return; + } + self->lazy_init_pending_ = false; + if (!success) { + self->config_->stats().rq_fanout_failure_.inc(); + self->sendHttpError(500, "Failed to initialize backends"); + return; + } + start_templates_list(); + }); + return; + } + + start_templates_list(); } void McpRouterFilter::handlePromptsList() { ENVOY_LOG(debug, "prompts/list: setting up fanout to {} backends", config_->backends().size()); - initializeFanout([weak_self = weak_from_this()](std::vector responses) { - auto self = weak_self.lock(); - if (!self) { - ENVOY_LOG(debug, "prompts/list callback ignored: filter destroyed"); - return; + auto start_prompts_list = [this]() { + initializeFanout([weak_self = weak_from_this()](std::vector responses) { + auto self = weak_self.lock(); + if (!self) { + ENVOY_LOG(debug, "prompts/list callback ignored: filter destroyed"); + return; + } + std::string response_body = self->aggregatePromptsList(responses); + ENVOY_LOG(debug, "prompts/list: response body: {}", response_body); + self->sendJsonResponse(response_body, self->encoded_session_id_); + }); + + if (lazy_init_request_body_.length() > 0) { + streamData(lazy_init_request_body_, true); } - std::string response_body = self->aggregatePromptsList(responses); - ENVOY_LOG(debug, "prompts/list: response body: {}", response_body); - self->sendJsonResponse(response_body, self->encoded_session_id_); - }); + }; + + if (config_->lazyInitialization() && initialized_backends_.size() < config_->backends().size()) { + lazy_init_pending_ = true; + lazyInitFanout([weak_self = weak_from_this(), + start_prompts_list = std::move(start_prompts_list)](bool success) { + auto self = weak_self.lock(); + if (!self) { + return; + } + self->lazy_init_pending_ = false; + if (!success) { + self->config_->stats().rq_fanout_failure_.inc(); + self->sendHttpError(500, "Failed to initialize backends"); + return; + } + start_prompts_list(); + }); + return; + } + + start_prompts_list(); } void McpRouterFilter::handlePromptsGet() { @@ -1079,23 +1293,54 @@ void McpRouterFilter::handlePromptsGet() { ENVOY_LOG(debug, "prompts/get: backend='{}', prompt='{}' -> '{}', needs_rewrite={}", backend_name, prompt_name_, actual_prompt, needs_body_rewrite_); - initializeSingleBackend(*backend, [weak_self = weak_from_this()](BackendResponse resp) { - auto self = weak_self.lock(); - if (!self) { - return; - } - if (resp.success) { - self->sendJsonResponse(resp.body, self->encoded_session_id_); - } else { - self->config_->stats().rq_backend_failure_.inc(); - self->sendHttpError(500, resp.error.empty() ? "Backend request failed" : resp.error); + auto start_prompts_get = [this, backend]() { + initializeSingleBackend(*backend, [weak_self = weak_from_this()](BackendResponse resp) { + auto self = weak_self.lock(); + if (!self) { + return; + } + if (resp.success) { + self->sendJsonResponse(resp.body, self->encoded_session_id_); + } else { + self->config_->stats().rq_backend_failure_.inc(); + self->sendHttpError(500, resp.error.empty() ? "Backend request failed" : resp.error); + } + }); + + if (lazy_init_request_body_.length() > 0) { + if (needs_body_rewrite_) { + config_->stats().rq_body_rewrite_.inc(); + rewritePromptsGetBody(lazy_init_request_body_); + needs_body_rewrite_ = false; + } + streamData(lazy_init_request_body_, true); } - }); + }; + + if (config_->lazyInitialization() && !initialized_backends_.contains(backend_name)) { + lazy_init_pending_ = true; + lazyInitSingleBackend(*backend, [weak_self = weak_from_this(), + start_prompts_get = std::move(start_prompts_get), + backend_name](bool success) { + auto self = weak_self.lock(); + if (!self) { + return; + } + self->lazy_init_pending_ = false; + if (!success) { + self->config_->stats().rq_backend_failure_.inc(); + self->sendHttpError(500, fmt::format("Failed to initialize backend '{}'", backend_name)); + return; + } + start_prompts_get(); + }); + return; + } + + start_prompts_get(); } void McpRouterFilter::handleCompletionComplete() { - // Route based on ref type: ref/prompt uses prompt name, ref/resource uses resource URI. - // https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion std::string backend_name; if (completion_ref_type_ == "ref/prompt") { @@ -1136,51 +1381,107 @@ void McpRouterFilter::handleCompletionComplete() { return; } - initializeSingleBackend(*backend, [weak_self = weak_from_this()](BackendResponse resp) { - auto self = weak_self.lock(); - if (!self) { - return; - } - if (resp.success) { - self->sendJsonResponse(resp.body, self->encoded_session_id_); - } else { - self->config_->stats().rq_backend_failure_.inc(); - self->sendHttpError(500, resp.error.empty() ? "Backend request failed" : resp.error); + auto start_completion = [this, backend]() { + initializeSingleBackend(*backend, [weak_self = weak_from_this()](BackendResponse resp) { + auto self = weak_self.lock(); + if (!self) { + return; + } + if (resp.success) { + self->sendJsonResponse(resp.body, self->encoded_session_id_); + } else { + self->config_->stats().rq_backend_failure_.inc(); + self->sendHttpError(500, resp.error.empty() ? "Backend request failed" : resp.error); + } + }); + + if (lazy_init_request_body_.length() > 0) { + if (needs_body_rewrite_) { + config_->stats().rq_body_rewrite_.inc(); + rewriteCompletionCompleteBody(lazy_init_request_body_); + needs_body_rewrite_ = false; + } + streamData(lazy_init_request_body_, true); } - }); + }; + + if (config_->lazyInitialization() && !initialized_backends_.contains(backend_name)) { + lazy_init_pending_ = true; + lazyInitSingleBackend(*backend, [weak_self = weak_from_this(), + start_completion = std::move(start_completion), + backend_name](bool success) { + auto self = weak_self.lock(); + if (!self) { + return; + } + self->lazy_init_pending_ = false; + if (!success) { + self->config_->stats().rq_backend_failure_.inc(); + self->sendHttpError(500, fmt::format("Failed to initialize backend '{}'", backend_name)); + return; + } + start_completion(); + }); + return; + } + + start_completion(); } void McpRouterFilter::handleLoggingSetLevel() { - // Fan out to all backends and return empty result. - // https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging ENVOY_LOG(debug, "logging/setLevel: fanout to {} backends", config_->backends().size()); - initializeFanout([weak_self = weak_from_this()](std::vector responses) { - auto self = weak_self.lock(); - if (!self) { - ENVOY_LOG(debug, "logging/setLevel callback ignored: filter destroyed"); - return; - } - // Check if at least one backend succeeded. - bool any_success = false; - for (const auto& resp : responses) { - if (resp.success) { - any_success = true; - break; + auto start_logging = [this]() { + initializeFanout([weak_self = weak_from_this()](std::vector responses) { + auto self = weak_self.lock(); + if (!self) { + ENVOY_LOG(debug, "logging/setLevel callback ignored: filter destroyed"); + return; + } + bool any_success = false; + for (const auto& resp : responses) { + if (resp.success) { + any_success = true; + break; + } } - } - if (!any_success) { - self->config_->stats().rq_fanout_failure_.inc(); - self->sendHttpError(500, "All backends failed to set logging level"); - return; + if (!any_success) { + self->config_->stats().rq_fanout_failure_.inc(); + self->sendHttpError(500, "All backends failed to set logging level"); + return; + } + + std::string response = + fmt::format(R"({{"jsonrpc":"2.0","id":{},"result":{{}}}})", self->request_id_); + self->sendJsonResponse(response, self->encoded_session_id_); + }); + + if (lazy_init_request_body_.length() > 0) { + streamData(lazy_init_request_body_, true); } + }; + + if (config_->lazyInitialization() && initialized_backends_.size() < config_->backends().size()) { + lazy_init_pending_ = true; + lazyInitFanout( + [weak_self = weak_from_this(), start_logging = std::move(start_logging)](bool success) { + auto self = weak_self.lock(); + if (!self) { + return; + } + self->lazy_init_pending_ = false; + if (!success) { + self->config_->stats().rq_fanout_failure_.inc(); + self->sendHttpError(500, "Failed to initialize backends"); + return; + } + start_logging(); + }); + return; + } - // Return empty JSON-RPC result. - std::string response = - fmt::format(R"({{"jsonrpc":"2.0","id":{},"result":{{}}}})", self->request_id_); - self->sendJsonResponse(response, self->encoded_session_id_); - }); + start_logging(); } // Response aggregation helpers. @@ -1196,14 +1497,16 @@ std::string McpRouterFilter::extractJsonRpcFromResponse(const BackendResponse& r } std::string McpRouterFilter::aggregateInitialize(const std::vector& responses) { - // Check if at least one backend succeeded. - const bool any_success = std::any_of(responses.begin(), responses.end(), - [](const BackendResponse& resp) { return resp.success; }); + // Empty responses is valid for lazy initialization (no backends contacted yet). + if (!responses.empty()) { + const bool any_success = std::any_of(responses.begin(), responses.end(), + [](const BackendResponse& resp) { return resp.success; }); - if (!any_success) { - config_->stats().rq_fanout_failure_.inc(); - return absl::StrCat(R"({"jsonrpc":"2.0","id":)", request_id_, - R"(,"error":{"code":-32603,"message":"All backends failed"}})"); + if (!any_success) { + config_->stats().rq_fanout_failure_.inc(); + return absl::StrCat(R"({"jsonrpc":"2.0","id":)", request_id_, + R"(,"error":{"code":-32603,"message":"All backends failed"}})"); + } } // Return gateway capabilities. @@ -1632,6 +1935,147 @@ void McpRouterFilter::sendAccepted() { decoder_callbacks_->encodeHeaders(std::move(headers), true, "mcp_router"); } +std::string McpRouterFilter::buildSyntheticInitBody() { + return absl::StrCat(R"({"jsonrpc":"2.0","id":)", request_id_, + R"(,"method":"initialize","params":{"protocolVersion":")", kProtocolVersion, + R"(","capabilities":{},"clientInfo":{"name":")", kGatewayName, + R"(","version":")", kGatewayVersion, R"("}}})"); +} + +void McpRouterFilter::resetStreamState() { + if (multistream_) { + multistream_.reset(); + } + stream_callbacks_.clear(); + upstream_headers_.clear(); + aggregation_callback_ = nullptr; + single_backend_callback_ = nullptr; + pending_responses_.reset(); + response_count_.reset(); +} + +void McpRouterFilter::updateEncodedSessionId() { + std::string composite = + SessionCodec::buildCompositeSessionId(route_name_, session_subject_, backend_sessions_); + encoded_session_id_ = SessionCodec::encode(composite); +} + +void McpRouterFilter::lazyInitSingleBackend(const McpBackendConfig& backend, + std::function on_init_complete) { + ENVOY_LOG(debug, "Lazy init: initializing backend '{}'", backend.name); + + std::string init_body = buildSyntheticInitBody(); + + auto headers = createUpstreamHeaders(backend); + headers->setContentLength(init_body.size()); + + if (!muxdemux_->isIdle()) { + ENVOY_LOG(warn, "MuxDemux not idle for lazy init of '{}'", backend.name); + on_init_complete(false); + return; + } + + auto stream_cb = std::make_shared( + backend.name, + [weak_self = weak_from_this(), backend_name = backend.name, + on_init_complete = std::move(on_init_complete)](BackendResponse resp) { + auto self = weak_self.lock(); + if (!self) { + return; + } + + if (!resp.success) { + ENVOY_LOG(warn, "Lazy init failed for backend '{}': {}", backend_name, resp.error); + on_init_complete(false); + return; + } + + if (!resp.session_id.empty()) { + self->backend_sessions_[backend_name] = resp.session_id; + } + self->initialized_backends_.insert(backend_name); + self->updateEncodedSessionId(); + + ENVOY_LOG(debug, "Lazy init: backend '{}' initialized successfully", backend_name); + + self->resetStreamState(); + on_init_complete(true); + }, + request_id_, false, weak_from_this()); + + stream_callbacks_.push_back(stream_cb); + + Http::AsyncClient::StreamOptions options; + options.setTimeout(backend.timeout); + + std::vector mux_callbacks; + mux_callbacks.push_back({ + .cluster_name = backend.cluster_name, + .callbacks = std::weak_ptr(stream_cb), + .options = options, + }); + + Http::AsyncClient::StreamOptions default_options; + auto multistream_or = muxdemux_->multicast(default_options, mux_callbacks); + if (!multistream_or.ok()) { + ENVOY_LOG(error, "Failed to start lazy init for '{}': {}", backend.name, + multistream_or.status().message()); + on_init_complete(false); + return; + } + + multistream_ = std::move(*multistream_or); + upstream_headers_.clear(); + upstream_headers_.push_back(std::move(headers)); + + auto stream_it = multistream_->begin(); + if (stream_it != multistream_->end()) { + (*stream_it)->sendHeaders(*upstream_headers_.back(), false); + Buffer::OwnedImpl body(init_body); + (*stream_it)->sendData(body, true); + } +} + +void McpRouterFilter::lazyInitFanout(std::function on_init_complete) { + std::vector uninit_backends; + for (const auto& backend : config_->backends()) { + if (!initialized_backends_.contains(backend.name)) { + uninit_backends.push_back(backend.name); + } + } + + if (uninit_backends.empty()) { + on_init_complete(true); + return; + } + + ENVOY_LOG(debug, "Lazy init fanout: initializing {} backends", uninit_backends.size()); + initializeFanout( + [weak_self = weak_from_this(), + on_init_complete = std::move(on_init_complete)](std::vector responses) { + auto self = weak_self.lock(); + if (!self) { + return; + } + + bool any_success = false; + for (const auto& resp : responses) { + if (resp.success) { + any_success = true; + if (!resp.session_id.empty()) { + self->backend_sessions_[resp.backend_name] = resp.session_id; + } + self->initialized_backends_.insert(resp.backend_name); + } + } + + self->updateEncodedSessionId(); + self->resetStreamState(); + on_init_complete(any_success); + }, + uninit_backends); +} + void McpRouterFilter::sendHttpError(uint64_t status_code, const std::string& message) { auto headers = Http::ResponseHeaderMapImpl::create(); headers->setStatus(status_code); diff --git a/source/extensions/filters/http/mcp_router/mcp_router.h b/source/extensions/filters/http/mcp_router/mcp_router.h index eac5b131edc52..782688e313b2a 100644 --- a/source/extensions/filters/http/mcp_router/mcp_router.h +++ b/source/extensions/filters/http/mcp_router/mcp_router.h @@ -14,6 +14,7 @@ #include "source/extensions/filters/http/mcp_router/session_codec.h" #include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "absl/status/statusor.h" namespace Envoy { @@ -134,8 +135,9 @@ class McpRouterFilter : public Http::StreamDecoderFilter, // Extracts JSON-RPC payload from a response, handling SSE event wrapping. std::string extractJsonRpcFromResponse(const BackendResponse& response); - // Initialize fanout connections to all backends. - void initializeFanout(AggregationCallback callback); + // Initialize fanout connections to all backends (or a subset if target_backends is provided). + void initializeFanout(AggregationCallback callback, + const std::vector& target_backends = {}); // Initialize single backend connection. void initializeSingleBackend(const McpBackendConfig& backend, std::function callback); @@ -144,6 +146,14 @@ class McpRouterFilter : public Http::StreamDecoderFilter, std::function callback, bool streaming_enabled); + // Lazy initialization helpers. + void lazyInitSingleBackend(const McpBackendConfig& backend, + std::function on_init_complete); + void lazyInitFanout(std::function on_init_complete); + std::string buildSyntheticInitBody(); + void resetStreamState(); + void updateEncodedSessionId(); + // Stream data to established connection(s). void streamData(Buffer::Instance& data, bool end_stream); @@ -197,6 +207,11 @@ class McpRouterFilter : public Http::StreamDecoderFilter, bool initialized_{false}; // Track if SSE headers were sent (for aggregation with SSE backends) bool sse_headers_sent_{false}; + + // Lazy initialization state. + bool lazy_init_pending_{false}; + absl::flat_hash_set initialized_backends_; + Buffer::OwnedImpl lazy_init_request_body_; }; } // namespace McpRouter diff --git a/test/extensions/filters/http/mcp_router/mcp_router_test.cc b/test/extensions/filters/http/mcp_router/mcp_router_test.cc index 82c2a59287c66..a3ac98f66a202 100644 --- a/test/extensions/filters/http/mcp_router/mcp_router_test.cc +++ b/test/extensions/filters/http/mcp_router/mcp_router_test.cc @@ -458,6 +458,55 @@ class McpRouterFilterTest : public testing::Test { (*mcp_metadata.mutable_fields())["id"].set_number_value(static_cast(id)); } + void setMcpToolCallMetadata(const std::string& tool_name, int64_t id = 1, + const std::string& metadata_namespace = "envoy.filters.http.mcp") { + auto& mcp_metadata = (*dynamic_metadata_.mutable_filter_metadata())[metadata_namespace]; + (*mcp_metadata.mutable_fields())["method"].set_string_value("tools/call"); + (*mcp_metadata.mutable_fields())["id"].set_number_value(static_cast(id)); + auto& params = (*mcp_metadata.mutable_fields())["params"]; + (*params.mutable_struct_value()->mutable_fields())["name"].set_string_value(tool_name); + } + + void setupMockAsyncClient( + std::vector& http_callbacks, + std::vector>>& http_streams) { + factory_context_.server_factory_context_.cluster_manager_.initializeThreadLocalClusters( + {"test_cluster", "time_cluster", "calc_cluster"}); + EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_ + .async_client_, + start(_, _)) + .WillRepeatedly( + [&http_callbacks, &http_streams](Http::AsyncClient::StreamCallbacks& callbacks, + const Http::AsyncClient::StreamOptions&) { + http_callbacks.push_back(&callbacks); + http_streams.emplace_back(std::make_unique>()); + return http_streams.back().get(); + }); + } + + void simulateBackendResponse(Http::AsyncClient::StreamCallbacks* cb, const std::string& body, + const std::string& session_id = "", + const std::string& content_type = "application/json") { + auto response_headers = Http::ResponseHeaderMapImpl::create(); + response_headers->setStatus(200); + response_headers->setContentType(content_type); + if (!session_id.empty()) { + response_headers->addCopy(Http::LowerCaseString("mcp-session-id"), session_id); + } + cb->onHeaders(std::move(response_headers), false); + Buffer::OwnedImpl response_data(body); + cb->onData(response_data, true); + } + + void simulateBackendError(Http::AsyncClient::StreamCallbacks* cb, uint64_t status_code = 500) { + auto response_headers = Http::ResponseHeaderMapImpl::create(); + response_headers->setStatus(status_code); + response_headers->setContentType("text/plain"); + cb->onHeaders(std::move(response_headers), false); + Buffer::OwnedImpl response_data("error"); + cb->onData(response_data, true); + } + NiceMock factory_context_; NiceMock decoder_callbacks_; NiceMock stream_info_; @@ -799,6 +848,1286 @@ TEST(AggregateToolsListTest, SerializationPreservesNestedInputSchema) { EXPECT_EQ(*count_type, "integer"); } +// Verifies lazy_initialization config field defaults to false and can be enabled. +TEST_F(McpRouterConfigTest, LazyInitializationDefault) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); + EXPECT_FALSE(config.lazyInitialization()); +} + +TEST_F(McpRouterConfigTest, LazyInitializationEnabled) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + proto_config.set_lazy_initialization(true); + + McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_); + EXPECT_TRUE(config.lazyInitialization()); +} + +// Verifies McpRouterClusterConfigImpl delegates lazyInitialization to base. +TEST_F(McpRouterConfigTest, ClusterConfigDelegatesLazyInit) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter base_proto; + base_proto.set_lazy_initialization(true); + auto base_config = std::make_shared(base_proto, "test.", *store_.rootScope(), + factory_context_); + + envoy::extensions::clusters::mcp_multicluster::v3::ClusterConfig cluster_proto; + auto* server = cluster_proto.add_servers(); + server->set_name("cluster_backend"); + server->mutable_mcp_cluster()->set_cluster("target_cluster"); + + McpRouterClusterConfigImpl cluster_config(cluster_proto, base_config); + EXPECT_TRUE(cluster_config.lazyInitialization()); +} + +// Verifies lazy initialization responds immediately to initialize without backend fanout. +TEST_F(McpRouterFilterTest, LazyInitializeRespondsImmediately) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("initialize"); + + auto config = createConfig(proto_config); + McpRouterFilter filter(config); + filter.setDecoderFilterCallbacks(decoder_callbacks_); + + Http::TestRequestHeaderMapImpl headers{ + {":method", "POST"}, {":path", "/mcp"}, {"content-type", "application/json"}}; + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + auto session_header = headers.get(Http::LowerCaseString("mcp-session-id")); + EXPECT_FALSE(session_header.empty()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + filter.decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-06-18"}})"; + Buffer::OwnedImpl buffer(body); + filter.decodeData(buffer, true); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init tools/call triggers backend initialization then forwards the request. +TEST_F(McpRouterFilterTest, LazyInitToolsCallInitializesBackend) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpToolCallMetadata("get_time"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + // Build a session ID with empty backend sessions (lazy init state). + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"get_time"}})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + // First stream should be the lazy init (initialize) request to the backend. + ASSERT_GE(http_callbacks.size(), 1); + + // Simulate backend init response. + const std::string init_response = + R"({"jsonrpc":"2.0","id":2,"result":{"protocolVersion":"2025-06-18","capabilities":{}}})"; + simulateBackendResponse(http_callbacks[0], init_response, "backend-session-1"); + + // Second stream should be the actual tools/call request. + ASSERT_GE(http_callbacks.size(), 2); + + // Simulate tools/call response. + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + const std::string tool_response = + R"({"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"12:00"}]}})"; + simulateBackendResponse(http_callbacks[1], tool_response); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init tools/list triggers fanout initialization then forwards the list request. +TEST_F(McpRouterFilterTest, LazyInitToolsListInitializesAllBackends) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server1 = proto_config.add_servers(); + server1->set_name("time"); + server1->mutable_mcp_cluster()->set_cluster("time_cluster"); + auto* server2 = proto_config.add_servers(); + server2->set_name("calc"); + server2->mutable_mcp_cluster()->set_cluster("calc_cluster"); + + setMcpMethodMetadata("tools/list"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + // Build a session ID with empty backend sessions. + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = R"({"jsonrpc":"2.0","method":"tools/list","id":3})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + // First two streams should be the lazy init fanout (initialize to both backends). + ASSERT_GE(http_callbacks.size(), 2); + + const std::string init_response = + R"({"jsonrpc":"2.0","id":3,"result":{"protocolVersion":"2025-06-18","capabilities":{}}})"; + simulateBackendResponse(http_callbacks[0], init_response, "time-session"); + simulateBackendResponse(http_callbacks[1], init_response, "calc-session"); + + // Next two streams should be the actual tools/list fanout. + ASSERT_GE(http_callbacks.size(), 4); + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + const std::string time_tools = + R"({"jsonrpc":"2.0","id":3,"result":{"tools":[{"name":"get_time"}]}})"; + const std::string calc_tools = R"({"jsonrpc":"2.0","id":3,"result":{"tools":[{"name":"add"}]}})"; + simulateBackendResponse(http_callbacks[2], time_tools); + simulateBackendResponse(http_callbacks[3], calc_tools); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init notifications with no initialized backends respond 202 immediately. +TEST_F(McpRouterFilterTest, LazyInitNotificationNoBackends) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("notifications/initialized"); + + auto config = createConfig(proto_config); + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + // Build a session ID with empty backend sessions. + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool end_stream) { + EXPECT_EQ("202", headers.getStatusValue()); + EXPECT_TRUE(end_stream); + response_sent = true; + })); + + filter->decodeHeaders(headers, false); + + const std::string body = R"({"jsonrpc":"2.0","method":"notifications/initialized"})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init with eager mode disabled (default) behaves normally. +TEST_F(McpRouterFilterTest, EagerInitUnchangedWithLazyDisabled) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("initialize"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + Http::TestRequestHeaderMapImpl headers{ + {":method", "POST"}, {":path", "/mcp"}, {"content-type", "application/json"}}; + + filter->decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-06-18"}})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + // Eager mode should have started a backend stream (unlike lazy init which responds immediately). + EXPECT_GE(http_callbacks.size(), 1); +} + +// Verifies lazy init prompts/get triggers backend initialization. +TEST_F(McpRouterFilterTest, LazyInitPromptsGetInitializesBackend) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + // Set prompts/get metadata with prompt name. + auto& mcp_metadata = (*dynamic_metadata_.mutable_filter_metadata())["envoy.filters.http.mcp"]; + (*mcp_metadata.mutable_fields())["method"].set_string_value("prompts/get"); + (*mcp_metadata.mutable_fields())["id"].set_number_value(4); + auto& params = (*mcp_metadata.mutable_fields())["params"]; + (*params.mutable_struct_value()->mutable_fields())["name"].set_string_value("greeting"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"prompts/get","id":4,"params":{"name":"greeting"}})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + // First stream should be the lazy init request. + ASSERT_GE(http_callbacks.size(), 1); + + const std::string init_response = + R"({"jsonrpc":"2.0","id":4,"result":{"protocolVersion":"2025-06-18","capabilities":{}}})"; + simulateBackendResponse(http_callbacks[0], init_response, "backend-session-1"); + + // Second stream should be the actual prompts/get request. + ASSERT_GE(http_callbacks.size(), 2); + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + const std::string prompt_response = + R"({"jsonrpc":"2.0","id":4,"result":{"messages":[{"role":"assistant","content":{"type":"text","text":"Hello!"}}]}})"; + simulateBackendResponse(http_callbacks[1], prompt_response); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init prompts/list triggers fanout initialization. +TEST_F(McpRouterFilterTest, LazyInitPromptsListInitializesBackends) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("prompts/list"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = R"({"jsonrpc":"2.0","method":"prompts/list","id":5})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + // First stream: lazy init. + ASSERT_GE(http_callbacks.size(), 1); + + const std::string init_response = + R"({"jsonrpc":"2.0","id":5,"result":{"protocolVersion":"2025-06-18","capabilities":{}}})"; + simulateBackendResponse(http_callbacks[0], init_response, "backend-session-1"); + + // Second stream: actual prompts/list fanout. + ASSERT_GE(http_callbacks.size(), 2); + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + const std::string prompts_response = + R"({"jsonrpc":"2.0","id":5,"result":{"prompts":[{"name":"greeting"}]}})"; + simulateBackendResponse(http_callbacks[1], prompts_response); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init resources/list triggers fanout initialization. +TEST_F(McpRouterFilterTest, LazyInitResourcesListInitializesBackends) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("resources/list"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = R"({"jsonrpc":"2.0","method":"resources/list","id":6})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + // First stream: lazy init. + ASSERT_GE(http_callbacks.size(), 1); + + const std::string init_response = + R"({"jsonrpc":"2.0","id":6,"result":{"protocolVersion":"2025-06-18","capabilities":{}}})"; + simulateBackendResponse(http_callbacks[0], init_response, "backend-session-1"); + + // Second stream: actual resources/list fanout. + ASSERT_GE(http_callbacks.size(), 2); + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + const std::string resources_response = + R"({"jsonrpc":"2.0","id":6,"result":{"resources":[{"uri":"file://test","name":"test"}]}})"; + simulateBackendResponse(http_callbacks[1], resources_response); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init resources/templates/list triggers fanout initialization. +TEST_F(McpRouterFilterTest, LazyInitResourcesTemplatesListInitializesBackends) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("resources/templates/list"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = R"({"jsonrpc":"2.0","method":"resources/templates/list","id":7})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + const std::string init_response = + R"({"jsonrpc":"2.0","id":7,"result":{"protocolVersion":"2025-06-18","capabilities":{}}})"; + simulateBackendResponse(http_callbacks[0], init_response, "backend-session-1"); + + ASSERT_GE(http_callbacks.size(), 2); + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + const std::string templates_response = + R"({"jsonrpc":"2.0","id":7,"result":{"resourceTemplates":[{"uriTemplate":"file:///{path}","name":"file"}]}})"; + simulateBackendResponse(http_callbacks[1], templates_response); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init logging/setLevel triggers fanout initialization. +TEST_F(McpRouterFilterTest, LazyInitLoggingSetLevelInitializesBackends) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("logging/setLevel"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"logging/setLevel","id":8,"params":{"level":"debug"}})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + const std::string init_response = + R"({"jsonrpc":"2.0","id":8,"result":{"protocolVersion":"2025-06-18","capabilities":{}}})"; + simulateBackendResponse(http_callbacks[0], init_response, "backend-session-1"); + + ASSERT_GE(http_callbacks.size(), 2); + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + const std::string logging_response = R"({"jsonrpc":"2.0","id":8,"result":{}})"; + simulateBackendResponse(http_callbacks[1], logging_response); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init resources/read triggers backend initialization. +TEST_F(McpRouterFilterTest, LazyInitResourcesReadInitializesBackend) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + // Set resources/read metadata with URI. + auto& mcp_metadata = (*dynamic_metadata_.mutable_filter_metadata())["envoy.filters.http.mcp"]; + (*mcp_metadata.mutable_fields())["method"].set_string_value("resources/read"); + (*mcp_metadata.mutable_fields())["id"].set_number_value(9); + auto& params = (*mcp_metadata.mutable_fields())["params"]; + (*params.mutable_struct_value()->mutable_fields())["uri"].set_string_value("file://test.txt"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"resources/read","id":9,"params":{"uri":"file://test.txt"}})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + const std::string init_response = + R"({"jsonrpc":"2.0","id":9,"result":{"protocolVersion":"2025-06-18","capabilities":{}}})"; + simulateBackendResponse(http_callbacks[0], init_response, "backend-session-1"); + + ASSERT_GE(http_callbacks.size(), 2); + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + const std::string resource_response = + R"({"jsonrpc":"2.0","id":9,"result":{"contents":[{"uri":"file://test.txt","text":"hello"}]}})"; + simulateBackendResponse(http_callbacks[1], resource_response); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init completion/complete with ref/prompt triggers backend initialization. +TEST_F(McpRouterFilterTest, LazyInitCompletionCompleteInitializesBackend) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + // Set completion/complete metadata with ref/prompt. + auto& mcp_metadata = (*dynamic_metadata_.mutable_filter_metadata())["envoy.filters.http.mcp"]; + (*mcp_metadata.mutable_fields())["method"].set_string_value("completion/complete"); + (*mcp_metadata.mutable_fields())["id"].set_number_value(10); + auto& params = (*mcp_metadata.mutable_fields())["params"]; + auto& ref = (*params.mutable_struct_value()->mutable_fields())["ref"]; + (*ref.mutable_struct_value()->mutable_fields())["type"].set_string_value("ref/prompt"); + (*ref.mutable_struct_value()->mutable_fields())["name"].set_string_value("greeting"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"completion/complete","id":10,"params":{"ref":{"type":"ref/prompt","name":"greeting"},"argument":{"name":"name","value":"wo"}}})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + const std::string init_response = + R"({"jsonrpc":"2.0","id":10,"result":{"protocolVersion":"2025-06-18","capabilities":{}}})"; + simulateBackendResponse(http_callbacks[0], init_response, "backend-session-1"); + + ASSERT_GE(http_callbacks.size(), 2); + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + const std::string completion_response = + R"({"jsonrpc":"2.0","id":10,"result":{"completion":{"values":["world"]}}})"; + simulateBackendResponse(http_callbacks[1], completion_response); + + EXPECT_TRUE(response_sent); +} + +// Verifies lazy init tools/call sends error when backend init fails. +TEST_F(McpRouterFilterTest, LazyInitToolsCallInitFailure) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpToolCallMetadata("get_time"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"get_time"}})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + bool error_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("500", headers.getStatusValue()); + error_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + simulateBackendError(http_callbacks[0]); + + EXPECT_TRUE(error_sent); +} + +// Verifies lazy init tools/list sends error when fanout init fails. +TEST_F(McpRouterFilterTest, LazyInitToolsListInitFailure) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("tools/list"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = R"({"jsonrpc":"2.0","method":"tools/list","id":3})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + bool error_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("500", headers.getStatusValue()); + error_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + simulateBackendError(http_callbacks[0]); + + EXPECT_TRUE(error_sent); +} + +// Verifies lazy init prompts/get sends error when backend init fails. +TEST_F(McpRouterFilterTest, LazyInitPromptsGetInitFailure) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + auto& mcp_metadata = (*dynamic_metadata_.mutable_filter_metadata())["envoy.filters.http.mcp"]; + (*mcp_metadata.mutable_fields())["method"].set_string_value("prompts/get"); + (*mcp_metadata.mutable_fields())["id"].set_number_value(4); + auto& params = (*mcp_metadata.mutable_fields())["params"]; + (*params.mutable_struct_value()->mutable_fields())["name"].set_string_value("greeting"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"prompts/get","id":4,"params":{"name":"greeting"}})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + bool error_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("500", headers.getStatusValue()); + error_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + simulateBackendError(http_callbacks[0]); + + EXPECT_TRUE(error_sent); +} + +// Verifies lazy init prompts/list sends error when fanout init fails. +TEST_F(McpRouterFilterTest, LazyInitPromptsListInitFailure) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("prompts/list"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = R"({"jsonrpc":"2.0","method":"prompts/list","id":5})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + bool error_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("500", headers.getStatusValue()); + error_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + simulateBackendError(http_callbacks[0]); + + EXPECT_TRUE(error_sent); +} + +// Verifies lazy init resources/list sends error when fanout init fails. +TEST_F(McpRouterFilterTest, LazyInitResourcesListInitFailure) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("resources/list"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = R"({"jsonrpc":"2.0","method":"resources/list","id":6})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + bool error_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("500", headers.getStatusValue()); + error_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + simulateBackendError(http_callbacks[0]); + + EXPECT_TRUE(error_sent); +} + +// Verifies lazy init resources/read sends error when backend init fails. +TEST_F(McpRouterFilterTest, LazyInitResourcesReadInitFailure) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + auto& mcp_metadata = (*dynamic_metadata_.mutable_filter_metadata())["envoy.filters.http.mcp"]; + (*mcp_metadata.mutable_fields())["method"].set_string_value("resources/read"); + (*mcp_metadata.mutable_fields())["id"].set_number_value(9); + auto& params = (*mcp_metadata.mutable_fields())["params"]; + (*params.mutable_struct_value()->mutable_fields())["uri"].set_string_value("file://test.txt"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"resources/read","id":9,"params":{"uri":"file://test.txt"}})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + bool error_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("500", headers.getStatusValue()); + error_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + simulateBackendError(http_callbacks[0]); + + EXPECT_TRUE(error_sent); +} + +// Verifies lazy init completion/complete sends error when backend init fails. +TEST_F(McpRouterFilterTest, LazyInitCompletionCompleteInitFailure) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + auto& mcp_metadata = (*dynamic_metadata_.mutable_filter_metadata())["envoy.filters.http.mcp"]; + (*mcp_metadata.mutable_fields())["method"].set_string_value("completion/complete"); + (*mcp_metadata.mutable_fields())["id"].set_number_value(10); + auto& params = (*mcp_metadata.mutable_fields())["params"]; + auto& ref = (*params.mutable_struct_value()->mutable_fields())["ref"]; + (*ref.mutable_struct_value()->mutable_fields())["type"].set_string_value("ref/prompt"); + (*ref.mutable_struct_value()->mutable_fields())["name"].set_string_value("greeting"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = + R"({"jsonrpc":"2.0","method":"completion/complete","id":10,"params":{"ref":{"type":"ref/prompt","name":"greeting"}}})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + bool error_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("500", headers.getStatusValue()); + error_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + simulateBackendError(http_callbacks[0]); + + EXPECT_TRUE(error_sent); +} + +// Verifies lazy init resources/templates/list sends error when fanout init fails. +TEST_F(McpRouterFilterTest, LazyInitResourcesTemplatesListInitFailure) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("resources/templates/list"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = R"({"jsonrpc":"2.0","method":"resources/templates/list","id":7})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + bool error_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("500", headers.getStatusValue()); + error_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + simulateBackendError(http_callbacks[0]); + + EXPECT_TRUE(error_sent); +} + +// Verifies lazy init logging/setLevel sends error when fanout init fails. +TEST_F(McpRouterFilterTest, LazyInitLoggingSetLevelInitFailure) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpMethodMetadata("logging/setLevel"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + const std::string body = R"({"jsonrpc":"2.0","method":"logging/setLevel","id":8})"; + Buffer::OwnedImpl buffer(body); + filter->decodeData(buffer, true); + + ASSERT_GE(http_callbacks.size(), 1); + + bool error_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("500", headers.getStatusValue()); + error_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + simulateBackendError(http_callbacks[0]); + + EXPECT_TRUE(error_sent); +} + +// Verifies multi-chunk data is buffered during lazy init. +TEST_F(McpRouterFilterTest, LazyInitBuffersMultiChunkData) { + envoy::extensions::filters::http::mcp_router::v3::McpRouter proto_config; + proto_config.set_lazy_initialization(true); + auto* server = proto_config.add_servers(); + server->set_name("test"); + server->mutable_mcp_cluster()->set_cluster("test_cluster"); + + setMcpToolCallMetadata("get_time"); + + auto config = createConfig(proto_config); + + std::vector http_callbacks; + std::vector>> http_streams; + + auto filter = std::make_shared(config); + filter->setDecoderFilterCallbacks(decoder_callbacks_); + + setupMockAsyncClient(http_callbacks, http_streams); + + absl::flat_hash_map empty_sessions; + std::string composite = + SessionCodec::buildCompositeSessionId("default", "default", empty_sessions); + std::string encoded_session = SessionCodec::encode(composite); + + Http::TestRequestHeaderMapImpl headers{{":method", "POST"}, + {":path", "/mcp"}, + {"content-type", "application/json"}, + {"mcp-session-id", encoded_session}}; + + filter->decodeHeaders(headers, false); + + // Send first chunk (not end_stream) — triggers lazy init. + const std::string body_part1 = R"({"jsonrpc":"2.0","method":"tools/call",)"; + Buffer::OwnedImpl buffer1(body_part1); + EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter->decodeData(buffer1, false)); + + // Send second chunk while lazy init is still pending — should be buffered. + const std::string body_part2 = R"("id":2,"params":{"name":"get_time"}})"; + Buffer::OwnedImpl buffer2(body_part2); + EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter->decodeData(buffer2, true)); + + ASSERT_GE(http_callbacks.size(), 1); + + const std::string init_response = + R"({"jsonrpc":"2.0","id":2,"result":{"protocolVersion":"2025-06-18","capabilities":{}}})"; + simulateBackendResponse(http_callbacks[0], init_response, "backend-session-1"); + + ASSERT_GE(http_callbacks.size(), 2); + + bool response_sent = false; + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .WillOnce(testing::Invoke([&](Http::ResponseHeaderMap& headers, bool) { + EXPECT_EQ("200", headers.getStatusValue()); + response_sent = true; + })); + EXPECT_CALL(decoder_callbacks_, encodeData(_, true)); + + const std::string tool_response = + R"({"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"12:00"}]}})"; + simulateBackendResponse(http_callbacks[1], tool_response); + + EXPECT_TRUE(response_sent); +} + +// Verifies empty backend session map produces a valid session ID. +TEST_F(SessionCodecTest, EmptyBackendSessions) { + absl::flat_hash_map empty_sessions; + std::string composite = SessionCodec::buildCompositeSessionId("route", "user", empty_sessions); + + auto parsed = SessionCodec::parseCompositeSessionId(composite); + ASSERT_TRUE(parsed.ok()); + EXPECT_EQ(parsed->route, "route"); + EXPECT_EQ(parsed->subject, "user"); + EXPECT_TRUE(parsed->backend_sessions.empty()); + + std::string encoded = SessionCodec::encode(composite); + std::string decoded = SessionCodec::decode(encoded); + EXPECT_EQ(decoded, composite); +} + // Verifies tools with icons array are handled correctly. TEST(AggregateToolsListTest, IconsArrayPreserved) { const std::string tool_json = R"({ From aaea20a5f04eee18934e6e545266bc63190546dd Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Tue, 19 May 2026 12:27:53 -0400 Subject: [PATCH 344/750] mobile: add unregisterApi to external API and use in RtdsIntegrationTest (#45150) * Added `unregisterApi` to external.h and external.cc to allow removing APIs in the global registry. * Used `unregisterApi` in `RtdsIntegrationTest::TearDown` to clean up "envoy_proxy_resolver" between test runs. With the `RtdsReloadWithWorkerThread` test, depending on the options used, "envoy_proxy_resolver" will be registered in the global registry on some test runs and linger on for the `RtdsReloadWithWorkerThread` where it should *not* be registered, causing the test to fail. Signed-off-by: Ali Beyad --- mobile/library/common/api/external.cc | 2 ++ mobile/library/common/api/external.h | 5 +++++ mobile/test/common/integration/BUILD | 1 + mobile/test/common/integration/rtds_integration_test.cc | 6 ++++++ 4 files changed, 14 insertions(+) diff --git a/mobile/library/common/api/external.cc b/mobile/library/common/api/external.cc index e62ceacaa578d..718087a3b5e92 100644 --- a/mobile/library/common/api/external.cc +++ b/mobile/library/common/api/external.cc @@ -32,6 +32,8 @@ void* retrieveApi(absl::string_view name, bool allow_absent) { return api; } +void unregisterApi(absl::string_view name) { registry_.erase(name); } + } // namespace External } // namespace Api } // namespace Envoy diff --git a/mobile/library/common/api/external.h b/mobile/library/common/api/external.h index 1761995280dc0..f8f4496dcc451 100644 --- a/mobile/library/common/api/external.h +++ b/mobile/library/common/api/external.h @@ -18,6 +18,11 @@ void registerApi(std::string&& name, void* api); */ void* retrieveApi(absl::string_view name, bool allow_absent = false); +/** + * Unregister an external runtime API. + */ +void unregisterApi(absl::string_view name); + } // namespace External } // namespace Api } // namespace Envoy diff --git a/mobile/test/common/integration/BUILD b/mobile/test/common/integration/BUILD index 647da5a551aac..1db132b65adb1 100644 --- a/mobile/test/common/integration/BUILD +++ b/mobile/test/common/integration/BUILD @@ -141,6 +141,7 @@ envoy_cc_test_with_engine_builder( repository = "@envoy", deps = [ ":xds_integration_test_lib", + "//library/common/api:external_api_lib", "@envoy//test/test_common:environment_lib", "@envoy//test/test_common:test_runtime_lib", "@envoy//test/test_common:utility_lib", diff --git a/mobile/test/common/integration/rtds_integration_test.cc b/mobile/test/common/integration/rtds_integration_test.cc index 873ef7041447c..a2192fa2d3022 100644 --- a/mobile/test/common/integration/rtds_integration_test.cc +++ b/mobile/test/common/integration/rtds_integration_test.cc @@ -10,6 +10,7 @@ #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" +#include "library/common/api/external.h" #include "gtest/gtest.h" using testing::Ge; @@ -44,6 +45,11 @@ class RtdsIntegrationTest : public XdsIntegrationTest { void SetUp() override {} + void TearDown() override { + Api::External::unregisterApi("envoy_proxy_resolver"); + XdsIntegrationTest::TearDown(); + } + void runReloadTest() { initialize(); From e92a0e148f1505a088918f1e561e02b8e0c23d05 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 19 May 2026 18:50:14 +0100 Subject: [PATCH 345/750] changelogs: Migrate to file-per-entry layout (#45093) The single current.yaml file is a constant source of merge conflicts and rebase pain. Moving to one-file-per-entry fixes that and makes review/cherry-pick much simpler. see https://github.com/envoyproxy/envoy/issues/45095 --------- Signed-off-by: Ryan Northey --- changelogs/BUILD | 10 +- changelogs/changelogs.yaml | 96 ++++++ changelogs/current.yaml | 282 ------------------ ..._fixed-illegal-ambiguous-match-error-2.rst | 3 + ...d__fixed-illegal-ambiguous-match-error.rst | 3 + ...fixed-a-memory-leak-in-the-hickory-dns.rst | 4 + ...solver__fixed-a-uaf-in-the-hickory-dns.rst | 4 + ...r__fixed-macos-build-failure-45061-for.rst | 4 + ..._fixed-a-bug-where-the-http-filter-per.rst | 5 + ...ules__fixed-a-crashing-bug-in-the-http.rst | 3 + .../http__fixed-rst-error-discard.rst | 5 + ...t__fixed-a-bug-in-load-stats-reporting.rst | 3 + ...2__fixed-a-bug-where-hmac-verification.rst | 2 + ...h2__fixed-a-crash-in-the-oauth2-filter.rst | 6 + .../rbac__fixed-a-bug-in-the-ref-mtls.rst | 4 + ...fixed-a-bug-where-if-an-invalid-custom.rst | 2 + .../wasm__fixed-a-bug-where-envoy-did-not.rst | 4 + ...golang__reduced-the-per-cgo-call-mutex.rst | 14 + ...the-goaway-load-shed-point-is-fixed-to.rst | 3 + ..._the-upstream-transport-failure-reason.rst | 6 + ...s__optimized-prometheus-stats-endpoint.rst | 3 + ...s__sslsocket-now-reports-econnreset-as.rst | 7 + ..._added-upstream-server-name-access-log.rst | 2 + ...supported-the-singleton-stats-scope-in.rst | 1 + ...ibutes__added-upstream-server-name-cel.rst | 2 + ...site__added-support-for-the-ref-inline.rst | 5 + ...added-envoy-dynamic-module-callback-is.rst | 2 + ...s__added-envoy-dynamic-module-callback.rst | 5 + ...ector__enabled-balsa-parser-for-http-2.rst | 3 + ...spector__enabled-balsa-parser-for-http.rst | 3 + ...__added-ref-verification-status-header.rst | 8 + ..._balancing__out-of-band-orca-reporting.rst | 5 + ...ng__added-n-as-a-custom-spdlog-pattern.rst | 3 + .../mcp_router__lazy_initialization.rst | 4 + ...__added-ssl-termination-support-to-the.rst | 7 + ...__added-support-for-tls-session-ticket.rst | 5 + ..._make-namespace-for-storing-rate-limit.rst | 1 + ...__overload-manager-fixed-heap-resource.rst | 3 + ...er__added-ref-refresh-cluster-on-retry.rst | 5 + ..._added-support-for-refreshroutecluster.rst | 6 + ...ter__added-ref-per-route-configuration.rst | 2 + ...nks__added-a-new-ref-wasm-stats-filter.rst | 10 + ..._added-ref-max-data-points-per-request.rst | 3 + ...y__added-envoy-reloadable-features-tcp.rst | 3 + ...__added-ref-check-drain-close-envoy-v3.rst | 4 + ...added-substitution-commands-downstream.rst | 2 + changelogs/sections.yaml | 24 -- docs/BUILD | 2 +- docs/conf.py | 2 +- docs/tools/python/BUILD | 18 ++ docs/tools/python/requirements.in | 8 +- docs/tools/python/requirements.txt | 146 ++++----- tools/base/requirements.in | 4 +- tools/base/requirements.txt | 12 +- 54 files changed, 380 insertions(+), 403 deletions(-) create mode 100644 changelogs/changelogs.yaml create mode 100644 changelogs/current/bug_fixes/build__fixed-illegal-ambiguous-match-error-2.rst create mode 100644 changelogs/current/bug_fixes/build__fixed-illegal-ambiguous-match-error.rst create mode 100644 changelogs/current/bug_fixes/dns_resolver__fixed-a-memory-leak-in-the-hickory-dns.rst create mode 100644 changelogs/current/bug_fixes/dns_resolver__fixed-a-uaf-in-the-hickory-dns.rst create mode 100644 changelogs/current/bug_fixes/dns_resolver__fixed-macos-build-failure-45061-for.rst create mode 100644 changelogs/current/bug_fixes/dynamic_modules__fixed-a-bug-where-the-http-filter-per.rst create mode 100644 changelogs/current/bug_fixes/dynamic_modules__fixed-a-crashing-bug-in-the-http.rst create mode 100644 changelogs/current/bug_fixes/http__fixed-rst-error-discard.rst create mode 100644 changelogs/current/bug_fixes/load_report__fixed-a-bug-in-load-stats-reporting.rst create mode 100644 changelogs/current/bug_fixes/oauth2__fixed-a-bug-where-hmac-verification.rst create mode 100644 changelogs/current/bug_fixes/oauth2__fixed-a-crash-in-the-oauth2-filter.rst create mode 100644 changelogs/current/bug_fixes/rbac__fixed-a-bug-in-the-ref-mtls.rst create mode 100644 changelogs/current/bug_fixes/spiffe_validator__fixed-a-bug-where-if-an-invalid-custom.rst create mode 100644 changelogs/current/bug_fixes/wasm__fixed-a-bug-where-envoy-did-not.rst create mode 100644 changelogs/current/minor_behavior_changes/golang__reduced-the-per-cgo-call-mutex.rst create mode 100644 changelogs/current/minor_behavior_changes/http2__the-goaway-load-shed-point-is-fixed-to.rst create mode 100644 changelogs/current/minor_behavior_changes/router__the-upstream-transport-failure-reason.rst create mode 100644 changelogs/current/minor_behavior_changes/stats__optimized-prometheus-stats-endpoint.rst create mode 100644 changelogs/current/minor_behavior_changes/tls__sslsocket-now-reports-econnreset-as.rst create mode 100644 changelogs/current/new_features/access_log__added-upstream-server-name-access-log.rst create mode 100644 changelogs/current/new_features/access_log__supported-the-singleton-stats-scope-in.rst create mode 100644 changelogs/current/new_features/attributes__added-upstream-server-name-cel.rst create mode 100644 changelogs/current/new_features/composite__added-support-for-the-ref-inline.rst create mode 100644 changelogs/current/new_features/dynamic_modules__added-envoy-dynamic-module-callback-is.rst create mode 100644 changelogs/current/new_features/dynamic_modules__added-envoy-dynamic-module-callback.rst create mode 100644 changelogs/current/new_features/http_inspector__enabled-balsa-parser-for-http-2.rst create mode 100644 changelogs/current/new_features/http_inspector__enabled-balsa-parser-for-http.rst create mode 100644 changelogs/current/new_features/jwt_authn__added-ref-verification-status-header.rst create mode 100644 changelogs/current/new_features/load_balancing__out-of-band-orca-reporting.rst create mode 100644 changelogs/current/new_features/logging__added-n-as-a-custom-spdlog-pattern.rst create mode 100644 changelogs/current/new_features/mcp_router__lazy_initialization.rst create mode 100644 changelogs/current/new_features/mysql_proxy__added-ssl-termination-support-to-the.rst create mode 100644 changelogs/current/new_features/quic__added-support-for-tls-session-ticket.rst create mode 100644 changelogs/current/new_features/ratelimit__make-namespace-for-storing-rate-limit.rst create mode 100644 changelogs/current/new_features/resource_monitors__overload-manager-fixed-heap-resource.rst create mode 100644 changelogs/current/new_features/router__added-ref-refresh-cluster-on-retry.rst create mode 100644 changelogs/current/new_features/router__added-support-for-refreshroutecluster.rst create mode 100644 changelogs/current/new_features/set_metadata_filter__added-ref-per-route-configuration.rst create mode 100644 changelogs/current/new_features/stat_sinks__added-a-new-ref-wasm-stats-filter.rst create mode 100644 changelogs/current/new_features/stat_sinks__added-ref-max-data-points-per-request.rst create mode 100644 changelogs/current/new_features/tcp_proxy__added-envoy-reloadable-features-tcp.rst create mode 100644 changelogs/current/new_features/tcp_proxy__added-ref-check-drain-close-envoy-v3.rst create mode 100644 changelogs/current/new_features/tls__added-substitution-commands-downstream.rst delete mode 100644 changelogs/sections.yaml diff --git a/changelogs/BUILD b/changelogs/BUILD index 26d4cd5f88fee..516783a31dc25 100644 --- a/changelogs/BUILD +++ b/changelogs/BUILD @@ -1,12 +1,18 @@ licenses(["notice"]) # Apache 2 exports_files([ - "sections.yaml", + "changelogs.yaml", ]) filegroup( name = "changelogs", - srcs = glob(["*.*.*.yaml"]) + ["current.yaml"], + srcs = glob([ + "*.*.*.yaml", + "current/**/*.rst", + ]) + [ + "changelogs.yaml", + "current.yaml", + ], visibility = ["//visibility:public"], ) diff --git a/changelogs/changelogs.yaml b/changelogs/changelogs.yaml new file mode 100644 index 0000000000000..a0d9d4415e0b4 --- /dev/null +++ b/changelogs/changelogs.yaml @@ -0,0 +1,96 @@ +# `sections:` lists the valid top-level changelog sections (e.g. +# `bug_fixes`, `new_features`). +# NB: Do not change sections without prior agreement from maintainers +# TODO(phlax): move current config to tool defaults +# +# `areas:` lists the canonical set of areas accepted by per-entry +# changelog filenames in changelogs/current/
/__.rst. +# In filenames, '/' MUST be encoded as '~'. +# Adding a new area requires a PR updating this file. + +sections: + changes: + title: Changes + behavior_changes: + title: Incompatible behavior changes + description: | + *Changes that are expected to cause an incompatibility if applicable; + deployment changes are likely required* + + minor_behavior_changes: + title: Minor behavior changes + description: | + *Changes that may cause incompatibilities for some users, but should + not for most* + + bug_fixes: + title: Bug fixes + description: | + *Changes expected to improve the state of the world and are unlikely + to have negative effects* + + removed_config_or_runtime: + title: Removed config or runtime + description: | + *Normally occurs at the end of the* :ref:`deprecation period ` + + new_features: + title: New features + deprecated: + title: Deprecated + +areas: + access_log: + title: access_log + attributes: + title: attributes + build: + title: build + composite: + title: composite + dns_resolver: + title: dns_resolver + dynamic_modules: + title: dynamic_modules + golang: + title: golang + http: + title: http + http2: + title: http2 + http_inspector: + title: http_inspector + jwt_authn: + title: jwt_authn + load_report: + title: load_report + logging: + title: logging + mysql_proxy: + title: mysql_proxy + oauth2: + title: oauth2 + quic: + title: quic + ratelimit: + title: ratelimit + rbac: + title: rbac + resource_monitors: + title: resource_monitors + router: + title: router + set_metadata_filter: + title: set_metadata_filter + spiffe_validator: + title: spiffe_validator + stat_sinks: + title: stat_sinks + stats: + title: stats + tcp_proxy: + title: tcp_proxy + tls: + title: tls + wasm: + title: wasm diff --git a/changelogs/current.yaml b/changelogs/current.yaml index e83b1cd5b5fd6..2066eb9f1d60b 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,283 +1 @@ date: Pending - -behavior_changes: -# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* - -minor_behavior_changes: -# *Changes that may cause incompatibilities for some users, but should not for most* -- area: router - change: | - The upstream transport failure reason (e.g. TLS certificate validation errors) is no longer included - in the HTTP response body sent to downstream clients. It remains available in access logs via - ``%UPSTREAM_TRANSPORT_FAILURE_REASON%``. This behavioral change can be temporarily reverted by setting - runtime guard ``envoy.reloadable_features.hide_transport_failure_reason_in_response_body`` to ``false``. - This is being changed because in many cases the upstream failure details are inappropriate to send to - the downstream client as it discloses too many internal details. -- area: golang - change: | - Reduced the per-cgo-call mutex acquisition on the Golang HTTP filter by making the - ``has_destroyed_`` flag a ``std::atomic``. CAPI methods whose only Envoy-side work is - Filter-owned or runs on the worker thread (``setHeader``, ``removeHeader``, ``setTrailer``, - ``removeTrailer``, ``addData``, ``injectData``, ``continueStatus``, ``sendLocalReply``, - ``setBufferHelper``, ``copyBuffer``, ``drainBuffer``, ``setUpstreamOverrideHost``, - ``clearRouteCache``, ``setDynamicMetadata``, ``setStringFilterState``) no longer take the - mutex, eliminating an uncontended atomic compare-and-swap pair on every such call. The - mutex is retained on the CAPI methods that inline-dereference Envoy-stream-owned objects - from off-thread (``getHeader``, ``copyHeaders``, ``copyTrailers``, ``getIntegerValue``, - ``setDrainConnectionUponCompletion``) where it serialises against ``onDestroy`` to prevent - the worker thread from freeing the underlying header map or ``StreamInfo`` mid-access, and - on the five methods that write to the per-request ``strValue`` scratch buffer - (``getStringValue``, ``getDynamicMetadata``, ``getStringFilterState``, ``getStringProperty``, - ``getSecret``). -- area: stats - change: | - Optimized prometheus stats endpoint. Users should see a roughly 30-40% latency improvement in calls to the endpoint - for cases where the scrape results in lots of cluster stats. - There should be no visible changes to users, or incompatibilities. -- area: tls - change: | - ``SslSocket`` now reports ``ECONNRESET`` as ``ConnectionReset`` by reading the system error - code from BoringSSL's error queue, matching ``RawBufferSocket`` behavior. When a connection - is closed with ``ConnectionCloseType::AbortReset``, ``SslSocket`` also skips the TLS - ``close_notify`` shutdown so the peer reliably observes a TCP RST instead of racing a - graceful close against the reset. - This behavioral change can be temporarily reverted by setting runtime guard - ``envoy.reloadable_features.ssl_socket_report_connection_reset`` to ``false``. -- area: http2 - change: | - The GOAWAY load shed point is fixed to use a graceful two-phase shutdown sequence, to avoid - risk to client traffic. This behavioral change can be temporarily reverted by setting runtime - guard ``envoy.reloadable_features.http2_fix_goaway_loadshed_point`` to ``false``. - -bug_fixes: -# *Changes expected to improve the state of the world and are unlikely to have negative effects* -- area: wasm - change: | - Fixed a bug where Envoy did not recreate the Wasm VM when only - :ref:`environment_variables ` - changed in ``vm_config``. The VM was previously reused from the cache because environment variables - were not included in the vm_key computation. -- area: load_report - change: | - Fixed a bug in load stats reporting where reports were dropped if only custom metrics or completed - requests were present in a reporting interval. This behavioral change can be reverted by setting - the runtime guard ``envoy.reloadable_features.report_load_for_non_zero_stats`` to ``false``. -- area: http - change: | - Fixed a bug where an upstream HTTP/2 or HTTP/3 ``RST_STREAM(NO_ERROR)`` received after a complete - response would cause the response to be discarded and replaced with an error. This behavior is - common with some gRPC clients and servers, but is often intermittent. This behavior can be - temporarily reverted by setting the runtime flag - ``envoy.reloadable_features.http_preserve_rst_no_error`` to ``false``. -- area: spiffe_validator - change: | - Fixed a bug where if an invalid custom SAN matcher was configured it would later lead to a crash - when validating a certificate. -- area: rbac - change: | - Fixed a bug in the :ref:`mTLS Authenticated ` - principal extension where if an invalid SAN matcher was configured, it would incorrectly match any - certificate in the allowed trust chain. Known bad configurations are an invalid ``OID`` when using - an ``OTHER_NAME``, or specifying a ``StringMatcher`` with an invalid configuration. -- area: build - change: | - Fixed ``Illegal ambiguous match`` error when building contrib targets with ``--config=aws-lc-fips`` - on aarch64 by restricting the ``using_aws_lc`` branch of ``SELECTED_CONTRIB_EXTENSIONS`` to - ``linux_x86_64``. Mirrors the approach taken by #32382 for ``boringssl_fips``. -- area: dynamic_modules - change: | - Fixed a crashing bug in the HTTP filter when a stream was already above the downstream write-buffer - high watermark at filter-chain construction time. Downstream watermark callback registration is - now deferred until the in-module filter has been constructed. -- area: dynamic_modules - change: | - Fixed a bug where the HTTP filter per-route configuration and the upstream HTTP TCP bridge - configuration did not handle the ``google.protobuf.Struct`` configuration message as the API - definition requires. Both factories now serialize the ``Struct`` to a JSON string and pass the - string to the dynamic module side as the configuration, matching the behavior already in place - for every other dynamic module extension factory. -- area: oauth2 - change: | - Fixed a crash in the OAuth2 filter where AES-CBC decryption of token cookies could spuriously - succeed (~1/256) when the configured HMAC secret did not match the secret used to encrypt the - cookie (for example after secret rotation, or when receiving legacy unencrypted tokens). The - resulting binary "plaintext" was written back into the ``Cookie:`` request header and tripped a - ``HeaderString`` validation assert. Such plaintexts are now rejected and the original cookie value - is preserved, matching the behavior already documented for the explicit decryption-failure case. -- area: dns_resolver - change: | - Fixed a memory leak in the Hickory DNS resolver where ``ActiveDnsQuery::cancel()`` - did not free the pending query state or decrement the ``pending_resolutions`` gauge. Cancelled - queries now release their shell object, Rust-side query box, and gauge tick synchronously, - matching the contract followed by the c-ares and Apple DNS resolvers. -- area: dns_resolver - change: | - Fixed a UAF in the Hickory DNS resolver where the dispatcher lambda posted by the - Rust-thread completion callback captured a raw pointer to the resolver. If the resolver was - destroyed before the dispatcher drained the lambda, dereferencing the pointer was undefined - behavior. The lambda now captures a ``std::weak_ptr`` and locks it before touching the resolver. -- area: build - change: | - Fixed ``Illegal ambiguous match`` error when building contrib targets with ``--config=boringssl-fips`` - on aarch64 by restricting the ``using_boringssl_fips`` branch of ``SELECTED_CONTRIB_EXTENSIONS`` to - ``linux_x86_64``. Mirrors the approach taken by #44661 for ``aws-lc``. -- area: dns_resolver - change: | - Fixed macOS build failure (#45061) for the Hickory DNS resolver by linking - Apple's ``SystemConfiguration`` framework. The framework is required by the - ``system-configuration`` crate that ``hickory-resolver`` pulls in via its - ``system-config`` feature. -- area: oauth2 - change: | - Fixed a bug where HMAC verification may exposure a timing side channel that leaks information of - HMAC secret validity. - -removed_config_or_runtime: -# *Normally occurs at the end of the* :ref:`deprecation period ` - -new_features: -- area: router - change: | - Added support for ``refreshRouteCluster`` on weighted cluster routes. When a filter calls - ``refreshRouteCluster()``, the weighted cluster entry will select a different cluster from the - configured pool, avoiding previously-tried clusters within the same request. Once all clusters - have been tried, the selection pool resets so that any cluster may be chosen again. This enables - filters to implement per-attempt cluster failover across weighted clusters without replacing the - entire route. -- area: tcp_proxy - change: | - Added :ref:`check_drain_close - ` to the TCP proxy - filter to close downstream connections with ``FlushWrite`` when the drain manager requests drain close during - downstream read or write handling. -- area: stat_sinks - change: | - Added :ref:`max_data_points_per_request - ` - configuration to the OpenTelemetry stat sink to chunk metric export requests. -- area: set_metadata_filter - change: | - Added :ref:`per-route configuration support ` - to the ``set_metadata`` HTTP filter. -- area: logging - change: | - Added ``%N`` as a custom spdlog pattern flag that emits the Envoy version string. It can be - used in the ``--log-format`` CLI flag or the bootstrap ``application_log_config.log_format`` - to include the running version in every log line, e.g. ``--log-format "[%N][%l] %v"``. -- area: dynamic_modules - change: | - Added ``envoy_dynamic_module_callback_is_validation_mode`` ABI callback that allows dynamic - modules to check if the server is running in config validation mode. -- area: dynamic_modules - change: | - Added ``envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes`` and - ``envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed`` ABI callbacks so - that a dynamic-module cluster's load balancer can read filter state set by an upstream HTTP - filter (or any other producer) when picking a host. The Rust SDK exposes these as - ``ClusterLbContext::get_filter_state_bytes`` and ``ClusterLbContext::get_filter_state_typed``. -- area: mcp_router - change: | - Added :ref:`lazy_initialization ` - option to the MCP router filter. When enabled, the ``initialize`` response is returned immediately - without contacting backends. Each backend is initialized on-demand when a request first routes to it, - avoiding slow or misbehaving backends from blocking client initialization. -- area: access_log - change: | - Supported the singleton stats scope in the :ref:`stats access logger `. -- area: stat_sinks - change: | - Added a new :ref:`WASM stats filter ` contrib extension - (``envoy.stat_sinks.wasm_filter``) that acts as programmable middleware between - the metrics snapshot and any inner stats sink. A user-supplied WASM plugin can: - filter metrics by index, inject global tags from node metadata - (``stats_filter_set_global_tags``), rename metrics (``stats_filter_set_name_overrides``), - inject synthetic counters/gauges (``stats_filter_inject_metrics``), and filter - histograms (``stats_filter_get_histograms``). This enables moving centralized - metric processing logic (tag enrichment, name rewriting, custom metric injection) - into the proxy itself. Configured via - :ref:`WasmFilterStatsSinkConfig `. -- area: router - change: | - Added :ref:`refresh_cluster_on_retry ` - to :ref:`retry policies ` so retry attempts can - refresh the route-selected upstream cluster before being sent. This enables cross-cluster - retries for dynamic cluster selection such as the :ref:`matcher cluster specifier - `. -- area: mysql_proxy - change: | - Added SSL termination support to the MySQL proxy filter with RSA-mediated ``caching_sha2_password`` - authentication. The filter can now terminate downstream TLS connections using the - :ref:`starttls transport socket ` - and transparently mediate MySQL 8.0+ ``caching_sha2_password`` full authentication by performing - RSA public key exchange on behalf of the client. Added a new - :ref:`downstream_ssl ` - config option with ``DISABLE``, ``REQUIRE``, and ``ALLOW`` modes. -- area: load_balancing - change: | - client_side_weighted_round_robin: implemented out-of-band ORCA load reporting via - server-streaming gRPC (``xds.service.orca.v3.OpenRcaService.StreamCoreMetrics``) when - :ref:`enable_oob_load_report - ` - is true. Cluster-scoped stats are emitted under the ``lb_orca_oob.`` prefix. -- area: quic - change: | - Added support for TLS session ticket resumption in QUIC using configured session ticket keys from - :ref:`session_ticket_keys `. - This enables faster reconnection across server instances by allowing clients to resume TLS sessions - without full handshakes. The feature is disabled by default and can be enabled by setting runtime guard - ``envoy.reloadable_features.quic_session_ticket_support`` to ``true``. -- area: composite - change: | - Added support for the - :ref:`inline matcher ` - in the composite HTTP filter. Now users could specify the matcher inline in the filter configuration - instead of using the :ref:`ExtensionWithMatcher - ` filter. -- area: resource_monitors - change: | - Overload manager fixed heap resource monitor now supports :ref:`max_heap_size_bytes_runtime - ` - for runtime-overridable max heap size (e.g. RTDS or ``/runtime_modify``). -- area: jwt_authn - change: | - Added :ref:`verification_status_header - ` - to the ``ExtractOnlyWithoutValidation`` requirement. When a JWT is present in the request but fails - signature verification, the named request header (default ``x-jwt-signature-verified``) is set to - ``false`` so downstream filters (RBAC, ext_authz) can distinguish forwarded-but-unverified claims - from validated ones. The header is not set on a successfully verified JWT or when no JWT is present. - This behavior can be reverted by setting the runtime guard - ``envoy.reloadable_features.jwt_authn_add_verification_status_header`` to ``false``. -- area: access_log - change: | - Added ``%UPSTREAM_SERVER_NAME%`` access log formatter returning the SNI from the established upstream - TLS connection. -- area: attributes - change: | - Added ``upstream.server_name`` CEL attribute returning the SNI from the established upstream - TLS connection. -- area: tls - change: | - Added substitution commands ``%DOWNSTREAM_TLS_GROUP%`` and ``%UPSTREAM_TLS_GROUP%``. The TLS group may be used - to discern if a TLS connection used a post quantum safe key exchange (e.g. X25519MLKEM768). -- area: http_inspector - change: | - Enabled Balsa parser for HTTP inspector by default. This behavior can be temporarily - reverted by setting the runtime guard ``envoy.reloadable_features.http_inspector_use_balsa_parser`` - to ``false``. This runtime guard will be removed in a future release of Envoy. -- area: http_inspector - change: | - Enabled Balsa parser for HTTP inspector by default. This behavior can be temporarily - reverted by setting the runtime guard ``envoy.reloadable_features.http_inspector_use_balsa_parser`` - to ``false``. This runtime guard will be removed in a future release of Envoy. -- area: ratelimit - change: | - Make namespace for storing rate limit service response metadata configurable. - -- area: tcp_proxy - change: | - Added ``envoy.reloadable_features.tcp_proxy_delay_route_selection`` to delay selecting a route until just before the upstream - connection is established. The selection moment depends on the value of - :ref:`upstream_connect_mode`. - -deprecated: diff --git a/changelogs/current/bug_fixes/build__fixed-illegal-ambiguous-match-error-2.rst b/changelogs/current/bug_fixes/build__fixed-illegal-ambiguous-match-error-2.rst new file mode 100644 index 0000000000000..2a83549827e39 --- /dev/null +++ b/changelogs/current/bug_fixes/build__fixed-illegal-ambiguous-match-error-2.rst @@ -0,0 +1,3 @@ +Fixed ``Illegal ambiguous match`` error when building contrib targets with ``--config=boringssl-fips`` +on aarch64 by restricting the ``using_boringssl_fips`` branch of ``SELECTED_CONTRIB_EXTENSIONS`` to +``linux_x86_64``. Mirrors the approach taken by #44661 for ``aws-lc``. diff --git a/changelogs/current/bug_fixes/build__fixed-illegal-ambiguous-match-error.rst b/changelogs/current/bug_fixes/build__fixed-illegal-ambiguous-match-error.rst new file mode 100644 index 0000000000000..80482772f0e2a --- /dev/null +++ b/changelogs/current/bug_fixes/build__fixed-illegal-ambiguous-match-error.rst @@ -0,0 +1,3 @@ +Fixed ``Illegal ambiguous match`` error when building contrib targets with ``--config=aws-lc-fips`` +on aarch64 by restricting the ``using_aws_lc`` branch of ``SELECTED_CONTRIB_EXTENSIONS`` to +``linux_x86_64``. Mirrors the approach taken by #32382 for ``boringssl_fips``. diff --git a/changelogs/current/bug_fixes/dns_resolver__fixed-a-memory-leak-in-the-hickory-dns.rst b/changelogs/current/bug_fixes/dns_resolver__fixed-a-memory-leak-in-the-hickory-dns.rst new file mode 100644 index 0000000000000..93a87960fb202 --- /dev/null +++ b/changelogs/current/bug_fixes/dns_resolver__fixed-a-memory-leak-in-the-hickory-dns.rst @@ -0,0 +1,4 @@ +Fixed a memory leak in the Hickory DNS resolver where ``ActiveDnsQuery::cancel()`` +did not free the pending query state or decrement the ``pending_resolutions`` gauge. Cancelled +queries now release their shell object, Rust-side query box, and gauge tick synchronously, +matching the contract followed by the c-ares and Apple DNS resolvers. diff --git a/changelogs/current/bug_fixes/dns_resolver__fixed-a-uaf-in-the-hickory-dns.rst b/changelogs/current/bug_fixes/dns_resolver__fixed-a-uaf-in-the-hickory-dns.rst new file mode 100644 index 0000000000000..ab69957ebe443 --- /dev/null +++ b/changelogs/current/bug_fixes/dns_resolver__fixed-a-uaf-in-the-hickory-dns.rst @@ -0,0 +1,4 @@ +Fixed a UAF in the Hickory DNS resolver where the dispatcher lambda posted by the +Rust-thread completion callback captured a raw pointer to the resolver. If the resolver was +destroyed before the dispatcher drained the lambda, dereferencing the pointer was undefined +behavior. The lambda now captures a ``std::weak_ptr`` and locks it before touching the resolver. diff --git a/changelogs/current/bug_fixes/dns_resolver__fixed-macos-build-failure-45061-for.rst b/changelogs/current/bug_fixes/dns_resolver__fixed-macos-build-failure-45061-for.rst new file mode 100644 index 0000000000000..977a097281ffa --- /dev/null +++ b/changelogs/current/bug_fixes/dns_resolver__fixed-macos-build-failure-45061-for.rst @@ -0,0 +1,4 @@ +Fixed macOS build failure (#45061) for the Hickory DNS resolver by linking +Apple's ``SystemConfiguration`` framework. The framework is required by the +``system-configuration`` crate that ``hickory-resolver`` pulls in via its +``system-config`` feature. diff --git a/changelogs/current/bug_fixes/dynamic_modules__fixed-a-bug-where-the-http-filter-per.rst b/changelogs/current/bug_fixes/dynamic_modules__fixed-a-bug-where-the-http-filter-per.rst new file mode 100644 index 0000000000000..48f56f0d0bfcd --- /dev/null +++ b/changelogs/current/bug_fixes/dynamic_modules__fixed-a-bug-where-the-http-filter-per.rst @@ -0,0 +1,5 @@ +Fixed a bug where the HTTP filter per-route configuration and the upstream HTTP TCP bridge +configuration did not handle the ``google.protobuf.Struct`` configuration message as the API +definition requires. Both factories now serialize the ``Struct`` to a JSON string and pass the +string to the dynamic module side as the configuration, matching the behavior already in place +for every other dynamic module extension factory. diff --git a/changelogs/current/bug_fixes/dynamic_modules__fixed-a-crashing-bug-in-the-http.rst b/changelogs/current/bug_fixes/dynamic_modules__fixed-a-crashing-bug-in-the-http.rst new file mode 100644 index 0000000000000..3f3d406d3f5a4 --- /dev/null +++ b/changelogs/current/bug_fixes/dynamic_modules__fixed-a-crashing-bug-in-the-http.rst @@ -0,0 +1,3 @@ +Fixed a crashing bug in the HTTP filter when a stream was already above the downstream write-buffer +high watermark at filter-chain construction time. Downstream watermark callback registration is +now deferred until the in-module filter has been constructed. diff --git a/changelogs/current/bug_fixes/http__fixed-rst-error-discard.rst b/changelogs/current/bug_fixes/http__fixed-rst-error-discard.rst new file mode 100644 index 0000000000000..0303f0e30229e --- /dev/null +++ b/changelogs/current/bug_fixes/http__fixed-rst-error-discard.rst @@ -0,0 +1,5 @@ +Fixed a bug where an upstream HTTP/2 or HTTP/3 ``RST_STREAM(NO_ERROR)`` received after a complete +response would cause the response to be discarded and replaced with an error. This behavior is +common with some gRPC clients and servers, but is often intermittent. This behavior can be +temporarily reverted by setting the runtime flag +``envoy.reloadable_features.http_preserve_rst_no_error`` to ``false``. diff --git a/changelogs/current/bug_fixes/load_report__fixed-a-bug-in-load-stats-reporting.rst b/changelogs/current/bug_fixes/load_report__fixed-a-bug-in-load-stats-reporting.rst new file mode 100644 index 0000000000000..d93aae1232056 --- /dev/null +++ b/changelogs/current/bug_fixes/load_report__fixed-a-bug-in-load-stats-reporting.rst @@ -0,0 +1,3 @@ +Fixed a bug in load stats reporting where reports were dropped if only custom metrics or completed +requests were present in a reporting interval. This behavioral change can be reverted by setting +the runtime guard ``envoy.reloadable_features.report_load_for_non_zero_stats`` to ``false``. diff --git a/changelogs/current/bug_fixes/oauth2__fixed-a-bug-where-hmac-verification.rst b/changelogs/current/bug_fixes/oauth2__fixed-a-bug-where-hmac-verification.rst new file mode 100644 index 0000000000000..9d576b0108ba1 --- /dev/null +++ b/changelogs/current/bug_fixes/oauth2__fixed-a-bug-where-hmac-verification.rst @@ -0,0 +1,2 @@ +Fixed a bug where HMAC verification may exposure a timing side channel that leaks information of +HMAC secret validity. diff --git a/changelogs/current/bug_fixes/oauth2__fixed-a-crash-in-the-oauth2-filter.rst b/changelogs/current/bug_fixes/oauth2__fixed-a-crash-in-the-oauth2-filter.rst new file mode 100644 index 0000000000000..2ec43ae8e7878 --- /dev/null +++ b/changelogs/current/bug_fixes/oauth2__fixed-a-crash-in-the-oauth2-filter.rst @@ -0,0 +1,6 @@ +Fixed a crash in the OAuth2 filter where AES-CBC decryption of token cookies could spuriously +succeed (~1/256) when the configured HMAC secret did not match the secret used to encrypt the +cookie (for example after secret rotation, or when receiving legacy unencrypted tokens). The +resulting binary "plaintext" was written back into the ``Cookie:`` request header and tripped a +``HeaderString`` validation assert. Such plaintexts are now rejected and the original cookie value +is preserved, matching the behavior already documented for the explicit decryption-failure case. diff --git a/changelogs/current/bug_fixes/rbac__fixed-a-bug-in-the-ref-mtls.rst b/changelogs/current/bug_fixes/rbac__fixed-a-bug-in-the-ref-mtls.rst new file mode 100644 index 0000000000000..9170caf94e532 --- /dev/null +++ b/changelogs/current/bug_fixes/rbac__fixed-a-bug-in-the-ref-mtls.rst @@ -0,0 +1,4 @@ +Fixed a bug in the :ref:`mTLS Authenticated ` +principal extension where if an invalid SAN matcher was configured, it would incorrectly match any +certificate in the allowed trust chain. Known bad configurations are an invalid ``OID`` when using +an ``OTHER_NAME``, or specifying a ``StringMatcher`` with an invalid configuration. diff --git a/changelogs/current/bug_fixes/spiffe_validator__fixed-a-bug-where-if-an-invalid-custom.rst b/changelogs/current/bug_fixes/spiffe_validator__fixed-a-bug-where-if-an-invalid-custom.rst new file mode 100644 index 0000000000000..31d2f70df9358 --- /dev/null +++ b/changelogs/current/bug_fixes/spiffe_validator__fixed-a-bug-where-if-an-invalid-custom.rst @@ -0,0 +1,2 @@ +Fixed a bug where if an invalid custom SAN matcher was configured it would later lead to a crash +when validating a certificate. diff --git a/changelogs/current/bug_fixes/wasm__fixed-a-bug-where-envoy-did-not.rst b/changelogs/current/bug_fixes/wasm__fixed-a-bug-where-envoy-did-not.rst new file mode 100644 index 0000000000000..b69984d380156 --- /dev/null +++ b/changelogs/current/bug_fixes/wasm__fixed-a-bug-where-envoy-did-not.rst @@ -0,0 +1,4 @@ +Fixed a bug where Envoy did not recreate the Wasm VM when only +:ref:`environment_variables ` +changed in ``vm_config``. The VM was previously reused from the cache because environment variables +were not included in the vm_key computation. diff --git a/changelogs/current/minor_behavior_changes/golang__reduced-the-per-cgo-call-mutex.rst b/changelogs/current/minor_behavior_changes/golang__reduced-the-per-cgo-call-mutex.rst new file mode 100644 index 0000000000000..55c0ac7b12101 --- /dev/null +++ b/changelogs/current/minor_behavior_changes/golang__reduced-the-per-cgo-call-mutex.rst @@ -0,0 +1,14 @@ +Reduced the per-cgo-call mutex acquisition on the Golang HTTP filter by making the +``has_destroyed_`` flag a ``std::atomic``. CAPI methods whose only Envoy-side work is +Filter-owned or runs on the worker thread (``setHeader``, ``removeHeader``, ``setTrailer``, +``removeTrailer``, ``addData``, ``injectData``, ``continueStatus``, ``sendLocalReply``, +``setBufferHelper``, ``copyBuffer``, ``drainBuffer``, ``setUpstreamOverrideHost``, +``clearRouteCache``, ``setDynamicMetadata``, ``setStringFilterState``) no longer take the +mutex, eliminating an uncontended atomic compare-and-swap pair on every such call. The +mutex is retained on the CAPI methods that inline-dereference Envoy-stream-owned objects +from off-thread (``getHeader``, ``copyHeaders``, ``copyTrailers``, ``getIntegerValue``, +``setDrainConnectionUponCompletion``) where it serialises against ``onDestroy`` to prevent +the worker thread from freeing the underlying header map or ``StreamInfo`` mid-access, and +on the five methods that write to the per-request ``strValue`` scratch buffer +(``getStringValue``, ``getDynamicMetadata``, ``getStringFilterState``, ``getStringProperty``, +``getSecret``). diff --git a/changelogs/current/minor_behavior_changes/http2__the-goaway-load-shed-point-is-fixed-to.rst b/changelogs/current/minor_behavior_changes/http2__the-goaway-load-shed-point-is-fixed-to.rst new file mode 100644 index 0000000000000..0989d3785d512 --- /dev/null +++ b/changelogs/current/minor_behavior_changes/http2__the-goaway-load-shed-point-is-fixed-to.rst @@ -0,0 +1,3 @@ +The GOAWAY load shed point is fixed to use a graceful two-phase shutdown sequence, to avoid +risk to client traffic. This behavioral change can be temporarily reverted by setting runtime +guard ``envoy.reloadable_features.http2_fix_goaway_loadshed_point`` to ``false``. diff --git a/changelogs/current/minor_behavior_changes/router__the-upstream-transport-failure-reason.rst b/changelogs/current/minor_behavior_changes/router__the-upstream-transport-failure-reason.rst new file mode 100644 index 0000000000000..2496b79fecc1a --- /dev/null +++ b/changelogs/current/minor_behavior_changes/router__the-upstream-transport-failure-reason.rst @@ -0,0 +1,6 @@ +The upstream transport failure reason (e.g. TLS certificate validation errors) is no longer included +in the HTTP response body sent to downstream clients. It remains available in access logs via +``%UPSTREAM_TRANSPORT_FAILURE_REASON%``. This behavioral change can be temporarily reverted by setting +runtime guard ``envoy.reloadable_features.hide_transport_failure_reason_in_response_body`` to ``false``. +This is being changed because in many cases the upstream failure details are inappropriate to send to +the downstream client as it discloses too many internal details. diff --git a/changelogs/current/minor_behavior_changes/stats__optimized-prometheus-stats-endpoint.rst b/changelogs/current/minor_behavior_changes/stats__optimized-prometheus-stats-endpoint.rst new file mode 100644 index 0000000000000..14e702cfeb074 --- /dev/null +++ b/changelogs/current/minor_behavior_changes/stats__optimized-prometheus-stats-endpoint.rst @@ -0,0 +1,3 @@ +Optimized prometheus stats endpoint. Users should see a roughly 30-40% latency improvement in calls to the endpoint +for cases where the scrape results in lots of cluster stats. +There should be no visible changes to users, or incompatibilities. diff --git a/changelogs/current/minor_behavior_changes/tls__sslsocket-now-reports-econnreset-as.rst b/changelogs/current/minor_behavior_changes/tls__sslsocket-now-reports-econnreset-as.rst new file mode 100644 index 0000000000000..5117b52422a38 --- /dev/null +++ b/changelogs/current/minor_behavior_changes/tls__sslsocket-now-reports-econnreset-as.rst @@ -0,0 +1,7 @@ +``SslSocket`` now reports ``ECONNRESET`` as ``ConnectionReset`` by reading the system error +code from BoringSSL's error queue, matching ``RawBufferSocket`` behavior. When a connection +is closed with ``ConnectionCloseType::AbortReset``, ``SslSocket`` also skips the TLS +``close_notify`` shutdown so the peer reliably observes a TCP RST instead of racing a +graceful close against the reset. +This behavioral change can be temporarily reverted by setting runtime guard +``envoy.reloadable_features.ssl_socket_report_connection_reset`` to ``false``. diff --git a/changelogs/current/new_features/access_log__added-upstream-server-name-access-log.rst b/changelogs/current/new_features/access_log__added-upstream-server-name-access-log.rst new file mode 100644 index 0000000000000..b1b5f6f09fb23 --- /dev/null +++ b/changelogs/current/new_features/access_log__added-upstream-server-name-access-log.rst @@ -0,0 +1,2 @@ +Added ``%UPSTREAM_SERVER_NAME%`` access log formatter returning the SNI from the established upstream +TLS connection. diff --git a/changelogs/current/new_features/access_log__supported-the-singleton-stats-scope-in.rst b/changelogs/current/new_features/access_log__supported-the-singleton-stats-scope-in.rst new file mode 100644 index 0000000000000..c2fc5e4129ce0 --- /dev/null +++ b/changelogs/current/new_features/access_log__supported-the-singleton-stats-scope-in.rst @@ -0,0 +1 @@ +Supported the singleton stats scope in the :ref:`stats access logger `. diff --git a/changelogs/current/new_features/attributes__added-upstream-server-name-cel.rst b/changelogs/current/new_features/attributes__added-upstream-server-name-cel.rst new file mode 100644 index 0000000000000..aa6f34411afce --- /dev/null +++ b/changelogs/current/new_features/attributes__added-upstream-server-name-cel.rst @@ -0,0 +1,2 @@ +Added ``upstream.server_name`` CEL attribute returning the SNI from the established upstream +TLS connection. diff --git a/changelogs/current/new_features/composite__added-support-for-the-ref-inline.rst b/changelogs/current/new_features/composite__added-support-for-the-ref-inline.rst new file mode 100644 index 0000000000000..bb1afd6e8ad8c --- /dev/null +++ b/changelogs/current/new_features/composite__added-support-for-the-ref-inline.rst @@ -0,0 +1,5 @@ +Added support for the +:ref:`inline matcher ` +in the composite HTTP filter. Now users could specify the matcher inline in the filter configuration +instead of using the :ref:`ExtensionWithMatcher +` filter. diff --git a/changelogs/current/new_features/dynamic_modules__added-envoy-dynamic-module-callback-is.rst b/changelogs/current/new_features/dynamic_modules__added-envoy-dynamic-module-callback-is.rst new file mode 100644 index 0000000000000..5913210e688c9 --- /dev/null +++ b/changelogs/current/new_features/dynamic_modules__added-envoy-dynamic-module-callback-is.rst @@ -0,0 +1,2 @@ +Added ``envoy_dynamic_module_callback_is_validation_mode`` ABI callback that allows dynamic +modules to check if the server is running in config validation mode. diff --git a/changelogs/current/new_features/dynamic_modules__added-envoy-dynamic-module-callback.rst b/changelogs/current/new_features/dynamic_modules__added-envoy-dynamic-module-callback.rst new file mode 100644 index 0000000000000..cfb200eb78616 --- /dev/null +++ b/changelogs/current/new_features/dynamic_modules__added-envoy-dynamic-module-callback.rst @@ -0,0 +1,5 @@ +Added ``envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_bytes`` and +``envoy_dynamic_module_callback_cluster_lb_context_get_filter_state_typed`` ABI callbacks so +that a dynamic-module cluster's load balancer can read filter state set by an upstream HTTP +filter (or any other producer) when picking a host. The Rust SDK exposes these as +``ClusterLbContext::get_filter_state_bytes`` and ``ClusterLbContext::get_filter_state_typed``. diff --git a/changelogs/current/new_features/http_inspector__enabled-balsa-parser-for-http-2.rst b/changelogs/current/new_features/http_inspector__enabled-balsa-parser-for-http-2.rst new file mode 100644 index 0000000000000..ac653bf52d490 --- /dev/null +++ b/changelogs/current/new_features/http_inspector__enabled-balsa-parser-for-http-2.rst @@ -0,0 +1,3 @@ +Enabled Balsa parser for HTTP inspector by default. This behavior can be temporarily +reverted by setting the runtime guard ``envoy.reloadable_features.http_inspector_use_balsa_parser`` +to ``false``. This runtime guard will be removed in a future release of Envoy. diff --git a/changelogs/current/new_features/http_inspector__enabled-balsa-parser-for-http.rst b/changelogs/current/new_features/http_inspector__enabled-balsa-parser-for-http.rst new file mode 100644 index 0000000000000..ac653bf52d490 --- /dev/null +++ b/changelogs/current/new_features/http_inspector__enabled-balsa-parser-for-http.rst @@ -0,0 +1,3 @@ +Enabled Balsa parser for HTTP inspector by default. This behavior can be temporarily +reverted by setting the runtime guard ``envoy.reloadable_features.http_inspector_use_balsa_parser`` +to ``false``. This runtime guard will be removed in a future release of Envoy. diff --git a/changelogs/current/new_features/jwt_authn__added-ref-verification-status-header.rst b/changelogs/current/new_features/jwt_authn__added-ref-verification-status-header.rst new file mode 100644 index 0000000000000..c4ca09381e915 --- /dev/null +++ b/changelogs/current/new_features/jwt_authn__added-ref-verification-status-header.rst @@ -0,0 +1,8 @@ +Added :ref:`verification_status_header +` +to the ``ExtractOnlyWithoutValidation`` requirement. When a JWT is present in the request but fails +signature verification, the named request header (default ``x-jwt-signature-verified``) is set to +``false`` so downstream filters (RBAC, ext_authz) can distinguish forwarded-but-unverified claims +from validated ones. The header is not set on a successfully verified JWT or when no JWT is present. +This behavior can be reverted by setting the runtime guard +``envoy.reloadable_features.jwt_authn_add_verification_status_header`` to ``false``. diff --git a/changelogs/current/new_features/load_balancing__out-of-band-orca-reporting.rst b/changelogs/current/new_features/load_balancing__out-of-band-orca-reporting.rst new file mode 100644 index 0000000000000..2df50012731dc --- /dev/null +++ b/changelogs/current/new_features/load_balancing__out-of-band-orca-reporting.rst @@ -0,0 +1,5 @@ +client_side_weighted_round_robin: implemented out-of-band ORCA load reporting via +server-streaming gRPC (``xds.service.orca.v3.OpenRcaService.StreamCoreMetrics``) when +:ref:`enable_oob_load_report +` +is true. Cluster-scoped stats are emitted under the ``lb_orca_oob.`` prefix. diff --git a/changelogs/current/new_features/logging__added-n-as-a-custom-spdlog-pattern.rst b/changelogs/current/new_features/logging__added-n-as-a-custom-spdlog-pattern.rst new file mode 100644 index 0000000000000..5f4942523e69f --- /dev/null +++ b/changelogs/current/new_features/logging__added-n-as-a-custom-spdlog-pattern.rst @@ -0,0 +1,3 @@ +Added ``%N`` as a custom spdlog pattern flag that emits the Envoy version string. It can be +used in the ``--log-format`` CLI flag or the bootstrap ``application_log_config.log_format`` +to include the running version in every log line, e.g. ``--log-format "[%N][%l] %v"``. diff --git a/changelogs/current/new_features/mcp_router__lazy_initialization.rst b/changelogs/current/new_features/mcp_router__lazy_initialization.rst new file mode 100644 index 0000000000000..eec647f86a48b --- /dev/null +++ b/changelogs/current/new_features/mcp_router__lazy_initialization.rst @@ -0,0 +1,4 @@ +Added :ref:`lazy_initialization ` +option to the MCP router filter. When enabled, the ``initialize`` response is returned immediately +without contacting backends. Each backend is initialized on-demand when a request first routes to it, +avoiding slow or misbehaving backends from blocking client initialization. diff --git a/changelogs/current/new_features/mysql_proxy__added-ssl-termination-support-to-the.rst b/changelogs/current/new_features/mysql_proxy__added-ssl-termination-support-to-the.rst new file mode 100644 index 0000000000000..fd2ee206f9690 --- /dev/null +++ b/changelogs/current/new_features/mysql_proxy__added-ssl-termination-support-to-the.rst @@ -0,0 +1,7 @@ +Added SSL termination support to the MySQL proxy filter with RSA-mediated ``caching_sha2_password`` +authentication. The filter can now terminate downstream TLS connections using the +:ref:`starttls transport socket ` +and transparently mediate MySQL 8.0+ ``caching_sha2_password`` full authentication by performing +RSA public key exchange on behalf of the client. Added a new +:ref:`downstream_ssl ` +config option with ``DISABLE``, ``REQUIRE``, and ``ALLOW`` modes. diff --git a/changelogs/current/new_features/quic__added-support-for-tls-session-ticket.rst b/changelogs/current/new_features/quic__added-support-for-tls-session-ticket.rst new file mode 100644 index 0000000000000..a197778be5955 --- /dev/null +++ b/changelogs/current/new_features/quic__added-support-for-tls-session-ticket.rst @@ -0,0 +1,5 @@ +Added support for TLS session ticket resumption in QUIC using configured session ticket keys from +:ref:`session_ticket_keys `. +This enables faster reconnection across server instances by allowing clients to resume TLS sessions +without full handshakes. The feature is disabled by default and can be enabled by setting runtime guard +``envoy.reloadable_features.quic_session_ticket_support`` to ``true``. diff --git a/changelogs/current/new_features/ratelimit__make-namespace-for-storing-rate-limit.rst b/changelogs/current/new_features/ratelimit__make-namespace-for-storing-rate-limit.rst new file mode 100644 index 0000000000000..2347c0f11f22c --- /dev/null +++ b/changelogs/current/new_features/ratelimit__make-namespace-for-storing-rate-limit.rst @@ -0,0 +1 @@ +Make namespace for storing rate limit service response metadata configurable. diff --git a/changelogs/current/new_features/resource_monitors__overload-manager-fixed-heap-resource.rst b/changelogs/current/new_features/resource_monitors__overload-manager-fixed-heap-resource.rst new file mode 100644 index 0000000000000..839bca20991ff --- /dev/null +++ b/changelogs/current/new_features/resource_monitors__overload-manager-fixed-heap-resource.rst @@ -0,0 +1,3 @@ +Overload manager fixed heap resource monitor now supports :ref:`max_heap_size_bytes_runtime +` +for runtime-overridable max heap size (e.g. RTDS or ``/runtime_modify``). diff --git a/changelogs/current/new_features/router__added-ref-refresh-cluster-on-retry.rst b/changelogs/current/new_features/router__added-ref-refresh-cluster-on-retry.rst new file mode 100644 index 0000000000000..5ab111f33a52c --- /dev/null +++ b/changelogs/current/new_features/router__added-ref-refresh-cluster-on-retry.rst @@ -0,0 +1,5 @@ +Added :ref:`refresh_cluster_on_retry ` +to :ref:`retry policies ` so retry attempts can +refresh the route-selected upstream cluster before being sent. This enables cross-cluster +retries for dynamic cluster selection such as the :ref:`matcher cluster specifier +`. diff --git a/changelogs/current/new_features/router__added-support-for-refreshroutecluster.rst b/changelogs/current/new_features/router__added-support-for-refreshroutecluster.rst new file mode 100644 index 0000000000000..2598f32e8bebf --- /dev/null +++ b/changelogs/current/new_features/router__added-support-for-refreshroutecluster.rst @@ -0,0 +1,6 @@ +Added support for ``refreshRouteCluster`` on weighted cluster routes. When a filter calls +``refreshRouteCluster()``, the weighted cluster entry will select a different cluster from the +configured pool, avoiding previously-tried clusters within the same request. Once all clusters +have been tried, the selection pool resets so that any cluster may be chosen again. This enables +filters to implement per-attempt cluster failover across weighted clusters without replacing the +entire route. diff --git a/changelogs/current/new_features/set_metadata_filter__added-ref-per-route-configuration.rst b/changelogs/current/new_features/set_metadata_filter__added-ref-per-route-configuration.rst new file mode 100644 index 0000000000000..d4ac50941f6b5 --- /dev/null +++ b/changelogs/current/new_features/set_metadata_filter__added-ref-per-route-configuration.rst @@ -0,0 +1,2 @@ +Added :ref:`per-route configuration support ` +to the ``set_metadata`` HTTP filter. diff --git a/changelogs/current/new_features/stat_sinks__added-a-new-ref-wasm-stats-filter.rst b/changelogs/current/new_features/stat_sinks__added-a-new-ref-wasm-stats-filter.rst new file mode 100644 index 0000000000000..0b9cb12c51dd5 --- /dev/null +++ b/changelogs/current/new_features/stat_sinks__added-a-new-ref-wasm-stats-filter.rst @@ -0,0 +1,10 @@ +Added a new :ref:`WASM stats filter ` contrib extension +(``envoy.stat_sinks.wasm_filter``) that acts as programmable middleware between +the metrics snapshot and any inner stats sink. A user-supplied WASM plugin can: +filter metrics by index, inject global tags from node metadata +(``stats_filter_set_global_tags``), rename metrics (``stats_filter_set_name_overrides``), +inject synthetic counters/gauges (``stats_filter_inject_metrics``), and filter +histograms (``stats_filter_get_histograms``). This enables moving centralized +metric processing logic (tag enrichment, name rewriting, custom metric injection) +into the proxy itself. Configured via +:ref:`WasmFilterStatsSinkConfig `. diff --git a/changelogs/current/new_features/stat_sinks__added-ref-max-data-points-per-request.rst b/changelogs/current/new_features/stat_sinks__added-ref-max-data-points-per-request.rst new file mode 100644 index 0000000000000..0eed514544b06 --- /dev/null +++ b/changelogs/current/new_features/stat_sinks__added-ref-max-data-points-per-request.rst @@ -0,0 +1,3 @@ +Added :ref:`max_data_points_per_request +` +configuration to the OpenTelemetry stat sink to chunk metric export requests. diff --git a/changelogs/current/new_features/tcp_proxy__added-envoy-reloadable-features-tcp.rst b/changelogs/current/new_features/tcp_proxy__added-envoy-reloadable-features-tcp.rst new file mode 100644 index 0000000000000..aea6ecf8b1ade --- /dev/null +++ b/changelogs/current/new_features/tcp_proxy__added-envoy-reloadable-features-tcp.rst @@ -0,0 +1,3 @@ +Added ``envoy.reloadable_features.tcp_proxy_delay_route_selection`` to delay selecting a route until just before the upstream +connection is established. The selection moment depends on the value of +:ref:`upstream_connect_mode`. diff --git a/changelogs/current/new_features/tcp_proxy__added-ref-check-drain-close-envoy-v3.rst b/changelogs/current/new_features/tcp_proxy__added-ref-check-drain-close-envoy-v3.rst new file mode 100644 index 0000000000000..a895800355700 --- /dev/null +++ b/changelogs/current/new_features/tcp_proxy__added-ref-check-drain-close-envoy-v3.rst @@ -0,0 +1,4 @@ +Added :ref:`check_drain_close +` to the TCP proxy +filter to close downstream connections with ``FlushWrite`` when the drain manager requests drain close during +downstream read or write handling. diff --git a/changelogs/current/new_features/tls__added-substitution-commands-downstream.rst b/changelogs/current/new_features/tls__added-substitution-commands-downstream.rst new file mode 100644 index 0000000000000..707d2a8266b52 --- /dev/null +++ b/changelogs/current/new_features/tls__added-substitution-commands-downstream.rst @@ -0,0 +1,2 @@ +Added substitution commands ``%DOWNSTREAM_TLS_GROUP%`` and ``%UPSTREAM_TLS_GROUP%``. The TLS group may be used +to discern if a TLS connection used a post quantum safe key exchange (e.g. X25519MLKEM768). diff --git a/changelogs/sections.yaml b/changelogs/sections.yaml deleted file mode 100644 index 0cfe73b1478f8..0000000000000 --- a/changelogs/sections.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# NB: you cannot remove sections from here without breaking old changelogs -# also, adding a section here requires an update to `envoy.base.utils.typing` -changes: - title: Changes -behavior_changes: - title: Incompatible behavior changes - description: | - *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* -minor_behavior_changes: - title: Minor behavior changes - description: | - *Changes that may cause incompatibilities for some users, but should not for most* -bug_fixes: - title: Bug fixes - description: | - *Changes expected to improve the state of the world and are unlikely to have negative effects* -removed_config_or_runtime: - title: Removed config or runtime - description: | - *Normally occurs at the end of the* :ref:`deprecation period ` -new_features: - title: New features -deprecated: - title: Deprecated diff --git a/docs/BUILD b/docs/BUILD index ceaa9d1367a09..f8961bcbe6221 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -193,7 +193,7 @@ genrule( "//tools:generate_version_histories", "@envoy//:VERSION.txt", "@envoy//changelogs", - "@envoy//changelogs:sections.yaml", + "@envoy//changelogs:changelogs.yaml", ], ) diff --git a/docs/conf.py b/docs/conf.py index 40099cb9d648a..e5981a0f9ebeb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -145,7 +145,7 @@ def _config(key): 'sphinx.ext.extlinks', 'sphinx.ext.ifconfig', 'sphinx.ext.intersphinx', - 'envoy.docs.sphinx_runner.sphinx_tabs.tabs', + 'sphinx_tabs.tabs', 'sphinx_copybutton', 'envoy.docs.sphinx_runner.ext.validating_code_block', 'sphinxext.rediraffe', diff --git a/docs/tools/python/BUILD b/docs/tools/python/BUILD index e69de29bb2d1d..a8583d6748ffa 100644 --- a/docs/tools/python/BUILD +++ b/docs/tools/python/BUILD @@ -0,0 +1,18 @@ +load("@rules_python//python:pip.bzl", "compile_pip_requirements") + +licenses(["notice"]) # Apache 2 + +exports_files([ + "entry_point.py", +]) + +compile_pip_requirements( + name = "requirements", + src = "requirements.in", + extra_args = [ + "--allow-unsafe", + "--generate-hashes", + "--reuse-hashes", + "--resolver=backtracking", + ], +) diff --git a/docs/tools/python/requirements.in b/docs/tools/python/requirements.in index 72afa3cd25d18..d578e4c0282b6 100644 --- a/docs/tools/python/requirements.in +++ b/docs/tools/python/requirements.in @@ -1,5 +1,5 @@ # Docs-specific dependencies -envoy.docs.sphinx_runner>=0.2.9 +envoy.docs.sphinx_runner>=0.3.2 sphinx>=8.1.3,<9 sphinxcontrib-applehelp>=1.0.8 sphinxcontrib-devhelp>=1.0.6 @@ -11,11 +11,9 @@ sphinx-rtd-theme>=3.0.2 # Shared dependencies (also in main workspace) aio.run.runner -envoy.base.utils>=0.5.10 -envoy.code.check>=0.5.12 +envoy.base.utils>=0.6.4 +envoy.code.check>=0.6.4 frozendict>=2.3.7 jinja2 packaging -pygments>=2.20.0 pyyaml -uvloop==0.20.0 diff --git a/docs/tools/python/requirements.txt b/docs/tools/python/requirements.txt index 5a29366eaab31..f3abadb769e71 100644 --- a/docs/tools/python/requirements.txt +++ b/docs/tools/python/requirements.txt @@ -2,37 +2,39 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --generate-hashes requirements.in +# bazel run //tools/python:requirements.update # -abstracts==0.0.12 \ - --hash=sha256:acc01ff56c8a05fb88150dff62e295f9071fc33388c42f1dfc2787a8d1c755ff +abstracts==0.2.0 \ + --hash=sha256:1dd6df509a67cae1722c0dd9f58cdd6dee6e42f45bf0d7197e159b2e4c5e98e9 \ + --hash=sha256:995f326ab4408a6652a346439cb33169f48e66f25515c6baf9fc47f1a3e4652a # via # aio-api-github # aio-core + # aio-run-checker # aio-run-runner # envoy-base-utils # envoy-code-check -aio-api-github==0.2.10 \ - --hash=sha256:2563ad2cd219352a7933b78190c17ab460b503b437bec348a738c0f804adf45f \ - --hash=sha256:75e17073e9e03386d719d1af355f599c16d68345992eaa97aebfa6159b07ad02 +aio-api-github==0.3.1 \ + --hash=sha256:04d2e3fb4fcb55927b75f9a3cefae7673cefabd2387dd2416848ff19326d719c \ + --hash=sha256:d083ca09e5577d0c4217138e45b18ea63d57d0cbb030eb336cc2a979f8b244b4 # via envoy-base-utils -aio-core==0.10.5 \ - --hash=sha256:ba512132df47514a15930b89835ba1ef13522b7e31ec83a3b0622b7431f3f42e \ - --hash=sha256:d8486a0115ade8e0362783b8e7cd988368766d0d911c62cb73b716bfe1f50a92 +aio-core==0.11.1 \ + --hash=sha256:03f1202029655c68d5d41c2fa1d18e344d61515b71080f770f8cf6f99fbc5f5a \ + --hash=sha256:76608a1dad3985866c1b3ffc81e0d08bd1a740e8e72125bfbf46d1ee6fc3abfe # via # aio-api-github # aio-run-runner # envoy-base-utils # envoy-code-check -aio-run-checker==0.5.8 \ - --hash=sha256:9bf5e20773f83113b2eee39277f0ec3e014aeecfd33deb44103b0ed2482d3ff5 \ - --hash=sha256:b5fcb91651da986b13eefd340a45b402100a2967e1eb1a1b941ccfbc36fe0eda +aio-run-checker==0.6.1 \ + --hash=sha256:0c929c3534600b5f163de177892841ed36267472a8d0609f550d717a523f28fd \ + --hash=sha256:17f8ccf7976406adc062c5042888dab9507c5332bb00b697c3bd48e56a1ffee0 # via envoy-code-check -aio-run-runner==0.3.4 \ - --hash=sha256:3f7bdb91c9a7a60d547d18c620ce2444b4e81d6e6a64a2e1ef465e54db8708bf \ - --hash=sha256:ffa5693d6452b9fc07674cbea665825959d3ea5adca4e492998d242dadf3a773 +aio-run-runner==0.4.1 \ + --hash=sha256:03c2978e4c7e1d02a55e25b0c081603d7900387bcce4fe80b8e8af8f3284e05b \ + --hash=sha256:43c80688c50ba3a528d88635fee5653b9f0f40fc4a036e1aeaddf8ada3794d2e # via - # -r requirements.in + # -r tools/python/requirements.in # aio-run-checker # envoy-base-utils # envoy-docs-sphinx-runner @@ -451,25 +453,28 @@ docutils==0.21.2 \ # envoy-docs-sphinx-runner # sphinx # sphinx-rtd-theme -envoy-base-utils==0.5.11 \ - --hash=sha256:008ebe43ecbde736e8033a34242659ff784caf50cc3301b1d3214899e31fa83d \ - --hash=sha256:be942364a42d92439281700486cf5040bdbcf3786a4e1dbcc9ea0af033a7e47e + # sphinx-tabs +envoy-base-utils==0.6.4 \ + --hash=sha256:5c576437d658ec84f04ff1f587b758208ca1544afc86ac1889c0931ac231a85b \ + --hash=sha256:dcc33a8a1d2d4db2bf90514cb2db256c0c25666aa2619de2cfac0a710dc3dd3b # via - # -r requirements.in + # -r tools/python/requirements.in # envoy-code-check # envoy-docs-sphinx-runner -envoy-code-check==0.5.14 \ - --hash=sha256:56ed152ba633b8d6846509424a4dc94996ed0eb3d656495ec2b32a94144ed904 \ - --hash=sha256:83cce34e3c2ee3d1b9a375922ee0d08fedbd8d3621f72d3ee55dd0a7d26a0e0e - # via -r requirements.in -envoy-docs-sphinx-runner==0.2.12 \ - --hash=sha256:b07e753f4a4694ff9786c943647702acd75c6bf9a7044e166ce4cf6d58a44af2 \ - --hash=sha256:ebe1620f19816edcc2fbf3908617fd15b6492b87e4276a2e76be45f3c7b50f9c - # via -r requirements.in +envoy-code-check==0.6.4 \ + --hash=sha256:6e5b82d9a6726a875bf07676ab42951734c8b7442cd452bcc961a5fe2182296c \ + --hash=sha256:a93ab843e4f8a78ea0a2f584132928b24d6b335534b7f9c97ef64b1d51c783ab + # via -r tools/python/requirements.in +envoy-docs-sphinx-runner==0.3.2 \ + --hash=sha256:76ca59da2237b6aabc453ed067808aa7d603913e1de0ad8b2db3dddee99190be \ + --hash=sha256:fb66b380809bf910e45c904477510c0ccb2c808f65ff7a8794d9c65ab3d3bbae + # via -r tools/python/requirements.in flake8==7.3.0 \ --hash=sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e \ --hash=sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872 - # via envoy-code-check + # via + # envoy-code-check + # pep8-naming frozendict==2.4.7 \ --hash=sha256:05dd27415f913cd11649009f53d97eb565ce7b76787d7869c4733738c10e8d27 \ --hash=sha256:0664092614d2b9d0aa404731f33ad5459a54fe8dab9d1fd45aa714fa6de4d0ef \ @@ -563,7 +568,7 @@ frozendict==2.4.7 \ --hash=sha256:ffd1a9f9babec9119712e76a39397d8aa0d72ef8c4ccad917c6175d7e7f81b74 \ --hash=sha256:fff8584e3bbdc5c1713cd016fbf4b88babfffd4e5e89b39020f2a208dd24c900 # via - # -r requirements.in + # -r tools/python/requirements.in # aio-run-runner # envoy-base-utils frozenlist==1.8.0 \ @@ -722,7 +727,7 @@ jinja2==3.1.6 \ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 # via - # -r requirements.in + # -r tools/python/requirements.in # envoy-base-utils # sphinx markupsafe==3.0.3 \ @@ -968,6 +973,7 @@ multidict==6.7.1 \ --hash=sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba \ --hash=sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19 # via + # aio-api-github # aiohttp # yarl orjson==3.11.7 \ @@ -1050,7 +1056,7 @@ packaging==26.1 \ --hash=sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f \ --hash=sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de # via - # -r requirements.in + # -r tools/python/requirements.in # aio-api-github # envoy-base-utils # envoy-code-check @@ -1060,6 +1066,10 @@ pathspec==1.0.4 \ --hash=sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645 \ --hash=sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723 # via yamllint +pep8-naming==0.15.1 \ + --hash=sha256:eb63925e7fd9e028c7f7ee7b1e413ec03d1ee5de0e627012102ee0222c273c86 \ + --hash=sha256:f6f4a499aba2deeda93c1f26ccc02f3da32b035c8b2db9696b730ef2c9639d29 + # via envoy-code-check platformdirs==4.5.1 \ --hash=sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda \ --hash=sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31 @@ -1220,9 +1230,9 @@ pygments==2.20.0 \ --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 # via - # -r requirements.in # envoy-docs-sphinx-runner # sphinx + # sphinx-tabs pyjwt[crypto]==2.12.1 \ --hash=sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c \ --hash=sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b @@ -1233,12 +1243,6 @@ python-gnupg==0.5.6 \ --hash=sha256:5743e96212d38923fc19083812dc127907e44dbd3bcf0db4d657e291d3c21eac \ --hash=sha256:b5050a55663d8ab9fcc8d97556d229af337a87a3ebebd7054cbd8b7e2043394a # via envoy-base-utils -pytz==2025.2 \ - --hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \ - --hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00 - # via - # aio-core - # envoy-base-utils pyyaml==6.0.3 \ --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ @@ -1314,9 +1318,10 @@ pyyaml==6.0.3 \ --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 # via - # -r requirements.in + # -r tools/python/requirements.in # aio-core # envoy-base-utils + # envoy-docs-sphinx-runner # yamllint requests==2.33.0 \ --hash=sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b \ @@ -1338,10 +1343,11 @@ sphinx==8.2.3 \ --hash=sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348 \ --hash=sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3 # via - # -r requirements.in + # -r tools/python/requirements.in # envoy-docs-sphinx-runner # sphinx-copybutton # sphinx-rtd-theme + # sphinx-tabs # sphinxcontrib-googleanalytics # sphinxcontrib-httpdomain # sphinxcontrib-jquery @@ -1354,29 +1360,36 @@ sphinx-rtd-theme==3.1.0 \ --hash=sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89 \ --hash=sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c # via - # -r requirements.in + # -r tools/python/requirements.in # envoy-docs-sphinx-runner +sphinx-tabs==3.5.0 \ + --hash=sha256:154be49de4d5c8249ea08c5d9bf88ca8f9c31e00a178305a93cbc33e000339e5 \ + --hash=sha256:91dba1187e4c35fd37380a56ac228bbd54c6c649b2351829f3bf033718277537 + # via envoy-docs-sphinx-runner sphinxcontrib-applehelp==2.0.0 \ --hash=sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1 \ --hash=sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5 # via - # -r requirements.in + # -r tools/python/requirements.in + # envoy-docs-sphinx-runner # sphinx sphinxcontrib-devhelp==2.0.0 \ --hash=sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad \ --hash=sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2 # via - # -r requirements.in + # -r tools/python/requirements.in + # envoy-docs-sphinx-runner # sphinx sphinxcontrib-googleanalytics==0.5 \ --hash=sha256:437e529c33c441bcccceabe3ead1585115e6ba83fe90e23bd42e42521333cc0a \ --hash=sha256:a2ac6df9d16b9c124febf6b44e714c1fd9725e692b73ee84ec6b52b377ff5561 - # via -r requirements.in + # via -r tools/python/requirements.in sphinxcontrib-htmlhelp==2.1.0 \ --hash=sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8 \ --hash=sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9 # via - # -r requirements.in + # -r tools/python/requirements.in + # envoy-docs-sphinx-runner # sphinx sphinxcontrib-httpdomain==2.0.0 \ --hash=sha256:9e4e8733bf41ee4d9d5f9eb4dbf3cc2c22a665221ba42c5c3ae181b98af8855d \ @@ -1396,13 +1409,14 @@ sphinxcontrib-qthelp==2.0.0 \ --hash=sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab \ --hash=sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb # via - # -r requirements.in + # -r tools/python/requirements.in + # envoy-docs-sphinx-runner # sphinx sphinxcontrib-serializinghtml==2.0.0 \ --hash=sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331 \ --hash=sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d # via - # -r requirements.in + # -r tools/python/requirements.in # envoy-docs-sphinx-runner # sphinx sphinxext-rediraffe==0.3.0 \ @@ -1415,34 +1429,6 @@ trycast==1.3.0 \ # via # aio-core # envoy-base-utils -types-docutils==0.22.3.20251115 \ - --hash=sha256:0f79ea6a7bd4d12d56c9f824a0090ffae0ea4204203eb0006392906850913e16 \ - --hash=sha256:c6e53715b65395d00a75a3a8a74e352c669bc63959e65a207dffaa22f4a2ad6e - # via types-pygments -types-orjson==3.6.2 \ - --hash=sha256:22ee9a79236b6b0bfb35a0684eded62ad930a88a56797fa3c449b026cf7dbfe4 \ - --hash=sha256:cf9afcc79a86325c7aff251790338109ed6f6b1bab09d2d4262dd18c85a3c638 - # via aio-core -types-protobuf==6.32.1.20251210 \ - --hash=sha256:2641f78f3696822a048cfb8d0ff42ccd85c25f12f871fbebe86da63793692140 \ - --hash=sha256:c698bb3f020274b1a2798ae09dc773728ce3f75209a35187bd11916ebfde6763 - # via envoy-base-utils -types-pygments==2.19.0.20251121 \ - --hash=sha256:cb3bfde34eb75b984c98fb733ce4f795213bd3378f855c32e75b49318371bb25 \ - --hash=sha256:eef114fde2ef6265365522045eac0f8354978a566852f69e75c531f0553822b1 - # via envoy-docs-sphinx-runner -types-pytz==2025.2.0.20251108 \ - --hash=sha256:0f1c9792cab4eb0e46c52f8845c8f77cf1e313cb3d68bf826aa867fe4717d91c \ - --hash=sha256:fca87917836ae843f07129567b74c1929f1870610681b4c92cb86a3df5817bdb - # via - # aio-core - # envoy-base-utils -types-pyyaml==6.0.12.20250915 \ - --hash=sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3 \ - --hash=sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6 - # via - # aio-core - # envoy-code-check typing-extensions==4.15.0 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 @@ -1487,9 +1473,7 @@ uvloop==0.20.0 \ --hash=sha256:e97152983442b499d7a71e44f29baa75b3b02e65d9c44ba53b10338e98dedb66 \ --hash=sha256:f0e94b221295b5e69de57a1bd4aeb0b3a29f61be6e1b478bb8a69a73377db7ba \ --hash=sha256:fee6044b64c965c425b65a4e17719953b96e065c5b7e09b599ff332bb2744bdf - # via - # -r requirements.in - # aio-run-runner + # via aio-run-runner verboselogs==1.7 \ --hash=sha256:d63f23bf568295b95d3530c6864a0b580cec70e7ff974177dead1e4ffbc6ff49 \ --hash=sha256:e33ddedcdfdafcb3a174701150430b11b46ceb64c2a9a26198c76a156568e427 @@ -1633,7 +1617,9 @@ yarl==1.22.0 \ --hash=sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82 \ --hash=sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd \ --hash=sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249 - # via aiohttp + # via + # aio-api-github + # aiohttp zstandard==0.25.0 \ --hash=sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64 \ --hash=sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a \ diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 2abf30611eae9..681ecc11177e5 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -4,9 +4,9 @@ aio.core>=0.11.1 aiohttp>=3.13.3 aioquic>=0.9.21 dependatool>=0.3.1 -envoy.base.utils>=0.6.3 +envoy.base.utils>=0.6.4 envoy.ci.report>=0.1.1 -envoy.code.check>=0.6.3 +envoy.code.check>=0.6.4 envoy.dependency.check>=0.2.1 envoy.distribution.release>=0.1.2 envoy.distribution.verify>=0.1.1 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index d47f9f8828520..b713ee46703d2 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -483,9 +483,9 @@ dependatool==0.3.1 \ --hash=sha256:265fc69e8b1605edb8e33872a36d3a2c2cc055743bec8e58d1ed3c72a17a9431 \ --hash=sha256:6be19ce9fbdffccf639bad0393e454080b7bba8510a98edc78855a6a93895020 # via -r tools/base/requirements.in -envoy-base-utils==0.6.3 \ - --hash=sha256:b7a7ccd51948e5feb903dd42b4dab56ab28309c0e1a6b6dfb7840c99cd0eccd8 \ - --hash=sha256:d16caa01af0a9edb03db1e1fc998daace543636b2cd33e6bb769b9d56f8d880f +envoy-base-utils==0.6.4 \ + --hash=sha256:5c576437d658ec84f04ff1f587b758208ca1544afc86ac1889c0931ac231a85b \ + --hash=sha256:dcc33a8a1d2d4db2bf90514cb2db256c0c25666aa2619de2cfac0a710dc3dd3b # via # -r tools/base/requirements.in # envoy-code-check @@ -498,9 +498,9 @@ envoy-ci-report==0.1.1 \ --hash=sha256:481115be4d05941b83ffbbd8fdac241f10e9823c076f850d8bef7f0308aa382d \ --hash=sha256:a38da2e996b266b32ff80f2f404018d1887f156ad086e3fa8fd4233bb4cd6fe7 # via -r tools/base/requirements.in -envoy-code-check==0.6.3 \ - --hash=sha256:2824fe748328ba88e8424d7f118e6090b5cd605e7654c3e36c0261081dc78444 \ - --hash=sha256:d546e94897c0a60e3c90bec69fe1aee6925623f8328413a048a77f993ea8c89f +envoy-code-check==0.6.4 \ + --hash=sha256:6e5b82d9a6726a875bf07676ab42951734c8b7442cd452bcc961a5fe2182296c \ + --hash=sha256:a93ab843e4f8a78ea0a2f584132928b24d6b335534b7f9c97ef64b1d51c783ab # via -r tools/base/requirements.in envoy-dependency-check==0.2.1 \ --hash=sha256:a037ab34b7db9893ecf07247c8e9149481148de26f340f1c27e4adfa7a48e227 \ From b63c5baf02effe9640a6bc288add944d2226fd75 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Tue, 19 May 2026 14:04:31 -0400 Subject: [PATCH 346/750] Update QUICHE from f129e801d to 6460010e2 (#45151) https://github.com/google/quiche/compare/f129e801d..6460010e2 ``` $ git log f129e801d..6460010e2 --date=short --no-merges --format="%ad %al %s" 2026-05-18 martinduke Factor PublishedSubscription out of MoqtSession, into SubscriptionPublisher. 2026-05-18 martinduke Deprecate quic_update_max_datagram. 2026-05-18 haoyuewang Add alpn() & ciphersuite() methods with a default implementation to QuicCryptoStream. 2026-05-17 vasilvv Update ACK timestamp encoding to use Delta Largest Acknowledged rather than Gap. 2026-05-15 wub No public description 2026-05-15 martinduke Create OutgoingFetchStream and factor out OutgoingUniStream as a parent of both data stream types. 2026-05-14 martinduke Cleanup OutgoingSubgroupStream. 2026-05-14 martinduke Refactor: Move OutgoingDataStream to a separate file and make the interfaces with PublishedSubscription explicit. ``` Signed-off-by: Ali Beyad --- bazel/deps.yaml | 2 +- bazel/repository_locations.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 0dd259f478dc8..34e5dbd632f37 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -296,7 +296,7 @@ quiche: project_name: "QUICHE" project_desc: "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols" project_url: "https://github.com/google/quiche" - release_date: "2026-05-14" + release_date: "2026-05-18" use_category: - controlplane - dataplane_core diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index bfa0107a2b2be..4cf42c5e199f3 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -553,8 +553,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/simdutf/simdutf/releases/download/v{version}/singleheader.zip"], ), quiche = dict( - version = "f129e801d183fd095620d3bd68f8119ca9d5c0ea", - sha256 = "f70ab89a950bb46f239c29e7e9757b3ce3082403a65e9e1920ccd2cf3fcedbeb", + version = "6460010e28a23919ce1f6db92d9fa125ce41fab6", + sha256 = "807c62410050395fc8d26eac6454e206490b12ac2e0a98b808a5250e303174d3", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", ), From 91eec10bf1f99dd6f7673234d7aedba1b3ccce9c Mon Sep 17 00:00:00 2001 From: botengyao Date: Tue, 19 May 2026 15:07:43 -0400 Subject: [PATCH 347/750] mcp: parser reset enhancement (#45154) Commit Message: Additional Description: Risk Level: none Testing: Docs Changes: Signed-off-by: Boteng Yao --- source/extensions/filters/http/mcp/mcp_json_parser.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/extensions/filters/http/mcp/mcp_json_parser.cc b/source/extensions/filters/http/mcp/mcp_json_parser.cc index efe942c5be772..830ae82baf044 100644 --- a/source/extensions/filters/http/mcp/mcp_json_parser.cc +++ b/source/extensions/filters/http/mcp/mcp_json_parser.cc @@ -777,9 +777,10 @@ const Protobuf::Value* McpJsonParser::getNestedValue(const std::string& dotted_p void McpJsonParser::reset() { metadata_.Clear(); - extractor_.reset(); stream_parser_.reset(); + extractor_.reset(); parsing_started_ = false; + all_fields_collected_ = false; } } // namespace Mcp From 3e626835687fb17e1911f36d1390e1b1fa11b3d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 20:40:12 +0100 Subject: [PATCH 348/750] build(deps): bump idna from 3.10 to 3.15 in /tools/base in the pip group across 1 directory (#45155) Bumps the pip group with 1 update in the /tools/base directory: [idna](https://github.com/kjd/idna). Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 52 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index b713ee46703d2..14b465de1ef12 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -23,12 +23,12 @@ abstracts==0.2.0 \ aio-api-bazel==0.1.1 \ --hash=sha256:04ee9aa1dddc2998b674ca0a6a791cb7ab9d49f1f400bd19fcd431e00ea54587 \ --hash=sha256:59a5050d43f4d5ebf19bb669b4a386c74a66520002dc72e5d900ebd058f4d5d7 - # via -r tools/base/requirements.in + # via -r requirements.in aio-api-github==0.3.1 \ --hash=sha256:04d2e3fb4fcb55927b75f9a3cefae7673cefabd2387dd2416848ff19326d719c \ --hash=sha256:d083ca09e5577d0c4217138e45b18ea63d57d0cbb030eb336cc2a979f8b244b4 # via - # -r tools/base/requirements.in + # -r requirements.in # envoy-base-utils # envoy-ci-report # envoy-dependency-check @@ -36,7 +36,7 @@ aio-core==0.11.1 \ --hash=sha256:03f1202029655c68d5d41c2fa1d18e344d61515b71080f770f8cf6f99fbc5f5a \ --hash=sha256:76608a1dad3985866c1b3ffc81e0d08bd1a740e8e72125bfbf46d1ee6fc3abfe # via - # -r tools/base/requirements.in + # -r requirements.in # aio-api-bazel # aio-api-github # aio-run-runner @@ -203,7 +203,7 @@ aiohttp==3.13.5 \ --hash=sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540 \ --hash=sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254 # via - # -r tools/base/requirements.in + # -r requirements.in # aio-api-github # aiodocker # envoy-base-utils @@ -223,7 +223,7 @@ aioquic==1.3.0 \ --hash=sha256:9d15a89213d38cbc4679990fa5151af8ea02655a1d6ce5ec972b0a6af74d5f1c \ --hash=sha256:a8881239801279188e33ced6f9849cedf033325a48a6f44d7e55e583abc555a3 \ --hash=sha256:ba30016244e45d9222fdd1fbd4e8b0e5f6811e81a5d0643475ad7024a537274a - # via -r tools/base/requirements.in + # via -r requirements.in aiosignal==1.4.0 \ --hash=sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e \ --hash=sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7 @@ -482,12 +482,12 @@ cryptography==47.0.0 \ dependatool==0.3.1 \ --hash=sha256:265fc69e8b1605edb8e33872a36d3a2c2cc055743bec8e58d1ed3c72a17a9431 \ --hash=sha256:6be19ce9fbdffccf639bad0393e454080b7bba8510a98edc78855a6a93895020 - # via -r tools/base/requirements.in + # via -r requirements.in envoy-base-utils==0.6.4 \ --hash=sha256:5c576437d658ec84f04ff1f587b758208ca1544afc86ac1889c0931ac231a85b \ --hash=sha256:dcc33a8a1d2d4db2bf90514cb2db256c0c25666aa2619de2cfac0a710dc3dd3b # via - # -r tools/base/requirements.in + # -r requirements.in # envoy-code-check # envoy-dependency-check # envoy-distribution-release @@ -497,23 +497,23 @@ envoy-base-utils==0.6.4 \ envoy-ci-report==0.1.1 \ --hash=sha256:481115be4d05941b83ffbbd8fdac241f10e9823c076f850d8bef7f0308aa382d \ --hash=sha256:a38da2e996b266b32ff80f2f404018d1887f156ad086e3fa8fd4233bb4cd6fe7 - # via -r tools/base/requirements.in + # via -r requirements.in envoy-code-check==0.6.4 \ --hash=sha256:6e5b82d9a6726a875bf07676ab42951734c8b7442cd452bcc961a5fe2182296c \ --hash=sha256:a93ab843e4f8a78ea0a2f584132928b24d6b335534b7f9c97ef64b1d51c783ab - # via -r tools/base/requirements.in + # via -r requirements.in envoy-dependency-check==0.2.1 \ --hash=sha256:a037ab34b7db9893ecf07247c8e9149481148de26f340f1c27e4adfa7a48e227 \ --hash=sha256:d91134dc18d2a6a69694623b70a7cb0571647f141a493f0e1bff12d843ec9f7b - # via -r tools/base/requirements.in + # via -r requirements.in envoy-distribution-release==0.1.2 \ --hash=sha256:0a784b46563d2611eb7c68a10c46dbe1821161f0b9fa0018220e5ec373a3b7f3 \ --hash=sha256:c15873e88743970143d8ac9fd911bac8cbf34aef6b0315ab78c003a242ef60ef - # via -r tools/base/requirements.in + # via -r requirements.in envoy-distribution-verify==0.1.1 \ --hash=sha256:21bf9cbb259c81d953ed768fca9b6d96e65b46c731cc406fe1d3cba5398591bc \ --hash=sha256:f96840fd1c2f37f39e72f2a7e9d11d3076f67fe20f598ecc1d96153e19c7b09c - # via -r tools/base/requirements.in + # via -r requirements.in envoy-docker-utils==0.1.1 \ --hash=sha256:2375ae633783c07652f42c86e2db190cb34f4d0486468b8a475cf3417a8d67b5 \ --hash=sha256:9c3df7ca912972f17469661f56b29cd7785705bf2176606dbfded283dab85f4d @@ -525,7 +525,7 @@ envoy-github-release==0.1.2 \ envoy-gpg-sign==0.3.2 \ --hash=sha256:307179de846a381675a73bcdd8dc7a46c1423f9596a5eb0926ab97f5f3957350 \ --hash=sha256:ccfe864e5a47a8f363b3169088a3027db3390208347ee9f1b65b48138f901e17 - # via -r tools/base/requirements.in + # via -r requirements.in flake8==7.3.0 \ --hash=sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e \ --hash=sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872 @@ -723,7 +723,7 @@ gitdb==4.0.11 \ gitpython==3.1.50 \ --hash=sha256:80da2d12504d52e1f998772dc5baf6e553f8d2fcfe1fcc226c9d9a2ee3372dcc \ --hash=sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9 - # via -r tools/base/requirements.in + # via -r requirements.in humanfriendly==10.0 \ --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 \ --hash=sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc @@ -731,10 +731,10 @@ humanfriendly==10.0 \ icalendar==7.0.3 \ --hash=sha256:8c9fea6d3a89671bba8b6938d8565b4d0ec465c6a2796ef0f92790dcb9e627cd \ --hash=sha256:95027ece087ab87184d765f03761f25875821f74cdd18d3b57e9c868216d8fde - # via -r tools/base/requirements.in -idna==3.10 \ - --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ - --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via -r requirements.in +idna==3.15 \ + --hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ + --hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc # via # requests # yarl @@ -1038,7 +1038,7 @@ orjson==3.11.9 \ --hash=sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7 \ --hash=sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81 # via - # -r tools/base/requirements.in + # -r requirements.in # envoy-base-utils packaging==26.2 \ --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \ @@ -1057,7 +1057,7 @@ pep8-naming==0.15.1 \ --hash=sha256:eb63925e7fd9e028c7f7ee7b1e413ec03d1ee5de0e627012102ee0222c273c86 \ --hash=sha256:f6f4a499aba2deeda93c1f26ccc02f3da32b035c8b2db9696b730ef2c9639d29 # via - # -r tools/base/requirements.in + # -r requirements.in # envoy-code-check platformdirs==4.3.6 \ --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ @@ -1066,7 +1066,7 @@ platformdirs==4.3.6 \ ply==3.11 \ --hash=sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3 \ --hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce - # via -r tools/base/requirements.in + # via -r requirements.in propcache==0.3.1 \ --hash=sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e \ --hash=sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b \ @@ -1179,7 +1179,7 @@ protobuf==7.34.1 \ --hash=sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7 \ --hash=sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c # via - # -r tools/base/requirements.in + # -r requirements.in # envoy-base-utils pyasn1==0.6.3 \ --hash=sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf \ @@ -1206,7 +1206,7 @@ pyflakes==3.4.0 \ pygithub==2.9.1 \ --hash=sha256:2ec78fca30092d51a42d76f4ddb02131b6f0c666a35dfdf364cf302cdda115b9 \ --hash=sha256:59771d7ff63d54d427be2e7d0dad2208dfffc2b0a045fec959263787739b611c - # via -r tools/base/requirements.in + # via -r requirements.in pyjwt[crypto]==2.12.1 \ --hash=sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c \ --hash=sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b @@ -1322,7 +1322,7 @@ pyyaml==6.0.3 \ --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 # via - # -r tools/base/requirements.in + # -r requirements.in # aio-core # envoy-base-utils # yamllint @@ -1341,7 +1341,7 @@ six==1.16.0 \ slack-sdk==3.41.0 \ --hash=sha256:bb18dcdfff1413ec448e759cf807ec3324090993d8ab9111c74081623b692a89 \ --hash=sha256:eb61eb12a65bebeca9cb5d36b3f799e836ed2be21b456d15df2627cfe34076ca - # via -r tools/base/requirements.in + # via -r requirements.in smmap==5.0.1 \ --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ --hash=sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da @@ -1679,4 +1679,4 @@ zstandard==0.25.0 \ setuptools==82.0.1 \ --hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \ --hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb - # via -r tools/base/requirements.in + # via -r requirements.in From 86ae80bcd188f3e8ea94fc303238cec1fdad76c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 20:41:12 +0100 Subject: [PATCH 349/750] build(deps): bump idna from 3.11 to 3.15 in /docs/tools/python in the pip group across 1 directory (#45157) Bumps the pip group with 1 update in the /docs/tools/python directory: [idna](https://github.com/kjd/idna). Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/tools/python/requirements.txt | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/tools/python/requirements.txt b/docs/tools/python/requirements.txt index f3abadb769e71..8caebde9c9db5 100644 --- a/docs/tools/python/requirements.txt +++ b/docs/tools/python/requirements.txt @@ -34,7 +34,7 @@ aio-run-runner==0.4.1 \ --hash=sha256:03c2978e4c7e1d02a55e25b0c081603d7900387bcce4fe80b8e8af8f3284e05b \ --hash=sha256:43c80688c50ba3a528d88635fee5653b9f0f40fc4a036e1aeaddf8ada3794d2e # via - # -r tools/python/requirements.in + # -r requirements.in # aio-run-checker # envoy-base-utils # envoy-docs-sphinx-runner @@ -458,17 +458,17 @@ envoy-base-utils==0.6.4 \ --hash=sha256:5c576437d658ec84f04ff1f587b758208ca1544afc86ac1889c0931ac231a85b \ --hash=sha256:dcc33a8a1d2d4db2bf90514cb2db256c0c25666aa2619de2cfac0a710dc3dd3b # via - # -r tools/python/requirements.in + # -r requirements.in # envoy-code-check # envoy-docs-sphinx-runner envoy-code-check==0.6.4 \ --hash=sha256:6e5b82d9a6726a875bf07676ab42951734c8b7442cd452bcc961a5fe2182296c \ --hash=sha256:a93ab843e4f8a78ea0a2f584132928b24d6b335534b7f9c97ef64b1d51c783ab - # via -r tools/python/requirements.in + # via -r requirements.in envoy-docs-sphinx-runner==0.3.2 \ --hash=sha256:76ca59da2237b6aabc453ed067808aa7d603913e1de0ad8b2db3dddee99190be \ --hash=sha256:fb66b380809bf910e45c904477510c0ccb2c808f65ff7a8794d9c65ab3d3bbae - # via -r tools/python/requirements.in + # via -r requirements.in flake8==7.3.0 \ --hash=sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e \ --hash=sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872 @@ -568,7 +568,7 @@ frozendict==2.4.7 \ --hash=sha256:ffd1a9f9babec9119712e76a39397d8aa0d72ef8c4ccad917c6175d7e7f81b74 \ --hash=sha256:fff8584e3bbdc5c1713cd016fbf4b88babfffd4e5e89b39020f2a208dd24c900 # via - # -r tools/python/requirements.in + # -r requirements.in # aio-run-runner # envoy-base-utils frozenlist==1.8.0 \ @@ -713,9 +713,9 @@ humanfriendly==10.0 \ --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 \ --hash=sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc # via coloredlogs -idna==3.11 \ - --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \ - --hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902 +idna==3.15 \ + --hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ + --hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc # via # requests # yarl @@ -727,7 +727,7 @@ jinja2==3.1.6 \ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 # via - # -r tools/python/requirements.in + # -r requirements.in # envoy-base-utils # sphinx markupsafe==3.0.3 \ @@ -1056,7 +1056,7 @@ packaging==26.1 \ --hash=sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f \ --hash=sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de # via - # -r tools/python/requirements.in + # -r requirements.in # aio-api-github # envoy-base-utils # envoy-code-check @@ -1318,7 +1318,7 @@ pyyaml==6.0.3 \ --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 # via - # -r tools/python/requirements.in + # -r requirements.in # aio-core # envoy-base-utils # envoy-docs-sphinx-runner @@ -1343,7 +1343,7 @@ sphinx==8.2.3 \ --hash=sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348 \ --hash=sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3 # via - # -r tools/python/requirements.in + # -r requirements.in # envoy-docs-sphinx-runner # sphinx-copybutton # sphinx-rtd-theme @@ -1360,7 +1360,7 @@ sphinx-rtd-theme==3.1.0 \ --hash=sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89 \ --hash=sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c # via - # -r tools/python/requirements.in + # -r requirements.in # envoy-docs-sphinx-runner sphinx-tabs==3.5.0 \ --hash=sha256:154be49de4d5c8249ea08c5d9bf88ca8f9c31e00a178305a93cbc33e000339e5 \ @@ -1370,25 +1370,25 @@ sphinxcontrib-applehelp==2.0.0 \ --hash=sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1 \ --hash=sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5 # via - # -r tools/python/requirements.in + # -r requirements.in # envoy-docs-sphinx-runner # sphinx sphinxcontrib-devhelp==2.0.0 \ --hash=sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad \ --hash=sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2 # via - # -r tools/python/requirements.in + # -r requirements.in # envoy-docs-sphinx-runner # sphinx sphinxcontrib-googleanalytics==0.5 \ --hash=sha256:437e529c33c441bcccceabe3ead1585115e6ba83fe90e23bd42e42521333cc0a \ --hash=sha256:a2ac6df9d16b9c124febf6b44e714c1fd9725e692b73ee84ec6b52b377ff5561 - # via -r tools/python/requirements.in + # via -r requirements.in sphinxcontrib-htmlhelp==2.1.0 \ --hash=sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8 \ --hash=sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9 # via - # -r tools/python/requirements.in + # -r requirements.in # envoy-docs-sphinx-runner # sphinx sphinxcontrib-httpdomain==2.0.0 \ @@ -1409,14 +1409,14 @@ sphinxcontrib-qthelp==2.0.0 \ --hash=sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab \ --hash=sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb # via - # -r tools/python/requirements.in + # -r requirements.in # envoy-docs-sphinx-runner # sphinx sphinxcontrib-serializinghtml==2.0.0 \ --hash=sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331 \ --hash=sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d # via - # -r tools/python/requirements.in + # -r requirements.in # envoy-docs-sphinx-runner # sphinx sphinxext-rediraffe==0.3.0 \ From 3d386c86aa3d2f65437b4c822dda3050218b3243 Mon Sep 17 00:00:00 2001 From: code Date: Wed, 20 May 2026 14:06:58 +0800 Subject: [PATCH 350/750] lb: unified the lifetime mechanism of worker local lb (#45100) Commit Message: lb: unify the lifetime mechanism of worker local lb Additional Description: In the previous implementation, the thread aware lb will recreate the worker local lb when endpoint set changes. This PR updated the implementation of thread aware lb to update the worker local lb in place rather than to create a new one. The new behavior is same with all other LB implementation. Risk Level: mid. Testing: unit. Docs Changes: n/a. Release Notes: n/a. Platform Specific Features: n/a. --------- Signed-off-by: wbpcode/wangbaiping Signed-off-by: code Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../common/thread_aware_lb_impl.cc | 34 +++++--- .../common/thread_aware_lb_impl.h | 20 +++-- .../deferred_cluster_initialization_test.cc | 8 +- .../maglev/maglev_lb_test.cc | 87 +++++++++++++++++++ 4 files changed, 129 insertions(+), 20 deletions(-) diff --git a/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.cc b/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.cc index dcfcc8d1f62de..73ca2c2eb1ac5 100644 --- a/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.cc +++ b/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.cc @@ -236,16 +236,30 @@ ThreadAwareLoadBalancerBase::LoadBalancerImpl::chooseHost(LoadBalancerContext* c return host; } -LoadBalancerPtr ThreadAwareLoadBalancerBase::LoadBalancerFactoryImpl::create(LoadBalancerParams) { - auto lb = std::make_unique(stats_, random_, hash_policy_); - - // We must protect current_lb_ via a RW lock since it is accessed and written to by multiple - // threads. All complex processing has already been precalculated however. - absl::ReaderMutexLock lock(mutex_); - lb->healthy_per_priority_load_ = healthy_per_priority_load_; - lb->degraded_per_priority_load_ = degraded_per_priority_load_; - lb->per_priority_state_ = per_priority_state_; - return lb; +void ThreadAwareLoadBalancerBase::LoadBalancerImpl::refresh() { + // The per priority state is shared across all threads and refreshed on main thread. We need to + // copy the latest per priority state to the worker thread load balancer instance under lock. + absl::ReaderMutexLock lock(factory_->mutex_); + healthy_per_priority_load_ = factory_->healthy_per_priority_load_; + degraded_per_priority_load_ = factory_->degraded_per_priority_load_; + per_priority_state_ = factory_->per_priority_state_; +} + +ThreadAwareLoadBalancerBase::LoadBalancerImpl::LoadBalancerImpl( + std::shared_ptr factory, ClusterLbStats& stats, + Random::RandomGenerator& random, HashPolicySharedPtr hash_policy, + const Upstream::PrioritySet& priority_set) + : factory_(std::move(factory)), stats_(stats), random_(random), + hash_policy_(std::move(hash_policy)) { + member_update_cb_ = + priority_set.addMemberUpdateCb([this](const HostVector&, const HostVector&) { refresh(); }); + refresh(); +} + +LoadBalancerPtr +ThreadAwareLoadBalancerBase::LoadBalancerFactoryImpl::create(LoadBalancerParams params) { + return std::make_unique(shared_from_this(), stats_, random_, hash_policy_, + params.priority_set); } double ThreadAwareLoadBalancerBase::BoundedLoadHashingLoadBalancer::hostOverloadFactor( diff --git a/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.h b/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.h index 7faa9675181c2..9ccf5f6aa5a38 100644 --- a/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.h +++ b/source/extensions/load_balancing_policies/common/thread_aware_lb_impl.h @@ -126,10 +126,11 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL }; using PerPriorityStatePtr = std::unique_ptr; + struct LoadBalancerFactoryImpl; struct LoadBalancerImpl : public LoadBalancer { - LoadBalancerImpl(ClusterLbStats& stats, Random::RandomGenerator& random, - HashPolicySharedPtr hash_policy) - : stats_(stats), random_(random), hash_policy_(std::move(hash_policy)) {} + LoadBalancerImpl(std::shared_ptr factory, ClusterLbStats& stats, + Random::RandomGenerator& random, HashPolicySharedPtr hash_policy, + const Upstream::PrioritySet& priority_set); // Upstream::LoadBalancer HostSelectionResponse chooseHost(LoadBalancerContext* context) override; @@ -145,6 +146,10 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL return {}; } + void refresh(); + + std::shared_ptr factory_; + ClusterLbStats& stats_; Random::RandomGenerator& random_; HashPolicySharedPtr hash_policy_; @@ -152,16 +157,21 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL std::shared_ptr> per_priority_state_; std::shared_ptr healthy_per_priority_load_; std::shared_ptr degraded_per_priority_load_; + + Common::CallbackHandlePtr member_update_cb_; }; - struct LoadBalancerFactoryImpl : public LoadBalancerFactory { + struct LoadBalancerFactoryImpl : public LoadBalancerFactory, + public std::enable_shared_from_this { LoadBalancerFactoryImpl(ClusterLbStats& stats, Random::RandomGenerator& random, std::shared_ptr hash_policy) : stats_(stats), random_(random), hash_policy_(std::move(hash_policy)) {} // Upstream::LoadBalancerFactory - // Ignore the params for the thread-aware LB. + // Uses the per-worker params to create the thread-aware LB instance, including the worker + // priority_set used to register member-update callbacks. LoadBalancerPtr create(LoadBalancerParams) override; + bool recreateOnHostChange() const override { return false; } ClusterLbStats& stats_; Random::RandomGenerator& random_; diff --git a/test/common/upstream/deferred_cluster_initialization_test.cc b/test/common/upstream/deferred_cluster_initialization_test.cc index 209314ab767ae..7ea63ad65c7ca 100644 --- a/test/common/upstream/deferred_cluster_initialization_test.cc +++ b/test/common/upstream/deferred_cluster_initialization_test.cc @@ -546,11 +546,9 @@ TEST_P(EdsTest, ShouldNotMergeAddingHostsForDifferentClustersWithSameName) { addEndpoint(cluster_load_assignment, 1000); doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); - auto initiailization_instance = + auto initialization_instance = cluster_manager_->clusterInitializationMap().find("cluster_1")->second; - EXPECT_NE(nullptr, initiailization_instance->load_balancer_factory_); - // RING_HASH lb policy requires Envoy re-create the load balancer when the cluster is updated. - EXPECT_TRUE(initiailization_instance->load_balancer_factory_->recreateOnHostChange()); + EXPECT_NE(nullptr, initialization_instance->load_balancer_factory_); // Update the cluster with a different lb policy. Now it's a different cluster and should // not be merged. @@ -565,7 +563,7 @@ TEST_P(EdsTest, ShouldNotMergeAddingHostsForDifferentClustersWithSameName) { auto new_initialization_instance = cluster_manager_->clusterInitializationMap().find("cluster_1")->second; - EXPECT_NE(initiailization_instance.get(), new_initialization_instance.get()); + EXPECT_NE(initialization_instance.get(), new_initialization_instance.get()); EXPECT_EQ(1, new_initialization_instance->per_priority_state_.at(1).hosts_added_.size()); // Ensure the hosts_added_ is empty for priority 0. Because if unexpected merge happens, diff --git a/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc b/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc index a8f7a0c318069..e6daaee45e2e0 100644 --- a/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc +++ b/test/extensions/load_balancing_policies/maglev/maglev_lb_test.cc @@ -135,6 +135,93 @@ TEST_F(MaglevLoadBalancerTest, LbDestructedBeforeFactory) { EXPECT_NE(nullptr, factory->create(lb_params_)); } +// The thread-aware factory updates the worker LB in place via a member update callback on the +// worker priority set, so the cluster manager should not recreate the worker LB on host changes. +TEST_F(MaglevLoadBalancerTest, FactoryDoesNotRecreateOnHostChange) { + init(7); + EXPECT_FALSE(lb_->factory()->recreateOnHostChange()); +} + +// Worker LB instances pick up new factory state when the worker priority set fires a member +// update, without needing to be recreated. +TEST_F(MaglevLoadBalancerTest, WorkerLbRefreshesOnMemberUpdate) { + host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90")}; + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.runCallbacks({}, {}); + init(7); + + // The worker LB is created once and keeps a reference to the factory. + LoadBalancerPtr lb = lb_->factory()->create(lb_params_); + EXPECT_EQ(host_set_.hosts_[0], lb->chooseHost(nullptr).host); + + // Replace the host set. host_set_.runCallbacks fires the main priority set's callbacks, which + // recomputes the factory state on the main thread. + auto new_host = makeTestHost(info_, "tcp://127.0.0.1:91"); + host_set_.hosts_ = {new_host}; + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.runCallbacks({}, {}); + + // The worker LB still references the previous per-priority state until its own priority set + // fires the member update. + EXPECT_EQ("127.0.0.1:90", lb->chooseHost(nullptr).host->address()->asString()); + + // Simulate the worker priority set firing the member update on the worker. The worker LB must + // refresh its cached state from the factory. + worker_priority_set_.member_update_cb_helper_.runCallbacks({}, {}); + + EXPECT_EQ(new_host, lb->chooseHost(nullptr).host); +} + +// Multiple worker LBs share the same factory; each refreshes independently when its own priority +// set fires a member update. +TEST_F(MaglevLoadBalancerTest, MultipleWorkerLbsRefreshIndependently) { + host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90")}; + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.runCallbacks({}, {}); + init(7); + + NiceMock worker_priority_set_a; + NiceMock worker_priority_set_b; + LoadBalancerParams params_a{worker_priority_set_a, {}}; + LoadBalancerParams params_b{worker_priority_set_b, {}}; + + LoadBalancerPtr lb_a = lb_->factory()->create(params_a); + LoadBalancerPtr lb_b = lb_->factory()->create(params_b); + + EXPECT_EQ(host_set_.hosts_[0], lb_a->chooseHost(nullptr).host); + EXPECT_EQ(host_set_.hosts_[0], lb_b->chooseHost(nullptr).host); + + auto new_host = makeTestHost(info_, "tcp://127.0.0.1:91"); + host_set_.hosts_ = {new_host}; + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.runCallbacks({}, {}); + + // Only refresh worker A; worker B keeps the previous state until its own priority set fires. + worker_priority_set_a.member_update_cb_helper_.runCallbacks({}, {}); + EXPECT_EQ(new_host, lb_a->chooseHost(nullptr).host); + EXPECT_EQ("127.0.0.1:90", lb_b->chooseHost(nullptr).host->address()->asString()); + + worker_priority_set_b.member_update_cb_helper_.runCallbacks({}, {}); + EXPECT_EQ(new_host, lb_b->chooseHost(nullptr).host); +} + +// The worker LB unregisters its member update callback on destruction; firing the worker priority +// set's callback after the LB is gone must not touch freed memory. +TEST_F(MaglevLoadBalancerTest, WorkerLbCallbackUnregisteredOnDestruction) { + host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:90")}; + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.runCallbacks({}, {}); + init(7); + + LoadBalancerPtr lb = lb_->factory()->create(lb_params_); + EXPECT_EQ(host_set_.hosts_[0], lb->chooseHost(nullptr).host); + + lb.reset(); + + // Must be a no-op rather than calling into freed memory. + worker_priority_set_.member_update_cb_helper_.runCallbacks({}, {}); +} + // Throws an exception if table size is not a prime number. TEST_F(MaglevLoadBalancerTest, NoPrimeNumber) { EXPECT_THROW_WITH_MESSAGE(init(8), EnvoyException, From b5c0d52f1b6be631be7b04d6c124b0c5ba521e2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 10:35:49 +0100 Subject: [PATCH 351/750] build(deps): bump idna from 3.13 to 3.15 in /mobile/tools/python in the pip group across 1 directory (#45156) Bumps the pip group with 1 update in the /mobile/tools/python directory: [idna](https://github.com/kjd/idna). Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mobile/tools/python/requirements.txt | 67 ++-------------------------- 1 file changed, 3 insertions(+), 64 deletions(-) diff --git a/mobile/tools/python/requirements.txt b/mobile/tools/python/requirements.txt index d9f6e01c469ba..394677c773748 100644 --- a/mobile/tools/python/requirements.txt +++ b/mobile/tools/python/requirements.txt @@ -179,67 +179,6 @@ click==8.3.1 \ --hash=sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a \ --hash=sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6 # via black -greenlet==3.5.0 \ - --hash=sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846 \ - --hash=sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4 \ - --hash=sha256:0ff251e9a0279522e62f6176412869395a64ddf2b5c5f782ff609a8216a4e662 \ - --hash=sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce \ - --hash=sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2 \ - --hash=sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588 \ - --hash=sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13 \ - --hash=sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e \ - --hash=sha256:29ea813b2e1f45fa9649a17853b2b5465c4072fbcb072e5af6cd3a288216574a \ - --hash=sha256:362624e6a8e5bca3b8233e45eef33903a100e9539a2b995c364d595dbc4018b3 \ - --hash=sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b \ - --hash=sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033 \ - --hash=sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628 \ - --hash=sha256:41353ec2ecedf7aa8f682753a41919f8718031a6edac46b8d3dc7ed9e1ceb136 \ - --hash=sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b \ - --hash=sha256:4964101b8585c144cbda5532b1aa644255126c08a265dae90c16e7a0e63aaa9d \ - --hash=sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2 \ - --hash=sha256:4b28037cb07768933c54d81bfe47a85f9f402f57d7d69743b991a713b63954eb \ - --hash=sha256:4d0eadc7e4d9ffb2af4247b606cae307be8e448911e5a0d0b16d72fc3d224cfd \ - --hash=sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b \ - --hash=sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1 \ - --hash=sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16 \ - --hash=sha256:58c1c374fe2b3d852f9b6b11a7dff4c85404e51b9a596fd9e89cf904eb09866d \ - --hash=sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106 \ - --hash=sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba \ - --hash=sha256:5ecd83806b0f4c2f53b1018e0005cd82269ea01d42befc0368730028d850ed1c \ - --hash=sha256:64d6ac45f7271f48e45f67c95b54ef73534c52ec041fcda8edf520c6d811f4bc \ - --hash=sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7 \ - --hash=sha256:6c18dfb59c70f5a94acd271c72e90128c3c776e41e5f07767908c8c1b74ad339 \ - --hash=sha256:6d874e79afd41a96e11ff4c5d0bc90a80973e476fda1c2c64985667397df432b \ - --hash=sha256:7022615368890680e67b9965d33f5773aade330d5343bbe25560135aaa849eae \ - --hash=sha256:703cb211b820dbffbbc55a16bfc6e4583a6e6e990f33a119d2cc8b83211119c8 \ - --hash=sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2 \ - --hash=sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5 \ - --hash=sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf \ - --hash=sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f \ - --hash=sha256:804a70b328e706b785c6ef16187051c394a63dd1a906d89be24b6ad77759f13f \ - --hash=sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2 \ - --hash=sha256:884f649de075b84739713d41dd4dfd41e2b910bfb769c4a3ea02ec1da52cd9bb \ - --hash=sha256:8f1cc966c126639cd152fdaa52624d2655f492faa79e013fea161de3e6dda082 \ - --hash=sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7 \ - --hash=sha256:9c615f869163e14bb1ced20322d8038fb680b08236521ac3f30cd4c1288785a0 \ - --hash=sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c \ - --hash=sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853 \ - --hash=sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988 \ - --hash=sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3 \ - --hash=sha256:ba8f0bdc2fae6ce915dfd0c16d2d00bca7e4247c1eae4416e06430e522137858 \ - --hash=sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37 \ - --hash=sha256:cda05425526240807408156b6960a17a79a0c760b813573b67027823be760977 \ - --hash=sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4 \ - --hash=sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8 \ - --hash=sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86 \ - --hash=sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f \ - --hash=sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112 \ - --hash=sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e \ - --hash=sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2 \ - --hash=sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8 \ - --hash=sha256:f8c30c2225f40dd76c50790f0eb3b5c7c18431efb299e2782083e1981feed243 \ - --hash=sha256:fa94cb2288681e3a11645958f1871d48ee9211bd2f66628fdace505927d6e564 - # via gevent h11==0.16.0 \ --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 @@ -252,9 +191,9 @@ httpx==0.28.1 \ --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad # via -r requirements.in -idna==3.13 \ - --hash=sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242 \ - --hash=sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3 +idna==3.15 \ + --hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ + --hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc # via # anyio # httpx From 905e5a4ff227982ae8035ca5286f80db77c0f2c3 Mon Sep 17 00:00:00 2001 From: William Dauchy Date: Wed, 20 May 2026 14:01:39 +0200 Subject: [PATCH 352/750] golang: always post continueStatus/sendLocalReply to the dispatcher (#44974) --- ...lang__fixed-a-crash-in-the-golang-http.rst | 7 + .../filters/http/source/golang_filter.cc | 22 +-- .../filters/http/test/golang_filter_test.cc | 146 ++++++------------ 3 files changed, 63 insertions(+), 112 deletions(-) create mode 100644 changelogs/current/bug_fixes/golang__fixed-a-crash-in-the-golang-http.rst diff --git a/changelogs/current/bug_fixes/golang__fixed-a-crash-in-the-golang-http.rst b/changelogs/current/bug_fixes/golang__fixed-a-crash-in-the-golang-http.rst new file mode 100644 index 0000000000000..110117882c820 --- /dev/null +++ b/changelogs/current/bug_fixes/golang__fixed-a-crash-in-the-golang-http.rst @@ -0,0 +1,7 @@ +Fixed a crash in the Golang HTTP filter introduced by #44503 where ``continueStatus`` or +``sendLocalReply`` invoked synchronously from inside a Go ``OnHttpHeader``/``OnHttpData`` +cgo callback would re-enter the C++ state machine while the cgo frame was still on the +stack, tripping the ``ASSERT(filterState() == Processing*)`` at the top of +``handle*GolangStatus`` once the cgo call returned. The inline-on-worker-thread +optimization from #44503 is reverted for these two CAPI methods; both now always +post to the dispatcher, matching the pre-#44503 behavior. diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index 4cb88818e4438..9e387c3cf09b6 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -479,7 +479,6 @@ CAPIStatus Filter::sendLocalReply(ProcessorState& state, Http::Code response_code, std::string body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, std::string details) { - bool on_worker_thread = state.isThreadSafe(); if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; @@ -490,11 +489,9 @@ Filter::sendLocalReply(ProcessorState& state, Http::Code response_code, std::str } ENVOY_LOG(debug, "sendLocalReply, response code: {}", int(response_code)); - if (on_worker_thread) { - sendLocalReplyInternal(state, response_code, body_text, modify_headers, grpc_status, details); - return CAPIStatus::CAPIOK; - } - + // Always post to the dispatcher: inline execution from inside a Go cgo callback + // would re-enter the C++ state machine before handle*GolangStatus consumes the + // returned status, tripping its ASSERT (see #44704). auto weak_ptr = weak_from_this(); state.getDispatcher().post([this, &state, weak_ptr, response_code, body_text, modify_headers, grpc_status, details] { @@ -520,7 +517,6 @@ CAPIStatus Filter::sendPanicReply(ProcessorState& state, absl::string_view detai } CAPIStatus Filter::continueStatus(ProcessorState& state, GolangStatus status) { - bool on_worker_thread = state.isThreadSafe(); if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; @@ -532,15 +528,9 @@ CAPIStatus Filter::continueStatus(ProcessorState& state, GolangStatus status) { ENVOY_LOG(debug, "golang filter continue from Go, status: {}, state: {}", int(status), state.stateStr()); - // When on the worker thread, continueStatusInternal re-enters the filter chain - // (continueProcessing / continueDoData / sendLocalReply) while the cgo call is still on the - // stack. This synchronous reentrancy is intentional and is the same model used by the addData - // inline path below; callers must not hold any state-mutating locks across this call. - if (on_worker_thread) { - continueStatusInternal(state, status); - return CAPIStatus::CAPIOK; - } - + // Always post to the dispatcher: inline execution from inside a Go cgo callback + // would re-enter the C++ state machine before handle*GolangStatus consumes the + // returned status, tripping its ASSERT (see #44704). auto weak_ptr = weak_from_this(); state.getDispatcher().post([this, &state, weak_ptr, status] { if (!weak_ptr.expired() && !hasDestroyed()) { diff --git a/contrib/golang/filters/http/test/golang_filter_test.cc b/contrib/golang/filters/http/test/golang_filter_test.cc index 955a09cc76191..bac9a281bda5a 100644 --- a/contrib/golang/filters/http/test/golang_filter_test.cc +++ b/contrib/golang/filters/http/test/golang_filter_test.cc @@ -42,7 +42,6 @@ class TestFilter : public Filter { using Filter::continueStatus; using Filter::continueStatusInternal; using Filter::Filter; - using Filter::sendLocalReply; DecodingProcessorState& testDecodingState() { return decoding_state_; } HttpRequestInternal* testReq() { return req_; } }; @@ -266,112 +265,67 @@ TEST_F(GolangHttpFilterTest, BufferedDataAfterDestroyDuringContinue) { delete filter->testReq(); } -// Helper to set up a mock-based filter for dispatcher post tests. -struct MockFilterContext { - std::shared_ptr> dso_lib; - std::shared_ptr config; +// Regression test for #44704: when the Go plugin calls continueStatus() from inside +// the OnHttpHeader cgo callback, the call must post to the dispatcher rather than +// transition the C++ state machine inline; otherwise handleHeaderGolangStatus trips +// its ASSERT(filterState == ProcessingHeader) once the cgo call returns. +TEST_F(GolangHttpFilterTest, ContinueStatusFromInsideHeaderCgoCallbackPostsToDispatcher) { + auto dso_lib = std::make_shared>(); + ON_CALL(*dso_lib, envoyGoFilterNewHttpPluginConfig(_)).WillByDefault(Return(1)); + + const auto yaml = R"EOF( + library_id: test + library_path: test + plugin_name: test + )EOF"; + envoy::extensions::filters::http::golang::v3alpha::Config proto_config; + TestUtility::loadFromYaml(yaml, proto_config); NiceMock mock_context; - Network::Address::InstanceConstSharedPtr addr; + auto config = std::make_shared(proto_config, dso_lib, "", mock_context); + config->newGoPluginConfig(); + NiceMock mock_callbacks; NiceMock mock_enc_callbacks; NiceMock mock_connection; - std::shared_ptr filter; - - void setup(bool thread_safe) { - dso_lib = std::make_shared>(); - ON_CALL(*dso_lib, envoyGoFilterNewHttpPluginConfig(_)).WillByDefault(Return(1)); - ON_CALL(*dso_lib, envoyGoFilterOnHttpHeader(_, _, _, _)) - .WillByDefault(Return(static_cast(GolangStatus::Running))); - - const auto yaml = R"EOF( - library_id: test - library_path: test - plugin_name: test - )EOF"; - envoy::extensions::filters::http::golang::v3alpha::Config proto_config; - TestUtility::loadFromYaml(yaml, proto_config); - config = std::make_shared(proto_config, dso_lib, "", mock_context); - config->newGoPluginConfig(); - - addr.reset((*Network::Address::PipeInstance::create("/test/test.sock")).release()); - ON_CALL(mock_callbacks, connection()) - .WillByDefault(Return(OptRef{mock_connection})); - mock_connection.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr); - mock_connection.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr); - EXPECT_CALL(mock_callbacks.dispatcher_, isThreadSafe()).WillRepeatedly(Return(thread_safe)); - - filter = std::make_shared(config, dso_lib, 0); - filter->setDecoderFilterCallbacks(mock_callbacks); - filter->setEncoderFilterCallbacks(mock_enc_callbacks); - - Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, - filter->decodeHeaders(request_headers, false)); - } - - void teardown() { - filter->onDestroy(); - delete filter->testReq(); - } -}; - -// Verify that continueStatus executes inline when already on the worker thread, -// bypassing dispatcher.post for lower latency. -TEST_F(GolangHttpFilterTest, ContinueStatusSkipsPostOnWorkerThread) { - MockFilterContext ctx; - ctx.setup(true); - - EXPECT_CALL(ctx.mock_callbacks.dispatcher_, post(_)).Times(0); - EXPECT_CALL(ctx.mock_callbacks, continueDecoding()); - - auto status = ctx.filter->continueStatus(ctx.filter->testDecodingState(), GolangStatus::Continue); - EXPECT_EQ(CAPIStatus::CAPIOK, status); - - ctx.teardown(); -} - -// Verify that continueStatus posts to the dispatcher when not on the worker thread. -TEST_F(GolangHttpFilterTest, ContinueStatusPostsOffWorkerThread) { - MockFilterContext ctx; - ctx.setup(false); - - EXPECT_CALL(ctx.mock_callbacks.dispatcher_, post(_)); - - auto status = ctx.filter->continueStatus(ctx.filter->testDecodingState(), GolangStatus::Continue); - EXPECT_EQ(CAPIStatus::CAPIOK, status); - - ctx.teardown(); -} - -// Verify that sendLocalReply executes inline when already on the worker thread. -TEST_F(GolangHttpFilterTest, SendLocalReplySkipsPostOnWorkerThread) { - MockFilterContext ctx; - ctx.setup(true); + auto addr = (*Network::Address::PipeInstance::create("/test/test.sock")).release(); + Network::Address::InstanceConstSharedPtr addr_ptr(addr); + ON_CALL(mock_callbacks, connection()) + .WillByDefault(Return(OptRef{mock_connection})); + mock_connection.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr_ptr); + mock_connection.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr_ptr); - EXPECT_CALL(ctx.mock_callbacks.dispatcher_, post(_)).Times(0); - EXPECT_CALL(ctx.mock_callbacks, sendLocalReply(Http::Code::BadRequest, "bad request", _, _, _)); + EXPECT_CALL(mock_callbacks.dispatcher_, isThreadSafe()).WillRepeatedly(Return(true)); - auto status = - ctx.filter->sendLocalReply(ctx.filter->testDecodingState(), Http::Code::BadRequest, - "bad request", nullptr, Grpc::Status::WellKnownGrpcStatus::Ok, ""); - EXPECT_EQ(CAPIStatus::CAPIOK, status); + // Capture the posted closure instead of running it inline (the default + // MockDispatcher::post() ON_CALL invokes the callback synchronously, which would + // defeat the point of the regression test). + Event::PostCb captured_post; + EXPECT_CALL(mock_callbacks.dispatcher_, post(_)).WillOnce([&captured_post](Event::PostCb cb) { + captured_post = std::move(cb); + }); - ctx.teardown(); -} + auto filter = std::make_shared(config, dso_lib, 0); + filter->setDecoderFilterCallbacks(mock_callbacks); + filter->setEncoderFilterCallbacks(mock_enc_callbacks); -// Verify that sendLocalReply posts to the dispatcher when not on the worker thread. -TEST_F(GolangHttpFilterTest, SendLocalReplyPostsOffWorkerThread) { - MockFilterContext ctx; - ctx.setup(false); + EXPECT_CALL(*dso_lib, envoyGoFilterOnHttpHeader(_, _, _, _)) + .WillOnce(Invoke([&filter](processState*, int, int, uint64_t) -> uint64_t { + auto status = filter->continueStatus(filter->testDecodingState(), GolangStatus::Continue); + EXPECT_EQ(CAPIStatus::CAPIOK, status); + return static_cast(GolangStatus::Running); + })); - EXPECT_CALL(ctx.mock_callbacks.dispatcher_, post(_)); + // No inline continueDecoding while the cgo frame is on the stack: state must still + // be ProcessingHeader when handleHeaderGolangStatus runs. + EXPECT_CALL(mock_callbacks, continueDecoding()).Times(0); - auto status = - ctx.filter->sendLocalReply(ctx.filter->testDecodingState(), Http::Code::BadRequest, - "bad request", nullptr, Grpc::Status::WellKnownGrpcStatus::Ok, ""); - EXPECT_EQ(CAPIStatus::CAPIOK, status); + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter->decodeHeaders(request_headers, false)); + EXPECT_TRUE(captured_post != nullptr) << "continueStatus must defer via dispatcher.post()"; - ctx.teardown(); + filter->onDestroy(); + delete filter->testReq(); } } // namespace From bd348f4b5c347ede5e4877cf434f0f9a15b0c3d4 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Wed, 20 May 2026 08:55:43 -0400 Subject: [PATCH 353/750] ci: restrict Slack failure notifications to the main envoy repo (#45182) Only send Slack notifications on the envoyproxy/envoy repository, preventing forks and variants from triggering notifications. --------- Signed-off-by: Jonh Wendell --- .github/workflows/_run.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index f9e9723462985..8e39198cddc6f 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -465,7 +465,11 @@ jobs: ENVOY_PUBLISH_DRY_RUN: ${{ (fromJSON(inputs.request).request.version.dev || ! inputs.trusted) && 1 || '' }} - name: Notify Slack on failure - if: ${{ steps.ci-run.outputs.exit-code != 0 && inputs.slack-channel != '' && github.event.workflow_run.event == 'push' }} + if: >- + steps.ci-run.outputs.exit-code != 0 + && inputs.slack-channel != '' + && github.event.workflow_run.event == 'push' + && github.repository == 'envoyproxy/envoy' uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 env: JOB_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} From 7f1e965905a3615dd4e6c22d2e99c06b22d69564 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 20 May 2026 06:30:52 -0700 Subject: [PATCH 354/750] filter_state: remove checks for read-only (#44343) The checks for mutable vs read-only have caused many subtle runtime bugs, and have not provided much benefit. The parameter for read-only vs mutable is now ignored, and will be removed from the codebase in phases. Signed-off-by: Greg Greenway --- .../filters/common/set_filter_state/v3/BUILD | 1 + .../common/set_filter_state/v3/value.proto | 6 +- .../http/http_filters/lua_filter.rst | 2 +- .../advanced/data_sharing_between_filters.rst | 4 +- envoy/stream_info/filter_state.h | 31 +++++---- source/common/network/connection_impl.cc | 2 +- .../common/stream_info/filter_state_impl.cc | 38 ++-------- source/common/stream_info/filter_state_impl.h | 2 +- .../upstream/transport_socket_match_impl.cc | 2 +- .../common/set_filter_state/filter_config.cc | 4 +- .../common/set_filter_state/filter_config.h | 1 - .../io_socket/user_space/io_handle_impl.cc | 4 +- test/common/common/matchers_test.cc | 41 ++++------- test/common/http/async_client_impl_test.cc | 2 +- test/common/http/hash_policy_test.cc | 8 +-- test/common/network/connection_impl_test.cc | 1 - .../transport_socket_options_impl_test.cc | 45 ++++++------ .../stream_info/filter_state_impl_test.cc | 69 +------------------ .../upstream/transport_socket_input_test.cc | 19 +++-- .../upstream/transport_socket_matcher_test.cc | 6 +- .../grpc/grpc_access_log_utils_test.cc | 2 - .../open_telemetry/otlp_log_utils_test.cc | 12 ++-- .../clusters/common/logical_host_test.cc | 3 +- .../filters/common/expr/context_test.cc | 7 +- .../filters/common/rbac/matchers_test.cc | 6 +- .../set_filter_state/filter_config_test.cc | 2 - .../http/jwt_authn/filter_config_test.cc | 8 +-- .../http/jwt_authn/filter_integration_test.cc | 1 - .../original_dst/original_dst_test.cc | 4 -- .../user_space/io_handle_impl_test.cc | 4 +- .../filter_state_override/config_test.cc | 1 - .../network/common/inputs_integration_test.cc | 9 +-- .../matching/network/common/inputs_test.cc | 7 +- .../internal_upstream_test.cc | 4 +- .../cert_selectors/on_demand/config_test.cc | 1 - .../spiffe_validator_integration_test.cc | 1 - .../spiffe/spiffe_validator_test.cc | 1 - 37 files changed, 109 insertions(+), 252 deletions(-) diff --git a/api/envoy/extensions/filters/common/set_filter_state/v3/BUILD b/api/envoy/extensions/filters/common/set_filter_state/v3/BUILD index 504c6c70514ac..a216e596f0ddb 100644 --- a/api/envoy/extensions/filters/common/set_filter_state/v3/BUILD +++ b/api/envoy/extensions/filters/common/set_filter_state/v3/BUILD @@ -6,6 +6,7 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/annotations:pkg", "//envoy/config/core/v3:pkg", "@xds//udpa/annotations:pkg", ], diff --git a/api/envoy/extensions/filters/common/set_filter_state/v3/value.proto b/api/envoy/extensions/filters/common/set_filter_state/v3/value.proto index 054529c546f37..7f2578e876451 100644 --- a/api/envoy/extensions/filters/common/set_filter_state/v3/value.proto +++ b/api/envoy/extensions/filters/common/set_filter_state/v3/value.proto @@ -4,6 +4,7 @@ package envoy.extensions.filters.common.set_filter_state.v3; import "envoy/config/core/v3/substitution_format_string.proto"; +import "envoy/annotations/deprecation.proto"; import "udpa/annotations/status.proto"; import "validate/validate.proto"; @@ -91,9 +92,8 @@ message FilterStateValue { config.core.v3.SubstitutionFormatString format_string = 2; } - // If marked as read-only, the filter state key value is locked, and cannot - // be overridden by any filter, including this filter. - bool read_only = 3; + // This field is deprecated and its value has no effect. + bool read_only = 3 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // Configures the object to be shared with the upstream internal connections. See :ref:`internal upstream // transport ` for more details on the filter state sharing with diff --git a/docs/root/configuration/http/http_filters/lua_filter.rst b/docs/root/configuration/http/http_filters/lua_filter.rst index 7b43ba7e192cc..8754dac5536b9 100644 --- a/docs/root/configuration/http/http_filters/lua_filter.rst +++ b/docs/root/configuration/http/http_filters/lua_filter.rst @@ -1369,7 +1369,7 @@ Sets a filter state object by name using a registered :ref:`object factory ` for the list of available factory keys. * ``payload`` is a string passed to the factory's ``createFromBytes`` method. -The object is stored as read-only with filter chain lifespan and no upstream sharing. +The object is stored with filter chain lifespan and no upstream sharing. Raises a Lua error if the factory key is not registered or if the factory fails to create an object from the given payload. diff --git a/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst b/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst index 9e39057fabd53..50456356cd439 100644 --- a/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst +++ b/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst @@ -87,8 +87,8 @@ connection and HTTP stream (i.e., HTTP request/response pair) respectively. ``StreamInfo`` contains a set of fixed attributes as part of the class definition (e.g., HTTP protocol, requested server name, etc.). In addition, it provides a facility to store typed objects in a map -(``map``). The state stored per filter can be -either write-once (immutable), or write-many (mutable). +(``map``). The state stored per filter is +write-many (mutable). See :ref:`the well-known dynamic metadata ` and :ref:`the well-known filter state ` for the reference diff --git a/envoy/stream_info/filter_state.h b/envoy/stream_info/filter_state.h index 55bc1f21e945f..6fc9650500321 100644 --- a/envoy/stream_info/filter_state.h +++ b/envoy/stream_info/filter_state.h @@ -136,7 +136,6 @@ class FilterState { struct FilterObject { std::shared_ptr data_; - StateType state_type_{StateType::ReadOnly}; StreamSharingMayImpactPooling stream_sharing_{StreamSharingMayImpactPooling::None}; std::string name_; }; @@ -149,24 +148,28 @@ class FilterState { /** * @param data_name the name of the data being set. * @param data an owning pointer to the data to be stored. - * @param state_type indicates whether the object is mutable or not. * @param life_span indicates the life span of the object: bound to the filter chain, a * request, or a connection. * - * Note that it is an error to call setData() twice with the same - * data_name, if the existing object is immutable. Similarly, it is an - * error to call setData() with same data_name but different state_types - * (mutable and readOnly, or readOnly and mutable) or different life_span. - * This is to enforce a single authoritative source for each piece of - * data stored in FilterState. + * Note that it is an error to call setData() twice with the same data_name, but different + * life_span. */ virtual void - setData(absl::string_view data_name, std::shared_ptr data, StateType state_type, + setData(absl::string_view data_name, std::shared_ptr data, LifeSpan life_span = LifeSpan::FilterChain, StreamSharingMayImpactPooling stream_sharing = StreamSharingMayImpactPooling::None) PURE; /** - * @param data_name the name of the data being looked up (mutable/readonly). + * Deprecated version of setData that takes the no longer used `StateType`. + */ + void setData(absl::string_view data_name, std::shared_ptr data, StateType /*state_type*/, + LifeSpan life_span = LifeSpan::FilterChain, + StreamSharingMayImpactPooling stream_sharing = StreamSharingMayImpactPooling::None) { + return setData(data_name, std::move(data), life_span, stream_sharing); + } + + /** + * @param data_name the name of the data being looked up. * @return a typed pointer to the stored data or nullptr if the data does not exist or the data * type does not match the expected type. */ @@ -175,13 +178,13 @@ class FilterState { } /** - * @param data_name the name of the data being looked up (mutable/readonly). + * @param data_name the name of the data being looked up. * @return a const pointer to the stored data or nullptr if the data does not exist. */ virtual const Object* getDataReadOnlyGeneric(absl::string_view data_name) const PURE; /** - * @param data_name the name of the data being looked up (mutable/readonly). + * @param data_name the name of the data being looked up. * @return a typed pointer to the stored data or nullptr if the data does not exist or the data * type does not match the expected type. */ @@ -190,13 +193,13 @@ class FilterState { } /** - * @param data_name the name of the data being looked up (mutable/readonly). + * @param data_name the name of the data being looked up. * @return a pointer to the stored data or nullptr if the data does not exist. */ virtual Object* getDataMutableGeneric(absl::string_view data_name) PURE; /** - * @param data_name the name of the data being looked up (mutable/readonly). + * @param data_name the name of the data being looked up. * @return a shared pointer to the stored data or nullptr if the data does not exist. */ virtual std::shared_ptr getDataSharedMutableGeneric(absl::string_view data_name) PURE; diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index b507d2b5fb109..4a730fa83ed00 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -1124,7 +1124,7 @@ ClientConnectionImpl::ClientConnectionImpl( if (transport_options) { for (const auto& object : transport_options->downstreamSharedFilterStateObjects()) { // This does not throw as all objects are distinctly named and the stream info is empty. - stream_info_.filterState()->setData(object.name_, object.data_, object.state_type_, + stream_info_.filterState()->setData(object.name_, object.data_, StreamInfo::FilterState::LifeSpan::Connection, object.stream_sharing_); } diff --git a/source/common/stream_info/filter_state_impl.cc b/source/common/stream_info/filter_state_impl.cc index 0b608b5b5eed6..5d3776e3dedfb 100644 --- a/source/common/stream_info/filter_state_impl.cc +++ b/source/common/stream_info/filter_state_impl.cc @@ -32,7 +32,7 @@ void FilterStateImpl::maybeCreateParent(FilterStateSharedPtr ancestor) { } void FilterStateImpl::setData(absl::string_view data_name, std::shared_ptr data, - FilterState::StateType state_type, FilterState::LifeSpan life_span, + FilterState::LifeSpan life_span, StreamSharingMayImpactPooling stream_sharing) { if (life_span > life_span_) { if (hasDataWithNameInternally(data_name)) { @@ -46,7 +46,7 @@ void FilterStateImpl::setData(absl::string_view data_name, std::shared_ptrsetData(data_name, data, state_type, life_span, stream_sharing); + parent_->setData(data_name, data, life_span, stream_sharing); return; } if (parent_ && parent_->hasDataWithName(data_name)) { @@ -56,30 +56,9 @@ void FilterStateImpl::setData(absl::string_view data_name, std::shared_ptrsecond.get(); - if (current->state_type_ == FilterState::StateType::ReadOnly) { - IS_ENVOY_BUG(fmt::format("FilterStateAccessViolation: FilterState::setData " - "called twice on same ReadOnly state: {}.", - data_name)); - return; - } - - if (current->state_type_ != state_type) { - IS_ENVOY_BUG(fmt::format("FilterStateAccessViolation: FilterState::setData " - "called twice with different state types: {}.", - data_name)); - return; - } - } std::unique_ptr filter_object(new FilterStateImpl::FilterObject()); filter_object->data_ = data; - filter_object->state_type_ = state_type; filter_object->stream_sharing_ = stream_sharing; data_storage_[data_name] = std::move(filter_object); } @@ -119,14 +98,6 @@ FilterStateImpl::getDataSharedMutableGeneric(absl::string_view data_name) { } FilterStateImpl::FilterObject* current = it->second.get(); - if (current->state_type_ == FilterState::StateType::ReadOnly) { - IS_ENVOY_BUG(fmt::format("FilterStateAccessViolation: FilterState accessed " - "immutable data as mutable: {}.", - data_name)); - // To reduce the chances of a crash, allow the mutation in this case instead of returning a - // nullptr. - } - return current->data_; } @@ -143,11 +114,10 @@ FilterState::ObjectsPtr FilterStateImpl::objectsSharedWithUpstreamConnection() c for (const auto& [name, object] : data_storage_) { switch (object->stream_sharing_) { case StreamSharingMayImpactPooling::SharedWithUpstreamConnection: - objects->push_back({object->data_, object->state_type_, object->stream_sharing_, name}); + objects->push_back({object->data_, object->stream_sharing_, name}); break; case StreamSharingMayImpactPooling::SharedWithUpstreamConnectionOnce: - objects->push_back( - {object->data_, object->state_type_, StreamSharingMayImpactPooling::None, name}); + objects->push_back({object->data_, StreamSharingMayImpactPooling::None, name}); break; default: break; diff --git a/source/common/stream_info/filter_state_impl.h b/source/common/stream_info/filter_state_impl.h index 47390432719ab..e94cba83bdcd8 100644 --- a/source/common/stream_info/filter_state_impl.h +++ b/source/common/stream_info/filter_state_impl.h @@ -34,7 +34,7 @@ class FilterStateImpl : public FilterState { // FilterState void setData( - absl::string_view data_name, std::shared_ptr data, FilterState::StateType state_type, + absl::string_view data_name, std::shared_ptr data, FilterState::LifeSpan life_span = FilterState::LifeSpan::FilterChain, StreamSharingMayImpactPooling stream_sharing = StreamSharingMayImpactPooling::None) override; bool hasDataWithName(absl::string_view) const override; diff --git a/source/common/upstream/transport_socket_match_impl.cc b/source/common/upstream/transport_socket_match_impl.cc index ee12c6ab1bab8..e5f63d53157ed 100644 --- a/source/common/upstream/transport_socket_match_impl.cc +++ b/source/common/upstream/transport_socket_match_impl.cc @@ -143,7 +143,7 @@ TransportSocketMatcher::MatchData TransportSocketMatcherImpl::resolveUsingMatche filter_state = std::make_shared( StreamInfo::FilterState::LifeSpan::Connection); for (const auto& object : shared_objects) { - filter_state->setData(object.name_, object.data_, object.state_type_, + filter_state->setData(object.name_, object.data_, StreamInfo::FilterState::LifeSpan::Connection, object.stream_sharing_); } diff --git a/source/extensions/filters/common/set_filter_state/filter_config.cc b/source/extensions/filters/common/set_filter_state/filter_config.cc index a6cb1a6557cdf..86c17bd72be6c 100644 --- a/source/extensions/filters/common/set_filter_state/filter_config.cc +++ b/source/extensions/filters/common/set_filter_state/filter_config.cc @@ -57,7 +57,6 @@ Config::parse(const Protobuf::RepeatedPtrField& proto_val if (value.factory_ == nullptr) { throw EnvoyException(fmt::format("'{}' does not have an object factory", value.key_)); } - value.state_type_ = proto_value.read_only() ? StateType::ReadOnly : StateType::Mutable; switch (proto_value.shared_with_upstream()) { case FilterStateValueProto::ONCE: value.stream_sharing_ = StreamSharing::SharedWithUpstreamConnectionOnce; @@ -92,8 +91,7 @@ void Config::updateFilterState(const Formatter::Context& context, continue; } ENVOY_LOG(debug, "Created the filter state '{}' from value '{}'", value.key_, bytes_value); - info.filterState()->setData(value.key_, std::move(object), value.state_type_, life_span_, - value.stream_sharing_); + info.filterState()->setData(value.key_, std::move(object), life_span_, value.stream_sharing_); } } diff --git a/source/extensions/filters/common/set_filter_state/filter_config.h b/source/extensions/filters/common/set_filter_state/filter_config.h index 25e048e0cbed0..bcd65288882d5 100644 --- a/source/extensions/filters/common/set_filter_state/filter_config.h +++ b/source/extensions/filters/common/set_filter_state/filter_config.h @@ -22,7 +22,6 @@ using FilterStateValueProto = struct Value { std::string key_; const StreamInfo::FilterState::ObjectFactory* factory_; - StateType state_type_{StateType::ReadOnly}; StreamSharing stream_sharing_{StreamSharing::None}; bool skip_if_empty_; Formatter::FormatterConstSharedPtr value_; diff --git a/source/extensions/io_socket/user_space/io_handle_impl.cc b/source/extensions/io_socket/user_space/io_handle_impl.cc index f5aef8bb84421..42a9442493a70 100644 --- a/source/extensions/io_socket/user_space/io_handle_impl.cc +++ b/source/extensions/io_socket/user_space/io_handle_impl.cc @@ -385,8 +385,8 @@ void PassthroughStateImpl::mergeInto(envoy::config::core::v3::Metadata& metadata } for (const auto& object : filter_state_objects_) { // This should not throw as stream info is new and filter objects are uniquely named. - filter_state.setData(object.name_, object.data_, object.state_type_, - StreamInfo::FilterState::LifeSpan::Connection, object.stream_sharing_); + filter_state.setData(object.name_, object.data_, StreamInfo::FilterState::LifeSpan::Connection, + object.stream_sharing_); } metadata_ = nullptr; filter_state_objects_.clear(); diff --git a/test/common/common/matchers_test.cc b/test/common/common/matchers_test.cc index ea58a99be03b5..b3b128a6f33fb 100644 --- a/test/common/common/matchers_test.cc +++ b/test/common/common/matchers_test.cc @@ -651,8 +651,7 @@ TEST_F(FilterStateMatcher, MatchFilterStateWithoutString) { matcher.set_key(key); matcher.mutable_string_match()->set_exact("exact"); StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); - filter_state.setData(key, std::make_shared(absl::nullopt), - StreamInfo::FilterState::StateType::ReadOnly); + filter_state.setData(key, std::make_shared(absl::nullopt)); auto filter_state_matcher = Matchers::FilterStateMatcher::create(matcher, context_); ASSERT_TRUE(filter_state_matcher.ok()); EXPECT_FALSE((*filter_state_matcher)->match(filter_state)); @@ -666,8 +665,7 @@ TEST_F(FilterStateMatcher, MatchFilterStateDifferentString) { matcher.mutable_string_match()->set_exact(value); StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); filter_state.setData(key, - std::make_shared(absl::make_optional("different")), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_shared(absl::make_optional("different"))); auto filter_state_matcher = Matchers::FilterStateMatcher::create(matcher, context_); ASSERT_TRUE(filter_state_matcher.ok()); EXPECT_FALSE((*filter_state_matcher)->match(filter_state)); @@ -680,8 +678,7 @@ TEST_F(FilterStateMatcher, MatchFilterState) { matcher.set_key(key); matcher.mutable_string_match()->set_exact(value); StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); - filter_state.setData(key, std::make_shared(absl::make_optional(value)), - StreamInfo::FilterState::StateType::ReadOnly); + filter_state.setData(key, std::make_shared(absl::make_optional(value))); auto filter_state_matcher = Matchers::FilterStateMatcher::create(matcher, context_); ASSERT_TRUE(filter_state_matcher.ok()); EXPECT_TRUE((*filter_state_matcher)->match(filter_state)); @@ -701,10 +698,8 @@ TEST_F(FilterStateMatcher, MatchFilterStateAddressMatchIpv4) { StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); filter_state.setData( - key, - std::make_shared( - Envoy::Network::Utility::parseInternetAddressNoThrow("4.5.6.7", 456, false)), - StreamInfo::FilterState::StateType::Mutable); + key, std::make_shared( + Envoy::Network::Utility::parseInternetAddressNoThrow("4.5.6.7", 456, false))); auto filter_state_matcher = Matchers::FilterStateMatcher::create(matcher, context_); ASSERT_TRUE(filter_state_matcher.ok()); @@ -725,10 +720,8 @@ TEST_F(FilterStateMatcher, NoMatchFilterStateAddressMatchIpv4) { StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); filter_state.setData( - key, - std::make_shared( - Envoy::Network::Utility::parseInternetAddressNoThrow("4.5.6.8", 456, false)), - StreamInfo::FilterState::StateType::Mutable); + key, std::make_shared( + Envoy::Network::Utility::parseInternetAddressNoThrow("4.5.6.8", 456, false))); auto filter_state_matcher = Matchers::FilterStateMatcher::create(matcher, context_); ASSERT_TRUE(filter_state_matcher.ok()); @@ -749,10 +742,8 @@ TEST_F(FilterStateMatcher, MatchFilterStateAddressMatchIpv6) { StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); filter_state.setData( - key, - std::make_shared( - Envoy::Network::Utility::parseInternetAddressNoThrow("2001:db8::1", 8080, false)), - StreamInfo::FilterState::StateType::Mutable); + key, std::make_shared( + Envoy::Network::Utility::parseInternetAddressNoThrow("2001:db8::1", 8080, false))); auto filter_state_matcher = Matchers::FilterStateMatcher::create(matcher, context_); ASSERT_TRUE(filter_state_matcher.ok()); @@ -773,10 +764,8 @@ TEST_F(FilterStateMatcher, NoMatchFilterStateAddressMatchIpv6) { StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); filter_state.setData( - key, - std::make_shared( - Envoy::Network::Utility::parseInternetAddressNoThrow("2001:db7::1", 8080, false)), - StreamInfo::FilterState::StateType::Mutable); + key, std::make_shared( + Envoy::Network::Utility::parseInternetAddressNoThrow("2001:db7::1", 8080, false))); auto filter_state_matcher = Matchers::FilterStateMatcher::create(matcher, context_); ASSERT_TRUE(filter_state_matcher.ok()); @@ -859,11 +848,9 @@ TEST_F(FilterStateMatcher, AddressMatchWithInvertMatch) { matcher.mutable_address_match()->set_invert_match(test_case.invert_match); StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); - filter_state.setData( - key, - std::make_shared( - Envoy::Network::Utility::parseInternetAddressNoThrow(test_case.test_ip, 456, false)), - StreamInfo::FilterState::StateType::Mutable); + filter_state.setData(key, std::make_shared( + Envoy::Network::Utility::parseInternetAddressNoThrow( + test_case.test_ip, 456, false))); auto filter_state_matcher = Matchers::FilterStateMatcher::create(matcher, context_); ASSERT_TRUE(filter_state_matcher.ok()); diff --git a/test/common/http/async_client_impl_test.cc b/test/common/http/async_client_impl_test.cc index 7e7e4f5dfa25f..5e9efc0e8a018 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -954,7 +954,7 @@ TEST_F(AsyncClientImplTest, WithFilterState) { auto state_object = std::make_shared("stored-test-state"); auto filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); - filter_state->setData("test-filter", state_object, StreamInfo::FilterState::StateType::Mutable); + filter_state->setData("test-filter", state_object); options.setFilterState(filter_state); auto* request = client_.send(std::move(message_), callbacks_, options); diff --git a/test/common/http/hash_policy_test.cc b/test/common/http/hash_policy_test.cc index 2972ca35926f7..7c440876cc959 100644 --- a/test/common/http/hash_policy_test.cc +++ b/test/common/http/hash_policy_test.cc @@ -246,8 +246,7 @@ TEST_F(HashPolicyImplTest, FilterStateHashForExistingKey) { auto filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::Request); - filter_state->setData("test-key", std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly); + filter_state->setData("test-key", std::make_unique()); testing::NiceMock stream_info; stream_info.filter_state_ = filter_state; @@ -265,8 +264,7 @@ TEST_F(HashPolicyImplTest, FilterStateHashForAbsentKey) { auto filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::Request); - filter_state->setData("another-key", std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly); + filter_state->setData("another-key", std::make_unique()); testing::NiceMock stream_info; stream_info.filter_state_ = filter_state; @@ -283,7 +281,7 @@ TEST_F(HashPolicyImplTest, FilterStateHashForNullFilterState) { auto filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::Request); - filter_state->setData("test-key", nullptr, StreamInfo::FilterState::StateType::ReadOnly); + filter_state->setData("test-key", nullptr); testing::NiceMock stream_info; stream_info.filter_state_ = filter_state; diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index 34bbda1f983c7..e8521f2427961 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -4716,7 +4716,6 @@ TEST_P(ClientConnectionWithCustomRawBufferSocketTest, TransportSocketCallbacks) StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); auto filter_state_object = std::make_shared(); filter_state.setData("test-filter-state", filter_state_object, - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); transport_socket_options_ = TransportSocketOptionsUtility::fromFilterState(filter_state); diff --git a/test/common/network/transport_socket_options_impl_test.cc b/test/common/network/transport_socket_options_impl_test.cc index 3ea359e5405cc..f9e9d95e444f4 100644 --- a/test/common/network/transport_socket_options_impl_test.cc +++ b/test/common/network/transport_socket_options_impl_test.cc @@ -28,8 +28,7 @@ class TransportSocketOptionsImplTest : public testing::Test { EXPECT_EQ(key, factory->name()); auto object = factory->createFromBytes(value); ASSERT_NE(nullptr, object); - filter_state_.setData(key, std::move(object), StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::FilterState::LifeSpan::FilterChain); + filter_state_.setData(key, std::move(object), StreamInfo::FilterState::LifeSpan::FilterChain); } protected: @@ -38,17 +37,17 @@ class TransportSocketOptionsImplTest : public testing::Test { TEST_F(TransportSocketOptionsImplTest, Nullptr) { EXPECT_EQ(nullptr, TransportSocketOptionsUtility::fromFilterState(filter_state_)); - filter_state_.setData( - "random_key_has_no_effect", std::make_unique("www.example.com"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + filter_state_.setData("random_key_has_no_effect", + std::make_unique("www.example.com"), + StreamInfo::FilterState::LifeSpan::FilterChain); EXPECT_EQ(nullptr, TransportSocketOptionsUtility::fromFilterState(filter_state_)); } TEST_F(TransportSocketOptionsImplTest, SharedFilterState) { - filter_state_.setData( - "random_key_has_effect", std::make_unique("www.example.com"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain, - StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); + filter_state_.setData("random_key_has_effect", + std::make_unique("www.example.com"), + StreamInfo::FilterState::LifeSpan::FilterChain, + StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); auto transport_socket_options = TransportSocketOptionsUtility::fromFilterState(filter_state_); auto objects = transport_socket_options->downstreamSharedFilterStateObjects(); EXPECT_EQ(1, objects.size()); @@ -56,16 +55,15 @@ TEST_F(TransportSocketOptionsImplTest, SharedFilterState) { } TEST_F(TransportSocketOptionsImplTest, UpstreamServer) { - filter_state_.setData( - UpstreamServerName::key(), std::make_unique("www.example.com"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + filter_state_.setData(UpstreamServerName::key(), + std::make_unique("www.example.com"), + StreamInfo::FilterState::LifeSpan::FilterChain); filter_state_.setData(ProxyProtocolFilterState::key(), std::make_unique(Network::ProxyProtocolData{ Network::Address::InstanceConstSharedPtr( new Network::Address::Ipv4Instance("202.168.0.13", 52000)), Network::Address::InstanceConstSharedPtr( new Network::Address::Ipv4Instance("174.2.2.222", 80))}), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); auto transport_socket_options = TransportSocketOptionsUtility::fromFilterState(filter_state_); EXPECT_EQ(absl::make_optional("www.example.com"), @@ -78,9 +76,9 @@ TEST_F(TransportSocketOptionsImplTest, UpstreamServer) { TEST_F(TransportSocketOptionsImplTest, ApplicationProtocols) { std::vector http_alpns{Http::Utility::AlpnNames::get().Http2, Http::Utility::AlpnNames::get().Http11}; - filter_state_.setData( - ApplicationProtocols::key(), std::make_unique(http_alpns), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + filter_state_.setData(ApplicationProtocols::key(), + std::make_unique(http_alpns), + StreamInfo::FilterState::LifeSpan::FilterChain); auto transport_socket_options = TransportSocketOptionsUtility::fromFilterState(filter_state_); EXPECT_EQ(absl::nullopt, transport_socket_options->serverNameOverride()); EXPECT_EQ(http_alpns, transport_socket_options->applicationProtocolListOverride()); @@ -89,12 +87,12 @@ TEST_F(TransportSocketOptionsImplTest, ApplicationProtocols) { TEST_F(TransportSocketOptionsImplTest, Both) { std::vector http_alpns{Http::Utility::AlpnNames::get().Http2, Http::Utility::AlpnNames::get().Http11}; - filter_state_.setData( - UpstreamServerName::key(), std::make_unique("www.example.com"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); - filter_state_.setData( - ApplicationProtocols::key(), std::make_unique(http_alpns), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + filter_state_.setData(UpstreamServerName::key(), + std::make_unique("www.example.com"), + StreamInfo::FilterState::LifeSpan::FilterChain); + filter_state_.setData(ApplicationProtocols::key(), + std::make_unique(http_alpns), + StreamInfo::FilterState::LifeSpan::FilterChain); auto transport_socket_options = TransportSocketOptionsUtility::fromFilterState(filter_state_); EXPECT_EQ(absl::make_optional("www.example.com"), transport_socket_options->serverNameOverride()); @@ -119,7 +117,6 @@ class HashableObj : public StreamInfo::FilterState::Object, public Hashable { TEST_F(TransportSocketOptionsImplTest, FilterStateHashable) { filter_state_.setData("hashable", std::make_shared(), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); auto transport_socket_options = TransportSocketOptionsUtility::fromFilterState(filter_state_); @@ -131,7 +128,6 @@ TEST_F(TransportSocketOptionsImplTest, FilterStateHashable) { TEST_F(TransportSocketOptionsImplTest, FilterStateNonHashable) { filter_state_.setData("non-hashable", std::make_shared(), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); auto transport_socket_options = TransportSocketOptionsUtility::fromFilterState(filter_state_); @@ -174,7 +170,6 @@ TEST_F(TransportSocketOptionsImplTest, NetworkNamespaceSharedWithUpstream) { // Set network namespace as shared with upstream connection. filter_state_.setData(DownstreamNetworkNamespace::key(), std::make_unique(network_namespace_filepath), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); diff --git a/test/common/stream_info/filter_state_impl_test.cc b/test/common/stream_info/filter_state_impl_test.cc index ed6ab46a82b0d..87ad739327cab 100644 --- a/test/common/stream_info/filter_state_impl_test.cc +++ b/test/common/stream_info/filter_state_impl_test.cc @@ -157,44 +157,6 @@ TEST_F(FilterStateImplTest, SimpleTypeMutable) { EXPECT_EQ(200, filterState().getDataReadOnly("test_2")->access()); } -TEST_F(FilterStateImplTest, NameConflictReadOnly) { - // read only data cannot be overwritten (by any state type) - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::ReadOnly, - FilterState::LifeSpan::FilterChain); - EXPECT_ENVOY_BUG(filterState().setData("test_1", std::make_unique(2), - FilterState::StateType::ReadOnly, - FilterState::LifeSpan::FilterChain), - "FilterStateAccessViolation: FilterState::setData called twice on " - "same ReadOnly state: test_1."); - EXPECT_ENVOY_BUG(filterState().setData("test_1", std::make_unique(2), - FilterState::StateType::Mutable, - FilterState::LifeSpan::FilterChain), - "FilterStateAccessViolation: FilterState::setData called twice on " - "same ReadOnly state: test_1."); - EXPECT_EQ(1, filterState().getDataReadOnly("test_1")->access()); -} - -TEST_F(FilterStateImplTest, NameConflictDifferentTypesReadOnly) { - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::ReadOnly, - FilterState::LifeSpan::FilterChain); - EXPECT_ENVOY_BUG( - filterState().setData("test_1", std::make_unique(2, nullptr, nullptr), - FilterState::StateType::ReadOnly, FilterState::LifeSpan::FilterChain), - "FilterStateAccessViolation: FilterState::setData called twice on " - "same ReadOnly state: test_1."); -} - -TEST_F(FilterStateImplTest, NameConflictMutableAndReadOnly) { - // Mutable data cannot be overwritten by read only data. - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::Mutable, - FilterState::LifeSpan::FilterChain); - EXPECT_ENVOY_BUG(filterState().setData("test_1", std::make_unique(2), - FilterState::StateType::ReadOnly, - FilterState::LifeSpan::FilterChain), - "FilterStateAccessViolation: FilterState::setData called twice with " - "different state types: test_1."); -} - TEST_F(FilterStateImplTest, NoNameConflictMutableAndMutable) { // Mutable data can be overwritten by another mutable data of same or different type. @@ -226,18 +188,6 @@ TEST_F(FilterStateImplTest, WrongTypeGet) { EXPECT_EQ(nullptr, filterState().getDataReadOnly("test_name")); } -TEST_F(FilterStateImplTest, ErrorAccessingReadOnlyAsMutable) { - // Accessing read only data as mutable should throw error - filterState().setData("test_name", std::make_unique(5, nullptr, nullptr), - FilterState::StateType::ReadOnly, FilterState::LifeSpan::FilterChain); - EXPECT_ENVOY_BUG(filterState().getDataMutable("test_name"), - "FilterStateAccessViolation: FilterState accessed immutable " - "data as mutable: test_name."); - EXPECT_ENVOY_BUG(filterState().getDataSharedMutableGeneric("test_name"), - "FilterStateAccessViolation: FilterState accessed " - "immutable data as mutable: test_name."); -} - namespace { class A : public FilterState::Object {}; @@ -294,16 +244,9 @@ TEST_F(FilterStateImplTest, LifeSpanInitFromParent) { EXPECT_TRUE(new_filter_state.hasDataWithName("test_4")); EXPECT_TRUE(new_filter_state.hasDataWithName("test_5")); EXPECT_TRUE(new_filter_state.hasDataWithName("test_6")); - EXPECT_ENVOY_BUG(new_filter_state.getDataMutable("test_3"), - "FilterStateAccessViolation: FilterState accessed " - "immutable data as mutable: test_3."); - + EXPECT_EQ(3, new_filter_state.getDataMutable("test_3")->access()); EXPECT_EQ(4, new_filter_state.getDataMutable("test_4")->access()); - - EXPECT_ENVOY_BUG(new_filter_state.getDataMutable("test_5"), - "FilterStateAccessViolation: FilterState accessed " - "immutable data as mutable: test_5."); - + EXPECT_EQ(5, new_filter_state.getDataMutable("test_5")->access()); EXPECT_EQ(6, new_filter_state.getDataMutable("test_6")->access()); } @@ -329,9 +272,7 @@ TEST_F(FilterStateImplTest, LifeSpanInitFromGrandparent) { EXPECT_FALSE(new_filter_state.hasDataWithName("test_4")); EXPECT_TRUE(new_filter_state.hasDataWithName("test_5")); EXPECT_TRUE(new_filter_state.hasDataWithName("test_6")); - EXPECT_ENVOY_BUG(new_filter_state.getDataMutable("test_5"), - "FilterStateAccessViolation: FilterState accessed " - "immutable data as mutable: test_5."); + EXPECT_EQ(5, new_filter_state.getDataMutable("test_5")->access()); EXPECT_EQ(6, new_filter_state.getDataMutable("test_6")->access()); } @@ -383,20 +324,16 @@ TEST_F(FilterStateImplTest, SharedWithUpstream) { std::sort(objects->begin(), objects->end(), [](const auto& lhs, const auto& rhs) -> bool { return lhs.name_ < rhs.name_; }); EXPECT_EQ(objects->at(0).name_, "shared_1"); - EXPECT_EQ(objects->at(0).state_type_, FilterState::StateType::ReadOnly); EXPECT_EQ(objects->at(0).stream_sharing_, StreamSharingMayImpactPooling::SharedWithUpstreamConnection); EXPECT_EQ(objects->at(0).data_.get(), shared.get()); EXPECT_EQ(objects->at(1).name_, "shared_4"); - EXPECT_EQ(objects->at(1).state_type_, FilterState::StateType::Mutable); EXPECT_EQ(objects->at(1).stream_sharing_, StreamSharingMayImpactPooling::SharedWithUpstreamConnection); EXPECT_EQ(objects->at(2).name_, "shared_5"); - EXPECT_EQ(objects->at(2).state_type_, FilterState::StateType::ReadOnly); EXPECT_EQ(objects->at(2).stream_sharing_, StreamSharingMayImpactPooling::SharedWithUpstreamConnection); EXPECT_EQ(objects->at(3).name_, "shared_7"); - EXPECT_EQ(objects->at(3).state_type_, FilterState::StateType::ReadOnly); EXPECT_EQ(objects->at(3).stream_sharing_, StreamSharingMayImpactPooling::None); } diff --git a/test/common/upstream/transport_socket_input_test.cc b/test/common/upstream/transport_socket_input_test.cc index 2c51e126e6dab..f4f0e5b7b286a 100644 --- a/test/common/upstream/transport_socket_input_test.cc +++ b/test/common/upstream/transport_socket_input_test.cc @@ -285,10 +285,10 @@ TEST_F(TransportSocketInputTest, FilterStateInput_NoFilterState) { TEST_F(TransportSocketInputTest, FilterStateInput_WithValue) { auto filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::Connection); - filter_state->setData( - "envoy.network.namespace", std::make_shared("namespace-1"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, - StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); + filter_state->setData("envoy.network.namespace", + std::make_shared("namespace-1"), + StreamInfo::FilterState::LifeSpan::Connection, + StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); FilterStateInput input("envoy.network.namespace"); TransportSocketMatchingData data(nullptr, nullptr, filter_state.get()); @@ -301,7 +301,6 @@ TEST_F(TransportSocketInputTest, FilterStateInput_MissingKey) { auto filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::Connection); filter_state->setData("some.other.key", std::make_shared("value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); @@ -317,7 +316,6 @@ TEST_F(TransportSocketInputTest, FilterStateInput_NonSerializable) { auto filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::Connection); filter_state->setData("test.key", std::make_shared(), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); @@ -333,7 +331,6 @@ TEST_F(TransportSocketInputTest, FilterStateInput_EmptyString) { auto filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::Connection); filter_state->setData("test.key", std::make_shared(), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); @@ -360,10 +357,10 @@ TEST_F(TransportSocketInputTest, FilterStateInputFactory) { // Test with filter state. auto filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::Connection); - filter_state->setData( - "envoy.network.namespace", std::make_shared("test-namespace"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, - StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); + filter_state->setData("envoy.network.namespace", + std::make_shared("test-namespace"), + StreamInfo::FilterState::LifeSpan::Connection, + StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); TransportSocketMatchingData data(nullptr, nullptr, filter_state.get()); auto result = input->get(data); diff --git a/test/common/upstream/transport_socket_matcher_test.cc b/test/common/upstream/transport_socket_matcher_test.cc index 33181c2f3ecd2..6bd69110a4d29 100644 --- a/test/common/upstream/transport_socket_matcher_test.cc +++ b/test/common/upstream/transport_socket_matcher_test.cc @@ -648,8 +648,7 @@ name: "namespace_b_socket" // Simulate a filter setting network namespace in downstream filter state. auto ns_object = std::make_shared("/run/netns/namespace-a"); downstream_filter_state->setData( - "envoy.network.namespace", ns_object, StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::FilterState::LifeSpan::Connection, + "envoy.network.namespace", ns_object, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); // Create TransportSocketOptions with the shared filter state objects. @@ -672,8 +671,7 @@ name: "namespace_b_socket" auto ns_object = std::make_shared("/run/netns/namespace-b"); downstream_filter_state->setData( - "envoy.network.namespace", ns_object, StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::FilterState::LifeSpan::Connection, + "envoy.network.namespace", ns_object, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); auto shared_objects = downstream_filter_state->objectsSharedWithUpstreamConnection(); diff --git a/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc b/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc index 057623b45deeb..9aea8af63296b 100644 --- a/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc +++ b/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc @@ -122,7 +122,6 @@ TEST(UtilityExtractCommonAccessLogPropertiesTest, FilterStateFromUpstream) { std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); state->setValue("value_from_upstream_peer"); filter_state->setData("upstream_peer", std::move(state), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); stream_info.upstreamInfo()->setUpstreamFilterState(filter_state); @@ -171,7 +170,6 @@ TEST(UtilityExtractCommonAccessLogPropertiesTest, std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); upstream_state->setValue("value_from_upstream_peer"); filter_state->setData("same_key", std::move(upstream_state), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); stream_info.upstreamInfo()->setUpstreamFilterState(filter_state); diff --git a/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc b/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc index c252da6bf73f9..5227bc85aa59a 100644 --- a/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc +++ b/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc @@ -253,9 +253,9 @@ TEST(OtlpLogUtilsTest, AddFilterStateToAttributesFromUpstream) { auto upstream_filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); - upstream_filter_state->setData( - "upstream_key", std::make_unique("upstream_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + upstream_filter_state->setData("upstream_key", + std::make_unique("upstream_value"), + StreamInfo::FilterState::LifeSpan::FilterChain); stream_info.upstreamInfo()->setUpstreamFilterState(upstream_filter_state); std::vector filter_state_objects = {"upstream_key"}; @@ -279,9 +279,9 @@ TEST(OtlpLogUtilsTest, AddFilterStateToAttributesDownstreamPrecedence) { // Add to upstream. auto upstream_filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::FilterChain); - upstream_filter_state->setData( - "same_key", std::make_unique("upstream_loses"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + upstream_filter_state->setData("same_key", + std::make_unique("upstream_loses"), + StreamInfo::FilterState::LifeSpan::FilterChain); stream_info.upstreamInfo()->setUpstreamFilterState(upstream_filter_state); std::vector filter_state_objects = {"same_key"}; diff --git a/test/extensions/clusters/common/logical_host_test.cc b/test/extensions/clusters/common/logical_host_test.cc index 86ff7c3089c77..13f0503552e4a 100644 --- a/test/extensions/clusters/common/logical_host_test.cc +++ b/test/extensions/clusters/common/logical_host_test.cc @@ -96,8 +96,7 @@ class LogicalHostTransportSocketResolutionTest : public testing::Test { auto filter_state = std::make_shared( StreamInfo::FilterState::LifeSpan::Connection); auto string_accessor = std::make_shared(value); - filter_state->setData(key, string_accessor, StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::FilterState::LifeSpan::Connection, + filter_state->setData(key, string_accessor, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); auto shared_objects = filter_state->objectsSharedWithUpstreamConnection(); return std::make_shared( diff --git a/test/extensions/filters/common/expr/context_test.cc b/test/extensions/filters/common/expr/context_test.cc index 5734afd8cbe23..97883d5a76b26 100644 --- a/test/extensions/filters/common/expr/context_test.cc +++ b/test/extensions/filters/common/expr/context_test.cc @@ -939,7 +939,7 @@ TEST(Context, FilterStateAttributes) { const std::string missing = "missing_key"; auto accessor = std::make_shared(serialized); - filter_state.setData(key, accessor, StreamInfo::FilterState::StateType::ReadOnly); + filter_state.setData(key, accessor); EXPECT_EQ(0, wrapper.size()); @@ -967,7 +967,7 @@ TEST(Context, FilterStateAttributes) { cel_state->setValue(v.SerializeAsString()); EXPECT_TRUE(cel_state->serializeAsString().has_value()); const std::string cel_key = "cel_state_key"; - filter_state.setData(cel_key, cel_state, StreamInfo::FilterState::StateType::ReadOnly); + filter_state.setData(cel_key, cel_state); { auto value = wrapper[CelValue::CreateStringView(cel_key)]; @@ -981,8 +981,7 @@ TEST(Context, FilterStateAttributes) { const std::string port_string = "port"; filter_state.setData(address_key, std::make_unique( - std::make_shared("10.10.11.11", 6666)), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_shared("10.10.11.11", 6666))); { auto value = wrapper[CelValue::CreateStringView(address_key)]; ASSERT_TRUE(value.has_value()); diff --git a/test/extensions/filters/common/rbac/matchers_test.cc b/test/extensions/filters/common/rbac/matchers_test.cc index 55017a62732d3..5383e131eed21 100644 --- a/test/extensions/filters/common/rbac/matchers_test.cc +++ b/test/extensions/filters/common/rbac/matchers_test.cc @@ -808,8 +808,7 @@ TEST(FilterStateMatcher, FilterStateMatcher) { matcher.mutable_string_match()->set_prefix("test"); checkMatcher(FilterStateMatcher(matcher, context), false, conn, header, info); - filter_state.setData("test.key", std::make_shared(), - StreamInfo::FilterState::StateType::ReadOnly); + filter_state.setData("test.key", std::make_shared()); checkMatcher(FilterStateMatcher(matcher, context), true, conn, header, info); } @@ -1194,8 +1193,7 @@ TEST(PrincipalMatcher, UrlPathAndFilterState) { NiceMock info; StreamInfo::FilterStateImpl filter_state_impl(StreamInfo::FilterState::LifeSpan::Connection); - filter_state_impl.setData("test.key", std::make_shared(), - StreamInfo::FilterState::StateType::ReadOnly); + filter_state_impl.setData("test.key", std::make_shared()); EXPECT_CALL(Const(info), filterState()).WillRepeatedly(ReturnRef(filter_state_impl)); auto matcher_filter_state = Matcher::create(principal_filter_state, factory_context); diff --git a/test/extensions/filters/common/set_filter_state/filter_config_test.cc b/test/extensions/filters/common/set_filter_state/filter_config_test.cc index 8483e7462c190..372bfa7ccbb00 100644 --- a/test/extensions/filters/common/set_filter_state/filter_config_test.cc +++ b/test/extensions/filters/common/set_filter_state/filter_config_test.cc @@ -253,7 +253,6 @@ TEST_F(ConfigTest, SetValueUpstreamSharedOnce) { const auto objects = info_.filterState()->objectsSharedWithUpstreamConnection(); EXPECT_EQ(1, objects->size()); EXPECT_EQ(StreamSharing::None, objects->at(0).stream_sharing_); - EXPECT_EQ(StateType::Mutable, objects->at(0).state_type_); EXPECT_EQ("foo", objects->at(0).name_); EXPECT_EQ(foo, objects->at(0).data_.get()); } @@ -275,7 +274,6 @@ TEST_F(ConfigTest, SetValueUpstreamSharedTransitive) { const auto objects = info_.filterState()->objectsSharedWithUpstreamConnection(); EXPECT_EQ(1, objects->size()); EXPECT_EQ(StreamSharing::SharedWithUpstreamConnection, objects->at(0).stream_sharing_); - EXPECT_EQ(StateType::ReadOnly, objects->at(0).state_type_); EXPECT_EQ("foo", objects->at(0).name_); EXPECT_EQ(foo, objects->at(0).data_.get()); } diff --git a/test/extensions/filters/http/jwt_authn/filter_config_test.cc b/test/extensions/filters/http/jwt_authn/filter_config_test.cc index bf81d4fa35607..ce62ea11bdd9c 100644 --- a/test/extensions/filters/http/jwt_authn/filter_config_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_config_test.cc @@ -218,16 +218,15 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByFilterState) { // Wrong selector StreamInfo::FilterStateImpl filter_state2(StreamInfo::FilterState::LifeSpan::FilterChain); - filter_state2.setData( - "jwt_selector", std::make_unique("wrong_selector"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + filter_state2.setData("jwt_selector", + std::make_unique("wrong_selector"), + StreamInfo::FilterState::LifeSpan::FilterChain); EXPECT_TRUE(filter_conf->findVerifier(Http::TestRequestHeaderMapImpl(), filter_state2) == nullptr); // correct selector StreamInfo::FilterStateImpl filter_state3(StreamInfo::FilterState::LifeSpan::FilterChain); filter_state3.setData("jwt_selector", std::make_unique("selector1"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); EXPECT_TRUE(filter_conf->findVerifier(Http::TestRequestHeaderMapImpl(), filter_state3) != nullptr); @@ -235,7 +234,6 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByFilterState) { // correct selector StreamInfo::FilterStateImpl filter_state4(StreamInfo::FilterState::LifeSpan::FilterChain); filter_state4.setData("jwt_selector", std::make_unique("selector2"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); EXPECT_TRUE(filter_conf->findVerifier(Http::TestRequestHeaderMapImpl(), filter_state4) != nullptr); diff --git a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc index 44a21cfcd4563..140ab277de07d 100644 --- a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc @@ -289,7 +289,6 @@ TEST_P(LocalJwksIntegrationTest, FilterStateRequirement) { format_string: text_format_source: inline_string: "%REQ(jwt_selector)%" - read_only: true )"); initialize(); diff --git a/test/extensions/filters/listener/original_dst/original_dst_test.cc b/test/extensions/filters/listener/original_dst/original_dst_test.cc index acc43f0fc0f7c..a803c8181a786 100644 --- a/test/extensions/filters/listener/original_dst/original_dst_test.cc +++ b/test/extensions/filters/listener/original_dst/original_dst_test.cc @@ -86,11 +86,9 @@ TEST_F(OriginalDstTest, InternalFilterState) { const auto remote = Network::Utility::parseInternetAddressNoThrow("127.0.0.1", 8000, false); cb_.filter_state_.setData("envoy.filters.listener.original_dst.local_ip", std::make_shared(local), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); cb_.filter_state_.setData("envoy.filters.listener.original_dst.remote_ip", std::make_shared(remote), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); filter_.onAccept(cb_); EXPECT_TRUE(socket_.connectionInfoProvider().localAddressRestored()); @@ -102,11 +100,9 @@ TEST_F(OriginalDstTest, InternalFilterStateNullAddress) { expectInternalAddress(); cb_.filter_state_.setData("envoy.filters.listener.original_dst.local_ip", std::make_shared(nullptr), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); cb_.filter_state_.setData("envoy.filters.listener.original_dst.remote_ip", std::make_shared(nullptr), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); filter_.onAccept(cb_); EXPECT_FALSE(socket_.connectionInfoProvider().localAddressRestored()); diff --git a/test/extensions/io_socket/user_space/io_handle_impl_test.cc b/test/extensions/io_socket/user_space/io_handle_impl_test.cc index 7a12724d525cd..1fa931daec008 100644 --- a/test/extensions/io_socket/user_space/io_handle_impl_test.cc +++ b/test/extensions/io_socket/user_space/io_handle_impl_test.cc @@ -1191,8 +1191,8 @@ TEST_F(IoHandleImplTest, PassthroughState) { StreamInfo::FilterState::Objects source_filter_state; auto object = std::make_shared(1000); source_filter_state.push_back( - {object, StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection, "object_key"}); + {object, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection, + "object_key"}); ASSERT_NE(nullptr, io_handle_->passthroughState()); io_handle_->passthroughState()->initialize(std::move(source_metadata), source_filter_state); diff --git a/test/extensions/local_address_selectors/filter_state_override/config_test.cc b/test/extensions/local_address_selectors/filter_state_override/config_test.cc index 2920ae1fa6cc7..4f7e8ef383dc3 100644 --- a/test/extensions/local_address_selectors/filter_state_override/config_test.cc +++ b/test/extensions/local_address_selectors/filter_state_override/config_test.cc @@ -54,7 +54,6 @@ constexpr absl::string_view FilterStateKey = Network::TransportSocketOptionsConstSharedPtr optionsWithOverride(absl::string_view netns) { StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); filter_state.setData(FilterStateKey, std::make_shared(netns), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnectionOnce); return Network::TransportSocketOptionsUtility::fromFilterState(filter_state); diff --git a/test/extensions/matching/network/common/inputs_integration_test.cc b/test/extensions/matching/network/common/inputs_integration_test.cc index 1b520fae9e607..8f44ed02c7c41 100644 --- a/test/extensions/matching/network/common/inputs_integration_test.cc +++ b/test/extensions/matching/network/common/inputs_integration_test.cc @@ -209,7 +209,6 @@ TEST_F(InputsIntegrationTest, FilterStateInput) { StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); filter_state.setData(key, std::make_shared(value), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); Network::MockConnectionSocket socket; @@ -294,7 +293,7 @@ TEST_F(InputsIntegrationTest, FilterStateInputWithField) { key, std::make_shared(absl::flat_hash_map{ {"my_field", "field_value"}, {"other_field", "other_value"}}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); Network::MockConnectionSocket socket; envoy::config::core::v3::Metadata metadata; @@ -318,7 +317,7 @@ TEST_F(InputsIntegrationTest, FilterStateInputWithFieldNoMatch) { key, std::make_shared( absl::flat_hash_map{{"my_field", "field_value"}}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); Network::MockConnectionSocket socket; envoy::config::core::v3::Metadata metadata; @@ -343,7 +342,7 @@ TEST_F(InputsIntegrationTest, FilterStateInputWithFieldMissing) { key, std::make_shared( absl::flat_hash_map{{"my_field", "field_value"}}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); Network::MockConnectionSocket socket; envoy::config::core::v3::Metadata metadata; @@ -367,14 +366,12 @@ TEST_F(InputsIntegrationTest, FilterStateInputFailure) { EXPECT_THAT(match_tree_()->match(data), HasNoMatch()); filter_state.setData("unknown_key", std::make_shared(value), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); // Unknown key in filter state - no match EXPECT_THAT(match_tree_()->match(data), HasNoMatch()); filter_state.setData(key, std::make_shared("unknown_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); // Known key in filter state but unknown value - no match diff --git a/test/extensions/matching/network/common/inputs_test.cc b/test/extensions/matching/network/common/inputs_test.cc index fd083ea6e07e4..dcdd26db65d77 100644 --- a/test/extensions/matching/network/common/inputs_test.cc +++ b/test/extensions/matching/network/common/inputs_test.cc @@ -312,7 +312,6 @@ TEST(MatchingData, FilterStateInput) { } filter_state.setData("unknown_key", std::make_shared("some_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); { @@ -323,7 +322,6 @@ TEST(MatchingData, FilterStateInput) { std::string value = "filter_state_value"; filter_state.setData(key, std::make_shared(value), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); { @@ -379,7 +377,7 @@ TEST(MatchingData, FilterStateInputWithField) { key, std::make_shared(absl::flat_hash_map{ {"my_field", "field_value"}, {"other_field", "other_value"}}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); // Should return the specific field value, not the serialized whole object. { @@ -419,7 +417,6 @@ TEST(MatchingData, FilterStateInputWithFieldFallbackToSerialize) { // StringAccessorImpl does NOT support field access. filter_state.setData(key, std::make_shared("plain_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); { @@ -445,7 +442,7 @@ TEST(MatchingData, FilterStateInputWithoutFieldUsesSerialize) { key, std::make_shared( absl::flat_hash_map{{"my_field", "field_value"}}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); { const auto result = input.get(data); diff --git a/test/extensions/transport_sockets/internal_upstream/internal_upstream_test.cc b/test/extensions/transport_sockets/internal_upstream/internal_upstream_test.cc index 88a089ee070c0..0cb5a1f9f3306 100644 --- a/test/extensions/transport_sockets/internal_upstream/internal_upstream_test.cc +++ b/test/extensions/transport_sockets/internal_upstream/internal_upstream_test.cc @@ -81,8 +81,8 @@ TEST_F(InternalSocketTest, NativeSocket) { TEST_F(InternalSocketTest, PassthroughStateInjected) { auto filter_state_object = std::make_shared(); filter_state_objects_.push_back( - {filter_state_object, StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection, "test.object"}); + {filter_state_object, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection, + "test.object"}); Protobuf::Struct& map = (*metadata_->mutable_filter_metadata())["envoy.test"]; Protobuf::Value val; val.set_string_value("val"); diff --git a/test/extensions/transport_sockets/tls/cert_selectors/on_demand/config_test.cc b/test/extensions/transport_sockets/tls/cert_selectors/on_demand/config_test.cc index 241e4f0045d5d..fbc61418be2d2 100644 --- a/test/extensions/transport_sockets/tls/cert_selectors/on_demand/config_test.cc +++ b/test/extensions/transport_sockets/tls/cert_selectors/on_demand/config_test.cc @@ -113,7 +113,6 @@ TEST(FilterStateMapper, Derivation) { auto filter_state_object = std::make_shared("new_value"); StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); filter_state.setData("envoy.tls.certificate_mappers.on_demand_secret", filter_state_object, - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); auto transport_socket_options = diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc index e1a6783802381..5c6e64afb2e8b 100644 --- a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_integration_test.cc @@ -51,7 +51,6 @@ Network::ClientConnectionPtr SslSPIFFECertValidatorIntegrationTest::makeSslClien StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); filter_state.setData("envoy.tls.cert_validator.spiffe.workload_trust_domain", std::make_shared(*workload_trust_domain), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); socket_options = Network::TransportSocketOptionsUtility::fromFilterState(filter_state); diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc index 4bdb2f1fa7f0b..7629929b383aa 100644 --- a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc @@ -561,7 +561,6 @@ name: envoy.tls.cert_validator.spiffe StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); filter_state.setData("envoy.tls.cert_validator.spiffe.workload_trust_domain", std::make_shared("mydomain.org"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); auto socket_options = Network::TransportSocketOptionsUtility::fromFilterState(filter_state); From f87cfd3a86071d614c2d709089a739c694c80080 Mon Sep 17 00:00:00 2001 From: "Antonio V. Leonti" <53806445+antoniovleonti@users.noreply.github.com> Date: Wed, 20 May 2026 11:15:53 -0400 Subject: [PATCH 355/750] gcp_authn: add crypto utility for certificate fingerprinting (#44956) Commit Message: (gcp_authn) add crypto utility for certificate fingerprinting Additional Description: This PR introduces the crypto_utils.h library, which provides the functionality to extract and validate certificate fingerprints from a secret provider. This will later be used to generate bound tokens in this filter. See also: https://github.com/envoyproxy/envoy/pull/44929 Risk Level: none (not used yet) Testing: new unit tests added Docs Changes: none Release Notes: none --------- Signed-off-by: antoniovleonti --- .../extensions/filters/http/gcp_authn/BUILD | 18 ++ .../filters/http/gcp_authn/crypto_utils.cc | 125 +++++++++++ .../filters/http/gcp_authn/crypto_utils.h | 26 +++ test/extensions/filters/http/gcp_authn/BUILD | 20 ++ .../http/gcp_authn/crypto_utils_test.cc | 207 ++++++++++++++++++ 5 files changed, 396 insertions(+) create mode 100644 source/extensions/filters/http/gcp_authn/crypto_utils.cc create mode 100644 source/extensions/filters/http/gcp_authn/crypto_utils.h create mode 100644 test/extensions/filters/http/gcp_authn/crypto_utils_test.cc diff --git a/source/extensions/filters/http/gcp_authn/BUILD b/source/extensions/filters/http/gcp_authn/BUILD index c16d38a602243..74110ed1bd82a 100644 --- a/source/extensions/filters/http/gcp_authn/BUILD +++ b/source/extensions/filters/http/gcp_authn/BUILD @@ -77,3 +77,21 @@ envoy_cc_extension( "@envoy_api//envoy/extensions/filters/http/gcp_authn/v3:pkg_cc_proto", ], ) + +envoy_cc_library( + name = "crypto_utils", + srcs = ["crypto_utils.cc"], + hdrs = ["crypto_utils.h"], + deps = [ + "//envoy/api:api_interface", + "//envoy/secret:secret_provider_interface", + "//source/common/common:assert_lib", + "//source/common/common:base64_lib", + "//source/common/common:logger_lib", + "//source/common/common:matchers_lib", + "//source/common/config:datasource_lib", + "//source/common/http:utility_lib", + "//source/common/tls:utility_lib", + "@abseil-cpp//absl/strings:string_view", + ], +) diff --git a/source/extensions/filters/http/gcp_authn/crypto_utils.cc b/source/extensions/filters/http/gcp_authn/crypto_utils.cc new file mode 100644 index 0000000000000..a73dcfc27e160 --- /dev/null +++ b/source/extensions/filters/http/gcp_authn/crypto_utils.cc @@ -0,0 +1,125 @@ +#include "source/extensions/filters/http/gcp_authn/crypto_utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "envoy/api/api.h" +#include "envoy/secret/secret_provider.h" + +#include "source/common/common/base64.h" +#include "source/common/common/logger.h" +#include "source/common/common/matchers.h" +#include "source/common/common/thread.h" +#include "source/common/config/datasource.h" +#include "source/common/tls/utility.h" + +#include "absl/status/status.h" +#include "absl/status/statusor.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace GcpAuthn { +namespace { + +std::vector getSubjectAltNames(X509* cert) { + std::vector sans = + Envoy::Extensions::TransportSockets::Tls::Utility::getSubjectAltNames(*cert, GEN_URI); + std::vector dns_sans = + Envoy::Extensions::TransportSockets::Tls::Utility::getSubjectAltNames(*cert, GEN_DNS); + sans.insert(sans.end(), dns_sans.begin(), dns_sans.end()); + return sans; +} + +bool validateSubjectAltNames(const std::vector& sans, + const std::vector& san_matchers) { + for (const auto& san : sans) { + for (const auto& matcher : san_matchers) { + if (matcher.match(san)) { + return true; + } + } + } + return false; +} + +absl::StatusOr calculateFingerprint(X509* cert) { + unsigned char* der = nullptr; + int len = i2d_X509(cert, &der); + if (len <= 0) { + ENVOY_LOG_MISC(error, "Failed to get DER encoding of certificate"); + return absl::InternalError("Failed to get DER encoding of certificate"); + } + std::unique_ptr free_der(der, ::OPENSSL_free); + + std::vector digest(SHA256_DIGEST_LENGTH); + SHA256(der, len, digest.data()); + + // Base64 encode unpadded + return Base64::encode(reinterpret_cast(digest.data()), digest.size(), false); +} + +} // namespace + +absl::StatusOr getBase64EncodedCertificateFingerprint( + Secret::TlsCertificateConfigProviderSharedPtr tls_cert_provider, + const std::vector& san_matchers, Api::Api& api) { + // Config::DataSource::read() is blocking and should only be called on the + // main thread. + ASSERT_IS_MAIN_OR_TEST_THREAD(); + + if (san_matchers.empty()) { + return absl::InvalidArgumentError("SAN matchers are empty"); + } + + if (tls_cert_provider == nullptr) { + return absl::InvalidArgumentError("TLS certificate provider is null"); + } + const auto* secret = tls_cert_provider->secret(); + if (secret == nullptr) { + return absl::NotFoundError("Secret is null"); + } + + // Read certificate from secret. + const auto& cert_chain = secret->certificate_chain(); + auto file_content_or_error = Config::DataSource::read(cert_chain, true, api); + if (!file_content_or_error.ok()) { + return absl::InternalError("Failed to read certificate from data source"); + } + std::string file_content = file_content_or_error.value(); + + if (file_content.empty()) { + return absl::InvalidArgumentError("Certificate file content is empty"); + } + + bssl::UniquePtr bio(BIO_new_mem_buf(file_content.data(), file_content.size())); + bssl::UniquePtr cert(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); + if (!cert) { + ENVOY_LOG_MISC(error, "Failed to parse certificate"); + return absl::InvalidArgumentError("Failed to parse certificate"); + } + + std::vector sans = getSubjectAltNames(cert.get()); + if (!validateSubjectAltNames(sans, san_matchers)) { + return absl::InvalidArgumentError("Subject Alternative Names do not match"); + } + + return calculateFingerprint(cert.get()); +} + +} // namespace GcpAuthn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/gcp_authn/crypto_utils.h b/source/extensions/filters/http/gcp_authn/crypto_utils.h new file mode 100644 index 0000000000000..742dc2e429b3b --- /dev/null +++ b/source/extensions/filters/http/gcp_authn/crypto_utils.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +#include "envoy/api/api.h" +#include "envoy/secret/secret_provider.h" + +#include "source/common/common/matchers.h" + +#include "absl/status/statusor.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace GcpAuthn { + +absl::StatusOr getBase64EncodedCertificateFingerprint( + Secret::TlsCertificateConfigProviderSharedPtr tls_cert_provider, + const std::vector& san_matchers, Api::Api& api); + +} // namespace GcpAuthn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/gcp_authn/BUILD b/test/extensions/filters/http/gcp_authn/BUILD index f4cbb1bb0c333..b444eb857230d 100644 --- a/test/extensions/filters/http/gcp_authn/BUILD +++ b/test/extensions/filters/http/gcp_authn/BUILD @@ -108,3 +108,23 @@ envoy_extension_cc_test( "@envoy_api//envoy/extensions/filters/http/gcp_authn/v3:pkg_cc_proto", ], ) + +envoy_extension_cc_test( + name = "crypto_utils_test", + size = "small", + srcs = ["crypto_utils_test.cc"], + data = [ + "//test/config/integration/certs", + ], + extension_names = ["envoy.filters.http.gcp_authn"], + deps = [ + "//envoy/api:api_interface", + "//source/common/common:matchers_lib", + "//source/extensions/filters/http/gcp_authn:crypto_utils", + "//test/mocks/secret:secret_mocks", + "//test/test_common:environment_lib", + "//test/test_common:utility_lib", + "@abseil-cpp//absl/status", + "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/filters/http/gcp_authn/crypto_utils_test.cc b/test/extensions/filters/http/gcp_authn/crypto_utils_test.cc new file mode 100644 index 0000000000000..acb73ca0d8028 --- /dev/null +++ b/test/extensions/filters/http/gcp_authn/crypto_utils_test.cc @@ -0,0 +1,207 @@ +#include +#include +#include +#include + +#include "envoy/api/api.h" +#include "envoy/common/callback.h" +#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/secret.pb.h" +#include "envoy/init/target.h" +#include "envoy/secret/secret_provider.h" + +#include "source/common/common/matchers.h" +#include "source/extensions/filters/http/gcp_authn/crypto_utils.h" + +#include "test/test_common/environment.h" +#include "test/test_common/utility.h" + +#include "absl/status/status.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Secret { + +class MockTlsCertificateConfigProvider : public TlsCertificateConfigProvider { +public: + MockTlsCertificateConfigProvider(); + ~MockTlsCertificateConfigProvider() override; + + MOCK_METHOD(const envoy::extensions::transport_sockets::tls::v3::TlsCertificate*, secret, (), + (const)); + MOCK_METHOD(Common::CallbackHandlePtr, addValidationCallback, + (std::function)); + MOCK_METHOD(Common::CallbackHandlePtr, addUpdateCallback, (std::function)); + MOCK_METHOD(Common::CallbackHandlePtr, addRemoveCallback, (std::function)); + MOCK_METHOD(const Init::Target*, initTarget, ()); + MOCK_METHOD(void, start, ()); +}; + +MockTlsCertificateConfigProvider::MockTlsCertificateConfigProvider() = default; +MockTlsCertificateConfigProvider::~MockTlsCertificateConfigProvider() = default; + +} // namespace Secret + +namespace Extensions { +namespace HttpFilters { +namespace GcpAuthn { +namespace { + +using testing::NiceMock; +using testing::Return; + +class CryptoUtilsTest : public testing::Test { +public: + CryptoUtilsTest() : api_(Api::createApiForTest()) {} + + Api::ApiPtr api_; +}; + +TEST_F(CryptoUtilsTest, GetBase64EncodedCertificateFingerprintSuccess) { + auto secret_provider = std::make_shared>(); + auto secret = std::make_unique(); + + std::string cert_path = + TestEnvironment::runfilesPath("test/config/integration/certs/clientcert.pem"); + secret->mutable_certificate_chain()->set_filename(cert_path); + + EXPECT_CALL(*secret_provider, secret()).WillRepeatedly(Return(secret.get())); + + std::vector san_matchers; + san_matchers.emplace_back( + Matchers::StringMatcherImpl::createExactMatcher("spiffe://lyft.com/frontend-team")); + + auto fingerprint = getBase64EncodedCertificateFingerprint(secret_provider, san_matchers, *api_); + ASSERT_TRUE(fingerprint.ok()); + // SHA256 fingerprint of + // test/config/integration/certs/clientcert.pem + // SPELLCHECKER(off) + EXPECT_EQ(fingerprint.value(), "wH4U/EO5x7PZLxAE+R06ngcdnJOlivx2tMFDA646DzQ"); + // SPELLCHECKER(on) +} + +TEST_F(CryptoUtilsTest, GetBase64EncodedCertificateFingerprintSuccessDnsSan) { + auto secret_provider = std::make_shared>(); + auto secret = std::make_unique(); + + std::string cert_path = + TestEnvironment::runfilesPath("test/config/integration/certs/upstreamlocalhostcert.pem"); + secret->mutable_certificate_chain()->set_filename(cert_path); + + EXPECT_CALL(*secret_provider, secret()).WillRepeatedly(Return(secret.get())); + + std::vector san_matchers; + san_matchers.emplace_back(Matchers::StringMatcherImpl::createExactMatcher("localhost")); + + auto fingerprint = getBase64EncodedCertificateFingerprint(secret_provider, san_matchers, *api_); + ASSERT_TRUE(fingerprint.ok()); +} + +TEST_F(CryptoUtilsTest, GetBase64EncodedCertificateFingerprintNoSecret) { + auto secret_provider = std::make_shared>(); + EXPECT_CALL(*secret_provider, secret()).WillRepeatedly(Return(nullptr)); + + std::vector san_matchers; + san_matchers.emplace_back( + Matchers::StringMatcherImpl::createExactMatcher("spiffe://lyft.com/frontend-team")); + auto fingerprint = getBase64EncodedCertificateFingerprint(secret_provider, san_matchers, *api_); + EXPECT_FALSE(fingerprint.ok()); + EXPECT_EQ(fingerprint.status().message(), "Secret is null"); +} + +TEST_F(CryptoUtilsTest, GetBase64EncodedCertificateFingerprintSanMismatch) { + auto secret_provider = std::make_shared>(); + auto secret = std::make_unique(); + + std::string cert_path = + TestEnvironment::runfilesPath("test/config/integration/certs/clientcert.pem"); + secret->mutable_certificate_chain()->set_filename(cert_path); + + EXPECT_CALL(*secret_provider, secret()).WillRepeatedly(Return(secret.get())); + + std::vector san_matchers; + san_matchers.emplace_back( + Matchers::StringMatcherImpl::createExactMatcher("spiffe://example.com/other-team")); + + auto fingerprint = getBase64EncodedCertificateFingerprint(secret_provider, san_matchers, *api_); + EXPECT_FALSE(fingerprint.ok()); + EXPECT_EQ(fingerprint.status().message(), "Subject Alternative Names do not match"); +} + +TEST_F(CryptoUtilsTest, GetBase64EncodedCertificateFingerprintEmptyMatchers) { + auto secret_provider = std::make_shared>(); + auto secret = std::make_unique(); + + std::string cert_path = + TestEnvironment::runfilesPath("test/config/integration/certs/clientcert.pem"); + secret->mutable_certificate_chain()->set_filename(cert_path); + + EXPECT_CALL(*secret_provider, secret()).WillRepeatedly(Return(secret.get())); + + std::vector san_matchers; // Empty + auto fingerprint = getBase64EncodedCertificateFingerprint(secret_provider, san_matchers, *api_); + EXPECT_FALSE(fingerprint.ok()); + EXPECT_EQ(fingerprint.status().message(), "SAN matchers are empty"); +} + +TEST_F(CryptoUtilsTest, GetBase64EncodedCertificateFingerprintNullProvider) { + std::vector san_matchers; + san_matchers.emplace_back( + Matchers::StringMatcherImpl::createExactMatcher("spiffe://lyft.com/frontend-team")); + auto fingerprint = getBase64EncodedCertificateFingerprint(nullptr, san_matchers, *api_); + EXPECT_FALSE(fingerprint.ok()); + EXPECT_EQ(fingerprint.status().message(), "TLS certificate provider is null"); +} + +TEST_F(CryptoUtilsTest, GetBase64EncodedCertificateFingerprintDataSourceReadFailure) { + auto secret_provider = std::make_shared>(); + auto secret = std::make_unique(); + + secret->mutable_certificate_chain()->set_filename("/nonexistent_file.pem"); + EXPECT_CALL(*secret_provider, secret()).WillRepeatedly(Return(secret.get())); + + std::vector san_matchers; + san_matchers.emplace_back( + Matchers::StringMatcherImpl::createExactMatcher("spiffe://lyft.com/frontend-team")); + auto fingerprint = getBase64EncodedCertificateFingerprint(secret_provider, san_matchers, *api_); + EXPECT_FALSE(fingerprint.ok()); + EXPECT_EQ(fingerprint.status().message(), "Failed to read certificate from data source"); +} + +TEST_F(CryptoUtilsTest, GetBase64EncodedCertificateFingerprintEmptyContent) { + auto secret_provider = std::make_shared>(); + auto secret = std::make_unique(); + + secret->mutable_certificate_chain()->set_inline_string(""); + EXPECT_CALL(*secret_provider, secret()).WillRepeatedly(Return(secret.get())); + + std::vector san_matchers; + san_matchers.emplace_back( + Matchers::StringMatcherImpl::createExactMatcher("spiffe://lyft.com/frontend-team")); + auto fingerprint = getBase64EncodedCertificateFingerprint(secret_provider, san_matchers, *api_); + EXPECT_FALSE(fingerprint.ok()); + EXPECT_EQ(fingerprint.status().message(), "Certificate file content is empty"); +} + +TEST_F(CryptoUtilsTest, GetBase64EncodedCertificateFingerprintParseFailure) { + auto secret_provider = std::make_shared>(); + auto secret = std::make_unique(); + + secret->mutable_certificate_chain()->set_inline_string("invalid cert content"); + EXPECT_CALL(*secret_provider, secret()).WillRepeatedly(Return(secret.get())); + + std::vector san_matchers; + san_matchers.emplace_back( + Matchers::StringMatcherImpl::createExactMatcher("spiffe://lyft.com/frontend-team")); + auto fingerprint = getBase64EncodedCertificateFingerprint(secret_provider, san_matchers, *api_); + EXPECT_FALSE(fingerprint.ok()); + EXPECT_EQ(fingerprint.status().message(), "Failed to parse certificate"); +} + +} // namespace +} // namespace GcpAuthn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy From b25e5bfc781c96a73040774c4457900405cf1c7d Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Wed, 20 May 2026 13:08:42 -0400 Subject: [PATCH 356/750] build: remove yq dependency from envoy_repo repository rule (#45187) The @yq host alias repo creates a relative symlink to a platform-specific repo (e.g. @yq_linux_amd64). In Bazel 8, this symlink target repo is not transitively fetched when the envoy_repo rule resolves the label, causing "No such file or directory" errors on clean builds. Replace the yq invocation with direct Starlark parsing of the config.yml file, which only needs simple key-value pairs from the build-image section. Signed-off-by: Jonh Wendell --- bazel/repo.bzl | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/bazel/repo.bzl b/bazel/repo.bzl index 561c99b71c0f7..92486dce62f17 100644 --- a/bazel/repo.bzl +++ b/bazel/repo.bzl @@ -66,23 +66,29 @@ def _envoy_repo_impl(repository_ctx): """ # parse container information for use in RBE - json_result = repository_ctx.execute([ - repository_ctx.path(repository_ctx.attr.yq), - repository_ctx.path(repository_ctx.attr.envoy_ci_config), - "-ojson", - ]) - if json_result.return_code != 0: - fail("yq failed: {}".format(json_result.stderr)) - repository_ctx.file("ci-config.json", json_result.stdout) - config_data = json.decode(repository_ctx.read("ci-config.json")) + ci_config = repository_ctx.read(repository_ctx.path(repository_ctx.attr.envoy_ci_config)) + build_image = {} + in_build_image = False + for line in ci_config.split("\n"): + if line.strip() == "build-image:": + in_build_image = True + continue + if in_build_image: + if not line.strip() or line.startswith(" #"): + continue + if not line.startswith(" "): + break + parts = line.strip().split(": ", 1) + if len(parts) == 2: + build_image[parts[0]] = parts[1] repository_ctx.file("containers.bzl", CONTAINERS.format( - repo = config_data["build-image"]["repo"], - repo_gcr = config_data["build-image"]["repo-gcr"], - sha = config_data["build-image"]["sha"], - sha_gcc = config_data["build-image"]["sha-gcc"], - sha_mobile = config_data["build-image"]["sha-mobile"], - sha_worker = config_data["build-image"]["sha-worker"], - tag = config_data["build-image"]["tag"], + repo = build_image["repo"], + repo_gcr = build_image["repo-gcr"], + sha = build_image["sha"], + sha_gcc = build_image["sha-gcc"], + sha_mobile = build_image["sha-mobile"], + sha_worker = build_image["sha-worker"], + tag = build_image["tag"], )) repo_version_path = repository_ctx.path(repository_ctx.attr.envoy_version) api_version_path = repository_ctx.path(repository_ctx.attr.envoy_api_version) @@ -305,7 +311,6 @@ _envoy_repo = repository_rule( "envoy_version": attr.label(default = "@envoy//:VERSION.txt"), "envoy_api_version": attr.label(default = "@envoy//:API_VERSION.txt"), "envoy_ci_config": attr.label(default = "@envoy//:.github/config.yml"), - "yq": attr.label(default = "@yq"), }, environ = ["BAZEL_LLVM_PATH", "BAZEL_USE_HOST_SYSROOT"], ) From bccc4204e1f0f1975e5173e6c5f03c87ccb77e21 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 20 May 2026 19:09:51 +0100 Subject: [PATCH 357/750] ci/deps: Fix build image updater (#45191) Signed-off-by: Ryan Northey --- .github/workflows/envoy-dependency.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index a4ca8fd334167..da11bcbb9ee05 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -138,6 +138,23 @@ jobs: with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} + - uses: envoyproxy/toolshed/actions/bind-mounts@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 + if: ! github.event.repository.private + with: + mounts: | + - src: /mnt/workspace + target: GITHUB_WORKSPACE + chown: "runner:runner" + - src: /mnt/runner-cache + target: /home/runner/.cache + chown: "runner:runner" + - name: Free disk space + if: github.event.repository.private + uses: envoyproxy/toolshed/actions/diskspace@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 + with: + to_remove: | + /usr/local/.ghcup + /usr/local/lib/android - uses: envoyproxy/toolshed/actions/github/checkout@1f5b552c6749502b885cb8cf23549b929e745547 # actions-v0.4.17 id: checkout name: Checkout Envoy repository @@ -181,6 +198,9 @@ jobs: echo "ERROR: Could not determine build image tag from envoyproxy/toolshed releases" >&2 exit 1 fi + # Normalize: accept `docker-v0.1.4`, `v0.1.4` or `0.1.4` from inputs.version + CONTAINER_TAG="${CONTAINER_TAG#docker-}" + [[ "$CONTAINER_TAG" == v* ]] || CONTAINER_TAG="v${CONTAINER_TAG}" echo "tag=${CONTAINER_TAG}" >> "$GITHUB_OUTPUT" echo "tag_short=${CONTAINER_TAG}" >> "$GITHUB_OUTPUT" env: From bd77d3e51e88ce468af9b1f32a35f596c6528223 Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Wed, 20 May 2026 20:23:27 +0200 Subject: [PATCH 358/750] [test/integration] Defer fake upstream read enable until initialize (#45029) --- test/integration/fake_upstream.cc | 36 +++++++++++++++++++++++-------- test/integration/fake_upstream.h | 9 ++++++-- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index 72ec3fec08971..f6ea46d6806e4 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -401,8 +401,10 @@ FakeHttpConnection::FakeHttpConnection( Event::TestTimeSystem& time_system, uint32_t max_request_headers_kb, uint32_t max_request_headers_count, envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction - headers_with_underscores_action) + headers_with_underscores_action, + bool deferred_read_enable) : FakeConnectionBase(shared_connection, time_system), type_(type), + deferred_read_enable_(deferred_read_enable), header_validator_factory_( IntegrationUtil::makeHeaderValidationFactory(fakeUpstreamHeaderValidatorConfig())) { ASSERT(max_request_headers_count != 0); @@ -437,6 +439,17 @@ FakeHttpConnection::FakeHttpConnection( Network::ReadFilterSharedPtr{new ReadFilter(*this)}); } +void FakeHttpConnection::initialize() { + FakeConnectionBase::initialize(); + if (deferred_read_enable_ && shared_connection_.connected() && + !shared_connection_.connection().readEnabled()) { + // Re-enable reads that were explicitly deferred by consumeConnection(defer_read_enable=true) + // to ensure the HTTP codec and read filter are fully initialized before processing request + // bytes. This must not re-enable reads when disable_and_do_not_enable_ is active. + shared_connection_.connection().readDisable(false); + } +} + AssertionResult FakeConnectionBase::close(std::chrono::milliseconds timeout) { ENVOY_LOG(trace, "FakeConnectionBase close"); if (!shared_connection_.connected()) { @@ -821,8 +834,10 @@ AssertionResult FakeUpstream::waitForHttpConnection(Event::Dispatcher& client_di return runOnDispatcherThreadAndWait([&]() { absl::MutexLock lock(lock_); connection = std::make_unique( - *this, consumeConnection(), http_type_, time_system_, config_.max_request_headers_kb_, - config_.max_request_headers_count_, config_.headers_with_underscores_action_); + *this, consumeConnection(/*defer_read_enable=*/true), http_type_, time_system_, + config_.max_request_headers_kb_, config_.max_request_headers_count_, + config_.headers_with_underscores_action_, + /*deferred_read_enable=*/read_disable_on_new_connection_ && !disable_and_do_not_enable_); connection->initialize(); return AssertionSuccess(); }); @@ -858,9 +873,11 @@ FakeUpstream::waitForHttpConnection(Event::Dispatcher& client_dispatcher, EXPECT_TRUE(upstream.runOnDispatcherThreadAndWait([&]() { absl::MutexLock lock(upstream.lock_); connection = std::make_unique( - upstream, upstream.consumeConnection(), upstream.http_type_, upstream.timeSystem(), - Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, - envoy::config::core::v3::HttpProtocolOptions::ALLOW); + upstream, upstream.consumeConnection(/*defer_read_enable=*/true), upstream.http_type_, + upstream.timeSystem(), Http::DEFAULT_MAX_REQUEST_HEADERS_KB, + Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW, + /*deferred_read_enable=*/upstream.read_disable_on_new_connection_ && + !upstream.disable_and_do_not_enable_); connection->initialize(); return AssertionSuccess(); })); @@ -929,7 +946,7 @@ void FakeUpstream::convertFromRawToHttp(FakeRawConnectionPtr& raw_connection, raw_connection.release(); } -SharedConnectionWrapper& FakeUpstream::consumeConnection() { +SharedConnectionWrapper& FakeUpstream::consumeConnection(bool defer_read_enable) { ASSERT(!new_connections_.empty()); auto* const connection_wrapper = new_connections_.front().get(); // Skip the thread safety check if the network connection has already been freed since there's no @@ -939,10 +956,11 @@ SharedConnectionWrapper& FakeUpstream::consumeConnection() { connection_wrapper->moveBetweenLists(new_connections_, consumed_connections_); if (read_disable_on_new_connection_ && connection_wrapper->connected() && http_type_ != Http::CodecType::HTTP3 && !disable_and_do_not_enable_) { - // Re-enable read and early close detection. auto& connection = connection_wrapper->connection(); connection.detectEarlyCloseWhenReadDisabled(true); - connection.readDisable(false); + if (!defer_read_enable) { + connection.readDisable(false); + } } return *connection_wrapper; } diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index c64e751d94edd..105060835d655 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -544,7 +544,10 @@ class FakeHttpConnection : public Http::ServerConnectionCallbacks, public FakeCo Http::CodecType type, Event::TestTimeSystem& time_system, uint32_t max_request_headers_kb, uint32_t max_request_headers_count, envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction - headers_with_underscores_action); + headers_with_underscores_action, + bool deferred_read_enable = false); + + void initialize() override; ABSL_MUST_USE_RESULT testing::AssertionResult @@ -606,6 +609,7 @@ class FakeHttpConnection : public Http::ServerConnectionCallbacks, public FakeCo }; const Http::CodecType type_; + bool deferred_read_enable_; Http::ServerConnectionPtr codec_; std::list new_streams_ ABSL_GUARDED_BY(lock_); testing::NiceMock overload_manager_; @@ -1001,7 +1005,8 @@ class FakeUpstream : Logger::Loggable, }; void threadRoutine(); - SharedConnectionWrapper& consumeConnection() ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_); + SharedConnectionWrapper& consumeConnection(bool defer_read_enable = false) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_); Network::FilterStatus onRecvDatagram(Network::UdpRecvData& data); AssertionResult runOnDispatcherThreadAndWait(std::function cb, From 805ceca91446864af17dc3fd34b62347baa52282 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 20 May 2026 11:34:52 -0700 Subject: [PATCH 359/750] filter_state: remove StateType param from all test calls (#45192) This is a follow-up to #44343 Signed-off-by: Greg Greenway --- test/common/common/execution_context_test.cc | 1 - .../formatter/substitution_formatter_test.cc | 29 ++--- test/common/http/conn_manager_impl_test_3.cc | 6 +- test/common/http/matching/inputs_test.cc | 8 +- .../listener_manager_impl_test.cc | 28 ++--- test/common/router/config_impl_test.cc | 13 +- test/common/router/header_formatter_test.cc | 4 - test/common/router/router_test.cc | 10 +- test/common/router/router_test_base.cc | 2 +- .../stream_info/filter_state_impl_test.cc | 114 ++++++++---------- .../stream_info/stream_info_impl_test.cc | 3 +- test/common/tcp_proxy/config_test.cc | 3 +- test/common/tcp_proxy/tcp_proxy_test.cc | 24 ++-- .../default_validator_integration_test.cc | 3 +- .../grpc/grpc_access_log_utils_test.cc | 2 - .../grpc/http_grpc_access_log_impl_test.cc | 6 - .../open_telemetry/otlp_log_utils_test.cc | 4 +- .../substitution_formatter_test.cc | 24 ++-- .../dynamic_forward_proxy/cluster_test.cc | 8 +- .../clusters/dynamic_modules/cluster_test.cc | 9 +- .../original_dst/original_dst_cluster_test.cc | 6 +- .../common/expr/expr_context_speed_test.cc | 2 +- .../set_filter_state/filter_config_test.cc | 3 +- .../filters/http/composite/filter_test.cc | 2 - .../proxy_filter_test.cc | 9 -- .../filters/http/ext_authz/ext_authz_test.cc | 4 +- .../ext_proc/mapped_attribute_builder_test.cc | 3 +- .../http/jwt_authn/jwt_authn_fuzz_test.cc | 1 - .../filters/http/lua/wrappers_test.cc | 50 ++++---- .../proto_api_scrubber/integration_test.cc | 3 +- .../filters/http/ratelimit/ratelimit_test.cc | 9 +- .../filters/http/rbac/rbac_filter_test.cc | 2 +- .../network/geoip/geoip_filter_test.cc | 1 - .../match_delegate_integration_test.cc | 1 - .../network/redis_proxy/router_impl_test.cc | 12 +- .../proxy_filter_test.cc | 6 +- .../udp_proxy/session_filters/buffer_filter.h | 6 +- .../session_filters/drainer_filter.h | 2 +- .../dynamic_forward_proxy/dfp_setter.h | 6 +- .../proxy_filter_test.cc | 12 +- .../udp_proxy/session_filters/psc_setter.h | 3 +- .../udp/udp_proxy/udp_proxy_filter_test.cc | 15 +-- test/extensions/formatter/cel/cel_test.cc | 3 +- .../http_11_proxy/connect_test.cc | 1 - .../spiffe/spiffe_validator_test.cc | 2 +- .../upstreams/tcp/generic/config_test.cc | 4 +- .../filters/encoder_recreate_stream_filter.cc | 2 +- .../filters/header_to_proxy_filter.cc | 1 - .../filters/test_listener_filter.h | 7 +- .../integration/tcp_proxy_integration_test.cc | 1 - .../upstream_access_log_integration_test.cc | 3 +- 51 files changed, 183 insertions(+), 300 deletions(-) diff --git a/test/common/common/execution_context_test.cc b/test/common/common/execution_context_test.cc index 79a8db92766f6..3aae7343a02e9 100644 --- a/test/common/common/execution_context_test.cc +++ b/test/common/common/execution_context_test.cc @@ -76,7 +76,6 @@ class ExecutionContextTest : public testing::Test { stream_info_.filter_state_ = std::make_shared( StreamInfo::FilterState::LifeSpan::Connection); stream_info_.filter_state_->setData(kConnectionExecutionContextFilterStateName, context_, - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); } diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index fb49f1f9bee55..be0aa78d13ecd 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -3769,22 +3769,17 @@ TEST(SubstitutionFormatterTest, FilterStateFormatter) { StreamInfo::MockStreamInfo stream_info; stream_info.filter_state_->setData("key", - std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique("test_value")); stream_info.filter_state_->setData("key-struct", - std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique()); stream_info.filter_state_->setData("key-no-serialization", - std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique()); stream_info.filter_state_->setData( "key-serialization-error", - std::make_unique(std::chrono::seconds(-281474976710656)), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique(std::chrono::seconds(-281474976710656))); stream_info.filter_state_->setData( - "test_key", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + "test_key", std::make_unique("test_value")); EXPECT_CALL(Const(stream_info), filterState()).Times(testing::AtLeast(1)); { @@ -4828,11 +4823,9 @@ TEST(SubstitutionFormatterTest, JsonFormatterFilterStateTest) { StreamInfo::MockStreamInfo stream_info; stream_info.filter_state_->setData("test_key", - std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique("test_value")); stream_info.filter_state_->setData("test_obj", - std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique()); EXPECT_CALL(Const(stream_info), filterState()).Times(testing::AtLeast(1)); const std::string expected_json_map = R"EOF( @@ -4861,8 +4854,7 @@ TEST(SubstitutionFormatterTest, FilterStateSpeciferTest) { NiceMock stream_info; stream_info.filter_state_->setData( - "test_key", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + "test_key", std::make_unique("test_value")); stream_info.upstream_info_->setUpstreamFilterState( stream_info.filter_state_); // Reuse the same filter state for test only. EXPECT_CALL(Const(stream_info), filterState()).Times(testing::AtLeast(1)); @@ -4898,8 +4890,7 @@ TEST(SubstitutionFormatterTest, FilterStateErrorSpeciferTest) { StreamInfo::MockStreamInfo stream_info; stream_info.filter_state_->setData( - "test_key", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + "test_key", std::make_unique("test_value")); // 'ABCDE' is error specifier. Protobuf::Struct key_mapping; @@ -5167,11 +5158,9 @@ TEST(SubstitutionFormatterTest, CompositeFormatterSuccess) { EXPECT_CALL(Const(stream_info), filterState()).Times(testing::AtLeast(1)); stream_info.filter_state_->setData("testing", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); stream_info.filter_state_->setData("serialized", std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); const std::string format = "%FILTER_STATE(testing)%|%FILTER_STATE(serialized)%|" "%FILTER_STATE(testing):8%|%FILTER_STATE(nonexisting)%"; diff --git a/test/common/http/conn_manager_impl_test_3.cc b/test/common/http/conn_manager_impl_test_3.cc index cbb50fa3838de..e380fdda916ab 100644 --- a/test/common/http/conn_manager_impl_test_3.cc +++ b/test/common/http/conn_manager_impl_test_3.cc @@ -1669,8 +1669,7 @@ class SimpleType : public StreamInfo::FilterState::Object { TEST_F(HttpConnectionManagerImplTest, ConnectionFilterState) { filter_callbacks_.connection_.stream_info_.filter_state_->setData( - "connection_provided_data", std::make_shared(555), - StreamInfo::FilterState::StateType::ReadOnly); + "connection_provided_data", std::make_shared(555)); setup(SetupOpts().setTracing(false)); setupFilterChain(1, 0, /* num_requests = */ 3); @@ -1690,15 +1689,12 @@ TEST_F(HttpConnectionManagerImplTest, ConnectionFilterState) { .WillOnce(Invoke([this](HeaderMap&, bool) -> FilterHeadersStatus { decoder_filters_[0]->callbacks_->streamInfo().filterState()->setData( "per_filter_chain", std::make_unique(1), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); decoder_filters_[0]->callbacks_->streamInfo().filterState()->setData( "per_downstream_request", std::make_unique(2), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request); decoder_filters_[0]->callbacks_->streamInfo().filterState()->setData( "per_downstream_connection", std::make_unique(3), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); return FilterHeadersStatus::StopIteration; })); diff --git a/test/common/http/matching/inputs_test.cc b/test/common/http/matching/inputs_test.cc index 1a7b290633f44..456bf06216f7c 100644 --- a/test/common/http/matching/inputs_test.cc +++ b/test/common/http/matching/inputs_test.cc @@ -184,9 +184,9 @@ TEST(MatchingData, FilterStateInput) { EXPECT_EQ(result.stringData(), absl::nullopt); } - stream_info.filterState()->setData( - "unknown_key", std::make_shared("some_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + stream_info.filterState()->setData("unknown_key", + std::make_shared("some_value"), + StreamInfo::FilterState::LifeSpan::Connection); { Network::Matching::FilterStateInput input("filter_state_key"); @@ -197,7 +197,7 @@ TEST(MatchingData, FilterStateInput) { stream_info.filterState()->setData( "filter_state_key", std::make_shared("filter_state_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); { Network::Matching::FilterStateInput input("filter_state_key"); diff --git a/test/common/listener_manager/listener_manager_impl_test.cc b/test/common/listener_manager/listener_manager_impl_test.cc index 7f02091b3adf7..6e77014d3f371 100644 --- a/test/common/listener_manager/listener_manager_impl_test.cc +++ b/test/common/listener_manager/listener_manager_impl_test.cc @@ -4214,17 +4214,17 @@ TEST_P(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithFilterStateM auto filter_chain = findFilterChain(1234, "127.0.0.1", "", "", {}, "8.8.8.8", 111); EXPECT_EQ(filter_chain, nullptr); - stream_info_.filterState()->setData( - "unknown_key", std::make_shared("unknown_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + stream_info_.filterState()->setData("unknown_key", + std::make_shared("unknown_value"), + StreamInfo::FilterState::LifeSpan::Connection); // Filter state set to a non-matching key - no match. filter_chain = findFilterChain(1234, "127.0.0.1", "", "", {}, "8.8.8.8", 111); EXPECT_EQ(filter_chain, nullptr); - stream_info_.filterState()->setData( - "filter_state_key", std::make_shared("unknown_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + stream_info_.filterState()->setData("filter_state_key", + std::make_shared("unknown_value"), + StreamInfo::FilterState::LifeSpan::Connection); // Filter state set to a matching key but unknown value - no match. filter_chain = findFilterChain(1234, "127.0.0.1", "", "", {}, "8.8.8.8", 111); @@ -4232,7 +4232,7 @@ TEST_P(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithFilterStateM stream_info_.filterState()->setData( "filter_state_key", std::make_shared("filter_state_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); // Known filter state key and matching value - using 1st filter chain. filter_chain = findFilterChain(1234, "127.0.0.1", "", "", {}, "8.8.8.8", 111); @@ -5363,18 +5363,18 @@ TEST_P(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithFilterSta ASSERT_NE(filter_chain, nullptr); EXPECT_EQ(filter_chain->name(), "foo"); - stream_info_.filterState()->setData( - "unknown_key", std::make_shared("unknown_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + stream_info_.filterState()->setData("unknown_key", + std::make_shared("unknown_value"), + StreamInfo::FilterState::LifeSpan::Connection); // Filter state set to a non-matching key - no match. filter_chain = findFilterChain(1234, "127.0.0.1", "", "", {}, "8.8.8.8", 111); ASSERT_NE(filter_chain, nullptr); EXPECT_EQ(filter_chain->name(), "foo"); - stream_info_.filterState()->setData( - "filter_state_key", std::make_shared("unknown_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + stream_info_.filterState()->setData("filter_state_key", + std::make_shared("unknown_value"), + StreamInfo::FilterState::LifeSpan::Connection); // Filter state set to a matching key but unknown value - no match. filter_chain = findFilterChain(1234, "127.0.0.1", "", "", {}, "8.8.8.8", 111); @@ -5383,7 +5383,7 @@ TEST_P(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithFilterSta stream_info_.filterState()->setData( "filter_state_key", std::make_shared("filter_state_value"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); // Known filter state key and matching value - using 1st filter chain. filter_chain = findFilterChain(1234, "127.0.0.1", "", "", {}, "8.8.8.8", 111); diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index f75f303f3250f..d15ed1e5266dd 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -579,9 +579,9 @@ TEST_F(RouteMatcherTest, TestConnectRoutes) { EXPECT_EQ("http://bat4.com/new_path", redirect->newUri(headers)); } - stream_info.filterState()->setData( - Router::OriginalConnectPort::key(), std::make_unique(10), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request); + stream_info.filterState()->setData(Router::OriginalConnectPort::key(), + std::make_unique(10), + StreamInfo::FilterState::LifeSpan::Request); // Port addition for CONNECT without port { Http::TestRequestHeaderMapImpl headers = @@ -1370,7 +1370,6 @@ TEST_F(RouteMatcherTest, TestRoutes) { auto address_obj = std::make_unique( Envoy::Network::Utility::parseInternetAddressNoThrow("10.0.0.1", 443, false)); stream_info.filterState()->setData("envoy.address", std::move(address_obj), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request); EXPECT_EQ("filter_state_cluster", config.route(genHeaders("foo.com", "/", "GET"), stream_info, 0) @@ -2479,7 +2478,6 @@ class HeaderTransformsDoFormattingTest : public RouteMatcherTest { std::make_shared( Envoy::StreamInfo::FilterState::LifeSpan::FilterChain)); filter_state->setData("testing", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); ON_CALL(stream_info, filterState()).WillByDefault(ReturnRef(filter_state)); ON_CALL(Const(stream_info), filterState()).WillByDefault(ReturnRef(*filter_state)); @@ -3650,13 +3648,10 @@ class RouterMatcherFilterStateHashPolicyTest : public RouterMatcherHashPolicyTes : filter_state_(std::make_shared( StreamInfo::FilterState::LifeSpan::FilterChain)) { - filter_state_->setData("null-value", nullptr, StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::FilterState::LifeSpan::FilterChain); + filter_state_->setData("null-value", nullptr, StreamInfo::FilterState::LifeSpan::FilterChain); filter_state_->setData("nonhashable", std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); filter_state_->setData("hashable", std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); } class NonHashable : public StreamInfo::FilterState::Object {}; diff --git a/test/common/router/header_formatter_test.cc b/test/common/router/header_formatter_test.cc index cd9acd6969c02..a1f4db7bebbcc 100644 --- a/test/common/router/header_formatter_test.cc +++ b/test/common/router/header_formatter_test.cc @@ -199,7 +199,6 @@ TEST(HeaderParserTest, TestParse) { std::make_shared( Envoy::StreamInfo::FilterState::LifeSpan::FilterChain)); filter_state->setData("testing", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); ON_CALL(stream_info, filterState()).WillByDefault(ReturnRef(filter_state)); ON_CALL(Const(stream_info), filterState()).WillByDefault(ReturnRef(*filter_state)); @@ -616,7 +615,6 @@ request_headers_to_remove: ["x-nope"] std::make_shared( Envoy::StreamInfo::FilterState::LifeSpan::FilterChain)); filter_state->setData("testing", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); ON_CALL(stream_info, filterState()).WillByDefault(ReturnRef(filter_state)); ON_CALL(Const(stream_info), filterState()).WillByDefault(ReturnRef(*filter_state)); @@ -1084,7 +1082,6 @@ response_headers_to_remove: ["x-baz-header"] std::make_shared( Envoy::StreamInfo::FilterState::LifeSpan::FilterChain)); filter_state->setData("testing", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); ON_CALL(stream_info, filterState()).WillByDefault(ReturnRef(filter_state)); ON_CALL(Const(stream_info), filterState()).WillByDefault(ReturnRef(*filter_state)); @@ -1129,7 +1126,6 @@ response_headers_to_remove: ["x-baz-header"] std::make_shared( Envoy::StreamInfo::FilterState::LifeSpan::FilterChain)); filter_state->setData("testing", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); ON_CALL(stream_info, filterState()).WillByDefault(ReturnRef(filter_state)); ON_CALL(Const(stream_info), filterState()).WillByDefault(ReturnRef(*filter_state)); diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 5807b365daf44..fde22ffb29147 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -190,7 +190,6 @@ class RouterTest : public RouterTestBase { // tls_inspector by using the LifeSpan::Connection stream_info.filterState()->setData(Network::UpstreamServerName::key(), std::make_unique(pre_set_sni), - StreamInfo::FilterState::StateType::Mutable, pre_set_life_span); } expectResponseTimerCreate(); @@ -1572,7 +1571,6 @@ TEST_F(RouterTest, EnvoyAttemptCountInResponseWithRetries) { TEST_F(RouterTest, AllDebugConfig) { auto debug_config = DebugConfigFactory().createFromBytes("true"); callbacks_.streamInfo().filterState()->setData(DebugConfig::key(), std::move(debug_config), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); cm_.thread_local_cluster_.conn_pool_.host_->hostname_ = "scooby.doo"; @@ -6616,7 +6614,7 @@ TEST_F(RouterTest, PropagatesUpstreamFilterState) { upstream_stream_info_.filterState()->setData( "upstream data", std::make_unique(123), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); expectResponseTimerCreate(); expectNewStreamWithImmediateEncoder(encoder, &response_decoder, Http::Protocol::Http10); @@ -7694,7 +7692,7 @@ TEST_F(RouterTest, RedirectRecords) { router_->downstream_connection_.stream_info_.filterState()->setData( Network::UpstreamSocketOptionsFilterState::key(), std::make_unique(), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); router_->downstream_connection_.stream_info_.filterState() ->getDataMutable( Network::UpstreamSocketOptionsFilterState::key()) @@ -7721,7 +7719,7 @@ TEST_F(RouterTest, ApplicationProtocols) { callbacks_.streamInfo().filterState()->setData( Network::ApplicationProtocols::key(), std::make_unique(std::vector{"foo", "bar"}), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + StreamInfo::FilterState::LifeSpan::FilterChain); EXPECT_CALL(cm_.thread_local_cluster_, httpConnPool(_, _, _, _)) .WillOnce(Invoke([&](Upstream::HostConstSharedPtr, Upstream::ResourcePriority, @@ -7958,7 +7956,7 @@ TEST_F(RouterTest, Http3DisabledForHttp11Proxies) { callbacks_.stream_info_.filterState()->setData( Network::Http11ProxyInfoFilterState::key(), std::make_unique(hostname, address), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + StreamInfo::FilterState::LifeSpan::FilterChain); testRequestResponse(true, false); } diff --git a/test/common/router/router_test_base.cc b/test/common/router/router_test_base.cc index 605d381ed1a46..780cebf3fe2ad 100644 --- a/test/common/router/router_test_base.cc +++ b/test/common/router/router_test_base.cc @@ -191,7 +191,7 @@ void RouterTestBase::setNumPreviousRedirect(uint32_t num_previous_redirects) { callbacks_.streamInfo().filterState()->setData( "num_internal_redirects", std::make_shared(num_previous_redirects), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); + StreamInfo::FilterState::LifeSpan::Request); } void RouterTestBase::setIncludeAttemptCountInRequest(bool include) { diff --git a/test/common/stream_info/filter_state_impl_test.cc b/test/common/stream_info/filter_state_impl_test.cc index 87ad739327cab..09dfa16b9a49c 100644 --- a/test/common/stream_info/filter_state_impl_test.cc +++ b/test/common/stream_info/filter_state_impl_test.cc @@ -67,7 +67,7 @@ TEST_F(FilterStateImplTest, Simple) { size_t destruction_count = 0u; filterState().setData( "test_name", std::make_unique(5, &access_count, &destruction_count), - FilterState::StateType::ReadOnly, FilterState::LifeSpan::FilterChain); + FilterState::LifeSpan::FilterChain); EXPECT_EQ(0u, access_count); EXPECT_EQ(0u, destruction_count); @@ -85,7 +85,7 @@ TEST_F(FilterStateImplTest, SharedPointerAccessor) { size_t destruction_count = 0u; filterState().setData( "test_name", std::make_shared(5, &access_count, &destruction_count), - FilterState::StateType::Mutable, FilterState::LifeSpan::FilterChain); + FilterState::LifeSpan::FilterChain); EXPECT_EQ(0u, access_count); EXPECT_EQ(0u, destruction_count); @@ -113,11 +113,11 @@ TEST_F(FilterStateImplTest, SameTypes) { filterState().setData( "test_1", std::make_unique(ValueOne, &access_count_1, &destruction_count), - FilterState::StateType::ReadOnly, FilterState::LifeSpan::FilterChain); + FilterState::LifeSpan::FilterChain); filterState().setData( "test_2", std::make_unique(ValueTwo, &access_count_2, &destruction_count), - FilterState::StateType::ReadOnly, FilterState::LifeSpan::FilterChain); + FilterState::LifeSpan::FilterChain); EXPECT_EQ(0u, access_count_1); EXPECT_EQ(0u, access_count_2); EXPECT_EQ(0u, destruction_count); @@ -133,9 +133,9 @@ TEST_F(FilterStateImplTest, SameTypes) { } TEST_F(FilterStateImplTest, SimpleTypeReadOnly) { - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::ReadOnly, + filterState().setData("test_1", std::make_unique(1), FilterState::LifeSpan::FilterChain); - filterState().setData("test_2", std::make_unique(2), FilterState::StateType::ReadOnly, + filterState().setData("test_2", std::make_unique(2), FilterState::LifeSpan::FilterChain); EXPECT_EQ(1, filterState().getDataReadOnly("test_1")->access()); @@ -143,9 +143,9 @@ TEST_F(FilterStateImplTest, SimpleTypeReadOnly) { } TEST_F(FilterStateImplTest, SimpleTypeMutable) { - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::Mutable, + filterState().setData("test_1", std::make_unique(1), FilterState::LifeSpan::FilterChain); - filterState().setData("test_2", std::make_unique(2), FilterState::StateType::Mutable, + filterState().setData("test_2", std::make_unique(2), FilterState::LifeSpan::FilterChain); EXPECT_EQ(1, filterState().getDataReadOnly("test_1")->access()); @@ -161,17 +161,17 @@ TEST_F(FilterStateImplTest, NoNameConflictMutableAndMutable) { // Mutable data can be overwritten by another mutable data of same or different type. // mutable + mutable - same type - filterState().setData("test_2", std::make_unique(3), FilterState::StateType::Mutable, + filterState().setData("test_2", std::make_unique(3), FilterState::LifeSpan::FilterChain); - filterState().setData("test_2", std::make_unique(4), FilterState::StateType::Mutable, + filterState().setData("test_2", std::make_unique(4), FilterState::LifeSpan::FilterChain); EXPECT_EQ(4, filterState().getDataMutable("test_2")->access()); // mutable + mutable - different types - filterState().setData("test_4", std::make_unique(7), FilterState::StateType::Mutable, + filterState().setData("test_4", std::make_unique(7), FilterState::LifeSpan::FilterChain); filterState().setData("test_4", std::make_unique(8, nullptr, nullptr), - FilterState::StateType::Mutable, FilterState::LifeSpan::FilterChain); + FilterState::LifeSpan::FilterChain); EXPECT_EQ(8, filterState().getDataReadOnly("test_4")->access()); } @@ -183,7 +183,7 @@ TEST_F(FilterStateImplTest, UnknownName) { TEST_F(FilterStateImplTest, WrongTypeGet) { filterState().setData("test_name", std::make_unique(5, nullptr, nullptr), - FilterState::StateType::ReadOnly, FilterState::LifeSpan::FilterChain); + FilterState::LifeSpan::FilterChain); EXPECT_EQ(5, filterState().getDataReadOnly("test_name")->access()); EXPECT_EQ(nullptr, filterState().getDataReadOnly("test_name")); } @@ -199,21 +199,19 @@ class C : public B {}; } // namespace TEST_F(FilterStateImplTest, FungibleInheritance) { - filterState().setData("testB", std::make_unique(), FilterState::StateType::ReadOnly, - FilterState::LifeSpan::FilterChain); + filterState().setData("testB", std::make_unique(), FilterState::LifeSpan::FilterChain); EXPECT_TRUE(filterState().hasData("testB")); EXPECT_TRUE(filterState().hasData("testB")); EXPECT_FALSE(filterState().hasData("testB")); - filterState().setData("testC", std::make_unique(), FilterState::StateType::ReadOnly, - FilterState::LifeSpan::FilterChain); + filterState().setData("testC", std::make_unique(), FilterState::LifeSpan::FilterChain); EXPECT_TRUE(filterState().hasData("testC")); EXPECT_TRUE(filterState().hasData("testC")); EXPECT_TRUE(filterState().hasData("testC")); } TEST_F(FilterStateImplTest, HasData) { - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::ReadOnly, + filterState().setData("test_1", std::make_unique(1), FilterState::LifeSpan::FilterChain); EXPECT_TRUE(filterState().hasData("test_1")); EXPECT_FALSE(filterState().hasData("test_2")); @@ -224,17 +222,15 @@ TEST_F(FilterStateImplTest, HasData) { } TEST_F(FilterStateImplTest, LifeSpanInitFromParent) { - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::ReadOnly, + filterState().setData("test_1", std::make_unique(1), FilterState::LifeSpan::FilterChain); - filterState().setData("test_2", std::make_unique(2), FilterState::StateType::Mutable, + filterState().setData("test_2", std::make_unique(2), FilterState::LifeSpan::FilterChain); - filterState().setData("test_3", std::make_unique(3), FilterState::StateType::ReadOnly, - FilterState::LifeSpan::Request); - filterState().setData("test_4", std::make_unique(4), FilterState::StateType::Mutable, - FilterState::LifeSpan::Request); - filterState().setData("test_5", std::make_unique(5), FilterState::StateType::ReadOnly, + filterState().setData("test_3", std::make_unique(3), FilterState::LifeSpan::Request); + filterState().setData("test_4", std::make_unique(4), FilterState::LifeSpan::Request); + filterState().setData("test_5", std::make_unique(5), FilterState::LifeSpan::Connection); - filterState().setData("test_6", std::make_unique(6), FilterState::StateType::Mutable, + filterState().setData("test_6", std::make_unique(6), FilterState::LifeSpan::Connection); FilterStateImpl new_filter_state(filterState().parent(), FilterState::LifeSpan::FilterChain); @@ -251,17 +247,15 @@ TEST_F(FilterStateImplTest, LifeSpanInitFromParent) { } TEST_F(FilterStateImplTest, LifeSpanInitFromGrandparent) { - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::ReadOnly, + filterState().setData("test_1", std::make_unique(1), FilterState::LifeSpan::FilterChain); - filterState().setData("test_2", std::make_unique(2), FilterState::StateType::Mutable, + filterState().setData("test_2", std::make_unique(2), FilterState::LifeSpan::FilterChain); - filterState().setData("test_3", std::make_unique(3), FilterState::StateType::ReadOnly, - FilterState::LifeSpan::Request); - filterState().setData("test_4", std::make_unique(4), FilterState::StateType::Mutable, - FilterState::LifeSpan::Request); - filterState().setData("test_5", std::make_unique(5), FilterState::StateType::ReadOnly, + filterState().setData("test_3", std::make_unique(3), FilterState::LifeSpan::Request); + filterState().setData("test_4", std::make_unique(4), FilterState::LifeSpan::Request); + filterState().setData("test_5", std::make_unique(5), FilterState::LifeSpan::Connection); - filterState().setData("test_6", std::make_unique(6), FilterState::StateType::Mutable, + filterState().setData("test_6", std::make_unique(6), FilterState::LifeSpan::Connection); FilterStateImpl new_filter_state(filterState().parent()->parent(), @@ -277,17 +271,15 @@ TEST_F(FilterStateImplTest, LifeSpanInitFromGrandparent) { } TEST_F(FilterStateImplTest, LifeSpanInitFromNonParent) { - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::ReadOnly, + filterState().setData("test_1", std::make_unique(1), FilterState::LifeSpan::FilterChain); - filterState().setData("test_2", std::make_unique(2), FilterState::StateType::Mutable, + filterState().setData("test_2", std::make_unique(2), FilterState::LifeSpan::FilterChain); - filterState().setData("test_3", std::make_unique(3), FilterState::StateType::ReadOnly, - FilterState::LifeSpan::Request); - filterState().setData("test_4", std::make_unique(4), FilterState::StateType::Mutable, - FilterState::LifeSpan::Request); - filterState().setData("test_5", std::make_unique(5), FilterState::StateType::ReadOnly, + filterState().setData("test_3", std::make_unique(3), FilterState::LifeSpan::Request); + filterState().setData("test_4", std::make_unique(4), FilterState::LifeSpan::Request); + filterState().setData("test_5", std::make_unique(5), FilterState::LifeSpan::Connection); - filterState().setData("test_6", std::make_unique(6), FilterState::StateType::Mutable, + filterState().setData("test_6", std::make_unique(6), FilterState::LifeSpan::Connection); FilterStateImpl new_filter_state(filterState().parent(), FilterState::LifeSpan::Request); @@ -301,23 +293,20 @@ TEST_F(FilterStateImplTest, LifeSpanInitFromNonParent) { TEST_F(FilterStateImplTest, SharedWithUpstream) { auto shared = std::make_shared(1); - filterState().setData("shared_1", shared, FilterState::StateType::ReadOnly, - FilterState::LifeSpan::FilterChain, + filterState().setData("shared_1", shared, FilterState::LifeSpan::FilterChain, StreamSharingMayImpactPooling::SharedWithUpstreamConnection); - filterState().setData("test_2", std::make_shared(2), FilterState::StateType::Mutable, + filterState().setData("test_2", std::make_shared(2), FilterState::LifeSpan::FilterChain); - filterState().setData("test_3", std::make_shared(3), FilterState::StateType::ReadOnly, - FilterState::LifeSpan::Request); - filterState().setData("shared_4", std::make_shared(4), - FilterState::StateType::Mutable, FilterState::LifeSpan::Request, + filterState().setData("test_3", std::make_shared(3), FilterState::LifeSpan::Request); + filterState().setData("shared_4", std::make_shared(4), FilterState::LifeSpan::Request, StreamSharingMayImpactPooling::SharedWithUpstreamConnection); filterState().setData("shared_5", std::make_shared(5), - FilterState::StateType::ReadOnly, FilterState::LifeSpan::Connection, + FilterState::LifeSpan::Connection, StreamSharingMayImpactPooling::SharedWithUpstreamConnection); - filterState().setData("test_6", std::make_shared(6), FilterState::StateType::Mutable, + filterState().setData("test_6", std::make_shared(6), FilterState::LifeSpan::Connection); filterState().setData("shared_7", std::make_shared(7), - FilterState::StateType::ReadOnly, FilterState::LifeSpan::Connection, + FilterState::LifeSpan::Connection, StreamSharingMayImpactPooling::SharedWithUpstreamConnectionOnce); auto objects = filterState().objectsSharedWithUpstreamConnection(); EXPECT_EQ(objects->size(), 4); @@ -338,19 +327,18 @@ TEST_F(FilterStateImplTest, SharedWithUpstream) { } TEST_F(FilterStateImplTest, HasDataAtOrAboveLifeSpan) { - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::ReadOnly, + filterState().setData("test_1", std::make_unique(1), FilterState::LifeSpan::FilterChain); EXPECT_TRUE(filterState().hasDataAtOrAboveLifeSpan(FilterState::LifeSpan::FilterChain)); EXPECT_FALSE(filterState().hasDataAtOrAboveLifeSpan(FilterState::LifeSpan::Request)); EXPECT_FALSE(filterState().hasDataAtOrAboveLifeSpan(FilterState::LifeSpan::Connection)); - filterState().setData("test_2", std::make_unique(2), FilterState::StateType::ReadOnly, - FilterState::LifeSpan::Request); + filterState().setData("test_2", std::make_unique(2), FilterState::LifeSpan::Request); EXPECT_TRUE(filterState().hasDataAtOrAboveLifeSpan(FilterState::LifeSpan::FilterChain)); EXPECT_TRUE(filterState().hasDataAtOrAboveLifeSpan(FilterState::LifeSpan::Request)); EXPECT_FALSE(filterState().hasDataAtOrAboveLifeSpan(FilterState::LifeSpan::Connection)); - filterState().setData("test_3", std::make_unique(3), FilterState::StateType::ReadOnly, + filterState().setData("test_3", std::make_unique(3), FilterState::LifeSpan::Connection); EXPECT_TRUE(filterState().hasDataAtOrAboveLifeSpan(FilterState::LifeSpan::FilterChain)); EXPECT_TRUE(filterState().hasDataAtOrAboveLifeSpan(FilterState::LifeSpan::Request)); @@ -358,44 +346,38 @@ TEST_F(FilterStateImplTest, HasDataAtOrAboveLifeSpan) { } TEST_F(FilterStateImplTest, SetSameDataWithDifferentLifeSpan) { - filterState().setData("test_1", std::make_unique(1), FilterState::StateType::Mutable, + filterState().setData("test_1", std::make_unique(1), FilterState::LifeSpan::Connection); // Test reset on smaller LifeSpan EXPECT_ENVOY_BUG(filterState().setData("test_1", std::make_unique(2), - FilterState::StateType::Mutable, FilterState::LifeSpan::FilterChain), "FilterStateAccessViolation: FilterState::setData called twice with " "conflicting life_span on the same data_name: test_1."); Assert::resetEnvoyBugCountersForTest(); EXPECT_ENVOY_BUG(filterState().setData("test_1", std::make_unique(2), - FilterState::StateType::Mutable, FilterState::LifeSpan::Request), "FilterStateAccessViolation: FilterState::setData called twice with " "conflicting life_span on the same data_name: test_1."); // Still mutable on the correct LifeSpan. - filterState().setData("test_1", std::make_unique(2), FilterState::StateType::Mutable, + filterState().setData("test_1", std::make_unique(2), FilterState::LifeSpan::Connection); EXPECT_EQ(2, filterState().getDataMutable("test_1")->access()); - filterState().setData("test_2", std::make_unique(1), FilterState::StateType::Mutable, - FilterState::LifeSpan::Request); + filterState().setData("test_2", std::make_unique(1), FilterState::LifeSpan::Request); // Test reset on smaller and greater LifeSpan EXPECT_ENVOY_BUG(filterState().setData("test_2", std::make_unique(2), - FilterState::StateType::Mutable, FilterState::LifeSpan::FilterChain), "FilterStateAccessViolation: FilterState::setData called twice with " "conflicting life_span on the same data_name: test_2."); Assert::resetEnvoyBugCountersForTest(); EXPECT_ENVOY_BUG(filterState().setData("test_2", std::make_unique(2), - FilterState::StateType::Mutable, FilterState::LifeSpan::Connection), "FilterStateAccessViolation: FilterState::setData called twice with " "conflicting life_span on the same data_name: test_2."); // Still mutable on the correct LifeSpan. - filterState().setData("test_2", std::make_unique(2), FilterState::StateType::Mutable, - FilterState::LifeSpan::Request); + filterState().setData("test_2", std::make_unique(2), FilterState::LifeSpan::Request); EXPECT_EQ(2, filterState().getDataMutable("test_2")->access()); } diff --git a/test/common/stream_info/stream_info_impl_test.cc b/test/common/stream_info/stream_info_impl_test.cc index 9a75043a627e5..3cb1c2a1bee83 100644 --- a/test/common/stream_info/stream_info_impl_test.cc +++ b/test/common/stream_info/stream_info_impl_test.cc @@ -342,7 +342,6 @@ TEST_F(StreamInfoImplTest, MiscSettersAndGetters) { EXPECT_EQ(route.get(), stream_info.route().ptr()); stream_info.filterState()->setData("test", std::make_unique(1), - FilterState::StateType::ReadOnly, FilterState::LifeSpan::FilterChain); EXPECT_EQ(1, stream_info.filterState()->getDataReadOnly("test")->access()); @@ -445,7 +444,7 @@ TEST_F(StreamInfoImplTest, SetFrom) { s1.route_ = std::make_shared>(); s1.setDynamicMetadata("com.test", MessageUtil::keyValueStruct("test_key", "test_value")); s1.filterState()->setData("test", std::make_unique(1), - FilterState::StateType::ReadOnly, FilterState::LifeSpan::FilterChain); + FilterState::LifeSpan::FilterChain); Http::TestRequestHeaderMapImpl headers1; s1.setRequestHeaders(headers1); Upstream::ClusterInfoConstSharedPtr cluster_info(new NiceMock()); diff --git a/test/common/tcp_proxy/config_test.cc b/test/common/tcp_proxy/config_test.cc index fabed839dbdc1..abc3c9c335f29 100644 --- a/test/common/tcp_proxy/config_test.cc +++ b/test/common/tcp_proxy/config_test.cc @@ -713,7 +713,7 @@ TEST(ConfigTest, PerConnectionClusterWithTopLevelMetadataMatchConfig) { NiceMock connection; connection.stream_info_.filterState()->setData( "envoy.tcp_proxy.cluster", std::make_unique("filter_state_cluster"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); const auto route = config_obj.getRouteFromEntries(connection); EXPECT_NE(nullptr, route); @@ -1003,7 +1003,6 @@ TEST_F(TcpProxyHashingTest, HashWithFilterState) { { initializeFilter(); connection_.stream_info_.filter_state_->setData("foo", std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_, tcpConnPool(_, _, _)) diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 4aaccc5fdd48b..236ab9f2ff9db 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -101,7 +101,6 @@ class TcpProxyTest : public TcpProxyTestBase { filter_callbacks_.connection().streamInfo().filterState()->setData( TcpProxy::ReceiveBeforeConnectKey, std::make_unique(receive_before_connect), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); // Create and initialize filter. @@ -175,7 +174,6 @@ class TcpProxyTest : public TcpProxyTestBase { filter_callbacks_.connection_.streamInfo().filterState()->setData( Network::UpstreamSocketOptionsFilterState::key(), std::make_unique(), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); filter_callbacks_.connection_.streamInfo() .filterState() @@ -188,7 +186,6 @@ class TcpProxyTest : public TcpProxyTestBase { filter_callbacks_.connection().streamInfo().filterState()->setData( TcpProxy::ReceiveBeforeConnectKey, std::make_unique(receive_before_connect), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); filter_ = std::make_unique(config_, @@ -1487,7 +1484,7 @@ TEST_P(TcpProxyTest, IdleTimeoutWithFilterStateOverride) { filter_callbacks_.connection_.streamInfo().filterState()->setData( TcpProxy::PerConnectionIdleTimeoutMs, std::make_unique(idle_timeout_override), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); Event::MockTimer* idle_timer = new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(idle_timeout_override), _)); @@ -1972,7 +1969,7 @@ TEST_P(TcpProxyTest, ShareFilterState) { upstream_connections_.at(0)->streamInfo().filterState()->setData( "envoy.tcp_proxy.cluster", std::make_unique("filter_state_cluster"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); raiseEventUpstreamConnected(0); EXPECT_EQ("filter_state_cluster", filter_callbacks_.connection_.streamInfo() @@ -2563,7 +2560,7 @@ TEST_P(TcpProxyTest, SetDynamicTLVWithFilterState) { // Set filter state on the connection streamInfo. filter_callbacks_.connection_.stream_info_.filter_state_->setData( "test.key", std::make_unique("filter_state_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); setup(1, config); raiseEventUpstreamConnected(0); @@ -3009,7 +3006,6 @@ class TcpProxyTlsHandshakeTest : public TcpProxyTest { filter_callbacks_.connection().streamInfo().filterState()->setData( TcpProxy::ReceiveBeforeConnectKey, std::make_unique(receive_before_connect), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); // Create and initialize filter. @@ -3098,7 +3094,7 @@ TEST_P(TcpProxyTlsHandshakeTest, filter_callbacks_.connection_.stream_info_.filter_state_->setData( Envoy::TcpProxy::PerConnectionCluster::key(), std::make_shared("state_fake_cluster"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); // Set up connection pool expectations for when TLS handshake completes. EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_, @@ -3402,7 +3398,7 @@ TEST_P(TcpProxyTest, DelayRouteSelectionAllowsFilterStateChanges) { filter_callbacks_.connection_.stream_info_.filter_state_->setData( Envoy::TcpProxy::PerConnectionCluster::key(), std::make_shared("state_fake_cluster"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_, tcpConnPool(_, _, _)) @@ -3528,7 +3524,7 @@ TEST_P(TcpProxyTest, MergeWithDownstreamTlvsWithDownstreamState) { Network::ProxyProtocolDataWithVersion{ {downstream_src_addr, downstream_dst_addr, downstream_tlvs}, Network::ProxyProtocolVersion::V2}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); setup(1, config); raiseEventUpstreamConnected(0); @@ -3583,7 +3579,7 @@ TEST_P(TcpProxyTest, MergeWithDownstreamTlvsPrecedence) { Network::ProxyProtocolDataWithVersion{ {downstream_src_addr, downstream_dst_addr, downstream_tlvs}, Network::ProxyProtocolVersion::V2}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); setup(1, config); raiseEventUpstreamConnected(0); @@ -3629,7 +3625,7 @@ TEST_P(TcpProxyTest, NoMergeWithDownstreamTlvsWhenDisabled) { Network::ProxyProtocolDataWithVersion{ {downstream_src_addr, downstream_dst_addr, downstream_tlvs}, Network::ProxyProtocolVersion::V2}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); setup(1, config); raiseEventUpstreamConnected(0); @@ -3678,7 +3674,7 @@ TEST_P(TcpProxyTest, MergeWithDownstreamTlvsWithDynamicTlv) { Network::ProxyProtocolDataWithVersion{ {downstream_src_addr, downstream_dst_addr, downstream_tlvs}, Network::ProxyProtocolVersion::V2}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); setup(1, config); raiseEventUpstreamConnected(0); @@ -3730,7 +3726,7 @@ TEST_P(TcpProxyTest, AppendToDownstreamTlvsPreservesDuplicates) { Network::ProxyProtocolDataWithVersion{ {downstream_src_addr, downstream_dst_addr, downstream_tlvs}, Network::ProxyProtocolVersion::V2}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); setup(1, config); raiseEventUpstreamConnected(0); diff --git a/test/common/tls/cert_validator/default_validator_integration_test.cc b/test/common/tls/cert_validator/default_validator_integration_test.cc index 3a3547bb06868..96983f3e70b5f 100644 --- a/test/common/tls/cert_validator/default_validator_integration_test.cc +++ b/test/common/tls/cert_validator/default_validator_integration_test.cc @@ -151,8 +151,7 @@ TEST_P(SslCertValidatorIntegrationTest, CertValidationFailedDepthWithTrustRootOn class TestSanListenerFilter : public Network::ListenerFilter { public: Network::FilterStatus onAccept(Network::ListenerFilterCallbacks& cb) override { - cb.filterState().setData("test_san_filter_state", nullptr, - StreamInfo::FilterState::StateType::ReadOnly); + cb.filterState().setData("test_san_filter_state", nullptr); return Network::FilterStatus::Continue; } size_t maxReadBytes() const override { return 0; } diff --git a/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc b/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc index 9aea8af63296b..7512ee76c850b 100644 --- a/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc +++ b/test/extensions/access_loggers/grpc/grpc_access_log_utils_test.cc @@ -83,7 +83,6 @@ TEST(UtilityExtractCommonAccessLogPropertiesTest, FilterStateFromDownstream) { auto state = std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); state->setValue("value_from_downstream_peer"); stream_info.filter_state_->setData("downstream_peer", std::move(state), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); Formatter::Context formatter_context; @@ -161,7 +160,6 @@ TEST(UtilityExtractCommonAccessLogPropertiesTest, std::make_unique<::Envoy::Extensions::Filters::Common::Expr::CelState>(prototype); downstream_state->setValue("value_from_downstream_peer"); stream_info.filter_state_->setData("same_key", std::move(downstream_state), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); auto upstream_state = diff --git a/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc b/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc index 244b7c5b8049b..f2126d8bf2ea5 100644 --- a/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc +++ b/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc @@ -241,14 +241,11 @@ TEST_F(HttpGrpcAccessLogTest, Marshalling) { (*stream_info.metadata_.mutable_filter_metadata())["foo"] = Protobuf::Struct(); stream_info.filter_state_->setData("string_accessor", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); stream_info.filter_state_->setData("uint32_accessor", std::make_unique(42), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); stream_info.filter_state_->setData("serialized", std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); stream_info.onRequestComplete(); @@ -811,14 +808,11 @@ response: {} (*stream_info.metadata_.mutable_filter_metadata())["foo"] = Protobuf::Struct(); stream_info.filter_state_->setData("string_accessor", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); stream_info.filter_state_->setData("uint32_accessor", std::make_unique(42), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); stream_info.filter_state_->setData("serialized", std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); expectLog(R"EOF( diff --git a/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc b/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc index 5227bc85aa59a..00b3669c0ccc7 100644 --- a/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc +++ b/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc @@ -235,7 +235,7 @@ TEST(OtlpLogUtilsTest, AddFilterStateToAttributesFromDownstream) { stream_info.filter_state_->setData( "downstream_key", std::make_unique("downstream_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + StreamInfo::FilterState::LifeSpan::FilterChain); std::vector filter_state_objects = {"downstream_key"}; addFilterStateToAttributes(stream_info, filter_state_objects, log_entry); @@ -274,7 +274,7 @@ TEST(OtlpLogUtilsTest, AddFilterStateToAttributesDownstreamPrecedence) { // Add to downstream. stream_info.filter_state_->setData( "same_key", std::make_unique("downstream_wins"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + StreamInfo::FilterState::LifeSpan::FilterChain); // Add to upstream. auto upstream_filter_state = diff --git a/test/extensions/access_loggers/open_telemetry/substitution_formatter_test.cc b/test/extensions/access_loggers/open_telemetry/substitution_formatter_test.cc index 262af866d17ef..e5b4f9e93a643 100644 --- a/test/extensions/access_loggers/open_telemetry/substitution_formatter_test.cc +++ b/test/extensions/access_loggers/open_telemetry/substitution_formatter_test.cc @@ -653,11 +653,9 @@ TEST(SubstitutionFormatterTest, OpenTelemetryFormatterClusterMetadataNoClusterIn TEST(SubstitutionFormatterTest, OpenTelemetryFormatterFilterStateTest) { StreamInfo::MockStreamInfo stream_info; stream_info.filter_state_->setData("test_key", - std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique("test_value")); stream_info.filter_state_->setData("test_obj", - std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique()); EXPECT_CALL(Const(stream_info), filterState()).Times(testing::AtLeast(1)); OpenTelemetryFormatMap expected = {{"test_key", "\"test_value\""}, @@ -686,10 +684,8 @@ TEST(SubstitutionFormatterTest, OpenTelemetryFormatterUpstreamFilterStateTest) { const StreamInfo::FilterStateSharedPtr upstream_filter_state = std::make_shared(StreamInfo::FilterState::LifeSpan::Request); upstream_filter_state->setData("test_key", - std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); - upstream_filter_state->setData("test_obj", std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique("test_value")); + upstream_filter_state->setData("test_obj", std::make_unique()); EXPECT_CALL(stream_info, upstreamInfo()).Times(testing::AtLeast(1)); // Get pointer to MockUpstreamInfo. @@ -724,8 +720,7 @@ TEST(SubstitutionFormatterTest, OpenTelemetryFormatterUpstreamFilterStateTest) { TEST(SubstitutionFormatterTest, OpenTelemetryFormatterFilterStateSpeciferTest) { StreamInfo::MockStreamInfo stream_info; stream_info.filter_state_->setData( - "test_key", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + "test_key", std::make_unique("test_value")); EXPECT_CALL(Const(stream_info), filterState()).Times(testing::AtLeast(1)); OpenTelemetryFormatMap expected = { @@ -759,8 +754,7 @@ TEST(SubstitutionFormatterTest, OpenTelemetryFormatterUpstreamFilterStateSpecife std::make_shared(StreamInfo::FilterState::LifeSpan::Request)); stream_info.upstream_info_->upstreamFilterState()->setData( - "test_key", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + "test_key", std::make_unique("test_value")); EXPECT_CALL(Const(stream_info), upstreamInfo()).Times(testing::AtLeast(1)); @@ -790,8 +784,7 @@ TEST(SubstitutionFormatterTest, OpenTelemetryFormatterFilterStateErrorSpeciferTe StreamInfo::MockStreamInfo stream_info; std::string body; stream_info.filter_state_->setData( - "test_key", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + "test_key", std::make_unique("test_value")); // 'ABCDE' is error specifier. KeyValueList key_mapping; @@ -821,8 +814,7 @@ TEST(SubstitutionFormatterTest, OpenTelemetryFormatterUpstreamFilterStateErrorSp std::make_shared(StreamInfo::FilterState::LifeSpan::Request)); stream_info.upstream_info_->upstreamFilterState()->setData( - "test_key", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + "test_key", std::make_unique("test_value")); // 'ABCDE' is error specifier. KeyValueList key_mapping; diff --git a/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc b/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc index 7f346e01677e2..fbf29197f219b 100644 --- a/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc +++ b/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc @@ -125,10 +125,10 @@ class ClusterTest : public testing::Test, Upstream::MockLoadBalancerContext* setFilterStateHostAndReturnContext(const std::string& host) { StreamInfo::FilterStateSharedPtr filter_state = lb_context_.requestStreamInfo()->filterState(); - filter_state->setData( - "envoy.upstream.dynamic_host", std::make_shared(host), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection, - StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); + filter_state->setData("envoy.upstream.dynamic_host", + std::make_shared(host), + StreamInfo::FilterState::LifeSpan::Connection, + StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); return &lb_context_; } diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index 1ca55f05236cc..672022632d34e 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -2180,8 +2180,7 @@ TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateBytesNotFound) { TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateBytesFound) { NiceMock context; NiceMock stream_info; - stream_info.filter_state_->setData("k", std::make_unique("v"), - StreamInfo::FilterState::StateType::ReadOnly); + stream_info.filter_state_->setData("k", std::make_unique("v")); ON_CALL(context, requestStreamInfo()).WillByDefault(Return(&stream_info)); auto* context_ptr = static_cast(&context); std::string key = "k"; @@ -2240,8 +2239,7 @@ TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateTypedNotFound) { TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateTypedNotSerializable) { NiceMock context; NiceMock stream_info; - stream_info.filter_state_->setData("k", std::make_unique(), - StreamInfo::FilterState::StateType::ReadOnly); + stream_info.filter_state_->setData("k", std::make_unique()); ON_CALL(context, requestStreamInfo()).WillByDefault(Return(&stream_info)); auto* context_ptr = static_cast(&context); std::string key = "k"; @@ -2255,8 +2253,7 @@ TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateTypedNotSerializable) { TEST_F(DynamicModuleClusterTest, LbContextGetFilterStateTypedFound) { NiceMock context; NiceMock stream_info; - stream_info.filter_state_->setData("k", std::make_unique("typed-v"), - StreamInfo::FilterState::StateType::ReadOnly); + stream_info.filter_state_->setData("k", std::make_unique("typed-v")); ON_CALL(context, requestStreamInfo()).WillByDefault(Return(&stream_info)); auto* context_ptr = static_cast(&context); std::string key = "k"; diff --git a/test/extensions/clusters/original_dst/original_dst_cluster_test.cc b/test/extensions/clusters/original_dst/original_dst_cluster_test.cc index fbd9c357339a7..6678ba32d52f4 100644 --- a/test/extensions/clusters/original_dst/original_dst_cluster_test.cc +++ b/test/extensions/clusters/original_dst/original_dst_cluster_test.cc @@ -1067,8 +1067,7 @@ TEST_F(OriginalDstClusterTest, UseFilterState) { connection.stream_info_.filterState()->setData( Upstream::OriginalDstClusterFilterStateKey, std::make_shared( - std::make_shared("10.10.11.11", 6666)), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_shared("10.10.11.11", 6666))); TestLoadBalancerContext lb_context1(&connection, Http::Headers::get().EnvoyOriginalDstHost.get(), "127.0.0.1:5555"); @@ -1135,8 +1134,7 @@ TEST_F(OriginalDstClusterTest, UseFilterStateWithPortOverride) { connection.stream_info_.filterState()->setData( Upstream::OriginalDstClusterFilterStateKey, std::make_shared( - std::make_shared("10.10.11.11", 6666)), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_shared("10.10.11.11", 6666))); TestLoadBalancerContext lb_context1(&connection, Http::Headers::get().EnvoyOriginalDstHost.get(), "127.0.0.1:5555"); diff --git a/test/extensions/filters/common/expr/expr_context_speed_test.cc b/test/extensions/filters/common/expr/expr_context_speed_test.cc index de9e5fc31ec49..bf167491a2335 100644 --- a/test/extensions/filters/common/expr/expr_context_speed_test.cc +++ b/test/extensions/filters/common/expr/expr_context_speed_test.cc @@ -128,7 +128,7 @@ class ExpressionContextSpeedTest { std::string key = absl::StrCat("key_", i); std::string value = absl::StrCat("value_", i); auto accessor = std::make_shared(value); - info_.filterState()->setData(key, accessor, StreamInfo::FilterState::StateType::ReadOnly); + info_.filterState()->setData(key, accessor); filter_state_keys_.push_back(key); } } diff --git a/test/extensions/filters/common/set_filter_state/filter_config_test.cc b/test/extensions/filters/common/set_filter_state/filter_config_test.cc index 372bfa7ccbb00..d3beeb2a4ea0c 100644 --- a/test/extensions/filters/common/set_filter_state/filter_config_test.cc +++ b/test/extensions/filters/common/set_filter_state/filter_config_test.cc @@ -131,8 +131,7 @@ TEST_F(ConfigTest, UpdateValue) { text_format_source: inline_string: "XXX" )YAML"}); - info_.filterState()->setData("foo", std::make_unique("OLD"), - StateType::Mutable); + info_.filterState()->setData("foo", std::make_unique("OLD")); update(); EXPECT_FALSE(info_.filterState()->hasDataAtOrAboveLifeSpan(LifeSpan::Request)); const auto* foo = info_.filterState()->getDataReadOnly("foo"); diff --git a/test/extensions/filters/http/composite/filter_test.cc b/test/extensions/filters/http/composite/filter_test.cc index 8cfaa464541f8..13864da489bdc 100644 --- a/test/extensions/filters/http/composite/filter_test.cc +++ b/test/extensions/filters/http/composite/filter_test.cc @@ -672,7 +672,6 @@ TEST_F(FilterTest, FilterStateShouldBeUpdatedWithTheMatchingAction) { filter_state->setData(MatchedActionsFilterStateKey, std::make_shared("rootFilterName", "oldActionName"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::FilterChain); Http::FilterFactoryCb factory_callback = [&](Http::FilterChainFactoryCallbacks& cb) { @@ -703,7 +702,6 @@ TEST_F(FilterTest, MatchingActionShouldNotCollitionWithOtherRootFilter) { filter_state->setData(MatchedActionsFilterStateKey, std::make_shared("otherRootFilterName", "anyActionName"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::FilterChain); Http::FilterFactoryCb factory_callback = [&](Http::FilterChainFactoryCallbacks& cb) { diff --git a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_test.cc b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_test.cc index bb66a690b14ba..d6bda400d376c 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_test.cc +++ b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_test.cc @@ -629,7 +629,6 @@ TEST_F(UpstreamResolvedHostFilterStateHelper, UpdateResolvedHostFilterStateMetad const auto pre_address = Network::Utility::parseInternetAddressNoThrow("1.2.3.3", 80); filter_state->setData(StreamInfo::UpstreamAddress::key(), std::make_unique(pre_address), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); InSequence s; @@ -842,7 +841,6 @@ TEST_F(ProxyFilterWithFilterStateHostTest, WithFilterStateHostPresent) { const std::string filter_state_host = "filter-state-host.example.com"; filter_state_->setData("envoy.upstream.dynamic_host", std::make_unique(filter_state_host), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); // Should use the value from filter state, not host header. @@ -911,7 +909,6 @@ TEST_F(ProxyFilterWithFilterStateHostDisabledTest, DoesNotUseFilterStateWhenFlag const std::string filter_state_host = "filter-state-host.example.com"; filter_state_->setData("envoy.upstream.dynamic_host", std::make_unique(filter_state_host), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(callbacks_.stream_info_)); @@ -949,7 +946,6 @@ TEST_F(ProxyFilterWithFilterStateHostDisabledTest, IgnoresFilterStateHostWhenFla const std::string filter_state_host = "filter-state-host.example.com"; filter_state_->setData("envoy.upstream.dynamic_host", std::make_unique(filter_state_host), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); // Should use host header "foo", not filter state, when the flag is disabled. @@ -983,7 +979,6 @@ TEST_F(ProxyFilterWithFilterStateHostTest, WithFilterStatePortPresent) { constexpr uint32_t filter_state_port = 9999; filter_state_->setData("envoy.upstream.dynamic_port", std::make_unique(filter_state_port), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); // Should use "foo" from host header but port from filter state. @@ -1019,11 +1014,9 @@ TEST_F(ProxyFilterWithFilterStateHostTest, WithFilterStateHostAndPortPresent) { constexpr uint32_t filter_state_port = 9999; filter_state_->setData("envoy.upstream.dynamic_host", std::make_unique(filter_state_host), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); filter_state_->setData("envoy.upstream.dynamic_port", std::make_unique(filter_state_port), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); // Should use both host and port from filter state. @@ -1059,11 +1052,9 @@ TEST_F(ProxyFilterWithFilterStateHostDisabledTest, IgnoresFilterStatePortWhenFla constexpr uint32_t filter_state_port = 9999; filter_state_->setData("envoy.upstream.dynamic_host", std::make_unique(filter_state_host), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); filter_state_->setData("envoy.upstream.dynamic_port", std::make_unique(filter_state_port), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); // Should use "foo" from host header and default port 80, ignoring filter state. diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index 56fc0012413b4..09532a77d5f8d 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -5302,8 +5302,7 @@ TEST_P(EmitFilterStateTest, PreexistingFilterStateDifferentTypeMutable) { FilterConfigName, // This will not cast to ExtAuthzLoggingInfo, so when the filter tries to // getMutableData(...), it will return nullptr. - std::make_shared(), Envoy::StreamInfo::FilterState::StateType::Mutable, - Envoy::StreamInfo::FilterState::LifeSpan::Request); + std::make_shared(), Envoy::StreamInfo::FilterState::LifeSpan::Request); Filters::Common::ExtAuthz::Response response{}; response.status = Filters::Common::ExtAuthz::CheckStatus::OK; @@ -5323,7 +5322,6 @@ TEST_P(EmitFilterStateTest, PreexistingFilterStateSameTypeMutable) { // This will not cast to ExtAuthzLoggingInfo, so when the filter tries to // getMutableData(...), it will return nullptr. std::make_shared(absl::nullopt), - Envoy::StreamInfo::FilterState::StateType::Mutable, Envoy::StreamInfo::FilterState::LifeSpan::Request); Filters::Common::ExtAuthz::Response response{}; diff --git a/test/extensions/filters/http/ext_proc/mapped_attribute_builder_test.cc b/test/extensions/filters/http/ext_proc/mapped_attribute_builder_test.cc index 342f3f210530d..6d2ea17f93233 100644 --- a/test/extensions/filters/http/ext_proc/mapped_attribute_builder_test.cc +++ b/test/extensions/filters/http/ext_proc/mapped_attribute_builder_test.cc @@ -92,8 +92,7 @@ TEST_F(MappedAttributeBuilderTest, CelFilterState) { Http::TestRequestHeaderMapImpl request_headers; stream_info_.filter_state_->setData("fs_key", - std::make_unique("fs_value"), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique("fs_value")); ProcessingRequestModifier::Params params{ envoy::config::core::v3::TrafficDirection::INBOUND, diff --git a/test/extensions/filters/http/jwt_authn/jwt_authn_fuzz_test.cc b/test/extensions/filters/http/jwt_authn/jwt_authn_fuzz_test.cc index 742d2e107499f..ec1f85ded6746 100644 --- a/test/extensions/filters/http/jwt_authn/jwt_authn_fuzz_test.cc +++ b/test/extensions/filters/http/jwt_authn/jwt_authn_fuzz_test.cc @@ -106,7 +106,6 @@ DEFINE_PROTO_FUZZER(const JwtAuthnFuzzInput& input) { if (!rules.name().empty() && !rules.requires_().empty()) { filter_callbacks.stream_info_.filter_state_->setData( rules.name(), std::make_unique(input.filter_state_selector()), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); } } diff --git a/test/extensions/filters/http/lua/wrappers_test.cc b/test/extensions/filters/http/lua/wrappers_test.cc index eef4e615b6ff9..1c11f1ed4f279 100644 --- a/test/extensions/filters/http/lua/wrappers_test.cc +++ b/test/extensions/filters/http/lua/wrappers_test.cc @@ -1030,9 +1030,9 @@ TEST_F(LuaStreamInfoWrapperTest, GetFilterStateBasic) { StreamInfo::FilterState::LifeSpan::FilterChain); // Create a simple string accessor for testing. - stream_info.filterState()->setData( - "test_key", std::make_shared("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + stream_info.filterState()->setData("test_key", + std::make_shared("test_value"), + StreamInfo::FilterState::LifeSpan::FilterChain); Filters::Common::Lua::LuaDeathRef wrapper( StreamInfoWrapper::create(coroutine_->luaState(), stream_info), true); @@ -1100,11 +1100,9 @@ TEST_F(LuaStreamInfoWrapperTest, GetMultipleFilterStateObjects) { // Add multiple filter state objects. stream_info.filterState()->setData("key1", std::make_shared("value1"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); stream_info.filterState()->setData("key2", std::make_shared("value2"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); Filters::Common::Lua::LuaDeathRef wrapper( @@ -1143,9 +1141,9 @@ TEST_F(LuaStreamInfoWrapperTest, GetFilterStateNumericAccessor) { StreamInfo::FilterState::LifeSpan::FilterChain); // Add numeric filter state object. - stream_info.filterState()->setData( - "numeric_key", std::make_shared(12345), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + stream_info.filterState()->setData("numeric_key", + std::make_shared(12345), + StreamInfo::FilterState::LifeSpan::FilterChain); Filters::Common::Lua::LuaDeathRef wrapper( StreamInfoWrapper::create(coroutine_->luaState(), stream_info), true); @@ -1181,9 +1179,9 @@ TEST_F(LuaStreamInfoWrapperTest, GetFilterStateBooleanAccessor) { StreamInfo::FilterState::LifeSpan::FilterChain); // Add boolean filter state object. - stream_info.filterState()->setData( - "bool_key", std::make_shared(true), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + stream_info.filterState()->setData("bool_key", + std::make_shared(true), + StreamInfo::FilterState::LifeSpan::FilterChain); Filters::Common::Lua::LuaDeathRef wrapper( StreamInfoWrapper::create(coroutine_->luaState(), stream_info), true); @@ -1244,9 +1242,9 @@ TEST_F(LuaStreamInfoWrapperTest, GetFilterStateFieldAccessString) { StreamInfo::FilterState::LifeSpan::FilterChain); // Add field-supporting filter state object. - stream_info.filterState()->setData( - "field_key", std::make_shared("base_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + stream_info.filterState()->setData("field_key", + std::make_shared("base_value"), + StreamInfo::FilterState::LifeSpan::FilterChain); Filters::Common::Lua::LuaDeathRef wrapper( StreamInfoWrapper::create(coroutine_->luaState(), stream_info), true); @@ -1282,9 +1280,9 @@ TEST_F(LuaStreamInfoWrapperTest, GetFilterStateFieldAccessNumeric) { StreamInfo::FilterState::LifeSpan::FilterChain); // Add field-supporting filter state object. - stream_info.filterState()->setData( - "field_key", std::make_shared("base_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + stream_info.filterState()->setData("field_key", + std::make_shared("base_value"), + StreamInfo::FilterState::LifeSpan::FilterChain); Filters::Common::Lua::LuaDeathRef wrapper( StreamInfoWrapper::create(coroutine_->luaState(), stream_info), true); @@ -1315,9 +1313,9 @@ TEST_F(LuaStreamInfoWrapperTest, GetFilterStateFieldAccessNonExistent) { StreamInfo::FilterState::LifeSpan::FilterChain); // Add field-supporting filter state object. - stream_info.filterState()->setData( - "field_key", std::make_shared("base_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + stream_info.filterState()->setData("field_key", + std::make_shared("base_value"), + StreamInfo::FilterState::LifeSpan::FilterChain); Filters::Common::Lua::LuaDeathRef wrapper( StreamInfoWrapper::create(coroutine_->luaState(), stream_info), true); @@ -1346,9 +1344,9 @@ TEST_F(LuaStreamInfoWrapperTest, GetFilterStateFieldAccessNoSupport) { StreamInfo::FilterState::LifeSpan::FilterChain); // Add regular string accessor without field support. - stream_info.filterState()->setData( - "no_field_key", std::make_shared("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + stream_info.filterState()->setData("no_field_key", + std::make_shared("test_value"), + StreamInfo::FilterState::LifeSpan::FilterChain); Filters::Common::Lua::LuaDeathRef wrapper( StreamInfoWrapper::create(coroutine_->luaState(), stream_info), true); @@ -1384,9 +1382,9 @@ TEST_F(LuaStreamInfoWrapperTest, GetFilterStateFieldAccessFallback) { StreamInfo::FilterState::LifeSpan::FilterChain); // Add field-supporting filter state object. - stream_info.filterState()->setData( - "field_key", std::make_shared("test_base"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + stream_info.filterState()->setData("field_key", + std::make_shared("test_base"), + StreamInfo::FilterState::LifeSpan::FilterChain); Filters::Common::Lua::LuaDeathRef wrapper( StreamInfoWrapper::create(coroutine_->luaState(), stream_info), true); diff --git a/test/extensions/filters/http/proto_api_scrubber/integration_test.cc b/test/extensions/filters/http/proto_api_scrubber/integration_test.cc index 016ab577a2511..f8770b1a7e492 100644 --- a/test/extensions/filters/http/proto_api_scrubber/integration_test.cc +++ b/test/extensions/filters/http/proto_api_scrubber/integration_test.cc @@ -54,8 +54,7 @@ class MetadataInjectorFilter : public ::Envoy::Http::PassThroughDecoderFilter { const std::string key = kFilterStateLabelKey; const std::string value = kFilterStateLabelValue; decoder_callbacks_->streamInfo().filterState()->setData( - key, std::make_shared<::Envoy::Router::StringAccessorImpl>(value), - ::Envoy::StreamInfo::FilterState::StateType::ReadOnly); + key, std::make_shared<::Envoy::Router::StringAccessorImpl>(value)); return ::Envoy::Http::FilterHeadersStatus::Continue; } }; diff --git a/test/extensions/filters/http/ratelimit/ratelimit_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_test.cc index 13d5aa7f7d0d9..e637a7ad877e1 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_test.cc @@ -362,8 +362,7 @@ TEST_F(HttpRateLimitFilterTest, OkResponseWithAdditionalHitsAddend) { InSequence s; filter_callbacks_.stream_info_.filter_state_->setData( - "envoy.ratelimit.hits_addend", std::make_unique(5), - StreamInfo::FilterState::StateType::Mutable); + "envoy.ratelimit.hits_addend", std::make_unique(5)); EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) @@ -410,8 +409,7 @@ TEST_F(HttpRateLimitFilterTest, OkResponseWithAdditionalHitsAddend) { testing::Mock::VerifyAndClearExpectations(&vh_rate_limit_); filter_callbacks_.stream_info_.filter_state_->setData( // Ensures that addend can be set differently than the request path. - "envoy.ratelimit.hits_addend", std::make_unique(100), - StreamInfo::FilterState::StateType::Mutable); + "envoy.ratelimit.hits_addend", std::make_unique(100)); EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); EXPECT_CALL(vh_rate_limit_, applyOnStreamDone()).WillRepeatedly(Return(true)); EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _)) @@ -432,8 +430,7 @@ TEST_F(HttpRateLimitFilterTest, OkResponseWithHitsAddendFilterState) { InSequence s; filter_callbacks_.stream_info_.filter_state_->setData( - "envoy.ratelimit.hits_addend", std::make_unique(10), - StreamInfo::FilterState::StateType::Mutable); + "envoy.ratelimit.hits_addend", std::make_unique(10)); EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) diff --git a/test/extensions/filters/http/rbac/rbac_filter_test.cc b/test/extensions/filters/http/rbac/rbac_filter_test.cc index 2f4444c675a32..e88c55cd0f6fd 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_test.cc @@ -1279,7 +1279,7 @@ class UpstreamIpPortMatcherTests : public RoleBasedAccessControlFilterTest { std::make_unique( Envoy::Network::Utility::parseInternetAddressAndPortNoThrow(upstream_ips.back(), false)), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request); + StreamInfo::FilterState::LifeSpan::Request); } }; diff --git a/test/extensions/filters/network/geoip/geoip_filter_test.cc b/test/extensions/filters/network/geoip/geoip_filter_test.cc index afc1c35741f37..c654c34f2516e 100644 --- a/test/extensions/filters/network/geoip/geoip_filter_test.cc +++ b/test/extensions/filters/network/geoip/geoip_filter_test.cc @@ -109,7 +109,6 @@ class GeoipFilterTest : public testing::Test { void setFilterStateClientIp(const std::string& key, const std::string& ip) { filter_state_->setData(key, std::make_shared(ip), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } diff --git a/test/extensions/filters/network/match_delegate/match_delegate_integration_test.cc b/test/extensions/filters/network/match_delegate/match_delegate_integration_test.cc index 49cb75b094aaa..da8dbfe3c77c8 100644 --- a/test/extensions/filters/network/match_delegate/match_delegate_integration_test.cc +++ b/test/extensions/filters/network/match_delegate/match_delegate_integration_test.cc @@ -107,7 +107,6 @@ class SetFilterStateFilter : public Network::ReadFilter { if (!value_.empty()) { read_callbacks_->connection().streamInfo().filterState()->setData( "test_key", std::make_shared(value_), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } return Network::FilterStatus::Continue; diff --git a/test/extensions/filters/network/redis_proxy/router_impl_test.cc b/test/extensions/filters/network/redis_proxy/router_impl_test.cc index b3b0deb932e9f..73b8d3e72eab0 100644 --- a/test/extensions/filters/network/redis_proxy/router_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/router_impl_test.cc @@ -110,8 +110,7 @@ TEST(PrefixRoutesTest, TestFormatterWithCatchAllRoute) { "{%KEY%}-%ENVIRONMENT(ENVOY_TEST_ENV)%-%FILTER_STATE(redisKey)%-{%KEY%}"; stream_info.filterState()->setData( - "redisKey", std::make_unique("subjectCN"), - StreamInfo::FilterState::StateType::ReadOnly); + "redisKey", std::make_unique("subjectCN")); ON_CALL(filter_callbacks, connection()).WillByDefault(ReturnRef(connection)); ON_CALL(connection, streamInfo()).WillByDefault(ReturnRef(stream_info)); @@ -148,8 +147,7 @@ TEST(PrefixRoutesTest, TestFormatterWithPrefixRoute) { "{%KEY%}-%ENVIRONMENT(ENVOY_TEST_ENV)%-%FILTER_STATE(redisKey)%-{%KEY%}"; stream_info.filterState()->setData( - "redisKey", std::make_unique("subjectCN"), - StreamInfo::FilterState::StateType::ReadOnly); + "redisKey", std::make_unique("subjectCN")); ON_CALL(filter_callbacks, connection()).WillByDefault(ReturnRef(connection)); ON_CALL(connection, streamInfo()).WillByDefault(ReturnRef(stream_info)); @@ -185,8 +183,7 @@ TEST(PrefixRoutesTest, TestFormatterWithPercentInKey) { "{%KEY%}-%ENVIRONMENT(ENVOY_TEST_ENV)%-%FILTER_STATE(redisKey)%-{%KEY%}"; stream_info.filterState()->setData( - "redisKey", std::make_unique("subjectCN"), - StreamInfo::FilterState::StateType::ReadOnly); + "redisKey", std::make_unique("subjectCN")); ON_CALL(filter_callbacks, connection()).WillByDefault(ReturnRef(connection)); ON_CALL(connection, streamInfo()).WillByDefault(ReturnRef(stream_info)); @@ -220,8 +217,7 @@ TEST(PrefixRoutesTest, TestKeyPrefixFormatterWithMissingFilterState) { const std::string format = "%FILTER_STATE(redisKey)%"; stream_info.filterState()->setData( - "randomKey", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + "randomKey", std::make_unique("test_value")); ON_CALL(filter_callbacks, connection()).WillByDefault(ReturnRef(connection)); ON_CALL(connection, streamInfo()).WillByDefault(ReturnRef(stream_info)); diff --git a/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_test.cc b/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_test.cc index 6fe18468d3645..1db270d774469 100644 --- a/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_test.cc +++ b/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_test.cc @@ -55,13 +55,13 @@ class SniDynamicProxyFilterTest void setFilterStateHost(const std::string& host) { connection_.streamInfo().filterState()->setData( "envoy.upstream.dynamic_host", std::make_shared(host), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); } void setFilterStatePort(uint32_t port) { connection_.streamInfo().filterState()->setData( "envoy.upstream.dynamic_port", std::make_shared(port), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); } ~SniDynamicProxyFilterTest() override { @@ -349,7 +349,7 @@ TEST_F(UpstreamResolvedHostFilterStateHelper, UpdateResolvedHostFilterStateMetad StreamInfo::UpstreamAddress::key(), std::make_unique( Network::Utility::parseInternetAddressNoThrow("1.2.3.3", 443)), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); InSequence s; diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h b/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h index 56503da0aaf58..c3dd034a6eab0 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h +++ b/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h @@ -41,16 +41,14 @@ class BufferingSessionFilter : public Filter { read_callbacks_ = &callbacks; // Verify that the filter is able to access the stream info. callbacks.streamInfo().filterState()->setData( - "test.read", std::make_shared("val"), - Envoy::StreamInfo::FilterState::StateType::Mutable); + "test.read", std::make_shared("val")); } void initializeWriteFilterCallbacks(WriteFilterCallbacks& callbacks) override { write_callbacks_ = &callbacks; // Verify that the filter is able to access the stream info. callbacks.streamInfo().filterState()->setData( - "test.write", std::make_shared("val"), - Envoy::StreamInfo::FilterState::StateType::Mutable); + "test.write", std::make_shared("val")); } ReadFilterStatus onNewSession() override { return ReadFilterStatus::Continue; } diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.h b/test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.h index a8d7cffbcb229..c2e5b27d11cb7 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.h +++ b/test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.h @@ -44,7 +44,7 @@ class DrainerUdpSessionReadFilter : public virtual ReadFilter { read_callbacks_->streamInfo().filterState()->setData( "test.udp_session.drainer.on_session_complete", std::make_shared("session_complete"), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection, + StreamInfo::FilterState::LifeSpan::Connection, StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnection); } diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.h b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.h index c2a39c4d33c64..f85821e6b9805 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.h +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/dfp_setter.h @@ -32,11 +32,9 @@ class DynamicForwardProxySetterFilter : public virtual ReadFilter { ReadFilterStatus onNewSession() override { read_callbacks_->streamInfo().filterState()->setData( - "envoy.upstream.dynamic_host", std::make_shared(host_), - StreamInfo::FilterState::StateType::Mutable); + "envoy.upstream.dynamic_host", std::make_shared(host_)); read_callbacks_->streamInfo().filterState()->setData( - "envoy.upstream.dynamic_port", std::make_shared(port_), - StreamInfo::FilterState::StateType::Mutable); + "envoy.upstream.dynamic_port", std::make_shared(port_)); return ReadFilterStatus::Continue; } diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc index 245469d01d92a..680c5a5838318 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/proxy_filter_test.cc @@ -35,15 +35,15 @@ class DynamicProxyFilterTest } void setFilterStateHost(const std::string& host) { - stream_info_.filterState()->setData( - "envoy.upstream.dynamic_host", std::make_shared(host), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + stream_info_.filterState()->setData("envoy.upstream.dynamic_host", + std::make_shared(host), + StreamInfo::FilterState::LifeSpan::Connection); } void setFilterStatePort(uint32_t port) { - stream_info_.filterState()->setData( - "envoy.upstream.dynamic_port", std::make_shared(port), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + stream_info_.filterState()->setData("envoy.upstream.dynamic_port", + std::make_shared(port), + StreamInfo::FilterState::LifeSpan::Connection); } void setFilterState(const std::string& host, uint32_t port) { diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/psc_setter.h b/test/extensions/filters/udp/udp_proxy/session_filters/psc_setter.h index e6052fc37b03d..b615abecd4598 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/psc_setter.h +++ b/test/extensions/filters/udp/udp_proxy/session_filters/psc_setter.h @@ -35,8 +35,7 @@ class PerSessionClusterSetterFilter : public virtual ReadFilter { } read_callbacks_->streamInfo().filterState()->setData( - "envoy.udp_proxy.cluster", std::make_shared(cluster_), - StreamInfo::FilterState::StateType::Mutable); + "envoy.udp_proxy.cluster", std::make_shared(cluster_)); return ReadFilterStatus::Continue; } diff --git a/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc b/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc index a6ac14602c93d..4e37f7ef918e9 100644 --- a/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc +++ b/test/extensions/filters/udp/udp_proxy/udp_proxy_filter_test.cc @@ -2271,15 +2271,13 @@ class HttpUpstreamImplTest : public testing::Test { if (proxy_port) { stream_info_.filterState()->setData( "udp.connect.proxy_port", - std::make_shared(proxy_port.value()), - Envoy::StreamInfo::FilterState::StateType::Mutable); + std::make_shared(proxy_port.value())); } if (target_port) { stream_info_.filterState()->setData( "udp.connect.target_port", - std::make_shared(target_port.value()), - Envoy::StreamInfo::FilterState::StateType::Mutable); + std::make_shared(target_port.value())); } } @@ -2740,8 +2738,7 @@ TEST(TunnelingConfigImplTest, HeadersToAdd) { NiceMock stream_info; stream_info.filterState()->setData( - "test_key", std::make_shared("test_val"), - Envoy::StreamInfo::FilterState::StateType::Mutable); + "test_key", std::make_shared("test_val")); TunnelingConfig proto_config; auto* header_to_add = proto_config.add_headers_to_add(); @@ -2760,8 +2757,7 @@ TEST(TunnelingConfigImplTest, ProxyHostFromFilterState) { NiceMock stream_info; stream_info.filterState()->setData( - "test-proxy-host", std::make_shared("test.host.com"), - Envoy::StreamInfo::FilterState::StateType::Mutable); + "test-proxy-host", std::make_shared("test.host.com")); TunnelingConfig proto_config; proto_config.set_proxy_host("%FILTER_STATE(test-proxy-host:PLAIN)%"); @@ -2775,8 +2771,7 @@ TEST(TunnelingConfigImplTest, TargetHostFromFilterState) { NiceMock stream_info; stream_info.filterState()->setData( - "test-proxy-host", std::make_shared("test.host.com"), - Envoy::StreamInfo::FilterState::StateType::Mutable); + "test-proxy-host", std::make_shared("test.host.com")); TunnelingConfig proto_config; proto_config.set_target_host("%FILTER_STATE(test-proxy-host:PLAIN)%"); diff --git a/test/extensions/formatter/cel/cel_test.cc b/test/extensions/formatter/cel/cel_test.cc index 421566c894923..080ee3aee621f 100644 --- a/test/extensions/formatter/cel/cel_test.cc +++ b/test/extensions/formatter/cel/cel_test.cc @@ -508,8 +508,7 @@ TEST_F(CELFormatterTest, TestFilterStateConditionalWithKey) { // Add the filter state key to simulate it being set by previous filters stream_info_.filter_state_->setData( - kFilterStateKey, std::make_unique("192.168.1.100:9443"), - StreamInfo::FilterState::StateType::ReadOnly); + kFilterStateKey, std::make_unique("192.168.1.100:9443")); auto formatter = *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); diff --git a/test/extensions/transport_sockets/http_11_proxy/connect_test.cc b/test/extensions/transport_sockets/http_11_proxy/connect_test.cc index 39b11e04aea82..4217b8c231ea5 100644 --- a/test/extensions/transport_sockets/http_11_proxy/connect_test.cc +++ b/test/extensions/transport_sockets/http_11_proxy/connect_test.cc @@ -68,7 +68,6 @@ class Http11ConnectTest : public testing::TestWithParamsetData( "envoy.network.transport_socket.http_11_proxy.address", std::make_unique("www.foo.com", address), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); } diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc index 7629929b383aa..0fede029bee62 100644 --- a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc @@ -527,7 +527,7 @@ name: envoy.tls.cert_validator.spiffe callbacks.connection().streamInfo().filterState()->setData( "envoy.tls.cert_validator.spiffe.workload_trust_domain", std::make_shared("mydomain.org"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); { SCOPED_TRACE("Trust domain matches so should be accepted (server)."); diff --git a/test/extensions/upstreams/tcp/generic/config_test.cc b/test/extensions/upstreams/tcp/generic/config_test.cc index d750281e088fe..60c984eea932e 100644 --- a/test/extensions/upstreams/tcp/generic/config_test.cc +++ b/test/extensions/upstreams/tcp/generic/config_test.cc @@ -71,7 +71,7 @@ TEST_P(TcpConnPoolTest, TestTunnelingDisabledByFilterState) { downstream_stream_info_.filterState()->setData( TcpProxy::DisableTunnelingFilterStateKey, std::make_shared(true), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); EXPECT_CALL(thread_local_cluster_, tcpConnPool(_, _, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, @@ -88,7 +88,7 @@ TEST_P(TcpConnPoolTest, TestTunnelingNotDisabledIfFilterStateHasFalseValue) { downstream_stream_info_.filterState()->setData( TcpProxy::DisableTunnelingFilterStateKey, std::make_shared(false), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); if (!useUpstreamFilters()) { EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _, _)).WillOnce(Return(absl::nullopt)); diff --git a/test/integration/filters/encoder_recreate_stream_filter.cc b/test/integration/filters/encoder_recreate_stream_filter.cc index ebcbb4e40ed97..5321b9dd72d6b 100644 --- a/test/integration/filters/encoder_recreate_stream_filter.cc +++ b/test/integration/filters/encoder_recreate_stream_filter.cc @@ -32,7 +32,7 @@ class EncoderRecreateStreamFilter : public Http::PassThroughFilter { decoder_callbacks_->streamInfo().filterState()->setData( "test_key", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request); + StreamInfo::FilterState::LifeSpan::Request); if (decoder_callbacks_->recreateStream(nullptr)) { return ::Envoy::Http::FilterHeadersStatus::StopIteration; diff --git a/test/integration/filters/header_to_proxy_filter.cc b/test/integration/filters/header_to_proxy_filter.cc index 541dcadb6eab7..c6811332ba292 100644 --- a/test/integration/filters/header_to_proxy_filter.cc +++ b/test/integration/filters/header_to_proxy_filter.cc @@ -26,7 +26,6 @@ class HeaderToProxyFilter : public Http::PassThroughFilter { decoder_callbacks_->streamInfo().filterState()->setData( Network::Http11ProxyInfoFilterState::key(), std::make_unique(hostname, address), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); request_headers.remove(connect_proxy); } diff --git a/test/integration/filters/test_listener_filter.h b/test/integration/filters/test_listener_filter.h index 9b5c4a03d883d..b3ab4d15828d1 100644 --- a/test/integration/filters/test_listener_filter.h +++ b/test/integration/filters/test_listener_filter.h @@ -131,11 +131,10 @@ class TestQuicListenerFilter : public Network::QuicListenerFilter { Network::FilterStatus onAccept(Network::ListenerFilterCallbacks& cb) override { cb.filterState().setData(TestStringFilterState::key(), std::make_unique(added_value_), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); - cb.filterState().setData( - TestFirstPacketReceivedFilterState::key(), test_first_packet_received_filter_state_, - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + cb.filterState().setData(TestFirstPacketReceivedFilterState::key(), + test_first_packet_received_filter_state_, + StreamInfo::FilterState::LifeSpan::Connection); dispatcher_ = &cb.dispatcher(); return Network::FilterStatus::Continue; } diff --git a/test/integration/tcp_proxy_integration_test.cc b/test/integration/tcp_proxy_integration_test.cc index c6ee0d3da971b..f855f29590d55 100644 --- a/test/integration/tcp_proxy_integration_test.cc +++ b/test/integration/tcp_proxy_integration_test.cc @@ -1927,7 +1927,6 @@ class PauseFilter : public Network::ReadFilter { // the connection. read_callbacks_->connection().streamInfo().filterState()->setData( TcpProxy::ReceiveBeforeConnectKey, std::make_unique(true), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); } diff --git a/test/integration/upstream_access_log_integration_test.cc b/test/integration/upstream_access_log_integration_test.cc index e89ba6a0d2e0c..16ef9ab574036 100644 --- a/test/integration/upstream_access_log_integration_test.cc +++ b/test/integration/upstream_access_log_integration_test.cc @@ -45,8 +45,7 @@ class Socket : public Extensions::TransportSockets::PassthroughSocket { void onConnected() override { const Envoy::StreamInfo::FilterStateSharedPtr& filter_state = callbacks_->connection().streamInfo().filterState(); - filter_state->setData("test_key", std::make_unique("test_value"), - StreamInfo::FilterState::StateType::ReadOnly); + filter_state->setData("test_key", std::make_unique("test_value")); transport_socket_->onConnected(); } From b27925c960dcba06b93859fe2ee456e1e2597220 Mon Sep 17 00:00:00 2001 From: Xin Date: Wed, 20 May 2026 16:33:05 -0400 Subject: [PATCH 360/750] [network_ext_proc] Net ext_proc receiving (untyped) metadata (#44829) Commit Message: [network_ext_proc] Net ext_proc receiving (untyped) metadata Additional Description: Risk Level: LOW (new impl) Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Xin Zhuang --- .../network/ext_proc/v3/ext_proc.proto | 9 +++ changelogs/current.yaml | 7 +++ .../filters/network/ext_proc/ext_proc.cc | 11 ++++ .../filters/network/ext_proc/ext_proc.h | 10 ++++ .../filters/network/ext_proc/ext_proc_test.cc | 56 +++++++++++++++++++ 5 files changed, 93 insertions(+) diff --git a/api/envoy/extensions/filters/network/ext_proc/v3/ext_proc.proto b/api/envoy/extensions/filters/network/ext_proc/v3/ext_proc.proto index f37feaa94f8d1..a72773323d41e 100644 --- a/api/envoy/extensions/filters/network/ext_proc/v3/ext_proc.proto +++ b/api/envoy/extensions/filters/network/ext_proc/v3/ext_proc.proto @@ -105,4 +105,13 @@ message MetadataOptions { // Describes which typed or untyped dynamic metadata namespaces to forward to // the external processing server. MetadataNamespaces forwarding_namespaces = 1; + + // Describes which typed or untyped dynamic metadata namespaces to receive + // from the external processing server. + // Since the server returns untyped dynamic metadata, this configuration acts + // as a allowlist. Only metadata namespaces explicitly listed here will be + // ingested by Envoy from the server's response. + // Receiving of typed metadata is not supported. + // Set to empty or leave unset to disallow writing any received dynamic metadata. + MetadataNamespaces receiving_namespaces = 2; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2066eb9f1d60b..94a0cac1a4296 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1 +1,8 @@ date: Pending + +new_features: +- area: network_ext_proc + change: | + Added support for receiving untyped dynamic metadata from the external processing server. + Configured via :ref:`receiving_namespaces + `. diff --git a/source/extensions/filters/network/ext_proc/ext_proc.cc b/source/extensions/filters/network/ext_proc/ext_proc.cc index 0c12163c92ab0..5d130d8264d81 100644 --- a/source/extensions/filters/network/ext_proc/ext_proc.cc +++ b/source/extensions/filters/network/ext_proc/ext_proc.cc @@ -371,6 +371,17 @@ void NetworkExtProcFilter::onReceiveMessage(std::unique_ptr& return; } + if (response->has_dynamic_metadata()) { + const auto& response_metadata = response->dynamic_metadata(); + for (const auto& [key, value] : response_metadata.fields()) { + if (config_->untypedReceivingMetadataNamespaces().contains(key)) { + if (value.has_struct_value()) { + read_callbacks_->connection().streamInfo().setDynamicMetadata(key, value.struct_value()); + } + } + } + } + if (response->has_read_data()) { const auto& data = response->read_data(); if (timeout_manager_ && read_pending_) { diff --git a/source/extensions/filters/network/ext_proc/ext_proc.h b/source/extensions/filters/network/ext_proc/ext_proc.h index 804f3f2903923..9f38d9b6eaf44 100644 --- a/source/extensions/filters/network/ext_proc/ext_proc.h +++ b/source/extensions/filters/network/ext_proc/ext_proc.h @@ -14,6 +14,8 @@ #include "source/extensions/filters/network/ext_proc/client_impl.h" +#include "absl/container/flat_hash_set.h" + namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -104,6 +106,9 @@ class Config { typed_forwarding_namespaces_( config.metadata_options().forwarding_namespaces().typed().begin(), config.metadata_options().forwarding_namespaces().typed().end()), + untyped_receiving_namespaces_( + config.metadata_options().receiving_namespaces().untyped().begin(), + config.metadata_options().receiving_namespaces().untyped().end()), stats_(generateStats(config.stat_prefix(), scope)), message_timeout_(std::chrono::milliseconds( PROTOBUF_GET_MS_OR_DEFAULT(config, message_timeout, DefaultMessageTimeoutMs))) {}; @@ -124,6 +129,10 @@ class Config { return typed_forwarding_namespaces_; } + const absl::flat_hash_set& untypedReceivingMetadataNamespaces() const { + return untyped_receiving_namespaces_; + } + const NetworkExtProcStats& stats() const { return stats_; } const std::chrono::milliseconds& messageTimeout() const { return message_timeout_; } @@ -141,6 +150,7 @@ class Config { const envoy::config::core::v3::GrpcService grpc_service_; const std::vector untyped_forwarding_namespaces_; const std::vector typed_forwarding_namespaces_; + const absl::flat_hash_set untyped_receiving_namespaces_; NetworkExtProcStats stats_; const std::chrono::milliseconds message_timeout_; }; diff --git a/test/extensions/filters/network/ext_proc/ext_proc_test.cc b/test/extensions/filters/network/ext_proc/ext_proc_test.cc index 590ec12b3f7f9..f603fa2fb1ca1 100644 --- a/test/extensions/filters/network/ext_proc/ext_proc_test.cc +++ b/test/extensions/filters/network/ext_proc/ext_proc_test.cc @@ -1329,6 +1329,62 @@ TEST_F(NetworkExtProcFilterTest, LoggingInfoOnError) { EXPECT_EQ(logging_info->lastCallStatus(), Grpc::Status::WellKnownGrpcStatus::ResourceExhausted); } +TEST_F(NetworkExtProcFilterTest, ReceiveDynamicMetadataAllowed) { + std::vector receiving_namespaces = {"test-namespace"}; + envoy::extensions::filters::network::ext_proc::v3::NetworkExternalProcessor config; + config.set_failure_mode_allow(false); + config.mutable_grpc_service()->mutable_envoy_grpc()->set_cluster_name("ext_proc_server"); + for (const auto& ns : receiving_namespaces) { + config.mutable_metadata_options()->mutable_receiving_namespaces()->add_untyped(ns); + } + + auto filter_config = std::make_shared(config, scope_); + auto client = std::make_unique>(); + client_ = client.get(); + filter_ = std::make_unique(filter_config, std::move(client)); + filter_->initializeReadFilterCallbacks(read_callbacks_); + filter_->initializeWriteFilterCallbacks(write_callbacks_); + + auto response = std::make_unique(); + auto* dynamic_metadata = response->mutable_dynamic_metadata(); + Protobuf::Struct struct_obj; + auto& fields = *struct_obj.mutable_fields(); + fields["key1"].set_string_value("value1"); + *(*dynamic_metadata->mutable_fields())["test-namespace"].mutable_struct_value() = struct_obj; + + EXPECT_CALL(stream_info_, setDynamicMetadata("test-namespace", _)); + + filter_->onReceiveMessage(std::move(response)); +} + +TEST_F(NetworkExtProcFilterTest, ReceiveDynamicMetadataNotAllowed) { + std::vector receiving_namespaces = {"test-namespace"}; + envoy::extensions::filters::network::ext_proc::v3::NetworkExternalProcessor config; + config.set_failure_mode_allow(false); + config.mutable_grpc_service()->mutable_envoy_grpc()->set_cluster_name("ext_proc_server"); + for (const auto& ns : receiving_namespaces) { + config.mutable_metadata_options()->mutable_receiving_namespaces()->add_untyped(ns); + } + + auto filter_config = std::make_shared(config, scope_); + auto client = std::make_unique>(); + client_ = client.get(); + filter_ = std::make_unique(filter_config, std::move(client)); + filter_->initializeReadFilterCallbacks(read_callbacks_); + filter_->initializeWriteFilterCallbacks(write_callbacks_); + + auto response = std::make_unique(); + auto* dynamic_metadata = response->mutable_dynamic_metadata(); + Protobuf::Struct struct_obj; + auto& fields = *struct_obj.mutable_fields(); + fields["key1"].set_string_value("value1"); + *(*dynamic_metadata->mutable_fields())["other-namespace"].mutable_struct_value() = struct_obj; + + EXPECT_CALL(stream_info_, setDynamicMetadata("other-namespace", _)).Times(0); + + filter_->onReceiveMessage(std::move(response)); +} + } // namespace } // namespace ExtProc } // namespace NetworkFilters From 82cd1270c98c5d0e31f0936d540bb535b3daa62e Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 20 May 2026 14:01:37 -0700 Subject: [PATCH 361/750] filter_state: remove StateType param from all source/mobile/contrib (#45195) This is a follow-up to #44343 Signed-off-by: Greg Greenway --- .../golang/filters/network/source/golang.cc | 29 +++++++++---------- .../golang/filters/network/source/upstream.cc | 3 +- .../filters/network/test/filter_test.cc | 3 +- .../filters/http/alpn/source/alpn_filter.cc | 3 +- .../peer_metadata/source/peer_metadata.cc | 12 ++++---- .../source/metadata_exchange.cc | 4 +-- .../peer_metadata/source/peer_metadata.cc | 7 ++--- .../peer_metadata/test/peer_metadata_test.cc | 1 - .../http/network_configuration/filter.cc | 4 +-- mobile/test/common/http/client_test.cc | 3 -- source/common/http/conn_manager_impl.cc | 4 +-- source/common/http/filter_manager.cc | 1 - .../listener_manager/active_tcp_socket.cc | 1 - .../common/quic/envoy_quic_client_session.cc | 1 - source/common/router/router.cc | 7 ++--- source/common/tcp_proxy/tcp_proxy.cc | 16 +++++----- .../extensions/access_loggers/stats/stats.cc | 6 ++-- .../reverse_tunnel_acceptor_extension.cc | 2 -- .../reverse_tunnel_upstream_lifecycle.cc | 2 -- source/extensions/common/wasm/context.cc | 7 ++--- .../filters/http/cache/cache_filter.cc | 3 +- .../filters/http/composite/filter.cc | 1 - .../http/compressor/compressor_filter.cc | 6 ++-- .../dynamic_forward_proxy/proxy_filter.cc | 6 ++-- .../filters/http/dynamic_modules/abi_impl.cc | 6 ++-- .../filters/http/ext_authz/ext_authz.cc | 3 +- .../filters/http/ext_proc/ext_proc.cc | 1 - .../http/grpc_stats/grpc_stats_filter.cc | 1 - .../http/local_ratelimit/local_ratelimit.cc | 3 +- .../extensions/filters/http/lua/wrappers.cc | 1 - .../extensions/filters/http/mcp/mcp_filter.cc | 2 +- .../listener/dynamic_modules/abi_impl.cc | 1 - .../listener/original_dst/original_dst.cc | 1 - .../listener/proxy_protocol/proxy_protocol.cc | 3 -- .../network/dynamic_modules/abi_impl.cc | 8 ++--- .../filters/network/ext_proc/ext_proc.cc | 1 - .../network/generic_proxy/router/upstream.cc | 1 - .../network/generic_proxy/router/upstream.h | 1 - .../filters/network/geoip/geoip_filter.cc | 1 - .../network/sni_cluster/sni_cluster.cc | 2 +- .../sni_dynamic_forward_proxy/proxy_filter.cc | 4 +-- .../extensions/filters/udp/udp_proxy/config.h | 7 ++--- .../redirect_policy/redirect_policy.cc | 2 +- .../save_processing_response.cc | 3 +- .../previous_routes/previous_routes.cc | 1 - .../override_host/load_balancer.cc | 5 ++-- .../cert_validator/dynamic_modules/config.cc | 1 - test/coverage.yaml | 2 +- 48 files changed, 70 insertions(+), 123 deletions(-) diff --git a/contrib/golang/filters/network/source/golang.cc b/contrib/golang/filters/network/source/golang.cc index 43c09309fd2e5..4a34f1b15addb 100644 --- a/contrib/golang/filters/network/source/golang.cc +++ b/contrib/golang/filters/network/source/golang.cc @@ -114,8 +114,8 @@ Network::FilterStatus Filter::onWrite(Buffer::Instance& data, bool end_stream) { return Network::FilterStatus(ret); } -CAPIStatus Filter::setFilterState(absl::string_view key, absl::string_view value, int state_type, - int life_span, int stream_sharing) { +CAPIStatus Filter::setFilterState(absl::string_view key, absl::string_view value, + int /*state_type*/, int life_span, int stream_sharing) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (closed_) { @@ -126,26 +126,23 @@ CAPIStatus Filter::setFilterState(absl::string_view key, absl::string_view value if (dispatcher_->isThreadSafe()) { read_callbacks_->connection().streamInfo().filterState()->setData( key, std::make_shared(value), - static_cast(state_type), static_cast(life_span), static_cast(stream_sharing)); } else { auto key_str = std::string(key); auto filter_state = std::make_shared(value); auto weak_ptr = weak_from_this(); - dispatcher_->post( - [this, weak_ptr, key_str, filter_state, state_type, life_span, stream_sharing] { - if (!weak_ptr.expired() && !closed_) { - Thread::LockGuard lock(mutex_); - read_callbacks_->connection().streamInfo().filterState()->setData( - key_str, filter_state, static_cast(state_type), - static_cast(life_span), - static_cast(stream_sharing)); - } else { - ENVOY_CONN_LOG(info, "golang filter has gone or destroyed in setStringFilterState", - read_callbacks_->connection()); - } - }); + dispatcher_->post([this, weak_ptr, key_str, filter_state, life_span, stream_sharing] { + if (!weak_ptr.expired() && !closed_) { + Thread::LockGuard lock(mutex_); + read_callbacks_->connection().streamInfo().filterState()->setData( + key_str, filter_state, static_cast(life_span), + static_cast(stream_sharing)); + } else { + ENVOY_CONN_LOG(info, "golang filter has gone or destroyed in setStringFilterState", + read_callbacks_->connection()); + } + }); } return CAPIStatus::CAPIOK; } diff --git a/contrib/golang/filters/network/source/upstream.cc b/contrib/golang/filters/network/source/upstream.cc index 5ca433d3e003d..1be1a68b69cae 100644 --- a/contrib/golang/filters/network/source/upstream.cc +++ b/contrib/golang/filters/network/source/upstream.cc @@ -65,8 +65,7 @@ UpstreamConn::UpstreamConn(std::string addr, Dso::NetworkFilterDsoPtr dynamic_li throwEnvoyExceptionOrPanic(absl::StrCat("malformed IP address: ", addr)); } stream_info_->filterState()->setData("envoy.network.transport_socket.original_dst_address", - address, StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::FilterState::LifeSpan::FilterChain, + address, StreamInfo::FilterState::LifeSpan::FilterChain, StreamInfo::StreamSharingMayImpactPooling::None); } diff --git a/contrib/golang/filters/network/test/filter_test.cc b/contrib/golang/filters/network/test/filter_test.cc index cde51bac8b6f4..c18b83ed512ff 100644 --- a/contrib/golang/filters/network/test/filter_test.cc +++ b/contrib/golang/filters/network/test/filter_test.cc @@ -89,8 +89,7 @@ TEST_F(FilterTest, FilterState) { initialize(); EXPECT_CALL(*dso_.get(), envoyGoFilterOnDownstreamConnection(_, _, _, _)); filter_->onNewConnection(); - filter_->setFilterState("filterStateKey", "filterStateValue", - static_cast(StreamInfo::FilterState::StateType::Mutable), + filter_->setFilterState("filterStateKey", "filterStateValue", 0 /* unused state_type */, static_cast(StreamInfo::FilterState::LifeSpan::Connection), static_cast(StreamInfo::StreamSharingMayImpactPooling::None)); GoString str; diff --git a/contrib/istio/filters/http/alpn/source/alpn_filter.cc b/contrib/istio/filters/http/alpn/source/alpn_filter.cc index 9358aeff340ee..66456713da184 100644 --- a/contrib/istio/filters/http/alpn/source/alpn_filter.cc +++ b/contrib/istio/filters/http/alpn/source/alpn_filter.cc @@ -78,8 +78,7 @@ Http::FilterHeadersStatus AlpnFilter::decodeHeaders(Http::RequestHeaderMap&, boo ENVOY_LOG(debug, "override with {} ALPNs", alpn_override.size()); decoder_callbacks_->streamInfo().filterState()->setData( Network::ApplicationProtocols::key(), - std::make_unique(alpn_override), - Envoy::StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique(alpn_override)); } else { ENVOY_LOG(debug, "ALPN override is empty"); } diff --git a/contrib/istio/filters/http/peer_metadata/source/peer_metadata.cc b/contrib/istio/filters/http/peer_metadata/source/peer_metadata.cc index b80ad7a1dfa50..a356549db46e6 100644 --- a/contrib/istio/filters/http/peer_metadata/source/peer_metadata.cc +++ b/contrib/istio/filters/http/peer_metadata/source/peer_metadata.cc @@ -415,17 +415,17 @@ void FilterConfig::setFilterState(StreamInfo::StreamInfo& info, bool downstream, auto pb = value.serializeAsProto(); auto cel_state = std::make_unique(FilterConfig::peerInfoPrototype()); cel_state->setValue(absl::string_view(pb->SerializeAsString())); - info.filterState()->setData( - key, std::move(cel_state), StreamInfo::FilterState::StateType::Mutable, - StreamInfo::FilterState::LifeSpan::FilterChain, sharedWithUpstream()); + info.filterState()->setData(key, std::move(cel_state), + StreamInfo::FilterState::LifeSpan::FilterChain, + sharedWithUpstream()); // Also store WorkloadMetadataObject under a separate key for FIELD accessor support. // WorkloadMetadataObject implements hasFieldSupport() + getField() for // formatters using %FILTER_STATE(downstream_peer_obj:FIELD:fieldname)% syntax. auto workload_metadata = std::make_unique(value); - info.filterState()->setData( - obj_key, std::move(workload_metadata), StreamInfo::FilterState::StateType::Mutable, - StreamInfo::FilterState::LifeSpan::FilterChain, sharedWithUpstream()); + info.filterState()->setData(obj_key, std::move(workload_metadata), + StreamInfo::FilterState::LifeSpan::FilterChain, + sharedWithUpstream()); } else { ENVOY_LOG(debug, "Duplicate peer metadata, skipping"); } diff --git a/contrib/istio/filters/network/metadata_exchange/source/metadata_exchange.cc b/contrib/istio/filters/network/metadata_exchange/source/metadata_exchange.cc index 064aa6f674034..67451d3d31c04 100644 --- a/contrib/istio/filters/network/metadata_exchange/source/metadata_exchange.cc +++ b/contrib/istio/filters/network/metadata_exchange/source/metadata_exchange.cc @@ -277,13 +277,11 @@ void MetadataExchangeFilter::updatePeer(const Istio::Common::WorkloadMetadataObj auto& filter_state = *read_callbacks_->connection().streamInfo().filterState(); filter_state.setData(filter_state_key, std::move(peer_info), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); // Also store WorkloadMetadataObject for FIELD accessor and peerInfoRead() detection auto workload_metadata = std::make_unique(value); filter_state.setData(obj_key, std::move(workload_metadata), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } @@ -361,7 +359,7 @@ void MetadataExchangeFilter::setMetadataNotFoundFilterState() { } read_callbacks_->connection().streamInfo().filterState()->setData( Istio::Common::NoPeer, std::make_shared(true), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); } } // namespace MetadataExchange diff --git a/contrib/istio/filters/network/peer_metadata/source/peer_metadata.cc b/contrib/istio/filters/network/peer_metadata/source/peer_metadata.cc index fc79086144eb4..d98158ac02195 100644 --- a/contrib/istio/filters/network/peer_metadata/source/peer_metadata.cc +++ b/contrib/istio/filters/network/peer_metadata/source/peer_metadata.cc @@ -106,7 +106,7 @@ void Filter::populateBaggage() { ASSERT(read_callbacks_); read_callbacks_->connection().streamInfo().filterState()->setData( config_.baggage_key(), std::make_shared(baggage_), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + StreamInfo::FilterState::LifeSpan::FilterChain); } bool Filter::disableDiscovery() const { @@ -340,8 +340,7 @@ void UpstreamFilter::populatePeerMetadata(const Istio::Common::WorkloadMetadataO auto cel = std::make_shared(peerInfoPrototype()); cel->setValue(absl::string_view(proto.SerializeAsString())); callbacks_->connection().streamInfo().filterState()->setData( - Istio::Common::UpstreamPeer, std::move(cel), StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::FilterState::LifeSpan::Connection); + Istio::Common::UpstreamPeer, std::move(cel), StreamInfo::FilterState::LifeSpan::Connection); } void UpstreamFilter::populateNoPeerMetadata() { @@ -350,7 +349,7 @@ void UpstreamFilter::populateNoPeerMetadata() { callbacks_->connection().streamInfo().filterState()->setData( Istio::Common::NoPeer, std::make_shared(true), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); } ConfigFactory::ConfigFactory() diff --git a/contrib/istio/filters/network/peer_metadata/test/peer_metadata_test.cc b/contrib/istio/filters/network/peer_metadata/test/peer_metadata_test.cc index 703fb157226e1..976c80dfc8c77 100644 --- a/contrib/istio/filters/network/peer_metadata/test/peer_metadata_test.cc +++ b/contrib/istio/filters/network/peer_metadata/test/peer_metadata_test.cc @@ -92,7 +92,6 @@ class PeerMetadataFilterTest : public ::testing::Test { std::make_unique(response_headers)); auto filter_state = stream_info_.filterState(); filter_state->setData(TcpProxy::TunnelResponseHeaders::key(), headers, - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } diff --git a/mobile/library/common/extensions/filters/http/network_configuration/filter.cc b/mobile/library/common/extensions/filters/http/network_configuration/filter.cc index b5c003750e0b1..a48ffb4a37878 100644 --- a/mobile/library/common/extensions/filters/http/network_configuration/filter.cc +++ b/mobile/library/common/extensions/filters/http/network_configuration/filter.cc @@ -30,7 +30,7 @@ void NetworkConfigurationFilter::setDecoderFilterCallbacks( decoder_callbacks_ = &callbacks; decoder_callbacks_->streamInfo().filterState()->setData( StreamInfo::ExtraStreamInfo::key(), std::move(new_extra_stream_info), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); + StreamInfo::FilterState::LifeSpan::Request); auto options = std::make_shared(); connectivity_manager_->setInterfaceBindingEnabled(enable_interface_binding_); @@ -196,7 +196,7 @@ void NetworkConfigurationFilter::setInfo(absl::string_view authority, decoder_callbacks_->streamInfo().filterState()->setData( Network::Http11ProxyInfoFilterState::key(), std::make_unique(authority, address), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + StreamInfo::FilterState::LifeSpan::FilterChain); } Http::FilterHeadersStatus NetworkConfigurationFilter::encodeHeaders(Http::ResponseHeaderMap&, diff --git a/mobile/test/common/http/client_test.cc b/mobile/test/common/http/client_test.cc index 404707a1606f6..6ec2f79b852dd 100644 --- a/mobile/test/common/http/client_test.cc +++ b/mobile/test/common/http/client_test.cc @@ -1016,7 +1016,6 @@ TEST_P(ClientTest, SaveLatestStreamIntelPopulatesScone) { scone_state->scone_max_kbps = 100; scone_state->timestamp_ms = 12345; stream_info_.filter_state_->setData(Quic::SconeStateKey, scone_state, - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); auto stream_ptr = getDirectStream(stream_); @@ -1038,7 +1037,6 @@ TEST_P(ClientTest, SaveLatestStreamIntelPersistsScone) { scone_state->scone_max_kbps = 100; scone_state->timestamp_ms = 12345; stream_info_.filter_state_->setData(Quic::SconeStateKey, scone_state, - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); auto stream_ptr = getDirectStream(stream_); @@ -1073,7 +1071,6 @@ TEST_P(ClientTest, SaveLatestStreamIntelWithZeroSconeValue) { scone_state->scone_max_kbps = 0; scone_state->timestamp_ms = 12345; stream_info_.filter_state_->setData(Quic::SconeStateKey, scone_state, - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); auto stream_ptr = getDirectStream(stream_); diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index d6940a569b90d..56e1658fa13e1 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -201,7 +201,7 @@ void ConnectionManagerImpl::initializeReadFilterCallbacks(Network::ReadFilterCal std::make_unique(Network::ProxyProtocolData{ read_callbacks_->connection().connectionInfoProvider().remoteAddress(), read_callbacks_->connection().connectionInfoProvider().localAddress()}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); } if (config_->idleTimeout()) { @@ -1516,7 +1516,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapSharedPt filter_manager_.streamInfo().filterState()->setData( Router::OriginalConnectPort::key(), std::make_unique(optional_port.value()), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request); + StreamInfo::FilterState::LifeSpan::Request); } if (!state_.is_internally_created_) { // Only sanitize headers on first pass. diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 67b746f49fab6..a041c9d09c0fa 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -381,7 +381,6 @@ void ActiveStreamFilterBase::sendLocalReply( streamInfo().filterState()->setData( LocalReplyFilterStateKey, std::make_shared(filter_context_.config_name), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); } diff --git a/source/common/listener_manager/active_tcp_socket.cc b/source/common/listener_manager/active_tcp_socket.cc index a7a3bc2f868c9..09bd0804f75e8 100644 --- a/source/common/listener_manager/active_tcp_socket.cc +++ b/source/common/listener_manager/active_tcp_socket.cc @@ -26,7 +26,6 @@ ActiveTcpSocket::ActiveTcpSocket(ActiveStreamListenerBase& listener, stream_info_->filterState()->setData( Network::DownstreamNetworkNamespace::key(), std::make_unique(*network_namespace), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); } diff --git a/source/common/quic/envoy_quic_client_session.cc b/source/common/quic/envoy_quic_client_session.cc index 70948cf74efe0..38ec93bdbaba4 100644 --- a/source/common/quic/envoy_quic_client_session.cc +++ b/source/common/quic/envoy_quic_client_session.cc @@ -121,7 +121,6 @@ EnvoyQuicClientSession::EnvoyQuicClientSession( #endif streamInfo().filterState()->setData(SconeStateKey, std::make_shared(), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 1554e048ef2d3..6764c6bc2fd76 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -687,16 +687,14 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, if (!parsed_authority.is_ip_address_ && upstream_http_protocol_options->auto_sni() && !filter_state->hasDataWithName(Network::UpstreamServerName::key())) { filter_state->setData(Network::UpstreamServerName::key(), - std::make_unique(parsed_authority.host_), - StreamInfo::FilterState::StateType::Mutable); + std::make_unique(parsed_authority.host_)); } if (upstream_http_protocol_options->auto_san_validation() && !filter_state->hasDataWithName(Network::UpstreamSubjectAltNames::key())) { filter_state->setData(Network::UpstreamSubjectAltNames::key(), std::make_unique( - std::vector{std::string(parsed_authority.host_)}), - StreamInfo::FilterState::StateType::Mutable); + std::vector{std::string(parsed_authority.host_)})); } } @@ -2215,7 +2213,6 @@ bool Filter::convertRequestHeadersForInternalRedirect( num_internal_redirect = state.get(); filter_state->setData(NumInternalRedirectsFilterStateName, std::move(state), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); } diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index 7f2c4f5f91d6d..213535dc271ee 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -715,7 +715,7 @@ Network::FilterStatus Filter::establishUpstreamConnection() { std::make_shared(Network::ProxyProtocolData{ downstream_connection.connectionInfoProvider().remoteAddress(), downstream_connection.connectionInfoProvider().localAddress(), tlvs}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); } else if (config_->sharedConfig()->proxyProtocolTlvMergePolicy() != envoy::extensions::filters::network::tcp_proxy::v3::ADD_IF_ABSENT) { // Existing state found and merge policy is not ADD_IF_ABSENT - merge TLVs. @@ -759,7 +759,7 @@ Network::FilterStatus Filter::establishUpstreamConnection() { std::make_shared(Network::ProxyProtocolDataWithVersion{ {existing_data.src_addr_, existing_data.dst_addr_, merged_tlvs}, existing_data.version_}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); } // else: ADD_IF_ABSENT policy with existing state - keep existing state as-is. transport_socket_options_ = @@ -1040,9 +1040,9 @@ void TunnelingConfigHelperImpl::propagateResponseHeaders( if (!propagate_response_headers_) { return; } - filter_state->setData( - TunnelResponseHeaders::key(), std::make_shared(std::move(headers)), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + filter_state->setData(TunnelResponseHeaders::key(), + std::make_shared(std::move(headers)), + StreamInfo::FilterState::LifeSpan::Connection); } void TunnelingConfigHelperImpl::propagateResponseTrailers( @@ -1051,9 +1051,9 @@ void TunnelingConfigHelperImpl::propagateResponseTrailers( if (!propagate_response_trailers_) { return; } - filter_state->setData( - TunnelResponseTrailers::key(), std::make_shared(std::move(trailers)), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); + filter_state->setData(TunnelResponseTrailers::key(), + std::make_shared(std::move(trailers)), + StreamInfo::FilterState::LifeSpan::Connection); } void Filter::onConnectTimeout() { diff --git a/source/extensions/access_loggers/stats/stats.cc b/source/extensions/access_loggers/stats/stats.cc index d51513c7f4a3e..6b94be41a4811 100644 --- a/source/extensions/access_loggers/stats/stats.cc +++ b/source/extensions/access_loggers/stats/stats.cc @@ -462,9 +462,9 @@ void StatsAccessLog::emitLogForGauge(const Gauge& gauge, const Formatter::Contex if (!filter_state.hasData(AccessLogState::key())) { // TODO(TAOXUY): Create a new PR that adds test coverage around any corner cases of which // level should be used, and adds this comment or an updated version. - filter_state.setData( - AccessLogState::key(), std::make_shared(shared_from_this()), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); + filter_state.setData(AccessLogState::key(), + std::make_shared(shared_from_this()), + StreamInfo::FilterState::LifeSpan::Request); } auto* state = filter_state.getDataMutable(AccessLogState::key()); diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc index cf8c578764b8f..136388afb574a 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.cc @@ -37,7 +37,6 @@ void maybeSetStringFilterState(StreamInfo::FilterState& filter_state, absl::stri } filter_state.setData(key, std::make_shared(value), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); } @@ -48,7 +47,6 @@ void maybeSetUint64FilterState(StreamInfo::FilterState& filter_state, absl::stri } filter_state.setData(key, std::make_shared(value), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); } diff --git a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.cc b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.cc index 671d4b7ca17e9..abd9b63fd97b3 100644 --- a/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.cc +++ b/source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_upstream_lifecycle.cc @@ -46,7 +46,6 @@ void maybeSetStringFilterState(StreamInfo::FilterState& filter_state, absl::stri } filter_state.setData(key, std::make_shared(value), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); } @@ -57,7 +56,6 @@ void maybeSetUint64FilterState(StreamInfo::FilterState& filter_state, absl::stri } filter_state.setData(key, std::make_shared(value), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); } diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 3fafdbda4cc5c..014c595d835ea 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -1098,9 +1098,7 @@ WasmResult Context::setProperty(std::string_view path, std::string_view value) { : *it->second.get(); // NOLINT auto state_ptr = std::make_unique(prototype); state = state_ptr.get(); - stream_info->filterState()->setData(key, std::move(state_ptr), - StreamInfo::FilterState::StateType::Mutable, - prototype.life_span_); + stream_info->filterState()->setData(key, std::move(state_ptr), prototype.life_span_); } if (!state->setValue(toAbslStringView(value))) { return WasmResult::BadArgument; @@ -1126,8 +1124,7 @@ WasmResult Context::setEnvoyFilterState(std::string_view path, std::string_view return WasmResult::NotFound; } - stream_info->filterState()->setData(path, std::move(object), - StreamInfo::FilterState::StateType::Mutable, life_span); + stream_info->filterState()->setData(path, std::move(object), life_span); return WasmResult::Ok; } diff --git a/source/extensions/filters/http/cache/cache_filter.cc b/source/extensions/filters/http/cache/cache_filter.cc index 3c078d033ccd8..8b9c1c4ced687 100644 --- a/source/extensions/filters/http/cache/cache_filter.cc +++ b/source/extensions/filters/http/cache/cache_filter.cc @@ -94,8 +94,7 @@ void CacheFilter::onStreamComplete() { InsertStatus insert_status = insertStatus(); decoder_callbacks_->streamInfo().filterState()->setData( CacheFilterLoggingInfo::FilterStateKey, - std::make_shared(lookup_status, insert_status), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_shared(lookup_status, insert_status)); } Http::FilterHeadersStatus CacheFilter::decodeHeaders(Http::RequestHeaderMap& headers, diff --git a/source/extensions/filters/http/composite/filter.cc b/source/extensions/filters/http/composite/filter.cc index 1ea21afc13551..bb7ce8b4df92b 100644 --- a/source/extensions/filters/http/composite/filter.cc +++ b/source/extensions/filters/http/composite/filter.cc @@ -288,7 +288,6 @@ void Filter::updateFilterState(Http::StreamFilterCallbacks* callback, } else { callback->streamInfo().filterState()->setData( MatchedActionsFilterStateKey, std::make_shared(filter_name, action_name), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::FilterChain); } } diff --git a/source/extensions/filters/http/compressor/compressor_filter.cc b/source/extensions/filters/http/compressor/compressor_filter.cc index ebb6e01dc6828..2811a5d423969 100644 --- a/source/extensions/filters/http/compressor/compressor_filter.cc +++ b/source/extensions/filters/http/compressor/compressor_filter.cc @@ -317,8 +317,7 @@ void CompressorFilter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallba } else { auto registry_ptr = std::make_unique(); registry_ptr->filter_configs_.push_back(config_); - filter_state->setData(key, std::move(registry_ptr), - StreamInfo::FilterState::StateType::Mutable); + filter_state->setData(key, std::move(registry_ptr)); } } @@ -708,8 +707,7 @@ bool CompressorFilter::isAcceptEncodingAllowed(const Http::ResponseHeaderMap& he // No cached decision found, so decide now. std::unique_ptr decision = chooseEncoding(headers); bool result = shouldCompress(*decision); - filter_state->setData(encoding_decision_key, std::move(decision), - StreamInfo::FilterState::StateType::ReadOnly); + filter_state->setData(encoding_decision_key, std::move(decision)); return result; } diff --git a/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc b/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc index 374aebe35c6f8..6b99ad5897cc8 100644 --- a/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc +++ b/source/extensions/filters/http/dynamic_forward_proxy/proxy_filter.cc @@ -438,9 +438,9 @@ void ProxyFilter::addHostAddressToFilterState( const Envoy::StreamInfo::FilterStateSharedPtr& filter_state = decoder_callbacks_->streamInfo().filterState(); - filter_state->setData( - StreamInfo::UpstreamAddress::key(), std::make_unique(address), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); + filter_state->setData(StreamInfo::UpstreamAddress::key(), + std::make_unique(address), + StreamInfo::FilterState::LifeSpan::Request); } void ProxyFilter::onLoadClusterComplete() { diff --git a/source/extensions/filters/http/dynamic_modules/abi_impl.cc b/source/extensions/filters/http/dynamic_modules/abi_impl.cc index 0f5220fc95f8f..b1ef8e060704d 100644 --- a/source/extensions/filters/http/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/http/dynamic_modules/abi_impl.cc @@ -1267,8 +1267,7 @@ bool envoy_dynamic_module_callback_http_set_filter_state_bytes( absl::string_view key_view(key.ptr, key.length); absl::string_view value_view(value.ptr, value.length); stream_info->filterState()->setData(key_view, - std::make_unique(value_view), - StreamInfo::FilterState::StateType::ReadOnly); + std::make_unique(value_view)); return true; } @@ -1324,8 +1323,7 @@ bool envoy_dynamic_module_callback_http_set_filter_state_typed( return false; } - stream_info->filterState()->setData(key_view, std::move(object), - StreamInfo::FilterState::StateType::Mutable); + stream_info->filterState()->setData(key_view, std::move(object)); return true; } diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index f6b5ce114cb04..1238513fe63f6 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -292,7 +292,6 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) { if (!filter_state->hasData(decoder_callbacks_->filterConfigName())) { filter_state->setData(decoder_callbacks_->filterConfigName(), std::make_shared(config_->filterMetadata()), - Envoy::StreamInfo::FilterState::StateType::Mutable, Envoy::StreamInfo::FilterState::LifeSpan::Request); // This may return nullptr (if there's a value at this name whose type doesn't match or isn't @@ -1355,7 +1354,7 @@ void Filter::setShadowFilterState(Filters::Common::ExtAuthz::Response& response) // already uses the filter's configured name as its FilterState key (with a different lifespan). decoder_callbacks_->streamInfo().filterState()->setData( absl::StrCat(decoder_callbacks_->filterConfigName(), ".shadow"), std::move(object), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); + StreamInfo::FilterState::LifeSpan::FilterChain); } } // namespace ExtAuthz diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index fcd478f4aa1e3..388843a4f1ae1 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -689,7 +689,6 @@ void Filter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callb if (!filter_state->hasData(callbacks.filterConfigName())) { filter_state->setData(callbacks.filterConfigName(), std::make_shared(config_->filterMetadata()), - Envoy::StreamInfo::FilterState::StateType::Mutable, Envoy::StreamInfo::FilterState::LifeSpan::Request); } logging_info_ = filter_state->getDataMutable(callbacks.filterConfigName()); diff --git a/source/extensions/filters/http/grpc_stats/grpc_stats_filter.cc b/source/extensions/filters/http/grpc_stats/grpc_stats_filter.cc index edd50159f0e4e..6daef6ba21ec0 100644 --- a/source/extensions/filters/http/grpc_stats/grpc_stats_filter.cc +++ b/source/extensions/filters/http/grpc_stats/grpc_stats_filter.cc @@ -261,7 +261,6 @@ class GrpcStatsFilter : public Http::PassThroughFilter { filter_object_ = state.get(); decoder_callbacks_->streamInfo().filterState()->setData( "envoy.filters.http.grpc_stats", std::move(state), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::FilterChain); } if (connect_unary_) { diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc index 41f0fc67cfd6e..42648c10fdb36 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc @@ -247,8 +247,7 @@ Filters::Common::LocalRateLimit::LocalRateLimiterImpl& Filter::getPerConnectionR used_config_->descriptors(), used_config_->consumeDefaultTokenBucket()); decoder_callbacks_->streamInfo().filterState()->setData( - PerConnectionRateLimiter::key(), limiter, StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::FilterState::LifeSpan::Connection); + PerConnectionRateLimiter::key(), limiter, StreamInfo::FilterState::LifeSpan::Connection); return const_cast(limiter->value()); } diff --git a/source/extensions/filters/http/lua/wrappers.cc b/source/extensions/filters/http/lua/wrappers.cc index 4b32112876653..0cae89374b7a2 100644 --- a/source/extensions/filters/http/lua/wrappers.cc +++ b/source/extensions/filters/http/lua/wrappers.cc @@ -473,7 +473,6 @@ int FilterStateWrapper::luaSet(lua_State* state) { } streamInfo().filterState()->setData(object_key, std::move(object), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain, StreamInfo::StreamSharingMayImpactPooling::None); return 0; diff --git a/source/extensions/filters/http/mcp/mcp_filter.cc b/source/extensions/filters/http/mcp/mcp_filter.cc index 379a649ffcd17..4e49c888c2a1a 100644 --- a/source/extensions/filters/http/mcp/mcp_filter.cc +++ b/source/extensions/filters/http/mcp/mcp_filter.cc @@ -387,7 +387,7 @@ Http::FilterDataStatus McpFilter::completeParsing() { std::make_shared(parser_->getMethod(), metadata, is_mcp_request_); decoder_callbacks_->streamInfo().filterState()->setData( std::string(FilterStateObject::FilterStateKey), std::move(filter_state_obj), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request, + StreamInfo::FilterState::LifeSpan::Request, StreamInfo::StreamSharingMayImpactPooling::None); } diff --git a/source/extensions/filters/listener/dynamic_modules/abi_impl.cc b/source/extensions/filters/listener/dynamic_modules/abi_impl.cc index fefae732d891c..bee0b5f5d3e6b 100644 --- a/source/extensions/filters/listener/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/listener/dynamic_modules/abi_impl.cc @@ -781,7 +781,6 @@ bool envoy_dynamic_module_callback_listener_filter_set_filter_state( // TODO(wbpcode): check whether the key already exists and whether overwriting is allowed. callbacks->filterState().setData(key_str, std::make_shared(value_str), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); return true; } diff --git a/source/extensions/filters/listener/original_dst/original_dst.cc b/source/extensions/filters/listener/original_dst/original_dst.cc index 64dfcda42f561..e155e88712936 100644 --- a/source/extensions/filters/listener/original_dst/original_dst.cc +++ b/source/extensions/filters/listener/original_dst/original_dst.cc @@ -55,7 +55,6 @@ Network::FilterStatus OriginalDstFilter::onAccept(Network::ListenerFilterCallbac if (!has_options) { filter_state.setData(Network::UpstreamSocketOptionsFilterState::key(), std::make_unique(), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } filter_state diff --git a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc index 72caa90b6cdb9..3df2d43397c2f 100644 --- a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc @@ -309,7 +309,6 @@ ReadOrParseState Filter::parseBuffer(Network::ListenerFilterBuffer& buffer) { {socket.connectionInfoProvider().remoteAddress(), socket.connectionInfoProvider().localAddress(), parsed_tlvs_}, absl::make_optional(header_version_)}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } else { ENVOY_LOG( @@ -327,7 +326,6 @@ ReadOrParseState Filter::parseBuffer(Network::ListenerFilterBuffer& buffer) { {proxy_protocol_header_.value().remote_address_, proxy_protocol_header_.value().local_address_, parsed_tlvs_}, absl::make_optional(header_version_)}), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } } @@ -624,7 +622,6 @@ bool Filter::parseTlvs(const uint8_t* buf, size_t len) { auto new_obj = std::make_unique(); tlv_filter_state_obj = new_obj.get(); cb_->filterState().setData(kFilterStateKey, std::move(new_obj), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); ENVOY_LOG(trace, "proxy_protocol: Created TLV FilterState object"); } diff --git a/source/extensions/filters/network/dynamic_modules/abi_impl.cc b/source/extensions/filters/network/dynamic_modules/abi_impl.cc index 24a97cdc89e0e..7aa16f64726e3 100644 --- a/source/extensions/filters/network/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/network/dynamic_modules/abi_impl.cc @@ -442,9 +442,9 @@ bool envoy_dynamic_module_callback_network_set_filter_state_bytes( absl::string_view key_view(key.ptr, key.length); absl::string_view value_view(value.ptr, value.length); - stream_info.filterState()->setData( - key_view, std::make_unique(value_view), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + stream_info.filterState()->setData(key_view, + std::make_unique(value_view), + StreamInfo::FilterState::LifeSpan::Connection); return true; } @@ -493,7 +493,6 @@ bool envoy_dynamic_module_callback_network_set_filter_state_typed( } stream_info.filterState()->setData(key_view, std::move(object), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); return true; } @@ -720,7 +719,6 @@ ensureUpstreamSocketOptionsFilterState(DynamicModuleNetworkFilter& filter) { if (!has_options) { filter_state.setData(Network::UpstreamSocketOptionsFilterState::key(), std::make_unique(), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } return filter_state.getDataMutable( diff --git a/source/extensions/filters/network/ext_proc/ext_proc.cc b/source/extensions/filters/network/ext_proc/ext_proc.cc index 5d130d8264d81..b82b447634b5b 100644 --- a/source/extensions/filters/network/ext_proc/ext_proc.cc +++ b/source/extensions/filters/network/ext_proc/ext_proc.cc @@ -110,7 +110,6 @@ void NetworkExtProcFilter::initializeLoggingInfo() { NetworkFilterNames::get().NetworkExternalProcessor)) { filter_state->setData(NetworkFilterNames::get().NetworkExternalProcessor, std::make_shared(), - Envoy::StreamInfo::FilterState::StateType::Mutable, Envoy::StreamInfo::FilterState::LifeSpan::Connection); } diff --git a/source/extensions/filters/network/generic_proxy/router/upstream.cc b/source/extensions/filters/network/generic_proxy/router/upstream.cc index 16b365dfe7e1c..c9e3e1e534473 100644 --- a/source/extensions/filters/network/generic_proxy/router/upstream.cc +++ b/source/extensions/filters/network/generic_proxy/router/upstream.cc @@ -330,7 +330,6 @@ GenericUpstreamSharedPtr ProdGenericUpstreamFactory::createGenericUpstream( bound_upstream = new_bound_upstream.get(); downstream_conn.streamInfo().filterState()->setData( RouterFilterName, std::move(new_bound_upstream), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } return bound_upstream->shared_from_this(); diff --git a/source/extensions/filters/network/generic_proxy/router/upstream.h b/source/extensions/filters/network/generic_proxy/router/upstream.h index e551596a224dc..9964d5aef51a0 100644 --- a/source/extensions/filters/network/generic_proxy/router/upstream.h +++ b/source/extensions/filters/network/generic_proxy/router/upstream.h @@ -320,7 +320,6 @@ class UpstreamBase : public GenericUpstream, encoder_decoder = data.get(); connection.streamInfo().filterState()->setData(RouterFilterEncoderDecoderName, std::move(data), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); } return encoder_decoder; diff --git a/source/extensions/filters/network/geoip/geoip_filter.cc b/source/extensions/filters/network/geoip/geoip_filter.cc index 8294bf485b42e..1d9bf24e534c9 100644 --- a/source/extensions/filters/network/geoip/geoip_filter.cc +++ b/source/extensions/filters/network/geoip/geoip_filter.cc @@ -118,7 +118,6 @@ void GeoipFilter::onLookupComplete(Geolocation::LookupResult&& result) { if (!geoip_info->empty()) { read_callbacks_->connection().streamInfo().filterState()->setData( std::string(GeoipFilterStateKey), std::move(geoip_info), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); ENVOY_LOG(debug, "geoip: stored data in filter state key '{}'", GeoipFilterStateKey); } diff --git a/source/extensions/filters/network/sni_cluster/sni_cluster.cc b/source/extensions/filters/network/sni_cluster/sni_cluster.cc index 6fea2639de93f..b52827d35a0c5 100644 --- a/source/extensions/filters/network/sni_cluster/sni_cluster.cc +++ b/source/extensions/filters/network/sni_cluster/sni_cluster.cc @@ -21,7 +21,7 @@ Network::FilterStatus SniClusterFilter::onNewConnection() { read_callbacks_->connection().streamInfo().filterState()->setData( TcpProxy::PerConnectionCluster::key(), std::make_unique(sni), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); } return Network::FilterStatus::Continue; diff --git a/source/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter.cc b/source/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter.cc index 7ca54837c194b..319d93bd8a193 100644 --- a/source/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter.cc +++ b/source/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter.cc @@ -75,7 +75,7 @@ Network::FilterStatus ProxyFilter::onNewConnection() { port = config_->port(); read_callbacks_->connection().streamInfo().filterState()->setData( "envoy.upstream.dynamic_port", std::make_shared(port), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); } auto result = config_->cache().loadDnsCacheEntry(host, port, false, *this); @@ -140,7 +140,7 @@ void ProxyFilter::addHostAddressToFilterState( read_callbacks_->connection().streamInfo().filterState()->setData( StreamInfo::UpstreamAddress::key(), std::move(address_obj), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + StreamInfo::FilterState::LifeSpan::Connection); } } // namespace SniDynamicForwardProxy diff --git a/source/extensions/filters/udp/udp_proxy/config.h b/source/extensions/filters/udp/udp_proxy/config.h index 3657a2e2c3688..0bf81cfa2b975 100644 --- a/source/extensions/filters/udp/udp_proxy/config.h +++ b/source/extensions/filters/udp/udp_proxy/config.h @@ -74,9 +74,9 @@ class TunnelingConfigImpl : public UdpTunnelingConfig { return; } - filter_state->setData( - TunnelResponseHeaders::key(), std::make_shared(std::move(headers)), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); + filter_state->setData(TunnelResponseHeaders::key(), + std::make_shared(std::move(headers)), + StreamInfo::FilterState::LifeSpan::Connection); } void @@ -88,7 +88,6 @@ class TunnelingConfigImpl : public UdpTunnelingConfig { filter_state->setData(TunnelResponseTrailers::key(), std::make_shared(std::move(trailers)), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); } diff --git a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc index 36fdb178518f5..37af18d5b671d 100644 --- a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc +++ b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc @@ -222,7 +222,7 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( "envoy.filters.http.custom_response", std::make_shared<::Envoy::Extensions::HttpFilters::CustomResponse::CustomResponseFilterState>( const_cast(this)->shared_from_this(), original_response_code), - StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request); + StreamInfo::FilterState::LifeSpan::Request); restore_original_headers.cancel(); decoder_callbacks->recreateStream(nullptr); diff --git a/source/extensions/http/ext_proc/response_processors/save_processing_response/save_processing_response.cc b/source/extensions/http/ext_proc/response_processors/save_processing_response/save_processing_response.cc index b4eb171033589..2c46bb0ee46fa 100644 --- a/source/extensions/http/ext_proc/response_processors/save_processing_response/save_processing_response.cc +++ b/source/extensions/http/ext_proc/response_processors/save_processing_response/save_processing_response.cc @@ -27,8 +27,7 @@ void SaveProcessingResponse::addToFilterState( if (filter_state == nullptr) { auto shared_filter_state = std::make_shared(); filter_state = shared_filter_state.get(); - stream_info.filterState()->setData(filter_state_name_, shared_filter_state, - Envoy::StreamInfo::FilterState::StateType::Mutable); + stream_info.filterState()->setData(filter_state_name_, shared_filter_state); } filter_state->response.emplace(SaveProcessingResponseFilterState::Response{ diff --git a/source/extensions/internal_redirect/previous_routes/previous_routes.cc b/source/extensions/internal_redirect/previous_routes/previous_routes.cc index a85031ffbb41d..d0689bfd60377 100644 --- a/source/extensions/internal_redirect/previous_routes/previous_routes.cc +++ b/source/extensions/internal_redirect/previous_routes/previous_routes.cc @@ -39,7 +39,6 @@ bool PreviousRoutesPredicate::acceptTargetRoute(StreamInfo::FilterState& filter_ absl::StrCat(PreviousRoutesPredicateStateNamePrefix, ".", current_route_name_); if (!filter_state.hasData(filter_state_name)) { filter_state.setData(filter_state_name, std::make_unique(), - StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Request); } auto predicate_state = diff --git a/source/extensions/load_balancing_policies/override_host/load_balancer.cc b/source/extensions/load_balancing_policies/override_host/load_balancer.cc index 05929336163ea..1ca077594c642 100644 --- a/source/extensions/load_balancing_policies/override_host/load_balancer.cc +++ b/source/extensions/load_balancing_policies/override_host/load_balancer.cc @@ -166,9 +166,8 @@ OverrideHostLoadBalancer::LoadBalancerImpl::chooseHostInternal(LoadBalancerConte auto state_ptr = std::make_shared(getSelectedHosts(context)); override_host_state = state_ptr.get(); - context->requestStreamInfo()->filterState()->setData( - OverrideHostFilterState::kFilterStateKey, std::move(state_ptr), - StreamInfo::FilterState::StateType::Mutable); + context->requestStreamInfo()->filterState()->setData(OverrideHostFilterState::kFilterStateKey, + std::move(state_ptr)); } if (override_host_state->empty()) { diff --git a/source/extensions/transport_sockets/tls/cert_validator/dynamic_modules/config.cc b/source/extensions/transport_sockets/tls/cert_validator/dynamic_modules/config.cc index b2cc327c6730a..1b6466e2e8d95 100644 --- a/source/extensions/transport_sockets/tls/cert_validator/dynamic_modules/config.cc +++ b/source/extensions/transport_sockets/tls/cert_validator/dynamic_modules/config.cc @@ -42,7 +42,6 @@ bool envoy_dynamic_module_callback_cert_validator_set_filter_state( config->current_callbacks_->connection().streamInfo().filterState()->setData( key_str, std::make_shared(value_str), - Envoy::StreamInfo::FilterState::StateType::ReadOnly, Envoy::StreamInfo::FilterState::LifeSpan::Connection); return true; } diff --git a/test/coverage.yaml b/test/coverage.yaml index 86382fcb9e89d..7b1127eb6224b 100644 --- a/test/coverage.yaml +++ b/test/coverage.yaml @@ -64,7 +64,7 @@ directories: source/extensions/internal_redirect: 86.2 source/extensions/internal_redirect/safe_cross_scheme: 81.3 source/extensions/internal_redirect/allow_listed_routes: 85.7 - source/extensions/internal_redirect/previous_routes: 89.3 + source/extensions/internal_redirect/previous_routes: 88.9 source/extensions/load_balancing_policies/common: 96.4 source/extensions/load_balancing_policies/maglev: 95.2 source/extensions/load_balancing_policies/round_robin: 96.4 From 24f21f94b39b2110cb1cbd306b616a4bc0eff642 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 21:31:15 +0000 Subject: [PATCH 362/750] deps: Bump build images -> `v0.1.4` (#45193) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- .github/config.yml | 18 +++++++++--------- .github/workflows/envoy-dependency.yml | 2 +- test/config/integration/certs/certs.sh | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/config.yml b/.github/config.yml index 92da6b09f73fd..69724c502ae72 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -4,16 +4,16 @@ build-image: repo: docker.io/envoyproxy/envoy-build repo-gcr: gcr.io/envoy-ci/envoy-build # default ci caching (ci) - sha: 1f67d403defd45e9dd312645122fdee70a16911fdd614378d274c31f27c518b3 - sha-ci: 1f67d403defd45e9dd312645122fdee70a16911fdd614378d274c31f27c518b3 - sha-devtools: 8e21fd13f52323f9e16fa4cf3030d5b6a7f28b1d82343735a425be961651da05 - sha-docker: c290f5f548906d3a426be5c0200e19942a794c3bbb11c6d6257f5529702d7f78 - sha-gcc: 2a12bbcd5e95bc037bae5dfb2eb6361e55faf737098cbf90b4378e2345d9eecf - sha-mobile: cf77d069ffcff08e01e9472c6b0bad608e861ede2bc1c7cb7eb68f7c8e419bce - sha-worker: 482b9906428e68a65101a1b27abf81f69dfd1b4377f66283866110bfd8127ea3 + sha: 58ec7bec0e349c7daff6a71c55fb05da62318637b13b647a4a0a96f02de28386 + sha-ci: 58ec7bec0e349c7daff6a71c55fb05da62318637b13b647a4a0a96f02de28386 + sha-devtools: 8ea83087c77726b02bc5b0eba79ce1ce94308c034833cd922a5ea0f3d5b1c610 + sha-docker: b622172bea807adb401306ed60b86b5a892e77b2cba871a384724f9082ad5667 + sha-gcc: 0adba37e34e6f42c45fa241e720c93197f5ed31d1dc76afd4880779933a91648 + sha-mobile: 406eb40441ab5cc2ac8e6e9f7330f618ce91a8163d5f9ecaad6d4bd7b1661164 + sha-worker: be284c733ef7aaba7c02b1a95e2c6760b09020840b99d9c3821ebe1293615c81 # TODO: remove this dupe (currently used by ci request handler) - mobile-sha: cf77d069ffcff08e01e9472c6b0bad608e861ede2bc1c7cb7eb68f7c8e419bce - tag: v0.1.3 + mobile-sha: 406eb40441ab5cc2ac8e6e9f7330f618ce91a8163d5f9ecaad6d4bd7b1661164 + tag: v0.1.4 config: envoy: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index da11bcbb9ee05..a43c345ea0704 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -185,7 +185,7 @@ jobs: # Source of truth: the latest `docker-v*` GitHub release in # envoyproxy/toolshed. Strip only the `docker-` prefix - the # published images keep the leading `v`, eg - # `envoyproxy/envoy-build:ci-v0.1.3`. + # `envoyproxy/envoy-build:ci-v0.1.4`. CONTAINER_TAG=$( gh api -H "Accept: application/vnd.github+json" \ --paginate \ diff --git a/test/config/integration/certs/certs.sh b/test/config/integration/certs/certs.sh index 108ed30488877..835d49a978388 100755 --- a/test/config/integration/certs/certs.sh +++ b/test/config/integration/certs/certs.sh @@ -40,7 +40,7 @@ generate_x509_cert() { generate_expired_x509_cert() { local days="${3:-730}" openssl req -new -key "${1}key.pem" -out "${1}cert.csr" -config "${1}cert.cfg" -batch -sha256 - docker run --rm -u root -v "$(pwd):/work" -w /work envoyproxy/envoy-build:gcc-v0.1.3 sh -c "apt-get update -y && apt-get install -y faketime && faketime '370 days ago' openssl x509 -req -days \"${days}\" -in \"${1}cert.csr\" -sha256 -CA \"${2}cert.pem\" -CAkey \"${2}key.pem\" -CAcreateserial -out \"${1}cert.pem\" -extensions v3_ca -extfile \"${1}cert.cfg\"" + docker run --rm -u root -v "$(pwd):/work" -w /work envoyproxy/envoy-build:gcc-v0.1.4 sh -c "apt-get update -y && apt-get install -y faketime && faketime '370 days ago' openssl x509 -req -days \"${days}\" -in \"${1}cert.csr\" -sha256 -CA \"${2}cert.pem\" -CAkey \"${2}key.pem\" -CAcreateserial -out \"${1}cert.pem\" -extensions v3_ca -extfile \"${1}cert.cfg\"" echo -e "// NOLINT(namespace-envoy)\nconstexpr char TEST_$(echo "$1" | tr "[:lower:]" "[:upper:]")_CERT_HASH[] = \"$(openssl x509 -in "${1}cert.pem" -noout -fingerprint -sha256 | cut -d"=" -f2)\";" > "${1}cert_hash.h" } From 02a4b85b5c6134593ed33f82341986905708ed06 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 20 May 2026 15:31:52 -0700 Subject: [PATCH 363/750] filter_state: remove StateType and final uses (#45200) This is the final step of #44343 Includes a few references missed previously. Signed-off-by: Greg Greenway --- .../filters/http/source/golang_filter.cc | 23 ++++++++----------- envoy/stream_info/filter_state.h | 11 --------- .../common/set_filter_state/filter_config.h | 1 - 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index 9e387c3cf09b6..aca637a077805 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -1447,7 +1447,7 @@ void Filter::setDynamicMetadataInternal(std::string filter_name, std::string key } CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view value, - int state_type, int life_span, int stream_sharing) { + int /*state_type*/, int life_span, int stream_sharing) { if (hasDestroyed()) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; @@ -1456,24 +1456,21 @@ CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view if (isThreadSafe()) { streamInfo().filterState()->setData( key, std::make_shared(value), - static_cast(state_type), static_cast(life_span), static_cast(stream_sharing)); } else { auto key_str = std::string(key); auto filter_state = std::make_shared(value); auto weak_ptr = weak_from_this(); - getDispatcher().post( - [this, weak_ptr, key_str, filter_state, state_type, life_span, stream_sharing] { - if (!weak_ptr.expired() && !hasDestroyed()) { - streamInfo().filterState()->setData( - key_str, filter_state, static_cast(state_type), - static_cast(life_span), - static_cast(stream_sharing)); - } else { - ENVOY_LOG(info, "golang filter has gone or destroyed in setStringFilterState"); - } - }); + getDispatcher().post([this, weak_ptr, key_str, filter_state, life_span, stream_sharing] { + if (!weak_ptr.expired() && !hasDestroyed()) { + streamInfo().filterState()->setData( + key_str, filter_state, static_cast(life_span), + static_cast(stream_sharing)); + } else { + ENVOY_LOG(info, "golang filter has gone or destroyed in setStringFilterState"); + } + }); } return CAPIStatus::CAPIOK; } diff --git a/envoy/stream_info/filter_state.h b/envoy/stream_info/filter_state.h index 6fc9650500321..c2cecea8d54a3 100644 --- a/envoy/stream_info/filter_state.h +++ b/envoy/stream_info/filter_state.h @@ -52,8 +52,6 @@ enum class StreamSharingMayImpactPooling { */ class FilterState { public: - enum class StateType { ReadOnly, Mutable }; - // Objects stored in the FilterState may have different life span. Life span is what controls // how long an object stored in FilterState lives. Implementation of this interface actually // stores objects in a (reverse) tree manner - multiple FilterStateImpl with shorter life span may @@ -159,15 +157,6 @@ class FilterState { LifeSpan life_span = LifeSpan::FilterChain, StreamSharingMayImpactPooling stream_sharing = StreamSharingMayImpactPooling::None) PURE; - /** - * Deprecated version of setData that takes the no longer used `StateType`. - */ - void setData(absl::string_view data_name, std::shared_ptr data, StateType /*state_type*/, - LifeSpan life_span = LifeSpan::FilterChain, - StreamSharingMayImpactPooling stream_sharing = StreamSharingMayImpactPooling::None) { - return setData(data_name, std::move(data), life_span, stream_sharing); - } - /** * @param data_name the name of the data being looked up. * @return a typed pointer to the stored data or nullptr if the data does not exist or the data diff --git a/source/extensions/filters/common/set_filter_state/filter_config.h b/source/extensions/filters/common/set_filter_state/filter_config.h index bcd65288882d5..94ff83640a1ef 100644 --- a/source/extensions/filters/common/set_filter_state/filter_config.h +++ b/source/extensions/filters/common/set_filter_state/filter_config.h @@ -14,7 +14,6 @@ namespace Common { namespace SetFilterState { using LifeSpan = StreamInfo::FilterState::LifeSpan; -using StateType = StreamInfo::FilterState::StateType; using StreamSharing = StreamInfo::StreamSharingMayImpactPooling; using FilterStateValueProto = envoy::extensions::filters::common::set_filter_state::v3::FilterStateValue; From 376e210c11d60fb097f7a42298355100f9b284b8 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Wed, 20 May 2026 20:58:11 -0400 Subject: [PATCH 364/750] changelog: migrate network_ext_proc entry to per-file format (#45199) Commit b27925c960 added a changelog entry directly to current.yaml Fix this to use the new mechanism. Signed-off-by: Jonh Wendell --- changelogs/changelogs.yaml | 2 ++ changelogs/current.yaml | 7 ------- ...twork_ext_proc__added-support-for-receiving-untyped.rst | 3 +++ 3 files changed, 5 insertions(+), 7 deletions(-) create mode 100644 changelogs/current/new_features/network_ext_proc__added-support-for-receiving-untyped.rst diff --git a/changelogs/changelogs.yaml b/changelogs/changelogs.yaml index a0d9d4415e0b4..b10ae2658c7be 100644 --- a/changelogs/changelogs.yaml +++ b/changelogs/changelogs.yaml @@ -68,6 +68,8 @@ areas: title: logging mysql_proxy: title: mysql_proxy + network_ext_proc: + title: network_ext_proc oauth2: title: oauth2 quic: diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 94a0cac1a4296..2066eb9f1d60b 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -1,8 +1 @@ date: Pending - -new_features: -- area: network_ext_proc - change: | - Added support for receiving untyped dynamic metadata from the external processing server. - Configured via :ref:`receiving_namespaces - `. diff --git a/changelogs/current/new_features/network_ext_proc__added-support-for-receiving-untyped.rst b/changelogs/current/new_features/network_ext_proc__added-support-for-receiving-untyped.rst new file mode 100644 index 0000000000000..9ee8240255ae5 --- /dev/null +++ b/changelogs/current/new_features/network_ext_proc__added-support-for-receiving-untyped.rst @@ -0,0 +1,3 @@ +Added support for receiving untyped dynamic metadata from the external processing server. +Configured via :ref:`receiving_namespaces +`. From 4616750da8dfc1e3293b7dc8db9fe5093b3ff242 Mon Sep 17 00:00:00 2001 From: Adam Anderson <6754028+AdamEAnderson@users.noreply.github.com> Date: Wed, 20 May 2026 18:29:14 -0700 Subject: [PATCH 365/750] dynamic modules: refactor existing go sdk shared api to prep for expansion (#45163) Commit Message: dynamic modules: refactor existing go sdk to prep for expansion Additional Description: getting ready to expand Go dynamic modules SDK to match the full ABI. This PR does some refactoring of the API code to split it into files that are http-related or cross-surface and to standardize on Godoc comments. Previously the http api used javadoc (i.e. `@param`, `@returns`) which isn't consistent with Golang expectations. The network filter api used Godoc as expected. Updated the Javadoc comments to conform to expectations. Otherwise no changes to the api itself, just splitting up the files differently and changing the comment formatting. See #44910 for more details related to abi changes. Risk Level: low Testing: n/a Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a --------- Signed-off-by: Adam Anderson <6754028+AdamEAnderson@users.noreply.github.com> --- source/extensions/dynamic_modules/BUILD | 5 +- .../dynamic_modules/sdk/go/shared/base.go | 873 --------------- .../{fake_stream_base.go => fake_http.go} | 5 + ..._stream_base_test.go => fake_http_test.go} | 0 .../dynamic_modules/sdk/go/shared/http.go | 561 ++++++++++ .../sdk/go/shared/{api.go => http_api.go} | 45 +- .../mocks/{mock_base.go => mock_http.go} | 321 ++---- .../mocks/{mock_api.go => mock_http_api.go} | 4 +- .../sdk/go/shared/mocks/mock_network_api.go | 220 ++++ .../sdk/go/shared/mocks/mock_network_base.go | 991 ++++++++++++++++++ .../sdk/go/shared/mocks/mock_types.go | 173 +++ .../sdk/go/shared/network_api.go | 1 + .../sdk/go/shared/network_base.go | 1 + .../dynamic_modules/sdk/go/shared/types.go | 336 ++++++ 14 files changed, 2371 insertions(+), 1165 deletions(-) delete mode 100644 source/extensions/dynamic_modules/sdk/go/shared/base.go rename source/extensions/dynamic_modules/sdk/go/shared/fake/{fake_stream_base.go => fake_http.go} (95%) rename source/extensions/dynamic_modules/sdk/go/shared/fake/{fake_stream_base_test.go => fake_http_test.go} (100%) create mode 100644 source/extensions/dynamic_modules/sdk/go/shared/http.go rename source/extensions/dynamic_modules/sdk/go/shared/{api.go => http_api.go} (82%) rename source/extensions/dynamic_modules/sdk/go/shared/mocks/{mock_base.go => mock_http.go} (88%) rename source/extensions/dynamic_modules/sdk/go/shared/mocks/{mock_api.go => mock_http_api.go} (98%) create mode 100644 source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_network_api.go create mode 100644 source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_network_base.go create mode 100644 source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_types.go create mode 100644 source/extensions/dynamic_modules/sdk/go/shared/types.go diff --git a/source/extensions/dynamic_modules/BUILD b/source/extensions/dynamic_modules/BUILD index 508e3c8d10174..201ced2f724df 100644 --- a/source/extensions/dynamic_modules/BUILD +++ b/source/extensions/dynamic_modules/BUILD @@ -54,10 +54,11 @@ envoy_cc_library( go_library( name = "go_sdk_shared", srcs = [ - "sdk/go/shared/api.go", - "sdk/go/shared/base.go", + "sdk/go/shared/http.go", + "sdk/go/shared/http_api.go", "sdk/go/shared/network_api.go", "sdk/go/shared/network_base.go", + "sdk/go/shared/types.go", ], cgo = True, importpath = "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/shared", diff --git a/source/extensions/dynamic_modules/sdk/go/shared/base.go b/source/extensions/dynamic_modules/sdk/go/shared/base.go deleted file mode 100644 index 807601c425433..0000000000000 --- a/source/extensions/dynamic_modules/sdk/go/shared/base.go +++ /dev/null @@ -1,873 +0,0 @@ -//go:generate mockgen -source=base.go -destination=mocks/mock_base.go -package=mocks -package shared - -import ( - "strings" - "unsafe" -) - -// UnsafeEnvoyBuffer is a struct that represents a buffer of data from Envoy. -// It contains a pointer to the data and its length. The memory of the data is managed by Envoy. -type UnsafeEnvoyBuffer struct { - // Pointer to the start of the buffer data. - Ptr *byte - // Length of the buffer data in bytes. - Len uint64 -} - -func (b UnsafeEnvoyBuffer) ToUnsafeBytes() []byte { - if b.Ptr == nil || b.Len == 0 { - return nil - } - // Use unsafe to create a byte slice that points to the buffer data without copying. - return unsafe.Slice(b.Ptr, b.Len) -} - -// ToBytes converts the UnsafeEnvoyBuffer to a byte slice. It creates a copy of the data in Go memory. -func (b UnsafeEnvoyBuffer) ToBytes() []byte { - if b.Ptr == nil || b.Len == 0 { - return nil - } - // Create a byte slice that copys the data from the buffer. - owned := make([]byte, b.Len) - // Use unsafe to copy the data from the buffer to the byte slice. - src := unsafe.Slice(b.Ptr, b.Len) - copy(owned, src) - return owned -} - -func (b UnsafeEnvoyBuffer) ToUnsafeString() string { - if b.Ptr == nil || b.Len == 0 { - return "" - } - // Use unsafe to create a string that points to the buffer data without copying. - return unsafe.String(b.Ptr, b.Len) -} - -func (b UnsafeEnvoyBuffer) ToString() string { - if b.Ptr == nil || b.Len == 0 { - return "" - } - return strings.Clone(b.ToUnsafeString()) -} - -// BodyBuffer is an interface that provides access to the request and response body. -// This should be implemented by the SDK or runtime. -type BodyBuffer interface { - // GetChunks retrieves the body content as a list of UnsafeEnvoyBuffer chunks. - // NOTE: The memory of underlying data may not be managed by Go GC. So you should - // copy the data if you need to keep it and use it later. - GetChunks() []UnsafeEnvoyBuffer - - // GetSize retrieves the total size of the body buffer. - GetSize() uint64 - - // Drain removes the specified number of bytes from the beginning of the body buffer. - // @Param numBytes the number of bytes to drain. - Drain(numBytes uint64) - - // Append adds the specified bytes to the end of the body buffer. - // @Param data the bytes to append. - Append(data []byte) -} - -// HeaderMap is an interface that provides access to the request and response headers. -// This should be implemented by the SDK or runtime. -type HeaderMap interface { - // Get retrieves the header values for a given key. If the key does not exist, - // nil will be returned. - // NOTE: The memory of underlying data may not be managed by Go GC. So you should - // copy the data if you need to keep it and use it later. - Get(key string) []UnsafeEnvoyBuffer - - // GetOne retrieves a single header value for a given key. - // If there are multiple values for the key, the first one will be returned. - // If the key does not exist, an empty UnsafeEnvoyBuffer will be returned. - // NOTE: The memory of underlying data may not be managed by Go GC. So you should - // copy the data if you need to keep it and use it later. - GetOne(key string) UnsafeEnvoyBuffer - - // GetAll retrieves all header values. You should not mutate the returned slice - // directly. - // NOTE: The memory of underlying data may not be managed by Go GC. So you should - // copy the data if you need to keep it and use it later. - GetAll() [][2]UnsafeEnvoyBuffer - - // Set sets the header value for a given key. - Set(key string, value string) - - // Add adds a single header value for a given key. - Add(key string, value string) - - // Remove removes the header values for a given key. - Remove(key string) -} - -type AttributeID uint32 - -const ( - // request.path - AttributeIDRequestPath AttributeID = iota - // request.url_path - AttributeIDRequestUrlPath - // request.host - AttributeIDRequestHost - // request.scheme - AttributeIDRequestScheme - // request.method - AttributeIDRequestMethod - // request.headers - AttributeIDRequestHeaders - // request.referer - AttributeIDRequestReferer - // request.useragent - AttributeIDRequestUserAgent - // request.time - AttributeIDRequestTime - // request.id - AttributeIDRequestId - // request.protocol - AttributeIDRequestProtocol - // request.query - AttributeIDRequestQuery - // request.duration - AttributeIDRequestDuration - // request.size - AttributeIDRequestSize - // request.total_size - AttributeIDRequestTotalSize - // response.code - AttributeIDResponseCode - // response.code_details - AttributeIDResponseCodeDetails - // response.flags - AttributeIDResponseFlags - // response.grpc_status - AttributeIDResponseGrpcStatus - // response.headers - AttributeIDResponseHeaders - // response.trailers - AttributeIDResponseTrailers - // response.size - AttributeIDResponseSize - // response.total_size - AttributeIDResponseTotalSize - // response.backend_latency - AttributeIDResponseBackendLatency - // source.address - AttributeIDSourceAddress - // source.port - AttributeIDSourcePort - // destination.address - AttributeIDDestinationAddress - // destination.port - AttributeIDDestinationPort - // connection.id - AttributeIDConnectionId - // connection.mtls - AttributeIDConnectionMtls - // connection.requested_server_name - AttributeIDConnectionRequestedServerName - // connection.tls_version - AttributeIDConnectionTlsVersion - // connection.subject_local_certificate - AttributeIDConnectionSubjectLocalCertificate - // connection.subject_peer_certificate - AttributeIDConnectionSubjectPeerCertificate - // connection.dns_san_local_certificate - AttributeIDConnectionDnsSanLocalCertificate - // connection.dns_san_peer_certificate - AttributeIDConnectionDnsSanPeerCertificate - // connection.uri_san_local_certificate - AttributeIDConnectionUriSanLocalCertificate - // connection.uri_san_peer_certificate - AttributeIDConnectionUriSanPeerCertificate - // connection.sha256_peer_certificate_digest - AttributeIDConnectionSha256PeerCertificateDigest - // connection.transport_failure_reason - AttributeIDConnectionTransportFailureReason - // connection.termination_details - AttributeIDConnectionTerminationDetails - // upstream.address - AttributeIDUpstreamAddress - // upstream.port - AttributeIDUpstreamPort - // upstream.tls_version - AttributeIDUpstreamTlsVersion - // upstream.subject_local_certificate - AttributeIDUpstreamSubjectLocalCertificate - // upstream.subject_peer_certificate - AttributeIDUpstreamSubjectPeerCertificate - // upstream.dns_san_local_certificate - AttributeIDUpstreamDnsSanLocalCertificate - // upstream.dns_san_peer_certificate - AttributeIDUpstreamDnsSanPeerCertificate - // upstream.uri_san_local_certificate - AttributeIDUpstreamUriSanLocalCertificate - // upstream.uri_san_peer_certificate - AttributeIDUpstreamUriSanPeerCertificate - // upstream.sha256_peer_certificate_digest - AttributeIDUpstreamSha256PeerCertificateDigest - // upstream.local_address - AttributeIDUpstreamLocalAddress - // upstream.transport_failure_reason - AttributeIDUpstreamTransportFailureReason - // upstream.request_attempt_count - AttributeIDUpstreamRequestAttemptCount - // upstream.cx_pool_ready_duration - AttributeIDUpstreamCxPoolReadyDuration - // upstream.locality - AttributeIDUpstreamLocality - // xds.node - AttributeIDXdsNode - // xds.cluster_name - AttributeIDXdsClusterName - // xds.cluster_metadata - AttributeIDXdsClusterMetadata - // xds.listener_direction - AttributeIDXdsListenerDirection - // xds.listener_metadata - AttributeIDXdsListenerMetadata - // xds.route_name - AttributeIDXdsRouteName - // xds.route_metadata - AttributeIDXdsRouteMetadata - // xds.virtual_host_name - AttributeIDXdsVirtualHostName - // xds.virtual_host_metadata - AttributeIDXdsVirtualHostMetadata - // xds.upstream_host_metadata - AttributeIDXdsUpstreamHostMetadata - // xds.filter_chain_name - AttributeIDXdsFilterChainName - // health_check - AttributeIDHealthCheck -) - -type LogLevel uint32 - -const ( - LogLevelTrace LogLevel = iota - LogLevelDebug - LogLevelInfo - LogLevelWarn - LogLevelError - LogLevelCritical - LogLevelOff -) - -type HttpCalloutInitResult uint32 - -const ( - HttpCalloutInitSuccess HttpCalloutInitResult = iota - HttpCalloutInitMissingRequiredHeaders - HttpCalloutInitClusterNotFound - HttpCalloutInitDuplicateCalloutId - HttpCalloutInitCannotCreateRequest -) - -type HttpCalloutResult uint32 - -const ( - HttpCalloutSuccess HttpCalloutResult = iota - HttpCalloutReset - HttpCalloutExceedResponseBufferLimit -) - -type MetadataSourceType uint32 - -const ( - MetadataSourceTypeDynamic MetadataSourceType = iota - MetadataSourceTypeRoute - MetadataSourceTypeCluster - MetadataSourceTypeHost - MetadataSourceTypeHostLocality -) - -type HttpCalloutCallback interface { - OnHttpCalloutDone(calloutID uint64, result HttpCalloutResult, - headers [][2]UnsafeEnvoyBuffer, body []UnsafeEnvoyBuffer) -} - -type HttpStreamResetReason uint32 - -const ( - HttpStreamResetReasonConnectionFailure = iota - HttpStreamResetReasonConnectionTermination - HttpStreamResetReasonLocalReset - HttpStreamResetReasonLocalRefusedStreamReset - HttpStreamResetReasonOverflow - HttpStreamResetReasonRemoteReset - HttpStreamResetReasonRemoteRefusedStreamReset - HttpStreamResetReasonProtocolError -) - -type HttpStreamCallback interface { - OnHttpStreamHeaders(streamID uint64, headers [][2]UnsafeEnvoyBuffer, endStream bool) - OnHttpStreamData(streamID uint64, body []UnsafeEnvoyBuffer, endStream bool) - OnHttpStreamTrailers(streamID uint64, trailers [][2]UnsafeEnvoyBuffer) - OnHttpStreamComplete(streamID uint64) - OnHttpStreamReset(streamID uint64, reason HttpStreamResetReason) -} - -// Scheduler is the interface that provides scheduling capabilities for asynchronous operations. -// This allow the plugins run tasks in another thread and continue the processing later at the -// thread where the stream plugin is being processed. -type Scheduler interface { - // Schedule schedules a function to be executed asynchronously in the thread where the stream - // plugin is being processed. - // @Param func the function to be executed. - // NOTE: This function may be ignored if the related plugin processing is completed. - Schedule(func()) -} - -type DownstreamWatermarkCallbacks interface { - OnAboveWriteBufferHighWatermark() - OnBelowWriteBufferLowWatermark() -} - -type HttpFilterStreamResetReason uint32 - -const ( - HttpFilterStreamResetReasonLocalReset HttpFilterStreamResetReason = iota - HttpFilterStreamResetReasonLocalRefusedStreamReset -) - -type SocketOptionState uint32 - -const ( - SocketOptionStatePrebind SocketOptionState = iota - SocketOptionStateBound - SocketOptionStateListening -) - -type SocketDirection uint32 - -const ( - SocketDirectionUpstream SocketDirection = iota - SocketDirectionDownstream -) - -type ClusterHostCounts struct { - Total uint64 - Healthy uint64 - Degraded uint64 -} - -type Span interface { - SetTag(key, value string) - SetOperation(operation string) - Log(event string) - SetSampled(sampled bool) - GetBaggage(key string) (UnsafeEnvoyBuffer, bool) - SetBaggage(key, value string) - GetTraceID() (UnsafeEnvoyBuffer, bool) - GetSpanID() (UnsafeEnvoyBuffer, bool) - SpawnChild(operation string) ChildSpan -} - -type ChildSpan interface { - Span - Finish() -} - -// HttpFilterHandle is the interface that provides access to the plugin's context and configuration. -// This should be implemented by the SDK or runtime. -type HttpFilterHandle interface { - // GetMetadataString retrieves the dynamic metadata string value of the stream. - // @Param source the metadata source type. - // @Param metadataNamespace the metadata namespace. - // @Param key the metadata key. - // @Return the metadata value if found, otherwise an empty UnsafeEnvoyBuffer. - GetMetadataString(source MetadataSourceType, metadataNamespace, key string) (UnsafeEnvoyBuffer, bool) - - // GetMetadataNumber retrieves the dynamic metadata number value of the stream. - // @Param source the metadata source type. - // @Param metadataNamespace the metadata namespace. - // @Param key the metadata key. - // @Return the metadata value if found, otherwise nil. - GetMetadataNumber(source MetadataSourceType, metadataNamespace, key string) (float64, bool) - - // GetMetadataBool retrieves the dynamic metadata bool value of the stream. - // @Param source the metadata source type. - // @Param metadataNamespace the metadata namespace. - // @Param key the metadata key. - // @Return the metadata value and true if found, otherwise false. - GetMetadataBool(source MetadataSourceType, metadataNamespace, key string) (bool, bool) - - // SetMetadata sets the dynamic metadata value of the stream. - // @Param metadataNamespace the metadata namespace. - // @Param key the metadata key. - // @Param value the metadata value. Only string/int/float/bool are supported. - SetMetadata(metadataNamespace, key string, value any) - - // GetMetadataKeys retrieves all keys in the given metadata namespace. - // @Param source the metadata source type. - // @Param metadataNamespace the metadata namespace. - // @Return the list of keys in the namespace, or nil if the namespace does not exist. - // NOTE: The memory of underlying data may not be managed by Go GC. So you should - // copy the data if you need to keep it and use it later. - GetMetadataKeys(source MetadataSourceType, metadataNamespace string) []UnsafeEnvoyBuffer - - // GetMetadataNamespaces retrieves all namespace names in the metadata. - // @Param source the metadata source type. - // @Return the list of namespace names, or nil if no namespaces exist. - // NOTE: The memory of underlying data may not be managed by Go GC. So you should - // copy the data if you need to keep it and use it later. - GetMetadataNamespaces(source MetadataSourceType) []UnsafeEnvoyBuffer - - // AddMetadataListNumber appends a number value to the dynamic metadata list stored under the - // given namespace and key. If the key does not exist, a new list is created. Returns false if - // the key exists but is not a list, or if the metadata is not accessible. - AddMetadataListNumber(metadataNamespace, key string, value float64) bool - - // AddMetadataListString appends a string value to the dynamic metadata list stored under the - // given namespace and key. If the key does not exist, a new list is created. Returns false if - // the key exists but is not a list, or if the metadata is not accessible. - AddMetadataListString(metadataNamespace, key string, value string) bool - - // AddMetadataListBool appends a bool value to the dynamic metadata list stored under the - // given namespace and key. If the key does not exist, a new list is created. Returns false if - // the key exists but is not a list, or if the metadata is not accessible. - AddMetadataListBool(metadataNamespace, key string, value bool) bool - - // GetMetadataListSize returns the number of elements in the metadata list stored under the - // given namespace and key. Returns (0, false) if the metadata is not accessible, the namespace - // or key does not exist, or the value is not a list. - GetMetadataListSize(source MetadataSourceType, metadataNamespace, key string) (int, bool) - - // GetMetadataListNumber returns the number element at the given index in the metadata list - // stored under the given namespace and key. Returns (0, false) if the metadata is not - // accessible, the namespace or key does not exist, the value is not a list, the index is out - // of range, or the element is not a number. - GetMetadataListNumber(source MetadataSourceType, metadataNamespace, key string, index int) (float64, bool) - - // GetMetadataListString returns the string element at the given index in the metadata list - // stored under the given namespace and key. Returns an empty buffer and false if the metadata is - // not accessible, the namespace or key does not exist, the value is not a list, the index is - // out of range, or the element is not a string. - // NOTE: The memory of underlying data may not be managed by Go GC. So you should - // copy the data if you need to keep it and use it later. - GetMetadataListString(source MetadataSourceType, metadataNamespace, key string, index int) (UnsafeEnvoyBuffer, bool) - - // GetMetadataListBool returns the bool element at the given index in the metadata list stored - // under the given namespace and key. Returns (false, false) if the metadata is not accessible, - // the namespace or key does not exist, the value is not a list, the index is out of range, or - // the element is not a bool. - GetMetadataListBool(source MetadataSourceType, metadataNamespace, key string, index int) (bool, bool) - - // GetFilterState retrieves the serialized filter state value of the stream. - // @Param key the filter state key. - // @Return the filter state value if found, otherwise an empty UnsafeEnvoyBuffer. - // NOTE: The memory of underlying data may not be managed by Go GC. So you should - // copy the data if you need to keep it and use it later. - GetFilterState(key string) (UnsafeEnvoyBuffer, bool) - - // SetFilterState sets the serialized filter state value of the stream. - // @Param key the filter state key. - // @Param value the filter state value. - SetFilterState(key string, value []byte) - - // SetFilterStateTyped sets a typed filter state object from serialized bytes using the registered - // Envoy ObjectFactory for the given key. - // @Param key the filter state key. - // @Param value the serialized bytes used to construct the typed object. - // @Return true if the value was stored successfully, otherwise false. - SetFilterStateTyped(key string, value []byte) bool - - // GetAttributeString retrieves the string attribute value of the stream. - // @Param attributeID the attribute ID. - // @Return the attribute value if found, otherwise an empty UnsafeEnvoyBuffer. - // NOTE: The memory of underlying data may not be managed by Go GC. So you should - // copy the data if you need to keep it and use it later. - GetAttributeString(attributeID AttributeID) (UnsafeEnvoyBuffer, bool) - - // GetAttributeNumber retrieves the float attribute value of the stream. - // @Param key the attribute key. - // @Return the attribute value if found, otherwise nil. - GetAttributeNumber(attributeID AttributeID) (float64, bool) - - // GetAttributeBool retrieves the bool attribute value of the stream. - // @Param attributeID the attribute ID. - // @Return the attribute value and true if found, otherwise false. - GetAttributeBool(attributeID AttributeID) (bool, bool) - - // GetFilterStateTyped retrieves the serialized value of a typed filter state object. - // @Param key the filter state key. - // @Return the serialized value if found, otherwise an empty UnsafeEnvoyBuffer. - GetFilterStateTyped(key string) (UnsafeEnvoyBuffer, bool) - - // GetData retrieves internal data stored for cross-phase communication. - // This data is not included in DynamicMetadata responses. - // @Param key the data key. - // @Return the data value if found, otherwise nil. - GetData(key string) any - - // SetData sets internal data for cross-phase communication. - // This data is not included in DynamicMetadata responses. - // @Param key the data key. - // @Param value the data value. - SetData(key string, value any) - - // SendLocalResponse sends a local reply to the client and terminates the stream. - // @Param status the HTTP status code. - // @Param headers the response headers. - // @Param body the response body. - // @Param detail a short description to the response for debugging purposes. - SendLocalResponse(status uint32, headers [][2]string, body []byte, detail string) - - // SendResponseHeaders sends response headers to the client. This is used for - // streaming local replies. - // - // @Param headers the response headers. - // @Param endOfStream whether this is the end of the stream. - SendResponseHeaders(headers [][2]string, endOfStream bool) - - // SendResponseData sends response body data to the client. This is used for - // streaming local replies. - // - // @Param body the response body data. - // @Param endOfStream whether this is the end of the stream. - SendResponseData(body []byte, endOfStream bool) - - // SendResponseTrailers sends response trailers to the client. This is used for - // streaming local replies. - // - // @Param trailers the response trailers. - SendResponseTrailers(trailers [][2]string) - - // AddCustomFlag adds a custom flag to the stream. This flag should be very short - // string to indicate some custom state or information of the stream. - // @Param flag the custom flag to add. - AddCustomFlag(flag string) - - // ContinueRequest continues the request stream processing. - // NOTE: This function should only be called when the plugin chains are hung up because - // of asynchronous operations. - ContinueRequest() - - // ContinueResponse continues the response stream processing. - // NOTE: This function should only be called when the plugin chains are hung up because - // of asynchronous operations. - ContinueResponse() - - // ClearRouteCache clears the cached route for the stream. - ClearRouteCache() - - // RefreshRouteCluster clears only the cluster selection for the current route without - // clearing the entire route cache. - // This is a subset of ClearRouteCache. Use this when a filter modifies headers that affect - // cluster selection but not the route itself. - RefreshRouteCluster() - - // GetWorkerIndex returns the worker index assigned to the current filter instance. - GetWorkerIndex() uint32 - - // SetSocketOptionInt sets an integer socket option on the upstream or downstream connection. - SetSocketOptionInt(level, name int64, state SocketOptionState, direction SocketDirection, value int64) bool - - // SetSocketOptionBytes sets a bytes socket option on the upstream or downstream connection. - SetSocketOptionBytes(level, name int64, state SocketOptionState, direction SocketDirection, value []byte) bool - - // GetSocketOptionInt retrieves an integer socket option from the upstream or downstream connection. - GetSocketOptionInt(level, name int64, state SocketOptionState, direction SocketDirection) (int64, bool) - - // GetSocketOptionBytes retrieves a bytes socket option from the upstream or downstream connection. - GetSocketOptionBytes(level, name int64, state SocketOptionState, direction SocketDirection) (UnsafeEnvoyBuffer, bool) - - // GetBufferLimit returns the current body buffering limit in bytes. - GetBufferLimit() uint64 - - // SetBufferLimit sets the current body buffering limit in bytes. - SetBufferLimit(limit uint64) - - // GetActiveSpan retrieves the active tracing span for the stream. - GetActiveSpan() Span - - // GetClusterName retrieves the selected upstream cluster name for the stream. - GetClusterName() (UnsafeEnvoyBuffer, bool) - - // GetClusterHostCounts retrieves the total, healthy, and degraded host counts for the selected - // cluster at the given priority. - GetClusterHostCounts(priority uint32) (ClusterHostCounts, bool) - - // SetUpstreamOverrideHost sets an override host for the selected upstream cluster. - SetUpstreamOverrideHost(host string, strict bool) bool - - // ResetStream resets the downstream HTTP stream with the given reason and details string. - ResetStream(reason HttpFilterStreamResetReason, details string) - - // SendGoAwayAndClose sends a GOAWAY frame to the downstream and closes the connection. - SendGoAwayAndClose(graceful bool) - - // RecreateStream recreates the HTTP stream, optionally with replacement headers. - RecreateStream(headers [][2]string) bool - - // RequestHeaders retrieves the request headers. - // @Return the request headers. - RequestHeaders() HeaderMap - - // BufferedRequestBody retrieves the buffered request body in the chain. - // NOTE: Different with the headers and trailers, because of the streaming processing, - // the request body is not always fully buffered. So this function only retrieves the - // currently buffered body in the chain. And the latest newly received body chunk is passed - // as the parameter to OnRequestBody. Only when endOfStream is true or OnRequestTrailers is - // called, the full request body is received. - // @Return the buffered request body. - BufferedRequestBody() BodyBuffer - - // ReceivedRequestBody retrieves the latest received request body chunk in the OnRequestBody callback. - // NOTE: This is only valid in the OnRequestBody callback, and it retrieves the latest received - // body chunk that triggers the callback. For other callbacks or outside of the callbacks, you - // should use BufferedRequestBody to get the currently buffered body in the chain. - ReceivedRequestBody() BodyBuffer - - // RequestTrailers retrieves the request trailers. - // @Return the request trailers. - RequestTrailers() HeaderMap - - // ResponseHeaders retrieves the response headers. - // @Return the response headers. - ResponseHeaders() HeaderMap - - // BufferedResponseBody retrieves the buffered response body in the chain. - // NOTE: Different with the headers and trailers, because of the streaming processing, - // the request body is not always fully buffered. So this function only retrieves the - // currently buffered body in the chain. And the latest newly received body chunk is passed - // as the parameter to OnResponseBody. Only when endOfStream is true or OnResponseTrailers is - // called, the full request body is received. - // @Return the buffered response body. - BufferedResponseBody() BodyBuffer - - // ReceivedResponseBody retrieves the latest received response body chunk in the OnResponseBody callback. - // NOTE: This is only valid in the OnResponseBody callback, and it retrieves the latest received - // body chunk that triggers the callback. For other callbacks or outside of the callbacks, you - // should use BufferedResponseBody to get the currently buffered body in the chain. - ReceivedResponseBody() BodyBuffer - - // ReceivedBufferedRequestBody returns true if the latest received request body is the - // previously buffered request body. This is true when a previous filter in the chain stopped - // and buffered the request body, then resumed, and this filter is now receiving that buffered - // body. - // NOTE: This is only meaningful inside the OnRequestBody callback. - ReceivedBufferedRequestBody() bool - - // ReceivedBufferedResponseBody returns true if the latest received response body is the - // previously buffered response body. This is true when a previous filter in the chain stopped - // and buffered the response body, then resumed, and this filter is now receiving that buffered - // body. - // NOTE: This is only meaningful inside the OnResponseBody callback. - ReceivedBufferedResponseBody() bool - - // ResponseTrailers retrieves the response trailers. - // @Return the response trailers. - ResponseTrailers() HeaderMap - - // GetMostSpecificConfig retrieves the most specific route configuration for the stream. - GetMostSpecificConfig() any - - // GetScheduler retrieves the scheduler related to this stream plugin for asynchronous - // operations. - // - // NOTE: This MUST only be called during OnRequest* or OnResponse* callbacks. But then the - // returned Scheduler can be used later even outside of the callbacks and even at other - // threads. - GetScheduler() Scheduler - - // Log will log the given message via the host environment's logging mechanism. - Log(level LogLevel, format string, args ...any) - - // HttpCallout performs an HTTP call to an external service. The call is asynchronous, and the - // response will be delivered via the provided callback. - // @Param cluster the cluster (target) name to which the HTTP call will be made. - // @Param headers the HTTP headers to be sent with the request. - // @Param body the HTTP body to be sent with the request. - // @Param timeoutMs the timeout in milliseconds for the HTTP call. - // @Param callback the callback function to be invoked when the response is received or an - // error occurs. - // The callback function receives the response headers, body, and an error if any occurred. - // - // @Return the result of the HTTP callout initialization and the callout ID. Non-success results - // indicate that the callout failed to start. - // - // NOTE: This method should only be called during OnRequest* or OnResponse* callbacks or - // scheduled functions via the Scheduler. By this way we can ensure this is only be called - // in the thread where the stream plugin is being processed. - HttpCallout(cluster string, headers [][2]string, body []byte, timeoutMs uint64, - cb HttpCalloutCallback) (HttpCalloutInitResult, uint64) - - // StartHttpStream starts a new HTTP stream to an external service. The stream is asynchronous, - // and the response will be delivered via the provided callback. - // @Param cluster the cluster (target) name to which the HTTP stream will be made. - // @Param headers the initial HTTP headers to be sent with the request. - // @Param body the initial HTTP body to be sent with the request. - // @Param endOfStream whether this is the end of the stream. - // @Param timeoutMs the timeout in milliseconds for the HTTP stream. - // @Param callback the callback interface to handle the stream events. - // - // @Return the result of the HTTP stream initialization and the stream ID. Non-success results - // indicate that the stream failed to start. - // - // NOTE: This method should only be called during OnRequest* or OnResponse* callbacks or - // scheduled functions via the Scheduler. By this way we can ensure this is only be called - // in the thread where the stream plugin is being processed. - StartHttpStream(cluster string, headers [][2]string, body []byte, endOfStream bool, timeoutMs uint64, - cb HttpStreamCallback) (HttpCalloutInitResult, uint64) - - // SendHttpStreamData sends data on an existing HTTP stream. - // @Param streamID the ID of the HTTP stream. - // @Param body the HTTP body to be sent with the request. - // @Param endOfStream whether this is the end of the stream. - // - // @Return whether the data was successfully sent. - // - // NOTE: This method should only be called during OnRequest* or OnResponse* callbacks or - // scheduled functions via the Scheduler. By this way we can ensure this is only be called - // in the thread where the stream plugin is being processed. - SendHttpStreamData(streamID uint64, body []byte, endOfStream bool) bool - - // SendHttpStreamTrailers sends trailers on an existing HTTP stream. - // @Param streamID the ID of the HTTP stream. - // @Param trailers the HTTP trailers to be sent with the request. - // - // @Return whether the trailers were successfully sent. - // - // NOTE: This method should only be called during OnRequest* or OnResponse* callbacks or - // scheduled functions via the Scheduler. By this way we can ensure this is only be called - // in the thread where the stream plugin is being processed. - SendHttpStreamTrailers(streamID uint64, trailers [][2]string) bool - - // ResetHttpStream resets an existing HTTP stream. - // @Param streamID the ID of the HTTP stream. - // - // NOTE: This method should only be called during OnRequest* or OnResponse* callbacks or - // scheduled functions via the Scheduler. By this way we can ensure this is only be called - // in the thread where the stream plugin is being processed. - ResetHttpStream(streamID uint64) - - // SetDownstreamWatermarkCallbacks sets the downstream watermark callbacks for the stream. - // @Param callbacks the downstream watermark callbacks. - SetDownstreamWatermarkCallbacks(callbacks DownstreamWatermarkCallbacks) - - // ClearDownstreamWatermarkCallbacks unsets the downstream watermark callbacks for the stream. - ClearDownstreamWatermarkCallbacks() - - // RecordValue records the given value to the histogram metric. - // @Param id the histogram metric id. - // @Param value the value to be recorded. - // @Param tagsValues the optional tag values associated with the metric. The order and size - // of the tag values must match the tag keys defined when the metric was created. - RecordHistogramValue(id MetricID, value uint64, tagsValues ...string) MetricsResult - - // SetValue sets the given value to the gauge metric. - // @Param id the gauge metric id. - // @Param value the value to be set. - // @Param tagsValues the optional tag values associated with the metric. The order and size - // of the tag values must match the tag keys defined when the metric was created. - SetGaugeValue(id MetricID, value uint64, tagsValues ...string) MetricsResult - - // IncrementGaugeValue adds the given value to the gauge metric. - // @Param id the gauge metric id. - // @Param value the value to be added. - // @Param tagsValues the optional tag values associated with the metric. The order and size - // of the tag values must match the tag keys defined when the metric was created. - IncrementGaugeValue(id MetricID, value uint64, tagsValues ...string) MetricsResult - - // DecrementGaugeValue subtracts the given value from the gauge metric. - // @Param id the gauge metric id. - // @Param value the value to be subtracted. - // @Param tagsValues the optional tag values associated with the metric. The order and size - // of the tag values must match the tag keys defined when the metric was created. - DecrementGaugeValue(id MetricID, value uint64, tagsValues ...string) MetricsResult - - // IncrementCounterValue adds the given value to the counter metric. - // @Param id the counter metric id. - // @Param value the value to be added. - // @Param tagsValues the optional tag values associated with the metric. The order and size - // of the tag values must match the tag keys defined when the metric was created. - IncrementCounterValue(id MetricID, value uint64, tagsValues ...string) MetricsResult -} - -type MetricID uint64 -type MetricsResult uint32 - -const ( - MetricsSuccess MetricsResult = iota - MetricsNotFound - MetricsInvalidTags - MetricsFrozen -) - -type HttpFilterConfigHandle interface { - // Log will log the given message via the host environment's logging mechanism. - Log(level LogLevel, format string, args ...any) - - // DefineHistogram creates a histogram metric with the given name, and tag keys. - // @Param name the name of the metric. - // @Param tagKeys the optional tag keys for the metric. - // @Return the histogram metric id. This metric can never be used after the plugin - // config is unloaded. - DefineHistogram(name string, tagKeys ...string) (MetricID, MetricsResult) - - // DefineGauge creates a gauge metric with the given name, description, and tag keys. - // @Param name the name of the metric. - // @Param tagKeys the optional tag keys for the metric. - // @Return the gauge metric id. This metric can never be used after the plugin - // config is unloaded. - DefineGauge(name string, tagKeys ...string) (MetricID, MetricsResult) - - // DefineCounter creates a counter metric with the given name, description, and tag keys. - // @Param name the name of the metric. - // @Param tagKeys the optional tag keys for the metric. - // @Return the counter metric id. This metric can never be used after the plugin - // config is unloaded. - DefineCounter(name string, tagKeys ...string) (MetricID, MetricsResult) - - // HttpCallout performs an HTTP call to an external service from the config context. - // The call is asynchronous, and the response will be delivered via the provided callback. - // This is similar to HttpFilterHandle.HttpCallout but runs on the main thread rather than - // the worker thread. - // @Param cluster the cluster (target) name to which the HTTP call will be made. - // @Param headers the HTTP headers to be sent with the request. - // @Param body the HTTP body to be sent with the request. - // @Param timeoutMs the timeout in milliseconds for the HTTP call. - // @Param callback the callback function to be invoked when the response is received. - // @Return the result of the HTTP callout initialization and the callout ID. - HttpCallout(cluster string, headers [][2]string, body []byte, timeoutMs uint64, - cb HttpCalloutCallback) (HttpCalloutInitResult, uint64) - - // StartHttpStream starts a new HTTP stream to an external service from the config context. - // The stream is asynchronous, and the response will be delivered via the provided callback. - // This is similar to HttpFilterHandle.StartHttpStream but runs on the main thread. - // @Param cluster the cluster (target) name. - // @Param headers the initial HTTP headers. - // @Param body the initial HTTP body. - // @Param endOfStream whether this is the end of the stream. - // @Param timeoutMs the timeout in milliseconds. - // @Param callback the callback interface to handle the stream events. - // @Return the result of the HTTP stream initialization and the stream ID. - StartHttpStream(cluster string, headers [][2]string, body []byte, endOfStream bool, - timeoutMs uint64, cb HttpStreamCallback) (HttpCalloutInitResult, uint64) - - // SendHttpStreamData sends data on an existing HTTP stream started via StartHttpStream. - // @Param streamID the ID of the HTTP stream. - // @Param body the HTTP body to be sent. - // @Param endOfStream whether this is the end of the stream. - // @Return whether the data was successfully sent. - SendHttpStreamData(streamID uint64, body []byte, endOfStream bool) bool - - // SendHttpStreamTrailers sends trailers on an existing HTTP stream started via StartHttpStream. - // @Param streamID the ID of the HTTP stream. - // @Param trailers the HTTP trailers to be sent. - // @Return whether the trailers were successfully sent. - SendHttpStreamTrailers(streamID uint64, trailers [][2]string) bool - - // ResetHttpStream resets an existing HTTP stream started via StartHttpStream. - // @Param streamID the ID of the HTTP stream. - ResetHttpStream(streamID uint64) - - // GetScheduler retrieves a scheduler for deferred task execution in the config context. - // This should be called only during the plugin configuration phase, and the returned - // Scheduler can be used later even outside of the callbacks and at other threads. - GetScheduler() Scheduler -} diff --git a/source/extensions/dynamic_modules/sdk/go/shared/fake/fake_stream_base.go b/source/extensions/dynamic_modules/sdk/go/shared/fake/fake_http.go similarity index 95% rename from source/extensions/dynamic_modules/sdk/go/shared/fake/fake_stream_base.go rename to source/extensions/dynamic_modules/sdk/go/shared/fake/fake_http.go index a6fee1eacd60f..f642d361ee5ac 100644 --- a/source/extensions/dynamic_modules/sdk/go/shared/fake/fake_stream_base.go +++ b/source/extensions/dynamic_modules/sdk/go/shared/fake/fake_http.go @@ -7,6 +7,11 @@ import ( "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/shared" ) +var ( + _ shared.HeaderMap = (*FakeHeaderMap)(nil) + _ shared.BodyBuffer = (*FakeBodyBuffer)(nil) +) + type FakeHeaderMap struct { Headers map[string][]string } diff --git a/source/extensions/dynamic_modules/sdk/go/shared/fake/fake_stream_base_test.go b/source/extensions/dynamic_modules/sdk/go/shared/fake/fake_http_test.go similarity index 100% rename from source/extensions/dynamic_modules/sdk/go/shared/fake/fake_stream_base_test.go rename to source/extensions/dynamic_modules/sdk/go/shared/fake/fake_http_test.go diff --git a/source/extensions/dynamic_modules/sdk/go/shared/http.go b/source/extensions/dynamic_modules/sdk/go/shared/http.go new file mode 100644 index 0000000000000..1f063d82453b7 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/go/shared/http.go @@ -0,0 +1,561 @@ +//go:generate mockgen -source=http.go -destination=mocks/mock_http.go -package=mocks +package shared + +// HTTP filter SDK surface for dynamic modules — handle, buffer, header, span, and watermark +// types used by the user-facing interfaces in http_api.go. +// +// Cross-surface primitives (UnsafeEnvoyBuffer, LogLevel, MetricID, AttributeID, Scheduler, +// HttpCalloutInitResult/Result/Callback, HttpStreamCallback/ResetReason, SocketOption*, +// ClusterHostCounts, HttpHeaderType) live in types.go. + +// BodyBuffer is an interface that provides access to the request and response body. +// This should be implemented by the SDK or runtime. +type BodyBuffer interface { + // GetChunks retrieves the body content as a list of UnsafeEnvoyBuffer chunks. + // NOTE: The memory of underlying data may not be managed by Go GC. So you should + // copy the data if you need to keep it and use it later. + GetChunks() []UnsafeEnvoyBuffer + + // GetSize retrieves the total size of the body buffer. + GetSize() uint64 + + // Drain removes the specified number of bytes from the beginning of the body buffer. + Drain(numBytes uint64) + + // Append adds the specified bytes to the end of the body buffer. + Append(data []byte) +} + +// HeaderMap is an interface that provides access to the request and response headers. +// This should be implemented by the SDK or runtime. +type HeaderMap interface { + // Get retrieves the header values for a given key. If the key does not exist, + // nil will be returned. + // NOTE: The memory of underlying data may not be managed by Go GC. So you should + // copy the data if you need to keep it and use it later. + Get(key string) []UnsafeEnvoyBuffer + + // GetOne retrieves a single header value for a given key. + // If there are multiple values for the key, the first one will be returned. + // If the key does not exist, an empty UnsafeEnvoyBuffer will be returned. + // NOTE: The memory of underlying data may not be managed by Go GC. So you should + // copy the data if you need to keep it and use it later. + GetOne(key string) UnsafeEnvoyBuffer + + // GetAll retrieves all header values. You should not mutate the returned slice + // directly. + // NOTE: The memory of underlying data may not be managed by Go GC. So you should + // copy the data if you need to keep it and use it later. + GetAll() [][2]UnsafeEnvoyBuffer + + // Set sets the header value for a given key. + Set(key string, value string) + + // Add adds a single header value for a given key. + Add(key string, value string) + + // Remove removes the header values for a given key. + Remove(key string) +} + +// MetadataSourceType identifies which metadata source to read from. Corresponds to +// envoy_dynamic_module_type_metadata_source. +type MetadataSourceType uint32 + +const ( + MetadataSourceTypeDynamic MetadataSourceType = iota + MetadataSourceTypeRoute + MetadataSourceTypeCluster + MetadataSourceTypeHost + MetadataSourceTypeHostLocality +) + +// DownstreamWatermarkCallbacks is the callback interface invoked when the downstream connection +// crosses the configured write-buffer high/low watermark. +type DownstreamWatermarkCallbacks interface { + OnAboveWriteBufferHighWatermark() + OnBelowWriteBufferLowWatermark() +} + +// HttpFilterStreamResetReason is the reason for resetting the main HTTP stream via +// HttpFilterHandle.ResetStream. This corresponds to envoy_dynamic_module_type_http_filter_stream_reset_reason +// in the dynamic module ABI. +type HttpFilterStreamResetReason uint32 + +const ( + // HttpFilterStreamResetReasonLocalReset indicates a local codec level reset was sent on the stream. + HttpFilterStreamResetReasonLocalReset HttpFilterStreamResetReason = iota + // HttpFilterStreamResetReasonLocalRefusedStreamReset indicates a local codec level refused stream + // reset was sent on the stream (allowing for retry). + HttpFilterStreamResetReasonLocalRefusedStreamReset +) + +// Span is a tracing span associated with the current HTTP stream. It is owned by Envoy and is +// valid for the lifetime of the HTTP stream. Modules MUST NOT call Finish on the active span - +// it is managed by Envoy. Use SpawnChild to create child spans whose lifetime the module owns. +type Span interface { + // SetTag sets a key/value tag on the span. + SetTag(key, value string) + + // SetOperation sets the operation name on the span. + SetOperation(operation string) + + // Log records an event on the span with the current timestamp. + Log(event string) + + // SetSampled overrides the sampling decision for the span. If sampled is false, this span and + // any subsequent child spans will not be reported to the tracing system. + SetSampled(sampled bool) + + // GetBaggage retrieves a baggage value from the span. Returns the value and true if the key + // was found, otherwise an empty buffer and false. + // NOTE: The memory of the underlying data may not be managed by Go GC. Copy the data if you + // need to keep it past the current callback. + GetBaggage(key string) (UnsafeEnvoyBuffer, bool) + + // SetBaggage sets a baggage value on the span. All subsequent child spans will have access to + // this baggage. + SetBaggage(key, value string) + + // GetTraceID retrieves the trace ID from the span. Returns the value and true if available, + // otherwise an empty buffer and false. + // NOTE: The memory of the underlying data may not be managed by Go GC. Copy the data if you + // need to keep it past the current callback. + GetTraceID() (UnsafeEnvoyBuffer, bool) + + // GetSpanID retrieves the span ID from the span. Returns the value and true if available, + // otherwise an empty buffer and false. + // NOTE: The memory of the underlying data may not be managed by Go GC. Copy the data if you + // need to keep it past the current callback. + GetSpanID() (UnsafeEnvoyBuffer, bool) + + // SpawnChild creates a child span with the given operation name. The returned ChildSpan must + // be finished by calling its Finish method when the module is done with it. Returns nil if + // the child span could not be created. + SpawnChild(operationName string) ChildSpan +} + +// ChildSpan is a tracing span owned by the module. It must be finished by calling Finish when +// the module is done with it. +type ChildSpan interface { + Span + + // Finish finishes and releases this span. After calling this method, the span is no longer + // valid and must not be used. Calling Finish more than once is a no-op. + Finish() +} + +// HttpFilterHandle is the interface that provides access to the plugin's context and configuration. +// This should be implemented by the SDK or runtime. +type HttpFilterHandle interface { + // GetMetadataString retrieves the dynamic metadata string value of the stream. + // Returns metadata value if found, otherwise an empty UnsafeEnvoyBuffer. + GetMetadataString(source MetadataSourceType, metadataNamespace, key string) (UnsafeEnvoyBuffer, bool) + + // GetMetadataNumber retrieves the dynamic metadata number value of the stream. + // Returns metadata value if found, otherwise nil. + GetMetadataNumber(source MetadataSourceType, metadataNamespace, key string) (float64, bool) + + // GetMetadataBool retrieves the dynamic metadata bool value of the stream. + // Returns metadata value and true if found, otherwise false. + GetMetadataBool(source MetadataSourceType, metadataNamespace, key string) (bool, bool) + + // SetMetadata sets the dynamic metadata value of the stream. + SetMetadata(metadataNamespace, key string, value any) + + // GetMetadataKeys retrieves all keys in the given metadata namespace. + // Returns list of keys in the namespace, or nil if the namespace does not exist. + // NOTE: The memory of underlying data may not be managed by Go GC. So you should + // copy the data if you need to keep it and use it later. + GetMetadataKeys(source MetadataSourceType, metadataNamespace string) []UnsafeEnvoyBuffer + + // GetMetadataNamespaces retrieves all namespace names in the metadata. + // Returns list of namespace names, or nil if no namespaces exist. + // NOTE: The memory of underlying data may not be managed by Go GC. So you should + // copy the data if you need to keep it and use it later. + GetMetadataNamespaces(source MetadataSourceType) []UnsafeEnvoyBuffer + + // AddMetadataListNumber appends a number value to the dynamic metadata list stored under the + // given namespace and key. If the key does not exist, a new list is created. Returns false if + // the key exists but is not a list, or if the metadata is not accessible. + AddMetadataListNumber(metadataNamespace, key string, value float64) bool + + // AddMetadataListString appends a string value to the dynamic metadata list stored under the + // given namespace and key. If the key does not exist, a new list is created. Returns false if + // the key exists but is not a list, or if the metadata is not accessible. + AddMetadataListString(metadataNamespace, key string, value string) bool + + // AddMetadataListBool appends a bool value to the dynamic metadata list stored under the + // given namespace and key. If the key does not exist, a new list is created. Returns false if + // the key exists but is not a list, or if the metadata is not accessible. + AddMetadataListBool(metadataNamespace, key string, value bool) bool + + // GetMetadataListSize returns the number of elements in the metadata list stored under the + // given namespace and key. Returns (0, false) if the metadata is not accessible, the namespace + // or key does not exist, or the value is not a list. + GetMetadataListSize(source MetadataSourceType, metadataNamespace, key string) (int, bool) + + // GetMetadataListNumber returns the number element at the given index in the metadata list + // stored under the given namespace and key. Returns (0, false) if the metadata is not + // accessible, the namespace or key does not exist, the value is not a list, the index is out + // of range, or the element is not a number. + GetMetadataListNumber(source MetadataSourceType, metadataNamespace, key string, index int) (float64, bool) + + // GetMetadataListString returns the string element at the given index in the metadata list + // stored under the given namespace and key. Returns an empty buffer and false if the metadata is + // not accessible, the namespace or key does not exist, the value is not a list, the index is + // out of range, or the element is not a string. + // NOTE: The memory of underlying data may not be managed by Go GC. So you should + // copy the data if you need to keep it and use it later. + GetMetadataListString(source MetadataSourceType, metadataNamespace, key string, index int) (UnsafeEnvoyBuffer, bool) + + // GetMetadataListBool returns the bool element at the given index in the metadata list stored + // under the given namespace and key. Returns (false, false) if the metadata is not accessible, + // the namespace or key does not exist, the value is not a list, the index is out of range, or + // the element is not a bool. + GetMetadataListBool(source MetadataSourceType, metadataNamespace, key string, index int) (bool, bool) + + // GetFilterState retrieves the serialized filter state value of the stream. + // Returns filter state value if found, otherwise an empty UnsafeEnvoyBuffer. + // NOTE: The memory of underlying data may not be managed by Go GC. So you should + // copy the data if you need to keep it and use it later. + GetFilterState(key string) (UnsafeEnvoyBuffer, bool) + + // SetFilterState sets the serialized filter state value of the stream. + SetFilterState(key string, value []byte) + + // SetFilterStateTyped sets the typed filter state value stored under the given key. The key + // MUST match a registered ObjectFactory; the bytes are passed to createFromBytes on that + // factory. This is the form required for interop with built-in Envoy filters that read filter + // state as typed objects (e.g., tcp_proxy reading PerConnectionCluster). + // Returns true on success, or false if no factory is registered for the key, the factory + // failed to create the object, or the key is read-only. + SetFilterStateTyped(key string, value []byte) bool + + // GetAttributeString retrieves the string attribute value of the stream. + // Returns attribute value if found, otherwise an empty UnsafeEnvoyBuffer. + // NOTE: The memory of underlying data may not be managed by Go GC. So you should + // copy the data if you need to keep it and use it later. + GetAttributeString(attributeID AttributeID) (UnsafeEnvoyBuffer, bool) + + // GetAttributeNumber retrieves the float attribute value of the stream. + // Returns attribute value if found, otherwise (0, false). + GetAttributeNumber(attributeID AttributeID) (float64, bool) + + // GetAttributeBool retrieves the bool attribute value of the stream. + // Returns attribute value and true if found, otherwise false. + GetAttributeBool(attributeID AttributeID) (bool, bool) + + // GetFilterStateTyped retrieves the serialized bytes of a typed filter state object stored + // under the given key. Unlike GetFilterState, this calls serializeAsString on the registered + // typed object, so it works for any filter state object type (not just StringAccessor). + // Returns serialized value if found, otherwise an empty UnsafeEnvoyBuffer and false. + // NOTE: The memory of the underlying data may not be managed by Go GC. Copy the data if you + // need to keep it past the current callback. + GetFilterStateTyped(key string) (UnsafeEnvoyBuffer, bool) + + // GetData retrieves internal data stored for cross-phase communication. + // This data is not included in DynamicMetadata responses. + // Returns data value if found, otherwise nil. + GetData(key string) any + + // SetData sets internal data for cross-phase communication. + // This data is not included in DynamicMetadata responses. + SetData(key string, value any) + + // SendLocalResponse sends a local reply to the client and terminates the stream. + SendLocalResponse(status uint32, headers [][2]string, body []byte, detail string) + + // SendResponseHeaders sends response headers to the client. This is used for + // streaming local replies. + SendResponseHeaders(headers [][2]string, endOfStream bool) + + // SendResponseData sends response body data to the client. This is used for + // streaming local replies. + SendResponseData(body []byte, endOfStream bool) + + // SendResponseTrailers sends response trailers to the client. This is used for + // streaming local replies. + SendResponseTrailers(trailers [][2]string) + + // AddCustomFlag adds a custom flag to the stream. This flag should be very short + // string to indicate some custom state or information of the stream. + AddCustomFlag(flag string) + + // ContinueRequest continues the request stream processing. + // NOTE: This function should only be called when the plugin chains are hung up because + // of asynchronous operations. + ContinueRequest() + + // ContinueResponse continues the response stream processing. + // NOTE: This function should only be called when the plugin chains are hung up because + // of asynchronous operations. + ContinueResponse() + + // ClearRouteCache clears the cached route for the stream. + ClearRouteCache() + + // RefreshRouteCluster clears only the cluster selection for the current route without + // clearing the entire route cache. + // This is a subset of ClearRouteCache. Use this when a filter modifies headers that affect + // cluster selection but not the route itself. + RefreshRouteCluster() + + // GetWorkerIndex returns the worker thread index assigned to the current HTTP filter. + // This can be used by the module to manage worker-specific resources. + GetWorkerIndex() uint32 + + // SetSocketOptionInt sets an integer-valued socket option on the upstream or downstream + // connection associated with the stream. Returns true on success. + SetSocketOptionInt(level, name int64, state SocketOptionState, direction SocketDirection, value int64) bool + + // SetSocketOptionBytes sets a bytes-valued socket option on the upstream or downstream + // connection associated with the stream. Returns true on success. + SetSocketOptionBytes(level, name int64, state SocketOptionState, direction SocketDirection, value []byte) bool + + // GetSocketOptionInt retrieves the integer value of a socket option. + // Returns value and true if found, otherwise 0 and false. + GetSocketOptionInt(level, name int64, state SocketOptionState, direction SocketDirection) (int64, bool) + + // GetSocketOptionBytes retrieves the bytes value of a socket option. The buffer is owned by + // Envoy and remains valid until the filter is destroyed. + // Returns value and true if found, otherwise an empty UnsafeEnvoyBuffer and false. + // NOTE: The memory of the underlying data may not be managed by Go GC. Copy the data if you + // need to keep it past the current callback. + GetSocketOptionBytes(level, name int64, state SocketOptionState, direction SocketDirection) (UnsafeEnvoyBuffer, bool) + + // GetBufferLimit returns the current per-stream body buffer limit in bytes. A limit of 0 + // indicates no limit is applied. + GetBufferLimit() uint64 + + // SetBufferLimit sets the per-stream body buffer limit. It is recommended (but not required) + // that filters only INCREASE the limit, to avoid conflicting with the buffer requirements of + // other filters in the chain. + SetBufferLimit(limit uint64) + + // GetActiveSpan returns the active tracing span for the stream, or nil if tracing is disabled + // or no span is available. The returned Span is owned by Envoy; do not Finish it. Use + // Span.SpawnChild to create module-owned child spans. + GetActiveSpan() Span + + // GetClusterName returns the name of the cluster the current request is routed to. + // Returns cluster name and true if found, otherwise an empty UnsafeEnvoyBuffer and false. + // NOTE: The memory of the underlying data may not be managed by Go GC. Copy the data if you + // need to keep it past the current callback. + GetClusterName() (UnsafeEnvoyBuffer, bool) + + // GetClusterHostCounts returns the host counts for the routed cluster at the given priority. + // Returns host counts and true if successful, otherwise a zero-valued struct and false. + GetClusterHostCounts(priority uint32) (ClusterHostCounts, bool) + + // SetUpstreamOverrideHost sets a host that the upstream load balancer should select first + // if it exists in the routed cluster. Useful for sticky sessions or host affinity. When + // strict is false, normal load balancing is used as a fallback. Returns false if the host + // address was invalid. + SetUpstreamOverrideHost(host string, strict bool) bool + + // ResetStream resets the HTTP stream with the given reason and optional details. After this + // call, no further filter callbacks will be invoked except OnDestroy. + ResetStream(reason HttpFilterStreamResetReason, details string) + + // SendGoAwayAndClose sends a GOAWAY frame to the downstream and closes the connection. If + // graceful is true, a graceful drain is initiated before closing. + SendGoAwayAndClose(graceful bool) + + // RecreateStream recreates the HTTP stream, optionally with new headers (or with the original + // headers if headers is nil). Useful for internal redirects or request retries. After a + // successful call, the current filter chain is destroyed and the filter SHOULD return Stop + // from the current callback. Returns false if recreation could not be initiated (e.g., the + // request body has not been fully received yet). + RecreateStream(headers [][2]string) bool + + // RequestHeaders retrieves the request headers. + RequestHeaders() HeaderMap + + // BufferedRequestBody retrieves the buffered request body in the chain. + // NOTE: Because of streaming processing, the request body is not always fully buffered. + // This function only retrieves the currently buffered body in the chain. The latest newly + // received body chunk is passed as the parameter to OnRequestBody. Only when endOfStream is + // true or OnRequestTrailers is called is the full request body received. + BufferedRequestBody() BodyBuffer + + // ReceivedRequestBody retrieves the latest received request body chunk in the OnRequestBody + // callback. + // NOTE: This is only valid in the OnRequestBody callback. For other callbacks or outside of + // callbacks, use BufferedRequestBody to get the currently buffered body in the chain. + ReceivedRequestBody() BodyBuffer + + // RequestTrailers retrieves the request trailers. + RequestTrailers() HeaderMap + + // ResponseHeaders retrieves the response headers. + ResponseHeaders() HeaderMap + + // BufferedResponseBody retrieves the buffered response body in the chain. See + // BufferedRequestBody for the buffering caveats. + BufferedResponseBody() BodyBuffer + + // ReceivedResponseBody retrieves the latest received response body chunk in the OnResponseBody + // callback. + // NOTE: This is only valid in the OnResponseBody callback. For other callbacks or outside of + // callbacks, use BufferedResponseBody to get the currently buffered body in the chain. + ReceivedResponseBody() BodyBuffer + + // ReceivedBufferedRequestBody returns true if the latest received request body is the + // previously buffered request body. This is true when a previous filter in the chain stopped + // and buffered the request body, then resumed, and this filter is now receiving that buffered + // body. + // NOTE: This is only meaningful inside the OnRequestBody callback. + ReceivedBufferedRequestBody() bool + + // ReceivedBufferedResponseBody returns true if the latest received response body is the + // previously buffered response body. This is true when a previous filter in the chain stopped + // and buffered the response body, then resumed, and this filter is now receiving that buffered + // body. + // NOTE: This is only meaningful inside the OnResponseBody callback. + ReceivedBufferedResponseBody() bool + + // ResponseTrailers retrieves the response trailers. + ResponseTrailers() HeaderMap + + // GetMostSpecificConfig retrieves the most specific route configuration for the stream. + GetMostSpecificConfig() any + + // GetScheduler retrieves the scheduler related to this stream plugin for asynchronous + // operations. + // + // NOTE: This MUST only be called during OnRequest* or OnResponse* callbacks. But then the + // returned Scheduler can be used later even outside of the callbacks and even at other + // threads. + GetScheduler() Scheduler + + // Log will log the given message via the host environment's logging mechanism. + Log(level LogLevel, format string, args ...any) + + // HttpCallout performs an HTTP call to an external service. The call is asynchronous; the + // response, or an error, is delivered to the provided callback. + // + // Returns the initialization result and the callout ID. A non-success result indicates the + // callout failed to start. + // + // NOTE: This method should only be called during OnRequest* or OnResponse* callbacks or + // from a scheduled function, so that it runs on the thread where the stream is being + // processed. + HttpCallout(cluster string, headers [][2]string, body []byte, timeoutMs uint64, + cb HttpCalloutCallback) (HttpCalloutInitResult, uint64) + + // StartHttpStream starts a new HTTP stream to an external service. The stream is asynchronous; + // responses are delivered to the provided callback. + // + // Returns the initialization result and the stream ID. A non-success result indicates the + // stream failed to start. + // + // NOTE: This method should only be called during OnRequest* or OnResponse* callbacks or + // from a scheduled function, so that it runs on the thread where the stream is being + // processed. + StartHttpStream(cluster string, headers [][2]string, body []byte, endOfStream bool, timeoutMs uint64, + cb HttpStreamCallback) (HttpCalloutInitResult, uint64) + + // SendHttpStreamData sends data on an existing HTTP stream. Returns true if the data was + // sent successfully. + // + // NOTE: This method should only be called during OnRequest* or OnResponse* callbacks or + // from a scheduled function, so that it runs on the thread where the stream is being + // processed. + SendHttpStreamData(streamID uint64, body []byte, endOfStream bool) bool + + // SendHttpStreamTrailers sends trailers on an existing HTTP stream. Returns true if the + // trailers were sent successfully. + // + // NOTE: This method should only be called during OnRequest* or OnResponse* callbacks or + // from a scheduled function, so that it runs on the thread where the stream is being + // processed. + SendHttpStreamTrailers(streamID uint64, trailers [][2]string) bool + + // ResetHttpStream resets an existing HTTP stream. + // + // NOTE: This method should only be called during OnRequest* or OnResponse* callbacks or + // from a scheduled function, so that it runs on the thread where the stream is being + // processed. + ResetHttpStream(streamID uint64) + + // SetDownstreamWatermarkCallbacks sets the downstream watermark callbacks for the stream. + SetDownstreamWatermarkCallbacks(callbacks DownstreamWatermarkCallbacks) + + // ClearDownstreamWatermarkCallbacks unsets the downstream watermark callbacks for the stream. + ClearDownstreamWatermarkCallbacks() + + // RecordHistogramValue records the given value to the histogram metric. The order and + // size of tagsValues must match the tag keys defined when the metric was created. + RecordHistogramValue(id MetricID, value uint64, tagsValues ...string) MetricsResult + + // SetGaugeValue sets the given value on the gauge metric. The order and size of + // tagsValues must match the tag keys defined when the metric was created. + SetGaugeValue(id MetricID, value uint64, tagsValues ...string) MetricsResult + + // IncrementGaugeValue adds the given value to the gauge metric. The order and size of + // tagsValues must match the tag keys defined when the metric was created. + IncrementGaugeValue(id MetricID, value uint64, tagsValues ...string) MetricsResult + + // DecrementGaugeValue subtracts the given value from the gauge metric. The order and + // size of tagsValues must match the tag keys defined when the metric was created. + DecrementGaugeValue(id MetricID, value uint64, tagsValues ...string) MetricsResult + + // IncrementCounterValue adds the given value to the counter metric. The order and + // size of tagsValues must match the tag keys defined when the metric was created. + IncrementCounterValue(id MetricID, value uint64, tagsValues ...string) MetricsResult +} + +// HttpFilterConfigHandle is the per-filter-config handle exposed to HttpFilterConfig +// implementations. It supports config-scoped logging, metric definition, and async I/O via +// HttpCallout / StartHttpStream from the main thread. +type HttpFilterConfigHandle interface { + // Log will log the given message via the host environment's logging mechanism. + Log(level LogLevel, format string, args ...any) + + // DefineHistogram creates a histogram metric with the given name, and tag keys. + // Returns histogram metric id. This metric can never be used after the plugin + // config is unloaded. + DefineHistogram(name string, tagKeys ...string) (MetricID, MetricsResult) + + // DefineGauge creates a gauge metric with the given name, description, and tag keys. + // Returns gauge metric id. This metric can never be used after the plugin + // config is unloaded. + DefineGauge(name string, tagKeys ...string) (MetricID, MetricsResult) + + // DefineCounter creates a counter metric with the given name, description, and tag keys. + // Returns counter metric id. This metric can never be used after the plugin + // config is unloaded. + DefineCounter(name string, tagKeys ...string) (MetricID, MetricsResult) + + // HttpCallout performs an HTTP call to an external service from the config context. + // The call is asynchronous, and the response will be delivered via the provided callback. + // This is similar to HttpFilterHandle.HttpCallout but runs on the main thread rather than + // the worker thread. + // Returns result of the HTTP callout initialization and the callout ID. + HttpCallout(cluster string, headers [][2]string, body []byte, timeoutMs uint64, + cb HttpCalloutCallback) (HttpCalloutInitResult, uint64) + + // StartHttpStream starts a new HTTP stream to an external service from the config context. + // The stream is asynchronous, and the response will be delivered via the provided callback. + // This is similar to HttpFilterHandle.StartHttpStream but runs on the main thread. + // Returns result of the HTTP stream initialization and the stream ID. + StartHttpStream(cluster string, headers [][2]string, body []byte, endOfStream bool, + timeoutMs uint64, cb HttpStreamCallback) (HttpCalloutInitResult, uint64) + + // SendHttpStreamData sends data on an existing HTTP stream started via StartHttpStream. + // Returns true if the data was sent successfully. + SendHttpStreamData(streamID uint64, body []byte, endOfStream bool) bool + + // SendHttpStreamTrailers sends trailers on an existing HTTP stream started via StartHttpStream. + // Returns true if the trailers were sent successfully. + SendHttpStreamTrailers(streamID uint64, trailers [][2]string) bool + + // ResetHttpStream resets an existing HTTP stream started via StartHttpStream. + ResetHttpStream(streamID uint64) + + // GetScheduler retrieves a scheduler for deferred task execution in the config context. + // This should be called only during the plugin configuration phase, and the returned + // Scheduler can be used later even outside of the callbacks and at other threads. + GetScheduler() Scheduler +} diff --git a/source/extensions/dynamic_modules/sdk/go/shared/api.go b/source/extensions/dynamic_modules/sdk/go/shared/http_api.go similarity index 82% rename from source/extensions/dynamic_modules/sdk/go/shared/api.go rename to source/extensions/dynamic_modules/sdk/go/shared/http_api.go index 209d6ab702f16..301acefd6b9b2 100644 --- a/source/extensions/dynamic_modules/sdk/go/shared/api.go +++ b/source/extensions/dynamic_modules/sdk/go/shared/http_api.go @@ -1,4 +1,4 @@ -//go:generate mockgen -source=api.go -destination=mocks/mock_api.go -package=mocks +//go:generate mockgen -source=http_api.go -destination=mocks/mock_http_api.go -package=mocks package shared type HeadersStatus int32 @@ -65,6 +65,8 @@ const ( TrailersStatusDefault TrailersStatus = TrailersStatusContinue ) +// LocalReplyStatus is returned from HttpFilter.OnLocalReply to control whether Envoy should +// send the local reply to the client or reset the stream instead. type LocalReplyStatus int32 const ( @@ -81,38 +83,28 @@ const ( // not implement flexible stream control. But it should be enough for most of the use cases. type HttpFilter interface { // OnRequestHeaders will be called when the request headers are received. - // @Param headers the request headers. - // @Param endOfStream whether this is the end of the stream. - // @Return HeadersStatus the status to control the plugin chain processing. + // Returns the status to control the plugin chain processing. OnRequestHeaders(headers HeaderMap, endOfStream bool) HeadersStatus // OnRequestBody will be called when the request body are received. This may be called multiple times. - // @Param body the request body. - // @Param endOfStream whether this is the end of the stream. - // @Return BodyStatus the status to control the plugin chain processing. + // Returns the status to control the plugin chain processing. OnRequestBody(body BodyBuffer, endOfStream bool) BodyStatus // OnRequestTrailers will be called when the request trailers are received. - // @Param trailers the request trailers. - // @Return TrailersStatus the status to control the plugin chain processing. + // Returns the status to control the plugin chain processing. OnRequestTrailers(trailers HeaderMap) TrailersStatus // OnResponseHeaders will be called when the response headers are received. - // @Param headers the response headers. - // @Param endOfStream whether this is the end of the stream. - // @Return HeadersStatus the status to control the plugin chain processing. + // Returns the status to control the plugin chain processing. OnResponseHeaders(headers HeaderMap, endOfStream bool) HeadersStatus // OnResponseBody will be called when the response body is received. This may be called multiple // times. - // @Param body the response body. - // @Param endOfStream whether this is the end of the stream. - // @Return BodyStatus the status to control the plugin chain processing. + // Returns the status to control the plugin chain processing. OnResponseBody(body BodyBuffer, endOfStream bool) BodyStatus // OnResponseTrailers will be called when the response trailers are received. - // @Param trailers the response trailers. - // @Return TrailersStatus the status to control the plugin chain processing. + // Returns the status to control the plugin chain processing. OnResponseTrailers(trailers HeaderMap) TrailersStatus // OnStreamComplete is called when the stream processing is complete and before access logs @@ -126,16 +118,15 @@ type HttpFilter interface { // any per-stream resources. OnDestroy() - // OnLocalReply is called when a local reply is being sent. This allows the filter to modify - // the local reply or decide to reset the stream instead. This is called before the local reply - // is sent to the client and before the stream is reset. - // @Param responseCode the response code of the local reply. - // @Param details the details of the local reply. This is usually used to indicate the reason - // for sending the local reply, for example, "buffer overflow" or "rate limit exceeded". - // @Param resetImminent whether the stream is going to be reset after this local reply. This allows - // the filter to decide whether to continue with sending the local reply or just reset the stream. - // @Return LocalReplyStatus the status to control whether to continue with sending the local reply - // or reset the stream. + // OnLocalReply is called when a local reply is being sent. The filter can either let the + // reply proceed (LocalReplyStatusContinue) or ask Envoy to reset the stream instead + // (LocalReplyStatusContinueAndResetStream). This is invoked before the reply leaves + // Envoy and before any stream reset. + // + // details is a short description of why the local reply is being sent (e.g. + // "buffer overflow", "rate limit exceeded"). The buffer aliases Envoy memory; copy + // before retaining past this call. resetImminent is true if Envoy is going to reset + // the stream after this call. OnLocalReply(responseCode uint32, details UnsafeEnvoyBuffer, resetImminent bool) LocalReplyStatus } diff --git a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_base.go b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_http.go similarity index 88% rename from source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_base.go rename to source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_http.go index 0793e6d3fcdfc..7e2a6a8b981cd 100644 --- a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_base.go +++ b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_http.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: base.go +// Source: http.go // // Generated by this command: // -// mockgen -source=base.go -destination=mocks/mock_base.go -package=mocks +// mockgen -source=http.go -destination=mocks/mock_http.go -package=mocks // // Package mocks is a generated GoMock package. @@ -194,210 +194,6 @@ func (mr *MockHeaderMapMockRecorder) Set(key, value any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockHeaderMap)(nil).Set), key, value) } -// MockHttpCalloutCallback is a mock of HttpCalloutCallback interface. -type MockHttpCalloutCallback struct { - ctrl *gomock.Controller - recorder *MockHttpCalloutCallbackMockRecorder - isgomock struct{} -} - -// MockHttpCalloutCallbackMockRecorder is the mock recorder for MockHttpCalloutCallback. -type MockHttpCalloutCallbackMockRecorder struct { - mock *MockHttpCalloutCallback -} - -// NewMockHttpCalloutCallback creates a new mock instance. -func NewMockHttpCalloutCallback(ctrl *gomock.Controller) *MockHttpCalloutCallback { - mock := &MockHttpCalloutCallback{ctrl: ctrl} - mock.recorder = &MockHttpCalloutCallbackMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockHttpCalloutCallback) EXPECT() *MockHttpCalloutCallbackMockRecorder { - return m.recorder -} - -// OnHttpCalloutDone mocks base method. -func (m *MockHttpCalloutCallback) OnHttpCalloutDone(calloutID uint64, result shared.HttpCalloutResult, headers [][2]shared.UnsafeEnvoyBuffer, body []shared.UnsafeEnvoyBuffer) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "OnHttpCalloutDone", calloutID, result, headers, body) -} - -// OnHttpCalloutDone indicates an expected call of OnHttpCalloutDone. -func (mr *MockHttpCalloutCallbackMockRecorder) OnHttpCalloutDone(calloutID, result, headers, body any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpCalloutDone", reflect.TypeOf((*MockHttpCalloutCallback)(nil).OnHttpCalloutDone), calloutID, result, headers, body) -} - -// MockHttpStreamCallback is a mock of HttpStreamCallback interface. -type MockHttpStreamCallback struct { - ctrl *gomock.Controller - recorder *MockHttpStreamCallbackMockRecorder - isgomock struct{} -} - -// MockHttpStreamCallbackMockRecorder is the mock recorder for MockHttpStreamCallback. -type MockHttpStreamCallbackMockRecorder struct { - mock *MockHttpStreamCallback -} - -// NewMockHttpStreamCallback creates a new mock instance. -func NewMockHttpStreamCallback(ctrl *gomock.Controller) *MockHttpStreamCallback { - mock := &MockHttpStreamCallback{ctrl: ctrl} - mock.recorder = &MockHttpStreamCallbackMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockHttpStreamCallback) EXPECT() *MockHttpStreamCallbackMockRecorder { - return m.recorder -} - -// OnHttpStreamComplete mocks base method. -func (m *MockHttpStreamCallback) OnHttpStreamComplete(streamID uint64) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "OnHttpStreamComplete", streamID) -} - -// OnHttpStreamComplete indicates an expected call of OnHttpStreamComplete. -func (mr *MockHttpStreamCallbackMockRecorder) OnHttpStreamComplete(streamID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpStreamComplete", reflect.TypeOf((*MockHttpStreamCallback)(nil).OnHttpStreamComplete), streamID) -} - -// OnHttpStreamData mocks base method. -func (m *MockHttpStreamCallback) OnHttpStreamData(streamID uint64, body []shared.UnsafeEnvoyBuffer, endStream bool) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "OnHttpStreamData", streamID, body, endStream) -} - -// OnHttpStreamData indicates an expected call of OnHttpStreamData. -func (mr *MockHttpStreamCallbackMockRecorder) OnHttpStreamData(streamID, body, endStream any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpStreamData", reflect.TypeOf((*MockHttpStreamCallback)(nil).OnHttpStreamData), streamID, body, endStream) -} - -// OnHttpStreamHeaders mocks base method. -func (m *MockHttpStreamCallback) OnHttpStreamHeaders(streamID uint64, headers [][2]shared.UnsafeEnvoyBuffer, endStream bool) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "OnHttpStreamHeaders", streamID, headers, endStream) -} - -// OnHttpStreamHeaders indicates an expected call of OnHttpStreamHeaders. -func (mr *MockHttpStreamCallbackMockRecorder) OnHttpStreamHeaders(streamID, headers, endStream any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpStreamHeaders", reflect.TypeOf((*MockHttpStreamCallback)(nil).OnHttpStreamHeaders), streamID, headers, endStream) -} - -// OnHttpStreamReset mocks base method. -func (m *MockHttpStreamCallback) OnHttpStreamReset(streamID uint64, reason shared.HttpStreamResetReason) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "OnHttpStreamReset", streamID, reason) -} - -// OnHttpStreamReset indicates an expected call of OnHttpStreamReset. -func (mr *MockHttpStreamCallbackMockRecorder) OnHttpStreamReset(streamID, reason any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpStreamReset", reflect.TypeOf((*MockHttpStreamCallback)(nil).OnHttpStreamReset), streamID, reason) -} - -// OnHttpStreamTrailers mocks base method. -func (m *MockHttpStreamCallback) OnHttpStreamTrailers(streamID uint64, trailers [][2]shared.UnsafeEnvoyBuffer) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "OnHttpStreamTrailers", streamID, trailers) -} - -// OnHttpStreamTrailers indicates an expected call of OnHttpStreamTrailers. -func (mr *MockHttpStreamCallbackMockRecorder) OnHttpStreamTrailers(streamID, trailers any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpStreamTrailers", reflect.TypeOf((*MockHttpStreamCallback)(nil).OnHttpStreamTrailers), streamID, trailers) -} - -// MockScheduler is a mock of Scheduler interface. -type MockScheduler struct { - ctrl *gomock.Controller - recorder *MockSchedulerMockRecorder - isgomock struct{} -} - -// MockSchedulerMockRecorder is the mock recorder for MockScheduler. -type MockSchedulerMockRecorder struct { - mock *MockScheduler -} - -// NewMockScheduler creates a new mock instance. -func NewMockScheduler(ctrl *gomock.Controller) *MockScheduler { - mock := &MockScheduler{ctrl: ctrl} - mock.recorder = &MockSchedulerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockScheduler) EXPECT() *MockSchedulerMockRecorder { - return m.recorder -} - -// Schedule mocks base method. -func (m *MockScheduler) Schedule(arg0 func()) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "Schedule", arg0) -} - -// Schedule indicates an expected call of Schedule. -func (mr *MockSchedulerMockRecorder) Schedule(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Schedule", reflect.TypeOf((*MockScheduler)(nil).Schedule), arg0) -} - -// MockDownstreamWatermarkCallbacks is a mock of DownstreamWatermarkCallbacks interface. -type MockDownstreamWatermarkCallbacks struct { - ctrl *gomock.Controller - recorder *MockDownstreamWatermarkCallbacksMockRecorder - isgomock struct{} -} - -// MockDownstreamWatermarkCallbacksMockRecorder is the mock recorder for MockDownstreamWatermarkCallbacks. -type MockDownstreamWatermarkCallbacksMockRecorder struct { - mock *MockDownstreamWatermarkCallbacks -} - -// NewMockDownstreamWatermarkCallbacks creates a new mock instance. -func NewMockDownstreamWatermarkCallbacks(ctrl *gomock.Controller) *MockDownstreamWatermarkCallbacks { - mock := &MockDownstreamWatermarkCallbacks{ctrl: ctrl} - mock.recorder = &MockDownstreamWatermarkCallbacksMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockDownstreamWatermarkCallbacks) EXPECT() *MockDownstreamWatermarkCallbacksMockRecorder { - return m.recorder -} - -// OnAboveWriteBufferHighWatermark mocks base method. -func (m *MockDownstreamWatermarkCallbacks) OnAboveWriteBufferHighWatermark() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "OnAboveWriteBufferHighWatermark") -} - -// OnAboveWriteBufferHighWatermark indicates an expected call of OnAboveWriteBufferHighWatermark. -func (mr *MockDownstreamWatermarkCallbacksMockRecorder) OnAboveWriteBufferHighWatermark() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAboveWriteBufferHighWatermark", reflect.TypeOf((*MockDownstreamWatermarkCallbacks)(nil).OnAboveWriteBufferHighWatermark)) -} - -// OnBelowWriteBufferLowWatermark mocks base method. -func (m *MockDownstreamWatermarkCallbacks) OnBelowWriteBufferLowWatermark() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "OnBelowWriteBufferLowWatermark") -} - -// OnBelowWriteBufferLowWatermark indicates an expected call of OnBelowWriteBufferLowWatermark. -func (mr *MockDownstreamWatermarkCallbacksMockRecorder) OnBelowWriteBufferLowWatermark() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnBelowWriteBufferLowWatermark", reflect.TypeOf((*MockDownstreamWatermarkCallbacks)(nil).OnBelowWriteBufferLowWatermark)) -} - // MockSpan is a mock of Span interface. type MockSpan struct { ctrl *gomock.Controller @@ -528,17 +324,17 @@ func (mr *MockSpanMockRecorder) SetTag(key, value any) *gomock.Call { } // SpawnChild mocks base method. -func (m *MockSpan) SpawnChild(operation string) shared.ChildSpan { +func (m *MockSpan) SpawnChild(operationName string) shared.ChildSpan { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SpawnChild", operation) + ret := m.ctrl.Call(m, "SpawnChild", operationName) ret0, _ := ret[0].(shared.ChildSpan) return ret0 } // SpawnChild indicates an expected call of SpawnChild. -func (mr *MockSpanMockRecorder) SpawnChild(operation any) *gomock.Call { +func (mr *MockSpanMockRecorder) SpawnChild(operationName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpawnChild", reflect.TypeOf((*MockSpan)(nil).SpawnChild), operation) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpawnChild", reflect.TypeOf((*MockSpan)(nil).SpawnChild), operationName) } // MockChildSpan is a mock of ChildSpan interface. @@ -577,51 +373,6 @@ func (mr *MockChildSpanMockRecorder) Finish() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Finish", reflect.TypeOf((*MockChildSpan)(nil).Finish)) } -// GetBaggage mocks base method. -func (m *MockChildSpan) GetBaggage(key string) (shared.UnsafeEnvoyBuffer, bool) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBaggage", key) - ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) - ret1, _ := ret[1].(bool) - return ret0, ret1 -} - -// GetBaggage indicates an expected call of GetBaggage. -func (mr *MockChildSpanMockRecorder) GetBaggage(key any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBaggage", reflect.TypeOf((*MockChildSpan)(nil).GetBaggage), key) -} - -// GetSpanID mocks base method. -func (m *MockChildSpan) GetSpanID() (shared.UnsafeEnvoyBuffer, bool) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSpanID") - ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) - ret1, _ := ret[1].(bool) - return ret0, ret1 -} - -// GetSpanID indicates an expected call of GetSpanID. -func (mr *MockChildSpanMockRecorder) GetSpanID() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSpanID", reflect.TypeOf((*MockChildSpan)(nil).GetSpanID)) -} - -// GetTraceID mocks base method. -func (m *MockChildSpan) GetTraceID() (shared.UnsafeEnvoyBuffer, bool) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTraceID") - ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) - ret1, _ := ret[1].(bool) - return ret0, ret1 -} - -// GetTraceID indicates an expected call of GetTraceID. -func (mr *MockChildSpanMockRecorder) GetTraceID() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTraceID", reflect.TypeOf((*MockChildSpan)(nil).GetTraceID)) -} - // Log mocks base method. func (m *MockChildSpan) Log(event string) { m.ctrl.T.Helper() @@ -683,17 +434,65 @@ func (mr *MockChildSpanMockRecorder) SetTag(key, value any) *gomock.Call { } // SpawnChild mocks base method. -func (m *MockChildSpan) SpawnChild(operation string) shared.ChildSpan { +func (m *MockChildSpan) SpawnChild(operationName string) shared.ChildSpan { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SpawnChild", operation) + ret := m.ctrl.Call(m, "SpawnChild", operationName) ret0, _ := ret[0].(shared.ChildSpan) return ret0 } // SpawnChild indicates an expected call of SpawnChild. -func (mr *MockChildSpanMockRecorder) SpawnChild(operation any) *gomock.Call { +func (mr *MockChildSpanMockRecorder) SpawnChild(operationName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpawnChild", reflect.TypeOf((*MockChildSpan)(nil).SpawnChild), operation) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpawnChild", reflect.TypeOf((*MockChildSpan)(nil).SpawnChild), operationName) +} + +// MockDownstreamWatermarkCallbacks is a mock of DownstreamWatermarkCallbacks interface. +type MockDownstreamWatermarkCallbacks struct { + ctrl *gomock.Controller + recorder *MockDownstreamWatermarkCallbacksMockRecorder + isgomock struct{} +} + +// MockDownstreamWatermarkCallbacksMockRecorder is the mock recorder for MockDownstreamWatermarkCallbacks. +type MockDownstreamWatermarkCallbacksMockRecorder struct { + mock *MockDownstreamWatermarkCallbacks +} + +// NewMockDownstreamWatermarkCallbacks creates a new mock instance. +func NewMockDownstreamWatermarkCallbacks(ctrl *gomock.Controller) *MockDownstreamWatermarkCallbacks { + mock := &MockDownstreamWatermarkCallbacks{ctrl: ctrl} + mock.recorder = &MockDownstreamWatermarkCallbacksMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDownstreamWatermarkCallbacks) EXPECT() *MockDownstreamWatermarkCallbacksMockRecorder { + return m.recorder +} + +// OnAboveWriteBufferHighWatermark mocks base method. +func (m *MockDownstreamWatermarkCallbacks) OnAboveWriteBufferHighWatermark() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnAboveWriteBufferHighWatermark") +} + +// OnAboveWriteBufferHighWatermark indicates an expected call of OnAboveWriteBufferHighWatermark. +func (mr *MockDownstreamWatermarkCallbacksMockRecorder) OnAboveWriteBufferHighWatermark() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAboveWriteBufferHighWatermark", reflect.TypeOf((*MockDownstreamWatermarkCallbacks)(nil).OnAboveWriteBufferHighWatermark)) +} + +// OnBelowWriteBufferLowWatermark mocks base method. +func (m *MockDownstreamWatermarkCallbacks) OnBelowWriteBufferLowWatermark() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnBelowWriteBufferLowWatermark") +} + +// OnBelowWriteBufferLowWatermark indicates an expected call of OnBelowWriteBufferLowWatermark. +func (mr *MockDownstreamWatermarkCallbacksMockRecorder) OnBelowWriteBufferLowWatermark() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnBelowWriteBufferLowWatermark", reflect.TypeOf((*MockDownstreamWatermarkCallbacks)(nil).OnBelowWriteBufferLowWatermark)) } // MockHttpFilterHandle is a mock of HttpFilterHandle interface. @@ -899,10 +698,10 @@ func (mr *MockHttpFilterHandleMockRecorder) GetAttributeBool(attributeID any) *g } // GetAttributeNumber mocks base method. -func (m *MockHttpFilterHandle) GetAttributeNumber(attributeID shared.AttributeID) (float64, bool) { +func (m *MockHttpFilterHandle) GetAttributeNumber(attributeID shared.AttributeID) (uint64, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAttributeNumber", attributeID) - ret0, _ := ret[0].(float64) + ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(bool) return ret0, ret1 } diff --git a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_api.go b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_http_api.go similarity index 98% rename from source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_api.go rename to source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_http_api.go index 34dae5915c643..2a53851cbeadd 100644 --- a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_api.go +++ b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_http_api.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: api.go +// Source: http_api.go // // Generated by this command: // -// mockgen -source=api.go -destination=mocks/mock_api.go -package=mocks +// mockgen -source=http_api.go -destination=mocks/mock_http_api.go -package=mocks // // Package mocks is a generated GoMock package. diff --git a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_network_api.go b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_network_api.go new file mode 100644 index 0000000000000..a8cc08bd0de40 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_network_api.go @@ -0,0 +1,220 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: network_api.go +// +// Generated by this command: +// +// mockgen -source=network_api.go -destination=mocks/mock_network_api.go -package=mocks +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + shared "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/shared" + gomock "go.uber.org/mock/gomock" +) + +// MockNetworkFilter is a mock of NetworkFilter interface. +type MockNetworkFilter struct { + ctrl *gomock.Controller + recorder *MockNetworkFilterMockRecorder + isgomock struct{} +} + +// MockNetworkFilterMockRecorder is the mock recorder for MockNetworkFilter. +type MockNetworkFilterMockRecorder struct { + mock *MockNetworkFilter +} + +// NewMockNetworkFilter creates a new mock instance. +func NewMockNetworkFilter(ctrl *gomock.Controller) *MockNetworkFilter { + mock := &MockNetworkFilter{ctrl: ctrl} + mock.recorder = &MockNetworkFilterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetworkFilter) EXPECT() *MockNetworkFilterMockRecorder { + return m.recorder +} + +// OnAboveWriteBufferHighWatermark mocks base method. +func (m *MockNetworkFilter) OnAboveWriteBufferHighWatermark() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnAboveWriteBufferHighWatermark") +} + +// OnAboveWriteBufferHighWatermark indicates an expected call of OnAboveWriteBufferHighWatermark. +func (mr *MockNetworkFilterMockRecorder) OnAboveWriteBufferHighWatermark() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAboveWriteBufferHighWatermark", reflect.TypeOf((*MockNetworkFilter)(nil).OnAboveWriteBufferHighWatermark)) +} + +// OnBelowWriteBufferLowWatermark mocks base method. +func (m *MockNetworkFilter) OnBelowWriteBufferLowWatermark() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnBelowWriteBufferLowWatermark") +} + +// OnBelowWriteBufferLowWatermark indicates an expected call of OnBelowWriteBufferLowWatermark. +func (mr *MockNetworkFilterMockRecorder) OnBelowWriteBufferLowWatermark() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnBelowWriteBufferLowWatermark", reflect.TypeOf((*MockNetworkFilter)(nil).OnBelowWriteBufferLowWatermark)) +} + +// OnDestroy mocks base method. +func (m *MockNetworkFilter) OnDestroy() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnDestroy") +} + +// OnDestroy indicates an expected call of OnDestroy. +func (mr *MockNetworkFilterMockRecorder) OnDestroy() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnDestroy", reflect.TypeOf((*MockNetworkFilter)(nil).OnDestroy)) +} + +// OnEvent mocks base method. +func (m *MockNetworkFilter) OnEvent(event shared.NetworkConnectionEvent) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnEvent", event) +} + +// OnEvent indicates an expected call of OnEvent. +func (mr *MockNetworkFilterMockRecorder) OnEvent(event any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnEvent", reflect.TypeOf((*MockNetworkFilter)(nil).OnEvent), event) +} + +// OnNewConnection mocks base method. +func (m *MockNetworkFilter) OnNewConnection() shared.NetworkFilterStatus { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnNewConnection") + ret0, _ := ret[0].(shared.NetworkFilterStatus) + return ret0 +} + +// OnNewConnection indicates an expected call of OnNewConnection. +func (mr *MockNetworkFilterMockRecorder) OnNewConnection() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnNewConnection", reflect.TypeOf((*MockNetworkFilter)(nil).OnNewConnection)) +} + +// OnRead mocks base method. +func (m *MockNetworkFilter) OnRead(data shared.NetworkBuffer, endOfStream bool) shared.NetworkFilterStatus { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnRead", data, endOfStream) + ret0, _ := ret[0].(shared.NetworkFilterStatus) + return ret0 +} + +// OnRead indicates an expected call of OnRead. +func (mr *MockNetworkFilterMockRecorder) OnRead(data, endOfStream any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnRead", reflect.TypeOf((*MockNetworkFilter)(nil).OnRead), data, endOfStream) +} + +// OnWrite mocks base method. +func (m *MockNetworkFilter) OnWrite(data shared.NetworkBuffer, endOfStream bool) shared.NetworkFilterStatus { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnWrite", data, endOfStream) + ret0, _ := ret[0].(shared.NetworkFilterStatus) + return ret0 +} + +// OnWrite indicates an expected call of OnWrite. +func (mr *MockNetworkFilterMockRecorder) OnWrite(data, endOfStream any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnWrite", reflect.TypeOf((*MockNetworkFilter)(nil).OnWrite), data, endOfStream) +} + +// MockNetworkFilterFactory is a mock of NetworkFilterFactory interface. +type MockNetworkFilterFactory struct { + ctrl *gomock.Controller + recorder *MockNetworkFilterFactoryMockRecorder + isgomock struct{} +} + +// MockNetworkFilterFactoryMockRecorder is the mock recorder for MockNetworkFilterFactory. +type MockNetworkFilterFactoryMockRecorder struct { + mock *MockNetworkFilterFactory +} + +// NewMockNetworkFilterFactory creates a new mock instance. +func NewMockNetworkFilterFactory(ctrl *gomock.Controller) *MockNetworkFilterFactory { + mock := &MockNetworkFilterFactory{ctrl: ctrl} + mock.recorder = &MockNetworkFilterFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetworkFilterFactory) EXPECT() *MockNetworkFilterFactoryMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockNetworkFilterFactory) Create(handle shared.NetworkFilterHandle) shared.NetworkFilter { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", handle) + ret0, _ := ret[0].(shared.NetworkFilter) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockNetworkFilterFactoryMockRecorder) Create(handle any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockNetworkFilterFactory)(nil).Create), handle) +} + +// OnDestroy mocks base method. +func (m *MockNetworkFilterFactory) OnDestroy() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnDestroy") +} + +// OnDestroy indicates an expected call of OnDestroy. +func (mr *MockNetworkFilterFactoryMockRecorder) OnDestroy() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnDestroy", reflect.TypeOf((*MockNetworkFilterFactory)(nil).OnDestroy)) +} + +// MockNetworkFilterConfigFactory is a mock of NetworkFilterConfigFactory interface. +type MockNetworkFilterConfigFactory struct { + ctrl *gomock.Controller + recorder *MockNetworkFilterConfigFactoryMockRecorder + isgomock struct{} +} + +// MockNetworkFilterConfigFactoryMockRecorder is the mock recorder for MockNetworkFilterConfigFactory. +type MockNetworkFilterConfigFactoryMockRecorder struct { + mock *MockNetworkFilterConfigFactory +} + +// NewMockNetworkFilterConfigFactory creates a new mock instance. +func NewMockNetworkFilterConfigFactory(ctrl *gomock.Controller) *MockNetworkFilterConfigFactory { + mock := &MockNetworkFilterConfigFactory{ctrl: ctrl} + mock.recorder = &MockNetworkFilterConfigFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetworkFilterConfigFactory) EXPECT() *MockNetworkFilterConfigFactoryMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockNetworkFilterConfigFactory) Create(handle shared.NetworkFilterConfigHandle, unparsedConfig []byte) (shared.NetworkFilterFactory, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", handle, unparsedConfig) + ret0, _ := ret[0].(shared.NetworkFilterFactory) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockNetworkFilterConfigFactoryMockRecorder) Create(handle, unparsedConfig any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockNetworkFilterConfigFactory)(nil).Create), handle, unparsedConfig) +} diff --git a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_network_base.go b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_network_base.go new file mode 100644 index 0000000000000..b42fd2634b665 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_network_base.go @@ -0,0 +1,991 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: network_base.go +// +// Generated by this command: +// +// mockgen -source=network_base.go -destination=mocks/mock_network_base.go -package=mocks +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + shared "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/shared" + gomock "go.uber.org/mock/gomock" +) + +// MockNetworkBuffer is a mock of NetworkBuffer interface. +type MockNetworkBuffer struct { + ctrl *gomock.Controller + recorder *MockNetworkBufferMockRecorder + isgomock struct{} +} + +// MockNetworkBufferMockRecorder is the mock recorder for MockNetworkBuffer. +type MockNetworkBufferMockRecorder struct { + mock *MockNetworkBuffer +} + +// NewMockNetworkBuffer creates a new mock instance. +func NewMockNetworkBuffer(ctrl *gomock.Controller) *MockNetworkBuffer { + mock := &MockNetworkBuffer{ctrl: ctrl} + mock.recorder = &MockNetworkBufferMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetworkBuffer) EXPECT() *MockNetworkBufferMockRecorder { + return m.recorder +} + +// Append mocks base method. +func (m *MockNetworkBuffer) Append(data []byte) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Append", data) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Append indicates an expected call of Append. +func (mr *MockNetworkBufferMockRecorder) Append(data any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Append", reflect.TypeOf((*MockNetworkBuffer)(nil).Append), data) +} + +// Drain mocks base method. +func (m *MockNetworkBuffer) Drain(numBytes uint64) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Drain", numBytes) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Drain indicates an expected call of Drain. +func (mr *MockNetworkBufferMockRecorder) Drain(numBytes any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Drain", reflect.TypeOf((*MockNetworkBuffer)(nil).Drain), numBytes) +} + +// GetChunks mocks base method. +func (m *MockNetworkBuffer) GetChunks() []shared.UnsafeEnvoyBuffer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetChunks") + ret0, _ := ret[0].([]shared.UnsafeEnvoyBuffer) + return ret0 +} + +// GetChunks indicates an expected call of GetChunks. +func (mr *MockNetworkBufferMockRecorder) GetChunks() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChunks", reflect.TypeOf((*MockNetworkBuffer)(nil).GetChunks)) +} + +// GetSize mocks base method. +func (m *MockNetworkBuffer) GetSize() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSize") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetSize indicates an expected call of GetSize. +func (mr *MockNetworkBufferMockRecorder) GetSize() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSize", reflect.TypeOf((*MockNetworkBuffer)(nil).GetSize)) +} + +// Prepend mocks base method. +func (m *MockNetworkBuffer) Prepend(data []byte) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Prepend", data) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Prepend indicates an expected call of Prepend. +func (mr *MockNetworkBufferMockRecorder) Prepend(data any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prepend", reflect.TypeOf((*MockNetworkBuffer)(nil).Prepend), data) +} + +// MockNetworkFilterHandle is a mock of NetworkFilterHandle interface. +type MockNetworkFilterHandle struct { + ctrl *gomock.Controller + recorder *MockNetworkFilterHandleMockRecorder + isgomock struct{} +} + +// MockNetworkFilterHandleMockRecorder is the mock recorder for MockNetworkFilterHandle. +type MockNetworkFilterHandleMockRecorder struct { + mock *MockNetworkFilterHandle +} + +// NewMockNetworkFilterHandle creates a new mock instance. +func NewMockNetworkFilterHandle(ctrl *gomock.Controller) *MockNetworkFilterHandle { + mock := &MockNetworkFilterHandle{ctrl: ctrl} + mock.recorder = &MockNetworkFilterHandleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetworkFilterHandle) EXPECT() *MockNetworkFilterHandleMockRecorder { + return m.recorder +} + +// AboveHighWatermark mocks base method. +func (m *MockNetworkFilterHandle) AboveHighWatermark() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AboveHighWatermark") + ret0, _ := ret[0].(bool) + return ret0 +} + +// AboveHighWatermark indicates an expected call of AboveHighWatermark. +func (mr *MockNetworkFilterHandleMockRecorder) AboveHighWatermark() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AboveHighWatermark", reflect.TypeOf((*MockNetworkFilterHandle)(nil).AboveHighWatermark)) +} + +// Close mocks base method. +func (m *MockNetworkFilterHandle) Close(closeType shared.NetworkConnectionCloseType) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close", closeType) +} + +// Close indicates an expected call of Close. +func (mr *MockNetworkFilterHandleMockRecorder) Close(closeType any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockNetworkFilterHandle)(nil).Close), closeType) +} + +// CloseWithDetails mocks base method. +func (m *MockNetworkFilterHandle) CloseWithDetails(closeType shared.NetworkConnectionCloseType, details string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "CloseWithDetails", closeType, details) +} + +// CloseWithDetails indicates an expected call of CloseWithDetails. +func (mr *MockNetworkFilterHandleMockRecorder) CloseWithDetails(closeType, details any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseWithDetails", reflect.TypeOf((*MockNetworkFilterHandle)(nil).CloseWithDetails), closeType, details) +} + +// ContinueReading mocks base method. +func (m *MockNetworkFilterHandle) ContinueReading() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ContinueReading") +} + +// ContinueReading indicates an expected call of ContinueReading. +func (mr *MockNetworkFilterHandleMockRecorder) ContinueReading() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContinueReading", reflect.TypeOf((*MockNetworkFilterHandle)(nil).ContinueReading)) +} + +// DecrementGaugeValue mocks base method. +func (m *MockNetworkFilterHandle) DecrementGaugeValue(id shared.MetricID, value uint64) shared.MetricsResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DecrementGaugeValue", id, value) + ret0, _ := ret[0].(shared.MetricsResult) + return ret0 +} + +// DecrementGaugeValue indicates an expected call of DecrementGaugeValue. +func (mr *MockNetworkFilterHandleMockRecorder) DecrementGaugeValue(id, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecrementGaugeValue", reflect.TypeOf((*MockNetworkFilterHandle)(nil).DecrementGaugeValue), id, value) +} + +// DisableClose mocks base method. +func (m *MockNetworkFilterHandle) DisableClose(disabled bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "DisableClose", disabled) +} + +// DisableClose indicates an expected call of DisableClose. +func (mr *MockNetworkFilterHandleMockRecorder) DisableClose(disabled any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableClose", reflect.TypeOf((*MockNetworkFilterHandle)(nil).DisableClose), disabled) +} + +// EnableHalfClose mocks base method. +func (m *MockNetworkFilterHandle) EnableHalfClose(enabled bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "EnableHalfClose", enabled) +} + +// EnableHalfClose indicates an expected call of EnableHalfClose. +func (mr *MockNetworkFilterHandleMockRecorder) EnableHalfClose(enabled any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableHalfClose", reflect.TypeOf((*MockNetworkFilterHandle)(nil).EnableHalfClose), enabled) +} + +// GetBufferLimit mocks base method. +func (m *MockNetworkFilterHandle) GetBufferLimit() uint32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBufferLimit") + ret0, _ := ret[0].(uint32) + return ret0 +} + +// GetBufferLimit indicates an expected call of GetBufferLimit. +func (mr *MockNetworkFilterHandleMockRecorder) GetBufferLimit() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBufferLimit", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetBufferLimit)) +} + +// GetClusterHostCounts mocks base method. +func (m *MockNetworkFilterHandle) GetClusterHostCounts(cluster string, priority uint32) (shared.ClusterHostCounts, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterHostCounts", cluster, priority) + ret0, _ := ret[0].(shared.ClusterHostCounts) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetClusterHostCounts indicates an expected call of GetClusterHostCounts. +func (mr *MockNetworkFilterHandleMockRecorder) GetClusterHostCounts(cluster, priority any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterHostCounts", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetClusterHostCounts), cluster, priority) +} + +// GetConnectionID mocks base method. +func (m *MockNetworkFilterHandle) GetConnectionID() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConnectionID") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetConnectionID indicates an expected call of GetConnectionID. +func (mr *MockNetworkFilterHandleMockRecorder) GetConnectionID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConnectionID", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetConnectionID)) +} + +// GetConnectionState mocks base method. +func (m *MockNetworkFilterHandle) GetConnectionState() shared.NetworkConnectionState { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConnectionState") + ret0, _ := ret[0].(shared.NetworkConnectionState) + return ret0 +} + +// GetConnectionState indicates an expected call of GetConnectionState. +func (mr *MockNetworkFilterHandleMockRecorder) GetConnectionState() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConnectionState", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetConnectionState)) +} + +// GetDirectRemoteAddress mocks base method. +func (m *MockNetworkFilterHandle) GetDirectRemoteAddress() (shared.UnsafeEnvoyBuffer, uint32, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDirectRemoteAddress") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(uint32) + ret2, _ := ret[2].(bool) + return ret0, ret1, ret2 +} + +// GetDirectRemoteAddress indicates an expected call of GetDirectRemoteAddress. +func (mr *MockNetworkFilterHandleMockRecorder) GetDirectRemoteAddress() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDirectRemoteAddress", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetDirectRemoteAddress)) +} + +// GetFilterState mocks base method. +func (m *MockNetworkFilterHandle) GetFilterState(key string) (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFilterState", key) + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetFilterState indicates an expected call of GetFilterState. +func (mr *MockNetworkFilterHandleMockRecorder) GetFilterState(key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilterState", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetFilterState), key) +} + +// GetFilterStateTyped mocks base method. +func (m *MockNetworkFilterHandle) GetFilterStateTyped(key string) (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFilterStateTyped", key) + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetFilterStateTyped indicates an expected call of GetFilterStateTyped. +func (mr *MockNetworkFilterHandleMockRecorder) GetFilterStateTyped(key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilterStateTyped", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetFilterStateTyped), key) +} + +// GetLocalAddress mocks base method. +func (m *MockNetworkFilterHandle) GetLocalAddress() (shared.UnsafeEnvoyBuffer, uint32, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLocalAddress") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(uint32) + ret2, _ := ret[2].(bool) + return ret0, ret1, ret2 +} + +// GetLocalAddress indicates an expected call of GetLocalAddress. +func (mr *MockNetworkFilterHandleMockRecorder) GetLocalAddress() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocalAddress", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetLocalAddress)) +} + +// GetMetadataBool mocks base method. +func (m *MockNetworkFilterHandle) GetMetadataBool(metadataNamespace, key string) (bool, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMetadataBool", metadataNamespace, key) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetMetadataBool indicates an expected call of GetMetadataBool. +func (mr *MockNetworkFilterHandleMockRecorder) GetMetadataBool(metadataNamespace, key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMetadataBool", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetMetadataBool), metadataNamespace, key) +} + +// GetMetadataNumber mocks base method. +func (m *MockNetworkFilterHandle) GetMetadataNumber(metadataNamespace, key string) (float64, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMetadataNumber", metadataNamespace, key) + ret0, _ := ret[0].(float64) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetMetadataNumber indicates an expected call of GetMetadataNumber. +func (mr *MockNetworkFilterHandleMockRecorder) GetMetadataNumber(metadataNamespace, key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMetadataNumber", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetMetadataNumber), metadataNamespace, key) +} + +// GetMetadataString mocks base method. +func (m *MockNetworkFilterHandle) GetMetadataString(metadataNamespace, key string) (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMetadataString", metadataNamespace, key) + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetMetadataString indicates an expected call of GetMetadataString. +func (mr *MockNetworkFilterHandleMockRecorder) GetMetadataString(metadataNamespace, key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMetadataString", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetMetadataString), metadataNamespace, key) +} + +// GetRemoteAddress mocks base method. +func (m *MockNetworkFilterHandle) GetRemoteAddress() (shared.UnsafeEnvoyBuffer, uint32, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRemoteAddress") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(uint32) + ret2, _ := ret[2].(bool) + return ret0, ret1, ret2 +} + +// GetRemoteAddress indicates an expected call of GetRemoteAddress. +func (mr *MockNetworkFilterHandleMockRecorder) GetRemoteAddress() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRemoteAddress", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetRemoteAddress)) +} + +// GetRequestedServerName mocks base method. +func (m *MockNetworkFilterHandle) GetRequestedServerName() (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRequestedServerName") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetRequestedServerName indicates an expected call of GetRequestedServerName. +func (mr *MockNetworkFilterHandleMockRecorder) GetRequestedServerName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRequestedServerName", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetRequestedServerName)) +} + +// GetSSLDNSSANs mocks base method. +func (m *MockNetworkFilterHandle) GetSSLDNSSANs() []shared.UnsafeEnvoyBuffer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSSLDNSSANs") + ret0, _ := ret[0].([]shared.UnsafeEnvoyBuffer) + return ret0 +} + +// GetSSLDNSSANs indicates an expected call of GetSSLDNSSANs. +func (mr *MockNetworkFilterHandleMockRecorder) GetSSLDNSSANs() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSSLDNSSANs", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetSSLDNSSANs)) +} + +// GetSSLSubject mocks base method. +func (m *MockNetworkFilterHandle) GetSSLSubject() (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSSLSubject") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetSSLSubject indicates an expected call of GetSSLSubject. +func (mr *MockNetworkFilterHandleMockRecorder) GetSSLSubject() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSSLSubject", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetSSLSubject)) +} + +// GetSSLURISANs mocks base method. +func (m *MockNetworkFilterHandle) GetSSLURISANs() []shared.UnsafeEnvoyBuffer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSSLURISANs") + ret0, _ := ret[0].([]shared.UnsafeEnvoyBuffer) + return ret0 +} + +// GetSSLURISANs indicates an expected call of GetSSLURISANs. +func (mr *MockNetworkFilterHandleMockRecorder) GetSSLURISANs() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSSLURISANs", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetSSLURISANs)) +} + +// GetScheduler mocks base method. +func (m *MockNetworkFilterHandle) GetScheduler() shared.Scheduler { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetScheduler") + ret0, _ := ret[0].(shared.Scheduler) + return ret0 +} + +// GetScheduler indicates an expected call of GetScheduler. +func (mr *MockNetworkFilterHandleMockRecorder) GetScheduler() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetScheduler", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetScheduler)) +} + +// GetSocketOptionBytes mocks base method. +func (m *MockNetworkFilterHandle) GetSocketOptionBytes(level, name int64, state shared.SocketOptionState) (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSocketOptionBytes", level, name, state) + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetSocketOptionBytes indicates an expected call of GetSocketOptionBytes. +func (mr *MockNetworkFilterHandleMockRecorder) GetSocketOptionBytes(level, name, state any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSocketOptionBytes", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetSocketOptionBytes), level, name, state) +} + +// GetSocketOptionInt mocks base method. +func (m *MockNetworkFilterHandle) GetSocketOptionInt(level, name int64, state shared.SocketOptionState) (int64, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSocketOptionInt", level, name, state) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetSocketOptionInt indicates an expected call of GetSocketOptionInt. +func (mr *MockNetworkFilterHandleMockRecorder) GetSocketOptionInt(level, name, state any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSocketOptionInt", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetSocketOptionInt), level, name, state) +} + +// GetSocketOptions mocks base method. +func (m *MockNetworkFilterHandle) GetSocketOptions() []shared.SocketOption { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSocketOptions") + ret0, _ := ret[0].([]shared.SocketOption) + return ret0 +} + +// GetSocketOptions indicates an expected call of GetSocketOptions. +func (mr *MockNetworkFilterHandleMockRecorder) GetSocketOptions() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSocketOptions", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetSocketOptions)) +} + +// GetUpstreamHostAddress mocks base method. +func (m *MockNetworkFilterHandle) GetUpstreamHostAddress() (shared.UnsafeEnvoyBuffer, uint32, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUpstreamHostAddress") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(uint32) + ret2, _ := ret[2].(bool) + return ret0, ret1, ret2 +} + +// GetUpstreamHostAddress indicates an expected call of GetUpstreamHostAddress. +func (mr *MockNetworkFilterHandleMockRecorder) GetUpstreamHostAddress() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUpstreamHostAddress", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetUpstreamHostAddress)) +} + +// GetUpstreamHostCluster mocks base method. +func (m *MockNetworkFilterHandle) GetUpstreamHostCluster() (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUpstreamHostCluster") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetUpstreamHostCluster indicates an expected call of GetUpstreamHostCluster. +func (mr *MockNetworkFilterHandleMockRecorder) GetUpstreamHostCluster() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUpstreamHostCluster", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetUpstreamHostCluster)) +} + +// GetUpstreamHostHostname mocks base method. +func (m *MockNetworkFilterHandle) GetUpstreamHostHostname() (shared.UnsafeEnvoyBuffer, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUpstreamHostHostname") + ret0, _ := ret[0].(shared.UnsafeEnvoyBuffer) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetUpstreamHostHostname indicates an expected call of GetUpstreamHostHostname. +func (mr *MockNetworkFilterHandleMockRecorder) GetUpstreamHostHostname() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUpstreamHostHostname", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetUpstreamHostHostname)) +} + +// GetWorkerIndex mocks base method. +func (m *MockNetworkFilterHandle) GetWorkerIndex() uint32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetWorkerIndex") + ret0, _ := ret[0].(uint32) + return ret0 +} + +// GetWorkerIndex indicates an expected call of GetWorkerIndex. +func (mr *MockNetworkFilterHandleMockRecorder) GetWorkerIndex() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkerIndex", reflect.TypeOf((*MockNetworkFilterHandle)(nil).GetWorkerIndex)) +} + +// HasUpstreamHost mocks base method. +func (m *MockNetworkFilterHandle) HasUpstreamHost() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasUpstreamHost") + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasUpstreamHost indicates an expected call of HasUpstreamHost. +func (mr *MockNetworkFilterHandleMockRecorder) HasUpstreamHost() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasUpstreamHost", reflect.TypeOf((*MockNetworkFilterHandle)(nil).HasUpstreamHost)) +} + +// HttpCallout mocks base method. +func (m *MockNetworkFilterHandle) HttpCallout(cluster string, headers [][2]string, body []byte, timeoutMs uint64, cb shared.HttpCalloutCallback) (shared.HttpCalloutInitResult, uint64) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HttpCallout", cluster, headers, body, timeoutMs, cb) + ret0, _ := ret[0].(shared.HttpCalloutInitResult) + ret1, _ := ret[1].(uint64) + return ret0, ret1 +} + +// HttpCallout indicates an expected call of HttpCallout. +func (mr *MockNetworkFilterHandleMockRecorder) HttpCallout(cluster, headers, body, timeoutMs, cb any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HttpCallout", reflect.TypeOf((*MockNetworkFilterHandle)(nil).HttpCallout), cluster, headers, body, timeoutMs, cb) +} + +// IncrementCounterValue mocks base method. +func (m *MockNetworkFilterHandle) IncrementCounterValue(id shared.MetricID, value uint64) shared.MetricsResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IncrementCounterValue", id, value) + ret0, _ := ret[0].(shared.MetricsResult) + return ret0 +} + +// IncrementCounterValue indicates an expected call of IncrementCounterValue. +func (mr *MockNetworkFilterHandleMockRecorder) IncrementCounterValue(id, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncrementCounterValue", reflect.TypeOf((*MockNetworkFilterHandle)(nil).IncrementCounterValue), id, value) +} + +// IncrementGaugeValue mocks base method. +func (m *MockNetworkFilterHandle) IncrementGaugeValue(id shared.MetricID, value uint64) shared.MetricsResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IncrementGaugeValue", id, value) + ret0, _ := ret[0].(shared.MetricsResult) + return ret0 +} + +// IncrementGaugeValue indicates an expected call of IncrementGaugeValue. +func (mr *MockNetworkFilterHandleMockRecorder) IncrementGaugeValue(id, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncrementGaugeValue", reflect.TypeOf((*MockNetworkFilterHandle)(nil).IncrementGaugeValue), id, value) +} + +// InjectReadData mocks base method. +func (m *MockNetworkFilterHandle) InjectReadData(data []byte, endStream bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "InjectReadData", data, endStream) +} + +// InjectReadData indicates an expected call of InjectReadData. +func (mr *MockNetworkFilterHandleMockRecorder) InjectReadData(data, endStream any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InjectReadData", reflect.TypeOf((*MockNetworkFilterHandle)(nil).InjectReadData), data, endStream) +} + +// InjectWriteData mocks base method. +func (m *MockNetworkFilterHandle) InjectWriteData(data []byte, endStream bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "InjectWriteData", data, endStream) +} + +// InjectWriteData indicates an expected call of InjectWriteData. +func (mr *MockNetworkFilterHandleMockRecorder) InjectWriteData(data, endStream any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InjectWriteData", reflect.TypeOf((*MockNetworkFilterHandle)(nil).InjectWriteData), data, endStream) +} + +// IsHalfCloseEnabled mocks base method. +func (m *MockNetworkFilterHandle) IsHalfCloseEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsHalfCloseEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsHalfCloseEnabled indicates an expected call of IsHalfCloseEnabled. +func (mr *MockNetworkFilterHandleMockRecorder) IsHalfCloseEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsHalfCloseEnabled", reflect.TypeOf((*MockNetworkFilterHandle)(nil).IsHalfCloseEnabled)) +} + +// IsSSL mocks base method. +func (m *MockNetworkFilterHandle) IsSSL() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsSSL") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsSSL indicates an expected call of IsSSL. +func (mr *MockNetworkFilterHandleMockRecorder) IsSSL() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSSL", reflect.TypeOf((*MockNetworkFilterHandle)(nil).IsSSL)) +} + +// Log mocks base method. +func (m *MockNetworkFilterHandle) Log(level shared.LogLevel, format string, args ...any) { + m.ctrl.T.Helper() + varargs := []any{level, format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Log", varargs...) +} + +// Log indicates an expected call of Log. +func (mr *MockNetworkFilterHandleMockRecorder) Log(level, format any, args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{level, format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log", reflect.TypeOf((*MockNetworkFilterHandle)(nil).Log), varargs...) +} + +// ReadBuffer mocks base method. +func (m *MockNetworkFilterHandle) ReadBuffer() shared.NetworkBuffer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadBuffer") + ret0, _ := ret[0].(shared.NetworkBuffer) + return ret0 +} + +// ReadBuffer indicates an expected call of ReadBuffer. +func (mr *MockNetworkFilterHandleMockRecorder) ReadBuffer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadBuffer", reflect.TypeOf((*MockNetworkFilterHandle)(nil).ReadBuffer)) +} + +// ReadDisable mocks base method. +func (m *MockNetworkFilterHandle) ReadDisable(disable bool) shared.NetworkReadDisableStatus { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadDisable", disable) + ret0, _ := ret[0].(shared.NetworkReadDisableStatus) + return ret0 +} + +// ReadDisable indicates an expected call of ReadDisable. +func (mr *MockNetworkFilterHandleMockRecorder) ReadDisable(disable any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadDisable", reflect.TypeOf((*MockNetworkFilterHandle)(nil).ReadDisable), disable) +} + +// ReadEnabled mocks base method. +func (m *MockNetworkFilterHandle) ReadEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// ReadEnabled indicates an expected call of ReadEnabled. +func (mr *MockNetworkFilterHandleMockRecorder) ReadEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadEnabled", reflect.TypeOf((*MockNetworkFilterHandle)(nil).ReadEnabled)) +} + +// RecordHistogramValue mocks base method. +func (m *MockNetworkFilterHandle) RecordHistogramValue(id shared.MetricID, value uint64) shared.MetricsResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RecordHistogramValue", id, value) + ret0, _ := ret[0].(shared.MetricsResult) + return ret0 +} + +// RecordHistogramValue indicates an expected call of RecordHistogramValue. +func (mr *MockNetworkFilterHandleMockRecorder) RecordHistogramValue(id, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordHistogramValue", reflect.TypeOf((*MockNetworkFilterHandle)(nil).RecordHistogramValue), id, value) +} + +// SetBufferLimits mocks base method. +func (m *MockNetworkFilterHandle) SetBufferLimits(limit uint32) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetBufferLimits", limit) +} + +// SetBufferLimits indicates an expected call of SetBufferLimits. +func (mr *MockNetworkFilterHandleMockRecorder) SetBufferLimits(limit any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBufferLimits", reflect.TypeOf((*MockNetworkFilterHandle)(nil).SetBufferLimits), limit) +} + +// SetFilterState mocks base method. +func (m *MockNetworkFilterHandle) SetFilterState(key string, value []byte) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetFilterState", key, value) + ret0, _ := ret[0].(bool) + return ret0 +} + +// SetFilterState indicates an expected call of SetFilterState. +func (mr *MockNetworkFilterHandleMockRecorder) SetFilterState(key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFilterState", reflect.TypeOf((*MockNetworkFilterHandle)(nil).SetFilterState), key, value) +} + +// SetFilterStateTyped mocks base method. +func (m *MockNetworkFilterHandle) SetFilterStateTyped(key string, value []byte) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetFilterStateTyped", key, value) + ret0, _ := ret[0].(bool) + return ret0 +} + +// SetFilterStateTyped indicates an expected call of SetFilterStateTyped. +func (mr *MockNetworkFilterHandleMockRecorder) SetFilterStateTyped(key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFilterStateTyped", reflect.TypeOf((*MockNetworkFilterHandle)(nil).SetFilterStateTyped), key, value) +} + +// SetGaugeValue mocks base method. +func (m *MockNetworkFilterHandle) SetGaugeValue(id shared.MetricID, value uint64) shared.MetricsResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetGaugeValue", id, value) + ret0, _ := ret[0].(shared.MetricsResult) + return ret0 +} + +// SetGaugeValue indicates an expected call of SetGaugeValue. +func (mr *MockNetworkFilterHandleMockRecorder) SetGaugeValue(id, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetGaugeValue", reflect.TypeOf((*MockNetworkFilterHandle)(nil).SetGaugeValue), id, value) +} + +// SetMetadata mocks base method. +func (m *MockNetworkFilterHandle) SetMetadata(metadataNamespace, key string, value any) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetMetadata", metadataNamespace, key, value) +} + +// SetMetadata indicates an expected call of SetMetadata. +func (mr *MockNetworkFilterHandleMockRecorder) SetMetadata(metadataNamespace, key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMetadata", reflect.TypeOf((*MockNetworkFilterHandle)(nil).SetMetadata), metadataNamespace, key, value) +} + +// SetSocketOptionBytes mocks base method. +func (m *MockNetworkFilterHandle) SetSocketOptionBytes(level, name int64, state shared.SocketOptionState, value []byte) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSocketOptionBytes", level, name, state, value) +} + +// SetSocketOptionBytes indicates an expected call of SetSocketOptionBytes. +func (mr *MockNetworkFilterHandleMockRecorder) SetSocketOptionBytes(level, name, state, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSocketOptionBytes", reflect.TypeOf((*MockNetworkFilterHandle)(nil).SetSocketOptionBytes), level, name, state, value) +} + +// SetSocketOptionInt mocks base method. +func (m *MockNetworkFilterHandle) SetSocketOptionInt(level, name int64, state shared.SocketOptionState, value int64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSocketOptionInt", level, name, state, value) +} + +// SetSocketOptionInt indicates an expected call of SetSocketOptionInt. +func (mr *MockNetworkFilterHandleMockRecorder) SetSocketOptionInt(level, name, state, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSocketOptionInt", reflect.TypeOf((*MockNetworkFilterHandle)(nil).SetSocketOptionInt), level, name, state, value) +} + +// StartUpstreamSecureTransport mocks base method. +func (m *MockNetworkFilterHandle) StartUpstreamSecureTransport() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StartUpstreamSecureTransport") + ret0, _ := ret[0].(bool) + return ret0 +} + +// StartUpstreamSecureTransport indicates an expected call of StartUpstreamSecureTransport. +func (mr *MockNetworkFilterHandleMockRecorder) StartUpstreamSecureTransport() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartUpstreamSecureTransport", reflect.TypeOf((*MockNetworkFilterHandle)(nil).StartUpstreamSecureTransport)) +} + +// Write mocks base method. +func (m *MockNetworkFilterHandle) Write(data []byte, endStream bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Write", data, endStream) +} + +// Write indicates an expected call of Write. +func (mr *MockNetworkFilterHandleMockRecorder) Write(data, endStream any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockNetworkFilterHandle)(nil).Write), data, endStream) +} + +// WriteBuffer mocks base method. +func (m *MockNetworkFilterHandle) WriteBuffer() shared.NetworkBuffer { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteBuffer") + ret0, _ := ret[0].(shared.NetworkBuffer) + return ret0 +} + +// WriteBuffer indicates an expected call of WriteBuffer. +func (mr *MockNetworkFilterHandleMockRecorder) WriteBuffer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteBuffer", reflect.TypeOf((*MockNetworkFilterHandle)(nil).WriteBuffer)) +} + +// MockNetworkFilterConfigHandle is a mock of NetworkFilterConfigHandle interface. +type MockNetworkFilterConfigHandle struct { + ctrl *gomock.Controller + recorder *MockNetworkFilterConfigHandleMockRecorder + isgomock struct{} +} + +// MockNetworkFilterConfigHandleMockRecorder is the mock recorder for MockNetworkFilterConfigHandle. +type MockNetworkFilterConfigHandleMockRecorder struct { + mock *MockNetworkFilterConfigHandle +} + +// NewMockNetworkFilterConfigHandle creates a new mock instance. +func NewMockNetworkFilterConfigHandle(ctrl *gomock.Controller) *MockNetworkFilterConfigHandle { + mock := &MockNetworkFilterConfigHandle{ctrl: ctrl} + mock.recorder = &MockNetworkFilterConfigHandleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetworkFilterConfigHandle) EXPECT() *MockNetworkFilterConfigHandleMockRecorder { + return m.recorder +} + +// DefineCounter mocks base method. +func (m *MockNetworkFilterConfigHandle) DefineCounter(name string) (shared.MetricID, shared.MetricsResult) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DefineCounter", name) + ret0, _ := ret[0].(shared.MetricID) + ret1, _ := ret[1].(shared.MetricsResult) + return ret0, ret1 +} + +// DefineCounter indicates an expected call of DefineCounter. +func (mr *MockNetworkFilterConfigHandleMockRecorder) DefineCounter(name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefineCounter", reflect.TypeOf((*MockNetworkFilterConfigHandle)(nil).DefineCounter), name) +} + +// DefineGauge mocks base method. +func (m *MockNetworkFilterConfigHandle) DefineGauge(name string) (shared.MetricID, shared.MetricsResult) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DefineGauge", name) + ret0, _ := ret[0].(shared.MetricID) + ret1, _ := ret[1].(shared.MetricsResult) + return ret0, ret1 +} + +// DefineGauge indicates an expected call of DefineGauge. +func (mr *MockNetworkFilterConfigHandleMockRecorder) DefineGauge(name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefineGauge", reflect.TypeOf((*MockNetworkFilterConfigHandle)(nil).DefineGauge), name) +} + +// DefineHistogram mocks base method. +func (m *MockNetworkFilterConfigHandle) DefineHistogram(name string) (shared.MetricID, shared.MetricsResult) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DefineHistogram", name) + ret0, _ := ret[0].(shared.MetricID) + ret1, _ := ret[1].(shared.MetricsResult) + return ret0, ret1 +} + +// DefineHistogram indicates an expected call of DefineHistogram. +func (mr *MockNetworkFilterConfigHandleMockRecorder) DefineHistogram(name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefineHistogram", reflect.TypeOf((*MockNetworkFilterConfigHandle)(nil).DefineHistogram), name) +} + +// GetScheduler mocks base method. +func (m *MockNetworkFilterConfigHandle) GetScheduler() shared.Scheduler { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetScheduler") + ret0, _ := ret[0].(shared.Scheduler) + return ret0 +} + +// GetScheduler indicates an expected call of GetScheduler. +func (mr *MockNetworkFilterConfigHandleMockRecorder) GetScheduler() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetScheduler", reflect.TypeOf((*MockNetworkFilterConfigHandle)(nil).GetScheduler)) +} + +// Log mocks base method. +func (m *MockNetworkFilterConfigHandle) Log(level shared.LogLevel, format string, args ...any) { + m.ctrl.T.Helper() + varargs := []any{level, format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Log", varargs...) +} + +// Log indicates an expected call of Log. +func (mr *MockNetworkFilterConfigHandleMockRecorder) Log(level, format any, args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{level, format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log", reflect.TypeOf((*MockNetworkFilterConfigHandle)(nil).Log), varargs...) +} diff --git a/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_types.go b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_types.go new file mode 100644 index 0000000000000..67d300e5ba4d7 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/go/shared/mocks/mock_types.go @@ -0,0 +1,173 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: types.go +// +// Generated by this command: +// +// mockgen -source=types.go -destination=mocks/mock_types.go -package=mocks +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + shared "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/shared" + gomock "go.uber.org/mock/gomock" +) + +// MockHttpCalloutCallback is a mock of HttpCalloutCallback interface. +type MockHttpCalloutCallback struct { + ctrl *gomock.Controller + recorder *MockHttpCalloutCallbackMockRecorder + isgomock struct{} +} + +// MockHttpCalloutCallbackMockRecorder is the mock recorder for MockHttpCalloutCallback. +type MockHttpCalloutCallbackMockRecorder struct { + mock *MockHttpCalloutCallback +} + +// NewMockHttpCalloutCallback creates a new mock instance. +func NewMockHttpCalloutCallback(ctrl *gomock.Controller) *MockHttpCalloutCallback { + mock := &MockHttpCalloutCallback{ctrl: ctrl} + mock.recorder = &MockHttpCalloutCallbackMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHttpCalloutCallback) EXPECT() *MockHttpCalloutCallbackMockRecorder { + return m.recorder +} + +// OnHttpCalloutDone mocks base method. +func (m *MockHttpCalloutCallback) OnHttpCalloutDone(calloutID uint64, result shared.HttpCalloutResult, headers [][2]shared.UnsafeEnvoyBuffer, body []shared.UnsafeEnvoyBuffer) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnHttpCalloutDone", calloutID, result, headers, body) +} + +// OnHttpCalloutDone indicates an expected call of OnHttpCalloutDone. +func (mr *MockHttpCalloutCallbackMockRecorder) OnHttpCalloutDone(calloutID, result, headers, body any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpCalloutDone", reflect.TypeOf((*MockHttpCalloutCallback)(nil).OnHttpCalloutDone), calloutID, result, headers, body) +} + +// MockHttpStreamCallback is a mock of HttpStreamCallback interface. +type MockHttpStreamCallback struct { + ctrl *gomock.Controller + recorder *MockHttpStreamCallbackMockRecorder + isgomock struct{} +} + +// MockHttpStreamCallbackMockRecorder is the mock recorder for MockHttpStreamCallback. +type MockHttpStreamCallbackMockRecorder struct { + mock *MockHttpStreamCallback +} + +// NewMockHttpStreamCallback creates a new mock instance. +func NewMockHttpStreamCallback(ctrl *gomock.Controller) *MockHttpStreamCallback { + mock := &MockHttpStreamCallback{ctrl: ctrl} + mock.recorder = &MockHttpStreamCallbackMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHttpStreamCallback) EXPECT() *MockHttpStreamCallbackMockRecorder { + return m.recorder +} + +// OnHttpStreamComplete mocks base method. +func (m *MockHttpStreamCallback) OnHttpStreamComplete(streamID uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnHttpStreamComplete", streamID) +} + +// OnHttpStreamComplete indicates an expected call of OnHttpStreamComplete. +func (mr *MockHttpStreamCallbackMockRecorder) OnHttpStreamComplete(streamID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpStreamComplete", reflect.TypeOf((*MockHttpStreamCallback)(nil).OnHttpStreamComplete), streamID) +} + +// OnHttpStreamData mocks base method. +func (m *MockHttpStreamCallback) OnHttpStreamData(streamID uint64, body []shared.UnsafeEnvoyBuffer, endStream bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnHttpStreamData", streamID, body, endStream) +} + +// OnHttpStreamData indicates an expected call of OnHttpStreamData. +func (mr *MockHttpStreamCallbackMockRecorder) OnHttpStreamData(streamID, body, endStream any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpStreamData", reflect.TypeOf((*MockHttpStreamCallback)(nil).OnHttpStreamData), streamID, body, endStream) +} + +// OnHttpStreamHeaders mocks base method. +func (m *MockHttpStreamCallback) OnHttpStreamHeaders(streamID uint64, headers [][2]shared.UnsafeEnvoyBuffer, endStream bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnHttpStreamHeaders", streamID, headers, endStream) +} + +// OnHttpStreamHeaders indicates an expected call of OnHttpStreamHeaders. +func (mr *MockHttpStreamCallbackMockRecorder) OnHttpStreamHeaders(streamID, headers, endStream any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpStreamHeaders", reflect.TypeOf((*MockHttpStreamCallback)(nil).OnHttpStreamHeaders), streamID, headers, endStream) +} + +// OnHttpStreamReset mocks base method. +func (m *MockHttpStreamCallback) OnHttpStreamReset(streamID uint64, reason shared.HttpStreamResetReason) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnHttpStreamReset", streamID, reason) +} + +// OnHttpStreamReset indicates an expected call of OnHttpStreamReset. +func (mr *MockHttpStreamCallbackMockRecorder) OnHttpStreamReset(streamID, reason any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpStreamReset", reflect.TypeOf((*MockHttpStreamCallback)(nil).OnHttpStreamReset), streamID, reason) +} + +// OnHttpStreamTrailers mocks base method. +func (m *MockHttpStreamCallback) OnHttpStreamTrailers(streamID uint64, trailers [][2]shared.UnsafeEnvoyBuffer) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnHttpStreamTrailers", streamID, trailers) +} + +// OnHttpStreamTrailers indicates an expected call of OnHttpStreamTrailers. +func (mr *MockHttpStreamCallbackMockRecorder) OnHttpStreamTrailers(streamID, trailers any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnHttpStreamTrailers", reflect.TypeOf((*MockHttpStreamCallback)(nil).OnHttpStreamTrailers), streamID, trailers) +} + +// MockScheduler is a mock of Scheduler interface. +type MockScheduler struct { + ctrl *gomock.Controller + recorder *MockSchedulerMockRecorder + isgomock struct{} +} + +// MockSchedulerMockRecorder is the mock recorder for MockScheduler. +type MockSchedulerMockRecorder struct { + mock *MockScheduler +} + +// NewMockScheduler creates a new mock instance. +func NewMockScheduler(ctrl *gomock.Controller) *MockScheduler { + mock := &MockScheduler{ctrl: ctrl} + mock.recorder = &MockSchedulerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockScheduler) EXPECT() *MockSchedulerMockRecorder { + return m.recorder +} + +// Schedule mocks base method. +func (m *MockScheduler) Schedule(arg0 func()) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Schedule", arg0) +} + +// Schedule indicates an expected call of Schedule. +func (mr *MockSchedulerMockRecorder) Schedule(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Schedule", reflect.TypeOf((*MockScheduler)(nil).Schedule), arg0) +} diff --git a/source/extensions/dynamic_modules/sdk/go/shared/network_api.go b/source/extensions/dynamic_modules/sdk/go/shared/network_api.go index 4d8d696973520..3d6342ccf5ea3 100644 --- a/source/extensions/dynamic_modules/sdk/go/shared/network_api.go +++ b/source/extensions/dynamic_modules/sdk/go/shared/network_api.go @@ -1,3 +1,4 @@ +//go:generate mockgen -source=network_api.go -destination=mocks/mock_network_api.go -package=mocks package shared // NetworkFilter is the interface to implement your own network filter logic. diff --git a/source/extensions/dynamic_modules/sdk/go/shared/network_base.go b/source/extensions/dynamic_modules/sdk/go/shared/network_base.go index 7133012e4fda3..018a22ed5a292 100644 --- a/source/extensions/dynamic_modules/sdk/go/shared/network_base.go +++ b/source/extensions/dynamic_modules/sdk/go/shared/network_base.go @@ -1,3 +1,4 @@ +//go:generate mockgen -source=network_base.go -destination=mocks/mock_network_base.go -package=mocks package shared // NetworkBuffer is an interface that provides access to the read and write buffers of a network diff --git a/source/extensions/dynamic_modules/sdk/go/shared/types.go b/source/extensions/dynamic_modules/sdk/go/shared/types.go new file mode 100644 index 0000000000000..da5927fcdac32 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/go/shared/types.go @@ -0,0 +1,336 @@ +//go:generate mockgen -source=types.go -destination=mocks/mock_types.go -package=mocks +package shared + +import ( + "strings" + "unsafe" +) + +// Cross-surface types and interfaces shared by multiple SDK surfaces (HTTP filter, network +// filter, listener filter, access logger, etc.). Surface-specific types live in their own +// files (http.go for HTTP, network.go for network, and so on). + +// UnsafeEnvoyBuffer is a struct that represents a buffer of data from Envoy. +// It contains a pointer to the data and its length. The memory of the data is managed by Envoy. +type UnsafeEnvoyBuffer struct { + // Pointer to the start of the buffer data. + Ptr *byte + // Length of the buffer data in bytes. + Len uint64 +} + +func (b UnsafeEnvoyBuffer) ToUnsafeBytes() []byte { + if b.Ptr == nil || b.Len == 0 { + return nil + } + // Use unsafe to create a byte slice that points to the buffer data without copying. + return unsafe.Slice(b.Ptr, b.Len) +} + +// ToBytes converts the UnsafeEnvoyBuffer to a byte slice. It creates a copy of the data in Go memory. +func (b UnsafeEnvoyBuffer) ToBytes() []byte { + if b.Ptr == nil || b.Len == 0 { + return nil + } + // Create a byte slice that copys the data from the buffer. + owned := make([]byte, b.Len) + // Use unsafe to copy the data from the buffer to the byte slice. + src := unsafe.Slice(b.Ptr, b.Len) + copy(owned, src) + return owned +} + +func (b UnsafeEnvoyBuffer) ToUnsafeString() string { + if b.Ptr == nil || b.Len == 0 { + return "" + } + // Use unsafe to create a string that points to the buffer data without copying. + return unsafe.String(b.Ptr, b.Len) +} + +func (b UnsafeEnvoyBuffer) ToString() string { + if b.Ptr == nil || b.Len == 0 { + return "" + } + return strings.Clone(b.ToUnsafeString()) +} + +// AttributeID identifies an attribute of the current request, response, connection, or upstream +// host that can be retrieved via Get{Attribute,...} family of methods. Corresponds to +// envoy_dynamic_module_type_attribute_id. +type AttributeID uint32 + +const ( + // request.path + AttributeIDRequestPath AttributeID = iota + // request.url_path + AttributeIDRequestUrlPath + // request.host + AttributeIDRequestHost + // request.scheme + AttributeIDRequestScheme + // request.method + AttributeIDRequestMethod + // request.headers + AttributeIDRequestHeaders + // request.referer + AttributeIDRequestReferer + // request.useragent + AttributeIDRequestUserAgent + // request.time + AttributeIDRequestTime + // request.id + AttributeIDRequestId + // request.protocol + AttributeIDRequestProtocol + // request.query + AttributeIDRequestQuery + // request.duration + AttributeIDRequestDuration + // request.size + AttributeIDRequestSize + // request.total_size + AttributeIDRequestTotalSize + // response.code + AttributeIDResponseCode + // response.code_details + AttributeIDResponseCodeDetails + // response.flags + AttributeIDResponseFlags + // response.grpc_status + AttributeIDResponseGrpcStatus + // response.headers + AttributeIDResponseHeaders + // response.trailers + AttributeIDResponseTrailers + // response.size + AttributeIDResponseSize + // response.total_size + AttributeIDResponseTotalSize + // response.backend_latency + AttributeIDResponseBackendLatency + // source.address + AttributeIDSourceAddress + // source.port + AttributeIDSourcePort + // destination.address + AttributeIDDestinationAddress + // destination.port + AttributeIDDestinationPort + // connection.id + AttributeIDConnectionId + // connection.mtls + AttributeIDConnectionMTLS + // connection.requested_server_name + AttributeIDConnectionRequestedServerName + // connection.tls_version + AttributeIDConnectionTLSVersion + // connection.subject_local_certificate + AttributeIDConnectionSubjectLocalCertificate + // connection.subject_peer_certificate + AttributeIDConnectionSubjectPeerCertificate + // connection.dns_san_local_certificate + AttributeIDConnectionDNSSanLocalCertificate + // connection.dns_san_peer_certificate + AttributeIDConnectionDNSSanPeerCertificate + // connection.uri_san_local_certificate + AttributeIDConnectionURISanLocalCertificate + // connection.uri_san_peer_certificate + AttributeIDConnectionURISanPeerCertificate + // connection.sha256_peer_certificate_digest + AttributeIDConnectionSha256PeerCertificateDigest + // connection.transport_failure_reason + AttributeIDConnectionTransportFailureReason + // connection.termination_details + AttributeIDConnectionTerminationDetails + // upstream.address + AttributeIDUpstreamAddress + // upstream.port + AttributeIDUpstreamPort + // upstream.tls_version + AttributeIDUpstreamTLSVersion + // upstream.subject_local_certificate + AttributeIDUpstreamSubjectLocalCertificate + // upstream.subject_peer_certificate + AttributeIDUpstreamSubjectPeerCertificate + // upstream.dns_san_local_certificate + AttributeIDUpstreamDNSSanLocalCertificate + // upstream.dns_san_peer_certificate + AttributeIDUpstreamDNSSanPeerCertificate + // upstream.uri_san_local_certificate + AttributeIDUpstreamURISanLocalCertificate + // upstream.uri_san_peer_certificate + AttributeIDUpstreamURISanPeerCertificate + // upstream.sha256_peer_certificate_digest + AttributeIDUpstreamSha256PeerCertificateDigest + // upstream.local_address + AttributeIDUpstreamLocalAddress + // upstream.transport_failure_reason + AttributeIDUpstreamTransportFailureReason + // upstream.request_attempt_count + AttributeIDUpstreamRequestAttemptCount + // upstream.cx_pool_ready_duration + AttributeIDUpstreamCxPoolReadyDuration + // upstream.locality + AttributeIDUpstreamLocality + // xds.node + AttributeIDXdsNode + // xds.cluster_name + AttributeIDXdsClusterName + // xds.cluster_metadata + AttributeIDXdsClusterMetadata + // xds.listener_direction + AttributeIDXdsListenerDirection + // xds.listener_metadata + AttributeIDXdsListenerMetadata + // xds.route_name + AttributeIDXdsRouteName + // xds.route_metadata + AttributeIDXdsRouteMetadata + // xds.virtual_host_name + AttributeIDXdsVirtualHostName + // xds.virtual_host_metadata + AttributeIDXdsVirtualHostMetadata + // xds.upstream_host_metadata + AttributeIDXdsUpstreamHostMetadata + // xds.filter_chain_name + AttributeIDXdsFilterChainName + // health_check + AttributeIDHealthCheck +) + +// LogLevel is the log level for messages logged via the host environment's logging mechanism. +type LogLevel uint32 + +const ( + LogLevelTrace LogLevel = iota + LogLevelDebug + LogLevelInfo + LogLevelWarn + LogLevelError + LogLevelCritical + LogLevelOff +) + +// HttpCalloutInitResult is the result of initializing an HTTP callout or stream. +type HttpCalloutInitResult uint32 + +const ( + HttpCalloutInitSuccess HttpCalloutInitResult = iota + HttpCalloutInitMissingRequiredHeaders + HttpCalloutInitClusterNotFound + HttpCalloutInitDuplicateCalloutId + HttpCalloutInitCannotCreateRequest +) + +// HttpCalloutResult is the result of a completed HTTP callout (delivered via +// HttpCalloutCallback.OnHttpCalloutDone). +type HttpCalloutResult uint32 + +const ( + HttpCalloutSuccess HttpCalloutResult = iota + HttpCalloutReset + HttpCalloutExceedResponseBufferLimit +) + +// HttpCalloutCallback is the callback interface invoked when an HTTP callout completes. +type HttpCalloutCallback interface { + OnHttpCalloutDone(calloutID uint64, result HttpCalloutResult, + headers [][2]UnsafeEnvoyBuffer, body []UnsafeEnvoyBuffer) +} + +// HttpStreamResetReason is the reason that an HTTP stream (started via StartHttpStream) +// was reset. +type HttpStreamResetReason uint32 + +const ( + HttpStreamResetReasonConnectionFailure HttpStreamResetReason = iota + HttpStreamResetReasonConnectionTermination + HttpStreamResetReasonLocalReset + HttpStreamResetReasonLocalRefusedStreamReset + HttpStreamResetReasonOverflow + HttpStreamResetReasonRemoteReset + HttpStreamResetReasonRemoteRefusedStreamReset + HttpStreamResetReasonProtocolError +) + +// HttpStreamCallback is the callback interface invoked for events on an HTTP stream started +// via StartHttpStream. +type HttpStreamCallback interface { + OnHttpStreamHeaders(streamID uint64, headers [][2]UnsafeEnvoyBuffer, endStream bool) + OnHttpStreamData(streamID uint64, body []UnsafeEnvoyBuffer, endStream bool) + OnHttpStreamTrailers(streamID uint64, trailers [][2]UnsafeEnvoyBuffer) + OnHttpStreamComplete(streamID uint64) + OnHttpStreamReset(streamID uint64, reason HttpStreamResetReason) +} + +// Scheduler is the interface that provides scheduling capabilities for asynchronous operations. +// This allow the plugins run tasks in another thread and continue the processing later at the +// thread where the stream plugin is being processed. +type Scheduler interface { + // Schedule schedules a function to be executed asynchronously in the thread where the stream + // plugin is being processed. + // + // NOTE: The function may be ignored if the related plugin processing is completed. + Schedule(func()) +} + +// SocketOptionState represents the socket state at which an option should be applied. +// This corresponds to envoy_dynamic_module_type_socket_option_state in the dynamic module ABI. +type SocketOptionState uint32 + +const ( + // SocketOptionStatePrebind applies the option before the socket is bound. + SocketOptionStatePrebind SocketOptionState = iota + // SocketOptionStateBound applies the option after the socket is bound. + SocketOptionStateBound + // SocketOptionStateListening applies the option after the socket starts listening. + SocketOptionStateListening +) + +// SocketDirection represents whether the socket option should be applied to the upstream +// (outgoing to backend) or downstream (incoming from client) connection. +// This corresponds to envoy_dynamic_module_type_socket_direction in the dynamic module ABI. +type SocketDirection uint32 + +const ( + // SocketDirectionUpstream applies the option to the upstream (outgoing) connection. + SocketDirectionUpstream SocketDirection = iota + // SocketDirectionDownstream applies the option to the downstream (incoming) connection. + SocketDirectionDownstream +) + +// ClusterHostCounts carries the host counts returned by HttpFilterHandle.GetClusterHostCounts. +type ClusterHostCounts struct { + // Total is the total number of hosts in the priority set. + Total uint64 + // Healthy is the number of hosts in the HEALTHY state. + Healthy uint64 + // Degraded is the number of hosts in the DEGRADED state. + Degraded uint64 +} + +// MetricID is an opaque identifier for a metric defined via Define{Counter,Gauge,Histogram}. +type MetricID uint64 + +// MetricsResult is the result of a metric definition or update operation. +type MetricsResult uint32 + +const ( + MetricsSuccess MetricsResult = iota + MetricsNotFound + MetricsInvalidTags + MetricsFrozen +) + +// HttpHeaderType identifies which HTTP header map to access. It corresponds to +// envoy_dynamic_module_type_http_header_type. The values match the ABI's enum order: +// RequestHeader, RequestTrailer, ResponseHeader, ResponseTrailer. +type HttpHeaderType uint32 + +const ( + HttpHeaderTypeRequestHeader HttpHeaderType = 0 + HttpHeaderTypeRequestTrailer HttpHeaderType = 1 + HttpHeaderTypeResponseHeader HttpHeaderType = 2 + HttpHeaderTypeResponseTrailer HttpHeaderType = 3 +) From acb584a453a6b38d70695bfcae0d5196af1cf2d7 Mon Sep 17 00:00:00 2001 From: code Date: Thu, 21 May 2026 13:34:05 +0800 Subject: [PATCH 366/750] lb: unified the lifetime mechanism of worker load lb 2 (#45170) Commit Message: lb: unified the lifetime mechanism of worker load lb 2 Additional Description: Similar to previous https://github.com/envoyproxy/envoy/pull/45100 but this PR have updated all left LB. Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: wbpcode/wangbaiping Signed-off-by: code Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../extensions/clusters/aggregate/cluster.h | 1 + .../extensions/clusters/composite/cluster.h | 1 + .../clusters/dynamic_forward_proxy/cluster.h | 1 + .../clusters/dynamic_modules/cluster.cc | 1 + .../original_dst/original_dst_cluster.h | 15 ++- .../clusters/redis/redis_cluster_lb.cc | 21 ++- .../clusters/redis/redis_cluster_lb.h | 24 ++-- .../reverse_connection/reverse_connection.h | 1 + .../override_host/load_balancer.h | 2 + .../original_dst/original_dst_cluster_test.cc | 124 +++++++++++++----- .../clusters/redis/redis_cluster_lb_test.cc | 59 ++++++++- 11 files changed, 202 insertions(+), 48 deletions(-) diff --git a/source/extensions/clusters/aggregate/cluster.h b/source/extensions/clusters/aggregate/cluster.h index da2061d4b3ea7..a2fd520c1273b 100644 --- a/source/extensions/clusters/aggregate/cluster.h +++ b/source/extensions/clusters/aggregate/cluster.h @@ -153,6 +153,7 @@ class AggregateLoadBalancerFactory : public Upstream::LoadBalancerFactory { cluster_.info(), cluster_.cluster_manager_, cluster_.runtime(), cluster_.random(), cluster_.clusters_); } + bool recreateOnHostChange() const override { return false; } const Cluster& cluster_; }; diff --git a/source/extensions/clusters/composite/cluster.h b/source/extensions/clusters/composite/cluster.h index 34b163c754524..d6d708d0f88f4 100644 --- a/source/extensions/clusters/composite/cluster.h +++ b/source/extensions/clusters/composite/cluster.h @@ -95,6 +95,7 @@ class CompositeLoadBalancerFactory : public Upstream::LoadBalancerFactory { return std::make_unique( cluster_.info(), cluster_.cluster_manager_, cluster_.clusters_); } + bool recreateOnHostChange() const override { return false; } const Cluster& cluster_; }; diff --git a/source/extensions/clusters/dynamic_forward_proxy/cluster.h b/source/extensions/clusters/dynamic_forward_proxy/cluster.h index d5fd9df880932..641dbbd35c7f6 100644 --- a/source/extensions/clusters/dynamic_forward_proxy/cluster.h +++ b/source/extensions/clusters/dynamic_forward_proxy/cluster.h @@ -187,6 +187,7 @@ class Cluster : public Upstream::BaseDynamicClusterImpl, Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams) override { return std::make_unique(cluster_); } + bool recreateOnHostChange() const override { return false; } private: Cluster& cluster_; diff --git a/source/extensions/clusters/dynamic_modules/cluster.cc b/source/extensions/clusters/dynamic_modules/cluster.cc index dd90e7867b8ea..99c5b75714c4d 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.cc +++ b/source/extensions/clusters/dynamic_modules/cluster.cc @@ -35,6 +35,7 @@ struct DynamicModuleThreadAwareLoadBalancer : public Upstream::ThreadAwareLoadBa Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams params) override { return std::make_unique(handle_, params.priority_set); } + bool recreateOnHostChange() const override { return false; } DynamicModuleClusterHandleSharedPtr handle_; }; diff --git a/source/extensions/clusters/original_dst/original_dst_cluster.h b/source/extensions/clusters/original_dst/original_dst_cluster.h index 55905560bdcfd..7b8709b4229f8 100644 --- a/source/extensions/clusters/original_dst/original_dst_cluster.h +++ b/source/extensions/clusters/original_dst/original_dst_cluster.h @@ -88,11 +88,16 @@ class OriginalDstCluster : public ClusterImplBase { */ class LoadBalancer : public Upstream::LoadBalancer { public: - LoadBalancer(const OriginalDstClusterHandleSharedPtr& parent) + LoadBalancer(const OriginalDstClusterHandleSharedPtr& parent, const PrioritySet& priority_set) : parent_(parent), http_header_name_(parent->cluster_->httpHeaderName()), metadata_key_(parent->cluster_->metadataKey()), port_override_(parent->cluster_->portOverride()), - host_map_(parent->cluster_->getCurrentHostMap()) {} + host_map_(parent->cluster_->getCurrentHostMap()) { + member_update_cb_ = + priority_set.addMemberUpdateCb([this](const HostVector&, const HostVector&) { + host_map_ = parent_->cluster_->getCurrentHostMap(); + }); + } // Upstream::LoadBalancer HostSelectionResponse chooseHost(LoadBalancerContext* context) override; @@ -121,6 +126,7 @@ class OriginalDstCluster : public ClusterImplBase { const absl::optional& metadata_key_; const absl::optional port_override_; HostMultiMapConstSharedPtr host_map_; + Common::CallbackHandlePtr member_update_cb_; }; const absl::optional& httpHeaderName() { return http_header_name_; } @@ -139,9 +145,10 @@ class OriginalDstCluster : public ClusterImplBase { LoadBalancerFactory(const OriginalDstClusterHandleSharedPtr& cluster) : cluster_(cluster) {} // Upstream::LoadBalancerFactory - Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams) override { - return std::make_unique(cluster_); + Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams params) override { + return std::make_unique(cluster_, params.priority_set); } + bool recreateOnHostChange() const override { return false; } const OriginalDstClusterHandleSharedPtr cluster_; }; diff --git a/source/extensions/clusters/redis/redis_cluster_lb.cc b/source/extensions/clusters/redis/redis_cluster_lb.cc index 31f5f9fe9e0e8..49818f2d648ce 100644 --- a/source/extensions/clusters/redis/redis_cluster_lb.cc +++ b/source/extensions/clusters/redis/redis_cluster_lb.cc @@ -101,9 +101,24 @@ void RedisClusterLoadBalancerFactory::onHostHealthUpdate() { } } -Upstream::LoadBalancerPtr RedisClusterLoadBalancerFactory::create(Upstream::LoadBalancerParams) { - absl::ReaderMutexLock lock(mutex_); - return std::make_unique(slot_array_, shard_vector_, random_); +Upstream::LoadBalancerPtr +RedisClusterLoadBalancerFactory::create(Upstream::LoadBalancerParams params) { + return std::make_unique(shared_from_this(), params.priority_set); +} + +RedisClusterLoadBalancerFactory::RedisClusterLoadBalancer::RedisClusterLoadBalancer( + std::shared_ptr factory, + const Upstream::PrioritySet& priority_set) + : factory_(factory), random_(factory->random_) { + refresh(); + member_update_cb_ = priority_set.addMemberUpdateCb( + [this](const Upstream::HostVector&, const Upstream::HostVector&) { refresh(); }); +} + +void RedisClusterLoadBalancerFactory::RedisClusterLoadBalancer::refresh() { + absl::ReaderMutexLock lock(factory_->mutex_); + slot_array_ = factory_->slot_array_; + shard_vector_ = factory_->shard_vector_; } namespace { diff --git a/source/extensions/clusters/redis/redis_cluster_lb.h b/source/extensions/clusters/redis/redis_cluster_lb.h index c4d391332f117..54a30fe150654 100644 --- a/source/extensions/clusters/redis/redis_cluster_lb.h +++ b/source/extensions/clusters/redis/redis_cluster_lb.h @@ -175,8 +175,10 @@ using ClusterSlotUpdateCallBackSharedPtr = std::shared_ptr { public: RedisClusterLoadBalancerFactory(Random::RandomGenerator& random) : random_(random) {} @@ -186,7 +188,8 @@ class RedisClusterLoadBalancerFactory : public ClusterSlotUpdateCallBack, void onHostHealthUpdate() override; // Upstream::LoadBalancerFactory - Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams) override; + Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams params) override; + bool recreateOnHostChange() const override { return false; } private: class RedisShard { @@ -267,10 +270,8 @@ class RedisClusterLoadBalancerFactory : public ClusterSlotUpdateCallBack, */ class RedisClusterLoadBalancer : public Upstream::LoadBalancer { public: - RedisClusterLoadBalancer(SlotArraySharedPtr slot_array, ShardVectorSharedPtr shard_vector, - Random::RandomGenerator& random) - : slot_array_(std::move(slot_array)), shard_vector_(std::move(shard_vector)), - random_(random) {} + RedisClusterLoadBalancer(std::shared_ptr factory, + const Upstream::PrioritySet& priority_set); // Upstream::LoadBalancerBase Upstream::HostSelectionResponse chooseHost(Upstream::LoadBalancerContext*) override; @@ -290,9 +291,14 @@ class RedisClusterLoadBalancerFactory : public ClusterSlotUpdateCallBack, } private: - const SlotArraySharedPtr slot_array_; - const ShardVectorSharedPtr shard_vector_; + // Re-snapshots the topology from the parent factory under its mutex. + void refresh(); + + const std::shared_ptr factory_; + SlotArraySharedPtr slot_array_; + ShardVectorSharedPtr shard_vector_; Random::RandomGenerator& random_; + ::Envoy::Common::CallbackHandlePtr member_update_cb_; }; absl::Mutex mutex_; diff --git a/source/extensions/clusters/reverse_connection/reverse_connection.h b/source/extensions/clusters/reverse_connection/reverse_connection.h index 9860cb1d0e658..91064c8b31418 100644 --- a/source/extensions/clusters/reverse_connection/reverse_connection.h +++ b/source/extensions/clusters/reverse_connection/reverse_connection.h @@ -183,6 +183,7 @@ class RevConCluster : public Upstream::ClusterImplBase { // Upstream::LoadBalancerFactory. Upstream::LoadBalancerPtr create() { return std::make_unique(cluster_); } Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams) override { return create(); } + bool recreateOnHostChange() const override { return false; } const std::shared_ptr cluster_; }; diff --git a/source/extensions/load_balancing_policies/override_host/load_balancer.h b/source/extensions/load_balancing_policies/override_host/load_balancer.h index b66630727dc01..491d81e1f720c 100644 --- a/source/extensions/load_balancing_policies/override_host/load_balancer.h +++ b/source/extensions/load_balancing_policies/override_host/load_balancer.h @@ -189,6 +189,8 @@ class OverrideHostLoadBalancer : public Upstream::ThreadAwareLoadBalancer, // Called by worker threads to create a thread-local load balancer. LoadBalancerPtr create(LoadBalancerParams params) override; + bool recreateOnHostChange() const override { return false; } + private: // Hosts in the load balancer. Owned by the cluster manager. const OverrideHostLbConfig& config_; diff --git a/test/extensions/clusters/original_dst/original_dst_cluster_test.cc b/test/extensions/clusters/original_dst/original_dst_cluster_test.cc index 6678ba32d52f4..9d988032d678a 100644 --- a/test/extensions/clusters/original_dst/original_dst_cluster_test.cc +++ b/test/extensions/clusters/original_dst/original_dst_cluster_test.cc @@ -192,7 +192,7 @@ TEST_F(OriginalDstClusterTest, NoContext) { // No downstream connection => no host. { TestLoadBalancerContext lb_context(nullptr); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); EXPECT_CALL(server_context_.dispatcher_, post(_)).Times(0); HostConstSharedPtr host = lb.chooseHost(&lb_context).host; EXPECT_EQ(host, nullptr); @@ -212,7 +212,7 @@ TEST_F(OriginalDstClusterTest, NoContext) { // First argument is normally the reference to the ThreadLocalCluster's HostSet, but in these // tests we do not have the thread local clusters, so we pass a reference to the HostSet of the // primary cluster. The implementation handles both cases the same. - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); EXPECT_CALL(server_context_.dispatcher_, post(_)).Times(0); HostConstSharedPtr host = lb.chooseHost(&lb_context).host; EXPECT_EQ(host, nullptr); @@ -225,7 +225,7 @@ TEST_F(OriginalDstClusterTest, NoContext) { connection.stream_info_.downstream_connection_info_provider_->restoreLocalAddress( *Network::Address::PipeInstance::create("unix://foo")); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); EXPECT_CALL(server_context_.dispatcher_, post(_)).Times(0); HostConstSharedPtr host = lb.chooseHost(&lb_context).host; EXPECT_EQ(host, nullptr); @@ -250,8 +250,8 @@ TEST_F(OriginalDstClusterTest, AddressCollision) { std::make_shared("10.10.11.11")); // Mock the cluster manager by recreating the load balancer each time to get a fresh host map - auto lb1 = OriginalDstCluster::LoadBalancer(handle_); - auto lb2 = OriginalDstCluster::LoadBalancer(handle_); + auto lb1 = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()); + auto lb2 = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()); // Simulate concurrent request for the same address from two workers. Event::PostCb post_cb1; @@ -280,7 +280,9 @@ TEST_F(OriginalDstClusterTest, AddressCollision) { EXPECT_EQ(2UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); // First host is returned on the 3rd call - HostConstSharedPtr host3 = OriginalDstCluster::LoadBalancer(handle_).chooseHost(&lb_context).host; + HostConstSharedPtr host3 = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()) + .chooseHost(&lb_context) + .host; EXPECT_EQ(host3, host1); // Make host time out, no membership changes happen on the first timeout. @@ -301,13 +303,59 @@ TEST_F(OriginalDstClusterTest, AddressCollision) { post_cb1 = std::move(cb); }); // Mock the cluster manager by recreating the load balancer with the new host map - HostConstSharedPtr host4 = OriginalDstCluster::LoadBalancer(handle_).chooseHost(&lb_context).host; + HostConstSharedPtr host4 = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()) + .chooseHost(&lb_context) + .host; post_cb1(); EXPECT_NE(host4, nullptr); EXPECT_NE(host4, host1); EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); } +// Verifies that an existing LB instance refreshes its cached host map via the +// priority set's member update callback, so a host added after the LB was +// constructed is visible to it on the next chooseHost without recreating the LB. +TEST_F(OriginalDstClusterTest, LoadBalancerRefreshesOnMemberUpdate) { + std::string yaml = R"EOF( + name: name + connect_timeout: 1.250s + type: ORIGINAL_DST + lb_policy: CLUSTER_PROVIDED + )EOF"; + + EXPECT_CALL(initialized_, ready()); + setupFromYaml(yaml); + + NiceMock connection; + TestLoadBalancerContext lb_context(&connection); + connection.stream_info_.downstream_connection_info_provider_->restoreLocalAddress( + std::make_shared("10.10.11.11")); + + // Construct the LB before any host exists; its cached host_map_ is empty. + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); + + // First chooseHost on the LB synthesizes a host and posts addHost. + Event::PostCb post_cb; + EXPECT_CALL(server_context_.dispatcher_, post(_)).WillOnce([&post_cb](Event::PostCb cb) { + post_cb = std::move(cb); + }); + HostConstSharedPtr host1 = lb.chooseHost(&lb_context).host; + ASSERT_NE(host1, nullptr); + + // Running the posted callback registers the host in the cluster, which + // triggers priority_set_.updateHosts and fires the LB's member_update_cb_, + // refreshing the LB's cached host_map_. + EXPECT_CALL(membership_updated_, ready()); + post_cb(); + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + + // The same LB instance must now find the host in the refreshed cache and + // must not post addHost again. + EXPECT_CALL(server_context_.dispatcher_, post(_)).Times(0); + HostConstSharedPtr host2 = lb.chooseHost(&lb_context).host; + EXPECT_EQ(host2, host1); +} + TEST_F(OriginalDstClusterTest, HostInUse) { std::string yaml = R"EOF( name: name @@ -326,12 +374,14 @@ TEST_F(OriginalDstClusterTest, HostInUse) { std::make_shared("10.10.11.11")); // Mock the cluster manager by recreating the load balancer each time to get a fresh host map - auto lb = OriginalDstCluster::LoadBalancer(handle_); + auto lb = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()); Event::PostCb post_cb; EXPECT_CALL(server_context_.dispatcher_, post(_)).WillOnce([&post_cb](Event::PostCb cb) { post_cb = std::move(cb); }); - HostConstSharedPtr host = OriginalDstCluster::LoadBalancer(handle_).chooseHost(&lb_context).host; + HostConstSharedPtr host = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()) + .chooseHost(&lb_context) + .host; EXPECT_CALL(membership_updated_, ready()); post_cb(); @@ -343,7 +393,9 @@ TEST_F(OriginalDstClusterTest, HostInUse) { EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); cleanup_timer_->invokeCallback(); } - HostConstSharedPtr host2 = OriginalDstCluster::LoadBalancer(handle_).chooseHost(&lb_context).host; + HostConstSharedPtr host2 = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()) + .chooseHost(&lb_context) + .host; EXPECT_EQ(host2, host); // Reset a handle, and run cleanup twice again. @@ -375,17 +427,21 @@ TEST_F(OriginalDstClusterTest, CollisionHostInUse) { std::make_shared("10.10.11.11")); // Mock the cluster manager by recreating the load balancer each time to get a fresh host map - auto lb = OriginalDstCluster::LoadBalancer(handle_); + auto lb = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()); Event::PostCb post_cb1; EXPECT_CALL(server_context_.dispatcher_, post(_)).WillOnce([&post_cb1](Event::PostCb cb) { post_cb1 = std::move(cb); }); - HostConstSharedPtr host1 = OriginalDstCluster::LoadBalancer(handle_).chooseHost(&lb_context).host; + HostConstSharedPtr host1 = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()) + .chooseHost(&lb_context) + .host; Event::PostCb post_cb2; EXPECT_CALL(server_context_.dispatcher_, post(_)).WillOnce([&post_cb2](Event::PostCb cb) { post_cb2 = std::move(cb); }); - HostConstSharedPtr host2 = OriginalDstCluster::LoadBalancer(handle_).chooseHost(&lb_context).host; + HostConstSharedPtr host2 = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()) + .chooseHost(&lb_context) + .host; EXPECT_CALL(membership_updated_, ready()); post_cb1(); @@ -400,7 +456,9 @@ TEST_F(OriginalDstClusterTest, CollisionHostInUse) { EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); cleanup_timer_->invokeCallback(); } - HostConstSharedPtr host3 = OriginalDstCluster::LoadBalancer(handle_).chooseHost(&lb_context).host; + HostConstSharedPtr host3 = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()) + .chooseHost(&lb_context) + .host; EXPECT_EQ(host3, host1); // Reset a handle, and run cleanup twice again. @@ -446,7 +504,9 @@ TEST_F(OriginalDstClusterTest, Membership) { post_cb = std::move(cb); }); // Mock the cluster manager by recreating the load balancer each time to get a fresh host map - HostConstSharedPtr host = OriginalDstCluster::LoadBalancer(handle_).chooseHost(&lb_context).host; + HostConstSharedPtr host = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()) + .chooseHost(&lb_context) + .host; post_cb(); auto cluster_hosts = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts(); @@ -466,7 +526,9 @@ TEST_F(OriginalDstClusterTest, Membership) { // Same host is returned on the 2nd call // Mock the cluster manager by recreating the load balancer with the new host map - HostConstSharedPtr host2 = OriginalDstCluster::LoadBalancer(handle_).chooseHost(&lb_context).host; + HostConstSharedPtr host2 = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()) + .chooseHost(&lb_context) + .host; EXPECT_EQ(host2, host); // Make host time out, no membership changes happen on the first timeout. @@ -495,7 +557,9 @@ TEST_F(OriginalDstClusterTest, Membership) { post_cb = std::move(cb); }); // Mock the cluster manager by recreating the load balancer with the new host map - HostConstSharedPtr host3 = OriginalDstCluster::LoadBalancer(handle_).chooseHost(&lb_context).host; + HostConstSharedPtr host3 = OriginalDstCluster::LoadBalancer(handle_, cluster_->prioritySet()) + .chooseHost(&lb_context) + .host; post_cb(); EXPECT_NE(host3, nullptr); EXPECT_NE(host3, host); @@ -536,7 +600,7 @@ TEST_F(OriginalDstClusterTest, Membership2) { connection2.stream_info_.downstream_connection_info_provider_->restoreLocalAddress( std::make_shared("10.10.11.12")); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); EXPECT_CALL(membership_updated_, ready()); Event::PostCb post_cb; EXPECT_CALL(server_context_.dispatcher_, post(_)).WillOnce([&post_cb](Event::PostCb cb) { @@ -619,7 +683,7 @@ TEST_F(OriginalDstClusterTest, Connection) { connection.stream_info_.downstream_connection_info_provider_->restoreLocalAddress( std::make_shared("FD00::1")); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; EXPECT_CALL(server_context_.dispatcher_, post(_)).WillOnce([&post_cb](Event::PostCb cb) { post_cb = std::move(cb); @@ -672,7 +736,7 @@ TEST_F(OriginalDstClusterTest, MultipleClusters) { connection.stream_info_.downstream_connection_info_provider_->restoreLocalAddress( std::make_shared("FD00::1")); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; EXPECT_CALL(server_context_.dispatcher_, post(_)).WillOnce([&post_cb](Event::PostCb cb) { post_cb = std::move(cb); @@ -710,7 +774,7 @@ TEST_F(OriginalDstClusterTest, UseHttpHeaderEnabled) { 0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; // HTTP header override. @@ -780,7 +844,7 @@ TEST_F(OriginalDstClusterTest, UseHttpAuthorityHeader) { EXPECT_CALL(initialized_, ready()); setupFromYaml(yaml); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; // HTTP header override by `:authority`. @@ -832,7 +896,7 @@ TEST_F(OriginalDstClusterTest, UseHttpHeaderDisabled) { 0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; // Downstream connection with original_dst filter, HTTP header override ignored. @@ -893,7 +957,7 @@ TEST_F(OriginalDstClusterTest, UseMetadataKeyWithRequest) { EXPECT_CALL(initialized_, ready()); setupFromYaml(yaml); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; NiceMock connection; @@ -943,7 +1007,7 @@ TEST_F(OriginalDstClusterTest, UseMetadataKeyNoRequest) { EXPECT_CALL(initialized_, ready()); setupFromYaml(yaml); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; NiceMock connection; @@ -984,7 +1048,7 @@ TEST_F(OriginalDstClusterTest, UseMetadataKeyInvalidValue) { EXPECT_CALL(initialized_, ready()); setupFromYaml(yaml); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; NiceMock connection; @@ -1021,7 +1085,7 @@ TEST_F(OriginalDstClusterTest, UseMetadataKeyListValue) { EXPECT_CALL(initialized_, ready()); setupFromYaml(yaml); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; NiceMock connection; @@ -1059,7 +1123,7 @@ TEST_F(OriginalDstClusterTest, UseFilterState) { EXPECT_CALL(initialized_, ready()); setupFromYaml(yaml); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; // Filter state takes priority over header override. @@ -1094,7 +1158,7 @@ TEST_F(OriginalDstClusterTest, UsePortOverride) { EXPECT_CALL(initialized_, ready()); setupFromYaml(yaml); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; NiceMock connection; @@ -1126,7 +1190,7 @@ TEST_F(OriginalDstClusterTest, UseFilterStateWithPortOverride) { EXPECT_CALL(initialized_, ready()); setupFromYaml(yaml); - OriginalDstCluster::LoadBalancer lb(handle_); + OriginalDstCluster::LoadBalancer lb(handle_, cluster_->prioritySet()); Event::PostCb post_cb; // Filter state takes priority over header override. diff --git a/test/extensions/clusters/redis/redis_cluster_lb_test.cc b/test/extensions/clusters/redis/redis_cluster_lb_test.cc index bfdf6af0dbb99..65a1fd86a1b2e 100644 --- a/test/extensions/clusters/redis/redis_cluster_lb_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_lb_test.cc @@ -37,8 +37,8 @@ class TestLoadBalancerContext : public RedisLoadBalancerContext, const std::string& clientZone() const override { return client_zone_; } absl::optional hash_key_; - bool is_read_; - NetworkFilters::Common::Redis::Client::ReadPolicy read_policy_; + bool is_read_{}; + NetworkFilters::Common::Redis::Client::ReadPolicy read_policy_{}; std::string client_zone_; }; @@ -428,6 +428,61 @@ TEST_F(RedisClusterLoadBalancerTest, ClusterSlotUpdate) { validateAssignment(hosts, updated_assignments); } +// Verifies that a worker-local LB instance refreshes its slot and shard +// snapshot when the worker priority set fires its member update callback, +// rather than relying on the cluster manager to recreate the LB. +TEST_F(RedisClusterLoadBalancerTest, LoadBalancerRefreshesOnMemberUpdate) { + Upstream::HostVector hosts{Upstream::makeTestHost(info_, "tcp://127.0.0.1:90"), + Upstream::makeTestHost(info_, "tcp://127.0.0.1:91")}; + Upstream::HostMap all_hosts{{hosts[0]->address()->asString(), hosts[0]}, + {hosts[1]->address()->asString(), hosts[1]}}; + init(); + + // Install the initial slot assignment. + factory_->onClusterSlotUpdate(std::make_unique>(std::vector{ + ClusterSlot(0, 1000, hosts[0]->address()), + ClusterSlot(1001, 16383, hosts[1]->address())}), + all_hosts); + + // Create a single LB *before* any further slot updates and exercise it. + Upstream::LoadBalancerPtr lb = lb_->factory()->create(lb_params_); + { + TestLoadBalancerContext context(100); // slot 100 → hosts[0] + EXPECT_EQ(hosts[0]->address()->asString(), + lb->chooseHost(&context).host->address()->asString()); + TestLoadBalancerContext context2(2100); // slot 2100 → hosts[1] + EXPECT_EQ(hosts[1]->address()->asString(), + lb->chooseHost(&context2).host->address()->asString()); + } + + // Update slot assignment in the factory so that slot 2100 now maps to hosts[0]. + factory_->onClusterSlotUpdate( + std::make_unique>(std::vector{ + ClusterSlot(0, 1000, hosts[0]->address()), ClusterSlot(1001, 2000, hosts[1]->address()), + ClusterSlot(2001, 16383, hosts[0]->address())}), + all_hosts); + + // Until the worker priority set fires its member update callback, the LB + // still holds the previous snapshot. + { + TestLoadBalancerContext context(2100); + EXPECT_EQ(hosts[1]->address()->asString(), + lb->chooseHost(&context).host->address()->asString()); + } + + // Simulate the worker host update broadcast: the cluster manager calls + // priority_set_.updateHosts on the worker, which fires every registered + // MemberUpdateCb -- including the one the LB installed in its constructor. + worker_priority_set_.runUpdateCallbacks(0, {}, {}); + + // The same LB instance now picks the updated assignment. + { + TestLoadBalancerContext context(2100); + EXPECT_EQ(hosts[0]->address()->asString(), + lb->chooseHost(&context).host->address()->asString()); + } +} + TEST_F(RedisClusterLoadBalancerTest, ClusterSlotNoUpdate) { Upstream::HostVector hosts{Upstream::makeTestHost(info_, "tcp://127.0.0.1:90"), Upstream::makeTestHost(info_, "tcp://127.0.0.1:91"), From 0d6e3c60aa55e434f28e581df1d25fcb83404b68 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 21 May 2026 06:56:39 +0100 Subject: [PATCH 367/750] test: Fix `ip_tagging_integration_test` flake (#45179) Signed-off-by: Ryan Northey --- .../filters/http/ip_tagging/ip_tagging_integration_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/filters/http/ip_tagging/ip_tagging_integration_test.cc b/test/extensions/filters/http/ip_tagging/ip_tagging_integration_test.cc index 82ed4bd4f5d33..0139e59a127a0 100644 --- a/test/extensions/filters/http/ip_tagging/ip_tagging_integration_test.cc +++ b/test/extensions/filters/http/ip_tagging/ip_tagging_integration_test.cc @@ -165,7 +165,7 @@ TEST_P(IpTaggingIntegrationTest, IptaggingFilterWithReloadNoCrashOnLdsUpdate) { }); new_config_helper.setLds("1"); test_server_->waitForGauge("listener_manager.total_listeners_active", testing::Eq(1)); - test_server_->waitForCounter("listener_manager.lds.update_success", testing::Eq(1)); + test_server_->waitForCounter("listener_manager.lds.update_success", testing::Ge(1)); test_server_->waitForGauge("listener_manager.total_listeners_draining", testing::Eq(0)); } From a6e26430dd28f60a117f8c5f003ce86443c9dd16 Mon Sep 17 00:00:00 2001 From: code Date: Thu, 21 May 2026 20:51:28 +0800 Subject: [PATCH 368/750] clang tidy: part of fixes of test only code (#45181) Commit Message: clang tidy: part of fixes of test only code Additional Description: Test only clang tidy issue fixes. Not all because there are tooooooo much problems and even AI is not sound enough for that. Risk Level: low. Testing: n/a. Docs Changes: n/a. Release Notes: n/a. Platform Specific Features: n/a. --------- Signed-off-by: wbpcode/wangbaiping --- test/common/buffer/buffer_test.cc | 1 + test/common/buffer/watermark_buffer_test.cc | 2 +- .../common/common/linked_object_speed_test.cc | 4 +- test/common/http/codes_test.cc | 7 +- .../http_server_properties_cache_impl_test.cc | 18 ++- test/common/http/session_idle_list_test.cc | 24 ++-- test/common/io/io_uring_impl_test.cc | 2 +- .../io/io_uring_worker_factory_impl_test.cc | 2 +- .../json/json_rpc_field_extractor_test.cc | 5 +- test/common/jwt/jwt_test.cc | 4 +- test/common/jwt/simple_lru_cache_test.cc | 120 +++++++++--------- test/common/jwt/verify_jwk_ec_test.cc | 2 +- test/common/jwt/verify_jwk_hmac_test.cc | 2 +- test/common/jwt/verify_jwk_okp_test.cc | 2 +- test/common/jwt/verify_jwk_rsa_pss_test.cc | 4 +- test/common/jwt/verify_jwk_rsa_test.cc | 2 +- test/common/network/address_impl_test.cc | 2 +- test/common/network/connection_impl_test.cc | 4 +- .../io_socket_handle_impl_benchmark_test.cc | 17 ++- .../network/lc_trie_ip_list_speed_test.cc | 20 +-- .../udp_listener_impl_batch_writer_test.cc | 4 +- test/common/network/udp_listener_impl_test.cc | 2 +- test/common/quic/envoy_quic_h3_fuzz_test.cc | 2 +- .../quic/envoy_quic_proof_source_test.cc | 2 +- .../quic/envoy_quic_proof_verifier_test.cc | 2 +- test/common/quic/quic_stat_names_test.cc | 9 +- test/common/runtime/runtime_impl_test.cc | 6 +- test/common/stats/refcount_ptr_test.cc | 2 +- test/common/tcp/async_tcp_client_impl_test.cc | 2 +- test/common/tcp/conn_pool_test.cc | 2 +- test/common/tcp_proxy/tcp_proxy_test.cc | 4 +- test/common/tcp_proxy/tcp_proxy_test_base.h | 10 +- test/common/tls/cert_validator/test_common.h | 2 +- .../integration/ssl_integration_test_base.h | 1 + .../tls/test_private_key_method_provider.h | 2 +- .../upstream/health_check_fuzz_test_utils.h | 10 +- .../upstream/health_checker_impl_test.cc | 10 +- .../upstream/transport_socket_input_test.cc | 1 - .../fluentd/fluentd_access_log_impl_test.cc | 1 + .../open_telemetry/otlp_log_utils_test.cc | 1 - .../access_loggers/stats/stats_speed_test.cc | 14 +- .../access_loggers/stats/stats_test.cc | 3 +- .../rc_connection_wrapper_test.cc | 2 +- .../reverse_connection_address_test.cc | 9 +- .../reverse_connection_io_handle_test.cc | 2 +- .../reverse_connection_resolver_test.cc | 4 - .../reverse_tunnel_acceptor_extension_test.cc | 2 +- .../bootstrap/wasm/test_data/speed_cpp.cc | 2 +- test/extensions/bootstrap/wasm/wasm_test.cc | 2 +- .../common/logical_host_integration_test.cc | 4 +- .../clusters/composite/cluster_test.cc | 2 +- .../clusters/redis/redis_cluster_lb_test.cc | 5 +- ...oles_anywhere_credentials_provider_test.cc | 2 +- .../eventstream/eventstream_parser_test.cc | 10 +- test/extensions/common/dubbo/codec_test.cc | 7 +- .../dns_cache_impl_test.cc | 2 +- .../redis/cluster_refresh_manager_test.cc | 6 +- .../common/wasm/test_data/test_cpp.cc | 5 +- .../zlib_decompressor_impl_test.cc | 4 +- .../zstd_decompressor_impl_test.cc | 2 +- .../grpc/new_grpc_mux_impl_test.cc | 6 +- .../grpc/sotw_subscription_state_test.cc | 3 +- .../dynamic_modules/listener/abi_impl_test.cc | 6 +- .../filters/common/expr/evaluator_test.cc | 2 - .../ext_authz/ext_authz_grpc_impl_test.cc | 2 +- .../fair_token_bucket_impl_test.cc | 32 ++--- .../http/cache/cache_entry_utils_test.cc | 3 +- .../cache/cache_filter_logging_info_test.cc | 7 +- .../http/cache_v2/cache_entry_utils_test.cc | 3 +- .../filters/http/cache_v2/config_test.cc | 4 +- .../http_cache_implementation_test_common.cc | 2 - .../filters/http/common/fuzz/uber_filter.h | 4 +- test/extensions/filters/http/common/mock.cc | 2 +- .../decompressor_filter_integration_test.cc | 4 +- .../proxy_filter_test.cc | 2 +- .../dynamic_forward_proxy/test_resolver.h | 2 +- .../http/ext_authz/ext_authz_fuzz_lib.cc | 2 +- .../ext_authz/ext_authz_integration_test.cc | 10 +- .../filters/http/ext_authz/ext_authz_test.cc | 17 +-- .../http/ext_authz/logging_test_filter.cc | 4 +- .../http/ext_proc/ext_proc_benchmark_test.cc | 2 +- .../ext_proc_full_duplex_integration_test.cc | 4 +- .../http/ext_proc/ext_proc_grpc_fuzz.h | 2 +- .../ext_proc/ext_proc_integration_common.cc | 4 +- .../ext_proc/ext_proc_integration_common.h | 11 +- .../ext_proc/ext_proc_integration_test.cc | 2 +- .../ext_proc_misc_integration_test.cc | 4 +- .../http/ext_proc/ext_proc_misc_test.cc | 8 +- .../filters/http/ext_proc/filter_test.cc | 12 +- .../ext_proc_http_integration_test.cc | 6 +- .../ext_proc/http_client/http_client_test.cc | 2 +- .../ext_proc/streaming_integration_test.cc | 2 +- .../extensions/filters/http/file_server/BUILD | 1 + .../absl_status_to_http_status_test.cc | 6 +- .../gcp_authn_filter_integration_test.cc | 6 +- .../message_converter_test.cc | 7 +- .../filters/http/mcp/mcp_json_parser_test.cc | 2 - .../http/oauth2/oauth_integration_test.cc | 1 + .../http/on_demand/odcds_integration_test.cc | 10 +- .../filter_benchmark_test.cc | 46 ++++--- .../http/rate_limit_quota/client_test.cc | 73 ++++++----- .../http/rate_limit_quota/client_test_utils.h | 12 +- .../filter_persistence_test.cc | 4 +- .../http/rate_limit_quota/integration_test.cc | 9 +- .../filters/http/rbac/rbac_filter_test.cc | 2 +- .../filters/http/router/config_test.cc | 3 +- .../tls_inspector/ja4_fingerprint_test.cc | 3 - .../tls_inspector/tls_inspector_ja4_test.cc | 2 - .../network/dubbo_proxy/app_exception_test.cc | 1 + .../network/dubbo_proxy/router_test.cc | 4 +- .../filters/network/ext_proc/config_test.cc | 2 +- .../ext_proc/ext_proc_integration_test.cc | 18 +-- .../filters/network/ext_proc/ext_proc_test.cc | 1 - .../generic_proxy/router/router_test.cc | 2 +- .../config_filter_dependencies_test.cc | 14 +- .../config_test_base.h | 8 +- .../filters/network/rbac/filter_test.cc | 4 +- .../redis_proxy_integration_test.cc | 2 +- .../reverse_tunnel/filter_unit_test.cc | 10 +- .../reverse_tunnel/integration_test.cc | 2 +- .../tcp_bandwidth_limit/filter_test.cc | 7 +- .../thrift_proxy/app_exception_impl_test.cc | 1 + .../network/thrift_proxy/router_test.cc | 8 +- .../dns_filter/dns_filter_access_log_test.cc | 2 +- .../file_based_metadata/integration_test.cc | 12 +- ...dential_injector_oauth_integration_test.cc | 4 +- .../file_based/key_value_store_test.cc | 2 +- ...lient_side_weighted_round_robin_lb_test.cc | 21 +-- .../common/load_balancer_impl_base_test.h | 6 +- .../benchmark/dns_resolver_benchmark.cc | 24 ++-- .../expr/config_test.cc | 2 +- .../cgroup_memory/config_test.cc | 1 - .../open_telemetry_benchmark.cc | 8 +- .../open_telemetry_impl_test.cc | 13 +- .../tracers/datadog/agent_http_client_test.cc | 3 +- test/extensions/tracers/datadog/span_test.cc | 4 +- .../tracers/dynamic_modules/tracer_test.cc | 1 - .../opentelemetry/operation_name_test.cc | 2 +- ...nfig_resource_detector_integration_test.cc | 2 +- .../opentelemetry/samplers/sampler_test.cc | 2 +- .../alts/alts_channel_pool_test.cc | 3 +- .../spiffe/spiffe_validator_test.cc | 4 +- .../upstreams/http/generic/config_test.cc | 3 +- .../upstreams/http/udp/config_test.cc | 1 - test/fuzz/fuzz_runner.cc | 1 + test/integration/ads_integration.h | 8 +- test/integration/ads_integration_test.cc | 2 +- test/integration/async_round_robin_lb.cc | 2 +- test/integration/base_integration_test.h | 2 +- .../cluster_filter_integration_test.cc | 6 +- test/integration/fake_upstream.h | 2 +- .../filter_manager_integration_test.cc | 2 +- .../filters/async_upstream_filter.cc | 2 +- test/integration/filters/tee_filter.h | 4 +- ...er_extension_discovery_integration_test.cc | 2 +- .../listener_lds_integration_test.cc | 2 +- test/integration/overload_integration_test.cc | 7 +- .../integration/quic_http_integration_test.cc | 8 +- test/integration/ssl_utility.h | 2 +- .../integration/tcp_proxy_integration_test.cc | 3 +- .../udp_tunneling_integration_test.cc | 2 +- test/mocks/network/mocks.h | 4 +- .../server/health_checker_factory_context.h | 2 +- test/mocks/server/hot_restart.cc | 1 - test/mocks/ssl/mocks.h | 2 +- test/mocks/stats/mocks.h | 1 + test/mocks/thread_local/mocks.h | 1 + test/mocks/upstream/host.cc | 4 +- test/mocks/upstream/host.h | 2 +- test/server/admin/stats_handler_speed_test.cc | 8 +- test/server/cgroup_cpu_util_test.cc | 2 - test/server/connection_handler_test.cc | 2 +- test/server/utils_test.cc | 3 +- test/test_common/BUILD | 6 +- test/test_common/enum_test_utils.h | 15 +++ test/test_common/environment.cc | 2 +- test/test_common/logging.h | 1 + test/test_common/network_utility.h | 2 +- test/test_common/simulated_time_system.h | 2 +- test/test_common/test_runtime.h | 4 +- test/test_common/wasm_base.h | 4 +- 181 files changed, 582 insertions(+), 518 deletions(-) create mode 100644 test/test_common/enum_test_utils.h diff --git a/test/common/buffer/buffer_test.cc b/test/common/buffer/buffer_test.cc index 264cbcda3ecb9..fd8f7c43d0125 100644 --- a/test/common/buffer/buffer_test.cc +++ b/test/common/buffer/buffer_test.cc @@ -1192,6 +1192,7 @@ TEST(BufferHelperTest, AddFragmentsOptimization) { unoptimized_buffer.add(prefill); std::vector fragments; + fragments.reserve(50); for (int i = 0; i < 50; i++) { fragments.push_back("fragment"); } diff --git a/test/common/buffer/watermark_buffer_test.cc b/test/common/buffer/watermark_buffer_test.cc index 3473db20de37d..bde9bdacb7744 100644 --- a/test/common/buffer/watermark_buffer_test.cc +++ b/test/common/buffer/watermark_buffer_test.cc @@ -85,7 +85,7 @@ TEST_F(WatermarkBufferTest, PrependToEmptyBuffer) { EXPECT_EQ(0, times_high_watermark_called_); EXPECT_EQ(suffix.size(), buffer_.length()); - buffer_.prepend(prefix.data()); + buffer_.prepend(prefix); EXPECT_EQ(1, times_high_watermark_called_); EXPECT_EQ(suffix.size() + prefix.size(), buffer_.length()); diff --git a/test/common/common/linked_object_speed_test.cc b/test/common/common/linked_object_speed_test.cc index 7ddfb0c0098a1..b0e4524dcf61b 100644 --- a/test/common/common/linked_object_speed_test.cc +++ b/test/common/common/linked_object_speed_test.cc @@ -14,12 +14,12 @@ namespace Envoy { struct StdListObject : public LinkedObject { explicit StdListObject(int v = 0) : value(v) {} - int value; + int value = 0; }; struct IntrusiveObject : public IntrusiveListNode { explicit IntrusiveObject(int v = 0) : value(v) {} - int value; + int value = 0; }; // Benchmark inserting N items at the front of a std::list>. diff --git a/test/common/http/codes_test.cc b/test/common/http/codes_test.cc index 46cbe504ede37..b077dc66758dd 100644 --- a/test/common/http/codes_test.cc +++ b/test/common/http/codes_test.cc @@ -10,6 +10,7 @@ #include "source/common/http/header_map_impl.h" #include "test/mocks/stats/mocks.h" +#include "test/test_common/enum_test_utils.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" @@ -66,7 +67,7 @@ TEST_F(CodeUtilityTest, GroupStrings) { EXPECT_EQ("3xx", CodeUtility::groupStringForResponseCode(Code::Found)); EXPECT_EQ("4xx", CodeUtility::groupStringForResponseCode(Code::NotFound)); EXPECT_EQ("5xx", CodeUtility::groupStringForResponseCode(Code::NotImplemented)); - EXPECT_EQ("", CodeUtility::groupStringForResponseCode(static_cast(600))); + EXPECT_EQ("", CodeUtility::groupStringForResponseCode(uncheckedEnumCastForTest(600))); } TEST_F(CodeUtilityTest, NoCanary) { @@ -209,13 +210,13 @@ TEST_F(CodeUtilityTest, All) { std::make_pair(Code::LoopDetected, "Loop Detected"), std::make_pair(Code::NotExtended, "Not Extended"), std::make_pair(Code::NetworkAuthenticationRequired, "Network Authentication Required"), - std::make_pair(static_cast(600), "Unknown")}; + std::make_pair(uncheckedEnumCastForTest(600), "Unknown")}; for (const auto& test_case : test_set) { EXPECT_EQ(test_case.second, CodeUtility::toString(test_case.first)); } - EXPECT_EQ(std::string("Unknown"), CodeUtility::toString(static_cast(600))); + EXPECT_EQ(std::string("Unknown"), CodeUtility::toString(uncheckedEnumCastForTest(600))); } TEST_F(CodeUtilityTest, RequestVirtualCluster) { diff --git a/test/common/http/http_server_properties_cache_impl_test.cc b/test/common/http/http_server_properties_cache_impl_test.cc index f6d0661d3932a..4944a64a084ca 100644 --- a/test/common/http/http_server_properties_cache_impl_test.cc +++ b/test/common/http/http_server_properties_cache_impl_test.cc @@ -15,7 +15,7 @@ namespace Http { namespace { static const absl::optional kNoTtl = absl::nullopt; -class HttpServerPropertiesCacheImplTest : public testing::TestWithParam { +class HttpServerPropertiesCacheImplTest : public testing::TestWithParam { public: HttpServerPropertiesCacheImplTest() : dispatcher_([]() { @@ -24,7 +24,7 @@ class HttpServerPropertiesCacheImplTest : public testing::TestWithParam(); }); return NiceMock(); }()), - store_(GetParam()), expiration1_(dispatcher_.timeSource().monotonicTime() + Seconds(5)), + expiration1_(dispatcher_.timeSource().monotonicTime() + Seconds(5)), expiration2_(dispatcher_.timeSource().monotonicTime() + Seconds(10)), protocol1_(alpn1_, hostname1_, port1_, expiration1_), protocol2_(alpn2_, hostname2_, port2_, expiration2_), protocols1_({protocol1_}), @@ -35,15 +35,23 @@ class HttpServerPropertiesCacheImplTest : public testing::TestWithParam>(); + store_ = owned_store_.get(); + } else { + owned_store_.reset(); + } protocols_ = std::make_unique( - dispatcher_, std::move(suffixes_), std::unique_ptr(store_), max_entries_); + dispatcher_, std::move(suffixes_), std::move(owned_store_), max_entries_); } size_t max_entries_ = 10; NiceMock dispatcher_; std::vector suffixes_; - MockKeyValueStore* store_; + MockKeyValueStore* store_{}; + std::unique_ptr owned_store_; std::unique_ptr protocols_; const std::string hostname1_ = "hostname1"; @@ -570,7 +578,7 @@ TEST_P(HttpServerPropertiesCacheImplTest, ExplicitAlternativeTakesPriorityOverCa // Execute all tests when key value store is nullptr and when it is valid. INSTANTIATE_TEST_SUITE_P(HttpServerPropertiesCacheImplTestSuite, HttpServerPropertiesCacheImplTest, - testing::Values(nullptr, new NiceMock())); + testing::Bool()); } // namespace } // namespace Http } // namespace Envoy diff --git a/test/common/http/session_idle_list_test.cc b/test/common/http/session_idle_list_test.cc index c57d21a1a2b94..d3932a0a4d6b6 100644 --- a/test/common/http/session_idle_list_test.cc +++ b/test/common/http/session_idle_list_test.cc @@ -21,11 +21,11 @@ namespace Http { class TestIdleSession : public IdleSessionInterface { public: TestIdleSession() = default; - virtual ~TestIdleSession() = default; + ~TestIdleSession() override = default; void TerminateIdleSession() override { is_closed_ = true; } - bool is_closed() const { return is_closed_; } + bool isClosed() const { return is_closed_; } private: bool is_closed_ = false; @@ -83,8 +83,8 @@ TEST_F(SessionIdleListTest, TerminateIdleSessionsUpToMaxSessionsAllowed) { EXPECT_EQ(idle_list_.idle_sessions()->size(), 2); time_system_->advanceTimeWait(std::chrono::minutes(1)); idle_list_.MaybeTerminateIdleSessions(/*is_saturated=*/false); - EXPECT_TRUE(session1.is_closed()); - EXPECT_FALSE(session2.is_closed()); + EXPECT_TRUE(session1.isClosed()); + EXPECT_FALSE(session2.isClosed()); EXPECT_FALSE(idle_list_.idle_sessions()->ContainsForTest(session1)); EXPECT_TRUE(idle_list_.idle_sessions()->ContainsForTest(session2)); EXPECT_EQ(idle_list_.idle_sessions()->size(), 1); @@ -95,12 +95,12 @@ TEST_F(SessionIdleListTest, RespectMinTimeBeforeTermination) { idle_list_.AddSession(session1); time_system_->advanceTimeWait(std::chrono::seconds(59)); idle_list_.MaybeTerminateIdleSessions(/*is_saturated=*/false); - EXPECT_FALSE(session1.is_closed()); + EXPECT_FALSE(session1.isClosed()); EXPECT_TRUE(idle_list_.idle_sessions()->ContainsForTest(session1)); time_system_->advanceTimeWait(std::chrono::seconds(1)); idle_list_.MaybeTerminateIdleSessions(/*is_saturated=*/false); - EXPECT_TRUE(session1.is_closed()); + EXPECT_TRUE(session1.isClosed()); EXPECT_FALSE(idle_list_.idle_sessions()->ContainsForTest(session1)); } @@ -119,9 +119,9 @@ TEST_F(SessionIdleListTest, TerminateMultipleSessionsByOrder) { // max_sessions_to_terminate_in_one_round = 2, so s1 and s2 should be // terminated. idle_list_.MaybeTerminateIdleSessions(/*is_saturated=*/false); - EXPECT_TRUE(session1.is_closed()); - EXPECT_TRUE(session2.is_closed()); - EXPECT_FALSE(session3.is_closed()); + EXPECT_TRUE(session1.isClosed()); + EXPECT_TRUE(session2.isClosed()); + EXPECT_FALSE(session3.isClosed()); EXPECT_FALSE(idle_list_.idle_sessions()->ContainsForTest(session1)); EXPECT_FALSE(idle_list_.idle_sessions()->ContainsForTest(session2)); EXPECT_TRUE(idle_list_.idle_sessions()->ContainsForTest(session3)); @@ -143,9 +143,9 @@ TEST_F(SessionIdleListTest, TerminateIdleSessionsWhenOverloaded) { // When ignore_min_time_before_termination_allowed_ is true, we should // terminate up to max_sessions_to_terminate_in_one_round_when_overload_ // sessions, which is 2. - EXPECT_TRUE(session1.is_closed()); - EXPECT_TRUE(session2.is_closed()); - EXPECT_FALSE(session3.is_closed()); + EXPECT_TRUE(session1.isClosed()); + EXPECT_TRUE(session2.isClosed()); + EXPECT_FALSE(session3.isClosed()); EXPECT_FALSE(idle_list_.idle_sessions()->ContainsForTest(session1)); EXPECT_FALSE(idle_list_.idle_sessions()->ContainsForTest(session2)); EXPECT_TRUE(idle_list_.idle_sessions()->ContainsForTest(session3)); diff --git a/test/common/io/io_uring_impl_test.cc b/test/common/io/io_uring_impl_test.cc index 64fe888494071..693703e9c139c 100644 --- a/test/common/io/io_uring_impl_test.cc +++ b/test/common/io/io_uring_impl_test.cc @@ -62,7 +62,7 @@ class IoUringImplTest : public ::testing::Test { } Api::ApiPtr api_; - IoUringPtr io_uring_{}; + IoUringPtr io_uring_; const bool should_skip_{}; }; diff --git a/test/common/io/io_uring_worker_factory_impl_test.cc b/test/common/io/io_uring_worker_factory_impl_test.cc index 78c597fda259a..22ec4f839ba74 100644 --- a/test/common/io/io_uring_worker_factory_impl_test.cc +++ b/test/common/io/io_uring_worker_factory_impl_test.cc @@ -24,7 +24,7 @@ class IoUringWorkerFactoryImplTest : public ::testing::Test { Api::ApiPtr api_; testing::NiceMock context_; - std::unique_ptr factory_{}; + std::unique_ptr factory_; bool should_skip_{}; }; diff --git a/test/common/json/json_rpc_field_extractor_test.cc b/test/common/json/json_rpc_field_extractor_test.cc index d4b2609e417b7..ccf52abcf68ed 100644 --- a/test/common/json/json_rpc_field_extractor_test.cc +++ b/test/common/json/json_rpc_field_extractor_test.cc @@ -10,10 +10,11 @@ namespace { class ExtractorTestJsonRpcParserConfig : public JsonRpcParserConfig { public: - ExtractorTestJsonRpcParserConfig() { initializeDefaults(); } + ExtractorTestJsonRpcParserConfig() { initializeDefaultsImpl(); } protected: - void initializeDefaults() override { + void initializeDefaults() override { initializeDefaultsImpl(); } + void initializeDefaultsImpl() { always_extract_.insert("id"); always_extract_.insert("jsonrpc"); always_extract_.insert("method"); diff --git a/test/common/jwt/jwt_test.cc b/test/common/jwt/jwt_test.cc index 66708bffe769a..8f536a2d82609 100644 --- a/test/common/jwt/jwt_test.cc +++ b/test/common/jwt/jwt_test.cc @@ -71,8 +71,8 @@ TEST(JwtParseTest, Copy) { std::vector> jwts{constructed, copied}; - for (auto jwt = jwts.begin(); jwt != jwts.end(); ++jwt) { - Jwt& ref = (*jwt); + for (auto& jwt : jwts) { + Jwt& ref = jwt; EXPECT_EQ(ref.alg_, original.alg_); EXPECT_EQ(ref.kid_, original.kid_); EXPECT_EQ(ref.iss_, original.iss_); diff --git a/test/common/jwt/simple_lru_cache_test.cc b/test/common/jwt/simple_lru_cache_test.cc index 68bad2eacb14d..77d929ff1e9d0 100644 --- a/test/common/jwt/simple_lru_cache_test.cc +++ b/test/common/jwt/simple_lru_cache_test.cc @@ -5,7 +5,6 @@ // // Tests fo SimpleLRUCache -#include #include #include @@ -58,7 +57,7 @@ struct TestValue { // Make sure that TestCache can delete TestValue when declared as friend. friend class SimpleLRUCache; friend class TestCache; - ~TestValue() {} + ~TestValue() = default; }; class TestCache : public SimpleLRUCache { @@ -67,7 +66,7 @@ class TestCache : public SimpleLRUCache { : SimpleLRUCache(size), check_in_cache_(check_in_cache) {} protected: - virtual void removeElement(TestValue* v) { + void removeElement(TestValue* v) override { if (v && check_in_cache_) { assert(in_cache[v->label]); std::cout << " Evict:" << v->label; @@ -81,19 +80,22 @@ class TestCache : public SimpleLRUCache { class SimpleLRUCacheTest : public ::testing::Test { protected: - SimpleLRUCacheTest() {} - virtual ~SimpleLRUCacheTest() {} + SimpleLRUCacheTest() = default; + ~SimpleLRUCacheTest() override = default; - virtual void SetUp() { - for (int i = 0; i < kElems; ++i) - in_cache[i] = false; + void SetUp() override { + for (bool& i : in_cache) { + i = false; + } } - virtual void TearDown() { - if (cache_) + void TearDown() override { + if (cache_) { cache_->clear(); - for (int i = 0; i < kElems; i++) { - assert(!in_cache[i]); + } + for (bool i : in_cache) { + (void)(i); + assert(!i); } } @@ -111,7 +113,7 @@ TEST_F(SimpleLRUCacheTest, IteratorDefaultConstruct) { TestCache::const_iterator TEST_F(SimpleLRUCacheTest, Iteration) { int count = 0; - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); // fill the cache, evict some items, ensure i can iterate over all remaining for (int i = 0; i < kElems; ++i) { @@ -147,7 +149,7 @@ TEST_F(SimpleLRUCacheTest, Iteration) { } TEST_F(SimpleLRUCacheTest, StdCopy) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); for (int i = 0; i < kElems; ++i) { in_cache[i] = true; cache_->insertPinned(i, new TestValue(i), 1); @@ -184,18 +186,18 @@ void SimpleLRUCacheTest::testInOrderEvictions(int cache_size) { } TEST_F(SimpleLRUCacheTest, InOrderEvictions) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); testInOrderEvictions(kCacheSize); } TEST_F(SimpleLRUCacheTest, InOrderEvictionsWithIdleEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); cache_->setMaxIdleSeconds(2000); testInOrderEvictions(kCacheSize); } TEST_F(SimpleLRUCacheTest, InOrderEvictionsWithAgeBasedEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); cache_->setAgeBasedEviction(2000); testInOrderEvictions(kCacheSize); } @@ -258,18 +260,18 @@ void SimpleLRUCacheTest::testSetMaxSize() { } TEST_F(SimpleLRUCacheTest, SetMaxSize) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); testSetMaxSize(); } TEST_F(SimpleLRUCacheTest, SetMaxSizeWithIdleEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); cache_->setMaxIdleSeconds(2000); testSetMaxSize(); } TEST_F(SimpleLRUCacheTest, SetMaxSizeWithAgeBasedEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); cache_->setAgeBasedEviction(2000); testSetMaxSize(); } @@ -291,12 +293,12 @@ TEST_F(SimpleLRUCacheTest, VoidValues) { // return value from Lookup(), so let's do it with StillInUse(). // - cache_.reset(new TestCache(1)); + cache_ = std::make_unique(1); - cache_->insertPinned(5, 0, 1); - cache_->release(5, 0); + cache_->insertPinned(5, nullptr, 1); + cache_->release(5, nullptr); - if (cache_->stillInUse(5, 0)) { + if (cache_->stillInUse(5, nullptr)) { // Released, but still in there // This path is executed given Dec 2007 implementation @@ -305,11 +307,11 @@ TEST_F(SimpleLRUCacheTest, VoidValues) { } else { // Not in there, let's insert it // This path is not executed given Dec 2007 implementation - cache_->insertPinned(5, 0, 1); + cache_->insertPinned(5, nullptr, 1); } ASSERT_EQ(1, cache_->pinnedSize()); - cache_->release(5, 0); + cache_->release(5, nullptr); ASSERT_EQ(0, cache_->pinnedSize()); cache_->clear(); @@ -345,12 +347,12 @@ void SimpleLRUCacheTest::testOverfullEvictionPolicy() { } TEST_F(SimpleLRUCacheTest, OverfullEvictionPolicy) { - cache_.reset(new TestCache(kCacheSize + 1)); + cache_ = std::make_unique(kCacheSize + 1); testOverfullEvictionPolicy(); } TEST_F(SimpleLRUCacheTest, OverfullEvictionPolicyWithIdleEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize + 1)); + cache_ = std::make_unique(kCacheSize + 1); // Here we are not testing idle eviction, just that LRU eviction // still works correctly when the cache is overfull. cache_->setMaxIdleSeconds(2000); @@ -358,7 +360,7 @@ TEST_F(SimpleLRUCacheTest, OverfullEvictionPolicyWithIdleEvictionEnabled) { } TEST_F(SimpleLRUCacheTest, OverfullEvictionPolicyWithAgeBasedEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); // With age-based eviction usage is ignored and instead the oldest inserted // element is evicted when cache becomes overfull. cache_->setAgeBasedEviction(2000); @@ -387,7 +389,7 @@ TEST_F(SimpleLRUCacheTest, OverfullEvictionPolicyWithAgeBasedEvictionEnabled) { } TEST_F(SimpleLRUCacheTest, Update) { - cache_.reset(new TestCache(kCacheSize, false)); // Don't check in_cache. + cache_ = std::make_unique(kCacheSize, false); // Don't check in_cache. // Insert some values. for (int i = 0; i < kCacheSize; i++) { ASSERT_TRUE(!cache_->lookup(i)); @@ -423,7 +425,7 @@ TEST_F(SimpleLRUCacheTest, Update) { TEST_F(SimpleLRUCacheTest, Pinning) { static const int kPinned = kCacheSize + 4; - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); for (int i = 0; i < kElems; i++) { ASSERT_TRUE(!cache_->lookup(i)); TestValue* v = new TestValue(i); @@ -444,7 +446,7 @@ TEST_F(SimpleLRUCacheTest, Pinning) { } TEST_F(SimpleLRUCacheTest, Remove) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); for (int i = 0; i < kElems; i++) { ASSERT_TRUE(!cache_->lookup(i)); TestValue* v = new TestValue(i); @@ -502,12 +504,12 @@ void SimpleLRUCacheTest::testRemoveUnpinned() { } TEST_F(SimpleLRUCacheTest, RemoveUnpinned) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); testRemoveUnpinned(); } TEST_F(SimpleLRUCacheTest, RemoveUnpinnedWithIdleEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); // Here we are not testing idle eviction, just that removeUnpinned // works correctly with it enabled. cache_->setMaxIdleSeconds(2000); @@ -515,7 +517,7 @@ TEST_F(SimpleLRUCacheTest, RemoveUnpinnedWithIdleEvictionEnabled) { } TEST_F(SimpleLRUCacheTest, RemoveUnpinnedWithAgeBasedEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); // Here we are not testing age-based eviction, just that removeUnpinned // works correctly with it enabled. cache_->setAgeBasedEviction(2000); @@ -523,7 +525,7 @@ TEST_F(SimpleLRUCacheTest, RemoveUnpinnedWithAgeBasedEvictionEnabled) { } TEST_F(SimpleLRUCacheTest, MultiInsert) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); for (int i = 0; i < kElems; i++) { TestValue* v = new TestValue(i); in_cache[i] = true; @@ -535,7 +537,7 @@ TEST_F(SimpleLRUCacheTest, MultiInsert) { } TEST_F(SimpleLRUCacheTest, MultiInsertPinned) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); TestValue* list[kElems]; for (int i = 0; i < kElems; i++) { TestValue* v = new TestValue(i); @@ -547,13 +549,13 @@ TEST_F(SimpleLRUCacheTest, MultiInsertPinned) { ASSERT_TRUE(in_cache[i]); ASSERT_TRUE(cache_->stillInUse(0, list[i])); } - for (int i = 0; i < kElems; i++) { - cache_->release(0, list[i]); + for (auto& i : list) { + cache_->release(0, i); } } void SimpleLRUCacheTest::testExpiration(bool lru, bool release_quickly) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); if (lru) { cache_->setMaxIdleSeconds(0.2); // 200 milliseconds } else { @@ -564,8 +566,9 @@ void SimpleLRUCacheTest::testExpiration(bool lru, bool release_quickly) { in_cache[i] = true; cache_->insert(i, v, 1); } - for (int i = 0; i < kCacheSize; i++) + for (int i = 0; i < kCacheSize; i++) { ASSERT_TRUE(in_cache[i]); + } usleep(110 * 1000); @@ -575,16 +578,18 @@ void SimpleLRUCacheTest::testExpiration(bool lru, bool release_quickly) { cache_->release(0, v1); v1 = nullptr; } - for (int i = 0; i < kCacheSize; i++) + for (int i = 0; i < kCacheSize; i++) { ASSERT_TRUE(in_cache[i]); + } // Sleep more: should cause expiration of everything we // haven't touched, and the one we touched if age-based. usleep(110 * 1000); // Nothing gets expired until we call one of the cache methods. - for (int i = 0; i < kCacheSize; i++) + for (int i = 0; i < kCacheSize; i++) { ASSERT_TRUE(in_cache[i]); + } // It's now 220 ms since element 0 was created, and // 110 ms since we last looked at it. If we configured @@ -599,8 +604,9 @@ void SimpleLRUCacheTest::testExpiration(bool lru, bool release_quickly) { ASSERT_EQ(v2 == nullptr, !lru); // In either case all the other elements should now be gone. - for (int i = 1; i < kCacheSize; i++) + for (int i = 1; i < kCacheSize; i++) { ASSERT_TRUE(!in_cache[i]); + } // Clean up bool cleaned_up = false; @@ -633,7 +639,7 @@ TEST_F(SimpleLRUCacheTest, ExpirationAgeBasedLongHeldPins) { void SimpleLRUCacheTest::testLargeExpiration(bool lru, double timeout) { // Make sure that setting a large timeout doesn't result in overflow and // cache entries expiring immediately. - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); if (lru) { cache_->setMaxIdleSeconds(timeout); } else { @@ -690,7 +696,7 @@ TEST_F(SimpleLRUCacheTest, UpdateSize) { // Create a cache larger than kCacheSize, to give us some overhead to // change the objects' sizes. We don't want an UpdateSize operation // to force a GC and throw off our ASSERT_TRUE()s down below. - cache_.reset(new TestCache(kCacheSize * 2)); + cache_ = std::make_unique(kCacheSize * 2); for (int i = 0; i < kCacheSize; i++) { TestValue* v = new TestValue(i); in_cache[i] = true; @@ -810,7 +816,7 @@ TEST_F(SimpleLRUCacheTest, UpdateSize) { } TEST_F(SimpleLRUCacheTest, DontUpdateEvictionOrder) { - cache_.reset(new TestCache(kCacheSize)); + cache_ = std::make_unique(kCacheSize); int64_t original_start, original_end; SimpleLRUCacheOptions options; @@ -868,7 +874,7 @@ TEST_F(SimpleLRUCacheTest, DontUpdateEvictionOrder) { } TEST_F(SimpleLRUCacheTest, ScopedLookup) { - cache_.reset(new TestCache(kElems)); + cache_ = std::make_unique(kElems); for (int i = 0; i < kElems; i++) { ASSERT_TRUE(!cache_->lookup(i)); TestValue* v = new TestValue(i); @@ -898,7 +904,7 @@ TEST_F(SimpleLRUCacheTest, ScopedLookup) { TEST_F(SimpleLRUCacheTest, AgeOfLRUItemInMicroseconds) { // Make sure empty cache returns zero. - cache_.reset(new TestCache(kElems)); + cache_ = std::make_unique(kElems); ASSERT_EQ(cache_->ageOfLRUItemInMicroseconds(), 0); // Make sure non-empty cache doesn't return zero. @@ -933,7 +939,7 @@ TEST_F(SimpleLRUCacheTest, AgeOfLRUItemInMicroseconds) { } TEST_F(SimpleLRUCacheTest, GetLastUseTime) { - cache_.reset(new TestCache(kElems)); + cache_ = std::make_unique(kElems); int64_t now, last; // Make sure nonexistent key returns -1 @@ -992,7 +998,7 @@ TEST_F(SimpleLRUCacheTest, GetLastUseTime) { } TEST_F(SimpleLRUCacheTest, GetInsertionTime) { - cache_.reset(new TestCache(kElems)); + cache_ = std::make_unique(kElems); int64_t now, last; cache_->setAgeBasedEviction(-1); @@ -1049,7 +1055,7 @@ TEST_F(SimpleLRUCacheTest, GetInsertionTime) { ASSERT_EQ(cache_->getInsertionTime(2), -1); } -std::string StringPrintf(void* p, int pin, int defer) { +std::string stringPrintf(void* p, int pin, int defer) { std::stringstream ss; ss << std::hex << p << std::dec << ": pin: " << pin; ss << ", is_deferred: " << defer; @@ -1057,7 +1063,7 @@ std::string StringPrintf(void* p, int pin, int defer) { } TEST_F(SimpleLRUCacheTest, DebugOutput) { - cache_.reset(new TestCache(kCacheSize, false /* check_in_cache */)); + cache_ = std::make_unique(kCacheSize, false /* check_in_cache */); TestValue* v1 = new TestValue(0); cache_->insertPinned(0, v1, 1); TestValue* v2 = new TestValue(0); @@ -1067,16 +1073,16 @@ TEST_F(SimpleLRUCacheTest, DebugOutput) { std::string s; cache_->debugOutput(&s); - EXPECT_THAT(s, HasSubstr(StringPrintf(v1, 1, 1))); - EXPECT_THAT(s, HasSubstr(StringPrintf(v2, 1, 1))); - EXPECT_THAT(s, HasSubstr(StringPrintf(v3, 0, 0))); + EXPECT_THAT(s, HasSubstr(stringPrintf(v1, 1, 1))); + EXPECT_THAT(s, HasSubstr(stringPrintf(v2, 1, 1))); + EXPECT_THAT(s, HasSubstr(stringPrintf(v3, 0, 0))); cache_->release(0, v1); cache_->release(0, v2); } TEST_F(SimpleLRUCacheTest, LookupWithoutEvictionOrderUpdateAndRemove) { - cache_.reset(new TestCache(kCacheSize, false /* check_in_cache */)); + cache_ = std::make_unique(kCacheSize, false /* check_in_cache */); for (int i = 0; i < 3; ++i) { cache_->insert(i, new TestValue(0), 1); diff --git a/test/common/jwt/verify_jwk_ec_test.cc b/test/common/jwt/verify_jwk_ec_test.cc index 4e3886170b7ac..8a3d21c8fb234 100644 --- a/test/common/jwt/verify_jwk_ec_test.cc +++ b/test/common/jwt/verify_jwk_ec_test.cc @@ -180,7 +180,7 @@ const std::string JwtTextECNoKid = class VerifyJwkECTest : public testing::Test { protected: - void SetUp() { + void SetUp() override { jwks_ = Jwks::createFrom(PublicKeyJwkEC, Jwks::Type::JWKS); EXPECT_EQ(jwks_->getStatus(), Status::Ok); } diff --git a/test/common/jwt/verify_jwk_hmac_test.cc b/test/common/jwt/verify_jwk_hmac_test.cc index f05a13993f6fb..e689925ba53c3 100644 --- a/test/common/jwt/verify_jwk_hmac_test.cc +++ b/test/common/jwt/verify_jwk_hmac_test.cc @@ -129,7 +129,7 @@ const std::string JwtTextWithNonExistKid = class VerifyJwkHmacTest : public testing::Test { protected: - void SetUp() { + void SetUp() override { jwks_ = Jwks::createFrom(SymmetricKeyHMAC, Jwks::Type::JWKS); EXPECT_EQ(jwks_->getStatus(), Status::Ok); } diff --git a/test/common/jwt/verify_jwk_okp_test.cc b/test/common/jwt/verify_jwk_okp_test.cc index 9f6824b8ccce7..bf72504c5a13d 100644 --- a/test/common/jwt/verify_jwk_okp_test.cc +++ b/test/common/jwt/verify_jwk_okp_test.cc @@ -87,7 +87,7 @@ const std::string JwtJWKEd25519WrongSignature = class VerifyJwkOKPTest : public testing::Test { protected: - void SetUp() { + void SetUp() override { jwks_ = Jwks::createFrom(PublicKeyJwkOKP, Jwks::Type::JWKS); EXPECT_EQ(jwks_->getStatus(), Status::Ok); } diff --git a/test/common/jwt/verify_jwk_rsa_pss_test.cc b/test/common/jwt/verify_jwk_rsa_pss_test.cc index 0c510bb699e07..5cb708ed64106 100644 --- a/test/common/jwt/verify_jwk_rsa_pss_test.cc +++ b/test/common/jwt/verify_jwk_rsa_pss_test.cc @@ -205,7 +205,7 @@ const std::string Ps512JwtTextWithCorrectKid = class VerifyJwkRsaPssTest : public testing::Test { protected: - void SetUp() { + void SetUp() override { jwks_ = Jwks::createFrom(PublicKeyRSAPSS, Jwks::Type::JWKS); EXPECT_EQ(jwks_->getStatus(), Status::Ok); } @@ -328,7 +328,7 @@ const std::string JwtTextWithNonExistentKid = // verification. class VerifyKidMatchingTest : public testing::Test { protected: - void SetUp() { + void SetUp() override { correct_jwks_ = Jwks::createFrom(JwtIoPublicKeyRSAPSS, Jwks::Type::JWKS); EXPECT_EQ(correct_jwks_->getStatus(), Status::Ok); wrong_jwks_ = Jwks::createFrom(PublicKeyRSAPSS, Jwks::Type::JWKS); diff --git a/test/common/jwt/verify_jwk_rsa_test.cc b/test/common/jwt/verify_jwk_rsa_test.cc index aa293326fdecd..130e6f85ad3a0 100644 --- a/test/common/jwt/verify_jwk_rsa_test.cc +++ b/test/common/jwt/verify_jwk_rsa_test.cc @@ -329,7 +329,7 @@ const std::string JwtTextWithNonExistKid = class VerifyJwkRsaTest : public testing::Test { protected: - void SetUp() { + void SetUp() override { jwks_ = Jwks::createFrom(PublicKeyRSA, Jwks::Type::JWKS); EXPECT_EQ(jwks_->getStatus(), Status::Ok); } diff --git a/test/common/network/address_impl_test.cc b/test/common/network/address_impl_test.cc index 8e3a9fdd5fadb..a79c5007c1cae 100644 --- a/test/common/network/address_impl_test.cc +++ b/test/common/network/address_impl_test.cc @@ -30,7 +30,7 @@ namespace Address { namespace { Ipv6Instance v4MappedV6Instance(const std::string& address) { - return Ipv6Instance(address, /*port=*/0, /*sock_interface=*/nullptr, /*v6only=*/false); + return {address, /*port=*/0, /*sock_interface=*/nullptr, /*v6only=*/false}; } bool addressesEqual(const InstanceConstSharedPtr& a, const Instance& b) { diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index e8521f2427961..a6c0e76ad5973 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -4163,9 +4163,7 @@ TEST_F(MockTransportConnectionImplTest, UpdatingBufferHighWatermarkTimeoutToZero // Fixture for validating behavior after a connection is closed. class PostCloseConnectionImplTest : public MockTransportConnectionImplTest { public: - PostCloseConnectionImplTest() : MockTransportConnectionImplTest() { - MockTransportConnectionImplTest::initializeConnection(); - } + PostCloseConnectionImplTest() { MockTransportConnectionImplTest::initializeConnection(); } protected: // Setup connection, single read event. diff --git a/test/common/network/io_socket_handle_impl_benchmark_test.cc b/test/common/network/io_socket_handle_impl_benchmark_test.cc index 530c5728890ba..86347f0434fa0 100644 --- a/test/common/network/io_socket_handle_impl_benchmark_test.cc +++ b/test/common/network/io_socket_handle_impl_benchmark_test.cc @@ -42,7 +42,7 @@ class IoSocketHandleImplTestWrapper { IoSocketHandleImpl io_handle_; }; -static void BM_GetOrCreateEnvoyAddressInstanceNoCache(benchmark::State& state) { +static void bmGetOrCreateEnvoyAddressInstanceNoCache(benchmark::State& state) { std::vector addresses = Test::getSockAddrSampleAddresses(/*count=*/4); IoSocketHandleImplTestWrapper wrapper(/*cache_size=*/0); for (auto _ : state) { @@ -52,9 +52,9 @@ static void BM_GetOrCreateEnvoyAddressInstanceNoCache(benchmark::State& state) { } } } -BENCHMARK(BM_GetOrCreateEnvoyAddressInstanceNoCache)->Iterations(1000); +BENCHMARK(bmGetOrCreateEnvoyAddressInstanceNoCache)->Iterations(1000); -static void BM_GetOrCreateEnvoyAddressInstanceConnectedSocket(benchmark::State& state) { +static void bmGetOrCreateEnvoyAddressInstanceConnectedSocket(benchmark::State& state) { std::vector addresses = Test::getSockAddrSampleAddresses(/*count=*/4); IoSocketHandleImplTestWrapper wrapper(/*cache_size=*/4); for (auto _ : state) { @@ -64,9 +64,9 @@ static void BM_GetOrCreateEnvoyAddressInstanceConnectedSocket(benchmark::State& } } } -BENCHMARK(BM_GetOrCreateEnvoyAddressInstanceConnectedSocket)->Iterations(1000); +BENCHMARK(bmGetOrCreateEnvoyAddressInstanceConnectedSocket)->Iterations(1000); -static void BM_GetOrCreateEnvoyAddressInstanceUnconnectedSocket(benchmark::State& state) { +static void bmGetOrCreateEnvoyAddressInstanceUnconnectedSocket(benchmark::State& state) { std::vector addresses = Test::getSockAddrSampleAddresses(/*count=*/100); IoSocketHandleImplTestWrapper wrapper(/*cache_size=*/4); for (auto _ : state) { @@ -75,10 +75,9 @@ static void BM_GetOrCreateEnvoyAddressInstanceUnconnectedSocket(benchmark::State } } } -BENCHMARK(BM_GetOrCreateEnvoyAddressInstanceUnconnectedSocket)->Iterations(1000); +BENCHMARK(bmGetOrCreateEnvoyAddressInstanceUnconnectedSocket)->Iterations(1000); -static void -BM_GetOrCreateEnvoyAddressInstanceUnconnectedSocketLargerCache(benchmark::State& state) { +static void bmGetOrCreateEnvoyAddressInstanceUnconnectedSocketLargerCache(benchmark::State& state) { std::vector addresses = Test::getSockAddrSampleAddresses(/*count=*/100); IoSocketHandleImplTestWrapper wrapper(/*cache_size=*/50); for (auto _ : state) { @@ -87,7 +86,7 @@ BM_GetOrCreateEnvoyAddressInstanceUnconnectedSocketLargerCache(benchmark::State& } } } -BENCHMARK(BM_GetOrCreateEnvoyAddressInstanceUnconnectedSocketLargerCache)->Iterations(1000); +BENCHMARK(bmGetOrCreateEnvoyAddressInstanceUnconnectedSocketLargerCache)->Iterations(1000); } // namespace Network } // namespace Envoy diff --git a/test/common/network/lc_trie_ip_list_speed_test.cc b/test/common/network/lc_trie_ip_list_speed_test.cc index 62a4b1b6a0da5..2e053bdae0ef0 100644 --- a/test/common/network/lc_trie_ip_list_speed_test.cc +++ b/test/common/network/lc_trie_ip_list_speed_test.cc @@ -115,7 +115,7 @@ protobufToCidrRanges(const Protobuf::RepeatedPtrFieldRange(10, 5000)->Unit(benchmark::kNanosecond); -BENCHMARK(BM_LcTrieIpListMatching)->Range(10, 5000)->Unit(benchmark::kNanosecond); +BENCHMARK(bmLinearIpListMatching)->Range(10, 5000)->Unit(benchmark::kNanosecond); +BENCHMARK(bmLcTrieIpListMatching)->Range(10, 5000)->Unit(benchmark::kNanosecond); // Focused benchmarks for common RBAC policy sizes -BENCHMARK(BM_LinearIpListMatching)->Arg(25)->Arg(50)->Arg(100)->Arg(250)->Arg(500)->Arg(1000); -BENCHMARK(BM_LcTrieIpListMatching)->Arg(25)->Arg(50)->Arg(100)->Arg(250)->Arg(500)->Arg(1000); +BENCHMARK(bmLinearIpListMatching)->Arg(25)->Arg(50)->Arg(100)->Arg(250)->Arg(500)->Arg(1000); +BENCHMARK(bmLcTrieIpListMatching)->Arg(25)->Arg(50)->Arg(100)->Arg(250)->Arg(500)->Arg(1000); // IPv6 benchmarks for realistic dual-stack scenarios -BENCHMARK(BM_LinearIpListMatchingIPv6)->Arg(50)->Arg(200)->Arg(500); -BENCHMARK(BM_LcTrieIpListMatchingIPv6)->Arg(50)->Arg(200)->Arg(500); +BENCHMARK(bmLinearIpListMatchingIPv6)->Arg(50)->Arg(200)->Arg(500); +BENCHMARK(bmLcTrieIpListMatchingIPv6)->Arg(50)->Arg(200)->Arg(500); } // namespace Address } // namespace Network diff --git a/test/common/network/udp_listener_impl_batch_writer_test.cc b/test/common/network/udp_listener_impl_batch_writer_test.cc index a5ec75522d27b..12a512f18426f 100644 --- a/test/common/network/udp_listener_impl_batch_writer_test.cc +++ b/test/common/network/udp_listener_impl_batch_writer_test.cc @@ -114,7 +114,7 @@ TEST_P(UdpListenerImplBatchWriterTest, SendData) { EXPECT_EQ(send_result.return_value_, payload.length()); // Verify udp_packet_writer stats for batch writing - if (internal_buffer.length() == 0 || /* internal buffer is empty*/ + if (internal_buffer.empty() || /* internal buffer is empty*/ payload.compare(last_buffered) == 0) { /*len(payload) == gso_size*/ pkts_to_send.emplace_back(payload); internal_buffer.append(payload); @@ -142,7 +142,7 @@ TEST_P(UdpListenerImplBatchWriterTest, SendData) { total_bytes_sent += pkt.length(); } pkts_to_send.clear(); - if (last_buffered.length() != 0) { + if (!last_buffered.empty()) { pkts_to_send.emplace_back(last_buffered); } send_buffered_pkts = false; diff --git a/test/common/network/udp_listener_impl_test.cc b/test/common/network/udp_listener_impl_test.cc index d35b29249644b..28e550d322424 100644 --- a/test/common/network/udp_listener_impl_test.cc +++ b/test/common/network/udp_listener_impl_test.cc @@ -746,7 +746,7 @@ TEST_P(UdpListenerImplTest, UdpGroReadLimit) { std::string payload(1024, (i % 2 == 0 ? 'a' : 'b')); client_data.push_back(payload); // The concatenated payload received from kernel supporting udp_gro - stacked_message = absl::StrCat(stacked_message, payload); + absl::StrAppend(&stacked_message, payload); // Actually send the packets to trigger I/O events on the listener. client_.write(payload, *send_to_addr_); } diff --git a/test/common/quic/envoy_quic_h3_fuzz_test.cc b/test/common/quic/envoy_quic_h3_fuzz_test.cc index 358cb28214592..d348d8e4931bd 100644 --- a/test/common/quic/envoy_quic_h3_fuzz_test.cc +++ b/test/common/quic/envoy_quic_h3_fuzz_test.cc @@ -196,7 +196,7 @@ struct Harness { Http::Http3::CodecStats http3_stats_; quic::QuicConfig quic_config_; - envoy::config::core::v3::Http3ProtocolOptions http3_options_{}; + envoy::config::core::v3::Http3ProtocolOptions http3_options_; quic::DeterministicConnectionIdGenerator generator_{quic::kQuicDefaultConnectionIdLength}; quic::QuicCompressedCertsCache compressed_certs_cache_{100}; EnvoyQuicTestCryptoServerStreamFactory crypto_stream_factory_; diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc index 3e4d8dfb9a039..bff3cf476bf78 100644 --- a/test/common/quic/envoy_quic_proof_source_test.cc +++ b/test/common/quic/envoy_quic_proof_source_test.cc @@ -236,7 +236,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { } ASSERT_TRUE(secret_update_callback_ != nullptr); absl::Status callback_status = secret_update_callback_(); - THROW_IF_NOT_OK(callback_status); + THROW_IF_NOT_OK_REF(callback_status); ASSERT_TRUE(callback_status.ok()); } diff --git a/test/common/quic/envoy_quic_proof_verifier_test.cc b/test/common/quic/envoy_quic_proof_verifier_test.cc index 0c411a02f9447..a7aa923fffc1a 100644 --- a/test/common/quic/envoy_quic_proof_verifier_test.cc +++ b/test/common/quic/envoy_quic_proof_verifier_test.cc @@ -86,7 +86,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { EXPECT_CALL(cert_validation_ctx_config_, autoSniSanMatch()).WillRepeatedly(Return(false)); auto context_or_error = Extensions::TransportSockets::Tls::ClientContextImpl::create( *store_.rootScope(), client_context_config_, factory_context_); - THROW_IF_NOT_OK(context_or_error.status()); + THROW_IF_NOT_OK_REF(context_or_error.status()); verifier_ = std::make_unique(std::move(*context_or_error)); } diff --git a/test/common/quic/quic_stat_names_test.cc b/test/common/quic/quic_stat_names_test.cc index a1ac2224e8b1f..579ded37415eb 100644 --- a/test/common/quic/quic_stat_names_test.cc +++ b/test/common/quic/quic_stat_names_test.cc @@ -3,6 +3,7 @@ #include "source/common/quic/quic_stat_names.h" #include "test/common/stats/stat_test_utility.h" +#include "test/test_common/enum_test_utils.h" #include "gtest/gtest.h" @@ -30,9 +31,9 @@ TEST_F(QuicStatNamesTest, QuicConnectionCloseStats) { TEST_F(QuicStatNamesTest, OutOfRangeQuicConnectionCloseStats) { uint64_t bad_error_code = quic::QUIC_LAST_ERROR + 1; - quic_stat_names_.chargeQuicConnectionCloseStats(scope_, - static_cast(bad_error_code), - quic::ConnectionCloseSource::FROM_SELF, false); + quic_stat_names_.chargeQuicConnectionCloseStats( + scope_, uncheckedEnumCastForTest(bad_error_code), + quic::ConnectionCloseSource::FROM_SELF, false); EXPECT_EQ(1U, store_.counter("http3.downstream.tx.quic_connection_close_error_code_QUIC_LAST_ERROR") .value()); @@ -51,7 +52,7 @@ TEST_F(QuicStatNamesTest, OutOfRangeResetStreamErrorStats) { quic_stat_names_.chargeQuicResetStreamErrorStats( scope_, quic::QuicResetStreamError::FromInternal( - static_cast(bad_error_code)), + uncheckedEnumCastForTest(bad_error_code)), true, false); EXPECT_EQ( 1U, store_.counter("http3.downstream.tx.quic_reset_stream_error_code_QUIC_STREAM_LAST_ERROR") diff --git a/test/common/runtime/runtime_impl_test.cc b/test/common/runtime/runtime_impl_test.cc index 48470c7a6acf2..64a0dc80f86e1 100644 --- a/test/common/runtime/runtime_impl_test.cc +++ b/test/common/runtime/runtime_impl_test.cc @@ -98,7 +98,7 @@ class DiskLoaderImplTest : public LoaderImplTest { absl::StatusOr> loader = Runtime::LoaderImpl::create(dispatcher_, tls_, layered_runtime, local_info_, store_, generator_, validation_visitor_, *api_); - THROW_IF_NOT_OK(loader.status()); + THROW_IF_NOT_OK_REF(loader.status()); loader_ = std::move(loader.value()); } @@ -545,7 +545,7 @@ class StaticLoaderImplTest : public LoaderImplTest { absl::StatusOr> loader = Runtime::LoaderImpl::create(dispatcher_, tls_, layered_runtime, local_info_, store_, generator_, validation_visitor_, *api_); - THROW_IF_NOT_OK(loader.status()); + THROW_IF_NOT_OK_REF(loader.status()); loader_ = std::move(loader.value()); } @@ -947,7 +947,7 @@ class RtdsLoaderImplTest : public LoaderImplTest { })); absl::StatusOr> loader = Runtime::LoaderImpl::create( dispatcher_, tls_, config, local_info_, store_, generator_, validation_visitor_, *api_); - THROW_IF_NOT_OK(loader.status()); + THROW_IF_NOT_OK_REF(loader.status()); loader_ = std::move(loader.value()); THROW_IF_NOT_OK(loader_->initialize(cm_)); for (auto* sub : rtds_subscriptions_) { diff --git a/test/common/stats/refcount_ptr_test.cc b/test/common/stats/refcount_ptr_test.cc index ff729b09ecbf2..448bd2fbc66d3 100644 --- a/test/common/stats/refcount_ptr_test.cc +++ b/test/common/stats/refcount_ptr_test.cc @@ -61,7 +61,7 @@ TEST(RefcountPtr, Operators) { EXPECT_EQ(&ref, ptr); SharedString shared2(new RefcountedString("Hello, World!")); EXPECT_NE(&ref, shared2.get()); - SharedString shared3(shared2.get()); + SharedString shared3(shared2); EXPECT_EQ(shared2, shared3); EXPECT_EQ(2, shared2.use_count()); shared2.reset(); diff --git a/test/common/tcp/async_tcp_client_impl_test.cc b/test/common/tcp/async_tcp_client_impl_test.cc index 3199276de6f2f..b8642718a695e 100644 --- a/test/common/tcp/async_tcp_client_impl_test.cc +++ b/test/common/tcp/async_tcp_client_impl_test.cc @@ -19,7 +19,7 @@ namespace Tcp { class CustomMockClientConnection : public Network::MockClientConnection { public: - ~CustomMockClientConnection() { + ~CustomMockClientConnection() override { if (state_ != Connection::State::Closed) { raiseEvent(Network::ConnectionEvent::LocalClose); } diff --git a/test/common/tcp/conn_pool_test.cc b/test/common/tcp/conn_pool_test.cc index 9db2ce2c4960f..01972bb693e33 100644 --- a/test/common/tcp/conn_pool_test.cc +++ b/test/common/tcp/conn_pool_test.cc @@ -72,7 +72,7 @@ struct ConnPoolCallbacks : public Tcp::ConnectionPool::Callbacks { StrictMock callbacks_; testing::MockFunction mock_pool_failure_cb_; testing::MockFunction mock_pool_ready_cb_; - ConnectionPool::ConnectionDataPtr conn_data_{}; + ConnectionPool::ConnectionDataPtr conn_data_; absl::optional reason_; std::string failure_reason_string_; Upstream::HostDescriptionConstSharedPtr host_; diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 236ab9f2ff9db..dd082866afedb 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -2610,8 +2610,8 @@ TEST_P(TcpProxyTest, SetDynamicTLVWithStartTime) { const std::string timestamp_value(tlvs[0].value.begin(), tlvs[0].value.end()); EXPECT_FALSE(timestamp_value.empty()); // Should contain date-like characters. - EXPECT_TRUE(timestamp_value.find("-") != std::string::npos || - timestamp_value.find(":") != std::string::npos); + EXPECT_TRUE(timestamp_value.find('-') != std::string::npos || + timestamp_value.find(':') != std::string::npos); } // Test buffer overflow behavior - should only readDisable, not re-trigger connection. diff --git a/test/common/tcp_proxy/tcp_proxy_test_base.h b/test/common/tcp_proxy/tcp_proxy_test_base.h index 90196b22d7f76..4d4adaea9a765 100644 --- a/test/common/tcp_proxy/tcp_proxy_test_base.h +++ b/test/common/tcp_proxy/tcp_proxy_test_base.h @@ -178,10 +178,10 @@ class TcpProxyTestBase ConfigSharedPtr config_; NiceMock filter_callbacks_; std::unique_ptr filter_; - std::vector>> upstream_hosts_{}; - std::vector>> upstream_connections_{}; + std::vector>> upstream_hosts_; + std::vector>> upstream_connections_; std::vector>> - upstream_connection_data_{}; + upstream_connection_data_; std::vector conn_pool_callbacks_; std::vector>> conn_pool_handles_; NiceMock conn_pool_; @@ -191,8 +191,8 @@ class TcpProxyTestBase Network::Address::InstanceConstSharedPtr upstream_remote_address_; std::list> new_connection_functions_; - Upstream::HostDescriptionConstSharedPtr upstream_host_{}; - Upstream::ClusterInfoConstSharedPtr upstream_cluster_{}; + Upstream::HostDescriptionConstSharedPtr upstream_host_; + Upstream::ClusterInfoConstSharedPtr upstream_cluster_; std::string redirect_records_data_ = "some data"; TestScopedRuntime scoped_runtime_; }; diff --git a/test/common/tls/cert_validator/test_common.h b/test/common/tls/cert_validator/test_common.h index 8ae3684912c93..41c15b67e6993 100644 --- a/test/common/tls/cert_validator/test_common.h +++ b/test/common/tls/cert_validator/test_common.h @@ -122,7 +122,7 @@ class TestCertificateValidationContextConfig Api::ApiPtr api_; const absl::optional custom_validator_config_; const std::vector - san_matchers_{}; + san_matchers_; const std::string ca_cert_; const std::string ca_cert_path_{"TEST_CA_CERT_PATH"}; const std::string ca_cert_name_{"TEST_CA_CERT_NAME"}; diff --git a/test/common/tls/integration/ssl_integration_test_base.h b/test/common/tls/integration/ssl_integration_test_base.h index 157639c03b439..141c9fe5cde53 100644 --- a/test/common/tls/integration/ssl_integration_test_base.h +++ b/test/common/tls/integration/ssl_integration_test_base.h @@ -20,6 +20,7 @@ class SslIntegrationTestBase : public HttpIntegrationTest { void initialize() override; + // NOLINTNEXTLINE(readability-identifier-naming) void TearDown(); Network::ClientConnectionPtr makeSslConn() { return makeSslClientConnection({}); } diff --git a/test/common/tls/test_private_key_method_provider.h b/test/common/tls/test_private_key_method_provider.h index f31aac5657258..6406a10dc551b 100644 --- a/test/common/tls/test_private_key_method_provider.h +++ b/test/common/tls/test_private_key_method_provider.h @@ -78,7 +78,7 @@ class TestPrivateKeyMethodProvider : public virtual Ssl::PrivateKeyMethodProvide static int ecdsaConnectionIndex(); private: - Ssl::BoringSslPrivateKeyMethodSharedPtr method_{}; + Ssl::BoringSslPrivateKeyMethodSharedPtr method_; bssl::UniquePtr pkey_; TestPrivateKeyConnectionTestOptions test_options_; std::string mode_; diff --git a/test/common/upstream/health_check_fuzz_test_utils.h b/test/common/upstream/health_check_fuzz_test_utils.h index 4e8b9fc071866..3812305ffc8a0 100644 --- a/test/common/upstream/health_check_fuzz_test_utils.h +++ b/test/common/upstream/health_check_fuzz_test_utils.h @@ -71,9 +71,9 @@ class HttpHealthCheckerImplTestBase : public HealthCheckerTestBase { std::vector test_sessions_; std::shared_ptr health_checker_; - std::list connection_index_{}; - std::list codec_index_{}; - const HostWithHealthCheckMap health_checker_map_{}; + std::list connection_index_; + std::list codec_index_; + const HostWithHealthCheckMap health_checker_map_; }; // TODO(zasweq): This class here isn't currently being used in the unit test class. @@ -131,8 +131,8 @@ class GrpcHealthCheckerImplTestBaseUtils : public HealthCheckerTestBase { std::vector test_sessions_; std::shared_ptr health_checker_; - std::list connection_index_{}; - std::list codec_index_{}; + std::list connection_index_; + std::list codec_index_; }; } // namespace Upstream diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 7d64db788237b..0fba2866a996f 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -869,9 +869,9 @@ class HttpHealthCheckerImplTest : public Event::TestUsingSimulatedTime, std::vector test_sessions_; std::shared_ptr health_checker_; - std::list connection_index_{}; - std::list codec_index_{}; - const HostWithHealthCheckMap health_checker_map_{}; + std::list connection_index_; + std::list codec_index_; + const HostWithHealthCheckMap health_checker_map_; }; TEST_F(HttpHealthCheckerImplTest, Success) { @@ -5272,8 +5272,8 @@ class GrpcHealthCheckerImplTestBase : public Event::TestUsingSimulatedTime, std::vector test_sessions_; std::shared_ptr health_checker_; - std::list connection_index_{}; - std::list codec_index_{}; + std::list connection_index_; + std::list codec_index_; }; // NOLINTNEXTLINE(readability-identifier-naming) diff --git a/test/common/upstream/transport_socket_input_test.cc b/test/common/upstream/transport_socket_input_test.cc index f4f0e5b7b286a..e379ed4b97210 100644 --- a/test/common/upstream/transport_socket_input_test.cc +++ b/test/common/upstream/transport_socket_input_test.cc @@ -13,7 +13,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::HasSubstr; using testing::NiceMock; namespace Envoy { diff --git a/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc b/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc index b675b6e4290ef..02de5896b054d 100644 --- a/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc +++ b/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc @@ -66,6 +66,7 @@ class FluentdAccessLoggerImplTest : public testing::Test { packer.pack_bin_body(record_bytes, data_.size()); } + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(buffer.data(), buffer.size()); } diff --git a/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc b/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc index 00b3669c0ccc7..bce5c561f19d3 100644 --- a/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc +++ b/test/extensions/access_loggers/open_telemetry/otlp_log_utils_test.cc @@ -16,7 +16,6 @@ namespace OpenTelemetry { namespace { using testing::NiceMock; -using testing::Return; using testing::ReturnRef; const std::string kTestZone = "test_zone"; diff --git a/test/extensions/access_loggers/stats/stats_speed_test.cc b/test/extensions/access_loggers/stats/stats_speed_test.cc index ef40405530966..538a7b37bd42c 100644 --- a/test/extensions/access_loggers/stats/stats_speed_test.cc +++ b/test/extensions/access_loggers/stats/stats_speed_test.cc @@ -26,8 +26,9 @@ class AccessLogStateUsingJoiner { void addInflightGauge(Stats::StatName stat_name, Stats::StatNameTagVectorOptConstRef tags, Stats::Gauge::ImportMode import_mode, uint64_t value, std::vector tags_storage) { - if (value == 0) + if (value == 0) { return; + } Stats::TagUtility::TagStatNameJoiner joiner(Stats::StatName(), stat_name, tags, logger_->scope().symbolTable()); @@ -44,8 +45,9 @@ class AccessLogStateUsingJoiner { void removeInflightGauge(Stats::StatName stat_name, Stats::StatNameTagVectorOptConstRef tags, Stats::Gauge::ImportMode import_mode, uint64_t value) { - if (value == 0) + if (value == 0) { return; + } Stats::TagUtility::TagStatNameJoiner joiner(Stats::StatName(), stat_name, tags, logger_->scope().symbolTable()); @@ -152,24 +154,24 @@ static void runBenchmark(benchmark::State& state, SharedBencherSetup& setup, T& } // --- Reality Benchmark --- -static void BM_AccessLogState(benchmark::State& state) { +static void bmAccessLogState(benchmark::State& state) { SharedBencherSetup setup; auto access_log_state = std::make_shared(setup.logger_); runBenchmark(state, setup, *access_log_state); } -BENCHMARK(BM_AccessLogState) +BENCHMARK(bmAccessLogState) ->Args({/*tag_count=*/3, /*length_selector=*/0}) ->Args({/*tag_count=*/10, /*length_selector=*/0}) ->Args({/*tag_count=*/3, /*length_selector=*/1}) ->Args({/*tag_count=*/10, /*length_selector=*/1}); // --- Joiner Benchmark --- -static void BM_AccessLogStateUsingJoiner(benchmark::State& state) { +static void bmAccessLogStateUsingJoiner(benchmark::State& state) { SharedBencherSetup setup; AccessLogStateUsingJoiner access_log_state(setup.logger_); runBenchmark(state, setup, access_log_state); } -BENCHMARK(BM_AccessLogStateUsingJoiner) +BENCHMARK(bmAccessLogStateUsingJoiner) ->Args({/*tag_count=*/3, /*length_selector=*/0}) ->Args({/*tag_count=*/10, /*length_selector=*/0}) ->Args({/*tag_count=*/3, /*length_selector=*/1}) diff --git a/test/extensions/access_loggers/stats/stats_test.cc b/test/extensions/access_loggers/stats/stats_test.cc index 9d2f84ef1ac6e..66bb9329c445f 100644 --- a/test/extensions/access_loggers/stats/stats_test.cc +++ b/test/extensions/access_loggers/stats/stats_test.cc @@ -56,8 +56,9 @@ class MockGaugeWithTags : public Stats::MockGauge { void iterateTagStatNames(const TagStatNameIterFn& fn) const override { for (const auto& tag : tags_storage_) { - if (!fn(tag.first->statName(), tag.second->statName())) + if (!fn(tag.first->statName(), tag.second->statName())) { return; + } } } diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc index cd63ad988869a..0e31388b6f807 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/rc_connection_wrapper_test.cc @@ -60,7 +60,7 @@ class RCConnectionWrapperTest : public testing::Test { void TearDown() override { io_handle_.reset(); extension_.reset(); - while (dispatcher_.to_delete_.size()) { + while (!dispatcher_.to_delete_.empty()) { dispatcher_.to_delete_.pop_front(); } } diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_address_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_address_test.cc index 580399b5e58e9..4de5f0a03f9ef 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_address_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_address_test.cc @@ -9,10 +9,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::Return; -using testing::ReturnRef; - namespace Envoy { namespace Extensions { namespace Bootstrap { @@ -29,9 +25,7 @@ class ReverseConnectionAddressTest : public testing::Test { } // Helper function to create a test address. - ReverseConnectionAddress createTestAddress() { - return ReverseConnectionAddress(createTestConfig()); - } + ReverseConnectionAddress createTestAddress() { return {createTestConfig()}; } // Set log level to debug for this test class. LogLevelSetter log_level_setter_ = LogLevelSetter(spdlog::level::debug); @@ -297,6 +291,7 @@ TEST_F(ReverseConnectionAddressTest, CopyAndAssignment) { ReverseConnectionAddress original(config); // Test copy constructor. + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) ReverseConnectionAddress copied(original); EXPECT_TRUE(original == copied); EXPECT_EQ(original.logicalName(), copied.logicalName()); diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc index e5d23f499634e..77765b8038f2d 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_io_handle_test.cc @@ -78,7 +78,7 @@ class ReverseConnectionIOHandleTest : public testing::Test { } extension_.reset(); socket_interface_.reset(); - while (dispatcher_.to_delete_.size()) { + while (!dispatcher_.to_delete_.empty()) { dispatcher_.to_delete_.pop_front(); } } diff --git a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_resolver_test.cc b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_resolver_test.cc index ab7ebfc075e32..e4efd98105fd3 100644 --- a/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_resolver_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/downstream_socket_interface/reverse_connection_resolver_test.cc @@ -7,10 +7,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::Return; -using testing::ReturnRef; - namespace Envoy { namespace Extensions { namespace Bootstrap { diff --git a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension_test.cc b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension_test.cc index 7bca8bae663df..a48a7698ae92f 100644 --- a/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension_test.cc +++ b/test/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension_test.cc @@ -475,7 +475,7 @@ TEST_F(ReverseTunnelAcceptorExtensionTest, MissThresholdOneMarksDeadOnFirstInval UpstreamReverseConnectionSocketInterface cfg; cfg.set_stat_prefix("test_prefix"); cfg.mutable_ping_failure_threshold()->set_value(1); - extension_.reset(new ReverseTunnelAcceptorExtension(*socket_interface_, context_, cfg)); + extension_ = std::make_unique(*socket_interface_, context_, cfg); // Provide dispatcher to thread local and set expectations for timers/file events. thread_local_.setDispatcher(&dispatcher_); diff --git a/test/extensions/bootstrap/wasm/test_data/speed_cpp.cc b/test/extensions/bootstrap/wasm/test_data/speed_cpp.cc index d2b93379418bb..04372f9de8b83 100644 --- a/test/extensions/bootstrap/wasm/test_data/speed_cpp.cc +++ b/test/extensions/bootstrap/wasm/test_data/speed_cpp.cc @@ -83,7 +83,7 @@ bool base64Decode(const std::basic_string& input, std::vector* ou return false; } size_t padding = 0; - if (input.length()) { + if (!input.empty()) { if (input[input.length() - 1] == padCharacter) { padding++; } diff --git a/test/extensions/bootstrap/wasm/wasm_test.cc b/test/extensions/bootstrap/wasm/wasm_test.cc index 6242ae2225308..3c516c628a704 100644 --- a/test/extensions/bootstrap/wasm/wasm_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_test.cc @@ -79,7 +79,7 @@ class WasmTestBase { std::string vm_configuration_; std::string vm_key_; proxy_wasm::AllowedCapabilitiesMap allowed_capabilities_; - Extensions::Common::Wasm::EnvironmentVariableMap envs_{}; + Extensions::Common::Wasm::EnvironmentVariableMap envs_; std::string plugin_configuration_; std::shared_ptr plugin_; std::shared_ptr wasm_; diff --git a/test/extensions/clusters/common/logical_host_integration_test.cc b/test/extensions/clusters/common/logical_host_integration_test.cc index 003c8a17fb3b8..b6ab47e82d34f 100644 --- a/test/extensions/clusters/common/logical_host_integration_test.cc +++ b/test/extensions/clusters/common/logical_host_integration_test.cc @@ -20,7 +20,7 @@ class LogicalHostIntegrationTest : public testing::TestWithParam(); + auto address_ptr = std::make_shared
(); address_ptr->address_ = 0; // first_address_string_ is used to make connections. It needs // to match with the IpVersion of the test. diff --git a/test/extensions/clusters/composite/cluster_test.cc b/test/extensions/clusters/composite/cluster_test.cc index d8004fecfef62..1fb8251fdccba 100644 --- a/test/extensions/clusters/composite/cluster_test.cc +++ b/test/extensions/clusters/composite/cluster_test.cc @@ -49,7 +49,7 @@ class CompositeClusterTest : public testing::Test { absl::Status creation_status = absl::OkStatus(); cluster_ = std::shared_ptr( new Cluster(cluster_config_, config_, factory_context, creation_status)); - THROW_IF_NOT_OK(creation_status); + THROW_IF_NOT_OK_REF(creation_status); } envoy::config::cluster::v3::Cluster cluster_config_; diff --git a/test/extensions/clusters/redis/redis_cluster_lb_test.cc b/test/extensions/clusters/redis/redis_cluster_lb_test.cc index 65a1fd86a1b2e..25fee45c76f76 100644 --- a/test/extensions/clusters/redis/redis_cluster_lb_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_lb_test.cc @@ -76,8 +76,9 @@ class RedisClusterLoadBalancerTest : public Event::TestUsingSimulatedTime, publi } } - static std::pair makePair(Upstream::HostSharedPtr host) { - return std::make_pair(host->address()->asString(), std::move(host)); + static std::pair + makePair(const Upstream::HostSharedPtr& host) { + return {host->address()->asString(), host}; } Upstream::HostMap generateHostMap(Upstream::HostVector& hosts) { diff --git a/test/extensions/common/aws/credential_providers/iam_roles_anywhere_credentials_provider_test.cc b/test/extensions/common/aws/credential_providers/iam_roles_anywhere_credentials_provider_test.cc index fc5977dc0b15b..f5f7c94c3c416 100644 --- a/test/extensions/common/aws/credential_providers/iam_roles_anywhere_credentials_provider_test.cc +++ b/test/extensions/common/aws/credential_providers/iam_roles_anywhere_credentials_provider_test.cc @@ -200,7 +200,7 @@ class IamRolesAnywhereCredentialsProviderTest : public testing::Test { DataSourceOptRef cert_chain_opt; - if (chain != "") { + if (!chain.empty()) { auto chain_env = std::string("CHAIN"); TestEnvironment::setEnvVar(chain_env, chain, 1); yaml = fmt::format(R"EOF( diff --git a/test/extensions/common/aws/eventstream/eventstream_parser_test.cc b/test/extensions/common/aws/eventstream/eventstream_parser_test.cc index a6385c012943b..f6e8f48d33c4d 100644 --- a/test/extensions/common/aws/eventstream/eventstream_parser_test.cc +++ b/test/extensions/common/aws/eventstream/eventstream_parser_test.cc @@ -59,6 +59,7 @@ std::string createEventstreamMessage(const std::string& headers_data, const std: message[total_length - 2] = (message_crc >> 8) & 0xFF; message[total_length - 1] = message_crc & 0xFF; + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(reinterpret_cast(message.data()), total_length); } @@ -157,14 +158,16 @@ TEST_F(EventstreamParserTest, ParseMessageWithHeadersAndPayload) { std::vector header_bytes; std::string header_name = ":message-type"; header_bytes.push_back(static_cast(header_name.size())); - for (char c : header_name) + for (char c : header_name) { header_bytes.push_back(c); + } header_bytes.push_back(7); // type = string std::string header_value = "event"; header_bytes.push_back(0); header_bytes.push_back(static_cast(header_value.size())); - for (char c : header_value) + for (char c : header_value) { header_bytes.push_back(c); + } std::string headers_data(reinterpret_cast(header_bytes.data()), header_bytes.size()); std::string payload = "test payload"; @@ -262,8 +265,9 @@ TEST_F(EventstreamParserTest, ParseMessageAllHeaderTypes) { header_bytes.push_back(1); header_bytes.push_back('u'); header_bytes.push_back(9); // UUID - for (int i = 0; i < 16; i++) + for (int i = 0; i < 16; i++) { header_bytes.push_back(i); + } std::string headers_data(reinterpret_cast(header_bytes.data()), header_bytes.size()); std::string msg = createEventstreamMessage(headers_data, ""); diff --git a/test/extensions/common/dubbo/codec_test.cc b/test/extensions/common/dubbo/codec_test.cc index 89eb86df8c6fb..c21eb96bc53eb 100644 --- a/test/extensions/common/dubbo/codec_test.cc +++ b/test/extensions/common/dubbo/codec_test.cc @@ -4,6 +4,7 @@ #include "source/extensions/common/dubbo/message.h" #include "test/extensions/common/dubbo/mocks.h" +#include "test/test_common/enum_test_utils.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" @@ -328,7 +329,7 @@ TEST(DubboCodecTest, DecodeDataTest) { MessageMetadata metadata; auto context = std::make_unique(); - context->setMessageType(static_cast(6)); + context->setMessageType(uncheckedEnumCastForTest(6)); context->setRequestId(1); context->setBodySize(buffer.length()); context->setResponseStatus(ResponseStatus::Ok); @@ -603,7 +604,7 @@ TEST(DubboCodecTest, EncodeTest) { MessageMetadata metadata; auto context = std::make_unique(); - context->setMessageType(static_cast(6)); + context->setMessageType(uncheckedEnumCastForTest(6)); context->setResponseStatus(ResponseStatus::Ok); context->setRequestId(12345); @@ -621,7 +622,7 @@ TEST(DubboCodecTest, EncodeHeaderForTestTest) { Buffer::OwnedImpl buffer; auto context = std::make_unique(); - context->setMessageType(static_cast(6)); + context->setMessageType(uncheckedEnumCastForTest(6)); context->setResponseStatus(ResponseStatus::Ok); context->setRequestId(12345); diff --git a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc index 2604233951e1c..752ec5e6f683b 100644 --- a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc +++ b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc @@ -60,7 +60,7 @@ class DnsCacheImplTest : public testing::Test, public Event::TestUsingSimulatedT } ~DnsCacheImplTest() override { - if (dns_cache_.get()) { + if (dns_cache_) { dns_cache_.reset(); EXPECT_EQ(0, TestUtility::findGauge(context_.store_, "dns_cache.foo.num_hosts")->value()); } diff --git a/test/extensions/common/redis/cluster_refresh_manager_test.cc b/test/extensions/common/redis/cluster_refresh_manager_test.cc index acff419cdbbea..ae3aa4ef28dab 100644 --- a/test/extensions/common/redis/cluster_refresh_manager_test.cc +++ b/test/extensions/common/redis/cluster_refresh_manager_test.cc @@ -107,9 +107,9 @@ class ClusterRefreshManagerTest : public testing::Test, public Event::TestUsingS Event::SimulatedTimeSystem time_system_; std::shared_ptr refresh_manager_; ClusterRefreshManager::HandlePtr handle_; - std::atomic callback_count_{}; - std::atomic nthreads_waiting_{}; - std::atomic nthreads_going_{}; + std::atomic callback_count_{0}; + std::atomic nthreads_waiting_{0}; + std::atomic nthreads_going_{0}; Thread::CondVar wait_cv_; Thread::CondVar setter_wait_cv_; Thread::MutexBasicLockable time_mutex_; diff --git a/test/extensions/common/wasm/test_data/test_cpp.cc b/test/extensions/common/wasm/test_data/test_cpp.cc index d261a55872d50..5dffb980ff86d 100644 --- a/test/extensions/common/wasm/test_data/test_cpp.cc +++ b/test/extensions/common/wasm/test_data/test_cpp.cc @@ -107,8 +107,9 @@ WASM_EXPORT(uint32_t, proxy_on_vm_start, (uint32_t context_id, uint32_t configur name = "test_historam"; CHECK_RESULT(proxy_define_metric(MetricType::Histogram, name.data(), name.size(), &h)); // Bad type. - CHECK_RESULT_NOT_OK( - proxy_define_metric(static_cast(9999), name.data(), name.size(), &c)); + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + const auto bad_metric_type = static_cast(9999); + CHECK_RESULT_NOT_OK(proxy_define_metric(bad_metric_type, name.data(), name.size(), &c)); CHECK_RESULT(proxy_increment_metric(c, 1)); CHECK_RESULT(proxy_increment_metric(g, 1)); diff --git a/test/extensions/compression/gzip/decompressor/zlib_decompressor_impl_test.cc b/test/extensions/compression/gzip/decompressor/zlib_decompressor_impl_test.cc index e9e8fb95d3628..d05900e3115fa 100644 --- a/test/extensions/compression/gzip/decompressor/zlib_decompressor_impl_test.cc +++ b/test/extensions/compression/gzip/decompressor/zlib_decompressor_impl_test.cc @@ -61,7 +61,7 @@ class ZlibDecompressorImplTest : public testing::Test { static constexpr int64_t memory_level{8}; static constexpr uint64_t default_input_size{796}; - Stats::IsolatedStoreImpl stats_store_{}; + Stats::IsolatedStoreImpl stats_store_; Stats::Scope& stats_scope_{*stats_store_.rootScope()}; }; @@ -338,7 +338,7 @@ class ZlibDecompressorStatsTest : public testing::Test { protected: void chargeErrorStats(const int result) { decompressor_.chargeErrorStats(result); } - Stats::IsolatedStoreImpl stats_store_{}; + Stats::IsolatedStoreImpl stats_store_; Stats::Scope& stats_scope_{*stats_store_.rootScope()}; ZlibDecompressorImpl decompressor_{stats_scope_, "test.", 4096, 100}; }; diff --git a/test/extensions/compression/zstd/decompressor/zstd_decompressor_impl_test.cc b/test/extensions/compression/zstd/decompressor/zstd_decompressor_impl_test.cc index 0a1ea50af841b..09f5226048691 100644 --- a/test/extensions/compression/zstd/decompressor/zstd_decompressor_impl_test.cc +++ b/test/extensions/compression/zstd/decompressor/zstd_decompressor_impl_test.cc @@ -182,7 +182,7 @@ class ZstdDecompressorStatsTest : public testing::Test { protected: bool isError(size_t result) { return decompressor_.isError(result); } - Stats::IsolatedStoreImpl stats_store_{}; + Stats::IsolatedStoreImpl stats_store_; ZstdDDictManagerPtr ddict_manager_{nullptr}; ZstdDecompressorImpl decompressor_{*stats_store_.rootScope(), "test.", ddict_manager_, 16}; }; diff --git a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc index 8a5431debc91d..a6313ba33042a 100644 --- a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc @@ -98,12 +98,12 @@ class NewGrpcMuxImplTestBase : public testing::TestWithParam resource_names_subscribe = {}; - std::vector resource_names_unsubscribe = {}; + std::vector resource_names_subscribe; + std::vector resource_names_unsubscribe; std::string nonce = ""; Protobuf::int32 error_code = Grpc::Status::WellKnownGrpcStatus::Ok; std::string error_message = ""; - std::map initial_resource_versions = {}; + std::map initial_resource_versions; Grpc::MockAsyncStream* async_stream = nullptr; bool with_node = true; }; diff --git a/test/extensions/config_subscription/grpc/sotw_subscription_state_test.cc b/test/extensions/config_subscription/grpc/sotw_subscription_state_test.cc index 0edb24b57a432..4e9a9760a8126 100644 --- a/test/extensions/config_subscription/grpc/sotw_subscription_state_test.cc +++ b/test/extensions/config_subscription/grpc/sotw_subscription_state_test.cc @@ -8,6 +8,7 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" +#include "test/test_common/enum_test_utils.h" #include "test/test_common/simulated_time_system.h" #include "absl/container/flat_hash_set.h" @@ -68,7 +69,7 @@ class TestXdsResourcesDelegate : public XdsResourcesDelegate { // If the bad resource is requested, set an invalid enum value (1000) for the Policy's // drop percentage denominator. cla.mutable_policy()->add_drop_overloads()->mutable_drop_percentage()->set_denominator( - static_cast(1000)); + uncheckedEnumCastForTest(1000)); } envoy::service::discovery::v3::Resource resource; diff --git a/test/extensions/dynamic_modules/listener/abi_impl_test.cc b/test/extensions/dynamic_modules/listener/abi_impl_test.cc index ffa26586ff59c..9b0c3d5d2706c 100644 --- a/test/extensions/dynamic_modules/listener/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/listener/abi_impl_test.cc @@ -1321,7 +1321,7 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, WriteToSocketSuccess) { .WillOnce(testing::Invoke([](Buffer::Instance& buffer) -> Api::IoCallUint64Result { uint64_t len = buffer.length(); buffer.drain(len); - return Api::IoCallUint64Result(len, Api::IoError::none()); + return {len, Api::IoError::none()}; })); char data[] = "S"; @@ -1338,7 +1338,7 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, WriteToSocketMultipleBytes) { .WillOnce(testing::Invoke([](Buffer::Instance& buffer) -> Api::IoCallUint64Result { uint64_t len = buffer.length(); buffer.drain(len); - return Api::IoCallUint64Result(len, Api::IoError::none()); + return {len, Api::IoError::none()}; })); char data[] = "hello world"; @@ -1380,7 +1380,7 @@ TEST_F(DynamicModuleListenerFilterAbiCallbackTest, WriteToSocketIoError) { EXPECT_CALL(callbacks_.socket_, ioHandle()).WillOnce(testing::ReturnRef(io_handle)); EXPECT_CALL(io_handle, write(testing::_)) .WillOnce(testing::Invoke([](Buffer::Instance&) -> Api::IoCallUint64Result { - return Api::IoCallUint64Result(0, Network::IoSocketError::create(ECONNRESET)); + return {0, Network::IoSocketError::create(ECONNRESET)}; })); char data[] = "S"; diff --git a/test/extensions/filters/common/expr/evaluator_test.cc b/test/extensions/filters/common/expr/evaluator_test.cc index 6d7ce6dab9bc7..bff0fd5934fe1 100644 --- a/test/extensions/filters/common/expr/evaluator_test.cc +++ b/test/extensions/filters/common/expr/evaluator_test.cc @@ -15,8 +15,6 @@ namespace Common { namespace Expr { namespace { -using ::testing::MatchesRegex; - TEST(Evaluator, Print) { EXPECT_EQ(print(CelValue::CreateBool(false)), "false"); EXPECT_EQ(print(CelValue::CreateInt64(123)), "123"); diff --git a/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc b/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc index 4b576e69daad6..2382471ad6b2e 100644 --- a/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc +++ b/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc @@ -316,7 +316,7 @@ TEST_F(ExtAuthzGrpcClientTest, GrpcCallFailureDoesNotSetStatusCode) { // Expected: status_code should be unset (0), not Forbidden. auto expected_response = Response{}; expected_response.status = CheckStatus::Error; - expected_response.status_code = static_cast(0); // Unset + expected_response.status_code = {}; // Unset expected_response.grpc_status = grpc_status; envoy::service::auth::v3::CheckRequest request; diff --git a/test/extensions/filters/http/bandwidth_share/fair_token_bucket_impl_test.cc b/test/extensions/filters/http/bandwidth_share/fair_token_bucket_impl_test.cc index af610c25dc9c2..3d90f5315ce18 100644 --- a/test/extensions/filters/http/bandwidth_share/fair_token_bucket_impl_test.cc +++ b/test/extensions/filters/http/bandwidth_share/fair_token_bucket_impl_test.cc @@ -17,7 +17,7 @@ auto IsBetween = [](uint64_t min, uint64_t max) { }; class ClientTest : public testing::Test { - void SetUp() { + void SetUp() override { // Avoid starting at time zero. time_system_.setMonotonicTime(start_time_); } @@ -162,27 +162,27 @@ TEST_F(ClientTest, RunWithAggressiveThreadsToEnsureNoDeadlocks) { bool acting = true; absl::optional client; } threads[10]; - for (size_t i = 0; i < 10; i++) { - threads[i].client.emplace(bucket_, "foo", 1); - threads[i].thread = std::thread([&mu, &total_consumed, thread = &threads[i]] { + for (auto& thread : threads) { + thread.client.emplace(bucket_, "foo", 1); + thread.thread = std::thread([&mu, &total_consumed, &thread] { while (true) { - uint64_t got = thread->client->consume(1000); + uint64_t got = thread.client->consume(1000); { absl::MutexLock lock(mu); - thread->acting = false; - thread->consumed += got; + thread.acting = false; + thread.consumed += got; total_consumed += got; if (total_consumed >= 100000) { break; } - mu.Await(absl::Condition(&thread->acting)); + mu.Await(absl::Condition(&thread.acting)); } } }); } const auto all_threads_blocked = [&]() { - for (int i = 0; i < 10; i++) { - if (threads[i].acting) { + for (auto& thread : threads) { + if (thread.acting) { return false; } } @@ -192,23 +192,23 @@ TEST_F(ClientTest, RunWithAggressiveThreadsToEnsureNoDeadlocks) { absl::MutexLock lock(mu); // Wait for all threads to have consumed, then advance time. mu.Await(absl::Condition(&all_threads_blocked)); - for (int i = 0; i < 10; i++) { - threads[i].acting = true; + for (auto& thread : threads) { + thread.acting = true; } if (total_consumed >= 100000) { break; } time_system_.advanceTimeWaitImpl(std::chrono::milliseconds(50)); } - for (size_t i = 0; i < 10; i++) { - threads[i].thread.join(); + for (auto& thread : threads) { + thread.thread.join(); } // Since it's battling threads, and one of them got the initial bucket fill, // an exact fair share cannot be expected, so just verify that // everyone got a reasonable share. absl::MutexLock lock(mu); - for (size_t i = 0; i < 10; i++) { - EXPECT_THAT(threads[i].consumed, IsBetween(9000, 11000)); + for (auto& thread : threads) { + EXPECT_THAT(thread.consumed, IsBetween(9000, 11000)); } // We definitely shouldn't have granted more than 100000 tokens in less // than 99 fake-seconds. diff --git a/test/extensions/filters/http/cache/cache_entry_utils_test.cc b/test/extensions/filters/http/cache/cache_entry_utils_test.cc index 3d4d14331379e..9549660c1efb8 100644 --- a/test/extensions/filters/http/cache/cache_entry_utils_test.cc +++ b/test/extensions/filters/http/cache/cache_entry_utils_test.cc @@ -1,5 +1,6 @@ #include "source/extensions/filters/http/cache/cache_entry_utils.h" +#include "test/test_common/enum_test_utils.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -16,7 +17,7 @@ TEST(Coverage, CacheEntryStatusString) { EXPECT_EQ(cacheEntryStatusString(CacheEntryStatus::RequiresValidation), "RequiresValidation"); EXPECT_EQ(cacheEntryStatusString(CacheEntryStatus::FoundNotModified), "FoundNotModified"); EXPECT_EQ(cacheEntryStatusString(CacheEntryStatus::LookupError), "LookupError"); - EXPECT_ENVOY_BUG(cacheEntryStatusString(static_cast(99)), + EXPECT_ENVOY_BUG(cacheEntryStatusString(uncheckedEnumCastForTest(99)), "Unexpected CacheEntryStatus"); } diff --git a/test/extensions/filters/http/cache/cache_filter_logging_info_test.cc b/test/extensions/filters/http/cache/cache_filter_logging_info_test.cc index 6aa76376fed6f..2a2a1578572c3 100644 --- a/test/extensions/filters/http/cache/cache_filter_logging_info_test.cc +++ b/test/extensions/filters/http/cache/cache_filter_logging_info_test.cc @@ -1,5 +1,6 @@ #include "source/extensions/filters/http/cache/cache_filter_logging_info.h" +#include "test/test_common/enum_test_utils.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -22,7 +23,8 @@ TEST(Coverage, LookupStatusToString) { EXPECT_EQ(lookupStatusToString(LookupStatus::RequestNotCacheable), "RequestNotCacheable"); EXPECT_EQ(lookupStatusToString(LookupStatus::RequestIncomplete), "RequestIncomplete"); EXPECT_EQ(lookupStatusToString(LookupStatus::LookupError), "LookupError"); - EXPECT_ENVOY_BUG(lookupStatusToString(static_cast(99)), "Unexpected LookupStatus"); + EXPECT_ENVOY_BUG(lookupStatusToString(uncheckedEnumCastForTest(99)), + "Unexpected LookupStatus"); } TEST(Coverage, InsertStatusToString) { @@ -47,7 +49,8 @@ TEST(Coverage, InsertStatusToString) { EXPECT_EQ(insertStatusToString(InsertStatus::NoInsertResponseVaryDisallowed), "NoInsertResponseVaryDisallowed"); EXPECT_EQ(insertStatusToString(InsertStatus::NoInsertLookupError), "NoInsertLookupError"); - EXPECT_ENVOY_BUG(insertStatusToString(static_cast(99)), "Unexpected InsertStatus"); + EXPECT_ENVOY_BUG(insertStatusToString(uncheckedEnumCastForTest(99)), + "Unexpected InsertStatus"); } TEST(Coverage, StatusStream) { diff --git a/test/extensions/filters/http/cache_v2/cache_entry_utils_test.cc b/test/extensions/filters/http/cache_v2/cache_entry_utils_test.cc index a215644a05e16..43b09b1742093 100644 --- a/test/extensions/filters/http/cache_v2/cache_entry_utils_test.cc +++ b/test/extensions/filters/http/cache_v2/cache_entry_utils_test.cc @@ -1,5 +1,6 @@ #include "source/extensions/filters/http/cache_v2/cache_entry_utils.h" +#include "test/test_common/enum_test_utils.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -21,7 +22,7 @@ TEST(Coverage, CacheEntryStatusString) { EXPECT_EQ(cacheEntryStatusString(CacheEntryStatus::FoundNotModified), "FoundNotModified"); EXPECT_EQ(cacheEntryStatusString(CacheEntryStatus::LookupError), "LookupError"); EXPECT_EQ(cacheEntryStatusString(CacheEntryStatus::UpstreamReset), "UpstreamReset"); - EXPECT_ENVOY_BUG(cacheEntryStatusString(static_cast(99)), + EXPECT_ENVOY_BUG(cacheEntryStatusString(uncheckedEnumCastForTest(99)), "Unexpected CacheEntryStatus"); } diff --git a/test/extensions/filters/http/cache_v2/config_test.cc b/test/extensions/filters/http/cache_v2/config_test.cc index 5508fd9ef334e..733125626a162 100644 --- a/test/extensions/filters/http/cache_v2/config_test.cc +++ b/test/extensions/filters/http/cache_v2/config_test.cc @@ -61,9 +61,7 @@ TEST_F(CacheFilterFactoryTest, UnregisteredTypedConfig) { class FailToCreateCacheFactory : public HttpCacheFactory { public: - std::string name() const override { - return std::string("envoy.extensions.http.cache_v2.fake_fail"); - } + std::string name() const override { return {"envoy.extensions.http.cache_v2.fake_fail"}; } // Arbitrarily use "Key" as the proto type of the config because it's convenient, // and we have to register it as *some* type of proto message. ProtobufTypes::MessagePtr createEmptyConfigProto() override { return std::make_unique(); } diff --git a/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc b/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc index fb237e7e33cd0..62ff877915bce 100644 --- a/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc +++ b/test/extensions/filters/http/cache_v2/http_cache_implementation_test_common.cc @@ -16,9 +16,7 @@ #include "absl/status/status.h" #include "gtest/gtest.h" -using ::envoy::extensions::filters::http::cache_v2::v3::CacheV2Config; using ::testing::_; -using ::testing::AnyNumber; using ::testing::Eq; using ::testing::Ge; using ::testing::Mock; diff --git a/test/extensions/filters/http/common/fuzz/uber_filter.h b/test/extensions/filters/http/common/fuzz/uber_filter.h index 7e6e42514c410..31d75b65b1735 100644 --- a/test/extensions/filters/http/common/fuzz/uber_filter.h +++ b/test/extensions/filters/http/common/fuzz/uber_filter.h @@ -61,12 +61,12 @@ class UberFilterFuzzer : public HttpFilterFuzzer { NiceMock decoder_callbacks_; NiceMock encoder_callbacks_; - Api::MockApi api_{}; + Api::MockApi api_; Thread::ThreadFactory& thread_factory_; Event::DispatcherPtr worker_thread_dispatcher_; std::function destroy_filters_ = []() {}; - Buffer::InstancePtr decoding_buffer_{}; + Buffer::InstancePtr decoding_buffer_; }; } // namespace HttpFilters diff --git a/test/extensions/filters/http/common/mock.cc b/test/extensions/filters/http/common/mock.cc index dc1a72a4dfd55..cb4ea0a212613 100644 --- a/test/extensions/filters/http/common/mock.cc +++ b/test/extensions/filters/http/common/mock.cc @@ -17,7 +17,7 @@ MockUpstream::MockUpstream(Upstream::MockClusterManager& mock_cm, const std::str Http::ResponseMessagePtr response_message( new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ new Http::TestResponseHeaderMapImpl{{":status", status_}}})); - if (response_body_.length()) { + if (!response_body_.empty()) { response_message->body().add(response_body_); } else { response_message->body().drain(response_message->body().length()); diff --git a/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc b/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc index b256cca867879..062299eb55113 100644 --- a/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc +++ b/test/extensions/filters/http/decompressor/decompressor_filter_integration_test.cc @@ -46,8 +46,8 @@ class DecompressorIntegrationTest : public testing::TestWithParam> ReusableFuzzerUtil::setup( Filters::Common::ExtAuthz::ClientPtr client) { // Prepare filter. - const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config = input.config(); + const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& proto_config = input.config(); FilterConfigSharedPtr config; try { diff --git a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc index 9ec5d4189d244..b652f38ac8614 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc @@ -60,9 +60,9 @@ struct GrpcInitializeConfigOpts { struct WaitForSuccessfulUpstreamResponseOpts { // Fields of type Headers must be set at initialization. - const Headers headers_to_add = {}; - const Headers headers_to_append = {}; - const Headers headers_to_remove = {}; + const Headers headers_to_add; + const Headers headers_to_append; + const Headers headers_to_remove; const Http::TestRequestHeaderMapImpl new_headers_from_upstream = {}; const Http::TestRequestHeaderMapImpl headers_to_append_multiple = {}; bool failure_mode_allowed_header = false; @@ -705,7 +705,7 @@ class ExtAuthzGrpcIntegrationTest Buffer::OwnedImpl request_body_; const uint64_t response_size_ = 512; const uint64_t max_request_bytes_ = 1024; - envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config_{}; + envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config_; const std::string base_filter_config_ = R"EOF( allowed_headers: patterns: @@ -949,7 +949,7 @@ class ExtAuthzHttpIntegrationTest cleanup(); } - envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config_{}; + envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config_; FakeHttpConnectionPtr fake_ext_authz_connection_; FakeStreamPtr ext_authz_request_; IntegrationStreamDecoderPtr response_; diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index 09532a77d5f8d..79540ba8dc4a8 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -104,7 +104,7 @@ constexpr char FilterConfigName[] = "ext_authz_filter"; template class HttpFilterTestBase : public T { public: - HttpFilterTestBase() {} + HttpFilterTestBase() = default; void initialize(std::string&& yaml) { envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config{}; @@ -245,7 +245,7 @@ class HttpFilterTestParam initialize(getFilterConfig(std::get<0>(GetParam()), std::get<1>(GetParam()))); } - static std::string ParamsToString(const testing::TestParamInfo>& info) { + static std::string paramsToString(const testing::TestParamInfo>& info) { return absl::StrCat(std::get<0>(info.param) ? "FailOpen" : "FailClosed", "_", std::get<1>(info.param) ? "HttpClient" : "GrpcClient"); } @@ -253,7 +253,7 @@ class HttpFilterTestParam INSTANTIATE_TEST_SUITE_P(ParameterizedFilterConfig, HttpFilterTestParam, testing::Combine(testing::Bool(), testing::Bool()), - HttpFilterTestParam::ParamsToString); + HttpFilterTestParam::paramsToString); class ExtAuthzLoggingInfoTest : public testing::TestWithParam< @@ -289,7 +289,7 @@ class ExtAuthzLoggingInfoTest } } - static std::string ParamsToString( + static std::string paramsToString( const testing::TestParamInfo>>& info) { return absl::StrCat(std::get<1>(info.param).has_value() ? "" : "no_", std::get<0>(info.param), std::get<1>(info.param).has_value() @@ -305,12 +305,12 @@ INSTANTIATE_TEST_SUITE_P( testing::Combine(testing::Values("latency_us", "bytesSent", "bytesReceived"), testing::Values(absl::optional{}, absl::optional{0}, absl::optional{1})), - ExtAuthzLoggingInfoTest::ParamsToString); + ExtAuthzLoggingInfoTest::paramsToString); INSTANTIATE_TEST_SUITE_P(ExtAuthzLoggingInfoTestInvalid, ExtAuthzLoggingInfoTest, testing::Values(std::make_tuple("wrong_property_name", absl::optional{})), - ExtAuthzLoggingInfoTest::ParamsToString); + ExtAuthzLoggingInfoTest::paramsToString); class EmitFilterStateTest : public HttpFilterTestBase>& info) { + paramsToString(const testing::TestParamInfo>& info) { return absl::StrCat(std::get<0>(info.param) ? "HttpClient" : "GrpcClient", "_", std::get<1>(info.param) ? "EmitStats" : "DoNotEmitStats", "_", std::get<2>(info.param) ? "EmitFilterMetadata" : "DoNotEmitFilterMetadata"); @@ -437,7 +437,7 @@ class EmitFilterStateTest INSTANTIATE_TEST_SUITE_P(EmitFilterStateTestParams, EmitFilterStateTest, testing::Combine(testing::Bool(), testing::Bool(), testing::Bool()), - EmitFilterStateTest::ParamsToString); + EmitFilterStateTest::paramsToString); class InvalidMutationTest : public HttpFilterTestBase { public: @@ -493,6 +493,7 @@ class InvalidMutationTest : public HttpFilterTestBase { const std::string invalid_value_; static std::string getInvalidValue() { + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(reinterpret_cast(invalid_value_bytes_)); } }; diff --git a/test/extensions/filters/http/ext_authz/logging_test_filter.cc b/test/extensions/filters/http/ext_authz/logging_test_filter.cc index 5b33ab7079459..c96a2e1caf9a4 100644 --- a/test/extensions/filters/http/ext_authz/logging_test_filter.cc +++ b/test/extensions/filters/http/ext_authz/logging_test_filter.cc @@ -22,7 +22,7 @@ namespace ExternalProcessing { using test::integration::filters::LoggingTestFilterConfig; Grpc::Status::WellKnownGrpcStatus -GrpcStatusFromProto(LoggingTestFilterConfig::GrpcStatus grpc_status) { +grpcStatusFromProto(LoggingTestFilterConfig::GrpcStatus grpc_status) { switch (grpc_status) { case LoggingTestFilterConfig::OK: return Grpc::Status::WellKnownGrpcStatus::Ok; @@ -110,7 +110,7 @@ class LoggingTestFilter : public Http::PassThroughFilter { EXPECT_EQ(ext_authz_logging_info->upstreamHost()->cluster().name(), expected_cluster_name_); if (expect_grpc_status_ != LoggingTestFilterConfig::UNSPECIFIED) { ASSERT_TRUE(ext_authz_logging_info->grpcStatus().has_value()); - EXPECT_EQ(ext_authz_logging_info->grpcStatus(), GrpcStatusFromProto(expect_grpc_status_)); + EXPECT_EQ(ext_authz_logging_info->grpcStatus(), grpcStatusFromProto(expect_grpc_status_)); } else { EXPECT_FALSE(ext_authz_logging_info->grpcStatus().has_value()); } diff --git a/test/extensions/filters/http/ext_proc/ext_proc_benchmark_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_benchmark_test.cc index c5a9f2d4d97b1..84ce7e78dc84d 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_benchmark_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_benchmark_test.cc @@ -146,7 +146,7 @@ class BenchmarkTest : public HttpIntegrationTest, } TestProcessor test_processor_; - envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_{}; + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_; }; // Skip sending to the external processor completely. diff --git a/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc index 49e1ddabbddff..98f51a867bf6f 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_full_duplex_integration_test.cc @@ -168,7 +168,7 @@ TEST_P(ExtProcIntegrationTest, EXPECT_TRUE(request.has_request_body() || request.has_request_trailers()); if (!request.has_request_trailers()) { // request_body is received - body_received = absl::StrCat(body_received, request.request_body().body()); + absl::StrAppend(&body_received, request.request_body().body()); total_req_body_msg++; } else { // request_trailer is received. @@ -226,7 +226,7 @@ TEST_P(ExtProcIntegrationTest, ServerSendBodyRespWithouRecvEntireBodyDuplexStrea EXPECT_TRUE(request.has_request_body() || request.has_request_trailers()); if (!request.has_request_trailers()) { // Buffer the entire body. - body_received = absl::StrCat(body_received, request.request_body().body()); + absl::StrAppend(&body_received, request.request_body().body()); total_req_body_msg++; // After receiving every 7 body chunks, the server sends back three body responses. if (total_req_body_msg % 7 == 0) { diff --git a/test/extensions/filters/http/ext_proc/ext_proc_grpc_fuzz.h b/test/extensions/filters/http/ext_proc/ext_proc_grpc_fuzz.h index a46639581e7cd..2a62ce873aead 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_grpc_fuzz.h +++ b/test/extensions/filters/http/ext_proc/ext_proc_grpc_fuzz.h @@ -208,7 +208,7 @@ class ExtProcIntegrationFuzz : public HttpIntegrationTest, } } - envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_{}; + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_; TestProcessor test_processor_; Network::Address::IpVersion ip_version_; Grpc::ClientType client_type_; diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc index 4b39128a5a458..bfa4cffa6ce98 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_common.cc @@ -852,7 +852,7 @@ uint32_t ExtProcIntegrationTest::serverReceiveBodyDuplexStreamed(absl::string_vi ADD_FAILURE() << "Expected response body message but got unexpected message type"; return total_req_body_msg; } - body_received = absl::StrCat(body_received, body_request.response_body().body()); + absl::StrAppend(&body_received, body_request.response_body().body()); end_stream = body_request.response_body().end_of_stream(); total_req_body_msg++; } @@ -864,7 +864,7 @@ uint32_t ExtProcIntegrationTest::serverReceiveBodyDuplexStreamed(absl::string_vi ADD_FAILURE() << "Expected request body message but got unexpected message type"; return total_req_body_msg; } - body_received = absl::StrCat(body_received, body_request.request_body().body()); + absl::StrAppend(&body_received, body_request.request_body().body()); end_stream = body_request.request_body().end_of_stream(); total_req_body_msg++; } diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_common.h b/test/extensions/filters/http/ext_proc/ext_proc_integration_common.h index 094ded957fad2..10c38cb9dcf61 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_common.h +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_common.h @@ -43,9 +43,13 @@ using test::integration::filters::LoggingTestFilterConfig; struct ConfigOptions { enum class FilterSetup { + // NOLINTNEXTLINE(readability-identifier-naming) kNone, + // NOLINTNEXTLINE(readability-identifier-naming) kDownstream, + // NOLINTNEXTLINE(readability-identifier-naming) kCompositeMatchOnRequestHeaders, + // NOLINTNEXTLINE(readability-identifier-naming) kCompositeMatchOnResponseHeaders, }; @@ -102,6 +106,7 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config, const std::string& ext_proc_filter_name); + // NOLINTNEXTLINE(readability-identifier-naming) bool IsEnvoyGrpc() { return std::get<1>(GetParam()) == Envoy::Grpc::ClientType::EnvoyGrpc; } void setPerRouteConfig(Route* route, const ExtProcPerRoute& cfg); @@ -206,10 +211,10 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, std::unique_ptr processing_response_factory_; std::unique_ptr> processing_response_factory_registration_; - envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_{}; - envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_1_{}; + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_; + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_1_; bool protocol_config_encoded_ = false; - ProtocolConfiguration protocol_config_{}; + ProtocolConfiguration protocol_config_; uint32_t max_message_timeout_ms_{0}; std::vector grpc_upstreams_; FakeHttpConnectionPtr processor_connection_; diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index 355c5195cfaa9..4b8ea614a45b2 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -4704,7 +4704,7 @@ TEST_P(ExtProcIntegrationTest, ServerWaitForBodyBeforeSendsHeaderRespStreamedTes ProcessingRequest body_request; ASSERT_TRUE(processor_stream_->waitForGrpcMessage(*dispatcher_, body_request)); ASSERT_TRUE(body_request.has_request_body()); - body_received = absl::StrCat(body_received, body_request.request_body().body()); + absl::StrAppend(&body_received, body_request.request_body().body()); end_stream = body_request.request_body().end_of_stream(); total_body_msg_count++; } diff --git a/test/extensions/filters/http/ext_proc/ext_proc_misc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_misc_integration_test.cc index a76c12fbe20c4..8aec86fc9df93 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_misc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_misc_integration_test.cc @@ -76,9 +76,9 @@ class ExtProcStatusOnErrorIntegrationTest : public HttpIntegrationTest, ASSERT_TRUE(processor_stream_->waitForGrpcMessage(*dispatcher_, request)); } - bool IsEnvoyGrpc() { return std::get<1>(GetParam()) == Envoy::Grpc::ClientType::EnvoyGrpc; } + bool isEnvoyGrpc() { return std::get<1>(GetParam()) == Envoy::Grpc::ClientType::EnvoyGrpc; } - envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_{}; + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_; FakeStreamPtr processor_stream_; FakeHttpConnectionPtr processor_connection_; std::vector grpc_upstreams_; diff --git a/test/extensions/filters/http/ext_proc/ext_proc_misc_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_misc_test.cc index b5a47d5ff7868..918b982ae4bce 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_misc_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_misc_test.cc @@ -173,10 +173,10 @@ class ExtProcMiscIntegrationTest : public HttpIntegrationTest, EXPECT_EQ(std::to_string(status_code), response.headers().getStatusValue()); } - bool IsEnvoyGrpc() { return std::get<1>(GetParam()) == Envoy::Grpc::ClientType::EnvoyGrpc; } + bool isEnvoyGrpc() { return std::get<1>(GetParam()) == Envoy::Grpc::ClientType::EnvoyGrpc; } void websocketExtProcTest() { - if (!IsEnvoyGrpc()) { + if (!isEnvoyGrpc()) { return; } @@ -218,7 +218,7 @@ class ExtProcMiscIntegrationTest : public HttpIntegrationTest, verifyDownstreamResponse(*response, 200); } - envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_{}; + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_; std::vector grpc_upstreams_; FakeHttpConnectionPtr processor_connection_; FakeStreamPtr processor_stream_; @@ -232,7 +232,7 @@ INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDeferredProcessing, ExtProcMiscInte // Test sending empty last body chunk with end_of_stream = true. TEST_P(ExtProcMiscIntegrationTest, SendEmptyLastBodyChunk) { - if (IsEnvoyGrpc()) { + if (isEnvoyGrpc()) { return; } diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index ce66a158e5fe1..34e2e437593b3 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -96,8 +96,8 @@ class HttpFilterTest : public testing::Test { protected: enum DoStartOption { DEFAULT = 1, - ON_GRPC_ERROR = 2, - ON_GRPC_CLOSE = 3, + OnGrpcError = 2, + OnGrpcClose = 3, }; void initialize(std::string&& yaml, bool is_upstream_filter = false) { scoped_runtime_.mergeValues( @@ -212,12 +212,12 @@ class HttpFilterTest : public testing::Test { const Grpc::GrpcServiceConfigWithHashKey& config_with_hash_key, const Envoy::Http::AsyncClient::StreamOptions&, Envoy::Http::StreamFilterSidestreamWatermarkCallbacks&) { - if (do_start_option_ == ON_GRPC_ERROR) { + if (do_start_option_ == OnGrpcError) { callbacks.onGrpcError(Grpc::Status::Internal, "foo"); return nullptr; } - if (do_start_option_ == ON_GRPC_CLOSE) { + if (do_start_option_ == OnGrpcClose) { callbacks.onGrpcClose(); return nullptr; } @@ -5990,7 +5990,7 @@ TEST_F(HttpFilterTest, GrpcErrorOnOpenStream) { cluster_name: "ext_proc_server" )EOF"); - do_start_option_ = ON_GRPC_ERROR; + do_start_option_ = OnGrpcError; EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); filter_->onDestroy(); EXPECT_EQ(Grpc::Status::Internal, getExtProcLoggingInfo()->getGrpcStatusBeforeFirstCall()); @@ -6003,7 +6003,7 @@ TEST_F(HttpFilterTest, GrpcCloseOnOpenStream) { cluster_name: "ext_proc_server" )EOF"); - do_start_option_ = ON_GRPC_CLOSE; + do_start_option_ = OnGrpcClose; EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); filter_->onDestroy(); EXPECT_EQ(Grpc::Status::Aborted, getExtProcLoggingInfo()->getGrpcStatusBeforeFirstCall()); diff --git a/test/extensions/filters/http/ext_proc/http_client/ext_proc_http_integration_test.cc b/test/extensions/filters/http/ext_proc/http_client/ext_proc_http_integration_test.cc index 904849af3c6f4..1bf94df323152 100644 --- a/test/extensions/filters/http/ext_proc/http_client/ext_proc_http_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/http_client/ext_proc_http_integration_test.cc @@ -278,14 +278,14 @@ class ExtProcHttpClientIntegrationTest : public testing::TestWithParam& params) { + extProcHttpTestParamsToString(const ::testing::TestParamInfo& params) { return absl::StrCat( (params.param.version == Network::Address::IpVersion::v4 ? "IPv4_" : "IPv6_"), (params.param.downstream_protocol == Http::CodecType::HTTP1 ? "HTTP1_DS_" : "HTTP2_DS_"), (params.param.upstream_protocol == Http::CodecType::HTTP1 ? "HTTP1_US" : "HTTP2_US")); } - envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_{}; + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_; std::vector http_side_upstreams_; FakeHttpConnectionPtr processor_connection_; FakeStreamPtr processor_stream_; @@ -296,7 +296,7 @@ class ExtProcHttpClientIntegrationTest : public testing::TestWithParam context_; Upstream::MockClusterManager& cm_{context_.cluster_manager_}; std::unique_ptr client_; diff --git a/test/extensions/filters/http/ext_proc/streaming_integration_test.cc b/test/extensions/filters/http/ext_proc/streaming_integration_test.cc index 75c711a09eed4..f691c9fc5a519 100644 --- a/test/extensions/filters/http/ext_proc/streaming_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/streaming_integration_test.cc @@ -138,7 +138,7 @@ class StreamingIntegrationTest : public HttpIntegrationTest, } TestProcessor test_processor_; - envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_{}; + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor proto_config_; IntegrationStreamDecoderPtr client_response_; std::atomic processor_request_hash_; std::atomic processor_response_hash_; diff --git a/test/extensions/filters/http/file_server/BUILD b/test/extensions/filters/http/file_server/BUILD index c6177bf55d379..494b755a1885f 100644 --- a/test/extensions/filters/http/file_server/BUILD +++ b/test/extensions/filters/http/file_server/BUILD @@ -17,6 +17,7 @@ envoy_cc_test( srcs = ["absl_status_to_http_status_test.cc"], deps = [ "//source/extensions/filters/http/file_server:absl_status_to_http_status", + "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/http/file_server/absl_status_to_http_status_test.cc b/test/extensions/filters/http/file_server/absl_status_to_http_status_test.cc index 96f1e7d474a6a..f550fe4a2d1d1 100644 --- a/test/extensions/filters/http/file_server/absl_status_to_http_status_test.cc +++ b/test/extensions/filters/http/file_server/absl_status_to_http_status_test.cc @@ -1,5 +1,7 @@ #include "source/extensions/filters/http/file_server/absl_status_to_http_status.h" +#include "test/test_common/enum_test_utils.h" + #include "gtest/gtest.h" namespace Envoy { @@ -9,7 +11,7 @@ namespace FileServer { TEST(AbslStatusToHttpStatus, Coverage) { EXPECT_EQ(abslStatusToHttpStatus(absl::StatusCode::kOk), Http::Code::OK); - EXPECT_EQ(abslStatusToHttpStatus(absl::StatusCode::kCancelled), static_cast(499)); + EXPECT_EQ(static_cast(abslStatusToHttpStatus(absl::StatusCode::kCancelled)), 499); EXPECT_EQ(abslStatusToHttpStatus(absl::StatusCode::kUnknown), Http::Code::InternalServerError); EXPECT_EQ(abslStatusToHttpStatus(absl::StatusCode::kInvalidArgument), Http::Code::BadRequest); EXPECT_EQ(abslStatusToHttpStatus(absl::StatusCode::kDeadlineExceeded), @@ -26,7 +28,7 @@ TEST(AbslStatusToHttpStatus, Coverage) { Http::Code::ServiceUnavailable); EXPECT_EQ(abslStatusToHttpStatus(absl::StatusCode::kDataLoss), Http::Code::InternalServerError); EXPECT_EQ(abslStatusToHttpStatus(absl::StatusCode::kUnauthenticated), Http::Code::Unauthorized); - EXPECT_EQ(abslStatusToHttpStatus(static_cast(99999999)), + EXPECT_EQ(abslStatusToHttpStatus(uncheckedEnumCastForTest(99999999)), Http::Code::InternalServerError); } diff --git a/test/extensions/filters/http/gcp_authn/gcp_authn_filter_integration_test.cc b/test/extensions/filters/http/gcp_authn/gcp_authn_filter_integration_test.cc index 92bd8dd29c97c..883105c5af048 100644 --- a/test/extensions/filters/http/gcp_authn/gcp_authn_filter_integration_test.cc +++ b/test/extensions/filters/http/gcp_authn/gcp_authn_filter_integration_test.cc @@ -204,8 +204,8 @@ class GcpAuthnFilterIntegrationTest : public testing::TestWithParam #include #include +#include #include #include #include @@ -1176,9 +1177,9 @@ void runBatchAndDrainBackwards(const std::vector& input_buf // Test that we can drain the underlying data in any order. // Specifically, we will drain backwards from `output_data`. - for (auto it = output_data.rbegin(); it != output_data.rend(); it++) { - checkSerializedData(**it, {output_requests.back()}); - EXPECT_EQ((**it).length(), 0); + for (auto& it : std::ranges::reverse_view(output_data)) { + checkSerializedData(*it, {output_requests.back()}); + EXPECT_EQ((*it).length(), 0); output_requests.pop_back(); } } diff --git a/test/extensions/filters/http/mcp/mcp_json_parser_test.cc b/test/extensions/filters/http/mcp/mcp_json_parser_test.cc index bd892c3d866e3..9e6a79667e9ba 100644 --- a/test/extensions/filters/http/mcp/mcp_json_parser_test.cc +++ b/test/extensions/filters/http/mcp/mcp_json_parser_test.cc @@ -17,8 +17,6 @@ namespace Mcp { namespace { using ::testing::HasSubstr; -using ::testing::IsEmpty; -using ::testing::Not; using namespace Filters::Common::Mcp::McpConstants; class McpJsonParserTest : public testing::Test { diff --git a/test/extensions/filters/http/oauth2/oauth_integration_test.cc b/test/extensions/filters/http/oauth2/oauth_integration_test.cc index 5edcb7feca5fd..8f45e80283a5f 100644 --- a/test/extensions/filters/http/oauth2/oauth_integration_test.cc +++ b/test/extensions/filters/http/oauth2/oauth_integration_test.cc @@ -97,6 +97,7 @@ std::string decrypt(absl::string_view encrypted, absl::string_view secret) { // Resize to actual plaintext length plaintext.resize(plaintext_len); + // NOLINTNEXTLINE(modernize-return-braced-init-list) return std::string(plaintext.begin(), plaintext.end()); } diff --git a/test/extensions/filters/http/on_demand/odcds_integration_test.cc b/test/extensions/filters/http/on_demand/odcds_integration_test.cc index 66960f3a99dd1..8d42fa759d1d5 100644 --- a/test/extensions/filters/http/on_demand/odcds_integration_test.cc +++ b/test/extensions/filters/http/on_demand/odcds_integration_test.cc @@ -637,11 +637,11 @@ class OdCdsAdsIntegrationTest Network::Address::IpVersion ipVersion() const override { return std::get<0>(GetParam()); } Grpc::ClientType clientType() const override { return std::get<1>(GetParam()); } Grpc::SotwOrDelta sotwOrDelta() const { return std::get<2>(GetParam()); } - bool odcds_over_ads_fix_enabled() const { return std::get<3>(GetParam()); } + bool odcdsOverAdsFixEnabled() const { return std::get<3>(GetParam()); } void initialize() override { config_helper_.addRuntimeOverride("envoy.reloadable_features.odcds_over_ads_fix", - odcds_over_ads_fix_enabled() ? "true" : "false"); + odcdsOverAdsFixEnabled() ? "true" : "false"); AdsIntegrationTestBase::initialize(); test_server_->waitUntilListenersReady(); @@ -1061,7 +1061,7 @@ TEST_P(OdCdsAdsIntegrationTest, NoCdsConfigOnDemandClusterMultipleClustersSequen // but works with the new one (XdstpOdCdsApiImpl). // Once envoy.reloadable_features.odcds_over_ads_fix is removed, this test // will only execute the fixed component. - if (!odcds_over_ads_fix_enabled()) { + if (!odcdsOverAdsFixEnabled()) { GTEST_SKIP() << "This test only passes with the new XdstpOdCdsApiImpl implementation"; } config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { @@ -1271,7 +1271,7 @@ TEST_P(OdCdsAdsIntegrationTest, // but works with the new one (XdstpOdCdsApiImpl). // Once envoy.reloadable_features.odcds_over_ads_fix is removed, this test // will only execute the fixed component. - if (!odcds_over_ads_fix_enabled()) { + if (!odcdsOverAdsFixEnabled()) { GTEST_SKIP() << "This test only passes with the new XdstpOdCdsApiImpl implementation"; } config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { @@ -1924,7 +1924,7 @@ TEST_P(OdCdsXdstpIntegrationTest, OnDemandCdsWithEds) { */ class OdCdsXdstpAdsIntegrationTest : public AdsXdsTpConfigsIntegrationTest { public: - OdCdsXdstpAdsIntegrationTest() : AdsXdsTpConfigsIntegrationTest() { + OdCdsXdstpAdsIntegrationTest() { // Override the sotw_or_delta_ settings to only use SotW-ADS. // Note that in the future this can be modified to support other types as // well, but currently not needed. diff --git a/test/extensions/filters/http/proto_api_scrubber/filter_benchmark_test.cc b/test/extensions/filters/http/proto_api_scrubber/filter_benchmark_test.cc index 97d1917659f71..b95b3a3111dab 100644 --- a/test/extensions/filters/http/proto_api_scrubber/filter_benchmark_test.cc +++ b/test/extensions/filters/http/proto_api_scrubber/filter_benchmark_test.cc @@ -253,11 +253,12 @@ bool verifyFilterBehavior(benchmark::State& state, FilterBenchmarkFixture& fixtu // Measures the latency of processing a unary request when no fields match the scrubbing rules. // This establishes the baseline overhead of the filter's traversal logic. -static void BM_Request_Unary_Passthrough(benchmark::State& state) { +static void bmRequestUnaryPassthrough(benchmark::State& state) { FilterBenchmarkFixture fixture(state.range(0), false); // Perform validation to ensure setup is correct. - if (!verifyFilterBehavior(state, fixture, false, false)) + if (!verifyFilterBehavior(state, fixture, false, false)) { return; + } Http::TestRequestHeaderMapImpl headers{ {":path", "/test.extensions.filters.http.proto_api_scrubber.ScrubberTestService/Scrub"}, @@ -275,14 +276,15 @@ static void BM_Request_Unary_Passthrough(benchmark::State& state) { } state.SetComplexityN(state.range(0)); } -BENCHMARK(BM_Request_Unary_Passthrough)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); +BENCHMARK(bmRequestUnaryPassthrough)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); // Measures the latency of processing a unary request when fields (including Maps) are actively // scrubbed. This measures the cost of path normalization, match evaluation, and field removal. -static void BM_Request_Unary_Scrubbing(benchmark::State& state) { +static void bmRequestUnaryScrubbing(benchmark::State& state) { FilterBenchmarkFixture fixture(state.range(0), true); - if (!verifyFilterBehavior(state, fixture, false, true)) + if (!verifyFilterBehavior(state, fixture, false, true)) { return; + } Http::TestRequestHeaderMapImpl headers{ {":path", "/test.extensions.filters.http.proto_api_scrubber.ScrubberTestService/Scrub"}, @@ -300,14 +302,15 @@ static void BM_Request_Unary_Scrubbing(benchmark::State& state) { } state.SetComplexityN(state.range(0)); } -BENCHMARK(BM_Request_Unary_Scrubbing)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); +BENCHMARK(bmRequestUnaryScrubbing)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); // Measures the latency of processing a streaming request with active scrubbing. // In streaming mode, data frames may be processed incrementally. -static void BM_Request_Streaming_Scrubbing(benchmark::State& state) { +static void bmRequestStreamingScrubbing(benchmark::State& state) { FilterBenchmarkFixture fixture(state.range(0), true); - if (!verifyFilterBehavior(state, fixture, false, true)) + if (!verifyFilterBehavior(state, fixture, false, true)) { return; + } Http::TestRequestHeaderMapImpl headers{ {":path", "/test.extensions.filters.http.proto_api_scrubber.ScrubberTestService/Scrub"}, @@ -328,13 +331,14 @@ static void BM_Request_Streaming_Scrubbing(benchmark::State& state) { filter->decodeData(empty, true); state.SetComplexityN(state.range(0)); } -BENCHMARK(BM_Request_Streaming_Scrubbing)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); +BENCHMARK(bmRequestStreamingScrubbing)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); // Measures the latency of processing a unary response when no fields match the scrubbing rules. -static void BM_Response_Unary_Passthrough(benchmark::State& state) { +static void bmResponseUnaryPassthrough(benchmark::State& state) { FilterBenchmarkFixture fixture(state.range(0), false); - if (!verifyFilterBehavior(state, fixture, true, false)) + if (!verifyFilterBehavior(state, fixture, true, false)) { return; + } Http::TestRequestHeaderMapImpl req_headers{ {":path", "/test.extensions.filters.http.proto_api_scrubber.ScrubberTestService/Scrub"}, @@ -356,13 +360,14 @@ static void BM_Response_Unary_Passthrough(benchmark::State& state) { } state.SetComplexityN(state.range(0)); } -BENCHMARK(BM_Response_Unary_Passthrough)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); +BENCHMARK(bmResponseUnaryPassthrough)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); // Measures the latency of processing a unary response with active scrubbing enabled. -static void BM_Response_Unary_Scrubbing(benchmark::State& state) { +static void bmResponseUnaryScrubbing(benchmark::State& state) { FilterBenchmarkFixture fixture(state.range(0), true); - if (!verifyFilterBehavior(state, fixture, true, true)) + if (!verifyFilterBehavior(state, fixture, true, true)) { return; + } Http::TestRequestHeaderMapImpl req_headers{ {":path", "/test.extensions.filters.http.proto_api_scrubber.ScrubberTestService/Scrub"}, @@ -384,13 +389,14 @@ static void BM_Response_Unary_Scrubbing(benchmark::State& state) { } state.SetComplexityN(state.range(0)); } -BENCHMARK(BM_Response_Unary_Scrubbing)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); +BENCHMARK(bmResponseUnaryScrubbing)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); // Measures the latency of processing a streaming response with active scrubbing enabled. -static void BM_Response_Streaming_Scrubbing(benchmark::State& state) { +static void bmResponseStreamingScrubbing(benchmark::State& state) { FilterBenchmarkFixture fixture(state.range(0), true); - if (!verifyFilterBehavior(state, fixture, true, true)) + if (!verifyFilterBehavior(state, fixture, true, true)) { return; + } Http::TestRequestHeaderMapImpl req_headers{ {":path", "/test.extensions.filters.http.proto_api_scrubber.ScrubberTestService/Scrub"}, @@ -415,12 +421,12 @@ static void BM_Response_Streaming_Scrubbing(benchmark::State& state) { filter->encodeData(empty, true); state.SetComplexityN(state.range(0)); } -BENCHMARK(BM_Response_Streaming_Scrubbing)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); +BENCHMARK(bmResponseStreamingScrubbing)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); // Raw Protobuf Round-Trip (Control Group). // Measures the theoretical minimum cost of Parsing and Serializing the payload // using the raw Google Protobuf library, bypassing all Envoy filter logic. -static void BM_Raw_Proto_RoundTrip(benchmark::State& state) { +static void bmRawProtoRoundTrip(benchmark::State& state) { FilterBenchmarkFixture fixture(state.range(0), false); // Get the full gRPC frame @@ -452,7 +458,7 @@ static void BM_Raw_Proto_RoundTrip(benchmark::State& state) { } state.SetComplexityN(state.range(0)); } -BENCHMARK(BM_Raw_Proto_RoundTrip)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); +BENCHMARK(bmRawProtoRoundTrip)->RangeMultiplier(10)->Range(10, 10000)->Complexity(); } // namespace } // namespace ProtoApiScrubber diff --git a/test/extensions/filters/http/rate_limit_quota/client_test.cc b/test/extensions/filters/http/rate_limit_quota/client_test.cc index 36ea8bdad7dfb..792a42606d00f 100644 --- a/test/extensions/filters/http/rate_limit_quota/client_test.cc +++ b/test/extensions/filters/http/rate_limit_quota/client_test.cc @@ -51,7 +51,7 @@ using testing::Unused; class GlobalClientCallbacks : public GlobalRateLimitClientCallbacks { public: void onBucketCreated([[maybe_unused]] const BucketId& bucket_id, size_t id) override { - expected_buckets.at(id)->Notify(); + expected_buckets_.at(id)->Notify(); }; void onUsageReportsSent() override { report_sent->Notify(); }; void onQuotaResponseProcessed() override { response_processed->Notify(); }; @@ -65,21 +65,21 @@ class GlobalClientCallbacks : public GlobalRateLimitClientCallbacks { using ExpectedBuckets = absl::flat_hash_map>; void expectBuckets(std::vector ids) { - expected_buckets = ExpectedBuckets(); + expected_buckets_ = ExpectedBuckets(); for (size_t id : ids) { - expected_buckets[id] = std::make_unique(); + expected_buckets_[id] = std::make_unique(); } } void waitForExpectedBuckets(absl::Duration timeout = absl::Seconds(5)) { - for (const auto& [id, notif] : expected_buckets) { + for (const auto& [id, notif] : expected_buckets_) { EXPECT_TRUE(notif->WaitForNotificationWithTimeout(timeout)); - expected_buckets[id] = std::make_unique(); + expected_buckets_[id] = std::make_unique(); } } private: - ExpectedBuckets expected_buckets; + ExpectedBuckets expected_buckets_; }; inline void waitForNotification(std::unique_ptr& notif, @@ -144,12 +144,12 @@ class GlobalClientTest : public ::testing::Test { MessageDifferencer unordered_differencer_; - struct reportData { + struct ReportData { int allowed; int denied; BucketId bucket_id; }; - RateLimitQuotaUsageReports buildReports(const std::vector& test_reports) { + RateLimitQuotaUsageReports buildReports(const std::vector& test_reports) { RateLimitQuotaUsageReports reports; reports.set_domain(mock_domain_); @@ -246,12 +246,14 @@ void setAtomic(uint64_t value, std::atomic& counter) { absl::StatusOr> tryGetBucket(ThreadLocal::TypedSlot& buckets_tls, size_t id) { auto cache_ref = buckets_tls.get(); - if (!cache_ref.has_value() || cache_ref->quota_buckets_ == nullptr) + if (!cache_ref.has_value() || cache_ref->quota_buckets_ == nullptr) { return absl::NotFoundError("Bucket TLS not initialized"); + } auto bucket_it = cache_ref->quota_buckets_->find(id); - if (bucket_it == cache_ref->quota_buckets_->end()) + if (bucket_it == cache_ref->quota_buckets_->end()) { return absl::NotFoundError("Bucket not found"); + } return bucket_it->second; } @@ -262,8 +264,9 @@ void getBucket(ThreadLocal::TypedSlot& buckets_tls, siz ASSERT_TRUE(cache_ref->quota_buckets_); auto bucket_it = cache_ref->quota_buckets_->find(id); - if (bucket_it == cache_ref->quota_buckets_->end()) + if (bucket_it == cache_ref->quota_buckets_->end()) { return; + } bucket_out = bucket_it->second; } std::shared_ptr @@ -294,7 +297,7 @@ TEST_F(GlobalClientTest, TestInitialCreation) { // Expect an immediate usage report for the new bucket. RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)); @@ -352,7 +355,7 @@ TEST_F(GlobalClientTest, TestCreationWithDefaultDeny) { // Expect an immediate usage report for the new bucket. RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/0, /*denied=*/1, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/0, /*denied=*/1, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)); @@ -399,7 +402,7 @@ TEST_F(GlobalClientTest, BasicUsageReporting) { // Expect an immediate usage report for the new bucket. RateLimitQuotaUsageReports initial_report = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(initial_report), false)); @@ -414,7 +417,7 @@ TEST_F(GlobalClientTest, BasicUsageReporting) { setAtomic(2, quota_usage->num_requests_denied); RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/2, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/2, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)); @@ -470,7 +473,7 @@ TEST_F(GlobalClientTest, TestStreamCreationFailures) { } // A fourth stream creation should succeed & result in a sent usage report. RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/5, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/5, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)); @@ -489,7 +492,7 @@ TEST_F(GlobalClientTest, TestStreamFailureMidUse) { // Expect an immediate usage report for the new bucket. RateLimitQuotaUsageReports initial_report = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(initial_report), false)); @@ -505,7 +508,7 @@ TEST_F(GlobalClientTest, TestStreamFailureMidUse) { setAtomic(2, quota_usage->num_requests_denied); RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/2, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/2, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)); @@ -549,7 +552,7 @@ TEST_F(GlobalClientTest, TestStreamFailureMidUse) { // Expect stream creation & successful sending of a report. expected_reports = buildReports( - std::vector{{/*allowed=*/3, /*denied=*/4, /*bucket_id=*/sample_bucket_id_}, + std::vector{{/*allowed=*/3, /*denied=*/4, /*bucket_id=*/sample_bucket_id_}, {/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id2}}); EXPECT_CALL( mock_stream_client->stream_, @@ -583,7 +586,7 @@ TEST_F(GlobalClientTest, TestBasicResponseProcessing) { // reports. for (const BucketId& bucket_id : {sample_bucket_id_, sample_bucket_id2, sample_bucket_id3}) { RateLimitQuotaUsageReports initial_report = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/bucket_id}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/bucket_id}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(initial_report), false)); @@ -603,7 +606,7 @@ TEST_F(GlobalClientTest, TestBasicResponseProcessing) { setAtomic(3, getQuotaUsage(*buckets_tls_, sample_id_hash3)->num_requests_allowed); RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}, + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}, {/*allowed=*/2, /*denied=*/0, /*bucket_id=*/sample_bucket_id2}, {/*allowed=*/3, /*denied=*/0, /*bucket_id=*/sample_bucket_id3}}); EXPECT_CALL( @@ -657,7 +660,7 @@ TEST_F(GlobalClientTest, TestBasicResponseProcessing) { EXPECT_EQ(cached_tb.get(), token_bucket->token_bucket_limiter.get()); // Clean up expiration timers for all three buckets. - for (auto bucket : {token_bucket, deny_all_bucket, allow_all_bucket}) { + for (const auto& bucket : {token_bucket, deny_all_bucket, allow_all_bucket}) { ASSERT_TRUE(bucket->action_expiration_timer); RateLimitTestClient::assertMockTimer(bucket->action_expiration_timer.get())->invokeCallback(); waitForNotification(cb_ptr_->action_expired); @@ -673,7 +676,7 @@ TEST_F(GlobalClientTest, TestDuplicateTokenBucket) { mock_stream_client->expectTimerCreations(reporting_interval_, action_ttl, 2); RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)) @@ -760,7 +763,7 @@ TEST_F(GlobalClientTest, TestResponseProcessingForNonExistentBucket) { mock_stream_client->expectTimerCreations(reporting_interval_, action_ttl, 1); RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)); @@ -826,12 +829,12 @@ TEST_F(GlobalClientTest, TestResponseEdgeCases) { // reports. EXPECT_CALL(mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}})), false)); EXPECT_CALL(mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id2}})), false)); @@ -846,7 +849,7 @@ TEST_F(GlobalClientTest, TestResponseEdgeCases) { setAtomic(1, getQuotaUsage(*buckets_tls_, sample_id_hash2)->num_requests_allowed); RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}, + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}, {/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id2}}); EXPECT_CALL( mock_stream_client->stream_, @@ -935,7 +938,7 @@ TEST_F(GlobalClientTest, TestExpirationAndFallback) { // reports. for (const BucketId& bucket_id : {sample_bucket_id_, sample_bucket_id2, sample_bucket_id3}) { RateLimitQuotaUsageReports initial_report = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/bucket_id}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/bucket_id}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(initial_report), false)); @@ -956,7 +959,7 @@ TEST_F(GlobalClientTest, TestExpirationAndFallback) { // Defaults from no_assignment_behavior. RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}, + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}, {/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id2}, {/*allowed=*/0, /*denied=*/1, /*bucket_id=*/sample_bucket_id3}}); EXPECT_CALL( @@ -1146,7 +1149,7 @@ TEST_F(GlobalClientTest, TestFallbackToDuplicateTokenBucket) { // Defaults from no_assignment_behavior. RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)) @@ -1223,7 +1226,7 @@ TEST_F(GlobalClientTest, TestAbandonAction) { mock_stream_client->expectTimerCreations(reporting_interval_); RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)) @@ -1266,7 +1269,7 @@ TEST_F(GlobalClientTest, TestResponseBucketMissingId) { // Expect initial bucket creations to each trigger immediate bucket-specific // reports. RateLimitQuotaUsageReports initial_report = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(initial_report), false)); @@ -1279,7 +1282,7 @@ TEST_F(GlobalClientTest, TestResponseBucketMissingId) { setAtomic(1, getQuotaUsage(*buckets_tls_, sample_id_hash_)->num_requests_allowed); RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)); @@ -1309,7 +1312,7 @@ TEST_F(GlobalClientTest, TestResponseBucketMissingId) { class LocalClientTest : public GlobalClientTest { protected: - LocalClientTest() : GlobalClientTest() {} + LocalClientTest() = default; void SetUp() override { GlobalClientTest::SetUp(); @@ -1333,7 +1336,7 @@ TEST_F(LocalClientTest, TestLocalClient) { mock_stream_client->expectTimerCreations(reporting_interval_); RateLimitQuotaUsageReports expected_reports = buildReports( - std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); + std::vector{{/*allowed=*/1, /*denied=*/0, /*bucket_id=*/sample_bucket_id_}}); EXPECT_CALL( mock_stream_client->stream_, sendMessageRaw_(Grpc::ProtoBufferEqIgnoreRepeatedFieldOrdering(expected_reports), false)); diff --git a/test/extensions/filters/http/rate_limit_quota/client_test_utils.h b/test/extensions/filters/http/rate_limit_quota/client_test_utils.h index bdcd0936729b0..5b2f2377f0365 100644 --- a/test/extensions/filters/http/rate_limit_quota/client_test_utils.h +++ b/test/extensions/filters/http/rate_limit_quota/client_test_utils.h @@ -141,9 +141,8 @@ class RateLimitTestClient { std::chrono::milliseconds fallback_interval = std::chrono::milliseconds::zero(), int fallback_count = 0) { all_timers_ = std::vector(1 + expiration_count + fallback_count); - for (size_t i = 0; i < all_timers_.size(); ++i) { - auto* timer_ptr = new Event::MockTimer(dispatcher_); - all_timers_[i] = timer_ptr; + for (auto*& timer_ptr : all_timers_) { + timer_ptr = new Event::MockTimer(dispatcher_); // Prepare the timer to be either a reporting or expiration timer. EXPECT_CALL(*timer_ptr, enableTimer(_, _)) .WillOnce([&, report_interval, expiration_interval, expiration_count, fallback_interval, @@ -173,8 +172,9 @@ class RateLimitTestClient { } inline static Event::MockTimer* assertMockTimer(Event::Timer* timer) { - if (timer == nullptr) + if (timer == nullptr) { return nullptr; + } return dynamic_cast(timer); } @@ -229,8 +229,8 @@ class RateLimitTestClient { // Note: all expiration timers that are still running at the end of the test // will cause a failure, so tests must verify expiration behaviors. std::vector all_timers_; - absl::flat_hash_set expiration_timers_ = {}; - absl::flat_hash_set fallback_timers_ = {}; + absl::flat_hash_set expiration_timers_; + absl::flat_hash_set fallback_timers_; Event::MockDispatcher* dispatcher_ = &context_.server_factory_context_.dispatcher_; }; diff --git a/test/extensions/filters/http/rate_limit_quota/filter_persistence_test.cc b/test/extensions/filters/http/rate_limit_quota/filter_persistence_test.cc index b0282701509c0..f6cf5627a64ae 100644 --- a/test/extensions/filters/http/rate_limit_quota/filter_persistence_test.cc +++ b/test/extensions/filters/http/rate_limit_quota/filter_persistence_test.cc @@ -280,7 +280,7 @@ class FilterPersistenceTest : public Event::TestUsingSimulatedTime, } envoy::extensions::filters::http::rate_limit_quota::v3::RateLimitQuotaFilterConfig - rlqs_filter_config_{}; + rlqs_filter_config_; struct FakeRlqsUpstreamRefs { Network::Address::InstanceConstSharedPtr rlqs_endpoint_ = nullptr; @@ -291,7 +291,7 @@ class FilterPersistenceTest : public Event::TestUsingSimulatedTime, FakeStreamPtr rlqs_stream_ = nullptr; Cluster* rlqs_cluster_ = nullptr; }; - std::vector rlqs_upstreams_{}; + std::vector rlqs_upstreams_; Network::Address::InstanceConstSharedPtr traffic_endpoint_ = nullptr; AutonomousUpstream* traffic_upstream_ = nullptr; diff --git a/test/extensions/filters/http/rate_limit_quota/integration_test.cc b/test/extensions/filters/http/rate_limit_quota/integration_test.cc index 3f0a6e91c246c..6b379fdae7bb6 100644 --- a/test/extensions/filters/http/rate_limit_quota/integration_test.cc +++ b/test/extensions/filters/http/rate_limit_quota/integration_test.cc @@ -261,8 +261,9 @@ class RateLimitQuotaIntegrationTest : public Event::TestUsingSimulatedTime, bool expectDeniedRequest(int expected_status_code, std::vector> expected_headers = {}, std::string expected_body = "") { - if (!response_->waitForEndStream()) + if (!response_->waitForEndStream()) { return false; + } EXPECT_TRUE(response_->complete()); EXPECT_EQ(response_->headers().getStatusValue(), absl::StrCat(expected_status_code)); @@ -285,8 +286,9 @@ class RateLimitQuotaIntegrationTest : public Event::TestUsingSimulatedTime, } bool expectAllowedRequest() { - if (!response_->waitForEndStream()) + if (!response_->waitForEndStream()) { return false; + } EXPECT_TRUE(response_->complete()); EXPECT_EQ(response_->headers().getStatusValue(), "200"); @@ -296,8 +298,7 @@ class RateLimitQuotaIntegrationTest : public Event::TestUsingSimulatedTime, return true; } - envoy::extensions::filters::http::rate_limit_quota::v3::RateLimitQuotaFilterConfig - proto_config_{}; + envoy::extensions::filters::http::rate_limit_quota::v3::RateLimitQuotaFilterConfig proto_config_; Network::Address::InstanceConstSharedPtr traffic_endpoint_; AutonomousUpstream* traffic_upstream_; Cluster* traffic_cluster_; diff --git a/test/extensions/filters/http/rbac/rbac_filter_test.cc b/test/extensions/filters/http/rbac/rbac_filter_test.cc index e88c55cd0f6fd..e4f3df0268e6b 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_test.cc @@ -322,7 +322,7 @@ class RoleBasedAccessControlFilterTest : public testing::Test { } NiceMock callbacks_; - NiceMock connection_{}; + NiceMock connection_; NiceMock req_info_; Stats::TestUtil::TestStore stats_store_; NiceMock context_; diff --git a/test/extensions/filters/http/router/config_test.cc b/test/extensions/filters/http/router/config_test.cc index fa84bd6d00d77..3c815af0ff187 100644 --- a/test/extensions/filters/http/router/config_test.cc +++ b/test/extensions/filters/http/router/config_test.cc @@ -30,8 +30,7 @@ class QueryParameterMatcherTest : public testing::Test { Router::ConfigUtility::QueryParameterMatcher createQueryParamMatcher(const std::string& yaml) { envoy::config::route::v3::QueryParameterMatcher query_param_matcher; TestUtility::loadFromYaml(yaml, query_param_matcher); - return Router::ConfigUtility::QueryParameterMatcher(query_param_matcher, - context_.serverFactoryContext()); + return {query_param_matcher, context_.serverFactoryContext()}; } Api::ApiPtr api_; diff --git a/test/extensions/filters/listener/tls_inspector/ja4_fingerprint_test.cc b/test/extensions/filters/listener/tls_inspector/ja4_fingerprint_test.cc index 65105d963733d..4bf0a2fa8d7e7 100644 --- a/test/extensions/filters/listener/tls_inspector/ja4_fingerprint_test.cc +++ b/test/extensions/filters/listener/tls_inspector/ja4_fingerprint_test.cc @@ -7,9 +7,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using ::testing::Eq; -using ::testing::HasSubstr; - namespace Envoy { namespace Extensions { namespace ListenerFilters { diff --git a/test/extensions/filters/listener/tls_inspector/tls_inspector_ja4_test.cc b/test/extensions/filters/listener/tls_inspector/tls_inspector_ja4_test.cc index 4b5f8143983e5..bfa533b41a835 100644 --- a/test/extensions/filters/listener/tls_inspector/tls_inspector_ja4_test.cc +++ b/test/extensions/filters/listener/tls_inspector/tls_inspector_ja4_test.cc @@ -13,10 +13,8 @@ using testing::_; using testing::Eq; -using testing::InSequence; using testing::Invoke; using testing::NiceMock; -using testing::Return; using testing::ReturnNew; using testing::ReturnRef; using testing::SaveArg; diff --git a/test/extensions/filters/network/dubbo_proxy/app_exception_test.cc b/test/extensions/filters/network/dubbo_proxy/app_exception_test.cc index 94ec6b6acceec..3632e9923bb05 100644 --- a/test/extensions/filters/network/dubbo_proxy/app_exception_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/app_exception_test.cc @@ -77,6 +77,7 @@ TEST_F(AppExceptionTest, Encode) { EXPECT_TRUE(rpc_result.first->hasException()); buffer.drain(buffer.length()); + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) AppException new_app_exception(app_exception); EXPECT_EQ(new_app_exception.status_, ResponseStatus::ServiceNotFound); diff --git a/test/extensions/filters/network/dubbo_proxy/router_test.cc b/test/extensions/filters/network/dubbo_proxy/router_test.cc index 1d12f85448b20..41e80961d924f 100644 --- a/test/extensions/filters/network/dubbo_proxy/router_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/router_test.cc @@ -374,8 +374,8 @@ class DubboRouterTestBase { Registry::InjectFactory serializer_register_; Registry::InjectFactory protocol_register_; - std::function mock_serializer_cb_{}; - std::function mock_protocol_cb_{}; + std::function mock_serializer_cb_; + std::function mock_protocol_cb_; NiceMock context_; NiceMock connection_; diff --git a/test/extensions/filters/network/ext_proc/config_test.cc b/test/extensions/filters/network/ext_proc/config_test.cc index 7e633861e572a..684abf703c3a8 100644 --- a/test/extensions/filters/network/ext_proc/config_test.cc +++ b/test/extensions/filters/network/ext_proc/config_test.cc @@ -15,7 +15,7 @@ namespace { class ConfigTest : public testing::Test { public: - ConfigTest() {} + ConfigTest() = default; protected: NiceMock store_; diff --git a/test/extensions/filters/network/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/network/ext_proc/ext_proc_integration_test.cc index b0ebe4924f06e..339bcc871fba5 100644 --- a/test/extensions/filters/network/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/network/ext_proc/ext_proc_integration_test.cc @@ -158,15 +158,15 @@ class NetworkExtProcFilterIntegrationTest auto* listeners = bootstrap.mutable_static_resources()->mutable_listeners(0); auto* filter_chain = listeners->mutable_filter_chains(0); auto* filters = filter_chain->mutable_filters(); - for (int i = 0; i < filters->size(); i++) { - if ((*filters)[i].name() == "envoy.network_ext_proc.ext_proc_filter") { + for (auto& filter : *filters) { + if (filter.name() == "envoy.network_ext_proc.ext_proc_filter") { envoy::extensions::filters::network::ext_proc::v3::NetworkExternalProcessor config; - (*filters)[i].mutable_typed_config()->UnpackTo(&config); + filter.mutable_typed_config()->UnpackTo(&config); // Apply the provided modifier function config_modifier(config); - (*filters)[i].mutable_typed_config()->PackFrom(config); + filter.mutable_typed_config()->PackFrom(config); break; } } @@ -221,11 +221,11 @@ class NetworkExtProcFilterIntegrationTest auto* filter_chain = listener->mutable_filter_chains(0); auto* filters = filter_chain->mutable_filters(); - for (int i = 0; i < filters->size(); i++) { - if ((*filters)[i].name() == "test.metadata_setter") { + for (auto& filter : *filters) { + if (filter.name() == "test.metadata_setter") { Protobuf::Struct existing_config; - if ((*filters)[i].has_typed_config()) { - (*filters)[i].typed_config().UnpackTo(&existing_config); + if (filter.has_typed_config()) { + filter.typed_config().UnpackTo(&existing_config); } // Set untyped metadata @@ -265,7 +265,7 @@ class NetworkExtProcFilterIntegrationTest .set_string_value(typed_value.value()); } - (*filters)[i].mutable_typed_config()->PackFrom(existing_config); + filter.mutable_typed_config()->PackFrom(existing_config); break; } } diff --git a/test/extensions/filters/network/ext_proc/ext_proc_test.cc b/test/extensions/filters/network/ext_proc/ext_proc_test.cc index f603fa2fb1ca1..7518e40bef873 100644 --- a/test/extensions/filters/network/ext_proc/ext_proc_test.cc +++ b/test/extensions/filters/network/ext_proc/ext_proc_test.cc @@ -16,7 +16,6 @@ namespace ExtProc { namespace { using testing::_; -using testing::ByMove; using testing::NiceMock; using testing::Return; using testing::ReturnNull; diff --git a/test/extensions/filters/network/generic_proxy/router/router_test.cc b/test/extensions/filters/network/generic_proxy/router/router_test.cc index 53e482433062c..b5681dfe1a69e 100644 --- a/test/extensions/filters/network/generic_proxy/router/router_test.cc +++ b/test/extensions/filters/network/generic_proxy/router/router_test.cc @@ -53,7 +53,7 @@ class MockGenericUpstream : public GenericUpstream { std::make_shared>(); NiceMock mock_upstream_connection_; absl::flat_hash_map requests_; - NiceMock mock_client_codec_{}; + NiceMock mock_client_codec_; }; class MockGenericUpstreamFactory : public GenericUpstreamFactory { diff --git a/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc b/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc index 00cca4f73378b..de66eb8e1fa99 100644 --- a/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_filter_dependencies_test.cc @@ -101,9 +101,10 @@ TEST_F(HttpConnectionManagerConfigTest, AllDependenciesSatisfiedOk) { ChefFilterFactory cf; Registry::InjectFactory rc(cf); - HttpConnectionManagerConfig(hcm_config, context_, date_provider_, route_config_provider_manager_, - &scoped_routes_config_provider_manager_, tracer_manager_, - filter_config_provider_manager_, creation_status_); + HttpConnectionManagerConfig give_me_a_name( + hcm_config, context_, date_provider_, route_config_provider_manager_, + &scoped_routes_config_provider_manager_, tracer_manager_, filter_config_provider_manager_, + creation_status_); ASSERT_TRUE(creation_status_.ok()); } @@ -116,9 +117,10 @@ TEST_F(HttpConnectionManagerConfigTest, UnusedProvidencyOk) { PantryFilterFactory pf; Registry::InjectFactory rf(pf); - HttpConnectionManagerConfig(hcm_config, context_, date_provider_, route_config_provider_manager_, - &scoped_routes_config_provider_manager_, tracer_manager_, - filter_config_provider_manager_, creation_status_); + HttpConnectionManagerConfig give_me_a_name( + hcm_config, context_, date_provider_, route_config_provider_manager_, + &scoped_routes_config_provider_manager_, tracer_manager_, filter_config_provider_manager_, + creation_status_); ASSERT_TRUE(creation_status_.ok()); } diff --git a/test/extensions/filters/network/http_connection_manager/config_test_base.h b/test/extensions/filters/network/http_connection_manager/config_test_base.h index 57f81bb430f08..87d3dd8137459 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test_base.h +++ b/test/extensions/filters/network/http_connection_manager/config_test_base.h @@ -51,10 +51,10 @@ class HttpConnectionManagerConfigTest : public testing::Test { TestScopedRuntime scoped_runtime_; void createHttpConnectionManagerConfig(const std::string& yaml) { creation_status_ = absl::OkStatus(); - HttpConnectionManagerConfig(parseHttpConnectionManagerFromYaml(yaml), context_, date_provider_, - route_config_provider_manager_, - &scoped_routes_config_provider_manager_, tracer_manager_, - filter_config_provider_manager_, creation_status_); + [[maybe_unused]] HttpConnectionManagerConfig config( + parseHttpConnectionManagerFromYaml(yaml), context_, date_provider_, + route_config_provider_manager_, &scoped_routes_config_provider_manager_, tracer_manager_, + filter_config_provider_manager_, creation_status_); THROW_IF_NOT_OK(creation_status_); } absl::Status creation_status_{absl::OkStatus()}; diff --git a/test/extensions/filters/network/rbac/filter_test.cc b/test/extensions/filters/network/rbac/filter_test.cc index eff59b8854c15..671fab9c627d5 100644 --- a/test/extensions/filters/network/rbac/filter_test.cc +++ b/test/extensions/filters/network/rbac/filter_test.cc @@ -33,9 +33,7 @@ class RoleBasedAccessControlNetworkFilterTest : public testing::Test { Envoy::Logger::Registry::setLogLevel(spdlog::level::debug); } - RoleBasedAccessControlNetworkFilterTest() { - // No per-logger log level setting needed - } + RoleBasedAccessControlNetworkFilterTest() = default; void setupPolicy(bool with_policy = true, bool continuous = false, diff --git a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc index b9172aab54df9..e84930b4ebe78 100644 --- a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc +++ b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc @@ -1256,7 +1256,7 @@ TEST_P(RedisProxyIntegrationTest, KEYSRequestAndResponse) { initialize(); std::string request = makeBulkStringArray({"keys", "*"}); std::string upstream_response = "*2\r\n$4\r\nbar1\r\n$4\r\nbar2\r\n"; - std::string downstream_response = upstream_response; + const std::string& downstream_response = upstream_response; // Make KEYS request from downstream IntegrationTcpClientPtr redis_client = makeTcpConnection(lookupPort("redis_proxy")); diff --git a/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc b/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc index 5d331e5b51895..b107684b8cc5f 100644 --- a/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc +++ b/test/extensions/filters/network/reverse_tunnel/filter_unit_test.cc @@ -70,7 +70,7 @@ class ReverseTunnelFilterUnitTest : public testing::Test { } public: - ReverseTunnelFilterUnitTest() : stats_store_(), overload_manager_() { + ReverseTunnelFilterUnitTest() { // Prepare proto config with defaults. proto_config_.set_request_path("/reverse_connections/request"); proto_config_.set_request_method(envoy::config::core::v3::GET); @@ -1379,7 +1379,7 @@ TEST_F(ReverseTunnelFilterUnitTest, VariousHttpMalformations) { // Invalid characters in headers "GET /reverse_connections/request HTTP/1.1\r\nHo\x00st: test\r\n\r\n"}; - for (size_t i = 0; i < malformed_requests.size(); ++i) { + for (const auto& malformed_request : malformed_requests) { // Create new filter for each test to avoid state issues auto test_filter = std::make_unique(config_, *stats_store_.rootScope(), overload_manager_); @@ -1395,7 +1395,7 @@ TEST_F(ReverseTunnelFilterUnitTest, VariousHttpMalformations) { test_filter->initializeReadFilterCallbacks(test_callbacks); - Buffer::OwnedImpl request(malformed_requests[i]); + Buffer::OwnedImpl request(malformed_request); EXPECT_EQ(Network::FilterStatus::StopIteration, test_filter->onData(request, false)); } } @@ -1542,7 +1542,7 @@ TEST_F(ReverseTunnelFilterWithUpstreamTest, ProcessAcceptedConnectionReportsConn TEST_F(ReverseTunnelFilterUnitTest, SystematicHttpErrorPatterns) { auto patterns = HttpErrorHelper::getHttpErrorPatterns(); - for (size_t i = 0; i < patterns.size(); ++i) { + for (const auto& pattern : patterns) { // Create new filter for each test to avoid state pollution auto error_filter = std::make_unique(config_, *stats_store_.rootScope(), overload_manager_); @@ -1564,7 +1564,7 @@ TEST_F(ReverseTunnelFilterUnitTest, SystematicHttpErrorPatterns) { error_filter->initializeReadFilterCallbacks(error_callbacks); // Test this error pattern - Buffer::OwnedImpl error_request(patterns[i]); + Buffer::OwnedImpl error_request(pattern); EXPECT_EQ(Network::FilterStatus::StopIteration, error_filter->onData(error_request, false)); } } diff --git a/test/extensions/filters/network/reverse_tunnel/integration_test.cc b/test/extensions/filters/network/reverse_tunnel/integration_test.cc index 8c05fbb8b1515..bc46d2f4b9938 100644 --- a/test/extensions/filters/network/reverse_tunnel/integration_test.cc +++ b/test/extensions/filters/network/reverse_tunnel/integration_test.cc @@ -156,7 +156,7 @@ name: envoy.filters.network.set_filter_state UNREFERENCED_PARAMETER(node_uuid); UNREFERENCED_PARAMETER(cluster_uuid); UNREFERENCED_PARAMETER(tenant_uuid); - return std::string(); + return {}; } std::string createHttpRequest(const std::string& method, const std::string& path, diff --git a/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc b/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc index 9aacc3201b66c..bb9dc04baeeed 100644 --- a/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc +++ b/test/extensions/filters/network/tcp_bandwidth_limit/filter_test.cc @@ -1,3 +1,5 @@ +#include + #include "source/extensions/filters/network/tcp_bandwidth_limit/tcp_bandwidth_limit.h" #include "test/mocks/event/mocks.h" @@ -9,7 +11,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::InSequence; using testing::Invoke; using testing::NiceMock; @@ -885,7 +886,7 @@ TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedReadWithTokens) { setup(yaml); getReadBuffer().add("test data"); - getReadTimer().reset(new Event::MockTimer()); + getReadTimer() = std::make_unique(); time_source_.advanceTimeWait(std::chrono::milliseconds(100)); EXPECT_CALL(read_filter_callbacks_, @@ -911,7 +912,7 @@ TEST_F(TcpBandwidthLimitFilterTest, ProcessBufferedWriteWithTokens) { EXPECT_CALL(write_filter_callbacks_, onAboveWriteBufferHighWatermark()); getWriteBuffer().add("test data"); - getWriteTimer().reset(new Event::MockTimer()); + getWriteTimer() = std::make_unique(); time_source_.advanceTimeWait(std::chrono::milliseconds(100)); diff --git a/test/extensions/filters/network/thrift_proxy/app_exception_impl_test.cc b/test/extensions/filters/network/thrift_proxy/app_exception_impl_test.cc index fe93f96cbcc60..c06af44f9b79f 100644 --- a/test/extensions/filters/network/thrift_proxy/app_exception_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/app_exception_impl_test.cc @@ -17,6 +17,7 @@ namespace ThriftProxy { TEST(AppExceptionImplTest, CopyConstructor) { AppException app_ex(AppExceptionType::InternalError, "msg"); + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) AppException copy(app_ex); EXPECT_EQ(app_ex.type_, copy.type_); diff --git a/test/extensions/filters/network/thrift_proxy/router_test.cc b/test/extensions/filters/network/thrift_proxy/router_test.cc index 80d300e994cd8..a7dfc813e1c96 100644 --- a/test/extensions/filters/network/thrift_proxy/router_test.cc +++ b/test/extensions/filters/network/thrift_proxy/router_test.cc @@ -641,8 +641,8 @@ class ThriftRouterTestBase { Registry::InjectFactory transport_register_; Registry::InjectFactory protocol_register_; - std::function mock_transport_cb_{}; - std::function mock_protocol_cb_{}; + std::function mock_transport_cb_; + std::function mock_protocol_cb_; NiceMock dispatcher_; NiceMock context_; @@ -657,8 +657,8 @@ class ThriftRouterTestBase { NiceMock callbacks_; NiceMock* transport_{}; NiceMock* protocol_{}; - std::vector*> all_transports_{}; - std::vector*> all_protocols_{}; + std::vector*> all_transports_; + std::vector*> all_protocols_; int32_t transports_requested_{}; int32_t protocols_requested_{}; NiceMock* route_{}; diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc index f81ce17379024..bf5c323f6fb6f 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_access_log_test.cc @@ -33,7 +33,7 @@ namespace { using ResponseValidator = Utils::DnsResponseValidator; Api::IoCallUint64Result makeNoError(uint64_t rc) { - return Api::IoCallUint64Result(rc, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})); + return {rc, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; } // Test access logger that captures formatted output using DNS custom commands diff --git a/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc b/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc index f47cb58056a5d..673a3b53f420d 100644 --- a/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc +++ b/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc @@ -70,12 +70,12 @@ header_prefix: {} return config; } - std::string header_key_1_{}; - std::string header_value_1_{}; - std::string header_value_2_{}; - std::string header_prefix_1_{}; - std::string access_token_value_{}; - std::string credentials_factory_name_{}; + std::string header_key_1_; + std::string header_value_1_; + std::string header_value_2_; + std::string header_prefix_1_; + std::string access_token_value_; + std::string credentials_factory_name_; }; // Parameterize the loopback test server socket address and gRPC client type. diff --git a/test/extensions/http/credential_injector/oauth2/credential_injector_oauth_integration_test.cc b/test/extensions/http/credential_injector/oauth2/credential_injector_oauth_integration_test.cc index 2e9de72f4a197..1fabcd71225a6 100644 --- a/test/extensions/http/credential_injector/oauth2/credential_injector_oauth_integration_test.cc +++ b/test/extensions/http/credential_injector/oauth2/credential_injector_oauth_integration_test.cc @@ -125,8 +125,8 @@ class CredentialInjectorIntegrationTest : public HttpIntegrationTest, encodeGoodJsonResponseBody(); } - FakeHttpConnectionPtr fake_oauth2_connection_{}; - FakeStreamPtr oauth2_request_{}; + FakeHttpConnectionPtr fake_oauth2_connection_; + FakeStreamPtr oauth2_request_; std::string request_body_; }; diff --git a/test/extensions/key_value/file_based/key_value_store_test.cc b/test/extensions/key_value/file_based/key_value_store_test.cc index 62ec7dca54abb..3127f586c8b29 100644 --- a/test/extensions/key_value/file_based/key_value_store_test.cc +++ b/test/extensions/key_value/file_based/key_value_store_test.cc @@ -35,7 +35,7 @@ class KeyValueStoreTest : public testing::Test { } NiceMock dispatcher_; std::string filename_; - std::unique_ptr store_{}; + std::unique_ptr store_; std::chrono::seconds flush_interval_{5}; Event::MockTimer* ttl_timer_ = nullptr; Event::MockTimer* flush_timer_ = nullptr; diff --git a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb_test.cc b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb_test.cc index 490c51b2a6ea4..55d784f41673c 100644 --- a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb_test.cc +++ b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/client_side_weighted_round_robin_lb_test.cc @@ -418,12 +418,13 @@ TEST_P(ClientSideWeightedRoundRobinLoadBalancerTest, SlowStartConfig_RampUp) { size_t h1_count = 0, h2_count = 0; for (size_t i = 0; i < picks1; ++i) { auto chosen = lb_->chooseHost(nullptr).host; - if (chosen == h1) + if (chosen == h1) { ++h1_count; - else if (chosen == h2) + } else if (chosen == h2) { ++h2_count; - else + } else { FAIL(); + } } // Expect h1 to be chosen noticeably more often (ratio ~ (1):(0.35)). EXPECT_GT(h1_count, h2_count); @@ -436,12 +437,13 @@ TEST_P(ClientSideWeightedRoundRobinLoadBalancerTest, SlowStartConfig_RampUp) { h2_count = 0; for (size_t i = 0; i < picks1; ++i) { auto chosen = lb_->chooseHost(nullptr).host; - if (chosen == h1) + if (chosen == h1) { ++h1_count; - else if (chosen == h2) + } else if (chosen == h2) { ++h2_count; - else + } else { FAIL(); + } } // Now expect closer to 2:1 ratio in favor of h1; i.e., still h1 > h2. EXPECT_GT(h1_count, h2_count); @@ -454,12 +456,13 @@ TEST_P(ClientSideWeightedRoundRobinLoadBalancerTest, SlowStartConfig_RampUp) { h2_count = 0; for (size_t i = 0; i < picks1; ++i) { auto chosen = lb_->chooseHost(nullptr).host; - if (chosen == h1) + if (chosen == h1) { ++h1_count; - else if (chosen == h2) + } else if (chosen == h2) { ++h2_count; - else + } else { FAIL(); + } } // Expect approximately equal selection; allow 30% tolerance. double final_ratio = diff --git a/test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h b/test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h index a69bc0b9628d0..2f3cea65e0a65 100644 --- a/test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h +++ b/test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h @@ -58,9 +58,13 @@ class TestZoneAwareLoadBalancer : public ZoneAwareLoadBalancerBase { : ZoneAwareLoadBalancerBase(priority_set, nullptr, lb_stats, runtime, random, healthy_panic_threshold, locality_config) {} void runInvalidLocalitySourceType() { + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) localitySourceType(static_cast(123)); } - void runInvalidSourceType() { sourceType(static_cast(123)); } + void runInvalidSourceType() { + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + sourceType(static_cast(123)); + } HostConstSharedPtr chooseHostOnce(LoadBalancerContext*) override { PANIC("not implemented"); } HostConstSharedPtr peekAnotherHost(LoadBalancerContext*) override { PANIC("not implemented"); } }; diff --git a/test/extensions/network/dns_resolver/benchmark/dns_resolver_benchmark.cc b/test/extensions/network/dns_resolver/benchmark/dns_resolver_benchmark.cc index c703429545f44..16aeefce1b472 100644 --- a/test/extensions/network/dns_resolver/benchmark/dns_resolver_benchmark.cc +++ b/test/extensions/network/dns_resolver/benchmark/dns_resolver_benchmark.cc @@ -72,7 +72,7 @@ createDnsResolver(Event::Dispatcher& dispatcher, Api::Api& api, // fake-server response -> callback. // --------------------------------------------------------------------------- -static void BM_CaresSingleQueryLatency(::benchmark::State& state) { +static void bmCaresSingleQueryLatency(::benchmark::State& state) { ensureLibeventInitialized(); Network::Test::FakeUdpDnsServer dns_server; dns_server.setDefaultAResponse("1.2.3.4"); @@ -100,7 +100,7 @@ static void BM_CaresSingleQueryLatency(::benchmark::State& state) { dns_server.stop(); } -static void BM_HickorySingleQueryLatency(::benchmark::State& state) { +static void bmHickorySingleQueryLatency(::benchmark::State& state) { if (benchmark::skipExpensiveBenchmarks()) { state.SkipWithError("Skipping expensive Hickory benchmark."); return; @@ -139,7 +139,7 @@ static void BM_HickorySingleQueryLatency(::benchmark::State& state) { // queries are completed. Reports items/second. // --------------------------------------------------------------------------- -static void BM_CaresConcurrentQueries(::benchmark::State& state) { +static void bmCaresConcurrentQueries(::benchmark::State& state) { const int concurrent = static_cast(state.range(0)); if (benchmark::skipExpensiveBenchmarks() && concurrent > 50) { @@ -178,7 +178,7 @@ static void BM_CaresConcurrentQueries(::benchmark::State& state) { dns_server.stop(); } -static void BM_HickoryConcurrentQueries(::benchmark::State& state) { +static void bmHickoryConcurrentQueries(::benchmark::State& state) { const int concurrent = static_cast(state.range(0)); if (benchmark::skipExpensiveBenchmarks() && concurrent > 10) { @@ -223,7 +223,7 @@ static void BM_HickoryConcurrentQueries(::benchmark::State& state) { // It is relevant for dynamic cluster creation. // --------------------------------------------------------------------------- -static void BM_CaresResolverCreation(::benchmark::State& state) { +static void bmCaresResolverCreation(::benchmark::State& state) { ensureLibeventInitialized(); Api::ApiPtr api = Api::createApiForTest(); Event::DispatcherPtr dispatcher = api->allocateDispatcher("cares_bench"); @@ -236,7 +236,7 @@ static void BM_CaresResolverCreation(::benchmark::State& state) { } } -static void BM_HickoryResolverCreation(::benchmark::State& state) { +static void bmHickoryResolverCreation(::benchmark::State& state) { if (benchmark::skipExpensiveBenchmarks()) { state.SkipWithError("Skipping expensive Hickory resolver creation benchmark."); return; @@ -256,17 +256,17 @@ static void BM_HickoryResolverCreation(::benchmark::State& state) { // --- Registration --- -BENCHMARK(BM_CaresSingleQueryLatency)->Unit(::benchmark::kMicrosecond); -BENCHMARK(BM_HickorySingleQueryLatency)->Unit(::benchmark::kMicrosecond); +BENCHMARK(bmCaresSingleQueryLatency)->Unit(::benchmark::kMicrosecond); +BENCHMARK(bmHickorySingleQueryLatency)->Unit(::benchmark::kMicrosecond); -BENCHMARK(BM_CaresConcurrentQueries) +BENCHMARK(bmCaresConcurrentQueries) ->Arg(1) ->Arg(10) ->Arg(50) ->Arg(100) ->Arg(500) ->Unit(::benchmark::kMicrosecond); -BENCHMARK(BM_HickoryConcurrentQueries) +BENCHMARK(bmHickoryConcurrentQueries) ->Arg(1) ->Arg(10) ->Arg(50) @@ -274,8 +274,8 @@ BENCHMARK(BM_HickoryConcurrentQueries) ->Arg(500) ->Unit(::benchmark::kMicrosecond); -BENCHMARK(BM_CaresResolverCreation)->Unit(::benchmark::kMicrosecond); -BENCHMARK(BM_HickoryResolverCreation)->Unit(::benchmark::kMicrosecond); +BENCHMARK(bmCaresResolverCreation)->Unit(::benchmark::kMicrosecond); +BENCHMARK(bmHickoryResolverCreation)->Unit(::benchmark::kMicrosecond); } // namespace } // namespace Envoy diff --git a/test/extensions/rate_limit_descriptors/expr/config_test.cc b/test/extensions/rate_limit_descriptors/expr/config_test.cc index 295c8060cd9a8..51df14c3ad442 100644 --- a/test/extensions/rate_limit_descriptors/expr/config_test.cc +++ b/test/extensions/rate_limit_descriptors/expr/config_test.cc @@ -32,7 +32,7 @@ class RateLimitPolicyEntryTest : public testing::Test { absl::Status creation_status; rate_limit_entry_ = std::make_unique(rate_limit, context_, creation_status); - THROW_IF_NOT_OK(creation_status); + THROW_IF_NOT_OK_REF(creation_status); } NiceMock context_; diff --git a/test/extensions/resource_monitors/cgroup_memory/config_test.cc b/test/extensions/resource_monitors/cgroup_memory/config_test.cc index f8c30efb614ab..b2899b4dcffec 100644 --- a/test/extensions/resource_monitors/cgroup_memory/config_test.cc +++ b/test/extensions/resource_monitors/cgroup_memory/config_test.cc @@ -23,7 +23,6 @@ namespace CgroupMemory { using testing::NiceMock; using testing::Return; -using testing::StartsWith; // Basic test to verify factory registration TEST(CgroupMemoryConfigTest, BasicTest) { diff --git a/test/extensions/stats_sinks/open_telemetry/open_telemetry_benchmark.cc b/test/extensions/stats_sinks/open_telemetry/open_telemetry_benchmark.cc index 1a5f8e9ac93b1..62568e0f9f65e 100644 --- a/test/extensions/stats_sinks/open_telemetry/open_telemetry_benchmark.cc +++ b/test/extensions/stats_sinks/open_telemetry/open_telemetry_benchmark.cc @@ -100,7 +100,7 @@ struct BenchmarkSetup { std::vector>> histograms; }; -void BM_OpenTelemetrySinkFlush_NoLimit(benchmark::State& state) { +void bmOpenTelemetrySinkFlushNoLimit(benchmark::State& state) { BenchmarkSetup setup; testing::NiceMock server_factory_context; @@ -117,9 +117,9 @@ void BM_OpenTelemetrySinkFlush_NoLimit(benchmark::State& state) { sink.flush(*setup.snapshot); } } -BENCHMARK(BM_OpenTelemetrySinkFlush_NoLimit); +BENCHMARK(bmOpenTelemetrySinkFlushNoLimit); -void BM_OpenTelemetrySinkFlush_TrafficSplit_200(benchmark::State& state) { +void bmOpenTelemetrySinkFlushTrafficSplit200(benchmark::State& state) { BenchmarkSetup setup; testing::NiceMock server_factory_context; @@ -137,7 +137,7 @@ void BM_OpenTelemetrySinkFlush_TrafficSplit_200(benchmark::State& state) { sink.flush(*setup.snapshot); } } -BENCHMARK(BM_OpenTelemetrySinkFlush_TrafficSplit_200); +BENCHMARK(bmOpenTelemetrySinkFlushTrafficSplit200); } // namespace } // namespace OpenTelemetry diff --git a/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc b/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc index 80694b2be3cf5..569b1224b4708 100644 --- a/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc +++ b/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc @@ -242,8 +242,9 @@ class OtlpMetricsFlusherTests : public OpenTelemetryStatsSinkTests { } else if (metric.has_histogram() && metric.histogram().data_points_size() > 0) { attributes = &metric.histogram().data_points()[0].attributes(); } - if (attributes == nullptr) + if (attributes == nullptr) { return std::string(""); + } std::vector> attrs; for (const auto& attr : *attributes) { attrs.push_back({attr.key(), attr.value().string_value()}); @@ -262,16 +263,18 @@ class OtlpMetricsFlusherTests : public OpenTelemetryStatsSinkTests { void sortDataPoints(opentelemetry::proto::metrics::v1::Metric& metric) { auto sort_by_attr = [](const auto& a, const auto& b) { auto get_attr_str = [](const auto& dp) { - if (dp.attributes().empty()) + if (dp.attributes().empty()) { return std::string(""); + } std::vector attrs; for (const auto& attr : dp.attributes()) { attrs.push_back(attr.key() + "=" + attr.value().string_value()); } std::sort(attrs.begin(), attrs.end()); std::string res; - for (const auto& s : attrs) + for (const auto& s : attrs) { res += s + ";"; + } return res; }; return get_attr_str(a) < get_attr_str(b); @@ -1513,8 +1516,8 @@ class MetricAggregatorTests : public testing::Test { std::vector supported_quantiles_; std::vector computed_quantiles_; - std::vector supported_buckets_{}; - std::vector computed_buckets_{}; + std::vector supported_buckets_; + std::vector computed_buckets_; uint64_t sample_count_{0}; double sample_sum_{0}; }; diff --git a/test/extensions/tracers/datadog/agent_http_client_test.cc b/test/extensions/tracers/datadog/agent_http_client_test.cc index c0732c8083d45..32fc7e9e15b96 100644 --- a/test/extensions/tracers/datadog/agent_http_client_test.cc +++ b/test/extensions/tracers/datadog/agent_http_client_test.cc @@ -10,6 +10,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/enum_test_utils.h" #include "test/test_common/utility.h" #include "absl/types/optional.h" @@ -423,7 +424,7 @@ TEST_F(DatadogAgentHttpClientTest, OnErrorOther) { Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); msg->body().add("{}"); - const auto bogus_value = static_cast(-1); + const auto bogus_value = uncheckedEnumCastForTest(-1); callbacks_->onFailure(request_, bogus_value); } diff --git a/test/extensions/tracers/datadog/span_test.cc b/test/extensions/tracers/datadog/span_test.cc index e744c2834313f..0dc20ca85ec42 100644 --- a/test/extensions/tracers/datadog/span_test.cc +++ b/test/extensions/tracers/datadog/span_test.cc @@ -70,7 +70,7 @@ class TestLogger : public datadog::tracing::Logger { } const std::vector& errors() const { return errors_; } - const std::vector& startup_messages() const { return startup_messages_; } + const std::vector& startupMessages() const { return startup_messages_; } private: std::vector errors_; @@ -157,7 +157,7 @@ class DatadogTracerSpanTest : public testing::Test { validated_config = datadog::tracing::finalize_config(config); EXPECT_TRUE(validated_config); } - return datadog::tracing::Tracer(*validated_config, id_generator_); + return {*validated_config, id_generator_}; } protected: diff --git a/test/extensions/tracers/dynamic_modules/tracer_test.cc b/test/extensions/tracers/dynamic_modules/tracer_test.cc index 1141c42ac205d..3290a80fb2774 100644 --- a/test/extensions/tracers/dynamic_modules/tracer_test.cc +++ b/test/extensions/tracers/dynamic_modules/tracer_test.cc @@ -9,7 +9,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::NiceMock; namespace Envoy { diff --git a/test/extensions/tracers/opentelemetry/operation_name_test.cc b/test/extensions/tracers/opentelemetry/operation_name_test.cc index ef931eb8de90d..b5bc109f5adce 100644 --- a/test/extensions/tracers/opentelemetry/operation_name_test.cc +++ b/test/extensions/tracers/opentelemetry/operation_name_test.cc @@ -22,7 +22,7 @@ class OpenTelemetryTracerOperationNameTest : public testing::Test { protected: NiceMock config; NiceMock stream_info; - Tracing::TestTraceContextImpl trace_context{}; + Tracing::TestTraceContextImpl trace_context; NiceMock context; NiceMock cluster_manager_; }; diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/static/static_config_resource_detector_integration_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/static/static_config_resource_detector_integration_test.cc index cdbc8fea07fd5..13cda255ebd06 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/static/static_config_resource_detector_integration_test.cc +++ b/test/extensions/tracers/opentelemetry/resource_detectors/static/static_config_resource_detector_integration_test.cc @@ -130,7 +130,7 @@ TEST_P(StaticConfigResourceDetectorIntegrationTest, TestResourceAttributeSet) { absl::flat_hash_map expected_attributes = {{"key1", "value1"}, {"key2", "value2"}}; int matched_attributes_count = 0; - ASSERT_TRUE(message.resource_spans().size() > 0); + ASSERT_TRUE(!message.resource_spans().empty()); for (auto& actual : message.resource_spans()[0].resource().attributes()) { auto expected = expected_attributes.find(actual.key()); if (expected != expected_attributes.end()) { diff --git a/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc index 4cf5353993916..3ca359374538d 100644 --- a/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc @@ -52,7 +52,7 @@ class SamplerFactoryTest : public testing::Test { protected: NiceMock config; NiceMock stream_info; - Tracing::TestTraceContextImpl trace_context{}; + Tracing::TestTraceContextImpl trace_context; NiceMock context; }; diff --git a/test/extensions/transport_sockets/alts/alts_channel_pool_test.cc b/test/extensions/transport_sockets/alts/alts_channel_pool_test.cc index 578819cb2e14b..a6b325d1f70cb 100644 --- a/test/extensions/transport_sockets/alts/alts_channel_pool_test.cc +++ b/test/extensions/transport_sockets/alts/alts_channel_pool_test.cc @@ -1,5 +1,4 @@ -#include - +#include #include #include #include diff --git a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc index 0fede029bee62..af321ec76b336 100644 --- a/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc +++ b/test/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator_test.cc @@ -106,7 +106,7 @@ class TestSPIFFEValidator : public testing::Test { config_ = std::make_unique( typed_conf, allow_expired_certificate_, san_matchers_); - if (trust_bundle_file != "") { + if (!trust_bundle_file.empty()) { EXPECT_CALL(factory_context_.dispatcher_, createFilesystemWatcher_()) .WillRepeatedly(testing::Invoke([trust_bundle_file] { Filesystem::MockWatcher* mock_watcher = new NiceMock(); @@ -174,7 +174,7 @@ class TestSPIFFEValidator : public testing::Test { private: bool allow_expired_certificate_{false}; TestCertificateValidationContextConfigPtr config_; - std::vector san_matchers_{}; + std::vector san_matchers_; Stats::TestUtil::TestStore store_; SslStats stats_; Event::TestRealTimeSystem time_system_; diff --git a/test/extensions/upstreams/http/generic/config_test.cc b/test/extensions/upstreams/http/generic/config_test.cc index 6a96dcd3329ab..ae311a92b58fd 100644 --- a/test/extensions/upstreams/http/generic/config_test.cc +++ b/test/extensions/upstreams/http/generic/config_test.cc @@ -2,6 +2,7 @@ #include "test/mocks/router/mocks.h" #include "test/mocks/upstream/thread_local_cluster.h" +#include "test/test_common/enum_test_utils.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -50,7 +51,7 @@ TEST_F(GenericGenericConnPoolFactoryTest, InvalidConnPool) { // Passes an invalid UpstreamProtocol and check a nullptr is returned. EXPECT_FALSE(factory_.createGenericConnPool( nullptr, thread_local_cluster_, - static_cast(0xff), priority_, + uncheckedEnumCastForTest(0xff), priority_, Envoy::Http::Protocol::Http2, nullptr, *message_)); } diff --git a/test/extensions/upstreams/http/udp/config_test.cc b/test/extensions/upstreams/http/udp/config_test.cc index 0561c36b3dc8b..45b6bec5d0fa5 100644 --- a/test/extensions/upstreams/http/udp/config_test.cc +++ b/test/extensions/upstreams/http/udp/config_test.cc @@ -14,7 +14,6 @@ namespace Http { namespace Udp { using ::testing::NiceMock; -using ::testing::Return; class UdpGenericConnPoolFactoryTest : public ::testing::Test { public: diff --git a/test/fuzz/fuzz_runner.cc b/test/fuzz/fuzz_runner.cc index f6fa19bb28193..ded16eb3bd3af 100644 --- a/test/fuzz/fuzz_runner.cc +++ b/test/fuzz/fuzz_runner.cc @@ -71,6 +71,7 @@ void addCleanupHook(std::function cleanup) { void runCleanupHooks() { if (cleanup_hooks != nullptr) { // Run hooks in reverse order from how they were added. + // NOLINTNEXTLINE(modernize-loop-convert) for (auto iter = cleanup_hooks->rbegin(), end = cleanup_hooks->rend(); iter != end; ++iter) { (*iter)(); } diff --git a/test/integration/ads_integration.h b/test/integration/ads_integration.h index c02bd4e99b717..4334e3b1698ab 100644 --- a/test/integration/ads_integration.h +++ b/test/integration/ads_integration.h @@ -102,9 +102,9 @@ class AdsIntegrationTest public testing::TestWithParam< std::tuple> { public: - AdsIntegrationTest() : AdsIntegrationTestBase(ipVersion(), sotwOrDelta()) {} + AdsIntegrationTest() : AdsIntegrationTestBase(testIpVersion(), testSotwOrDelta()) {} AdsIntegrationTest(const std::string& config) - : AdsIntegrationTestBase(ipVersion(), sotwOrDelta(), config) {} + : AdsIntegrationTestBase(testIpVersion(), testSotwOrDelta(), config) {} void TearDown() override { cleanUpXdsConnection(); } @@ -138,6 +138,10 @@ class AdsIntegrationTest return sotwOrDelta() == Grpc::SotwOrDelta::Sotw || sotwOrDelta() == Grpc::SotwOrDelta::UnifiedSotw; } + +private: + static Network::Address::IpVersion testIpVersion() { return std::get<0>(GetParam()); } + static Grpc::SotwOrDelta testSotwOrDelta() { return std::get<2>(GetParam()); } }; // When old delta subscription state goes away, we could replace this macro back with diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index 4482e08c318f5..7d3d5395ad11f 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -2826,7 +2826,7 @@ TEST_P(AdsIntegrationTest, SrdsPausedDuringLds) { // ADS integration tests that exercise the ADS-replacement mechanism. class AdsReplacementIntegrationTest : public AdsIntegrationTest { public: - AdsReplacementIntegrationTest() {} + AdsReplacementIntegrationTest() = default; void createXdsUpstream() override { // Create the first ADS upstream. diff --git a/test/integration/async_round_robin_lb.cc b/test/integration/async_round_robin_lb.cc index 9d23416943a8e..afd7ab06369a9 100644 --- a/test/integration/async_round_robin_lb.cc +++ b/test/integration/async_round_robin_lb.cc @@ -13,7 +13,7 @@ class AsyncRoundRobinLoadBalancer : public RoundRobinLoadBalancer { HostConstSharedPtr preselected_host_; LoadBalancerContext* context_; - virtual void cancel() override { detachable_->parent_ = nullptr; } + void cancel() override { detachable_->parent_ = nullptr; } // If cancel is called, parent will be nulled out. struct Detachable { diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index df95f77a1c1bc..f17fa8d29bc6f 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -672,7 +672,7 @@ class BaseIntegrationTest : protected Logger::Loggable { // If this member is not empty, the test will use a fixed RNG value specified // by it. - absl::optional deterministic_value_{}; + absl::optional deterministic_value_; // Set true when your test will itself take care of ensuring listeners are up, and registering // them in the port_map_. diff --git a/test/integration/cluster_filter_integration_test.cc b/test/integration/cluster_filter_integration_test.cc index e716b0a5195ff..4cb6e092d0b61 100644 --- a/test/integration/cluster_filter_integration_test.cc +++ b/test/integration/cluster_filter_integration_test.cc @@ -30,7 +30,7 @@ class PoliteFilter : public Network::Filter, Logger::Loggableconnection(), data.length(), end_stream); if (!read_greeted_) { - if (greeting_.length() > 0) { + if (!greeting_.empty()) { Buffer::OwnedImpl greeter(greeting_); read_callbacks_->injectReadDataToFilterChain(greeter, false); } @@ -42,7 +42,7 @@ class PoliteFilter : public Network::Filter, Logger::Loggableconnection(), data.length(), end_stream); if (!write_greeted_) { - if (greeting_.length() > 0) { + if (!greeting_.empty()) { Buffer::OwnedImpl greeter("please "); write_callbacks_->injectWriteDataToFilterChain(greeter, false); } @@ -124,7 +124,7 @@ class ClusterFilterIntegrationTestBase : public testing::TestWithParam> on_new_connection_called_after_on_write_{}; + std::atomic> on_new_connection_called_after_on_write_; }; class ClusterFilterTcpIntegrationTest : public ClusterFilterIntegrationTestBase, diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index 105060835d655..506cec125f466 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -517,7 +517,7 @@ class FakeConnectionBase : public Logger::Loggable { Event::Dispatcher& dispatcher_; bool initialized_ ABSL_GUARDED_BY(lock_){}; bool half_closed_ ABSL_GUARDED_BY(lock_){}; - std::atomic pending_cbs_{}; + std::atomic pending_cbs_{0}; Event::TestTimeSystem& time_system_; }; diff --git a/test/integration/filter_manager_integration_test.cc b/test/integration/filter_manager_integration_test.cc index 56580887759a4..77a3511678156 100644 --- a/test/integration/filter_manager_integration_test.cc +++ b/test/integration/filter_manager_integration_test.cc @@ -49,7 +49,7 @@ class Throttler { private: void onTimerTick(); - Buffer::OwnedImpl buffer_{}; + Buffer::OwnedImpl buffer_; bool end_stream_{}; const Event::TimerPtr timer_; diff --git a/test/integration/filters/async_upstream_filter.cc b/test/integration/filters/async_upstream_filter.cc index 0a7862dd9b72f..6d885ff4870c0 100644 --- a/test/integration/filters/async_upstream_filter.cc +++ b/test/integration/filters/async_upstream_filter.cc @@ -62,7 +62,7 @@ class AsyncUpstreamFilter : public Http::PassThroughFilter, void onComplete() override { stream_ = nullptr; } void onReset() override {} - ~AsyncUpstreamFilter() { + ~AsyncUpstreamFilter() override { if (stream_) { stream_->reset(); } diff --git a/test/integration/filters/tee_filter.h b/test/integration/filters/tee_filter.h index 3a1121d8629c4..9e5434b034457 100644 --- a/test/integration/filters/tee_filter.h +++ b/test/integration/filters/tee_filter.h @@ -18,8 +18,8 @@ namespace Envoy { struct StreamTee { virtual ~StreamTee() = default; mutable absl::Mutex mutex_; - Buffer::OwnedImpl request_body_ ABSL_GUARDED_BY(mutex_){}; - Buffer::OwnedImpl response_body_ ABSL_GUARDED_BY(mutex_){}; + Buffer::OwnedImpl request_body_ ABSL_GUARDED_BY(mutex_); + Buffer::OwnedImpl response_body_ ABSL_GUARDED_BY(mutex_); bool decode_end_stream_ ABSL_GUARDED_BY(mutex_){false}; bool encode_end_stream_ ABSL_GUARDED_BY(mutex_){false}; Http::RequestHeaderMapPtr request_headers_ ABSL_GUARDED_BY(mutex_){nullptr}; diff --git a/test/integration/listener_extension_discovery_integration_test.cc b/test/integration/listener_extension_discovery_integration_test.cc index e5977d64f1fad..8171637d9300a 100644 --- a/test/integration/listener_extension_discovery_integration_test.cc +++ b/test/integration/listener_extension_discovery_integration_test.cc @@ -638,7 +638,7 @@ TEST_P(ListenerExtensionDiscoveryIntegrationTest, BasicSuccessWithConfigDump) { Json::ObjectSharedPtr json = Json::Factory::loadFromString(response->body()).value(); size_t index = 0; auto array = json->getObjectArray("configs").value(); - for (Json::ObjectSharedPtr obj_ptr : array) { + for (const Json::ObjectSharedPtr& obj_ptr : array) { EXPECT_TRUE(expected_types[index].compare(obj_ptr->getString("@type").value()) == 0); index++; } diff --git a/test/integration/listener_lds_integration_test.cc b/test/integration/listener_lds_integration_test.cc index d0b6d0524342a..01c87459844b7 100644 --- a/test/integration/listener_lds_integration_test.cc +++ b/test/integration/listener_lds_integration_test.cc @@ -1254,7 +1254,7 @@ class ListenerFilterIntegrationTest : public BaseIntegrationTest, envoy::config::listener::v3::Listener listener_config_; FakeHttpConnectionPtr lds_connection_; - FakeStreamPtr lds_stream_{}; + FakeStreamPtr lds_stream_; }; TEST_P(ListenerFilterIntegrationTest, InspectDataFilterDrainData) { diff --git a/test/integration/overload_integration_test.cc b/test/integration/overload_integration_test.cc index edd8574013898..b2461be6c7ffd 100644 --- a/test/integration/overload_integration_test.cc +++ b/test/integration/overload_integration_test.cc @@ -24,14 +24,15 @@ using testing::HasSubstr; class OverloadIntegrationTest : public BaseOverloadIntegrationTest, public HttpProtocolIntegrationTest { protected: - void initializeOverloadManager(const envoy::config::overload::v3::OverloadAction& overload_action, - absl::optional appendLocalOverloadHeader = absl::nullopt) { + void + initializeOverloadManager(const envoy::config::overload::v3::OverloadAction& overload_action, + absl::optional append_local_overload_header = absl::nullopt) { setupOverloadManagerConfig(overload_action); config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { *bootstrap.mutable_overload_manager() = this->overload_manager_config_; }); - if (appendLocalOverloadHeader.has_value() && appendLocalOverloadHeader.value()) { + if (append_local_overload_header.has_value() && append_local_overload_header.value()) { config_helper_.addConfigModifier( [=](envoy::extensions::filters::network::http_connection_manager::v3:: HttpConnectionManager& cm) -> void { cm.set_append_local_overload(true); }); diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index e49111ec2a2d8..d7258235115af 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -43,7 +43,7 @@ INSTANTIATE_TEST_SUITE_P(QuicHttpMultiAddressesIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); -static std::string SPATestParamsToString( +static std::string spaTestParamsToString( const ::testing::TestParamInfo>& params) { return absl::StrCat(TestUtility::ipVersionToString(std::get<0>(params.param)), "_", std::get<1>(params.param) ? "all_clients_impl" : "quiche_client_impl", "_", @@ -54,7 +54,7 @@ INSTANTIATE_TEST_SUITE_P( QuicHttpIntegrationSPATests, QuicHttpIntegrationSPATest, testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), testing::Values(true, false), testing::Values(true, false)), - SPATestParamsToString); + spaTestParamsToString); TEST_P(QuicHttpIntegrationTest, GetRequestAndEmptyResponse) { useAccessLog("%DOWNSTREAM_TLS_VERSION% %DOWNSTREAM_TLS_CIPHER% %DOWNSTREAM_TLS_SESSION_ID%"); @@ -370,7 +370,7 @@ class QuicHttpIntegrationPortMigrationTest } }; -static std::string PortMigrationTestParamsToString( +static std::string portMigrationTestParamsToString( const ::testing::TestParamInfo>& params) { return absl::StrCat(TestUtility::ipVersionToString(std::get<0>(params.param)), "_", std::get<1>(params.param) ? "migration_by_quiche" : "migration_in_house"); @@ -380,7 +380,7 @@ INSTANTIATE_TEST_SUITE_P( QuicHttpIntegrationPortMigrationTest, QuicHttpIntegrationPortMigrationTest, testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), testing::Values(true, false)), - PortMigrationTestParamsToString); + portMigrationTestParamsToString); TEST_P(QuicHttpIntegrationPortMigrationTest, PortMigrationOnPathDegrading) { setConcurrency(2); diff --git a/test/integration/ssl_utility.h b/test/integration/ssl_utility.h index 783a92c60f228..26f2af8afe4ed 100644 --- a/test/integration/ssl_utility.h +++ b/test/integration/ssl_utility.h @@ -72,7 +72,7 @@ struct ClientSslTransportOptions { bool alpn_{}; bool client_ecdsa_cert_{false}; - std::vector cipher_suites_{}; + std::vector cipher_suites_; std::string san_; std::vector sigalgs_; std::vector curves_; diff --git a/test/integration/tcp_proxy_integration_test.cc b/test/integration/tcp_proxy_integration_test.cc index f855f29590d55..86c503d8a02c9 100644 --- a/test/integration/tcp_proxy_integration_test.cc +++ b/test/integration/tcp_proxy_integration_test.cc @@ -2017,8 +2017,9 @@ TEST_P(TcpProxyReceiveBeforeConnectIntegrationTest, UpstreamBufferHighWatermark) config_helper_.setBufferLimits(upstream_buffer_limit, downstream_buffer_limit); std::string data; - for (uint32_t i = 0; i < data_size / 4; i++) + for (uint32_t i = 0; i < data_size / 4; i++) { data += "abcd"; + } initialize(); FakeRawConnectionPtr fake_upstream_connection; diff --git a/test/integration/udp_tunneling_integration_test.cc b/test/integration/udp_tunneling_integration_test.cc index e23c6819f53b1..231280f8a7d8e 100644 --- a/test/integration/udp_tunneling_integration_test.cc +++ b/test/integration/udp_tunneling_integration_test.cc @@ -107,7 +107,7 @@ class ConnectUdpTerminationIntegrationTest : public HttpProtocolIntegrationTest IntegrationStreamDecoderPtr response_; bool enable_timeout_{}; - std::string host_to_match_{}; + std::string host_to_match_; }; TEST_P(ConnectUdpTerminationIntegrationTest, ExchangeCapsules) { diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index ce5d6e9107486..7e75079fa92d3 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -341,7 +341,7 @@ class MockFilterChain : public DrainableFilterChain { MOCK_METHOD(bool, addedViaApi, (), (const)); MOCK_METHOD(const FilterChainInfoSharedPtr&, filterChainInfo, (), (const)); - envoy::config::core::v3::Metadata metadata_{}; + envoy::config::core::v3::Metadata metadata_; FilterChainInfoSharedPtr filter_chain_info_; }; @@ -355,7 +355,7 @@ class MockFilterChainInfo : public FilterChainInfo { MOCK_METHOD(const Envoy::Config::TypedMetadata&, typedMetadata, (), (const)); std::string filter_chain_name_{"mock"}; - envoy::config::core::v3::Metadata metadata_{}; + envoy::config::core::v3::Metadata metadata_; }; class MockFilterChainManager : public FilterChainManager { diff --git a/test/mocks/server/health_checker_factory_context.h b/test/mocks/server/health_checker_factory_context.h index 211af4e6755b7..c38bbfc29070e 100644 --- a/test/mocks/server/health_checker_factory_context.h +++ b/test/mocks/server/health_checker_factory_context.h @@ -45,7 +45,7 @@ class MockHealthCheckerFactoryContext : public virtual HealthCheckerFactoryConte testing::NiceMock dispatcher_; testing::NiceMock random_; testing::NiceMock runtime_; - testing::NiceMock api_{}; + testing::NiceMock api_; testing::NiceMock access_log_manager_; std::unique_ptr> event_logger_; testing::NiceMock server_context_; diff --git a/test/mocks/server/hot_restart.cc b/test/mocks/server/hot_restart.cc index 114964e86ce5e..87714a0911f84 100644 --- a/test/mocks/server/hot_restart.cc +++ b/test/mocks/server/hot_restart.cc @@ -8,7 +8,6 @@ namespace Envoy { namespace Server { -using ::testing::_; using ::testing::Return; using ::testing::ReturnRef; diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index 9ad28703e8c01..45f4e143a8311 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -137,7 +137,7 @@ class MockClientContextConfig : public ClientContextConfig { std::string alpn_{""}; std::string sigalgs_{""}; Network::Address::IpList iplist_; - std::string path_{}; + std::string path_; }; class MockServerContextConfig : public ServerContextConfig { diff --git a/test/mocks/stats/mocks.h b/test/mocks/stats/mocks.h index 3a664ac0ddb09..25aa5b7e4bad5 100644 --- a/test/mocks/stats/mocks.h +++ b/test/mocks/stats/mocks.h @@ -322,6 +322,7 @@ class MockScope : public TestUtil::TestScope { // to allow tests to inject EXPECT_CALL hooks for those. MOCK_METHOD(Counter&, counterFromStatNameWithTags, (const StatName&, StatNameTagVectorOptConstRef)); + // NOLINTNEXTLINE(readability-identifier-naming) Counter& counterFromStatNameWithTags_(const StatName& name, StatNameTagVectorOptConstRef); Gauge& gaugeFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef, diff --git a/test/mocks/thread_local/mocks.h b/test/mocks/thread_local/mocks.h index 09dff2377706c..7ca4fa8147ce4 100644 --- a/test/mocks/thread_local/mocks.h +++ b/test/mocks/thread_local/mocks.h @@ -40,6 +40,7 @@ class MockInstance : public Instance { void shutdownThread_() { shutdown_ = true; // Reverse order which is same as the production code. + // NOLINTNEXTLINE(modernize-loop-convert) for (auto it = data_.rbegin(); it != data_.rend(); ++it) { it->reset(); } diff --git a/test/mocks/upstream/host.cc b/test/mocks/upstream/host.cc index 0a2512b5f24ee..fca9c02a6e495 100644 --- a/test/mocks/upstream/host.cc +++ b/test/mocks/upstream/host.cc @@ -59,7 +59,7 @@ MockHostDescription::MockHostDescription() if (index >= lb_policy_datas_.size()) { return {}; } - return OptRef(*lb_policy_datas_[index]); + return {*lb_policy_datas_[index]}; })); } @@ -88,7 +88,7 @@ MockHost::MockHost() : socket_factory_(new testing::NiceMock= lb_policy_datas_.size()) { return {}; } - return OptRef(*lb_policy_datas_[index]); + return {*lb_policy_datas_[index]}; })); } diff --git a/test/mocks/upstream/host.h b/test/mocks/upstream/host.h index 891f340b559c9..1067d788683c1 100644 --- a/test/mocks/upstream/host.h +++ b/test/mocks/upstream/host.h @@ -145,7 +145,7 @@ class MockHostLight : public Host { struct MockCreateConnectionData { Network::ClientConnection* connection_{}; - HostDescriptionConstSharedPtr host_description_{}; + HostDescriptionConstSharedPtr host_description_; }; CreateConnectionData diff --git a/test/server/admin/stats_handler_speed_test.cc b/test/server/admin/stats_handler_speed_test.cc index b21f8c3d11e0c..82c57ef1bb63f 100644 --- a/test/server/admin/stats_handler_speed_test.cc +++ b/test/server/admin/stats_handler_speed_test.cc @@ -94,9 +94,7 @@ class FastMockHost : public testing::StrictMock { resolveTransportSocketFactory(const Network::Address::InstanceConstSharedPtr&, const envoy::config::core::v3::Metadata*, Network::TransportSocketOptionsConstSharedPtr) const override { - IS_ENVOY_BUG("unexpected call to resolveTransportSocketFactory"); - Network::UpstreamTransportSocketFactory* ptr = nullptr; - return *ptr; + PANIC("unexpected call to resolveTransportSocketFactory"); } Network::Address::InstanceConstSharedPtr address_; @@ -500,7 +498,7 @@ static void BM_TraditionalHistogramsPrometheusProtobuf(benchmark::State& state) } BENCHMARK(BM_TraditionalHistogramsPrometheusProtobuf)->Unit(benchmark::kMillisecond); -static void BM_TraditionalHistogramsPrometheusText(benchmark::State& state) { +static void bmTraditionalHistogramsPrometheusText(benchmark::State& state) { Envoy::Server::StatsHandlerTest& test_context = testContext(false); Envoy::Server::StatsParams params; Envoy::Buffer::OwnedImpl response; @@ -515,7 +513,7 @@ static void BM_TraditionalHistogramsPrometheusText(benchmark::State& state) { auto label = absl::StrCat("output per iteration: ", count, " (19 buckets/histogram)"); state.SetLabel(label); } -BENCHMARK(BM_TraditionalHistogramsPrometheusText)->Unit(benchmark::kMillisecond); +BENCHMARK(bmTraditionalHistogramsPrometheusText)->Unit(benchmark::kMillisecond); // NOLINTNEXTLINE(readability-identifier-naming) static void BM_NativeHistogramsPrometheusProtobuf(benchmark::State& state) { diff --git a/test/server/cgroup_cpu_util_test.cc b/test/server/cgroup_cpu_util_test.cc index 61e35c1d6e9a0..e335082070507 100644 --- a/test/server/cgroup_cpu_util_test.cc +++ b/test/server/cgroup_cpu_util_test.cc @@ -12,9 +12,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::NiceMock; -using testing::Return; namespace Envoy { diff --git a/test/server/connection_handler_test.cc b/test/server/connection_handler_test.cc index 174e8eed60add..094d7c13a08ba 100644 --- a/test/server/connection_handler_test.cc +++ b/test/server/connection_handler_test.cc @@ -204,7 +204,7 @@ class ConnectionHandlerTest : public testing::Test, std::unique_ptr init_manager_; const bool ignore_global_conn_limit_; envoy::config::core::v3::TrafficDirection direction_; - absl::flat_hash_map udp_listener_callback_map_{}; + absl::flat_hash_map udp_listener_callback_map_; Network::ListenerInfoConstSharedPtr listener_info_; }; diff --git a/test/server/utils_test.cc b/test/server/utils_test.cc index 60851d0fb58bf..4fe6886cf41ac 100644 --- a/test/server/utils_test.cc +++ b/test/server/utils_test.cc @@ -1,6 +1,7 @@ #include "source/server/utils.h" #include "test/mocks/server/options.h" +#include "test/test_common/enum_test_utils.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -15,7 +16,7 @@ namespace Utility { // of special cases to get remaining coverage. TEST(UtilsTest, BadServerState) { Utility::serverState(Init::Manager::State::Uninitialized, true); - EXPECT_ENVOY_BUG(Utility::serverState(static_cast(123), true), + EXPECT_ENVOY_BUG(Utility::serverState(uncheckedEnumCastForTest(123), true), "unexpected server state"); } diff --git a/test/test_common/BUILD b/test/test_common/BUILD index bf69cf8f74120..c070b421f8bb2 100644 --- a/test/test_common/BUILD +++ b/test/test_common/BUILD @@ -124,7 +124,10 @@ envoy_cc_test_library( envoy_cc_test_library( name = "utility_lib", srcs = ["utility.cc"], - hdrs = ["utility.h"], + hdrs = [ + "enum_test_utils.h", + "utility.h", + ], deps = [ ":file_system_for_test_lib", ":logging_lib", @@ -155,6 +158,7 @@ envoy_cc_test_library( "//source/common/protobuf:utility_lib", "//source/common/stats:stats_lib", "//test/mocks/stats:stats_mocks", + "@abseil-cpp//absl/base:core_headers", "@abseil-cpp//absl/strings", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", diff --git a/test/test_common/enum_test_utils.h b/test/test_common/enum_test_utils.h new file mode 100644 index 0000000000000..c8c7df3fa4f2a --- /dev/null +++ b/test/test_common/enum_test_utils.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "absl/base/casts.h" + +namespace Envoy { + +template Enum uncheckedEnumCastForTest(std::underlying_type_t value) { + static_assert(std::is_enum_v); + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + return static_cast(value); +} + +} // namespace Envoy diff --git a/test/test_common/environment.cc b/test/test_common/environment.cc index afa949e32fa17..1657acc32b701 100644 --- a/test/test_common/environment.cc +++ b/test/test_common/environment.cc @@ -103,7 +103,7 @@ void TestEnvironment::createPath(const std::string& path) { } const Filesystem::PathSplitResult parent = Filesystem::fileSystemForTest().splitPathFromFilename(path).value(); - if (parent.file_.length() > 0) { + if (!parent.file_.empty()) { TestEnvironment::createPath(std::string(parent.directory_)); } #ifndef WIN32 diff --git a/test/test_common/logging.h b/test/test_common/logging.h index 7f469da44998f..801334fb01324 100644 --- a/test/test_common/logging.h +++ b/test/test_common/logging.h @@ -87,6 +87,7 @@ class LogExpectation { }; // Initializes the global log environment and must be called prior to execution of Envoy code. +// NOLINTNEXTLINE(readability-identifier-naming) inline LogRecordingSink& GetLogSink() { return *static_cast(Logger::Registry::getSink()->recorder_test_only_); } diff --git a/test/test_common/network_utility.h b/test/test_common/network_utility.h index fd17e564f5a4c..fe0fe0ef5970a 100644 --- a/test/test_common/network_utility.h +++ b/test/test_common/network_utility.h @@ -201,7 +201,7 @@ class EmptyFilterChain : public FilterChain { private: const DownstreamTransportSocketFactoryPtr transport_socket_factory_; - const NetworkFilterFactoriesList empty_network_filter_factory_{}; + const NetworkFilterFactoriesList empty_network_filter_factory_; const FilterChainInfoSharedPtr filter_chain_info_; }; diff --git a/test/test_common/simulated_time_system.h b/test/test_common/simulated_time_system.h index 907e8c117c802..4febef6d7100f 100644 --- a/test/test_common/simulated_time_system.h +++ b/test/test_common/simulated_time_system.h @@ -105,7 +105,7 @@ class SimulatedTimeSystemHelper : public TestTimeSystem { std::set schedulers_ ABSL_GUARDED_BY(mutex_); mutable absl::Mutex mutex_; uint32_t pending_updates_ ABSL_GUARDED_BY(mutex_){0}; - std::atomic warning_logged_{}; + std::atomic warning_logged_{0}; }; // Represents a simulated time system, where time is advanced by calling diff --git a/test/test_common/test_runtime.h b/test/test_common/test_runtime.h index ca25c31d90bce..828bce74b7388 100644 --- a/test/test_common/test_runtime.h +++ b/test/test_common/test_runtime.h @@ -43,7 +43,7 @@ class TestScopedRuntime { absl::StatusOr> loader = Runtime::LoaderImpl::create( dispatcher_, tls_, config, local_info_, store_, generator_, validation_visitor_, *api_); - THROW_IF_NOT_OK(loader.status()); + THROW_IF_NOT_OK_REF(loader.status()); // This will ignore values set in test, but just use flag defaults! runtime_ = std::move(loader.value()); } @@ -86,7 +86,7 @@ class TestScopedStaticReloadableFeaturesRuntime { absl::StatusOr> loader = Runtime::LoaderImpl::create( dispatcher_, tls_, config, local_info_, store_, generator_, validation_visitor_, *api_); - THROW_IF_NOT_OK(loader.status()); + THROW_IF_NOT_OK_REF(loader.status()); // This will ignore values set in test, but just use flag defaults! runtime_ = std::move(loader.value()); } diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index 923b82575d800..63ef45e6b8b89 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -138,8 +138,8 @@ template class WasmTestBase : public Base { bool fail_open_ = false; absl::optional allow_on_headers_stop_iteration_ = absl::nullopt; std::string plugin_configuration_ = ""; - proxy_wasm::AllowedCapabilitiesMap allowed_capabilities_ = {}; - envoy::extensions::wasm::v3::EnvironmentVariables envs_ = {}; + proxy_wasm::AllowedCapabilitiesMap allowed_capabilities_; + envoy::extensions::wasm::v3::EnvironmentVariables envs_; }; template class WasmHttpFilterTestBase : public WasmTestBase { From 6f7f7f7534b3007e3c67ce983ec2395cff6b87e1 Mon Sep 17 00:00:00 2001 From: "Ethan M." <59028647+ebm@users.noreply.github.com> Date: Thu, 21 May 2026 11:54:14 -0400 Subject: [PATCH 369/750] Changed base64 implementation to Abseil's. (#44915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit Message: replaces base64 implementation with Abseil's base64 implementation. Additional Description: Replaces the existing base64 implementation with Abseil’s base64 one. This change maintains identical encoding/decoding behavior. Benchmarks show ~2x at 8 bytes ~5x at 64 bytes ~8-10x at 512 bytes and above
Full Benchmark

----------------------------------------------------------------------------------------------------------------
Benchmark Time CPU Iterations UserCounters...

----------------------------------------------------------------------------------------------------------------
BM_AbslBase64Encode/8 20.5 ns 20.4 ns 33893373
bytes_per_second=374.723Mi/s
BM_AbslBase64Encode/64 56.1 ns 55.8 ns 12443880
bytes_per_second=1.06846Gi/s
BM_AbslBase64Encode/512 248 ns 247 ns 2822896
bytes_per_second=1.93045Gi/s
BM_AbslBase64Encode/4096 1777 ns 1770 ns 395332
bytes_per_second=2.15477Gi/s
BM_AbslBase64Encode/32768 14170 ns 14108 ns 50054
bytes_per_second=2.16317Gi/s
BM_AbslBase64Encode/262144 110850 ns 110328 ns 6340
bytes_per_second=2.21287Gi/s
BM_AbslBase64Encode/1048576 442523 ns 440467 ns 1589
bytes_per_second=2.21711Gi/s
BM_LegacyBase64Encode/8 42.7 ns 42.5 ns 16462454
bytes_per_second=179.434Mi/s
BM_LegacyBase64Encode/64 310 ns 308 ns 2324094
bytes_per_second=197.93Mi/s
BM_LegacyBase64Encode/512 2202 ns 2193 ns 322767
bytes_per_second=222.7Mi/s
BM_LegacyBase64Encode/4096 17763 ns 17687 ns 40018
bytes_per_second=220.852Mi/s
BM_LegacyBase64Encode/32768 141403 ns 140443 ns 5038
bytes_per_second=222.51Mi/s
BM_LegacyBase64Encode/262144 1127638 ns 1121530 ns 633
bytes_per_second=222.91Mi/s
BM_LegacyBase64Encode/1048576 4445197 ns 4424678 ns 155
bytes_per_second=226.005Mi/s
BM_AbslBase64Decode/8 25.9 ns 25.8 ns 26666600
bytes_per_second=443.357Mi/s
BM_AbslBase64Decode/64 80.2 ns 79.8 ns 8809031
bytes_per_second=1.02672Gi/s
BM_AbslBase64Decode/512 398 ns 396 ns 1774014 bytes_per_second=1.607Gi/s
BM_AbslBase64Decode/4096 3000 ns 2985 ns 237276
bytes_per_second=1.70479Gi/s
BM_AbslBase64Decode/32768 23209 ns 23118 ns 30188
bytes_per_second=1.76019Gi/s
BM_AbslBase64Decode/262144 185230 ns 184336 ns 3801
bytes_per_second=1.76593Gi/s
BM_AbslBase64Decode/1048576 742042 ns 738630 ns 953
bytes_per_second=1.76284Gi/s
BM_LegacyBase64Decode/8 59.7 ns 59.5 ns 11753923
bytes_per_second=192.427Mi/s
BM_LegacyBase64Decode/64 431 ns 428 ns 1568483
bytes_per_second=195.924Mi/s
BM_LegacyBase64Decode/512 3209 ns 3196 ns 216661
bytes_per_second=204.13Mi/s
BM_LegacyBase64Decode/4096 25388 ns 25295 ns 27671
bytes_per_second=206.001Mi/s
BM_LegacyBase64Decode/32768 203233 ns 202417 ns 3500
bytes_per_second=205.852Mi/s
BM_LegacyBase64Decode/262144 1615847 ns 1608513 ns 437
bytes_per_second=207.232Mi/s
BM_LegacyBase64Decode/1048576 6483992 ns 6455343 ns 109
bytes_per_second=206.548Mi/s
BM_AbslBase64BufferInstanceEncode/8 35.3 ns 35.1 ns 19569259
bytes_per_second=217.294Mi/s
BM_AbslBase64BufferInstanceEncode/64 70.5 ns 70.1 ns 9924410
bytes_per_second=870.793Mi/s
BM_AbslBase64BufferInstanceEncode/512 269 ns 268 ns 2601418
bytes_per_second=1.77916Gi/s
BM_AbslBase64BufferInstanceEncode/4096 1836 ns 1826 ns 383958
bytes_per_second=2.0896Gi/s
BM_AbslBase64BufferInstanceEncode/32768 14150 ns 14086 ns 49749
bytes_per_second=2.16657Gi/s
BM_AbslBase64BufferInstanceEncode/262144 111975 ns 111408 ns 6288
bytes_per_second=2.19141Gi/s
BM_AbslBase64BufferInstanceEncode/1048576 446107 ns 443873 ns 1580
bytes_per_second=2.20009Gi/s
BM_LegacyBase64BufferInstanceEncode/8 54.1 ns 53.9 ns 13240954
bytes_per_second=141.619Mi/s
BM_LegacyBase64BufferInstanceEncode/64 335 ns 334 ns 2099860
bytes_per_second=183.004Mi/s
BM_LegacyBase64BufferInstanceEncode/512 2355 ns 2346 ns 299256
bytes_per_second=208.131Mi/s
BM_LegacyBase64BufferInstanceEncode/4096 18410 ns 18336 ns 38182
bytes_per_second=213.041Mi/s
BM_LegacyBase64BufferInstanceEncode/32768 147610 ns 147060 ns 4762
bytes_per_second=212.498Mi/s
BM_LegacyBase64BufferInstanceEncode/262144 1179349 ns 1174398 ns 596
bytes_per_second=212.875Mi/s
BM_LegacyBase64BufferInstanceEncode/1048576 4742301 ns 4721762 ns 149
bytes_per_second=211.785Mi/s
BM_AbslBase64BufferInstanceEncodeMultiSlice/4096 1911 ns 1903 ns 369925
bytes_per_second=2.00461Gi/s
BM_AbslBase64BufferInstanceEncodeMultiSlice/32768 15227 ns 15165 ns
45886 bytes_per_second=2.01239Gi/s
BM_AbslBase64BufferInstanceEncodeMultiSlice/262144 124408 ns 123841 ns
5669 bytes_per_second=1.9714Gi/s
BM_AbslBase64BufferInstanceEncodeMultiSlice/1048576 499894 ns 497611 ns
1407 bytes_per_second=1.9625Gi/s
BM_LegacyBase64BufferInstanceEncodeMultiSlice/4096 18588 ns 18516 ns
37684 bytes_per_second=210.969Mi/s
BM_LegacyBase64BufferInstanceEncodeMultiSlice/32768 147629 ns 147052 ns
4634 bytes_per_second=212.51Mi/s
BM_LegacyBase64BufferInstanceEncodeMultiSlice/262144 1183149 ns 1177438
ns 594 bytes_per_second=212.325Mi/s
BM_LegacyBase64BufferInstanceEncodeMultiSlice/1048576 4724186 ns 4702789
ns 149 bytes_per_second=212.64Mi/s
BM_AbslBase64UrlEncode/8 19.9 ns 19.8 ns 35318734
bytes_per_second=385.207Mi/s
BM_AbslBase64UrlEncode/64 54.6 ns 54.4 ns 12961587
bytes_per_second=1.09557Gi/s
BM_AbslBase64UrlEncode/512 252 ns 252 ns 2792499
bytes_per_second=1.89564Gi/s
BM_AbslBase64UrlEncode/4096 1760 ns 1753 ns 399001
bytes_per_second=2.17577Gi/s
BM_AbslBase64UrlEncode/32768 14038 ns 13986 ns 50051
bytes_per_second=2.18199Gi/s
BM_AbslBase64UrlEncode/262144 110773 ns 110276 ns 6348
bytes_per_second=2.2139Gi/s
BM_AbslBase64UrlEncode/1048576 442787 ns 440739 ns 1589
bytes_per_second=2.21574Gi/s
BM_LegacyBase64UrlEncode/8 41.5 ns 41.3 ns 16950384
bytes_per_second=184.617Mi/s
BM_LegacyBase64UrlEncode/64 297 ns 296 ns 2355033
bytes_per_second=206.009Mi/s
BM_LegacyBase64UrlEncode/512 2136 ns 2128 ns 328029
bytes_per_second=229.444Mi/s
BM_LegacyBase64UrlEncode/4096 16885 ns 16823 ns 41566
bytes_per_second=232.199Mi/s
BM_LegacyBase64UrlEncode/32768 134893 ns 134392 ns 5212
bytes_per_second=232.529Mi/s
BM_LegacyBase64UrlEncode/262144 1083154 ns 1078276 ns 650
bytes_per_second=231.852Mi/s
BM_LegacyBase64UrlEncode/1048576 4356684 ns 4336895 ns 161
bytes_per_second=230.58Mi/s
BM_AbslBase64UrlDecode/8 25.3 ns 25.2 ns 27737820
bytes_per_second=415.973Mi/s
BM_AbslBase64UrlDecode/64 78.3 ns 78.0 ns 8924236
bytes_per_second=1.02696Gi/s
BM_AbslBase64UrlDecode/512 397 ns 395 ns 1767540
bytes_per_second=1.60968Gi/s
BM_AbslBase64UrlDecode/4096 2967 ns 2952 ns 242422
bytes_per_second=1.7233Gi/s
BM_AbslBase64UrlDecode/32768 23697 ns 23524 ns 29233
bytes_per_second=1.72977Gi/s
BM_AbslBase64UrlDecode/262144 185443 ns 184584 ns 3795
bytes_per_second=1.76354Gi/s
BM_AbslBase64UrlDecode/1048576 746481 ns 742547 ns 953
bytes_per_second=1.75354Gi/s
BM_LegacyBase64UrlDecode/8 60.3 ns 60.0 ns 11947521
bytes_per_second=174.801Mi/s
BM_LegacyBase64UrlDecode/64 427 ns 425 ns 1636120
bytes_per_second=193.1Mi/s
BM_LegacyBase64UrlDecode/512 3163 ns 3151 ns 219959
bytes_per_second=206.698Mi/s
BM_LegacyBase64UrlDecode/4096 25836 ns 25510 ns 27914
bytes_per_second=204.193Mi/s
BM_LegacyBase64UrlDecode/32768 203179 ns 202069 ns 3471
bytes_per_second=206.201Mi/s
BM_LegacyBase64UrlDecode/262144 1621516 ns 1614225 ns 435
bytes_per_second=206.498Mi/s
BM_LegacyBase64UrlDecode/1048576 6539881 ns 6506282 ns 107
bytes_per_second=204.93Mi/s
Risk Level: Medium (encoding/decoding is core utils logic and should preserve behavior if tests pass). Testing: Existing unit tests for base64 encoding/decoding pass. Added differential fuzz testing (test/common/common/base64_fuzz_test.cc) and benchmarks (test/common/common/base64_speed_test.cc) comparing the new base64 implementation against the legacy implementation. Docs Changes: None Release Notes: Base64 implementation updated to use Abseil utilities. No behavioral changes expected. Platform Specific Features: N/A [Optional Fixes #Issue]: Fixes #12694. [Optional Fixes commit #PR or SHA]: Related to PR #29360 and PR #12703. --------- Signed-off-by: Ethan Marantz Signed-off-by: ethan Co-authored-by: ethan --- source/common/common/base64.cc | 251 ++++----------- test/common/common/BUILD | 38 +++ .../base64_differential_corpus/favicon.ico | Bin 0 -> 67646 bytes .../base64_differential_corpus/singleton | 1 + .../common/base64_differential_fuzz_test.cc | 26 ++ test/common/common/base64_legacy.cc | 294 ++++++++++++++++++ test/common/common/base64_legacy.h | 100 ++++++ test/common/common/base64_speed_test.cc | 166 ++++++++++ test/common/common/base64_test.cc | 6 +- 9 files changed, 693 insertions(+), 189 deletions(-) create mode 100644 test/common/common/base64_differential_corpus/favicon.ico create mode 100644 test/common/common/base64_differential_corpus/singleton create mode 100644 test/common/common/base64_differential_fuzz_test.cc create mode 100644 test/common/common/base64_legacy.cc create mode 100644 test/common/common/base64_legacy.h create mode 100644 test/common/common/base64_speed_test.cc diff --git a/source/common/common/base64.cc b/source/common/common/base64.cc index 7ca2109fbb502..833ddfc9eb61d 100644 --- a/source/common/common/base64.cc +++ b/source/common/common/base64.cc @@ -3,21 +3,15 @@ #include #include -#include "source/common/common/assert.h" #include "source/common/common/empty_string.h" -#include "absl/container/fixed_array.h" +#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" namespace Envoy { namespace { -// clang-format off -constexpr char CHAR_TABLE[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -// Conversion table is taken from -// https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c constexpr unsigned char REVERSE_LOOKUP_TABLE[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, @@ -31,11 +25,6 @@ constexpr unsigned char REVERSE_LOOKUP_TABLE[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; -// The base64url tables are copied from above and modified based on table in -// https://tools.ietf.org/html/rfc4648#section-5 -constexpr char URL_CHAR_TABLE[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - constexpr unsigned char URL_REVERSE_LOOKUP_TABLE[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, @@ -48,99 +37,53 @@ constexpr unsigned char URL_REVERSE_LOOKUP_TABLE[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; -// clang-format on -inline bool decodeBase(const uint8_t cur_char, uint64_t pos, std::string& ret, - const unsigned char* const reverse_lookup_table) { - const unsigned char c = reverse_lookup_table[static_cast(cur_char)]; - if (c == 64) { - // Invalid character - return false; +std::string decodeHelper(absl::string_view input, const unsigned char* lookup_table, + bool (*unescape_fn)(absl::string_view, std::string*)) { + if (input.empty()) { + return EMPTY_STRING; } - switch (pos % 4) { - case 0: - ret.push_back(c << 2); - break; - case 1: - ret.back() |= c >> 4; - ret.push_back(c << 4); - break; - case 2: - ret.back() |= c >> 2; - ret.push_back(c << 6); - break; - case 3: - ret.back() |= c; - break; - } - return true; -} + // Strip up to two trailing '=' characters. + const size_t n = input.length(); + const size_t count = (input[n - 1] == '=') + (n >= 2 && input[n - 2] == '='); + const size_t real_chars = n - count; -inline bool decodeLast(const uint8_t cur_char, uint64_t pos, std::string& ret, - const unsigned char* const reverse_lookup_table) { - const unsigned char c = reverse_lookup_table[static_cast(cur_char)]; - if (c == 64) { - // Invalid character - return false; + // A base64 string with length % 4 == 1 is structurally invalid + if (real_chars % 4 == 1) { + return EMPTY_STRING; } - switch (pos % 4) { - case 0: - return false; - case 1: - ret.back() |= c >> 4; - return (c & 0b1111) == 0; - case 2: - ret.back() |= c >> 2; - return (c & 0b11) == 0; - case 3: - ret.back() |= c; - break; + // Validate the trailing bits of the last real character. + if (real_chars > 0) { + const uint8_t last_value = lookup_table[static_cast(input[real_chars - 1])]; + if (last_value == 64) { + return EMPTY_STRING; + } + // Base64 groups input into 4 character blocks. Each character represents 6 bits. + // A valid encoded string can have trailing characters where the unused lower bits must be 0. + // This code checks whether the unused bits are 0. If they are not, the input is invalid. + if (real_chars % 4 == 2 && (last_value & 0x0F) != 0) { + return EMPTY_STRING; + } + if (real_chars % 4 == 3 && (last_value & 0x03) != 0) { + return EMPTY_STRING; + } } - return true; -} -inline void encodeBase(const uint8_t cur_char, uint64_t pos, uint8_t& next_c, std::string& ret, - const char* const char_table) { - switch (pos % 3) { - case 0: - ret.push_back(char_table[cur_char >> 2]); - next_c = (cur_char & 0x03) << 4; - break; - case 1: - ret.push_back(char_table[next_c | (cur_char >> 4)]); - next_c = (cur_char & 0x0f) << 2; - break; - case 2: - ret.push_back(char_table[next_c | (cur_char >> 6)]); - ret.push_back(char_table[cur_char & 0x3f]); - next_c = 0; - break; + std::string ret; + if (!unescape_fn(input.substr(0, real_chars), &ret)) { + return EMPTY_STRING; } -} -inline void encodeLast(uint64_t pos, uint8_t last_char, std::string& ret, - const char* const char_table, bool add_padding) { - switch (pos % 3) { - case 1: - ret.push_back(char_table[last_char]); - if (add_padding) { - ret.push_back('='); - ret.push_back('='); - } - break; - case 2: - ret.push_back(char_table[last_char]); - if (add_padding) { - ret.push_back('='); - } - break; - default: - break; + // If abseil decodes the wrong number of bytes, we must reject the entire input. + size_t expected_length = real_chars * 3 / 4; + if (ret.length() != expected_length) { + return EMPTY_STRING; } -} + return ret; +} } // namespace std::string Base64::decode(absl::string_view input) { @@ -151,66 +94,25 @@ std::string Base64::decode(absl::string_view input) { } std::string Base64::decodeWithoutPadding(absl::string_view input) { - if (input.empty()) { - return EMPTY_STRING; - } - - // At most last two chars can be '='. - size_t n = input.length(); - if (input[n - 1] == '=') { - n--; - if (n > 0 && input[n - 1] == '=') { - n--; - } - } - // Last position before "valid" padding character. - uint64_t last = n - 1; - // Determine output length. - size_t max_length = (n + 3) / 4 * 3; - if (n % 4 == 3) { - max_length -= 1; - } - if (n % 4 == 2) { - max_length -= 2; - } - - std::string ret; - ret.reserve(max_length); - for (uint64_t i = 0; i < last; ++i) { - if (!decodeBase(input[i], i, ret, REVERSE_LOOKUP_TABLE)) { - return EMPTY_STRING; - } - } - - if (!decodeLast(input[last], last, ret, REVERSE_LOOKUP_TABLE)) { - return EMPTY_STRING; - } - - ASSERT(ret.size() == max_length); - return ret; + return decodeHelper(input, REVERSE_LOOKUP_TABLE, absl::Base64Unescape); } std::string Base64::encode(const Buffer::Instance& buffer, uint64_t length) { - uint64_t output_length = (std::min(length, buffer.length()) + 2) / 3 * 4; + const uint64_t encode_length = std::min(length, buffer.length()); + if (encode_length == 0) { + return EMPTY_STRING; + } std::string ret; - ret.reserve(output_length); - - uint64_t j = 0; - uint8_t next_c = 0; - for (const Buffer::RawSlice& slice : buffer.getRawSlices()) { - const uint8_t* slice_mem = static_cast(slice.mem_); - - for (uint64_t i = 0; i < slice.len_ && j < length; ++i, ++j) { - encodeBase(slice_mem[i], j, next_c, ret, CHAR_TABLE); - } - - if (j == length) { - break; - } + const auto slices = buffer.getRawSlices(); + if (slices.size() == 1 && slices[0].len_ >= encode_length) { + absl::Base64Escape(absl::string_view(static_cast(slices[0].mem_), encode_length), + &ret); + } else { + std::string tmp; + tmp.resize(encode_length); + buffer.copyOut(0, encode_length, tmp.data()); + absl::Base64Escape(tmp, &ret); } - - encodeLast(j, next_c, ret, CHAR_TABLE, true); - return ret; } @@ -221,19 +123,15 @@ std::string Base64::encode(const char* input, uint64_t length) { } std::string Base64::encode(const char* input, uint64_t length, bool add_padding) { - uint64_t output_length = (length + 2) / 3 * 4; std::string ret; - ret.reserve(output_length); + absl::Base64Escape(absl::string_view(input, length), &ret); - uint64_t pos = 0; - uint8_t next_c = 0; - - for (uint64_t i = 0; i < length; ++i) { - encodeBase(input[i], pos++, next_c, ret, CHAR_TABLE); + if (!add_padding) { + // Remove trailing '=' + const size_t n = ret.size(); + const size_t count = (n >= 1 && ret[n - 1] == '=') + (n >= 2 && ret[n - 2] == '='); + ret.resize(n - count); } - - encodeLast(pos, next_c, ret, CHAR_TABLE, add_padding); - return ret; } @@ -245,41 +143,20 @@ void Base64::completePadding(std::string& encoded) { } std::string Base64Url::decode(absl::string_view input) { - if (input.empty()) { - return EMPTY_STRING; - } - - std::string ret; - ret.reserve(input.length() / 4 * 3 + 3); - - uint64_t last = input.length() - 1; - for (uint64_t i = 0; i < last; ++i) { - if (!decodeBase(input[i], i, ret, URL_REVERSE_LOOKUP_TABLE)) { + // URL decoding does not accept any padding + if (!input.empty()) { + const uint8_t last_value = + URL_REVERSE_LOOKUP_TABLE[static_cast(input[input.size() - 1])]; + if (last_value == 64) { return EMPTY_STRING; } } - - if (!decodeLast(input[last], last, ret, URL_REVERSE_LOOKUP_TABLE)) { - return EMPTY_STRING; - } - - return ret; + return decodeHelper(input, URL_REVERSE_LOOKUP_TABLE, absl::WebSafeBase64Unescape); } std::string Base64Url::encode(const char* input, uint64_t length) { - uint64_t output_length = (length + 2) / 3 * 4; std::string ret; - ret.reserve(output_length); - - uint64_t pos = 0; - uint8_t next_c = 0; - - for (uint64_t i = 0; i < length; ++i) { - encodeBase(input[i], pos++, next_c, ret, URL_CHAR_TABLE); - } - - encodeLast(pos, next_c, ret, URL_CHAR_TABLE, false); - + absl::WebSafeBase64Escape(absl::string_view(input, length), &ret); return ret; } diff --git a/test/common/common/BUILD b/test/common/common/BUILD index 890e94b4b2166..8f7486fbf7cc4 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -4,6 +4,7 @@ load( "envoy_cc_benchmark_binary", "envoy_cc_fuzz_test", "envoy_cc_test", + "envoy_cc_test_library", "envoy_package", ) @@ -97,6 +98,21 @@ envoy_cc_fuzz_test( deps = ["//source/common/common:base64_lib"], ) +envoy_cc_fuzz_test( + name = "base64_differential_fuzz_test", + srcs = ["base64_differential_fuzz_test.cc"], + corpus = "base64_differential_corpus", + rbe_pool = "6gig", + # Fuzzer is stable, no bugs, simple test target; avoid emitting CO2. + tags = ["no_fuzz"], + deps = [ + ":base64_legacy_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:base64_lib", + "//test/fuzz:utility_lib", + ], +) + envoy_cc_fuzz_test( name = "utility_fuzz_test", srcs = ["utility_fuzz_test.cc"], @@ -430,6 +446,28 @@ envoy_cc_benchmark_binary( ], ) +envoy_cc_test_library( + name = "base64_legacy_lib", + srcs = ["base64_legacy.cc"], + hdrs = ["base64_legacy.h"], + deps = [ + "//source/common/buffer:buffer_lib", + "//source/common/common:empty_string", + ], +) + +envoy_cc_benchmark_binary( + name = "base64_speed_test", + srcs = ["base64_speed_test.cc"], + rbe_pool = "6gig", + deps = [ + ":base64_legacy_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:base64_lib", + "@benchmark", + ], +) + envoy_cc_benchmark_binary( name = "utility_speed_test", srcs = ["utility_speed_test.cc"], diff --git a/test/common/common/base64_differential_corpus/favicon.ico b/test/common/common/base64_differential_corpus/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7cd45f110377fb45a3fce4ac12af2a4125fbaa34 GIT binary patch literal 67646 zcmeI537i~dnTOv5IYc1Hy4;X}ASe*PgGB*}uqpzJ+zb5tv1=q;>Y}?M2!siWvIig$ zL_yY7SpvlMfQ5u`I4(kt$uPMlb7bzLr|FL_%?ds|)Gn1K2_hgdk=%25ur@Okk zp7;9Rs_qh*#s8Kp5&q4SllPk?hq81sT7vi!|DUHz{F`kKGxWdpV$7+N!0`G1XsdrE9->@y$WUZkEVt znVKzi*=?hEN(iFia(e8+Z)4;a|)6ll2n6alM33t&`X>YsEOIO^ki3 zv0(a}h_c@x!4qM9;bw_`eWQe**eH>gSZ_dm8zmOsB#Gf-{&BShY##J;ylA}_<= zy-A`GV!$vlz$g^4z)(+{Iey#Wq)_s^6M|*mFO6Kr;Hxh%lNaDkr zBz)l(iQL5a5aWw*uSZT4DP^zglj9$cz}`VDsH>Ow^TdV+sSy{l{JBkH99JvG0lII~ z<0$R9d$CCzAE+1i5!7^Fg!2#4O4JGec%#IUD20a0&7V3z=Lo56UWj1}ZLog@Jy1uj zSxXLjnYrekd*pY`C##{(S}w+3+P@)}f4oVemp4i5jz)<-u~njPY?a71*aulR3N;#k zjRA;SFg|FBm_NiabAr-(FhOl-%E|);qn|pUg&44^UIv%0m&7;gBym}t44zRZ#^Jip z)!Z<7o8cI~uZuZdlLXI({q5+PCW$mOOVow}*Z_VX#plP*pV|-Ju^4l~sA@xIP8g>r zFcJRAA#y+*d%zA7wt7jdXMBpsZlRa>Nu7t*O6~h5e*|XU7PM!pbzLjH`c?^@+d^I6 zB;os;CGtGudPXno2hlM6jggeMhZu1omL?vk0;mV}b#VV9<1*ET%ser^p2vEzfgt?> z<_d|lWe+-9aljh$aUeNN92ms^-Q-qB~)N#MA@NZ^CoOP+ZockUCn zN!z|{68>0=guV>#A2v()b+oZXqWvur3BWz6?1=^Gk=W~FBk^ZW*u^~X4Qj`K$LFun zzK&|6B(H=0qvU|MV2yp~Troag{yJC026VnS=&zS#A2zUoc(6iigLM+UbhU&|rar93 zf>F#tbLA@PI_mgCP40mUV1Fz6F~0vMHsFMR1iv3fhKd}JRg`i27(k6cY=}jX5C3n) z@86|<_zgC2ALj$EfY%B1j1E!Z>nG9syBhZQqyNSh)?gQHjP!~%PvmVO-zzGv3)r4v zDw%?Pn7Uwq7(i{1c(_iYH#28kutvfkeOtnRt#zWtxKR$rjC;6?9!{Hhk8YOGXSPZ3 zpSFqrm#q?fvqgff@OPj%%!g2Vy!^8@frD`?`W4*2u~CATzQ??Mqj)lXJnb2peM9$w zoFhD1`v}yTmk}Gj1$XQq+Qj)n7yL)94Xr#-z(1||0N;=cwmqdT&QV1=NE|y|E%+b4a^hPO6=%&#C;&gV@Aytl!2aK zZxY|B+r)Q$y9B=v`xmxJcr9ZG@>BQ4Tj6g>*^kAa95BROFpPQ{CH%@(i9dwCx{>;k zp72SqJ!G{Q@2?r7Zx4sr?A!kJ68aFlPJT~9pQYb(6RfpfSc&@KA7(u@LH==KfuEev zPCQscJird(OQ;X8)tsO6g77*NZc;W6y&hIPyr*GZgtg7Lvx zj?rZHc{;upzsg*P(<+YRJ0$S8ZQ}bn+#e^;zk;tfz`q~v;R*8BT5t$^urt1k9sCMA zxT{%WS8A`ILyTk5oXH&zW7Y;S4(58Gx%7-aO-{I)df*;%#Ix{UgB`eFPd>=>1}DHj z8G?Hs^M!hH!pqnK{iDHKv4IQN=3K1}^_Wd=-*^41v1NKUdVVf=h}L(l;{8&a1n$`; zf#0DnmgD686#R`sjphHXFee_w;ZBZ-=zQ>9V*eB5?1l6JPuE^iCHgM9O}!pSdyr;5 z5&Ide*1_L~{4B@S7E0qlaV#+70_?RWPz@0aVs(;iXZgi-lDKQFBtE}dqDO5ePHK&) z$8gt^ocJDKzq7mOiFbvH|B;g)I&FGKRhxas!@7yioJgjRMw4 z)fRMKPya~!2LB8HR>T}3kwjV}>N%raUa)Eccw7EoG{S$7T;L`TY-Voo1a|QE#DX); zn$+04CR4jkPg_vG>yVB)U1FctE#V7#B($hYLjT$+!QY}fY{7-1`2TPz|IzJ0Yr#+$ z+2OysO(M_2{sH3tjja+tuU(8|IQRO2+FcMM^*!~y;XQA&44uDO2LBN|_yO0BEh9Fp zg*)ep6I?$sA^s_9!YDT2Kx?sw7sw4iqCWiEdKp?^UW@2GNc&|IkI0pMh2~w&@txv3 zuSwh@`x9N_zoJKi-zMKb!Sd@S`=Ae7Fjy`UF@X4AC^H{m7sJGYBsn5LAFvU< zhJHrQ|85i8-wJ!p@zZgxtW5NpFf^YY&sAJIdNE+3R|ae~RTW>cI|P=#cQY=m%c-uCyPjKCkvR zo%`k*m(>ompYt}?s8HK~0p{O<{r_-Y*oGpkr^d@4TS(*o**Gv5g1v`au!cFp(~LJ^ z6Q|U2-4U9SIRZXE3%&>UiR*+;aecO%+OC_rzE}Lq=;_pTOTdnXQChi=rUDz#T%h#; z%!A$32kRLBhkXAC+!qn=&ubR{Jl=P=J9o-HYkhEB1GRx(6F^LO4DP?D_ovqh^zc{| zThRKDo{+|X(c^%|1neLU|6yc<{aVIn*O~K#>;59KbKm89IO5_QWmHZ-x3;C*Ro`nD z*NO1Hyj#5Ahy4oF4DSK>hgeQBrpW_4o$7hE%=e1 z3%2%QcN_KkV66+ZZiAnGm*gvOUJCC&!QIKI*OO)Vn>H}=yui!}gT#VCwSfeFA7Xph zL9%HL^A6^U_fsSM-Crbre7So^4$jMJj_;*c-%l*=A=bnHu^#cihPqJ<=0nIR6gFX$ zsvsW3;P0jv*b4Kv+a&lbb>Th4`zxvMKGv6+TTM+&n+ltHid?DZeBpQ+x&|%A|DQvA z=Pa=edAPPbt~O98le=jX@YWcB1~bAAhOh(X3WFWQgICwe&?77_TF2b$9qObvsYO;X zKbh?3>!+sg67SjYzr9cV|J5trT9#Xo6ZX+w38ean`GxFtxs<)GTl_mE0Q=xZ^c=o_ zN3(dp$b8{6?H7?h52%^O^W2>~o(6ppWA;0o^WytNN9cD8;mmhUWB-B;yhwepnRRa= zdtDxjKX#zGV2BtHK@M!7iQds$^bVd`EAgA(k=WU_#L%7F-~6j=;yb5LeBVXSp=RX9 z{|B)JL&b7NWAUdR97IuUz{a?RIl>B^C&2u+ZP=FfaJ09Yf7d;GNxzd?m+2ja&e0ws zcJL&;Uqkx+f&mnz_Se2(kqzj&6$kKrqgaDU?7)Q$Y{nLTMLzg0{iAaxdnEDgMg7$E zed6tacL4qg#^H(Zr!EM>y`S3gHO7Y+|FM_a4&OhqUqXLHZL#lk>}gE)*p2pj{j9b0 zEUDK%M6c*0oC{vSHKPmR{_o@hoi8$1Na#7DiSSPm5909m!G9a7TSGn=Z6C(_ct7>M zP5eVB#c~>3FjN+QYqa73V;YeU{Da7c&-bEraDNHzHC*rUCF1@^^g90W?!%t`Jk5Eq zKDl0oE~O6mKKbBf#5^J1iyi1Sg`6i!>bU~?L#6eA858ocU@#5)6rw&DT0GffSI8e5 z7!QBg`b@2U6;{F`$e@f=8ll}Zt@p|oNaqZc`FrQ1` z=x^v7T}k}sJaghV@UEi=q_H459{xH} zI+6D>f43w`|4`?PiA$*IZ!`M_$yLM!t|v`IcRhcz4#?L7J{Rm);QveT{Vz01AJ^N> zoSJ?jd?)f;?HjyFjm&k!7gG;Bi1eCruf^XQt$Bgv|I`E8ADDFh&-=eGRqX%8{IP>} z`a?hJ6ZZo0{qbGYx78d!5z-?+SC1juk2>%K*mHeY@?l~?uf^RO^ZYY?V&(#s;GfU` zRvnNZ{Ts;tH|{yR0|LTsQJ8`y4cn6-t>Iiqs(w~H@@a``{c-K+Zpv7RR?BY*E=yLg8z{>%jp)%O1H#sA&a_&*Mo zJM)x0|0wZ4PW&%ESETvCROb32t|QCzjLJ~|Rr&7>uX4X;{QNUL!m{&sWb=R3|I1Bw zeA_GKPj4`DU2#4RSpHu*{@6ge%71*k%KfbI@@HuvPPaxv}TASID1h&iOv_$lpJ%6n`iDQI&s=$kZ;3pMRnv{$I-9iUS2>)&DDK z62<)W_Xu*oRh#*3BC8%?tPp=M@!y;C{{rrre875@{~pTUN&ZI}{@8#~r~*5v@}ENf zeBUtp+tu6E0s0#QoF7i;H6xRr|HFE*#h)=<$X=JL{3{5(VAG}Xe{BAVvhjDp-zA0QY2`a)L8>sRxH`sA*Z~E}} z;QyYg|BsVfxt_LT{<-;p{uYsb$B^G5nSB1Y;=fxwDbxQ={-t@K8vn~hdgSd)5B?tF zf0h5pgev>!c=_{vg0lNRzCWb?kJ8kFMxm@Yp#JZu_J1p81f~4>J-QL^U84M{0rh*O zWsCpH-&5sZF}CB`a3Ozwv%Gn9{+Zt+!9SM|s{TJ7kURODQvTWdSY%>= z+JZF?WYl}v!9R}w$0yzYsq)`RU}b*Uc==-sW#;d~|J@n?Ck~|D;x<&SoUD4Fn*Ym; zyl8Wy@h^=7RsKbXZI4-*Zv69c!18}jHUC%8+ARL&@7%_kEx*r?=6pc?pBjMQp3MAC zeHrq<8UMZ1|0oy#tNbendxTAom%rZUw37U(1&l&5H?;ZzRsSD>Te%)8Gp<#?!&Klik1CI;wz?TTVRHYQ~G$I9aWRsQ9mH~DRhpMU1wb;SR2 z@^|I@pB!MQ@_wIN#{V5v|DQbS5x0=%A6m$Lu9~g;9_oD#nG@vVKqK`Z{o#u6CkLdf z{71x7@dwA^uQp)i1I9G9U!J;a&PJ(~W=L4$S!g zb-@N-_54pMwz91k^2hI+3w>Xg^YuV9&0HXb@0Zp8L*h&k2Xg*j~uG!8UNG)*0lA_fdGPCEY=^2Y|!!~jE8_5X^Y9&y7Kf9{36 zko%uEPc45(#{ZQ+GE}8@fIU?6|A^$u^I$RmaC57K4OL4{W;|%g`G2I0{%`URSNWF* z=1y#7{QM)?_+MWBuuoO_?*zCqy<}qiBjo=|@plma9oxl!zc|_h;`otU>V(0vB*k^oOYddad|xjRkoiO-)!K{tohgF@LKL@QdBW{C|a4 z>`T02`vUiSJ4EmKHp!1a<6o}#id`rDf7K+ui(AD1AnaGc+-q^SMvFhSA^f8$`T=Fj z|HS>FeEc{0TXvAI2OZ&%xSEIqD}Ca8)GLl#d}6;Cn>fwIy>4rzR%1a$?oGdk8Obc} zp|nqnxR2}<-~4UjyR?P-B)3R#DXdp*lW+?$Aj)W{EdKd1lEwy7#DF1c!LrQ%lz$|b z|3~JJ9nc?gMVSY9Q77EjV+TK@FZi{fc+Rq6$HcPPdl;*-e;mDk7VHmflhCK(eDgL5 zJl!h3hE@r7vF<~$HVLOj=N~RB|0w;Rh&VF*v4LFt&-?#&`CynBkmOvkpL0ZO(XXio z?)HoAa*x>08j#Mpe(5^6S{vj|NALf-S$aO$EdF_VKlE+lxvEV9_qI#u*=-VBk3!od zl*A^|$SBkZc97*C#{a{U_WwM8M+!R_4}a|u55s&A`B?5n>(EQs!V+@AB_6Sz?5N)V zIcEUs?{NHRoA@rH&iet}Uxx8|#$M!Sc?b=o9rE8s{x{=)+4wJ_2e=+}z&{WYdxGWR zQvWaP2^IDS;jZV2g8^~(lM7Z67ygynV4+|7&UZ`yM+|BkV!^%!aW8j3_V5Kh?_OQh z^fvDQPCa*0yZArXCjOh+CGar!Mt`$YLbeVGg^*zhJ1|t(K#?r}ZzJck97q0U3AMA` z_2i04-~T-%wsRt4zY9GVmVxGw47kH$AKVdtYhGZE8Vj7%1}+=gf*q_tPf#D+hAo`! z6z5?xX|A9(eV23{-Y&j#x+VAx?vwl^daYA}jqo2J21MaLXtCFENBpV(lJIs>CpNmo_uLLjR-st~AjlaA@{99n} zMMj~p1^nL>Hh~SOEL#YZ55hy#1rdDS!EzHd;#=6jGl;q|cy^n3kDe@lgFH>$cNnq% zw1C(?5BFPG{xQr~!oM5ESmyjd@w{Mc{?0h;J*XA_Z^Qptt|PkFBhIVnADjaF_oF?g zcVynr+qQoPHqO1Vuk8~5{b(6>@D6_8)+qrmW4w^PE?ew%Eak5?0gRak20NG|yow%a zmB1qH5p9+5DO(w81ZW5Hf6R8QmOb&P;DE1YIoM0b7A(X@p^c+#{ zJmFY&KrUde;7T!9v||HrqDT1+f$RCrxl_>5EHe*k+t=EM-EGu;=W`rn6Ynv-5v$M+w~1Jewp_{_b=!Y->p63e+>42Am-Pz-pg_n_QT`nA5{Ls#D5#?*P>@(e=oXD z^E~76#FshBXe#`~kovvd(sH0*`cH@VE$A8gMC*_TdkB*=lG-yYofj0w1DzXW=LgpH zq+%TvH5Me{9zc!ch1ax) zh`#HQfh)Yya|(TfLwWDLcZN?TUjv7II;oS;{BHU;J>t6?Jq7bOP!kHFB>abo3x=xH z21@y}K1@#Vz`hOkD?22-l-PdDJPXZG_lS73f{UQldH(4ScD z&=`;vc_DNC$acBGvIA>`J@Fu;Vd8)TZ9$JyA7TUcQ^*TCN0^d0Hy)eOeWBJqsE_)t zN4(ecis$Ej;%)2|pA)|~@(Qq=*Y-9Lq!!3%m@(Bw&yU&=_I`Q-fqQumdcvNgl=*n} zYbu{p%lVxS>6l}eu6g+W1;haD8$E=U!(a0P^F?Qbb3}R_Y3}@SVH_BNKi@ZXhGB0< ztEmm1rABxFJNO6IKg*o)*sLu~`CLJ3`bO*!A3n}5;RSZ_e|13oKfnf-^^0#UqXX{Y zJbSf+JbPWHNfg5dTF}cLaoq>|^R$n$D|$!T!_gei zJ?}r)C;lZce-&**F_g54Kh-b(;fe7NCgJSDrZ&O+@$6ht1h-9#)>c4 z!0ZU;eW?TIGgrKZcyJ&3Pwe0=)J(15#}3pMhAn%r{NEZ2@<8GDjlKAOGdA%m_V6g@ zl)jD~TtY0EueE{3q3m%QYxgGnH{GUgyNl1%FRnBC#r=1E;`t^%xfI^7!Mg?CK9&s? zHnE+GZ6T8vv=$gdUYKts#=pV%i*E7V1OF?@AE#ysY*hY~uV3_qx0q-@n%; zp8tmTTKKyLU=H_TWE6^6KpZe-#e#l%Ju22iaPMV4P|y7EWyZ&-8*kz91)VZ5w~{@a z34Z>vK3!u$fa8hZAMcUgv-~n}Be?)Ou>T)*fejnbxxsk(^Idee&JPmwi~R8KMGeG( zm(YE7u05hIIB_x`0K1({9dnXh+?S#ycJaKb_c=y=c6=Ts&@eWTE@ZFERva*Mf#w5! z%;b;1_c1s86aM~V9>0NF@RM3cs?Tfun#?}$4zK07?rl&XQWu&6SALi-;Y=C=IIK$M1J#KNn%J^^8i3_oXbCqFGf|=_P$EppK zw_c}&K7@~-Jir`-`;OEcb?=?DC zv`Oe>Jujr!fb0(J%CV1{577IxH_o-9$It`*I7~0+8qzz71HSYqs9q$?s2(>dmOeQKg)6W z=k9xMaks`1_%rYKhLMdq0`o%Of53lHm$(&S{mK`)J!U&zssm z#$53->VgM}2d@(gHU_zdj9d|*KG5q+xyCRb4@&(X-r3(M%=|u~+5-13aCO1?P4pk= z8sa>CJNp9q2GdxJrS0(pf{YK|R#DTvdC*0VP;NHY6sK-ncu6&|8pb! zxu2~ohR;W{it9a2t$Udi-rv?*wqgPG0QWxExZoQi2E_4ydOsc;%FZKn&Ul7`=~E_$X8m|b8hzVGM6v_X$>#8~DCa z=9)sic0hlpmb{QVhn$-ixCTk*nt!FTu)YB{?r8#_&Z=<#~kr_ z^q`IYlAiyghB!{=lVkN3cJ#Tbf1mgfATP|)I+1vAhFb={0{>rP2V1cLt}ibye|WoG z;@yJ3KW(FD$@|VH$LsglW^CVP;>61T3hf=~d1h*ZGwB^%3;%n_3)G0tH>m|%u!Sfi zdBM%KB*i@i1+x?2e*t#zb@T*r0XuLu5EJ^y3w-y)rRNO`?q!p?uhlU9 zp_ujuP(9}m{=jyY!v0otJ~{q)?G;!46I9<}GDkq;f!ctNYY}~7I~M;xjX3aC?BGf2 zgSW8(AN=(il0yE>2lf1+%gvmy)+LUga2>&oFh7-ZRLAIfDz?jaRI9muGHj;$wK`X@ zN&WtOZ|&pQ!40tgKI3nR1*=gf@n5ei)pLdIi1P!^=kfa*{Qg=e-)o@8dw;d2pK5C5 zdq4G8?Ir7VK%DbAO0P#H4qOWJ?{VGG9~j?ZM6Ta5-pTkSVgqxD-Xn?m2l9HYE2{N< z`H<|=EvX$ir000%2iM{MPvh@gCz8=m8LuQy%=1dyLA!J(r})hc^Iz)x?7}GP-E)fn z{^z}l*F}of(;_v64|40LgsYkh)_Hl+22#3ye*OsoT`$<9Ze1_fpvYc- zF4!YBH;0$hSQ}t(&S#Tzt#$pIKF3;LqC1oMkNHSu+q_5i@8(N&Bjy9jR&RiqBNeA z#v3avS@9?vme_}Epel`LrSYyb9$K$kqMt_pmBv$ZkMi->T*~jK73XpXCbOkn*wnaI z9Pjm|`ORsZE6x{euqdDC9_6CD?jak@MPXfMW4Y|0>uj(f{qVpNu|AysIr9;7RCJv^ fnyaJrj}6pV%b78kjIxiG=Hu=hbn^+L=J)>x5u&*7 literal 0 HcmV?d00001 diff --git a/test/common/common/base64_differential_corpus/singleton b/test/common/common/base64_differential_corpus/singleton new file mode 100644 index 0000000000000..314d73bf4f576 --- /dev/null +++ b/test/common/common/base64_differential_corpus/singleton @@ -0,0 +1 @@ +% diff --git a/test/common/common/base64_differential_fuzz_test.cc b/test/common/common/base64_differential_fuzz_test.cc new file mode 100644 index 0000000000000..af20bbe0dcb88 --- /dev/null +++ b/test/common/common/base64_differential_fuzz_test.cc @@ -0,0 +1,26 @@ +#include "source/common/common/base64.h" + +#include "test/common/common/base64_legacy.h" +#include "test/fuzz/fuzz_runner.h" +#include "test/fuzz/utility.h" + +namespace Envoy { +namespace Fuzz { + +DEFINE_FUZZER(const uint8_t* buf, size_t len) { + const std::string input(reinterpret_cast(buf), len); + Envoy::Buffer::OwnedImpl buffer(input); + + // Encode: both must produce identical output for any input + FUZZ_ASSERT(Base64::encode(input) == Base64Legacy::encode(input)); + FUZZ_ASSERT(Base64::encode(buffer, input.size()) == Base64Legacy::encode(buffer, input.size())); + FUZZ_ASSERT(Base64Url::encode(input.data(), input.size()) == + Base64UrlLegacy::encode(input.data(), input.size())); + + // Decode: both must agree on every input, valid or invalid + FUZZ_ASSERT(Base64::decode(input) == Base64Legacy::decode(input)); + FUZZ_ASSERT(Base64Url::decode(input) == Base64UrlLegacy::decode(input)); +} + +} // namespace Fuzz +} // namespace Envoy diff --git a/test/common/common/base64_legacy.cc b/test/common/common/base64_legacy.cc new file mode 100644 index 0000000000000..13b6967ca1ce6 --- /dev/null +++ b/test/common/common/base64_legacy.cc @@ -0,0 +1,294 @@ +// Copied from source/common/common/base64.cc prior to PR #44915 to verify +// functional equivalence with the refactored implementation. Tests can be +// found at: +// test/common/common/base64_fuzz_test.cc +// test/common/common/base64_speed_test.cc + +#include "test/common/common/base64_legacy.h" + +#include +#include + +#include "source/common/common/assert.h" +#include "source/common/common/empty_string.h" + +#include "absl/container/fixed_array.h" +#include "absl/strings/str_cat.h" + +namespace Envoy { +namespace { + +// clang-format off +constexpr char CHAR_TABLE[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// Conversion table is taken from +// https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c +constexpr unsigned char REVERSE_LOOKUP_TABLE[256] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; + +// The base64url tables are copied from above and modified based on table in +// https://tools.ietf.org/html/rfc4648#section-5 +constexpr char URL_CHAR_TABLE[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +constexpr unsigned char URL_REVERSE_LOOKUP_TABLE[256] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 63, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; +// clang-format on + +inline bool decodeBase(const uint8_t cur_char, uint64_t pos, std::string& ret, + const unsigned char* const reverse_lookup_table) { + const unsigned char c = reverse_lookup_table[static_cast(cur_char)]; + if (c == 64) { + // Invalid character + return false; + } + + switch (pos % 4) { + case 0: + ret.push_back(c << 2); + break; + case 1: + ret.back() |= c >> 4; + ret.push_back(c << 4); + break; + case 2: + ret.back() |= c >> 2; + ret.push_back(c << 6); + break; + case 3: + ret.back() |= c; + break; + } + return true; +} + +inline bool decodeLast(const uint8_t cur_char, uint64_t pos, std::string& ret, + const unsigned char* const reverse_lookup_table) { + const unsigned char c = reverse_lookup_table[static_cast(cur_char)]; + if (c == 64) { + // Invalid character + return false; + } + + switch (pos % 4) { + case 0: + return false; + case 1: + ret.back() |= c >> 4; + return (c & 0b1111) == 0; + case 2: + ret.back() |= c >> 2; + return (c & 0b11) == 0; + case 3: + ret.back() |= c; + break; + } + return true; +} + +inline void encodeBase(const uint8_t cur_char, uint64_t pos, uint8_t& next_c, std::string& ret, + const char* const char_table) { + switch (pos % 3) { + case 0: + ret.push_back(char_table[cur_char >> 2]); + next_c = (cur_char & 0x03) << 4; + break; + case 1: + ret.push_back(char_table[next_c | (cur_char >> 4)]); + next_c = (cur_char & 0x0f) << 2; + break; + case 2: + ret.push_back(char_table[next_c | (cur_char >> 6)]); + ret.push_back(char_table[cur_char & 0x3f]); + next_c = 0; + break; + } +} + +inline void encodeLast(uint64_t pos, uint8_t last_char, std::string& ret, + const char* const char_table, bool add_padding) { + switch (pos % 3) { + case 1: + ret.push_back(char_table[last_char]); + if (add_padding) { + ret.push_back('='); + ret.push_back('='); + } + break; + case 2: + ret.push_back(char_table[last_char]); + if (add_padding) { + ret.push_back('='); + } + break; + default: + break; + } +} + +} // namespace + +std::string Base64Legacy::decode(absl::string_view input) { + if (input.length() % 4) { + return EMPTY_STRING; + } + return decodeWithoutPadding(input); +} + +std::string Base64Legacy::decodeWithoutPadding(absl::string_view input) { + if (input.empty()) { + return EMPTY_STRING; + } + + // At most last two chars can be '='. + size_t n = input.length(); + if (input[n - 1] == '=') { + n--; + if (n > 0 && input[n - 1] == '=') { + n--; + } + } + // Last position before "valid" padding character. + uint64_t last = n - 1; + // Determine output length. + size_t max_length = (n + 3) / 4 * 3; + if (n % 4 == 3) { + max_length -= 1; + } + if (n % 4 == 2) { + max_length -= 2; + } + + std::string ret; + ret.reserve(max_length); + for (uint64_t i = 0; i < last; ++i) { + if (!decodeBase(input[i], i, ret, REVERSE_LOOKUP_TABLE)) { + return EMPTY_STRING; + } + } + + if (!decodeLast(input[last], last, ret, REVERSE_LOOKUP_TABLE)) { + return EMPTY_STRING; + } + + ASSERT(ret.size() == max_length); + return ret; +} + +std::string Base64Legacy::encode(const Buffer::Instance& buffer, uint64_t length) { + uint64_t output_length = (std::min(length, buffer.length()) + 2) / 3 * 4; + std::string ret; + ret.reserve(output_length); + + uint64_t j = 0; + uint8_t next_c = 0; + for (const Buffer::RawSlice& slice : buffer.getRawSlices()) { + const uint8_t* slice_mem = static_cast(slice.mem_); + + for (uint64_t i = 0; i < slice.len_ && j < length; ++i, ++j) { + encodeBase(slice_mem[i], j, next_c, ret, CHAR_TABLE); + } + + if (j == length) { + break; + } + } + + encodeLast(j, next_c, ret, CHAR_TABLE, true); + + return ret; +} + +std::string Base64Legacy::encode(absl::string_view input) { + return encode(input.data(), input.length()); +} + +std::string Base64Legacy::encode(const char* input, uint64_t length) { + return encode(input, length, true); +} + +std::string Base64Legacy::encode(const char* input, uint64_t length, bool add_padding) { + uint64_t output_length = (length + 2) / 3 * 4; + std::string ret; + ret.reserve(output_length); + + uint64_t pos = 0; + uint8_t next_c = 0; + + for (uint64_t i = 0; i < length; ++i) { + encodeBase(input[i], pos++, next_c, ret, CHAR_TABLE); + } + + encodeLast(pos, next_c, ret, CHAR_TABLE, add_padding); + + return ret; +} + +void Base64Legacy::completePadding(std::string& encoded) { + if (encoded.length() % 4 != 0) { + std::string trailing_padding(4 - encoded.length() % 4, '='); + absl::StrAppend(&encoded, trailing_padding); + } +} + +std::string Base64UrlLegacy::decode(absl::string_view input) { + if (input.empty()) { + return EMPTY_STRING; + } + + std::string ret; + ret.reserve(input.length() / 4 * 3 + 3); + + uint64_t last = input.length() - 1; + for (uint64_t i = 0; i < last; ++i) { + if (!decodeBase(input[i], i, ret, URL_REVERSE_LOOKUP_TABLE)) { + return EMPTY_STRING; + } + } + + if (!decodeLast(input[last], last, ret, URL_REVERSE_LOOKUP_TABLE)) { + return EMPTY_STRING; + } + + return ret; +} + +std::string Base64UrlLegacy::encode(const char* input, uint64_t length) { + uint64_t output_length = (length + 2) / 3 * 4; + std::string ret; + ret.reserve(output_length); + + uint64_t pos = 0; + uint8_t next_c = 0; + + for (uint64_t i = 0; i < length; ++i) { + encodeBase(input[i], pos++, next_c, ret, URL_CHAR_TABLE); + } + + encodeLast(pos, next_c, ret, URL_CHAR_TABLE, false); + + return ret; +} + +} // namespace Envoy diff --git a/test/common/common/base64_legacy.h b/test/common/common/base64_legacy.h new file mode 100644 index 0000000000000..aeba95ba46c91 --- /dev/null +++ b/test/common/common/base64_legacy.h @@ -0,0 +1,100 @@ +#pragma once + +// Copied from source/common/common/base64.h prior to PR #44915 to verify +// functional equivalence with the refactored implementation. Tests can be +// found at: +// test/common/common/base64_fuzz_test.cc +// test/common/common/base64_speed_test.cc + +#include +#include + +#include "envoy/buffer/buffer.h" + +#include "absl/strings/string_view.h" + +namespace Envoy { + +/** + * A utility class to support base64 encoding, which is defined in RFC4648 Section 4. + * See https://tools.ietf.org/html/rfc4648#section-4 + */ +class Base64Legacy { +public: + /** + * Base64 encode an input buffer. + * @param buffer supplies the buffer to encode. + * @param length supplies the length to encode which may be <= the buffer length. + */ + static std::string encode(const Buffer::Instance& buffer, uint64_t length); + + /** + * Base64 encode an input char buffer with a given length. + * @param input char array to encode. + * @param length of the input array. + */ + static std::string encode(const char* input, uint64_t length); + + /** + * Base64 encode an input char buffer with a given length. + * @param input string to encode. + */ + static std::string encode(absl::string_view input); + + /** + * Base64 encode an input char buffer with a given length. + * @param input char array to encode. + * @param length of the input array. + * @param whether add padding at the end of the output. + */ + static std::string encode(const char* input, uint64_t length, bool add_padding); + + /** + * Base64 decode an input string. Padding is required. + * @param input supplies the input to decode. + * + * Note, decoded string may contain '\0' at any position, it should be treated as a sequence of + * bytes. + */ + static std::string decode(absl::string_view input); + + /** + * Base64 decode an input string. Padding is not required. + * @param input supplies the input to decode. + * + * Note, decoded string may contain '\0' at any position, it should be treated as a sequence of + * bytes. + */ + static std::string decodeWithoutPadding(absl::string_view input); + + /** + * Add the padding in the base64 encoded binary if the padding is missing. + * @param encoded is the target to complete the padding. + */ + static void completePadding(std::string& encoded); +}; + +/** + * A utility class to support base64url encoding, which is defined in RFC4648 Section 5. + * See https://tools.ietf.org/html/rfc4648#section-5 + */ +class Base64UrlLegacy { +public: + /** + * Base64url encode an input char buffer with a given length. + * @param input char array to encode. + * @param length of the input array. + */ + static std::string encode(const char* input, uint64_t length); + + /** + * Base64url decode an input string. Padding must not be included in the input. + * @param input supplies the input to decode. + * + * Note, decoded string may contain '\0' at any position, it should be treated as a sequence of + * bytes. + */ + static std::string decode(absl::string_view input); +}; + +} // namespace Envoy diff --git a/test/common/common/base64_speed_test.cc b/test/common/common/base64_speed_test.cc new file mode 100644 index 0000000000000..9a07c09bd4950 --- /dev/null +++ b/test/common/common/base64_speed_test.cc @@ -0,0 +1,166 @@ +// Note: this should be run with --compilation_mode=opt, and would benefit from +// a quiescent system with disabled cstate power management. + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/base64.h" + +#include "base64_legacy.h" +#include "benchmark/benchmark.h" + +// NOLINT(namespace-envoy) + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_AbslBase64Encode(benchmark::State& state) { + const std::string input(state.range(0), 'x'); + for (auto _ : state) { + std::string res = Envoy::Base64::encode(input); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * state.range(0)); +} +BENCHMARK(BM_AbslBase64Encode)->Range(8, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_LegacyBase64Encode(benchmark::State& state) { + const std::string input(state.range(0), 'x'); + for (auto _ : state) { + std::string res = Envoy::Base64Legacy::encode(input); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * state.range(0)); +} +BENCHMARK(BM_LegacyBase64Encode)->Range(8, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_AbslBase64Decode(benchmark::State& state) { + const std::string input(state.range(0), 'x'); + const std::string encoded = Envoy::Base64::encode(input); + + for (auto _ : state) { + std::string res = Envoy::Base64::decode(encoded); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * encoded.size()); +} +BENCHMARK(BM_AbslBase64Decode)->Range(8, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_LegacyBase64Decode(benchmark::State& state) { + const std::string input(state.range(0), 'x'); + const std::string encoded = Envoy::Base64::encode(input); + + for (auto _ : state) { + std::string res = Envoy::Base64Legacy::decode(encoded); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * encoded.size()); +} +BENCHMARK(BM_LegacyBase64Decode)->Range(8, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_AbslBase64BufferInstanceEncode(benchmark::State& state) { + const std::string input(state.range(0), 'x'); + Envoy::Buffer::OwnedImpl buffer(input); + + for (auto _ : state) { + std::string res = Envoy::Base64::encode(buffer, input.size()); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * state.range(0)); +} +BENCHMARK(BM_AbslBase64BufferInstanceEncode)->Range(8, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_LegacyBase64BufferInstanceEncode(benchmark::State& state) { + const std::string input(state.range(0), 'x'); + Envoy::Buffer::OwnedImpl buffer(input); + + for (auto _ : state) { + std::string res = Envoy::Base64Legacy::encode(buffer, input.size()); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * state.range(0)); +} +BENCHMARK(BM_LegacyBase64BufferInstanceEncode)->Range(8, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_AbslBase64BufferInstanceEncodeMultiSlice(benchmark::State& state) { + const uint64_t total_size = state.range(0); + const uint64_t slice_size = 1024; + Envoy::Buffer::OwnedImpl buffer; + for (uint64_t off = 0; off < total_size; off += slice_size) { + const uint64_t n = std::min(slice_size, total_size - off); + buffer.appendSliceForTest(std::string(n, 'x')); + } + for (auto _ : state) { + std::string res = Envoy::Base64::encode(buffer, total_size); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * state.range(0)); +} +BENCHMARK(BM_AbslBase64BufferInstanceEncodeMultiSlice)->Range(4096, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_LegacyBase64BufferInstanceEncodeMultiSlice(benchmark::State& state) { + const uint64_t total_size = state.range(0); + const uint64_t slice_size = 1024; + Envoy::Buffer::OwnedImpl buffer; + for (uint64_t off = 0; off < total_size; off += slice_size) { + const uint64_t n = std::min(slice_size, total_size - off); + buffer.appendSliceForTest(std::string(n, 'x')); + } + for (auto _ : state) { + std::string res = Envoy::Base64Legacy::encode(buffer, total_size); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * state.range(0)); +} +BENCHMARK(BM_LegacyBase64BufferInstanceEncodeMultiSlice)->Range(4096, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_AbslBase64UrlEncode(benchmark::State& state) { + const std::string input(state.range(0), 'x'); + for (auto _ : state) { + std::string res = Envoy::Base64Url::encode(input.data(), input.size()); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * state.range(0)); +} +BENCHMARK(BM_AbslBase64UrlEncode)->Range(8, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_LegacyBase64UrlEncode(benchmark::State& state) { + const std::string input(state.range(0), 'x'); + for (auto _ : state) { + std::string res = Envoy::Base64UrlLegacy::encode(input.data(), input.size()); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * state.range(0)); +} +BENCHMARK(BM_LegacyBase64UrlEncode)->Range(8, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_AbslBase64UrlDecode(benchmark::State& state) { + const std::string input(state.range(0), 'x'); + const std::string encoded = Envoy::Base64Url::encode(input.data(), input.size()); + + for (auto _ : state) { + std::string res = Envoy::Base64Url::decode(encoded); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * encoded.size()); +} +BENCHMARK(BM_AbslBase64UrlDecode)->Range(8, 1 << 20); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_LegacyBase64UrlDecode(benchmark::State& state) { + const std::string input(state.range(0), 'x'); + const std::string encoded = Envoy::Base64Url::encode(input.data(), input.size()); + + for (auto _ : state) { + std::string res = Envoy::Base64UrlLegacy::decode(encoded); + benchmark::DoNotOptimize(res); + } + state.SetBytesProcessed(state.iterations() * encoded.size()); +} +BENCHMARK(BM_LegacyBase64UrlDecode)->Range(8, 1 << 20); diff --git a/test/common/common/base64_test.cc b/test/common/common/base64_test.cc index e00ae7f998271..d358b3e8a8ed9 100644 --- a/test/common/common/base64_test.cc +++ b/test/common/common/base64_test.cc @@ -89,6 +89,7 @@ TEST(Base64Test, DecodeFailure) { EXPECT_EQ("", Base64::decode("A===")); EXPECT_EQ("", Base64::decode("====")); EXPECT_EQ("", Base64::decode("123")); + EXPECT_EQ("", Base64::decode("u\nUU")); } TEST(Base64Test, DecodeWithoutPadding) { @@ -112,8 +113,8 @@ TEST(Base64Test, DecodeWithoutPadding) { TEST(Base64Test, MultiSlicesBufferEncode) { Buffer::OwnedImpl buffer; - buffer.add("foob", 4); - buffer.add("ar", 2); + buffer.appendSliceForTest("foob", 4); + buffer.appendSliceForTest("ar", 2); EXPECT_EQ("Zm9vYg==", Base64::encode(buffer, 4)); EXPECT_EQ("Zm9vYmE=", Base64::encode(buffer, 5)); EXPECT_EQ("Zm9vYmFy", Base64::encode(buffer, 6)); @@ -226,5 +227,6 @@ TEST(Base64UrlTest, DecodeFailure) { EXPECT_EQ("", Base64Url::decode("Zh")); // 011001 100001 <- unused bit at tail EXPECT_EQ("", Base64Url::decode("Zm9")); // 011001 100110 111101 <- unused bit at tail EXPECT_EQ("", Base64Url::decode("A")); + EXPECT_EQ("", Base64Url::decode("ygg=")); } } // namespace Envoy From 671bb592d4542a65e2872ec587dcaa11d3c2280e Mon Sep 17 00:00:00 2001 From: code Date: Fri, 22 May 2026 00:06:14 +0800 Subject: [PATCH 370/750] runtime: enable new dns cluster implementation (#45048) Commit Message: runtime: enable new dns cluster implementation Additional Description: We have released multiple versions after the new DNS clusters implementation. Now it's time to enable it by default. Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: wbpcode/wangbaiping --- changelogs/changelogs.yaml | 2 ++ .../dns__enabled-new-dns-implementation-by-default.rst | 3 +++ source/common/runtime/runtime_features.cc | 7 +------ 3 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 changelogs/current/minor_behavior_changes/dns__enabled-new-dns-implementation-by-default.rst diff --git a/changelogs/changelogs.yaml b/changelogs/changelogs.yaml index b10ae2658c7be..697b273173b81 100644 --- a/changelogs/changelogs.yaml +++ b/changelogs/changelogs.yaml @@ -96,3 +96,5 @@ areas: title: tls wasm: title: wasm + dns: + title: dns diff --git a/changelogs/current/minor_behavior_changes/dns__enabled-new-dns-implementation-by-default.rst b/changelogs/current/minor_behavior_changes/dns__enabled-new-dns-implementation-by-default.rst new file mode 100644 index 0000000000000..ffa111f899b1e --- /dev/null +++ b/changelogs/current/minor_behavior_changes/dns__enabled-new-dns-implementation-by-default.rst @@ -0,0 +1,3 @@ +Runtime guard ``envoy.reloadable_features.enable_new_dns_implementation`` is now enabled by +default. This activates the new DNS implementation, a merged implementation of strict and +logical DNS clusters. It can be temporarily reverted by setting the runtime guard to ``false``. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 977335204c93b..e86d19ee11798 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -44,6 +44,7 @@ RUNTIME_GUARD(envoy_reloadable_features_disallow_quic_client_udp_mmsg); RUNTIME_GUARD(envoy_reloadable_features_enable_cel_regex_precompilation); RUNTIME_GUARD(envoy_reloadable_features_enable_cel_response_path_matching); RUNTIME_GUARD(envoy_reloadable_features_enable_compression_bomb_protection); +RUNTIME_GUARD(envoy_reloadable_features_enable_new_dns_implementation); // TODO(adisuissa): enable once the LRS server-self-ads support is fully // implemented, and tested. FALSE_RUNTIME_GUARD(envoy_reloadable_features_enable_lrs_server_self_ads); @@ -207,12 +208,6 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_getaddrinfo_no_ai_flags); // Flip to true after two release periods. FALSE_RUNTIME_GUARD(envoy_reloadable_features_remove_legacy_route_formatter); -// TODO(grnmeira): -// Enables the new DNS implementation, a merged implementation of -// strict and logical DNS clusters. This new implementation will -// take over the split ones, and will be used as a base for the -// implementation of on-demand DNS. -FALSE_RUNTIME_GUARD(envoy_reloadable_features_enable_new_dns_implementation); // Force a local reply from upstream envoy for reverse connections. FALSE_RUNTIME_GUARD(envoy_reloadable_features_reverse_conn_force_local_reply); // RELEASE_ASSERT when upstream stream detects UAF of downstream response decoder instance. From 96294652f7f2a3db92b3e0c78efdd84941a2059a Mon Sep 17 00:00:00 2001 From: Omkhar Arasaratnam Date: Thu, 21 May 2026 15:49:43 -0400 Subject: [PATCH 371/750] file_server: fix use-after-free in FileStreamer async-file callbacks after request cancel (#45153) **Commit Message:** file_server: fix UAF on async-file callback after request cancel `FileStreamer::start()` captured `[this]` into the async-file callbacks returned by `AsyncFileManager::stat()` and `AsyncFileManager::read()`. The async-file manager guarantees the callback fires (even if cancel was called), so when the `FileStreamer` is destroyed before the callback runs the bare `this` capture becomes dangling and the callback dereferences freed memory. This is reachable on any deployment using the `file_server` filter when downstream clients can cancel or reset requests while an async-file operation is in flight. Specifically, the race is if the callback is placed in a dispatcher queue by `AsyncFileManager` before cancelation, *then* deletion before the callback reaches the head of the queue. Solution is to hold a CancelCallback sentinel in `FileStreamer` and wrap the async-file callbacks in `cancelWrapper`. **Additional Description:** Reported privately via GHSA-c4w8-wqj5-4xph; envoy security team (@wbpcode) asked for this to be fixed in the open given the `file_server` filter's WIP status. Fixes #45152. **Risk Level:** Low. Defensive lifetime guard around two existing async-file callback sites. No behavior change for the in-flight-completes-before-cancel path. The cancel-then-callback path previously dereferenced freed memory and now no-ops. **Testing:** `bazel test --config=clang --config=clang-asan //test/extensions/filters/http/file_server:file_server_test` passes locally on the patched tree at upstream main. Happy to add a focused regression test that drives the cancel-during-stat path if maintainers would like. **Docs Changes:** None required. **Release Notes:** Added to `changelogs/current.yaml` under `bug_fixes` (area: file_server). **Platform Specific Features:** None. --------- Signed-off-by: Omkhar Arasaratnam Co-authored-by: Omkhar Arasaratnam --- ...e-after-free-in-filestreamer-callbacks.rst | 9 + .../extensions/filters/http/file_server/BUILD | 1 + .../filters/http/file_server/file_streamer.cc | 206 ++++++++++-------- .../filters/http/file_server/file_streamer.h | 10 + 4 files changed, 136 insertions(+), 90 deletions(-) create mode 100644 changelogs/current/bug_fixes/file_server__fixed-use-after-free-in-filestreamer-callbacks.rst diff --git a/changelogs/current/bug_fixes/file_server__fixed-use-after-free-in-filestreamer-callbacks.rst b/changelogs/current/bug_fixes/file_server__fixed-use-after-free-in-filestreamer-callbacks.rst new file mode 100644 index 0000000000000..8b0b5524d5ce3 --- /dev/null +++ b/changelogs/current/bug_fixes/file_server__fixed-use-after-free-in-filestreamer-callbacks.rst @@ -0,0 +1,9 @@ +Fixed a use-after-free in the ``file_server`` HTTP filter when a request is cancelled +(downstream RST, stream timeout, etc.) before the underlying ``AsyncFileManager::stat`` +or ``read`` callback fires. ``FileStreamer`` previously captured ``[this]`` into the +async-file callbacks, which left a dangling pointer if the ``FileStreamer`` was +destroyed while a stat or read was still in flight or already queued on the owner +dispatcher. The callbacks now use ``cancelWrapped`` (``source/common/common/cancel_wrapper.h``): +the in-flight file operation is aborted via the file-side cancel, and any callback +already dispatched to the worker's event loop short-circuits before dereferencing the +freed ``FileStreamer``. diff --git a/source/extensions/filters/http/file_server/BUILD b/source/extensions/filters/http/file_server/BUILD index c803e9de3be17..64e5cecce6e23 100644 --- a/source/extensions/filters/http/file_server/BUILD +++ b/source/extensions/filters/http/file_server/BUILD @@ -34,6 +34,7 @@ envoy_cc_library( ":absl_status_to_http_status", "//envoy/buffer:buffer_interface", "//envoy/server:instance_interface", + "//source/common/common:cancel_wrapper_lib", "//source/common/common:enum_to_int", "//source/common/common:radix_tree_lib", "//source/common/http:codes_lib", diff --git a/source/extensions/filters/http/file_server/file_streamer.cc b/source/extensions/filters/http/file_server/file_streamer.cc index c4adb38025146..17059d68bb8c1 100644 --- a/source/extensions/filters/http/file_server/file_streamer.cc +++ b/source/extensions/filters/http/file_server/file_streamer.cc @@ -31,29 +31,35 @@ void FileStreamer::begin(const FileServerConfig& config, Event::Dispatcher& disp end_ = end; file_path_ = std::move(file_path); cancel_callback_ = file_server_config_->asyncFileManager()->stat( - dispatcher_, file_path_.string(), [this](absl::StatusOr result) { - if (!result.ok()) { - client_.errorFromFile(abslStatusToHttpStatus(result.status().code()), - absl::StrCat("file_server_stat_error")); - return; - } - const struct stat& s = result.value(); - if (S_ISDIR(s.st_mode)) { - startDir(0); - return; - } - cancel_callback_ = file_server_config_->asyncFileManager()->openExistingFile( - dispatcher_, file_path_.string(), Common::AsyncFiles::AsyncFileManager::Mode::ReadOnly, - [this](absl::StatusOr result) { - if (!result.ok()) { - client_.errorFromFile(abslStatusToHttpStatus(result.status().code()), - absl::StrCat("file_server_open_error")); - return; - } - async_file_ = std::move(result.value()); - startFile(); - }); - }); + dispatcher_, file_path_.string(), + Envoy::CancelWrapper::cancelWrapped( + [this](absl::StatusOr result) { + if (!result.ok()) { + client_.errorFromFile(abslStatusToHttpStatus(result.status().code()), + absl::StrCat("file_server_stat_error")); + return; + } + const struct stat& s = result.value(); + if (S_ISDIR(s.st_mode)) { + startDir(0); + return; + } + cancel_callback_ = file_server_config_->asyncFileManager()->openExistingFile( + dispatcher_, file_path_.string(), + Common::AsyncFiles::AsyncFileManager::Mode::ReadOnly, + Envoy::CancelWrapper::cancelWrapped( + [this](absl::StatusOr result) { + if (!result.ok()) { + client_.errorFromFile(abslStatusToHttpStatus(result.status().code()), + absl::StrCat("file_server_open_error")); + return; + } + async_file_ = std::move(result.value()); + startFile(); + }, + &cancel_dispatcher_callbacks_)); + }, + &cancel_dispatcher_callbacks_)); } void FileStreamer::startDir(int behavior_index) { @@ -67,18 +73,21 @@ void FileStreamer::startDir(int behavior_index) { cancel_callback_ = file_server_config_->asyncFileManager()->openExistingFile( dispatcher_, (file_path_ / std::filesystem::path{behavior->default_file()}).string(), Common::AsyncFiles::AsyncFileManager::Mode::ReadOnly, - [this, behavior_index](absl::StatusOr result) { - if (!result.ok()) { - // Try the next directoryBehavior. - // Since the action is dispatched, this isn't recursion. - return startDir(behavior_index + 1); - } - file_path_ = file_path_ / - std::filesystem::path{ - file_server_config_->directoryBehavior(behavior_index)->default_file()}; - async_file_ = std::move(result.value()); - startFile(); - }); + Envoy::CancelWrapper::cancelWrapped( + [this, behavior_index](absl::StatusOr result) { + if (!result.ok()) { + // Try the next directoryBehavior. + // Since the action is dispatched, this isn't recursion. + return startDir(behavior_index + 1); + } + file_path_ = + file_path_ / + std::filesystem::path{ + file_server_config_->directoryBehavior(behavior_index)->default_file()}; + async_file_ = std::move(result.value()); + startFile(); + }, + &cancel_dispatcher_callbacks_)); return; } else if (behavior->has_list()) { client_.errorFromFile(Http::Code::Forbidden, "file_server_list_not_implemented"); @@ -92,44 +101,50 @@ void FileStreamer::startDir(int behavior_index) { void FileStreamer::startFile() { ASSERT(async_file_); - auto queued = async_file_->stat(dispatcher_, [this](absl::StatusOr result) { - if (!result.ok()) { - client_.errorFromFile(abslStatusToHttpStatus(result.status().code()), - "file_server_opened_file_stat_failed"); - return; - } - const struct stat& s = result.value(); - if (static_cast(s.st_size) < end_ || static_cast(s.st_size) < pos_ || - (end_ != 0 && end_ < pos_)) { - client_.errorFromFile(Http::Code::RangeNotSatisfiable, "file_server_range_not_satisfiable"); - return; - } - auto headers = Http::ResponseHeaderMapImpl::create(); - headers->setReference(acceptRangesHeaderKey(), "bytes"); - if (pos_ || end_) { - // Range request gets PartialContent, a content-range, and reduced content-length header. - if (!end_) { - end_ = s.st_size; - } - headers->setContentLength(end_ - pos_); - // Subtract one from end_ in this header because range headers use [start, end) vs. - // end_ is in normal programmer [start, end] style. - headers->setReferenceKey(Envoy::Http::Headers::get().ContentRange, - absl::StrCat("bytes ", pos_, "-", end_ - 1, "/", s.st_size)); - headers->setStatus(enumToInt(Http::Code::PartialContent)); - } else { - end_ = s.st_size; - headers->setContentLength(s.st_size); - headers->setStatus(enumToInt(Http::Code::OK)); - } - absl::string_view ct = file_server_config_->contentTypeForPath(file_path_); - if (!ct.empty()) { - headers->setContentType(ct); - } - if (client_.headersFromFile(std::move(headers))) { - readBodyChunk(); - } - }); + auto queued = async_file_->stat( + dispatcher_, + Envoy::CancelWrapper::cancelWrapped( + [this](absl::StatusOr result) { + if (!result.ok()) { + client_.errorFromFile(abslStatusToHttpStatus(result.status().code()), + "file_server_opened_file_stat_failed"); + return; + } + const struct stat& s = result.value(); + if (static_cast(s.st_size) < end_ || + static_cast(s.st_size) < pos_ || (end_ != 0 && end_ < pos_)) { + client_.errorFromFile(Http::Code::RangeNotSatisfiable, + "file_server_range_not_satisfiable"); + return; + } + auto headers = Http::ResponseHeaderMapImpl::create(); + headers->setReference(acceptRangesHeaderKey(), "bytes"); + if (pos_ || end_) { + // Range request gets PartialContent, a content-range, and reduced content-length + // header. + if (!end_) { + end_ = s.st_size; + } + headers->setContentLength(end_ - pos_); + // Subtract one from end_ in this header because range headers use [start, end) vs. + // end_ is in normal programmer [start, end] style. + headers->setReferenceKey(Envoy::Http::Headers::get().ContentRange, + absl::StrCat("bytes ", pos_, "-", end_ - 1, "/", s.st_size)); + headers->setStatus(enumToInt(Http::Code::PartialContent)); + } else { + end_ = s.st_size; + headers->setContentLength(s.st_size); + headers->setStatus(enumToInt(Http::Code::OK)); + } + absl::string_view ct = file_server_config_->contentTypeForPath(file_path_); + if (!ct.empty()) { + headers->setContentType(ct); + } + if (client_.headersFromFile(std::move(headers))) { + readBodyChunk(); + } + }, + &cancel_dispatcher_callbacks_)); ASSERT(queued.ok()); cancel_callback_ = std::move(queued.value()); } @@ -150,29 +165,40 @@ void FileStreamer::readBodyChunk() { ASSERT(async_file_); static const uint64_t kMaxReadSize = 32 * 1024; uint64_t sz = std::min(end_ - pos_, kMaxReadSize); - auto queued = - async_file_->read(dispatcher_, pos_, sz, [this](absl::StatusOr result) { - if (!result.ok()) { - client_.errorFromFile(abslStatusToHttpStatus(result.status().code()), - "file_server_read_operation_failed"); - return; - } - Buffer::InstancePtr buf = std::move(result.value()); - pos_ += buf->length(); - client_.bodyChunkFromFile(std::move(buf), pos_ == end_); - if (!paused_ && pos_ != end_) { - readBodyChunk(); - } else if (paused_) { - action_has_been_postponed_by_pause_ = true; - } - }); + auto queued = async_file_->read(dispatcher_, pos_, sz, + Envoy::CancelWrapper::cancelWrapped( + [this](absl::StatusOr result) { + if (!result.ok()) { + client_.errorFromFile( + abslStatusToHttpStatus(result.status().code()), + "file_server_read_operation_failed"); + return; + } + Buffer::InstancePtr buf = std::move(result.value()); + pos_ += buf->length(); + client_.bodyChunkFromFile(std::move(buf), pos_ == end_); + if (!paused_ && pos_ != end_) { + readBodyChunk(); + } else if (paused_) { + action_has_been_postponed_by_pause_ = true; + } + }, + &cancel_dispatcher_callbacks_)); ASSERT(queued.ok()); cancel_callback_ = std::move(queued.value()); } -void FileStreamer::abort() { cancel_callback_(); } +void FileStreamer::abort() { + cancel_callback_(); + // Short-circuit any callback the dispatcher has already enqueued before the + // cancel could land (see source/extensions/common/async_files/ + // async_file_manager_thread_pool.cc): without this, the callback would + // dereference the destroyed FileStreamer / FileStreamerClient. + cancel_dispatcher_callbacks_(); +} FileStreamer::~FileStreamer() { + cancel_dispatcher_callbacks_(); if (async_file_) { async_file_->close(nullptr, [](absl::Status) {}).IgnoreError(); } diff --git a/source/extensions/filters/http/file_server/file_streamer.h b/source/extensions/filters/http/file_server/file_streamer.h index d0f47e28cb250..eaa150960dcc4 100644 --- a/source/extensions/filters/http/file_server/file_streamer.h +++ b/source/extensions/filters/http/file_server/file_streamer.h @@ -4,6 +4,7 @@ #include "envoy/http/codes.h" #include "envoy/http/header_map.h" +#include "source/common/common/cancel_wrapper.h" #include "source/extensions/common/async_files/async_file_manager.h" #include "source/extensions/filters/http/file_server/filter_config.h" @@ -61,7 +62,16 @@ class FileStreamer { bool paused_ = false; bool action_has_been_postponed_by_pause_ = false; AsyncFileHandle async_file_; + // Cancel handle for the in-flight AsyncFileManager operation. Stops the + // file-side work if it has not yet completed when abort() runs. CancelFunction cancel_callback_ = []() {}; + // Cancel handle for the dispatcher-queued completion callback. The + // AsyncFileManager posts the callback onto the owner dispatcher before + // observing cancellation (see source/extensions/common/async_files/ + // async_file_manager_thread_pool.cc), so a completion can already be + // queued by the time abort() runs. Invoking this on destruction + // short-circuits the queued callback before it dereferences this object. + Envoy::CancelWrapper::CancelFunction cancel_dispatcher_callbacks_ = []() {}; }; } // namespace FileServer From 10840c2643bbbcaef5aa781c696c4f2aa6b27909 Mon Sep 17 00:00:00 2001 From: Ethan Truong Date: Thu, 21 May 2026 16:09:30 -0400 Subject: [PATCH 372/750] Throw EnvoyException when string length is a negative varint (#45213) Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] Signed-off-by: Ethan Truong --- .../thrift_proxy/header_transport_impl.cc | 4 ++ .../header_transport_impl_test.cc | 31 ++++++++++++++ .../network/thrift_proxy/integration_test.cc | 41 +++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/source/extensions/filters/network/thrift_proxy/header_transport_impl.cc b/source/extensions/filters/network/thrift_proxy/header_transport_impl.cc index 4a5155f4dd012..e15601fbd2d2b 100644 --- a/source/extensions/filters/network/thrift_proxy/header_transport_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/header_transport_impl.cc @@ -312,6 +312,10 @@ int32_t HeaderTransportImpl::drainVarIntI32(Buffer::Instance& buffer, int32_t& h std::string HeaderTransportImpl::drainVarString(Buffer::Instance& buffer, int32_t& header_size, const char* desc) { const int16_t str_len = drainVarIntI16(buffer, header_size, desc); + if (str_len < 0) { + throw EnvoyException(fmt::format( + "unable to read header transport {}: invalid negative length {}", desc, str_len)); + } if (str_len == 0) { return ""; } diff --git a/test/extensions/filters/network/thrift_proxy/header_transport_impl_test.cc b/test/extensions/filters/network/thrift_proxy/header_transport_impl_test.cc index 3a7bcebd7ce87..c8cc97cb5e704 100644 --- a/test/extensions/filters/network/thrift_proxy/header_transport_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/header_transport_impl_test.cc @@ -189,6 +189,37 @@ TEST(HeaderTransportTest, InvalidHeaderSize) { } } +TEST(HeaderTransportTest, NegativeStringLength) { + HeaderTransportImpl transport; + MessageMetadata metadata; + + Buffer::OwnedImpl buffer; + buffer.writeBEInt(100); // frame size + buffer.writeBEInt(0x0FFF); // magic + buffer.writeBEInt(0); // flags + buffer.writeBEInt(1); // sequence number + buffer.writeBEInt(10); // header size / 4 (header_size = 40) + + // Header data + buffer.writeByte(0); // Protocol ID (Binary) - varint 0 + buffer.writeByte(0); // Num transforms - varint 0 + buffer.writeByte(1); // Info ID (1) - varint 1 + buffer.writeByte(1); // Header Count (1) - varint 1 + + // Key length: -1 (encoded as varint 0xFF 0xFF 0xFF 0xFF 0x0F) + buffer.writeByte(0xFF); + buffer.writeByte(0xFF); + buffer.writeByte(0xFF); + buffer.writeByte(0xFF); + buffer.writeByte(0x0F); + + addRepeated(buffer, 80, 0); // padding + + EXPECT_THROW_WITH_MESSAGE( + transport.decodeFrameStart(buffer, metadata), EnvoyException, + "unable to read header transport header key: invalid negative length -1"); +} + TEST(HeaderTransportTest, InvalidProto) { HeaderTransportImpl transport; MessageMetadata metadata; diff --git a/test/extensions/filters/network/thrift_proxy/integration_test.cc b/test/extensions/filters/network/thrift_proxy/integration_test.cc index 08e17de797f64..a54c4d056725c 100644 --- a/test/extensions/filters/network/thrift_proxy/integration_test.cc +++ b/test/extensions/filters/network/thrift_proxy/integration_test.cc @@ -482,6 +482,47 @@ TEST_P(ThriftConnManagerIntegrationTest, OnewayEarlyClosePartialRequest) { EXPECT_EQ(1U, counter->value()); } +TEST_P(ThriftConnManagerIntegrationTest, NegativeVarIntCrashRepro) { + // Only relevant for Header transport + if (std::get<0>(GetParam()) != TransportType::Header) { + return; + } + + initializeCommon(); + + Buffer::OwnedImpl buffer; + buffer.writeBEInt(100); // frame size + buffer.writeBEInt(0x0FFF); // magic + buffer.writeBEInt(0); // flags + buffer.writeBEInt(1); // sequence number + buffer.writeBEInt(10); // header size / 4 (header_size = 40) + + // Header data + buffer.writeByte(0); // Protocol ID (Binary) - varint 0 + buffer.writeByte(0); // Num transforms - varint 0 + buffer.writeByte(1); // Info ID (1) - varint 1 + buffer.writeByte(1); // Header Count (1) - varint 1 + + // Key length: -1 (encoded as varint 0xFF 0xFF 0xFF 0xFF 0x0F) + buffer.writeByte(0xFF); + buffer.writeByte(0xFF); + buffer.writeByte(0xFF); + buffer.writeByte(0xFF); + buffer.writeByte(0x0F); + + for (int i = 0; i < 80; i++) { + buffer.writeByte(0); + } + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + EXPECT_TRUE(tcp_client->write(buffer.toString(), false, false)); + + // The connection should be closed because of the exception in decodeFrameStart + tcp_client->waitForDisconnect(); + + test_server_->waitForCounter("thrift.thrift_stats.request_decoding_error", Ge(1)); +} + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions From 6d26e3412330f3c42fdf71fa6665643e685f2662 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Thu, 21 May 2026 20:36:41 -0400 Subject: [PATCH 373/750] build: Bump Bazel to 8 (#45218) Bazel 8 disables WORKSPACE mode by default in favor of bzlmod. Since envoy still uses WORKSPACE, we add --enable_workspace and --noenable_bzlmod flags. Several other compatibility changes are needed across the build system, tooling, and mobile SDK. Bazel core: - Bump .bazelversion to 8.7.0. - Add compatibility flags: --enable_workspace, --noincompatible_disallow_empty_glob, --noincompatible_disallow_ctx_resolve_tools, --legacy_external_runfiles. - Rename --experimental_allow_tags_propagation to --incompatible_allow_tags_propagation (graduated in Bazel 8). - Rename --experimental_generate_json_trace_profile to --generate_json_trace_profile. Dependency upgrades: - rules_java 7.12.5 -> 9.6.1 (required for rules_java_deps.bzl, used by protobuf/grpc under Bazel 8). Call java_compatibility_proxy_repo() in repositories_extra.bzl. - Build images v0.1.4 -> v0.1.5 (includes Android build-tools 35.0.0). Patches (new): - rules_python: Add //conditions:default entries to select() in toolchain_aliases.bzl. Without this, builds targeting platforms not in the registered Python platform list (e.g. WASI for WASM, Android) fail because no select() branch matches. Not upstreamable as-is; a lateral fix would be to register Python platforms for all target configs. - rules_java: Patch to fix compatibility with Bazel 8. - emsdk: Default to linux binaries instead of :empty filegroups in //conditions:default, fixing WASM RBE builds where the exec platform doesn't match any host config_setting. Runfiles layout: Bazel 8 places external repo runfiles directly under the runfiles root (e.g. {RUNFILES_DIR}/openssl/...) instead of nesting them under the workspace (e.g. {RUNFILES_DIR}/envoy/external/openssl/...). - compat/openssl: Try both Bazel 7 and 8 runfiles paths. - AWS signing corpus tests: Use TestEnvironment::runfilesDirectory() with the repo name to resolve external data files. - Coverage tools: Replace PYTHON_RUNFILES (no longer set in Bazel 8) with RUNFILES_DIR, and add runfiles-based resolution for validate.sh. Mobile / Android: - Upgrade rules_android 0.1.1 -> 0.7.2. This version requires explicit WORKSPACE setup: call rules_android_prereqs() and register its toolchains. It also runs ImportDepsChecker on aar_import targets, which the Bazel 7 built-in aar_import did not. - Add deps to the envoy_mobile_android aar_import (kotlin-stdlib, androidx, jetbrains-annotations) to satisfy ImportDepsChecker. - Bump kotlin-stdlib from 1.6.21 to 1.9.23 to match rules_kotlin v1.9.6's bundled compiler. The old version lacked kotlin.enums.* classes introduced in 1.8.20, causing ImportDepsChecker failures. - Add cherry-picked maven_install and go_repository deps from rules_android_workspace() (rules_android_maven, android_ide_common, bazel_worker_maven, rules_shell, Go repos). We cannot call rules_android_workspace() directly as it conflicts with envoy's existing Go and protobuf setup. - Replace android_common.multi_cpu_configuration (removed in Bazel 8) with android_split_transition from rules_android in android_debug_info.bzl. - Remove --fat_apk_cpu flags (deprecated in Bazel 8, replaced by --android_platforms which was already set). - Patch rules_kotlin to redirect android_sdk java_import from @bazel_tools//tools/android:android_jar (removed in Bazel 8) to @androidsdk//:platforms/android-30/android.jar. - Bump Android build-tools from 30.0.2 to 35.0.0 (required by rules_android v0.7.2). - Fix CronvoyEngineTest: assertThat(map.containsKey(k)) was a no-op (Truth's assertThat(boolean) without an assertion method). Changed to assertThat(map).containsKey(k), and fixed the stat name "http.hcm.downstream_total" -> "http.hcm.downstream_rq_total". - Remove android_xds_dist alias (target no longer exists). - Remove --swiftcopt=-wmo (incompatible with Bazel 8). Signed-off-by: Jonh Wendell Co-authored-by: Claude Opus 4.6 --- .bazelrc | 6 +- .bazelversion | 2 +- .github/config.yml | 18 +- .github/workflows/envoy-dependency.yml | 4 +- bazel/deps.yaml | 15 +- bazel/emsdk.patch | 660 ++---------------- bazel/repositories.bzl | 2 + bazel/repositories_extra.bzl | 2 + bazel/repository_locations.bzl | 4 +- bazel/rules_java.patch | 14 + bazel/rules_python.patch | 72 ++ bazel/tests/external/.bazelrc | 1 + ci/build_setup.sh | 2 +- compat/openssl/source/ossl_dlutil.c | 10 +- compat/openssl/source/ossl_dlutil.h | 2 +- mobile/.bazelrc | 6 - mobile/BUILD | 11 +- mobile/WORKSPACE | 9 +- mobile/bazel/android_debug_info.bzl | 13 +- mobile/bazel/envoy_mobile_dependencies.bzl | 138 +++- mobile/bazel/envoy_mobile_repositories.bzl | 16 +- mobile/bazel/rules_kotlin.patch | 12 + mobile/ci/linux_ci_setup.sh | 2 +- .../chromium/net/impl/CronvoyEngineTest.java | 6 +- test/config/integration/certs/certs.sh | 2 +- .../aws/signers/sigv4_signer_corpus_test.cc | 4 +- .../aws/signers/sigv4a_signer_corpus_test.cc | 4 +- tools/coverage/report_generator.sh.template | 18 +- tools/coverage/validate.sh | 14 + 29 files changed, 397 insertions(+), 672 deletions(-) create mode 100644 bazel/rules_python.patch create mode 100644 mobile/bazel/rules_kotlin.patch diff --git a/.bazelrc b/.bazelrc index 69fefca40a8bb..a9d01631e2b23 100644 --- a/.bazelrc +++ b/.bazelrc @@ -20,6 +20,10 @@ startup --host_jvm_args="-DBAZEL_TRACK_SOURCE_DIRECTORIES=1" ############################################################################# common --noenable_bzlmod +common --enable_workspace +common --noincompatible_disallow_empty_glob +common --noincompatible_disallow_ctx_resolve_tools +common --legacy_external_runfiles fetch --color=yes run --color=yes @@ -59,7 +63,7 @@ test --test_verbose_timeout_warnings test --experimental_ui_max_stdouterr_bytes=11712829 #default 1048576 # Allow tags to influence execution requirements -common --experimental_allow_tags_propagation +common --incompatible_allow_tags_propagation # Python common --@rules_python//python/config_settings:bootstrap_impl=script diff --git a/.bazelversion b/.bazelversion index 5942a0d3a0e74..df5119ec64e60 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -7.7.1 +8.7.0 diff --git a/.github/config.yml b/.github/config.yml index 69724c502ae72..c5288951050cb 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -4,16 +4,16 @@ build-image: repo: docker.io/envoyproxy/envoy-build repo-gcr: gcr.io/envoy-ci/envoy-build # default ci caching (ci) - sha: 58ec7bec0e349c7daff6a71c55fb05da62318637b13b647a4a0a96f02de28386 - sha-ci: 58ec7bec0e349c7daff6a71c55fb05da62318637b13b647a4a0a96f02de28386 - sha-devtools: 8ea83087c77726b02bc5b0eba79ce1ce94308c034833cd922a5ea0f3d5b1c610 - sha-docker: b622172bea807adb401306ed60b86b5a892e77b2cba871a384724f9082ad5667 - sha-gcc: 0adba37e34e6f42c45fa241e720c93197f5ed31d1dc76afd4880779933a91648 - sha-mobile: 406eb40441ab5cc2ac8e6e9f7330f618ce91a8163d5f9ecaad6d4bd7b1661164 - sha-worker: be284c733ef7aaba7c02b1a95e2c6760b09020840b99d9c3821ebe1293615c81 + sha: 1133b78586d5764929a46d024e74f5f9b200828d5b2c7e987377818f4a31cac2 + sha-ci: 1133b78586d5764929a46d024e74f5f9b200828d5b2c7e987377818f4a31cac2 + sha-devtools: 37d5b16264319c1d0991de02eb9b488016ed9b3c5a04df97e041b99a45d9ed39 + sha-docker: 588854d3e7e72a7ebd89a92f5ce99af687e6fdd77239afbe0b9ef6cf430c53fd + sha-gcc: 4b63777fd1fdf0c5b8d00a0f19840c28839ed2c85d1b8f51354c26e4516bcaf5 + sha-mobile: 70f936a9770ddb75943c760fe7a28deb53226cced31289412bb850ad8e7cb8de + sha-worker: e539b366b3385c83b1a9b6bda377d399caee596565419fab0cfac6993610177c # TODO: remove this dupe (currently used by ci request handler) - mobile-sha: 406eb40441ab5cc2ac8e6e9f7330f618ce91a8163d5f9ecaad6d4bd7b1661164 - tag: v0.1.4 + mobile-sha: 70f936a9770ddb75943c760fe7a28deb53226cced31289412bb850ad8e7cb8de + tag: v0.1.5 config: envoy: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index a43c345ea0704..97a0a933ef03c 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -185,7 +185,7 @@ jobs: # Source of truth: the latest `docker-v*` GitHub release in # envoyproxy/toolshed. Strip only the `docker-` prefix - the # published images keep the leading `v`, eg - # `envoyproxy/envoy-build:ci-v0.1.4`. + # `envoyproxy/envoy-build:ci-v0.1.5`. CONTAINER_TAG=$( gh api -H "Accept: application/vnd.github+json" \ --paginate \ @@ -198,7 +198,7 @@ jobs: echo "ERROR: Could not determine build image tag from envoyproxy/toolshed releases" >&2 exit 1 fi - # Normalize: accept `docker-v0.1.4`, `v0.1.4` or `0.1.4` from inputs.version + # Normalize: accept `docker-v0.1.5`, `v0.1.5` or `0.1.4` from inputs.version CONTAINER_TAG="${CONTAINER_TAG#docker-}" [[ "$CONTAINER_TAG" == v* ]] || CONTAINER_TAG="v${CONTAINER_TAG}" echo "tag=${CONTAINER_TAG}" >> "$GITHUB_OUTPUT" diff --git a/bazel/deps.yaml b/bazel/deps.yaml index 34e5dbd632f37..4b8fd3cb447ac 100644 --- a/bazel/deps.yaml +++ b/bazel/deps.yaml @@ -650,6 +650,7 @@ com_google_protobuf: - com_google_protobuf_protoc_osx_aarch_64 - com_google_protobuf_protoc_osx_x86_64 - com_google_protobuf_protoc_win64 + - zlib cpe: "cpe:2.3:a:google:protobuf:*" license: "Protocol Buffers" license_url: "https://github.com/protocolbuffers/protobuf/blob/v{version}/LICENSE" @@ -1281,9 +1282,15 @@ rules_java: project_name: "Java rules for Bazel" project_desc: "Bazel rules for the Java language" project_url: "https://github.com/bazelbuild/rules_java/" - release_date: "2025-03-25" + release_date: "2026-02-24" use_category: - build + - dataplane_core + - dataplane_ext + - observability_ext + - controlplane + - api + - other license: "Apache-2.0" license_url: "https://github.com/bazelbuild/rules_java/blob/{version}/LICENSE" @@ -1365,6 +1372,12 @@ rules_shell: release_date: "2026-03-23" use_category: - build + - dataplane_core + - dataplane_ext + - observability_ext + - controlplane + - api + - other license: "Apache-2.0" license_url: "https://github.com/protocolbuffers/rules_shell/blob/{version}/LICENSE" diff --git a/bazel/emsdk.patch b/bazel/emsdk.patch index b64feb7509fa2..ff5fad67f26a1 100644 --- a/bazel/emsdk.patch +++ b/bazel/emsdk.patch @@ -1,11 +1,40 @@ diff --git a/bazel/BUILD b/bazel/BUILD -index cb3135c..4d6d92c 100644 --- a/bazel/BUILD +++ b/bazel/BUILD -@@ -78,6 +78,18 @@ alias( +@@ -50,7 +50,8 @@ alias( + ":macos": "@emscripten_bin_mac//:compiler_files", + ":macos_arm64": "@emscripten_bin_mac_arm64//:compiler_files", + ":windows": "@emscripten_bin_win//:compiler_files", +- "//conditions:default": ":empty", ++ # Default to linux for WASM target platform builds ++ "//conditions:default": "@emscripten_bin_linux//:compiler_files", }), ) - + +@@ -62,7 +63,8 @@ alias( + ":macos": "@emscripten_bin_mac//:linker_files", + ":macos_arm64": "@emscripten_bin_mac_arm64//:linker_files", + ":windows": "@emscripten_bin_win//:linker_files", +- "//conditions:default": ":empty", ++ # Default to linux for WASM target platform builds ++ "//conditions:default": "@emscripten_bin_linux//:linker_files", + }), + ) + +@@ -74,7 +76,8 @@ alias( + ":macos": "@emscripten_bin_mac//:ar_files", + ":macos_arm64": "@emscripten_bin_mac_arm64//:ar_files", + ":windows": "@emscripten_bin_win//:ar_files", +- "//conditions:default": ":empty", ++ # Default to linux for WASM target platform builds ++ "//conditions:default": "@emscripten_bin_linux//:ar_files", + }), + ) + +@@ -78,6 +81,19 @@ alias( + }), + ) + +alias( + name = "dwp_files", + actual = select({ @@ -14,622 +43,39 @@ index cb3135c..4d6d92c 100644 + ":macos": "@emscripten_bin_mac//:dwp_files", + ":macos_arm64": "@emscripten_bin_mac_arm64//:dwp_files", + ":windows": "@emscripten_bin_win//:dwp_files", -+ "//conditions:default": ":empty", ++ # Default to linux for WASM target platform builds ++ "//conditions:default": "@emscripten_bin_linux//:dwp_files", + }), +) + platform( name = "platform_wasm", constraint_values = [ -diff --git a/bazel/emscripten_deps.bzl b/bazel/emscripten_deps.bzl -index 9d020bd..f466edb 100644 ---- a/bazel/emscripten_deps.bzl -+++ b/bazel/emscripten_deps.bzl -@@ -96,6 +96,13 @@ filegroup( - ], - ), - ) -+ -+filegroup( -+ name = "dwp_files", -+ srcs = [ -+ "bin/llvm-dwp{bin_extension}", -+ ], -+) - """ - - def emscripten_deps(emscripten_version = "latest"): -@@ -173,38 +180,3 @@ def emscripten_deps(emscripten_version = "latest"): - build_file_content = BUILD_FILE_CONTENT_TEMPLATE.format(bin_extension = ".exe"), - type = "zip", - ) -- -- if "emscripten_npm_linux" not in excludes: -- npm_translate_lock( -- name = "emscripten_npm_linux", -- data = ["@emscripten_bin_linux//:emscripten/package.json"], -- npm_package_lock = "@emscripten_bin_linux//:emscripten/package-lock.json", -- ) -- -- if "emscripten_npm_linux_arm64" not in excludes: -- npm_translate_lock( -- name = "emscripten_npm_linux_arm64", -- data = ["@emscripten_bin_linux_arm64//:emscripten/package.json"], -- npm_package_lock = "@emscripten_bin_linux_arm64//:emscripten/package-lock.json", -- ) -- -- if "emscripten_npm_mac" not in excludes: -- npm_translate_lock( -- name = "emscripten_npm_mac", -- data = ["@emscripten_bin_mac//:emscripten/package.json"], -- npm_package_lock = "@emscripten_bin_mac//:emscripten/package-lock.json", -- ) -- -- if "emscripten_npm_mac_arm64" not in excludes: -- npm_translate_lock( -- name = "emscripten_npm_mac", -- data = ["@emscripten_bin_mac_arm64//:emscripten/package.json"], -- npm_package_lock = "@emscripten_bin_mac_arm64//:emscripten/package-lock.json", -- ) -- -- if "emscripten_npm_win" not in excludes: -- npm_translate_lock( -- name = "emscripten_npm_win", -- data = ["@emscripten_bin_win//:emscripten/package.json"], -- npm_package_lock = "@emscripten_bin_win//:emscripten/package-lock.json", -- ) diff --git a/bazel/emscripten_toolchain/BUILD.bazel b/bazel/emscripten_toolchain/BUILD.bazel -index a989450..4f1c213 100644 --- a/bazel/emscripten_toolchain/BUILD.bazel +++ b/bazel/emscripten_toolchain/BUILD.bazel -@@ -1,3 +1,4 @@ -+load("@python3_12//:defs.bzl", "py_binary") - load(":toolchain.bzl", "emscripten_cc_toolchain_config_rule") - - package(default_visibility = ["//visibility:public"]) -@@ -9,6 +10,7 @@ filegroup( +@@ -8,6 +8,8 @@ filegroup( + "@emscripten_cache//:emscripten_config", "env.sh", "env.bat", - "@nodejs//:node_files", ++ # Python interpreter for emscripten scripts + "@python3_12//:files", + "@nodejs//:node_files", ], ) - -@@ -43,12 +45,25 @@ filegroup( - ], - ) - -+filegroup( -+ name = "dwp_files", -+ srcs = [ -+ "emdwp-emscripten_bin_linux_arm64.sh", -+ "emdwp-emscripten_bin_linux.sh", -+ "emdwp-emscripten_bin_mac_arm64.sh", -+ "emdwp-emscripten_bin_mac.sh", -+ "emdwp-emscripten_bin_win.bat", -+ "@emsdk//:dwp_files", -+ ], -+) -+ - filegroup( - name = "all_files", - srcs = [ - ":ar_files", - ":compiler_files", - ":linker_files", -+ ":dwp_files", - ], - ) - -@@ -63,9 +78,9 @@ emscripten_cc_toolchain_config_rule( - em_config = "@emscripten_cache//:emscripten_config", - emscripten_binaries = "@emsdk//:compiler_files", - nodejs_bin = "@nodejs//:node", -- script_extension = select({ -- "@bazel_tools//src/conditions:host_windows": "bat", -- "//conditions:default": "sh", -+ is_windows = select({ -+ "@bazel_tools//src/conditions:host_windows": True, -+ "//conditions:default": False, - }), - ) - -@@ -75,7 +90,7 @@ cc_toolchain( - ar_files = ":ar_files", - as_files = ":empty", - compiler_files = ":compiler_files", -- dwp_files = ":empty", -+ dwp_files = ":dwp_files", - linker_files = ":linker_files", - objcopy_files = ":empty", - strip_files = ":empty", -diff --git a/bazel/emscripten_toolchain/dwp.bat b/bazel/emscripten_toolchain/dwp.bat -new file mode 100644 -index 0000000..85ed4b5 ---- /dev/null -+++ b/bazel/emscripten_toolchain/dwp.bat -@@ -0,0 +1,4 @@ -+@echo off -+REM Dummy dwp script for emscripten toolchain -+REM Since wasm doesn't support split debug info, this is a no-op -+exit /b 0 -diff --git a/bazel/emscripten_toolchain/dwp.sh b/bazel/emscripten_toolchain/dwp.sh -new file mode 100644 -index 0000000..50a1d80 ---- /dev/null -+++ b/bazel/emscripten_toolchain/dwp.sh -@@ -0,0 +1,8 @@ -+#!/bin/bash -+ -+# Dummy dwp script for emscripten toolchain -+# Since wasm doesn't support split debug info, this is a no-op -+# Just echo the command for debugging and exit successfully -+ -+echo "DWP called with args: $@" >&2 -+exit 0 -diff --git a/bazel/emscripten_toolchain/emar.bat b/bazel/emscripten_toolchain/emar.bat -index b8e9125..844c378 100644 ---- a/bazel/emscripten_toolchain/emar.bat -+++ b/bazel/emscripten_toolchain/emar.bat -@@ -2,4 +2,4 @@ - - call external\emsdk\emscripten_toolchain\env.bat - --py -3 %EMSCRIPTEN%\emar.py %* -+%EMSDK_PYTHON% %EMSCRIPTEN%\emar.py %* -diff --git a/bazel/emscripten_toolchain/emar.sh b/bazel/emscripten_toolchain/emar.sh -index b4ead6e..d57222f 100755 ---- a/bazel/emscripten_toolchain/emar.sh -+++ b/bazel/emscripten_toolchain/emar.sh -@@ -2,4 +2,11 @@ - - source $(dirname $0)/env.sh - --exec python3 $EMSCRIPTEN/emar.py "$@" -+if [[ "$EMSDK_PYTHON" != /* ]]; then -+ EMSDK_PYTHON="$ROOT_DIR/$EMSDK_PYTHON" -+fi -+ -+PYBINPATH="$(dirname "${EMSDK_PYTHON}")" -+export PATH=$PYBINPATH:$PATH -+ -+exec $EMSDK_PYTHON $EMSCRIPTEN/emar.py "$@" -diff --git a/bazel/emscripten_toolchain/emcc.bat b/bazel/emscripten_toolchain/emcc.bat -index aba66f4..b302736 100644 ---- a/bazel/emscripten_toolchain/emcc.bat -+++ b/bazel/emscripten_toolchain/emcc.bat -@@ -2,4 +2,4 @@ - - call external\emsdk\emscripten_toolchain\env.bat - --py -3 %EMSCRIPTEN%\emcc.py %* -+%EMSDK_PYTHON% %EMSCRIPTEN%\emcc.py %* -diff --git a/bazel/emscripten_toolchain/emcc.sh b/bazel/emscripten_toolchain/emcc.sh -index 5fdaf9c..f0f36ab 100755 ---- a/bazel/emscripten_toolchain/emcc.sh -+++ b/bazel/emscripten_toolchain/emcc.sh -@@ -1,5 +1,12 @@ --#!/bin/bash -+#!/bin/bash -e - - source $(dirname $0)/env.sh - --exec python3 $EMSCRIPTEN/emcc.py "$@" -+if [[ "$EMSDK_PYTHON" != /* ]]; then -+ EMSDK_PYTHON="$ROOT_DIR/$EMSDK_PYTHON" -+fi -+ -+PYBINPATH="$(dirname "${EMSDK_PYTHON}")" -+export PATH=$PYBINPATH:$PATH -+ -+exec $EMSDK_PYTHON $EMSCRIPTEN/emcc.py "$@" -diff --git a/bazel/emscripten_toolchain/emcc_base.sh b/bazel/emscripten_toolchain/emcc_base.sh -new file mode 100755 -index 0000000..e9ddd2c ---- /dev/null -+++ b/bazel/emscripten_toolchain/emcc_base.sh -@@ -0,0 +1,7 @@ -+#!/bin/bash -+ -+source $(dirname $0)/env.sh -+ -+PYTHON3="${PYTHON3:-python3}" -+ -+exec $PYTHON3 $EMSCRIPTEN/emcc.py "$@" -diff --git a/bazel/emscripten_toolchain/emcc_link.bat b/bazel/emscripten_toolchain/emcc_link.bat -index 8e5a6eb..fef6501 100644 ---- a/bazel/emscripten_toolchain/emcc_link.bat -+++ b/bazel/emscripten_toolchain/emcc_link.bat -@@ -2,4 +2,4 @@ - - call external\emsdk\emscripten_toolchain\env.bat - --py -3 external\emsdk\emscripten_toolchain\link_wrapper.py %* -+%EMSDK_PYTHON% external\emsdk\emscripten_toolchain\link_wrapper.py %* -diff --git a/bazel/emscripten_toolchain/emcc_link.sh b/bazel/emscripten_toolchain/emcc_link.sh -index 44f3235..950e529 100755 ---- a/bazel/emscripten_toolchain/emcc_link.sh -+++ b/bazel/emscripten_toolchain/emcc_link.sh -@@ -2,4 +2,11 @@ - - source $(dirname $0)/env.sh - --exec python3 $(dirname $0)/link_wrapper.py "$@" -+if [[ "$EMSDK_PYTHON" != /* ]]; then -+ EMSDK_PYTHON="$ROOT_DIR/$EMSDK_PYTHON" -+fi -+ -+PYBINPATH="$(dirname "${EMSDK_PYTHON}")" -+export PATH=$PYBINPATH:$PATH -+ -+exec $EMSDK_PYTHON $(dirname $0)/link_wrapper.py "$@" -diff --git a/bazel/emscripten_toolchain/emdwp-emscripten_bin_linux.sh b/bazel/emscripten_toolchain/emdwp-emscripten_bin_linux.sh -new file mode 100755 -index 0000000..513feee ---- /dev/null -+++ b/bazel/emscripten_toolchain/emdwp-emscripten_bin_linux.sh -@@ -0,0 +1,10 @@ -+#!/bin/bash -+# -+# This script differs in form from emcc.{py,bat}/…, because bazel are limited/bugged in the way of executing dwp tool. -+# Bazel dwp action configuration does not pass environment variables, so we cannot use them in this script. -+# For more info, see PR discussion and bazel issue: -+# - https://github.com/emscripten-core/emsdk/pull/1531#discussion_r1962090650 -+# - https://github.com/bazelbuild/bazel/issues/25336 -+# -+ -+exec external/emscripten_bin_linux/bin/llvm-dwp "$@" -diff --git a/bazel/emscripten_toolchain/emdwp-emscripten_bin_linux_arm64.sh b/bazel/emscripten_toolchain/emdwp-emscripten_bin_linux_arm64.sh -new file mode 100755 -index 0000000..f6f3383 ---- /dev/null -+++ b/bazel/emscripten_toolchain/emdwp-emscripten_bin_linux_arm64.sh -@@ -0,0 +1,10 @@ -+#!/bin/bash -+# -+# This script differs in form from emcc.{py,bat}/…, because bazel are limited/bugged in the way of executing dwp tool. -+# Bazel dwp action configuration does not pass environment variables, so we cannot use them in this script. -+# For more info, see PR discussion and bazel issue: -+# - https://github.com/emscripten-core/emsdk/pull/1531#discussion_r1962090650 -+# - https://github.com/bazelbuild/bazel/issues/25336 -+# -+ -+exec external/emscripten_bin_linux_arm64/bin/llvm-dwp "$@" -diff --git a/bazel/emscripten_toolchain/emdwp-emscripten_bin_mac.sh b/bazel/emscripten_toolchain/emdwp-emscripten_bin_mac.sh -new file mode 100755 -index 0000000..0f97690 ---- /dev/null -+++ b/bazel/emscripten_toolchain/emdwp-emscripten_bin_mac.sh -@@ -0,0 +1,10 @@ -+#!/bin/bash -+# -+# This script differs in form from emcc.{py,bat}/…, because bazel are limited/bugged in the way of executing dwp tool. -+# Bazel dwp action configuration does not pass environment variables, so we cannot use them in this script. -+# For more info, see PR discussion and bazel issue: -+# - https://github.com/emscripten-core/emsdk/pull/1531#discussion_r1962090650 -+# - https://github.com/bazelbuild/bazel/issues/25336 -+# -+ -+exec external/emscripten_bin_mac/bin/llvm-dwp "$@" -diff --git a/bazel/emscripten_toolchain/emdwp-emscripten_bin_mac_arm64.sh b/bazel/emscripten_toolchain/emdwp-emscripten_bin_mac_arm64.sh -new file mode 100755 -index 0000000..4ae033c ---- /dev/null -+++ b/bazel/emscripten_toolchain/emdwp-emscripten_bin_mac_arm64.sh -@@ -0,0 +1,10 @@ -+#!/bin/bash -+# -+# This script differs in form from emcc.{py,bat}/…, because bazel are limited/bugged in the way of executing dwp tool. -+# Bazel dwp action configuration does not pass environment variables, so we cannot use them in this script. -+# For more info, see PR discussion and bazel issue: -+# - https://github.com/emscripten-core/emsdk/pull/1531#discussion_r1962090650 -+# - https://github.com/bazelbuild/bazel/issues/25336 -+# -+ -+exec external/emscripten_bin_mac_arm64/bin/llvm-dwp "$@" -diff --git a/bazel/emscripten_toolchain/emdwp-emscripten_bin_win.bat b/bazel/emscripten_toolchain/emdwp-emscripten_bin_win.bat -new file mode 100644 -index 0000000..3cb1f2e ---- /dev/null -+++ b/bazel/emscripten_toolchain/emdwp-emscripten_bin_win.bat -@@ -0,0 +1,10 @@ -+:: -+:: This script differs in form from emcc.{py,bat}/…, because bazel are limited/bugged in the way of executing dwp tool. -+:: Bazel dwp action configuration does not pass environment variables, so we cannot use them in this script. -+:: For more info, see PR discussion and bazel issue: -+:: - https://github.com/emscripten-core/emsdk/pull/1531#discussion_r1962090650 -+:: - https://github.com/bazelbuild/bazel/issues/25336 -+:: -+@ECHO OFF -+ -+call external\emscripten_bin_win\bin\llvm-dwp %* -diff --git a/bazel/emscripten_toolchain/link_wrapper.py b/bazel/emscripten_toolchain/link_wrapper.py -index 6a6fe2f..6dcacf8 100644 ---- a/bazel/emscripten_toolchain/link_wrapper.py -+++ b/bazel/emscripten_toolchain/link_wrapper.py -@@ -1,4 +1,3 @@ --#!/usr/bin/env python - """wrapper around emcc link step. - - This wrapper currently serves the following purposes. -diff --git a/bazel/emscripten_toolchain/toolchain.bzl b/bazel/emscripten_toolchain/toolchain.bzl -index c8cec07..9559cd1 100644 ---- a/bazel/emscripten_toolchain/toolchain.bzl -+++ b/bazel/emscripten_toolchain/toolchain.bzl -@@ -54,6 +54,11 @@ CROSSTOOL_DEFAULT_WARNINGS = [ - "-Wall", - ] - -+def _os_path(ctx, path): -+ if ctx.attr.is_windows: -+ path = path.replace("/", "\\") -+ return path -+ - def _impl(ctx): - target_cpu = ctx.attr.cpu - toolchain_identifier = "emscripten-" + target_cpu -@@ -72,12 +77,15 @@ def _impl(ctx): - - emscripten_dir = ctx.attr.emscripten_binaries.label.workspace_root - nodejs_path = ctx.file.nodejs_bin.path -+ emscripten_name = ctx.attr.emscripten_binaries.label.workspace_name - - builtin_sysroot = emscripten_dir + "/emscripten/cache/sysroot" - -- emcc_script = "emcc.%s" % ctx.attr.script_extension -- emcc_link_script = "emcc_link.%s" % ctx.attr.script_extension -- emar_script = "emar.%s" % ctx.attr.script_extension -+ script_extension = "bat" if ctx.attr.is_windows else "sh" -+ emcc_script = "emcc.%s" % script_extension -+ emcc_link_script = "emcc_link.%s" % script_extension -+ emar_script = "emar.%s" % script_extension -+ emdwp_script = "emdwp-%s.%s" % (emscripten_name, script_extension) - - ################################################################ - # Tools -@@ -99,6 +107,7 @@ def _impl(ctx): - tool_path(name = "nm", path = "NOT_USED"), - tool_path(name = "objdump", path = "/bin/false"), - tool_path(name = "strip", path = "NOT_USED"), -+ tool_path(name = "dwp", path = emdwp_script), - ] - - ################################################################ -@@ -460,6 +469,49 @@ def _impl(ctx): - feature( - name = "wasm_standalone", - ), -+ # Support for debug fission. In short, debugging fission should: -+ # * reduce linking time, RAM usage and disk usage -+ # * speed up incremental builds -+ # * speed up debugger work (reduce startup and breakpoint time) -+ # (to use this, follow the --fission=yes flag) -+ # https://developer.chrome.com/blog/faster-wasm-debugging -+ # https://bazel.build/docs/user-manual#fission -+ feature( -+ name = "per_object_debug_info", -+ flag_sets = [ -+ flag_set( -+ actions = [ -+ ACTION_NAMES.c_compile, -+ ACTION_NAMES.cpp_compile, -+ ACTION_NAMES.cpp_module_codegen, -+ ACTION_NAMES.assemble, -+ ACTION_NAMES.preprocess_assemble, -+ ], -+ flag_groups = [ -+ flag_group( -+ flags = ["-g", "-gsplit-dwarf", "-gdwarf-5", "-gpubnames"], -+ expand_if_available = "per_object_debug_info_file", -+ ), -+ ], -+ ), -+ ], -+ enabled = True, -+ ), -+ feature( -+ name = "fission_support", -+ flag_sets = [ -+ flag_set( -+ actions = all_link_actions, -+ flag_groups = [ -+ flag_group( -+ flags = ["-sWASM_BIGINT"], # WASM_BIGINT required to support dwarf-5 -+ expand_if_available = "is_using_fission", -+ ), -+ ], -+ ), -+ ], -+ enabled = True, -+ ) - ] - - crosstool_default_flag_sets = [ -@@ -1046,6 +1098,16 @@ def _impl(ctx): - ), - ] - -+ python_files = ctx.attr._python_interpreter[DefaultInfo].files.to_list() -+ python_bin_path = None -+ for f in python_files: -+ if f.basename == "python3" or f.path.endswith("/bin/python3"): -+ python_bin_path = f.path -+ break -+ -+ if not python_bin_path: -+ fail("Could not find python3 binary in " + str(ctx.attr._python_interpreter)) -+ - crosstool_default_env_sets = [ - # Globals - env_set( -@@ -1065,6 +1127,10 @@ def _impl(ctx): - key = "NODE_JS_PATH", - value = nodejs_path, - ), -+ env_entry( -+ key = "EMSDK_PYTHON", -+ value = _os_path(ctx, python_bin_path), -+ ), - ], - ), - # Use llvm backend. Off by default, enabled via --features=llvm_backend -@@ -1140,7 +1206,8 @@ emscripten_cc_toolchain_config_rule = rule( - "em_config": attr.label(mandatory = True, allow_single_file = True), - "emscripten_binaries": attr.label(mandatory = True, cfg = "exec"), - "nodejs_bin": attr.label(mandatory = True, allow_single_file = True), -- "script_extension": attr.string(mandatory = True, values = ["sh", "bat"]), -+ "is_windows": attr.bool(mandatory = True), -+ "_python_interpreter": attr.label(cfg = "exec", default = Label("@python3_12//:files")), - }, - provides = [CcToolchainConfigInfo], - ) -diff --git a/bazel/emscripten_toolchain/wasm_binary.py b/bazel/emscripten_toolchain/wasm_binary.py -index d7d6142..0da7f55 100644 ---- a/bazel/emscripten_toolchain/wasm_binary.py -+++ b/bazel/emscripten_toolchain/wasm_binary.py -@@ -14,6 +14,7 @@ WebAssembly binary into a larger web application. - import argparse - import os - import tarfile -+import shutil - - - def ensure(f): -@@ -26,11 +27,20 @@ def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--archive', help='The archive to extract from.') - parser.add_argument('--outputs', help='Comma separated list of files that should be extracted from the archive. Only the extname has to match a file in the archive.') -+ parser.add_argument('--dwp_file', help='Optional dwp input file, generated when fission flags set.') - parser.add_argument('--allow_empty_outputs', help='If an output listed in --outputs does not exist, create it anyways.', action='store_true') - args = parser.parse_args() - - args.archive = os.path.normpath(args.archive) - args.outputs = args.outputs.split(",") -+ args.dwp_file = os.path.normpath(args.dwp_file) if args.dwp_file else None -+ -+ if args.dwp_file: -+ for idx, output in enumerate(args.outputs): -+ if output.endswith(".dwp"): # also update extension 'binary.dwp' to 'binary.wasm.dwp' -+ shutil.copy2(args.dwp_file, output) -+ args.outputs.pop(idx) +diff --git a/bazel/emscripten_toolchain/env.sh b/bazel/emscripten_toolchain/env.sh +--- a/bazel/emscripten_toolchain/env.sh ++++ b/bazel/emscripten_toolchain/env.sh +@@ -3,3 +3,12 @@ + export ROOT_DIR=${EXT_BUILD_ROOT:-$(pwd -P)} + export EMSCRIPTEN=$ROOT_DIR/$EM_BIN_PATH/emscripten + export EM_CONFIG=$ROOT_DIR/$EM_CONFIG_PATH ++ ++# Add Python to PATH for remote execution environments ++# Find Python from rules_python toolchain (supports multiple platform names) ++for python_dir in "$ROOT_DIR"/external/python3_*; do ++ if [ -x "$python_dir/bin/python3" ]; then ++ export PATH="$python_dir/bin:$PATH" + break - - tar = tarfile.open(args.archive) - -diff --git a/bazel/emscripten_toolchain/wasm_cc_binary.bzl b/bazel/emscripten_toolchain/wasm_cc_binary.bzl -index 6ea4f12..fcc3391 100644 ---- a/bazel/emscripten_toolchain/wasm_cc_binary.bzl -+++ b/bazel/emscripten_toolchain/wasm_cc_binary.bzl -@@ -69,6 +69,7 @@ _ALLOW_OUTPUT_EXTNAMES = [ - ".fetch.js", - ".js.symbols", - ".wasm.debug.wasm", -+ ".wasm.dwp", - ".html", - ".aw.js", - ] -@@ -107,10 +108,11 @@ _WASM_BINARY_COMMON_ATTRS = { - } - - def _wasm_cc_binary_impl(ctx): -- args = ctx.actions.args() - cc_target = ctx.attr.cc_target[0] -+ dwp_file = cc_target[DebugPackageInfo].dwp_file if DebugPackageInfo in cc_target else None -+ outputs = ctx.outputs.outputs - -- for output in ctx.outputs.outputs: -+ for output in outputs: - valid_extname = False - for allowed_extname in _ALLOW_OUTPUT_EXTNAMES: - if output.path.endswith(allowed_extname): -@@ -119,28 +121,35 @@ def _wasm_cc_binary_impl(ctx): - if not valid_extname: - fail("Invalid output '{}'. Allowed extnames: {}".format(output.basename, ", ".join(_ALLOW_OUTPUT_EXTNAMES))) - -+ inputs = ctx.files.cc_target -+ args = ctx.actions.args() - args.add_all("--archive", ctx.files.cc_target) -- args.add_joined("--outputs", ctx.outputs.outputs, join_with = ",") -+ args.add_joined("--outputs", outputs, join_with = ",") -+ -+ if dwp_file: -+ args.add("--dwp_file", dwp_file) -+ inputs = inputs + [dwp_file] - - ctx.actions.run( -- inputs = ctx.files.cc_target, -- outputs = ctx.outputs.outputs, -+ inputs = inputs, -+ outputs = outputs, - arguments = [args], - executable = ctx.executable._wasm_binary_extractor, - ) - - return [ - DefaultInfo( -- files = depset(ctx.outputs.outputs), -+ files = depset(outputs), - # This is needed since rules like web_test usually have a data - # dependency on this target. -- data_runfiles = ctx.runfiles(transitive_files = depset(ctx.outputs.outputs)), -+ data_runfiles = ctx.runfiles(transitive_files = depset(outputs)), - ), - OutputGroupInfo(_wasm_tar = cc_target.files), - ] - - def _wasm_cc_binary_legacy_impl(ctx): - cc_target = ctx.attr.cc_target[0] -+ dwp_file = cc_target[DebugPackageInfo].dwp_file if DebugPackageInfo in cc_target else None - outputs = [ - ctx.outputs.loader, - ctx.outputs.wasm, -@@ -151,20 +160,31 @@ def _wasm_cc_binary_legacy_impl(ctx): - ctx.outputs.data, - ctx.outputs.symbols, - ctx.outputs.dwarf, -+ ctx.outputs.dwp, - ctx.outputs.html, - ctx.outputs.audio_worklet, - ] - -+ inputs = ctx.files.cc_target - args = ctx.actions.args() - args.add("--allow_empty_outputs") - args.add_all("--archive", ctx.files.cc_target) - args.add_joined("--outputs", outputs, join_with = ",") - -+ if dwp_file: -+ args.add("--dwp_file", dwp_file) -+ inputs = inputs + [dwp_file] -+ -+ py_toolchain = ctx.toolchains["@rules_python//python:toolchain_type"] -+ python_path = py_toolchain.py3_runtime.interpreter.path -+ env = dict(ctx.configuration.default_shell_env) -+ env["PATH"] = "%s:/usr/bin:/bin" % python_path.rpartition("/")[0] - ctx.actions.run( -- inputs = ctx.files.cc_target, -+ inputs = inputs + py_toolchain.py3_runtime.files.to_list(), - outputs = outputs, -- arguments = [args], - executable = ctx.executable._wasm_binary_extractor, -+ arguments = [args], -+ env = env, - ) - - return [ -@@ -202,6 +222,7 @@ def _wasm_binary_legacy_outputs(name, cc_target): - "data": "{}/{}.data".format(name, basename), - "symbols": "{}/{}.js.symbols".format(name, basename), - "dwarf": "{}/{}.wasm.debug.wasm".format(name, basename), -+ "dwp": "{}/{}.wasm.dwp".format(name, basename), - "html": "{}/{}.html".format(name, basename), - "audio_worklet": "{}/{}.aw.js".format(name, basename) - } -@@ -212,6 +233,7 @@ _wasm_cc_binary_legacy = rule( - implementation = _wasm_cc_binary_legacy_impl, - attrs = _WASM_BINARY_COMMON_ATTRS, - outputs = _wasm_binary_legacy_outputs, -+ toolchains = ["@rules_python//python:toolchain_type"], - ) - - # Wraps a C++ Blaze target, extracting the appropriate files. ++ fi ++done diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 7df0b1c741fd4..4d2155df2c1f6 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -724,6 +724,8 @@ def _abseil_cpp(): def _com_google_protobuf(): external_http_archive( name = "rules_python", + patch_args = ["-p1"], + patches = ["@envoy//bazel:rules_python.patch"], ) external_http_archive( name = "rules_java", diff --git a/bazel/repositories_extra.bzl b/bazel/repositories_extra.bzl index fbc508127dc19..6e344551734b4 100644 --- a/bazel/repositories_extra.bzl +++ b/bazel/repositories_extra.bzl @@ -5,6 +5,7 @@ load("@envoy_toolshed//compile:libcxx_libs.bzl", "setup_libcxx_libs") load("@envoy_toolshed//sysroot:sysroot.bzl", "setup_sysroots") load("@proxy_wasm_cpp_host//bazel/cargo/wasmtime/remote:crates.bzl", "crate_repositories") load("@rules_cc//cc:extensions.bzl", "compatibility_proxy_repo") +load("@rules_java//java:rules_java_deps.bzl", java_compatibility_proxy_repo = "compatibility_proxy_repo") load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains") load("@toolchains_llvm//toolchain:deps.bzl", "bazel_toolchain_dependencies") load("//bazel/external/cargo:crates.bzl", "raze_fetch_remote_crates") @@ -24,6 +25,7 @@ def envoy_dependencies_extra( python_version = PYTHON_VERSION, ignore_root_user_error = False): compatibility_proxy_repo() + java_compatibility_proxy_repo() bazel_toolchain_dependencies() setup_libcxx_libs() setup_sysroots(glibc_version = glibc_version) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 4cf42c5e199f3..3c00ccbe506cb 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -467,8 +467,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/bazelbuild/rules_foreign_cc/archive/{version}.tar.gz"], ), rules_java = dict( - version = "7.12.5", - sha256 = "17b18cb4f92ab7b94aa343ce78531b73960b1bed2ba166e5b02c9fdf0b0ac270", + version = "9.6.1", + sha256 = "9de4e178c2c4f98d32aafe5194c3f2b717ae10405caa11bdcb460ac2a6f61516", urls = ["https://github.com/bazelbuild/rules_java/releases/download/{version}/rules_java-{version}.tar.gz"], ), rules_python = dict( diff --git a/bazel/rules_java.patch b/bazel/rules_java.patch index 5aefcfdca1d00..96132e20435d0 100644 --- a/bazel/rules_java.patch +++ b/bazel/rules_java.patch @@ -1,3 +1,17 @@ +diff --git a/MODULE.bazel b/MODULE.bazel +--- a/MODULE.bazel ++++ b/MODULE.bazel +@@ -105,10 +105,6 @@ + + [register_toolchains("@" + name + "_toolchain_config_repo//:all") for name in REMOTE_JDK_REPOS] + +-# Compatibility layer +-compat = use_extension("//java:rules_java_deps.bzl", "compatibility_proxy") +-use_repo(compat, "compatibility_proxy") +- + # Dev dependencies + bazel_dep(name = "rules_pkg", version = "0.9.1", dev_dependency = True) + bazel_dep(name = "stardoc", version = "0.8.0", dev_dependency = True) diff --git a/java/repositories.bzl b/java/repositories.bzl index 27f4f85..15b5306 100644 --- a/java/repositories.bzl diff --git a/bazel/rules_python.patch b/bazel/rules_python.patch new file mode 100644 index 0000000000000..96b3e24266668 --- /dev/null +++ b/bazel/rules_python.patch @@ -0,0 +1,72 @@ +--- a/python/private/toolchain_aliases.bzl ++++ b/python/private/toolchain_aliases.bzl +@@ -43,6 +43,16 @@ + visibility = ["//visibility:private"], + ) + ++ # Use a sensible default platform for unsupported target platforms (e.g., WASI). ++ # Prefer x86_64-unknown-linux-gnu as it's the most common CI/exec platform. ++ # This allows builds to proceed when Python is transitively depended on ++ # but not actually used in the target configuration. ++ default_platform = None ++ if "x86_64-unknown-linux-gnu" in platforms: ++ default_platform = "x86_64-unknown-linux-gnu" ++ elif platforms: ++ default_platform = platforms[0] ++ + prefix = name + for name in [ + "files", +@@ -52,29 +62,39 @@ + "python_headers", + "python_runtimes", + ]: ++ select_dict = { ++ ":" + platform: "@{}_{}//:{}".format(prefix, platform, name) ++ for platform in platforms ++ } ++ if default_platform: ++ select_dict["//conditions:default"] = "@{}_{}//:{}".format(prefix, default_platform, name) + native.alias( + name = name, +- actual = select({ +- ":" + platform: "@{}_{}//:{}".format(prefix, platform, name) +- for platform in platforms +- }), ++ actual = select(select_dict), + visibility = visibility, + ) + ++ python3_select_dict = { ++ ":" + platform: "@{}_{}//:{}".format(prefix, platform, "python.exe" if "windows" in platform else "bin/python3") ++ for platform in platforms ++ } ++ if default_platform: ++ python3_select_dict["//conditions:default"] = "@{}_{}//:bin/python3".format(prefix, default_platform) + native.alias( + name = "python3", +- actual = select({ +- ":" + platform: "@{}_{}//:{}".format(prefix, platform, "python.exe" if "windows" in platform else "bin/python3") +- for platform in platforms +- }), ++ actual = select(python3_select_dict), + visibility = visibility, + ) ++ ++ pip_select_dict = { ++ ":" + platform: "@{}_{}//:python_runtimes".format(prefix, platform) ++ for platform in platforms ++ if "windows" not in platform ++ } ++ if default_platform and "windows" not in default_platform: ++ pip_select_dict["//conditions:default"] = "@{}_{}//:python_runtimes".format(prefix, default_platform) + native.alias( + name = "pip", +- actual = select({ +- ":" + platform: "@{}_{}//:python_runtimes".format(prefix, platform) +- for platform in platforms +- if "windows" not in platform +- }), ++ actual = select(pip_select_dict), + visibility = visibility, + ) diff --git a/bazel/tests/external/.bazelrc b/bazel/tests/external/.bazelrc index e9d344297f6a0..1efd2066f6ada 100644 --- a/bazel/tests/external/.bazelrc +++ b/bazel/tests/external/.bazelrc @@ -15,6 +15,7 @@ run --color=yes common --enable_workspace common --noenable_bzlmod +common --noincompatible_disallow_empty_glob build --workspace_status_command="bash get_workspace_status" build --incompatible_strict_action_env diff --git a/ci/build_setup.sh b/ci/build_setup.sh index 81c418340d090..e07e654cd8a2c 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -114,7 +114,7 @@ BAZEL_BUILD_OPTIONS=( "${BAZEL_OPTIONS[@]}" "${BAZEL_GLOBAL_OPTIONS[@]}" "--verbose_failures" - "--experimental_generate_json_trace_profile" + "--generate_json_trace_profile" "${BAZEL_BUILD_EXTRA_OPTIONS[@]}" "${BAZEL_EXTRA_TEST_OPTIONS[@]}") diff --git a/compat/openssl/source/ossl_dlutil.c b/compat/openssl/source/ossl_dlutil.c index c9f485b3df3d1..f071ee71acf5f 100755 --- a/compat/openssl/source/ossl_dlutil.c +++ b/compat/openssl/source/ossl_dlutil.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "log.h" #include "ossl_dlutil.h" @@ -41,10 +42,15 @@ void ossl_dlopen(int expected_major, int expected_minor) { char ossl_path[PATH_MAX]; char temp_path[PATH_MAX]; - // Prefix libcrypto_path and libssl_path with the absolute path to the - // OpenSSL shared library directory under bazel's runfiles directory. + // Bazel 8 places external repo runfiles directly under the runfiles root + // (e.g. {RUNFILES_DIR}/openssl/...), while Bazel 7 nests them under the + // workspace (e.g. {RUNFILES_DIR}/{TEST_WORKSPACE}/external/openssl/...). snprintf(ossl_path, sizeof(ossl_path), "%s/%s/external/openssl/openssl/lib", runfiles_dir, test_workspace); + if (access(ossl_path, F_OK) != 0) { + snprintf(ossl_path, sizeof(ossl_path), "%s/openssl/openssl/lib", runfiles_dir); + } + strcpy(temp_path, libcrypto_path); snprintf(libcrypto_path, sizeof(libcrypto_path), "%s/%s", ossl_path, temp_path); diff --git a/compat/openssl/source/ossl_dlutil.h b/compat/openssl/source/ossl_dlutil.h index 605069e1a6c19..d333820d2265e 100644 --- a/compat/openssl/source/ossl_dlutil.h +++ b/compat/openssl/source/ossl_dlutil.h @@ -11,7 +11,7 @@ * * 1. **Bazel build/test environment** (When RUNFILES_DIR & TEST_WORKSPACE are set): * - OpenSSL libraries are built by Bazel and put in the runfiles directory as data dependencies - * - Libraries are loaded from: $RUNFILES_DIR/$TEST_WORKSPACE/external/openssl/openssl/lib/ + * - Libraries are loaded from the runfiles directory (trying both Bazel 7 and 8 layouts) * - Ensures the tests always use the correct Bazel-built libs, rather than libs from elsewhere * * 2. **Production/system environment** (When RUNFILES_DIR & TEST_WORKSPACE are not set): diff --git a/mobile/.bazelrc b/mobile/.bazelrc index a3078d99a573c..e890ee62e43bc 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -36,9 +36,6 @@ build --define=envoy_full_protos=disabled build --define envoy_exceptions=disabled build: --copt=-fno-exceptions -# We don't have a ton of Swift in Envoy Mobile, so always build with WMO -# This also helps work around a bug in rules_swift: https://github.com/bazelbuild/rules_swift/issues/949 -build --swiftcopt=-wmo # https://github.com/bazelbuild/rules_jvm_external/issues/445 build --repo_env=JAVA_HOME=../bazel_tools/jdk build --define disable_known_issue_asserts=true @@ -77,7 +74,6 @@ build:android --java_language_version=8 build:android --tool_java_language_version=8 # Default flags for Android -build:mobile-android --incompatible_enable_android_toolchain_resolution build:mobile-android --config=mobile-clang build:mobile-android --java_language_version=8 build:mobile-android --tool_java_language_version=8 @@ -91,12 +87,10 @@ build:mobile-android-release --config=mobile-release-common build:mobile-android-release --compilation_mode=opt build:mobile-android-release --linkopt=-fuse-ld=lld build:mobile-android-release --android_platforms=@envoy//bazel/platforms/android:x86_64 -build:mobile-android-release --fat_apk_cpu=x86_64 build:mobile-android-publish --config=mobile-android-release build:mobile-android-publish --config=mobile-release build:mobile-android-publish --android_platforms=@envoy//bazel/platforms/android:x86,@envoy//bazel/platforms/android:x86_64,@envoy//bazel/platforms/android:armeabi-v7a,@envoy//bazel/platforms/android::arm64-v8a -build:mobile-android-publish --fat_apk_cpu=x86,x86_64,armeabi-v7a,arm64-v8a ############################################################################# diff --git a/mobile/BUILD b/mobile/BUILD index 8cca5618955bc..d245740c2dbd1 100644 --- a/mobile/BUILD +++ b/mobile/BUILD @@ -58,6 +58,12 @@ aar_import( name = "envoy_mobile_android", aar = "//library/kotlin/io/envoyproxy/envoymobile:envoy_aar", visibility = ["//visibility:public"], + deps = [ + "@maven//:androidx_annotation_annotation", + "@maven//:androidx_core_core", + "@maven//:org_jetbrains_annotations", + "@maven//:org_jetbrains_kotlin_kotlin_stdlib", + ], ) alias( @@ -65,11 +71,6 @@ alias( actual = "//library/kotlin/io/envoyproxy/envoymobile:envoy_aar_with_artifacts", ) -alias( - name = "android_xds_dist", - actual = "//library/kotlin/io/envoyproxy/envoymobile:envoy_xds_aar_with_artifacts", -) - define_kt_toolchain( name = "kotlin_toolchain", jvm_target = "1.8", diff --git a/mobile/WORKSPACE b/mobile/WORKSPACE index abc7e68b5ac0f..88d5928ae40b2 100644 --- a/mobile/WORKSPACE +++ b/mobile/WORKSPACE @@ -61,6 +61,10 @@ load("@envoy//bazel:toolchains.bzl", "envoy_toolchains") envoy_toolchains() +load("@rules_android//:prereqs.bzl", "rules_android_prereqs") + +rules_android_prereqs() + load("@envoy_mobile//bazel:envoy_mobile_dependencies.bzl", "envoy_mobile_dependencies") envoy_mobile_dependencies() @@ -93,7 +97,7 @@ load("//bazel:android_configure.bzl", "android_configure") android_configure( name = "local_config_android", - build_tools_version = "30.0.2", + build_tools_version = "35.0.0", ndk_api_level = 23, sdk_api_level = 30, ) @@ -102,6 +106,9 @@ load("@local_config_android//:android_configure.bzl", "android_workspace") android_workspace() +register_toolchains("@rules_android//toolchains/android:all") +register_toolchains("@rules_android//toolchains/android_sdk:all") + load("@com_github_buildbuddy_io_rules_xcodeproj//xcodeproj:repositories.bzl", "xcodeproj_rules_dependencies") xcodeproj_rules_dependencies() diff --git a/mobile/bazel/android_debug_info.bzl b/mobile/bazel/android_debug_info.bzl index 313e25c177fcb..ab35777937979 100644 --- a/mobile/bazel/android_debug_info.bzl +++ b/mobile/bazel/android_debug_info.bzl @@ -9,6 +9,7 @@ But even if we could create those we'd need to get them out of the build somehow, this rule provides a separate --output_group for this """ +load("@rules_android//rules:android_split_transition.bzl", "android_split_transition") load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") @@ -16,13 +17,12 @@ def _impl(ctx): library_outputs = [] objdump_outputs = [] for platform, dep in ctx.split_attr.dep.items(): - # When --fat_apk_cpu isn't set, the platform is None if len(dep.files.to_list()) != 1: fail("Expected exactly one file in the library") cc_toolchain = ctx.split_attr._cc_toolchain[platform][cc_common.CcToolchainInfo] lib = dep.files.to_list()[0] - platform_name = platform or ctx.fragments.android.android_cpu + platform_name = platform or "default" objdump_output = ctx.actions.declare_file(platform_name + "/" + platform_name + ".objdump.gz") ctx.actions.run_shell( @@ -55,11 +55,14 @@ android_debug_info = rule( attrs = dict( dep = attr.label( providers = [CcInfo], - cfg = android_common.multi_cpu_configuration, + cfg = android_split_transition, ), _cc_toolchain = attr.label( - default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), - cfg = android_common.multi_cpu_configuration, + default = Label("@rules_cc//cc:current_cc_toolchain"), + cfg = android_split_transition, + ), + _allowlist_function_transition = attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", ), ), fragments = ["cpp", "android"], diff --git a/mobile/bazel/envoy_mobile_dependencies.bzl b/mobile/bazel/envoy_mobile_dependencies.bzl index 947443391d113..25045669878f6 100644 --- a/mobile/bazel/envoy_mobile_dependencies.bzl +++ b/mobile/bazel/envoy_mobile_dependencies.bzl @@ -1,3 +1,4 @@ +load("@bazel_gazelle//:deps.bzl", "go_repository") load("@build_bazel_apple_support//lib:repositories.bzl", "apple_support_dependencies") load("@build_bazel_rules_apple//apple:repositories.bzl", "apple_rules_dependencies") load("@build_bazel_rules_swift//swift:repositories.bzl", "swift_rules_dependencies") @@ -10,6 +11,7 @@ load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") load("@rules_proto//proto:toolchains.bzl", "rules_proto_toolchains") load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains") load("@rules_python//python:pip.bzl", "pip_parse") +load("@rules_shell//shell:repositories.bzl", "rules_shell_dependencies", "rules_shell_toolchains") def _default_extra_swift_sources_impl(ctx): ctx.file("WORKSPACE", "") @@ -60,6 +62,107 @@ def swift_dependencies(): def kotlin_dependencies(extra_maven_dependencies = []): rules_java_dependencies() + maven_install( + name = "rules_android_maven", + artifacts = [ + "androidx.privacysandbox.tools:tools:1.0.0-alpha06", + "androidx.privacysandbox.tools:tools-apigenerator:1.0.0-alpha06", + "androidx.privacysandbox.tools:tools-apipackager:1.0.0-alpha06", + "androidx.test:core:1.6.0-alpha01", + "androidx.test.ext:junit:1.2.0-alpha01", + "com.android.tools.apkdeployer:apkdeployer:8.11.0-alpha10", + "com.android.tools.build:bundletool:1.18.2", + "com.android.tools:desugar_jdk_libs_minimal:2.1.5", + "com.android.tools:desugar_jdk_libs_configuration_minimal:2.1.5", + "com.android.tools:desugar_jdk_libs_nio:2.1.5", + "com.android.tools:desugar_jdk_libs_configuration_nio:2.1.5", + "com.android.tools:desugar_jdk_libs_configuration:2.1.5", + "com.android.tools:r8:8.9.35", + "org.bouncycastle:bcprov-jdk18on:1.77", + "org.hamcrest:hamcrest-core:2.2", + "org.robolectric:robolectric:4.14.1", + "com.google.flogger:flogger:0.8", + "com.google.flogger:flogger-system-backend:0.8", + "com.google.guava:guava:32.1.2-jre", + "com.google.guava:failureaccess:1.0.1", + "info.picocli:picocli:4.7.4", + "jakarta.inject:jakarta.inject-api:2.0.1", + "junit:junit:4.13.2", + "com.beust:jcommander:1.82", + "com.google.protobuf:protobuf-java:4.33.4", + "com.google.protobuf:protobuf-java-util:4.33.4", + "com.google.code.findbugs:jsr305:3.0.2", + "androidx.databinding:databinding-compiler:8.7.0", + "org.ow2.asm:asm:9.6", + "org.ow2.asm:asm-commons:9.6", + "org.ow2.asm:asm-tree:9.6", + "org.ow2.asm:asm-util:9.6", + "com.android:zipflinger:8.7.0", + "com.android.tools.build:gradle:8.7.0", + "com.android:signflinger:8.7.0", + "com.android.tools.build:aapt2-proto:8.6.1-11315950", + "com.android.tools.build:apksig:8.7.0", + "com.android.tools.build:apkzlib:8.7.0", + "com.google.auto.value:auto-value:1.11.0", + "com.google.auto.value:auto-value-annotations:1.11.0", + "com.google.auto:auto-common:1.2.2", + "com.google.auto.service:auto-service:1.1.1", + "com.google.auto.service:auto-service-annotations:1.1.1", + "com.google.errorprone:error_prone_annotations:2.33.0", + "com.google.errorprone:error_prone_type_annotations:2.33.0", + "com.google.errorprone:error_prone_check_api:2.33.0", + "com.google.errorprone:error_prone_core:2.33.0", + "org.conscrypt:conscrypt-openjdk-uber:2.5.2", + ], + repositories = [ + "https://repo1.maven.org/maven2", + "https://maven.google.com", + ], + use_starlark_android_rules = True, + aar_import_bzl_label = "@rules_android//rules:rules.bzl", + ) + maven_install( + name = "android_ide_common_30_1_3", + aar_import_bzl_label = "@rules_android//rules:rules.bzl", + artifacts = [ + "com.android.tools.layoutlib:layoutlib-api:30.1.3", + "com.android.tools.build:manifest-merger:30.1.3", + "com.android.tools:common:30.1.3", + "com.android.tools:repository:30.1.3", + "com.android.tools.analytics-library:protos:30.1.3", + "com.android.tools.analytics-library:shared:30.1.3", + "com.android.tools.analytics-library:tracker:30.1.3", + "com.android.tools:annotations:30.1.3", + "com.android.tools:sdk-common:30.1.3", + "com.android.tools.build:builder:7.1.3", + "com.android.tools.build:builder-model:7.1.3", + "com.google.protobuf:protobuf-java:4.33.4", + "com.google.protobuf:protobuf-java-util:4.33.4", + ], + repositories = [ + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], + use_starlark_android_rules = True, + ) + maven_install( + name = "bazel_worker_maven", + artifacts = [ + "com.google.code.gson:gson:2.10.1", + "com.google.errorprone:error_prone_annotations:2.23.0", + "com.google.guava:guava:33.0.0-jre", + "com.google.protobuf:protobuf-java:4.33.4", + "com.google.protobuf:protobuf-java-util:4.33.4", + "junit:junit:4.13.2", + "org.mockito:mockito-core:5.4.0", + "com.google.truth:truth:1.4.0", + ], + aar_import_bzl_label = "@rules_android//rules:rules.bzl", + repositories = [ + "https://repo1.maven.org/maven2", + "https://maven.google.com", + ], + ) maven_install( artifacts = [ "com.google.code.findbugs:jsr305:3.0.2", @@ -67,9 +170,10 @@ def kotlin_dependencies(extra_maven_dependencies = []): # Java Proto Lite "com.google.protobuf:protobuf-javalite:4.33.1", # Kotlin - "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21", - "org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21", - "org.jetbrains.kotlin:kotlin-stdlib:1.6.21", + "org.jetbrains:annotations:23.0.0", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.23", + "org.jetbrains.kotlin:kotlin-stdlib-common:1.9.23", + "org.jetbrains.kotlin:kotlin-stdlib:1.9.23", "androidx.recyclerview:recyclerview:1.1.0", "androidx.core:core:1.3.2", # Dokka @@ -107,6 +211,34 @@ def kotlin_dependencies(extra_maven_dependencies = []): rules_proto_dependencies() rules_proto_toolchains() + go_repository( + name = "com_github_google_go_cmp", + importpath = "github.com/google/go-cmp", + sum = "h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=", + version = "v0.5.9", + ) + go_repository( + name = "org_golang_x_sync", + importpath = "golang.org/x/sync", + sum = "h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=", + version = "v0.0.0-20210220032951-036812b2e83c", + ) + go_repository( + name = "com_github_golang_glog", + importpath = "github.com/golang/glog", + version = "v1.1.2", + sum = "h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=", + ) + go_repository( + name = "org_bitbucket_creachadair_stringset", + importpath = "bitbucket.org/creachadair/stringset", + version = "v0.0.14", + sum = "h1:t1ejQyf8utS4GZV/4fM+1gvYucggZkfhb+tMobDxYOE=", + ) + + rules_shell_dependencies() + rules_shell_toolchains() + def python_dependencies(): pip_parse( name = "mobile_pip3", diff --git a/mobile/bazel/envoy_mobile_repositories.bzl b/mobile/bazel/envoy_mobile_repositories.bzl index ac6c1d1739fc7..3d8f172b7d857 100644 --- a/mobile/bazel/envoy_mobile_repositories.bzl +++ b/mobile/bazel/envoy_mobile_repositories.bzl @@ -74,14 +74,6 @@ def swift_repos(): ) def kotlin_repos(): - http_archive( - name = "rules_java", - sha256 = "c0ee60f8757f140c157fc2c7af703d819514de6e025ebf70386d38bdd85fce83", - url = "https://github.com/bazelbuild/rules_java/releases/download/7.12.3/rules_java-7.12.3.tar.gz", - patch_args = ["-p1"], - patches = ["@envoy//bazel:rules_java.patch"], - ) - http_archive( name = "rules_jvm_external", sha256 = "3afe5195069bd379373528899c03a3072f568d33bd96fe037bd43b1f590535e7", @@ -93,6 +85,8 @@ def kotlin_repos(): name = "rules_kotlin", sha256 = "3b772976fec7bdcda1d84b9d39b176589424c047eb2175bed09aac630e50af43", urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/v1.9.6/rules_kotlin-v1.9.6.tar.gz"], + patch_args = ["-p1"], + patches = ["@envoy_mobile//bazel:rules_kotlin.patch"], ) http_archive( @@ -126,9 +120,9 @@ def kotlin_repos(): def android_repos(): http_archive( name = "rules_android", - urls = ["https://github.com/bazelbuild/rules_android/archive/refs/tags/v0.1.1.zip"], - sha256 = "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", - strip_prefix = "rules_android-0.1.1", + urls = ["https://github.com/bazelbuild/rules_android/releases/download/v0.7.2/rules_android-v0.7.2.tar.gz"], + sha256 = "0da7198c7c8bac7e11e08dca3c434617b8593075858716595672e9aeefbef2a7", + strip_prefix = "rules_android-0.7.2", ) http_archive( name = "rules_android_ndk", diff --git a/mobile/bazel/rules_kotlin.patch b/mobile/bazel/rules_kotlin.patch new file mode 100644 index 0000000000000..2ed8e2fb11b34 --- /dev/null +++ b/mobile/bazel/rules_kotlin.patch @@ -0,0 +1,12 @@ +diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel +--- a/third_party/BUILD.bazel ++++ b/third_party/BUILD.bazel +@@ -29,7 +29,7 @@ + + java_import( + name = "android_sdk", +- jars = ["@bazel_tools//tools/android:android_jar"], ++ jars = ["@androidsdk//:platforms/android-30/android.jar"], + neverlink = True, + visibility = ["//visibility:public"], + ) diff --git a/mobile/ci/linux_ci_setup.sh b/mobile/ci/linux_ci_setup.sh index 78cf51f2b0674..6456983a23055 100755 --- a/mobile/ci/linux_ci_setup.sh +++ b/mobile/ci/linux_ci_setup.sh @@ -8,5 +8,5 @@ SDKMANAGER="${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" "${SDKMANAGER}" --install "platform-tools" "platforms;android-30" "${SDKMANAGER}" --uninstall "ndk-bundle" "${SDKMANAGER}" --install "ndk;26.3.11579264" -"${SDKMANAGER}" --install "build-tools;30.0.2" +"${SDKMANAGER}" --install "build-tools;35.0.0" echo "ANDROID_NDK_HOME=${ANDROID_HOME}/ndk/26.3.11579264" >> "$GITHUB_ENV" diff --git a/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java b/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java index 132b0312fcaed..019bd72ebff98 100644 --- a/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java +++ b/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java @@ -104,9 +104,9 @@ public void get_simple() throws Exception { // Do some basic stats accounting. String stats = cronvoyEngine.getEnvoyEngine().dumpStats(); Map statsMap = StatsUtils.statsToList(stats); - assertThat(statsMap.containsKey("http.hcm.downstream_rq_2xx")); - assertThat(statsMap.containsKey("http.hcm.downstream_total")); - assertThat(statsMap.containsKey("runtime.load_success")); + assertThat(statsMap).containsKey("http.hcm.downstream_rq_2xx"); + assertThat(statsMap).containsKey("http.hcm.downstream_rq_total"); + assertThat(statsMap).containsKey("runtime.load_success"); assertThat(statsMap.get("runtime.load_success")).isEqualTo("2"); } diff --git a/test/config/integration/certs/certs.sh b/test/config/integration/certs/certs.sh index 835d49a978388..d0fe9ac61dee4 100755 --- a/test/config/integration/certs/certs.sh +++ b/test/config/integration/certs/certs.sh @@ -40,7 +40,7 @@ generate_x509_cert() { generate_expired_x509_cert() { local days="${3:-730}" openssl req -new -key "${1}key.pem" -out "${1}cert.csr" -config "${1}cert.cfg" -batch -sha256 - docker run --rm -u root -v "$(pwd):/work" -w /work envoyproxy/envoy-build:gcc-v0.1.4 sh -c "apt-get update -y && apt-get install -y faketime && faketime '370 days ago' openssl x509 -req -days \"${days}\" -in \"${1}cert.csr\" -sha256 -CA \"${2}cert.pem\" -CAkey \"${2}key.pem\" -CAcreateserial -out \"${1}cert.pem\" -extensions v3_ca -extfile \"${1}cert.cfg\"" + docker run --rm -u root -v "$(pwd):/work" -w /work envoyproxy/envoy-build:gcc-v0.1.5 sh -c "apt-get update -y && apt-get install -y faketime && faketime '370 days ago' openssl x509 -req -days \"${days}\" -in \"${1}cert.csr\" -sha256 -CA \"${2}cert.pem\" -CAkey \"${2}key.pem\" -CAcreateserial -out \"${1}cert.pem\" -extensions v3_ca -extfile \"${1}cert.cfg\"" echo -e "// NOLINT(namespace-envoy)\nconstexpr char TEST_$(echo "$1" | tr "[:lower:]" "[:upper:]")_CERT_HASH[] = \"$(openssl x509 -in "${1}cert.pem" -noout -fingerprint -sha256 | cut -d"=" -f2)\";" > "${1}cert_hash.h" } diff --git a/test/extensions/common/aws/signers/sigv4_signer_corpus_test.cc b/test/extensions/common/aws/signers/sigv4_signer_corpus_test.cc index 6cc6761017b01..e2c402ce5e97e 100644 --- a/test/extensions/common/aws/signers/sigv4_signer_corpus_test.cc +++ b/test/extensions/common/aws/signers/sigv4_signer_corpus_test.cc @@ -22,8 +22,8 @@ namespace Aws { std::vector directoryListing() { std::vector directories; for (auto const& entry : std::filesystem::directory_iterator( - TestEnvironment::runfilesDirectory() + - "/external/aws-c-auth-testdata/tests/aws-signing-test-suite/v4/")) { + TestEnvironment::runfilesDirectory("aws-c-auth-testdata") + + "/tests/aws-signing-test-suite/v4/")) { directories.push_back(entry.path().string()); } return directories; diff --git a/test/extensions/common/aws/signers/sigv4a_signer_corpus_test.cc b/test/extensions/common/aws/signers/sigv4a_signer_corpus_test.cc index 9bc55a8581ead..c5cf160c30bf4 100644 --- a/test/extensions/common/aws/signers/sigv4a_signer_corpus_test.cc +++ b/test/extensions/common/aws/signers/sigv4a_signer_corpus_test.cc @@ -24,8 +24,8 @@ namespace Aws { std::vector directoryListing() { std::vector directories; for (auto const& entry : std::filesystem::directory_iterator( - TestEnvironment::runfilesDirectory() + - "/external/aws-c-auth-testdata/tests/aws-signing-test-suite/v4a")) { + TestEnvironment::runfilesDirectory("aws-c-auth-testdata") + + "/tests/aws-signing-test-suite/v4a")) { directories.push_back(entry.path().string()); } return directories; diff --git a/tools/coverage/report_generator.sh.template b/tools/coverage/report_generator.sh.template index 426c70e9cbfeb..483986e8b7aff 100755 --- a/tools/coverage/report_generator.sh.template +++ b/tools/coverage/report_generator.sh.template @@ -19,15 +19,23 @@ while [[ $# -gt 0 ]]; do shift done +# Bazel 8 no longer sets PYTHON_RUNFILES; use RUNFILES_DIR or the +# conventional